diff --git a/.github/workflows/cleanup-preview-releases.yml b/.github/workflows/cleanup-preview-releases.yml new file mode 100644 index 0000000..6d45d35 --- /dev/null +++ b/.github/workflows/cleanup-preview-releases.yml @@ -0,0 +1,78 @@ +name: Cleanup Preview Releases + +on: + pull_request: + types: [closed] + +permissions: + contents: write + pull-requests: write + +jobs: + cleanup: + runs-on: ubuntu-latest + + steps: + - name: Find and delete preview releases + uses: actions/github-script@v8 + with: + script: | + const prNumber = context.payload.pull_request.number; + + console.log(`Looking for preview releases for PR #${prNumber}...`); + + // Get all releases + const releases = await github.rest.repos.listReleases({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + }); + + let deletedCount = 0; + + // Find and delete preview releases for this PR + for (const release of releases.data) { + // Match pattern: v*-preview.{PR_NUMBER}.* + const previewPattern = new RegExp(`^v.*-preview\\.${prNumber}\\.`); + + if (previewPattern.test(release.tag_name)) { + console.log(`Deleting preview release: ${release.tag_name} (${release.name})`); + + try { + // Delete the release + await github.rest.repos.deleteRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release.id + }); + + // Delete the tag + await github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `tags/${release.tag_name}` + }); + + deletedCount++; + console.log(`โœ“ Deleted ${release.tag_name}`); + } catch (error) { + console.error(`โœ— Failed to delete ${release.tag_name}: ${error.message}`); + } + } + } + + if (deletedCount > 0) { + console.log(`\nSuccessfully deleted ${deletedCount} preview release(s)`); + + // Comment on PR if it was merged + if (context.payload.pull_request.merged) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `๐Ÿงน Cleaned up ${deletedCount} preview release(s) for this PR.` + }); + } + } else { + console.log('No preview releases found for this PR'); + } diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 8b82070..9abb632 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -15,9 +15,13 @@ on: push: tags: - 'v*-preview*' + - 'v*-preview' - 'v*-beta*' + - 'v*-beta' - 'v*-alpha*' + - 'v*-alpha' - 'v*-rc*' + - 'v*-rc' permissions: contents: write diff --git a/.github/workflows/pr-preview-release.yml b/.github/workflows/pr-preview-release.yml index a2da7b8..0d62cc0 100644 --- a/.github/workflows/pr-preview-release.yml +++ b/.github/workflows/pr-preview-release.yml @@ -139,14 +139,14 @@ jobs: cd release zip -r ../minibrew-${{ steps.version.outputs.version }}.zip minibrew/ - - name: Create draft release + - name: Create preview release id: create_release uses: softprops/action-gh-release@v2 with: tag_name: v${{ steps.version.outputs.version }} name: Preview ${{ steps.version.outputs.version }} (PR #${{ steps.pr.outputs.number }}) body_path: changelog.md - draft: true + draft: false prerelease: true files: | minibrew-${{ steps.version.outputs.version }}.zip diff --git a/custom_components/minibrew/config_flow.py b/custom_components/minibrew/config_flow.py index 387b9bd..d1b968e 100644 --- a/custom_components/minibrew/config_flow.py +++ b/custom_components/minibrew/config_flow.py @@ -51,9 +51,8 @@ async def _handle_user_input(self, user_input: dict) -> FlowResult: except ConnectionError: return self._show_user_form(errors={"base": "cannot_connect"}) - except ConnectionError: - return self._show_user_form(errors={"host": "cannot_connect"}) - except RuntimeError: + except Exception as err: + _LOGGER.error("Unexpected error: %s", err) return self._show_user_form(errors={"base": "unknown_error"}) @@ -75,14 +74,11 @@ def _is_existing_entry(self, unique_id: str) -> bool: @staticmethod @callback def async_get_options_flow(config_entry): - return PymbrewClientOptionsFlowHandler(config_entry) # Implement options flow if needed + return PymbrewClientOptionsFlowHandler() class PymbrewClientOptionsFlowHandler(config_entries.OptionsFlow): """Handle options flow for PymbrewClient.""" - def __init__(self, config_entry): - self.config_entry = config_entry - async def async_step_init(self, user_input=None): """Manage the options.""" if user_input is not None: diff --git a/custom_components/minibrew/sensor.py b/custom_components/minibrew/sensor.py index 99e5b98..7bde6c0 100644 --- a/custom_components/minibrew/sensor.py +++ b/custom_components/minibrew/sensor.py @@ -144,6 +144,8 @@ def _get_latest_device(self): class CraftSensorBrewStageSensor(CraftSensor): """Sensor for the current brew stage of the Craft device.""" + _attr_translation_key = "brew_stage" + @property def name(self): """Return the name of the sensor.""" @@ -169,6 +171,8 @@ def unique_id(self): class CraftSensorCurrentTemperatureSensor(CraftSensor): """Sensor for the current temperature of the Craft device.""" + _attr_translation_key = "current_temperature" + @property def name(self): """Return the name of the sensor.""" @@ -205,6 +209,8 @@ def unique_id(self): class CraftSensorTargetTemperatureSensor(CraftSensor): """Sensor for the target temperature of the Craft device.""" + _attr_translation_key = "target_temperature" + @property def name(self): """Return the name of the sensor.""" @@ -241,6 +247,8 @@ def unique_id(self): class CraftSensorOnlineStatusSensor(CraftSensor): """Sensor for the online status of the Craft device.""" + _attr_translation_key = "cloud_connection" + @property def name(self): """Return the name of the sensor.""" @@ -250,7 +258,7 @@ def name(self): def native_value(self): """Return the online status.""" device = self._get_latest_device() - return "Online" if device and device.get("online") else "Offline" + return "online" if device and device.get("online") else "offline" @property def entity_category(self): @@ -271,6 +279,8 @@ def unique_id(self): class CraftSensorIsUpdatingSensor(CraftSensor): """Sensor for the update status of the Craft device.""" + _attr_translation_key = "update_status" + @property def name(self): """Return the name of the sensor.""" @@ -280,7 +290,7 @@ def name(self): def native_value(self): """Return the update status.""" device = self._get_latest_device() - return "Updating" if device and device.get("updating") else "Not Updating" + return "updating" if device and device.get("updating") else "not_updating" @property def entity_category(self): @@ -300,6 +310,8 @@ def unique_id(self): class CraftUserActionRequiredSensor(CraftSensor): """Sensor for user action required status of the Craft device.""" + _attr_translation_key = "user_action_required" + @property def name(self): """Return the name of the sensor.""" @@ -310,15 +322,15 @@ def native_value(self): """Return the user action required status.""" device = self._get_latest_device() if not device: - return "Unknown" + return "unknown" action = device.get("user_action") if action != 0 and action is not None: - return "Action Required" + return "action_required" elif action == 0: - return "No Action Required" - return "Unknown" + return "no_action_required" + return "unknown" @property @@ -346,6 +358,8 @@ def unique_id(self): class CraftSensorCurrentStageSensor(CraftSensor): """Sensor for the current stage of the Craft device.""" + _attr_translation_key = "current_stage" + @property def name(self): """Return the name of the sensor.""" @@ -354,19 +368,12 @@ def name(self): @property def native_value(self): """Return a human-readable phase name based on the device's group.""" - phase_map = { - "brew_clean_idle": "Clean and Ready to Brew", - "fermenting": "Fermenting", - "serving": "Serving", - "brew_acid_clean_idle": "Ready to Clean" - } - for group_name, devices in self.coordinator.data.__dict__.items(): for dev in devices: if dev.get("serial_number") == self.device_id: - return phase_map.get(group_name, group_name) # fallback to raw name + return group_name - return "Unknown" + return "unknown" @property def icon(self): @@ -381,6 +388,8 @@ def unique_id(self): class CraftSensorTimeInStageSensor(CraftSensor): """Sensor for the time spent in the current stage of the Craft device.""" + _attr_translation_key = "time_in_stage" + @property def name(self): """Return the name of the sensor.""" @@ -416,6 +425,8 @@ def unique_id(self): class CraftSensorNeedsCleaningSensor(CraftSensor): """Sensor for the cleaning status of the Craft device.""" + _attr_translation_key = "needs_cleaning" + @property def name(self): """Return the name of the sensor.""" @@ -425,7 +436,7 @@ def name(self): def native_value(self): """Return the cleaning status.""" device = self._get_latest_device() - return "Needs Cleaning" if device and device.get("needs_acid_cleaning") else "Clean" + return "needs_cleaning" if device and device.get("needs_acid_cleaning") else "clean" @property def entity_category(self): @@ -492,6 +503,8 @@ def _get_latest_device(self): class KegCurrentTemperatureSensor(KegSensor): """Sensor for the current temperature of the Keg device.""" + _attr_translation_key = "temperature" + @property def name(self): """Return the name of the sensor.""" @@ -527,6 +540,8 @@ def unique_id(self): class KegTargetTemperatureSensor(KegSensor): """Sensor for the target temperature of the Keg device.""" + _attr_translation_key = "target_temperature" + @property def name(self): """Return the name of the sensor.""" @@ -562,6 +577,8 @@ def unique_id(self): class KegBeerStyleSensor(KegSensor): """Sensor for the beer style of the Keg device.""" + _attr_translation_key = "beer_style" + @property def name(self): """Return the name of the sensor.""" @@ -582,6 +599,8 @@ def unique_id(self): class KegBeerNameSensor(KegSensor): """Sensor for the beer name of the Keg device.""" + _attr_translation_key = "beer_name" + @property def name(self): """Return the name of the sensor.""" @@ -607,6 +626,8 @@ def unique_id(self): class KegOnlineStatusSensor(KegSensor): """Sensor for the online status of the Keg device.""" + _attr_translation_key = "cloud_connection" + @property def name(self): """Return the name of the sensor.""" @@ -616,7 +637,7 @@ def name(self): def native_value(self): """Return the online status.""" device = self._get_latest_device() - return "Online" if device and device.get("online") else "Offline" + return "online" if device and device.get("online") else "offline" @property def entity_category(self): @@ -637,6 +658,8 @@ def unique_id(self): class KegIsUpdatingSensor(KegSensor): """Sensor for the update status of the Keg device.""" + _attr_translation_key = "update_status" + @property def name(self): """Return the name of the sensor.""" @@ -646,7 +669,7 @@ def name(self): def native_value(self): """Return the update status.""" device = self._get_latest_device() - return "Updating" if device and device.get("updating") else "Not Updating" + return "updating" if device and device.get("updating") else "not_updating" @property def entity_category(self): @@ -667,6 +690,8 @@ def unique_id(self): class KegNeedsCleaningSensor(KegSensor): """Sensor for the cleaning status of the Keg device.""" + _attr_translation_key = "needs_cleaning" + @property def name(self): """Return the name of the sensor.""" @@ -676,7 +701,7 @@ def name(self): def native_value(self): """Return the cleaning status.""" device = self._get_latest_device() - return "Needs Cleaning" if device and device.get("needs_acid_cleaning") else "Clean" + return "needs_cleaning" if device and device.get("needs_acid_cleaning") else "clean" @property def entity_category(self): @@ -696,6 +721,8 @@ def unique_id(self): class KegActionRequiredSensor(KegSensor): """Sensor for user action required status of the Keg device.""" + _attr_translation_key = "user_action_required" + @property def name(self): """Return the name of the sensor.""" @@ -706,15 +733,15 @@ def native_value(self): """Return the user action required status.""" device = self._get_latest_device() if not device: - return "Unknown" + return "unknown" action = device.get("user_action") if action != 0 and action is not None: - return "Action Required" + return "action_required" elif action == 0: - return "No Action Required" - return "Unknown" + return "no_action_required" + return "unknown" @property diff --git a/custom_components/minibrew/strings.json b/custom_components/minibrew/strings.json new file mode 100644 index 0000000..0b2d26b --- /dev/null +++ b/custom_components/minibrew/strings.json @@ -0,0 +1,97 @@ +{ + "config": { + "step": { + "user": { + "title": "Set up MiniBrew", + "description": "Enter your MiniBrew credentials to connect to your devices.", + "data": { + "username": "Username", + "password": "Password" + } + } + }, + "error": { + "cannot_connect": "Failed to connect to MiniBrew. Please check your credentials and try again.", + "no_devices_found": "No MiniBrew devices found in your account.", + "unknown_error": "An unexpected error occurred. Please try again." + }, + "abort": { + "already_configured": "This MiniBrew account is already configured." + } + }, + "options": { + "step": { + "init": { + "title": "MiniBrew Options", + "description": "Configure how often the integration updates data from your MiniBrew devices.", + "data": { + "refresh_interval": "Update interval (seconds)" + } + } + } + }, + "entity": { + "sensor": { + "brew_stage": { + "name": "Brew stage" + }, + "current_temperature": { + "name": "Current temperature" + }, + "target_temperature": { + "name": "Target temperature" + }, + "cloud_connection": { + "name": "Cloud connection", + "state": { + "online": "Online", + "offline": "Offline" + } + }, + "update_status": { + "name": "Update status", + "state": { + "updating": "Updating", + "not_updating": "Not updating" + } + }, + "user_action_required": { + "name": "User action required", + "state": { + "action_required": "Action required", + "no_action_required": "No action required", + "unknown": "Unknown" + } + }, + "current_stage": { + "name": "Current stage", + "state": { + "brew_clean_idle": "Clean and ready to brew", + "fermenting": "Fermenting", + "serving": "Serving", + "brew_acid_clean_idle": "Ready to clean", + "unknown": "Unknown" + } + }, + "time_in_stage": { + "name": "Time in stage" + }, + "needs_cleaning": { + "name": "Needs cleaning", + "state": { + "needs_cleaning": "Needs cleaning", + "clean": "Clean" + } + }, + "temperature": { + "name": "Temperature" + }, + "beer_style": { + "name": "Beer style" + }, + "beer_name": { + "name": "Beer name" + } + } + } +} diff --git a/custom_components/minibrew/translations/en.json b/custom_components/minibrew/translations/en.json new file mode 100644 index 0000000..0b2d26b --- /dev/null +++ b/custom_components/minibrew/translations/en.json @@ -0,0 +1,97 @@ +{ + "config": { + "step": { + "user": { + "title": "Set up MiniBrew", + "description": "Enter your MiniBrew credentials to connect to your devices.", + "data": { + "username": "Username", + "password": "Password" + } + } + }, + "error": { + "cannot_connect": "Failed to connect to MiniBrew. Please check your credentials and try again.", + "no_devices_found": "No MiniBrew devices found in your account.", + "unknown_error": "An unexpected error occurred. Please try again." + }, + "abort": { + "already_configured": "This MiniBrew account is already configured." + } + }, + "options": { + "step": { + "init": { + "title": "MiniBrew Options", + "description": "Configure how often the integration updates data from your MiniBrew devices.", + "data": { + "refresh_interval": "Update interval (seconds)" + } + } + } + }, + "entity": { + "sensor": { + "brew_stage": { + "name": "Brew stage" + }, + "current_temperature": { + "name": "Current temperature" + }, + "target_temperature": { + "name": "Target temperature" + }, + "cloud_connection": { + "name": "Cloud connection", + "state": { + "online": "Online", + "offline": "Offline" + } + }, + "update_status": { + "name": "Update status", + "state": { + "updating": "Updating", + "not_updating": "Not updating" + } + }, + "user_action_required": { + "name": "User action required", + "state": { + "action_required": "Action required", + "no_action_required": "No action required", + "unknown": "Unknown" + } + }, + "current_stage": { + "name": "Current stage", + "state": { + "brew_clean_idle": "Clean and ready to brew", + "fermenting": "Fermenting", + "serving": "Serving", + "brew_acid_clean_idle": "Ready to clean", + "unknown": "Unknown" + } + }, + "time_in_stage": { + "name": "Time in stage" + }, + "needs_cleaning": { + "name": "Needs cleaning", + "state": { + "needs_cleaning": "Needs cleaning", + "clean": "Clean" + } + }, + "temperature": { + "name": "Temperature" + }, + "beer_style": { + "name": "Beer style" + }, + "beer_name": { + "name": "Beer name" + } + } + } +} diff --git a/hacs.json b/hacs.json index fe5da08..bcc1899 100644 --- a/hacs.json +++ b/hacs.json @@ -1,3 +1,3 @@ { - "name": "Minibrew" + "name": "MiniBrew" } \ No newline at end of file