From 514f83cc96adf6f150d858226050e575397b2dd6 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 08:12:30 +0200 Subject: [PATCH 01/24] Use common state for "Auto" in `reolink` (#142971) The common state replaces the internal references, too. --- homeassistant/components/reolink/strings.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index 10b4a07f971..8b7d276a9e3 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -652,7 +652,7 @@ "name": "Floodlight mode", "state": { "off": "[%key:common::state::off%]", - "auto": "[%key:component::reolink::entity::select::day_night_mode::state::auto%]", + "auto": "[%key:common::state::auto%]", "onatnight": "On at night", "schedule": "Schedule", "adaptive": "Adaptive", @@ -662,7 +662,7 @@ "day_night_mode": { "name": "Day night mode", "state": { - "auto": "Auto", + "auto": "[%key:common::state::auto%]", "color": "Color", "blackwhite": "Black & white" } @@ -691,7 +691,7 @@ "name": "Doorbell LED", "state": { "stayoff": "Stay off", - "auto": "[%key:component::reolink::entity::select::day_night_mode::state::auto%]", + "auto": "[%key:common::state::auto%]", "alwaysonatnight": "Auto & always on at night", "always": "Always on", "alwayson": "Always on" @@ -702,7 +702,7 @@ "state": { "off": "[%key:common::state::off%]", "on": "[%key:common::state::on%]", - "auto": "[%key:component::reolink::entity::select::day_night_mode::state::auto%]" + "auto": "[%key:common::state::auto%]" } }, "binning_mode": { @@ -710,7 +710,7 @@ "state": { "off": "[%key:common::state::off%]", "on": "[%key:common::state::on%]", - "auto": "[%key:component::reolink::entity::select::day_night_mode::state::auto%]" + "auto": "[%key:common::state::auto%]" } }, "hub_alarm_ringtone": { From 33a0db39353e6262e2a40c0bd9edab1c10889c1c Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 08:16:27 +0200 Subject: [PATCH 02/24] Use common state for "Auto" and fix sentence-casing in `plugwise` (#142970) --- homeassistant/components/plugwise/strings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/plugwise/strings.json b/homeassistant/components/plugwise/strings.json index 344cee66d68..d26e70d1c4f 100644 --- a/homeassistant/components/plugwise/strings.json +++ b/homeassistant/components/plugwise/strings.json @@ -23,7 +23,7 @@ }, "data_description": { "password": "The Smile ID printed on the label on the back of your Adam, Smile-T, or P1.", - "host": "The hostname or IP-address of your Smile. You can find it in your router or the Plugwise App.", + "host": "The hostname or IP-address of your Smile. You can find it in your router or the Plugwise app.", "port": "By default your Smile uses port 80, normally you should not have to change this.", "username": "Default is `smile`, or `stretch` for the legacy Stretch." } @@ -113,7 +113,7 @@ "name": "DHW mode", "state": { "off": "[%key:common::state::off%]", - "auto": "Auto", + "auto": "[%key:common::state::auto%]", "boost": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::boost%]", "comfort": "[%key:component::climate::entity_component::_::state_attributes::preset_mode::state::comfort%]" } From 254d4c65347de14b8ce8b2a03438e2ca5f63ab5a Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 08:17:03 +0200 Subject: [PATCH 03/24] Use common state for "Auto" and fix sentence-casing in `tado` (#142969) --- homeassistant/components/tado/strings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tado/strings.json b/homeassistant/components/tado/strings.json index 53de3969998..5d9c4237be8 100644 --- a/homeassistant/components/tado/strings.json +++ b/homeassistant/components/tado/strings.json @@ -53,7 +53,7 @@ "state_attributes": { "preset_mode": { "state": { - "auto": "Auto" + "auto": "[%key:common::state::auto%]" } } } @@ -139,7 +139,7 @@ "description": "Adds a meter reading to Tado Energy IQ.", "fields": { "config_entry": { - "name": "Config Entry", + "name": "Config entry", "description": "Config entry to add meter reading to." }, "reading": { From cdd8ba78e76e9057d54b00f4c64ea57f7d433076 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 08:18:08 +0200 Subject: [PATCH 04/24] Use common state for "Auto" in `climate` (#142948) --- homeassistant/components/climate/strings.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/climate/strings.json b/homeassistant/components/climate/strings.json index 298f953d2c7..250b2a67efe 100644 --- a/homeassistant/components/climate/strings.json +++ b/homeassistant/components/climate/strings.json @@ -28,10 +28,10 @@ "name": "Thermostat", "state": { "off": "[%key:common::state::off%]", + "auto": "[%key:common::state::auto%]", "heat": "Heat", "cool": "Cool", "heat_cool": "Heat/Cool", - "auto": "Auto", "dry": "Dry", "fan_only": "Fan only" }, @@ -50,7 +50,7 @@ "state": { "off": "[%key:common::state::off%]", "on": "[%key:common::state::on%]", - "auto": "Auto", + "auto": "[%key:common::state::auto%]", "low": "[%key:common::state::low%]", "medium": "[%key:common::state::medium%]", "high": "[%key:common::state::high%]", @@ -69,13 +69,13 @@ "hvac_action": { "name": "Current action", "state": { + "off": "[%key:common::state::off%]", + "idle": "[%key:common::state::idle%]", "cooling": "Cooling", "defrosting": "Defrosting", "drying": "Drying", "fan": "Fan", "heating": "Heating", - "idle": "[%key:common::state::idle%]", - "off": "[%key:common::state::off%]", "preheating": "Preheating" } }, @@ -258,7 +258,7 @@ "hvac_mode": { "options": { "off": "[%key:common::state::off%]", - "auto": "Auto", + "auto": "[%key:common::state::auto%]", "cool": "Cool", "dry": "Dry", "fan_only": "Fan only", From a9d4b1afe4555e74c7c52bf101b8cdb0077b8d53 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 14 Apr 2025 20:19:48 -1000 Subject: [PATCH 05/24] Bump zeroconf to 0.146.5 (#142962) --- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- pyproject.toml | 2 +- requirements.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index a7fbfdfeada..e2637d792e2 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "loggers": ["zeroconf"], "quality_scale": "internal", - "requirements": ["zeroconf==0.146.0"] + "requirements": ["zeroconf==0.146.5"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index cf46982af78..30b7718bad4 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -75,7 +75,7 @@ voluptuous-serialize==2.6.0 voluptuous==0.15.2 webrtc-models==0.3.0 yarl==1.19.0 -zeroconf==0.146.0 +zeroconf==0.146.5 # Constrain pycryptodome to avoid vulnerability # see https://github.com/home-assistant/core/pull/16238 diff --git a/pyproject.toml b/pyproject.toml index 6d28c0b9deb..c66f8ba6363 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,7 @@ dependencies = [ "voluptuous-openapi==0.0.6", "yarl==1.19.0", "webrtc-models==0.3.0", - "zeroconf==0.146.0", + "zeroconf==0.146.5", ] [project.urls] diff --git a/requirements.txt b/requirements.txt index b771b7f38b8..40200563ec1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,4 +60,4 @@ voluptuous-serialize==2.6.0 voluptuous-openapi==0.0.6 yarl==1.19.0 webrtc-models==0.3.0 -zeroconf==0.146.0 +zeroconf==0.146.5 diff --git a/requirements_all.txt b/requirements_all.txt index 0f097916061..f8fa274cb28 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -3152,7 +3152,7 @@ zabbix-utils==2.0.2 zamg==0.3.6 # homeassistant.components.zeroconf -zeroconf==0.146.0 +zeroconf==0.146.5 # homeassistant.components.zeversolar zeversolar==0.3.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e07d1976896..f8f9a006314 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2548,7 +2548,7 @@ yt-dlp[default]==2025.03.26 zamg==0.3.6 # homeassistant.components.zeroconf -zeroconf==0.146.0 +zeroconf==0.146.5 # homeassistant.components.zeversolar zeversolar==0.3.2 From 942bf2ef783f6c8a7dad738b9b5be45186577228 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 08:45:37 +0200 Subject: [PATCH 06/24] Use common state for "Auto" in `lg_thinq` (#142973) --- homeassistant/components/lg_thinq/strings.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/lg_thinq/strings.json b/homeassistant/components/lg_thinq/strings.json index 525a594f748..f609be91de5 100644 --- a/homeassistant/components/lg_thinq/strings.json +++ b/homeassistant/components/lg_thinq/strings.json @@ -123,7 +123,7 @@ "mid": "[%key:common::state::medium%]", "high": "[%key:common::state::high%]", "power": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::high%]", - "auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]" + "auto": "[%key:common::state::auto%]" } }, "preset_mode": { @@ -343,7 +343,7 @@ "growth_mode": { "name": "Mode", "state": { - "standard": "Auto", + "standard": "[%key:common::state::auto%]", "ext_leaf": "Vegetables", "ext_herb": "Herbs", "ext_flower": "Flowers", @@ -353,7 +353,7 @@ "growth_mode_for_location": { "name": "{location} mode", "state": { - "standard": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]", + "standard": "[%key:common::state::auto%]", "ext_leaf": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::ext_leaf%]", "ext_herb": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::ext_herb%]", "ext_flower": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::ext_flower%]", @@ -581,7 +581,7 @@ "name": "[%key:component::lg_thinq::entity::binary_sensor::one_touch_filter::name%]", "state": { "off": "[%key:common::state::off%]", - "auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]", + "auto": "[%key:common::state::auto%]", "power": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::high%]", "replace": "Replace filter", "smart_power": "Smart safe storage", @@ -599,7 +599,7 @@ "name": "Operating mode", "state": { "air_clean": "Purify", - "auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]", + "auto": "[%key:common::state::auto%]", "clothes_dry": "Laundry", "edge": "Edge cleaning", "heat_pump": "Heat pump", @@ -649,7 +649,7 @@ "current_dish_washing_course": { "name": "Current cycle", "state": { - "auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]", + "auto": "[%key:common::state::auto%]", "heavy": "Intensive", "delicate": "Delicate", "turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]", @@ -881,7 +881,7 @@ "high": "[%key:common::state::high%]", "power": "Turbo", "turbo": "[%key:component::lg_thinq::entity::select::wind_strength::state::power%]", - "auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]", + "auto": "[%key:common::state::auto%]", "wind_1": "Step 1", "wind_2": "Step 2", "wind_3": "Step 3", @@ -905,7 +905,7 @@ "name": "Operating mode", "state": { "air_clean": "Purifying", - "auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]", + "auto": "[%key:common::state::auto%]", "baby_care": "[%key:component::lg_thinq::entity::sensor::personalization_mode::state::baby%]", "circulator": "Booster", "clean": "Single", @@ -1016,7 +1016,7 @@ "name": "[%key:component::lg_thinq::entity::binary_sensor::one_touch_filter::name%]", "state": { "off": "[%key:common::state::off%]", - "auto": "[%key:component::lg_thinq::entity::sensor::growth_mode::state::standard%]", + "auto": "[%key:common::state::auto%]", "power": "[%key:component::lg_thinq::entity::sensor::current_job_mode::state::high%]", "replace": "[%key:component::lg_thinq::entity::sensor::fresh_air_filter::state::replace%]", "smart_power": "[%key:component::lg_thinq::entity::sensor::fresh_air_filter::state::smart_power%]", From fa81a83893828ca6185d175306dae1ffa8a9418d Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 15 Apr 2025 09:55:16 +0200 Subject: [PATCH 07/24] Fix switch state for Comelit (#142978) --- homeassistant/components/comelit/switch.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/comelit/switch.py b/homeassistant/components/comelit/switch.py index 9c9f6b747d4..658f37f70af 100644 --- a/homeassistant/components/comelit/switch.py +++ b/homeassistant/components/comelit/switch.py @@ -75,4 +75,7 @@ class ComelitSwitchEntity(ComelitBridgeBaseEntity, SwitchEntity): @property def is_on(self) -> bool: """Return True if switch is on.""" - return self.coordinator.data[OTHER][self._device.index].status == STATE_ON + return ( + self.coordinator.data[self._device.type][self._device.index].status + == STATE_ON + ) From b49a60fa3e9d439ab046b93eb5ffaa644bc95e8f Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 09:56:40 +0200 Subject: [PATCH 08/24] Use common state for "Auto" in `roborock` (#142972) Also moved the "off" state to the top to group the common states a bit. --- homeassistant/components/roborock/strings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/roborock/strings.json b/homeassistant/components/roborock/strings.json index d27f4064170..0f36fbec3d5 100644 --- a/homeassistant/components/roborock/strings.json +++ b/homeassistant/components/roborock/strings.json @@ -426,11 +426,11 @@ "state_attributes": { "fan_speed": { "state": { - "auto": "Auto", + "off": "[%key:common::state::off%]", + "auto": "[%key:common::state::auto%]", "balanced": "Balanced", "custom": "[%key:component::roborock::entity::select::mop_mode::state::custom%]", "gentle": "Gentle", - "off": "[%key:common::state::off%]", "max": "[%key:component::roborock::entity::select::mop_intensity::state::max%]", "max_plus": "Max plus", "medium": "[%key:common::state::medium%]", From 18feb4bb819970af6da325184dd01553bda49328 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 10:40:12 +0200 Subject: [PATCH 09/24] Bump codecov/codecov-action from 5.4.0 to 5.4.2 (#142974) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.0 to 5.4.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.4.0...v5.4.2) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.4.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6fc1fdbca1c..d8fdda601dd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1317,7 +1317,7 @@ jobs: pattern: coverage-* - name: Upload coverage to Codecov if: needs.info.outputs.test_full_suite == 'true' - uses: codecov/codecov-action@v5.4.0 + uses: codecov/codecov-action@v5.4.2 with: fail_ci_if_error: true flags: full-suite @@ -1459,7 +1459,7 @@ jobs: pattern: coverage-* - name: Upload coverage to Codecov if: needs.info.outputs.test_full_suite == 'false' - uses: codecov/codecov-action@v5.4.0 + uses: codecov/codecov-action@v5.4.2 with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} From f2fa58310170b07a5cda28e76bb16d4b38d36a47 Mon Sep 17 00:00:00 2001 From: cdheiser <10488026+cdheiser@users.noreply.github.com> Date: Tue, 15 Apr 2025 01:41:56 -0700 Subject: [PATCH 10/24] Bump lutron's dependency on pylutron to 0.2.17 (#142953) * Bump lutron's dependency on pylutron to 0.2.17 This fixes https://github.com/home-assistant/core/issues/127672 * Bump to pylutron 0.2.18 (0.2.17 has a bug with smartquotes that 0.2.18 fixes) --------- Co-authored-by: cdheiser --- homeassistant/components/lutron/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/lutron/manifest.json b/homeassistant/components/lutron/manifest.json index 82bdfad4774..8d3da47795a 100644 --- a/homeassistant/components/lutron/manifest.json +++ b/homeassistant/components/lutron/manifest.json @@ -6,6 +6,6 @@ "documentation": "https://www.home-assistant.io/integrations/lutron", "iot_class": "local_polling", "loggers": ["pylutron"], - "requirements": ["pylutron==0.2.16"], + "requirements": ["pylutron==0.2.18"], "single_config_entry": true } diff --git a/requirements_all.txt b/requirements_all.txt index f8fa274cb28..330d7ebcc47 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2113,7 +2113,7 @@ pylitterbot==2024.0.0 pylutron-caseta==0.24.0 # homeassistant.components.lutron -pylutron==0.2.16 +pylutron==0.2.18 # homeassistant.components.mailgun pymailgunner==1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f8f9a006314..8e0ecf5641b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1728,7 +1728,7 @@ pylitterbot==2024.0.0 pylutron-caseta==0.24.0 # homeassistant.components.lutron -pylutron==0.2.16 +pylutron==0.2.18 # homeassistant.components.mailgun pymailgunner==1.4 From 759d8a3f90e974d6d6a24105289923d14fe5060b Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 15 Apr 2025 12:07:48 +0200 Subject: [PATCH 11/24] Code optimization for UptimeRobot binary (#142986) --- homeassistant/components/uptimerobot/binary_sensor.py | 2 +- homeassistant/components/uptimerobot/entity.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/homeassistant/components/uptimerobot/binary_sensor.py b/homeassistant/components/uptimerobot/binary_sensor.py index f14d6d93d71..e8803b6ad89 100644 --- a/homeassistant/components/uptimerobot/binary_sensor.py +++ b/homeassistant/components/uptimerobot/binary_sensor.py @@ -43,4 +43,4 @@ class UptimeRobotBinarySensor(UptimeRobotEntity, BinarySensorEntity): @property def is_on(self) -> bool: """Return True if the entity is on.""" - return self.monitor_available + return bool(self.monitor.status == 2) diff --git a/homeassistant/components/uptimerobot/entity.py b/homeassistant/components/uptimerobot/entity.py index 71f7a2f1c00..a27d4a6f80e 100644 --- a/homeassistant/components/uptimerobot/entity.py +++ b/homeassistant/components/uptimerobot/entity.py @@ -59,8 +59,3 @@ class UptimeRobotEntity(CoordinatorEntity[UptimeRobotDataUpdateCoordinator]): ), self._monitor, ) - - @property - def monitor_available(self) -> bool: - """Returtn if the monitor is available.""" - return bool(self.monitor.status == 2) From 2074c7fcee36c61e319d147f3ede971d993c9eab Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 15 Apr 2025 15:03:47 +0200 Subject: [PATCH 12/24] Bump reolink-aio to 0.13.2 (#142985) --- homeassistant/components/reolink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/reolink/manifest.json b/homeassistant/components/reolink/manifest.json index 9105dfda66f..59a2741571f 100644 --- a/homeassistant/components/reolink/manifest.json +++ b/homeassistant/components/reolink/manifest.json @@ -19,5 +19,5 @@ "iot_class": "local_push", "loggers": ["reolink_aio"], "quality_scale": "platinum", - "requirements": ["reolink-aio==0.13.1"] + "requirements": ["reolink-aio==0.13.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 330d7ebcc47..0a814575271 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2633,7 +2633,7 @@ renault-api==0.2.9 renson-endura-delta==1.7.2 # homeassistant.components.reolink -reolink-aio==0.13.1 +reolink-aio==0.13.2 # homeassistant.components.idteck_prox rfk101py==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8e0ecf5641b..82575692d08 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2137,7 +2137,7 @@ renault-api==0.2.9 renson-endura-delta==1.7.2 # homeassistant.components.reolink -reolink-aio==0.13.1 +reolink-aio==0.13.2 # homeassistant.components.rflink rflink==0.0.66 From 595508bf7dfbc506faecf1a043ed14f2008cb6fb Mon Sep 17 00:00:00 2001 From: Brian Choromanski Date: Tue, 15 Apr 2025 09:50:11 -0400 Subject: [PATCH 13/24] Check that time_pattern interval matcher is not zero (#142630) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: AbĂ­lio Costa Co-authored-by: Franck Nijhof --- homeassistant/components/homeassistant/triggers/time_pattern.py | 2 ++ tests/components/homeassistant/triggers/test_time_pattern.py | 1 + 2 files changed, 3 insertions(+) diff --git a/homeassistant/components/homeassistant/triggers/time_pattern.py b/homeassistant/components/homeassistant/triggers/time_pattern.py index df49a79bcb6..14096d87277 100644 --- a/homeassistant/components/homeassistant/triggers/time_pattern.py +++ b/homeassistant/components/homeassistant/triggers/time_pattern.py @@ -37,6 +37,8 @@ class TimePattern: if isinstance(value, str) and value.startswith("/"): number = int(value[1:]) + if number == 0: + raise vol.Invalid(f"must be a value between 1 and {self.maximum}") else: value = number = int(value) diff --git a/tests/components/homeassistant/triggers/test_time_pattern.py b/tests/components/homeassistant/triggers/test_time_pattern.py index ffce8cd476b..2e7fa9dae08 100644 --- a/tests/components/homeassistant/triggers/test_time_pattern.py +++ b/tests/components/homeassistant/triggers/test_time_pattern.py @@ -365,6 +365,7 @@ async def test_invalid_schemas() -> None: {"platform": "time_pattern", "minutes": "/"}, {"platform": "time_pattern", "minutes": "*/5"}, {"platform": "time_pattern", "minutes": "/90"}, + {"platform": "time_pattern", "hours": "/0", "minutes": 10}, {"platform": "time_pattern", "hours": 12, "minutes": 0, "seconds": 100}, ) From 285f7ec6963e9149b164120797a67facc90b4826 Mon Sep 17 00:00:00 2001 From: Sid <27780930+autinerd@users.noreply.github.com> Date: Tue, 15 Apr 2025 16:45:56 +0200 Subject: [PATCH 14/24] Add number platform to eheimdigital (#142835) * Add number platform to eheimdigital * Pylint * Review * Update homeassistant/components/eheimdigital/number.py * Update homeassistant/components/eheimdigital/number.py * Review --------- Co-authored-by: Josef Zweck Co-authored-by: Joost Lekkerkerker --- .../components/eheimdigital/__init__.py | 2 +- .../components/eheimdigital/icons.json | 17 ++ .../components/eheimdigital/number.py | 177 +++++++++++ .../components/eheimdigital/strings.json | 17 ++ tests/components/eheimdigital/conftest.py | 5 + .../eheimdigital/snapshots/test_number.ambr | 286 ++++++++++++++++++ tests/components/eheimdigital/test_number.py | 189 ++++++++++++ 7 files changed, 692 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/eheimdigital/number.py create mode 100644 tests/components/eheimdigital/snapshots/test_number.ambr create mode 100644 tests/components/eheimdigital/test_number.py diff --git a/homeassistant/components/eheimdigital/__init__.py b/homeassistant/components/eheimdigital/__init__.py index e4fb7989931..77e722f3e0c 100644 --- a/homeassistant/components/eheimdigital/__init__.py +++ b/homeassistant/components/eheimdigital/__init__.py @@ -9,7 +9,7 @@ from homeassistant.helpers.device_registry import DeviceEntry from .const import DOMAIN from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator -PLATFORMS = [Platform.CLIMATE, Platform.LIGHT, Platform.SENSOR] +PLATFORMS = [Platform.CLIMATE, Platform.LIGHT, Platform.NUMBER, Platform.SENSOR] async def async_setup_entry( diff --git a/homeassistant/components/eheimdigital/icons.json b/homeassistant/components/eheimdigital/icons.json index 32f3f1eee9c..428e383dd83 100644 --- a/homeassistant/components/eheimdigital/icons.json +++ b/homeassistant/components/eheimdigital/icons.json @@ -1,5 +1,22 @@ { "entity": { + "number": { + "manual_speed": { + "default": "mdi:pump" + }, + "day_speed": { + "default": "mdi:weather-sunny" + }, + "night_speed": { + "default": "mdi:moon-waning-crescent" + }, + "temperature_offset": { + "default": "mdi:thermometer" + }, + "night_temperature_offset": { + "default": "mdi:thermometer" + } + }, "sensor": { "current_speed": { "default": "mdi:pump" diff --git a/homeassistant/components/eheimdigital/number.py b/homeassistant/components/eheimdigital/number.py new file mode 100644 index 00000000000..f4504be624c --- /dev/null +++ b/homeassistant/components/eheimdigital/number.py @@ -0,0 +1,177 @@ +"""EHEIM Digital numbers.""" + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Generic, TypeVar, override + +from eheimdigital.classic_vario import EheimDigitalClassicVario +from eheimdigital.device import EheimDigitalDevice +from eheimdigital.heater import EheimDigitalHeater +from eheimdigital.types import HeaterUnit + +from homeassistant.components.number import ( + NumberDeviceClass, + NumberEntity, + NumberEntityDescription, +) +from homeassistant.const import ( + PERCENTAGE, + PRECISION_HALVES, + PRECISION_TENTHS, + PRECISION_WHOLE, + EntityCategory, + UnitOfTemperature, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from .coordinator import EheimDigitalConfigEntry, EheimDigitalUpdateCoordinator +from .entity import EheimDigitalEntity + +PARALLEL_UPDATES = 0 + +_DeviceT_co = TypeVar("_DeviceT_co", bound=EheimDigitalDevice, covariant=True) + + +@dataclass(frozen=True, kw_only=True) +class EheimDigitalNumberDescription(NumberEntityDescription, Generic[_DeviceT_co]): + """Class describing EHEIM Digital sensor entities.""" + + value_fn: Callable[[_DeviceT_co], float | None] + set_value_fn: Callable[[_DeviceT_co, float], Awaitable[None]] + uom_fn: Callable[[_DeviceT_co], str] | None = None + + +CLASSICVARIO_DESCRIPTIONS: tuple[ + EheimDigitalNumberDescription[EheimDigitalClassicVario], ... +] = ( + EheimDigitalNumberDescription[EheimDigitalClassicVario]( + key="manual_speed", + translation_key="manual_speed", + entity_category=EntityCategory.CONFIG, + native_step=PRECISION_WHOLE, + native_unit_of_measurement=PERCENTAGE, + value_fn=lambda device: device.manual_speed, + set_value_fn=lambda device, value: device.set_manual_speed(int(value)), + ), + EheimDigitalNumberDescription[EheimDigitalClassicVario]( + key="day_speed", + translation_key="day_speed", + entity_category=EntityCategory.CONFIG, + native_step=PRECISION_WHOLE, + native_unit_of_measurement=PERCENTAGE, + value_fn=lambda device: device.day_speed, + set_value_fn=lambda device, value: device.set_day_speed(int(value)), + ), + EheimDigitalNumberDescription[EheimDigitalClassicVario]( + key="night_speed", + translation_key="night_speed", + entity_category=EntityCategory.CONFIG, + native_step=PRECISION_WHOLE, + native_unit_of_measurement=PERCENTAGE, + value_fn=lambda device: device.night_speed, + set_value_fn=lambda device, value: device.set_night_speed(int(value)), + ), +) + +HEATER_DESCRIPTIONS: tuple[EheimDigitalNumberDescription[EheimDigitalHeater], ...] = ( + EheimDigitalNumberDescription[EheimDigitalHeater]( + key="temperature_offset", + translation_key="temperature_offset", + entity_category=EntityCategory.CONFIG, + native_min_value=-3, + native_max_value=3, + native_step=PRECISION_TENTHS, + device_class=NumberDeviceClass.TEMPERATURE, + uom_fn=lambda device: ( + UnitOfTemperature.CELSIUS + if device.temperature_unit is HeaterUnit.CELSIUS + else UnitOfTemperature.FAHRENHEIT + ), + value_fn=lambda device: device.temperature_offset, + set_value_fn=lambda device, value: device.set_temperature_offset(value), + ), + EheimDigitalNumberDescription[EheimDigitalHeater]( + key="night_temperature_offset", + translation_key="night_temperature_offset", + entity_category=EntityCategory.CONFIG, + native_min_value=-5, + native_max_value=5, + native_step=PRECISION_HALVES, + device_class=NumberDeviceClass.TEMPERATURE, + uom_fn=lambda device: ( + UnitOfTemperature.CELSIUS + if device.temperature_unit is HeaterUnit.CELSIUS + else UnitOfTemperature.FAHRENHEIT + ), + value_fn=lambda device: device.night_temperature_offset, + set_value_fn=lambda device, value: device.set_night_temperature_offset(value), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: EheimDigitalConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up the callbacks for the coordinator so numbers can be added as devices are found.""" + coordinator = entry.runtime_data + + def async_setup_device_entities( + device_address: dict[str, EheimDigitalDevice], + ) -> None: + """Set up the number entities for one or multiple devices.""" + entities: list[EheimDigitalNumber[EheimDigitalDevice]] = [] + for device in device_address.values(): + if isinstance(device, EheimDigitalClassicVario): + entities.extend( + EheimDigitalNumber[EheimDigitalClassicVario]( + coordinator, device, description + ) + for description in CLASSICVARIO_DESCRIPTIONS + ) + if isinstance(device, EheimDigitalHeater): + entities.extend( + EheimDigitalNumber[EheimDigitalHeater]( + coordinator, device, description + ) + for description in HEATER_DESCRIPTIONS + ) + + async_add_entities(entities) + + coordinator.add_platform_callback(async_setup_device_entities) + async_setup_device_entities(coordinator.hub.devices) + + +class EheimDigitalNumber( + EheimDigitalEntity[_DeviceT_co], NumberEntity, Generic[_DeviceT_co] +): + """Represent a EHEIM Digital number entity.""" + + entity_description: EheimDigitalNumberDescription[_DeviceT_co] + + def __init__( + self, + coordinator: EheimDigitalUpdateCoordinator, + device: _DeviceT_co, + description: EheimDigitalNumberDescription[_DeviceT_co], + ) -> None: + """Initialize an EHEIM Digital number entity.""" + super().__init__(coordinator, device) + self.entity_description = description + self._attr_unique_id = f"{self._device_address}_{description.key}" + + @override + async def async_set_native_value(self, value: float) -> None: + return await self.entity_description.set_value_fn(self._device, value) + + @override + def _async_update_attrs(self) -> None: + self._attr_native_value = self.entity_description.value_fn(self._device) + self._attr_native_unit_of_measurement = ( + self.entity_description.uom_fn(self._device) + if self.entity_description.uom_fn + else self.entity_description.native_unit_of_measurement + ) diff --git a/homeassistant/components/eheimdigital/strings.json b/homeassistant/components/eheimdigital/strings.json index 81fa521bbaf..d7a14b023f7 100644 --- a/homeassistant/components/eheimdigital/strings.json +++ b/homeassistant/components/eheimdigital/strings.json @@ -47,6 +47,23 @@ } } }, + "number": { + "manual_speed": { + "name": "Manual speed" + }, + "day_speed": { + "name": "Day speed" + }, + "night_speed": { + "name": "Night speed" + }, + "temperature_offset": { + "name": "Temperature offset" + }, + "night_temperature_offset": { + "name": "Night temperature offset" + } + }, "sensor": { "current_speed": { "name": "Current speed" diff --git a/tests/components/eheimdigital/conftest.py b/tests/components/eheimdigital/conftest.py index 2c4af207642..01ef9e44b5d 100644 --- a/tests/components/eheimdigital/conftest.py +++ b/tests/components/eheimdigital/conftest.py @@ -61,6 +61,8 @@ def heater_mock(): heater_mock.temperature_unit = HeaterUnit.CELSIUS heater_mock.current_temperature = 24.2 heater_mock.target_temperature = 25.5 + heater_mock.temperature_offset = 0.1 + heater_mock.night_temperature_offset = -0.2 heater_mock.is_heating = True heater_mock.is_active = True heater_mock.operation_mode = HeaterMode.MANUAL @@ -77,6 +79,9 @@ def classic_vario_mock(): classic_vario_mock.aquarium_name = "Mock Aquarium" classic_vario_mock.sw_version = "1.0.0_1.0.0" classic_vario_mock.current_speed = 75 + classic_vario_mock.manual_speed = 75 + classic_vario_mock.day_speed = 80 + classic_vario_mock.night_speed = 20 classic_vario_mock.is_active = True classic_vario_mock.filter_mode = FilterMode.MANUAL classic_vario_mock.error_code = FilterErrorCode.NO_ERROR diff --git a/tests/components/eheimdigital/snapshots/test_number.ambr b/tests/components/eheimdigital/snapshots/test_number.ambr new file mode 100644 index 00000000000..d647b16bf49 --- /dev/null +++ b/tests/components/eheimdigital/snapshots/test_number.ambr @@ -0,0 +1,286 @@ +# serializer version: 1 +# name: test_setup[number.mock_classicvario_day_speed-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 100.0, + 'min': 0.0, + 'mode': , + 'step': 1, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mock_classicvario_day_speed', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Day speed', + 'platform': 'eheimdigital', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'day_speed', + 'unique_id': '00:00:00:00:00:03_day_speed', + 'unit_of_measurement': '%', + }) +# --- +# name: test_setup[number.mock_classicvario_day_speed-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock classicVARIO Day speed', + 'max': 100.0, + 'min': 0.0, + 'mode': , + 'step': 1, + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'number.mock_classicvario_day_speed', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_setup[number.mock_classicvario_manual_speed-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 100.0, + 'min': 0.0, + 'mode': , + 'step': 1, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mock_classicvario_manual_speed', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Manual speed', + 'platform': 'eheimdigital', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'manual_speed', + 'unique_id': '00:00:00:00:00:03_manual_speed', + 'unit_of_measurement': '%', + }) +# --- +# name: test_setup[number.mock_classicvario_manual_speed-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock classicVARIO Manual speed', + 'max': 100.0, + 'min': 0.0, + 'mode': , + 'step': 1, + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'number.mock_classicvario_manual_speed', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_setup[number.mock_classicvario_night_speed-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 100.0, + 'min': 0.0, + 'mode': , + 'step': 1, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mock_classicvario_night_speed', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Night speed', + 'platform': 'eheimdigital', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'night_speed', + 'unique_id': '00:00:00:00:00:03_night_speed', + 'unit_of_measurement': '%', + }) +# --- +# name: test_setup[number.mock_classicvario_night_speed-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Mock classicVARIO Night speed', + 'max': 100.0, + 'min': 0.0, + 'mode': , + 'step': 1, + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'number.mock_classicvario_night_speed', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_setup[number.mock_heater_night_temperature_offset-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 5, + 'min': -5, + 'mode': , + 'step': 0.5, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mock_heater_night_temperature_offset', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Night temperature offset', + 'platform': 'eheimdigital', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'night_temperature_offset', + 'unique_id': '00:00:00:00:00:02_night_temperature_offset', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup[number.mock_heater_night_temperature_offset-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Mock Heater Night temperature offset', + 'max': 5, + 'min': -5, + 'mode': , + 'step': 0.5, + }), + 'context': , + 'entity_id': 'number.mock_heater_night_temperature_offset', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_setup[number.mock_heater_temperature_offset-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 3, + 'min': -3, + 'mode': , + 'step': 0.1, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.mock_heater_temperature_offset', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Temperature offset', + 'platform': 'eheimdigital', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'temperature_offset', + 'unique_id': '00:00:00:00:00:02_temperature_offset', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup[number.mock_heater_temperature_offset-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Mock Heater Temperature offset', + 'max': 3, + 'min': -3, + 'mode': , + 'step': 0.1, + }), + 'context': , + 'entity_id': 'number.mock_heater_temperature_offset', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/eheimdigital/test_number.py b/tests/components/eheimdigital/test_number.py new file mode 100644 index 00000000000..d84c14f95a5 --- /dev/null +++ b/tests/components/eheimdigital/test_number.py @@ -0,0 +1,189 @@ +"""Tests for the number module.""" + +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.number import ( + ATTR_VALUE, + DOMAIN as NUMBER_DOMAIN, + SERVICE_SET_VALUE, +) +from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from .conftest import init_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.mark.usefixtures("classic_vario_mock", "heater_mock") +async def test_setup( + hass: HomeAssistant, + eheimdigital_hub_mock: MagicMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test number platform setup.""" + mock_config_entry.add_to_hass(hass) + + with ( + patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.NUMBER]), + patch( + "homeassistant.components.eheimdigital.coordinator.asyncio.Event", + new=AsyncMock, + ), + ): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + + for device in eheimdigital_hub_mock.return_value.devices: + await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"]( + device, eheimdigital_hub_mock.return_value.devices[device].device_type + ) + await hass.async_block_till_done() + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +@pytest.mark.usefixtures("classic_vario_mock", "heater_mock") +@pytest.mark.parametrize( + ("device_name", "entity_list"), + [ + ( + "heater_mock", + [ + ( + "number.mock_heater_temperature_offset", + 0.4, + "set_temperature_offset", + (0.4,), + ), + ( + "number.mock_heater_night_temperature_offset", + 0.4, + "set_night_temperature_offset", + (0.4,), + ), + ], + ), + ( + "classic_vario_mock", + [ + ( + "number.mock_classicvario_manual_speed", + 72.1, + "set_manual_speed", + (int(72.1),), + ), + ( + "number.mock_classicvario_day_speed", + 72.1, + "set_day_speed", + (int(72.1),), + ), + ( + "number.mock_classicvario_night_speed", + 72.1, + "set_night_speed", + (int(72.1),), + ), + ], + ), + ], +) +async def test_set_value( + hass: HomeAssistant, + eheimdigital_hub_mock: MagicMock, + mock_config_entry: MockConfigEntry, + device_name: str, + entity_list: list[tuple[str, float, str, tuple[float]]], + request: pytest.FixtureRequest, +) -> None: + """Test setting a value.""" + device: MagicMock = request.getfixturevalue(device_name) + await init_integration(hass, mock_config_entry) + + await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"]( + device.mac_address, device.device_type + ) + + await hass.async_block_till_done() + + for item in entity_list: + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: item[0], ATTR_VALUE: item[1]}, + blocking=True, + ) + calls = [call for call in device.mock_calls if call[0] == item[2]] + assert len(calls) == 1 and calls[0][1] == item[3] + + +@pytest.mark.usefixtures("classic_vario_mock", "heater_mock") +@pytest.mark.parametrize( + ("device_name", "entity_list"), + [ + ( + "heater_mock", + [ + ( + "number.mock_heater_temperature_offset", + "temperature_offset", + -1.1, + ), + ( + "number.mock_heater_night_temperature_offset", + "night_temperature_offset", + 2.3, + ), + ], + ), + ( + "classic_vario_mock", + [ + ( + "number.mock_classicvario_manual_speed", + "manual_speed", + 34, + ), + ( + "number.mock_classicvario_day_speed", + "day_speed", + 79, + ), + ( + "number.mock_classicvario_night_speed", + "night_speed", + 12, + ), + ], + ), + ], +) +async def test_state_update( + hass: HomeAssistant, + eheimdigital_hub_mock: MagicMock, + mock_config_entry: MockConfigEntry, + device_name: str, + entity_list: list[tuple[str, str, float]], + request: pytest.FixtureRequest, +) -> None: + """Test state updates.""" + device: MagicMock = request.getfixturevalue(device_name) + await init_integration(hass, mock_config_entry) + + await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"]( + device.mac_address, device.device_type + ) + + await hass.async_block_till_done() + + for item in entity_list: + setattr(device, item[1], item[2]) + await eheimdigital_hub_mock.call_args.kwargs["receive_callback"]() + assert (state := hass.states.get(item[0])) + assert state.state == str(item[2]) From 09a86d2ed25e5afaff2ea309e192882e4401e65d Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Tue, 15 Apr 2025 17:01:38 +0200 Subject: [PATCH 15/24] Add quality scale to UptimeRobot (#142912) * Add quality scale (gold) to UptimeRobot * todos * tweak * tweak comment * update after #142940 * improve comment * update as per review comment * one more comment * update reconfiguration use case --- .../components/uptimerobot/quality_scale.yaml | 92 +++++++++++++++++++ script/hassfest/quality_scale.py | 1 - 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/uptimerobot/quality_scale.yaml diff --git a/homeassistant/components/uptimerobot/quality_scale.yaml b/homeassistant/components/uptimerobot/quality_scale.yaml new file mode 100644 index 00000000000..1ab2c117483 --- /dev/null +++ b/homeassistant/components/uptimerobot/quality_scale.yaml @@ -0,0 +1,92 @@ +rules: + # Bronze + action-setup: + status: exempt + comment: no actions + appropriate-polling: done + brands: done + common-modules: done + config-flow-test-coverage: + status: todo + comment: fix name and docstring + config-flow: done + dependency-transparency: done + docs-actions: + status: exempt + comment: no actions + docs-high-level-description: done + docs-installation-instructions: done + docs-removal-instructions: done + entity-event-setup: + status: exempt + comment: no events + entity-unique-id: done + has-entity-name: done + runtime-data: done + test-before-configure: done + test-before-setup: done + unique-config-entry: done + + # Silver + action-exceptions: + status: todo + comment: we should not swallow the exception in switch.py + config-entry-unloading: done + docs-configuration-parameters: done + docs-installation-parameters: done + entity-unavailable: + status: todo + comment: Change the type of the coordinator data to be a dict[str, UptimeRobotMonitor] so we can just do a dict look up instead of iterating over the whole list + integration-owner: done + log-when-unavailable: done + parallel-updates: done + reauthentication-flow: done + test-coverage: + status: todo + comment: recheck typos + + # Gold + devices: done + diagnostics: done + discovery-update-info: + status: exempt + comment: device not discoverable + discovery: + status: exempt + comment: device not discoverable + docs-data-update: done + docs-examples: done + docs-known-limitations: + status: exempt + comment: no known limitations, yet + docs-supported-devices: done + docs-supported-functions: done + docs-troubleshooting: done + docs-use-cases: done + dynamic-devices: + status: todo + comment: create entities on runtime instead of triggering a reload + entity-category: done + entity-device-class: done + entity-disabled-by-default: + status: exempt + comment: no known use case + entity-translations: done + exception-translations: done + icon-translations: done + reconfiguration-flow: + status: todo + comment: handle API key change/update + repair-issues: + status: exempt + comment: no known use cases for repair issues or flows, yet + stale-devices: + status: todo + comment: We should remove the config entry from the device rather than remove the device + + # Platinum + async-dependency: done + inject-websession: done + strict-typing: + status: todo + comment: Requirement 'pyuptimerobot==22.2.0' appears untyped diff --git a/script/hassfest/quality_scale.py b/script/hassfest/quality_scale.py index d564bb51ead..5eea3048dcb 100644 --- a/script/hassfest/quality_scale.py +++ b/script/hassfest/quality_scale.py @@ -1060,7 +1060,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [ "upcloud", "upnp", "uptime", - "uptimerobot", "usb", "usgs_earthquakes_feed", "utility_meter", From 7b3e7b7aea919de8dc5a1174dbb17d177de17311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Tue, 15 Apr 2025 17:03:51 +0100 Subject: [PATCH 16/24] Remove uneeded setdefault from Whirlpool config entry (#142999) --- homeassistant/components/whirlpool/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/whirlpool/__init__.py b/homeassistant/components/whirlpool/__init__.py index cb073779379..fec26f03691 100644 --- a/homeassistant/components/whirlpool/__init__.py +++ b/homeassistant/components/whirlpool/__init__.py @@ -24,8 +24,6 @@ type WhirlpoolConfigEntry = ConfigEntry[AppliancesManager] async def async_setup_entry(hass: HomeAssistant, entry: WhirlpoolConfigEntry) -> bool: """Set up Whirlpool Sixth Sense from a config entry.""" - hass.data.setdefault(DOMAIN, {}) - session = async_get_clientsession(hass) region = CONF_REGIONS_MAP[entry.data.get(CONF_REGION, "EU")] brand = CONF_BRANDS_MAP[entry.data.get(CONF_BRAND, "Whirlpool")] From 998b33c207613254178a4f04ec14bc50a9596065 Mon Sep 17 00:00:00 2001 From: rappenze Date: Tue, 15 Apr 2025 18:41:53 +0200 Subject: [PATCH 17/24] Fix device creation in fibaro integration (#142957) * Fix device creation in fibaro integration * Better naming --- homeassistant/components/fibaro/__init__.py | 38 +++++++-------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/fibaro/__init__.py b/homeassistant/components/fibaro/__init__.py index 88288a86b59..7638b14c111 100644 --- a/homeassistant/components/fibaro/__init__.py +++ b/homeassistant/components/fibaro/__init__.py @@ -12,7 +12,7 @@ from pyfibaro.fibaro_client import ( FibaroClient, FibaroConnectFailed, ) -from pyfibaro.fibaro_data_helper import read_rooms +from pyfibaro.fibaro_data_helper import find_master_devices, read_rooms from pyfibaro.fibaro_device import DeviceModel from pyfibaro.fibaro_device_manager import FibaroDeviceManager from pyfibaro.fibaro_info import InfoModel @@ -176,35 +176,18 @@ class FibaroController: platform = Platform.LIGHT return platform - def _create_device_info( - self, device: DeviceModel, devices: list[DeviceModel] - ) -> None: - """Create the device info. Unrooted entities are directly shown below the home center.""" + def _create_device_info(self, main_device: DeviceModel) -> None: + """Create the device info for a main device.""" - # The home center is always id 1 (z-wave primary controller) - if device.parent_fibaro_id <= 1: - return - - master_entity: DeviceModel | None = None - if device.parent_fibaro_id == 1: - master_entity = device - else: - for parent in devices: - if parent.fibaro_id == device.parent_fibaro_id: - master_entity = parent - if master_entity is None: - _LOGGER.error("Parent with id %s not found", device.parent_fibaro_id) - return - - if "zwaveCompany" in master_entity.properties: - manufacturer = master_entity.properties.get("zwaveCompany") + if "zwaveCompany" in main_device.properties: + manufacturer = main_device.properties.get("zwaveCompany") else: manufacturer = None - self._device_infos[master_entity.fibaro_id] = DeviceInfo( - identifiers={(DOMAIN, master_entity.fibaro_id)}, + self._device_infos[main_device.fibaro_id] = DeviceInfo( + identifiers={(DOMAIN, main_device.fibaro_id)}, manufacturer=manufacturer, - name=master_entity.name, + name=main_device.name, via_device=(DOMAIN, self.hub_serial), ) @@ -239,6 +222,10 @@ class FibaroController: def _read_devices(self) -> None: """Read and process the device list.""" devices = self._fibaro_device_manager.get_devices() + + for main_device in find_master_devices(devices): + self._create_device_info(main_device) + self._device_map = {} last_climate_parent = None last_endpoint = None @@ -258,7 +245,6 @@ class FibaroController: if platform is None: continue device.unique_id_str = f"{slugify(self.hub_serial)}.{device.fibaro_id}" - self._create_device_info(device, devices) self._device_map[device.fibaro_id] = device _LOGGER.debug( "%s (%s, %s) -> %s %s", From dcf7520d2a830f690c2308bdf5d3ed77f8eb8fb6 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 19:29:15 +0200 Subject: [PATCH 18/24] Use common states for "Low", "Medium", "High" and "Auto" in `tuya` (#143002) --- homeassistant/components/tuya/strings.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/tuya/strings.json b/homeassistant/components/tuya/strings.json index 55fd9b18b1e..c6f6bfe9776 100644 --- a/homeassistant/components/tuya/strings.json +++ b/homeassistant/components/tuya/strings.json @@ -288,9 +288,9 @@ "motion_sensitivity": { "name": "Motion detection sensitivity", "state": { - "0": "Low sensitivity", - "1": "Medium sensitivity", - "2": "High sensitivity" + "0": "[%key:common::state::low%]", + "1": "[%key:common::state::medium%]", + "2": "[%key:common::state::high%]" } }, "record_mode": { @@ -404,7 +404,7 @@ "humidifier_spray_mode": { "name": "Spray mode", "state": { - "auto": "Auto", + "auto": "[%key:common::state::auto%]", "health": "Health", "sleep": "Sleep", "humidity": "Humidity", From fad1d7bd1f31db003061ec61e5b28be79ba06c3d Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 19:30:05 +0200 Subject: [PATCH 19/24] Use common state for "Auto" in `iron_os` (#143001) --- homeassistant/components/iron_os/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/iron_os/strings.json b/homeassistant/components/iron_os/strings.json index 629f7c32c9b..22c194cf41f 100644 --- a/homeassistant/components/iron_os/strings.json +++ b/homeassistant/components/iron_os/strings.json @@ -115,7 +115,7 @@ "state": { "right_handed": "Right-handed", "left_handed": "Left-handed", - "auto": "Auto" + "auto": "[%key:common::state::auto%]" } }, "animation_speed": { From 5fd17d092b165e52ae158469b5fa4520204b81f6 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 19:56:58 +0200 Subject: [PATCH 20/24] Use common states for "Auto" and "Manual" in `overkiz` (#143005) --- homeassistant/components/overkiz/strings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/overkiz/strings.json b/homeassistant/components/overkiz/strings.json index da6c01219f1..363147150dc 100644 --- a/homeassistant/components/overkiz/strings.json +++ b/homeassistant/components/overkiz/strings.json @@ -71,14 +71,14 @@ "state_attributes": { "preset_mode": { "state": { - "auto": "Auto", + "auto": "[%key:common::state::auto%]", + "manual": "[%key:common::state::manual%]", "comfort-1": "Comfort 1", "comfort-2": "Comfort 2", "drying": "Drying", "external": "External", "freeze": "Freeze", "frost_protection": "Frost protection", - "manual": "Manual", "night": "Night", "prog": "Prog" } From ae306893ff594e75c0c0b9517c5ddc9faaf03e95 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Apr 2025 08:09:51 -1000 Subject: [PATCH 21/24] Handle name conflicts in ESPHome config flow (#142966) --- .../components/esphome/config_flow.py | 79 +++++++++++++++- homeassistant/components/esphome/strings.json | 11 ++- tests/components/esphome/test_config_flow.py | 93 +++++++++++++++++++ 3 files changed, 179 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 95304476fae..96ffa43038d 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -47,6 +47,7 @@ from .const import ( DOMAIN, ) from .dashboard import async_get_or_create_dashboard_manager, async_set_dashboard_info +from .manager import async_replace_device ERROR_REQUIRES_ENCRYPTION_KEY = "requires_encryption_key" ERROR_INVALID_ENCRYPTION_KEY = "invalid_psk" @@ -74,6 +75,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): # The ESPHome name as per its config self._device_name: str | None = None self._device_mac: str | None = None + self._entry_with_name_conflict: ConfigEntry | None = None async def _async_step_user_base( self, user_input: dict[str, Any] | None = None, error: str | None = None @@ -137,7 +139,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): """Handle reauthorization flow when encryption was removed.""" if user_input is not None: self._noise_psk = None - return self._async_get_entry() + return await self._async_get_entry_or_resolve_conflict() return self.async_show_form( step_id="reauth_encryption_removed_confirm", @@ -227,7 +229,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): return await self.async_step_authenticate() self._password = "" - return self._async_get_entry() + return await self._async_get_entry_or_resolve_conflict() async def async_step_discovery_confirm( self, user_input: dict[str, Any] | None = None @@ -354,6 +356,77 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): ) return self.async_abort(reason="service_received") + async def async_step_name_conflict( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle name conflict resolution.""" + assert self._entry_with_name_conflict is not None + assert self._entry_with_name_conflict.unique_id is not None + assert self.unique_id is not None + assert self._device_name is not None + return self.async_show_menu( + step_id="name_conflict", + menu_options=["name_conflict_migrate", "name_conflict_overwrite"], + description_placeholders={ + "existing_mac": format_mac(self._entry_with_name_conflict.unique_id), + "existing_title": self._entry_with_name_conflict.title, + "mac": format_mac(self.unique_id), + "name": self._device_name, + }, + ) + + async def async_step_name_conflict_migrate( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle migration of existing entry.""" + assert self._entry_with_name_conflict is not None + assert self._entry_with_name_conflict.unique_id is not None + assert self.unique_id is not None + assert self._device_name is not None + assert self._host is not None + old_mac = format_mac(self._entry_with_name_conflict.unique_id) + new_mac = format_mac(self.unique_id) + entry_id = self._entry_with_name_conflict.entry_id + self.hass.config_entries.async_update_entry( + self._entry_with_name_conflict, + data={ + **self._entry_with_name_conflict.data, + CONF_HOST: self._host, + CONF_PORT: self._port or 6053, + CONF_PASSWORD: self._password or "", + CONF_NOISE_PSK: self._noise_psk or "", + }, + ) + await async_replace_device(self.hass, entry_id, old_mac, new_mac) + self.hass.config_entries.async_schedule_reload(entry_id) + return self.async_abort( + reason="name_conflict_migrated", + description_placeholders={ + "existing_mac": old_mac, + "mac": new_mac, + "name": self._device_name, + }, + ) + + async def async_step_name_conflict_overwrite( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle creating a new entry by removing the old one and creating new.""" + assert self._entry_with_name_conflict is not None + await self.hass.config_entries.async_remove( + self._entry_with_name_conflict.entry_id + ) + return self._async_get_entry() + + async def _async_get_entry_or_resolve_conflict(self) -> ConfigFlowResult: + """Return the entry or resolve a conflict.""" + if self.source != SOURCE_REAUTH: + for entry in self._async_current_entries(include_ignore=False): + if entry.data.get(CONF_DEVICE_NAME) == self._device_name: + self._entry_with_name_conflict = entry + return await self.async_step_name_conflict() + return self._async_get_entry() + @callback def _async_get_entry(self) -> ConfigFlowResult: config_data = { @@ -407,7 +480,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): error = await self.try_login() if error: return await self.async_step_authenticate(error=error) - return self._async_get_entry() + return await self._async_get_entry_or_resolve_conflict() errors = {} if error is not None: diff --git a/homeassistant/components/esphome/strings.json b/homeassistant/components/esphome/strings.json index 8c20fb4e95a..42862885ae9 100644 --- a/homeassistant/components/esphome/strings.json +++ b/homeassistant/components/esphome/strings.json @@ -9,7 +9,8 @@ "mqtt_missing_mac": "Missing MAC address in MQTT properties.", "mqtt_missing_api": "Missing API port in MQTT properties.", "mqtt_missing_ip": "Missing IP address in MQTT properties.", - "mqtt_missing_payload": "Missing MQTT Payload." + "mqtt_missing_payload": "Missing MQTT Payload.", + "name_conflict_migrated": "The configuration for `{name}` has been migrated to a new device with MAC address `{mac}` from `{existing_mac}`." }, "error": { "resolve_error": "Can't resolve address of the ESP. If this error persists, please set a static IP address", @@ -49,6 +50,14 @@ "discovery_confirm": { "description": "Do you want to add the device `{name}` to Home Assistant?", "title": "Discovered ESPHome device" + }, + "name_conflict": { + "title": "Name conflict", + "description": "**The name `{name}` is already being used by another device: {existing_title} (MAC address: `{existing_mac}`)**\n\nTo continue, please choose one of the following options:\n\n**Migrate Configuration to New Device:** If this is a replacement, migrate the existing settings to the new device (`{mac}`).\n**Overwrite the Existing Configuration:** If this is not a replacement, delete the old configuration for `{existing_mac}` and use the new device instead.", + "menu_options": { + "name_conflict_migrate": "Migrate configuration to new device", + "name_conflict_overwrite": "Overwrite the existing configuration" + } } }, "flow_title": "{name}" diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index 60c93d5fb2c..440e52700b1 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -1622,3 +1622,96 @@ async def test_discovery_mqtt_initiation( assert result["result"] assert result["result"].unique_id == "11:22:33:44:55:aa" + + +@pytest.mark.usefixtures("mock_zeroconf") +async def test_user_flow_name_conflict_migrate( + hass: HomeAssistant, + mock_client, + mock_setup_entry: None, +) -> None: + """Test handle migration on name conflict.""" + existing_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_DEVICE_NAME: "test"}, + unique_id="11:22:33:44:55:cc", + ) + existing_entry.add_to_hass(hass) + mock_client.device_info = AsyncMock( + return_value=DeviceInfo( + uses_password=False, + name="test", + mac_address="11:22:33:44:55:AA", + ) + ) + + result = await hass.config_entries.flow.async_init( + "esphome", + context={"source": config_entries.SOURCE_USER}, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053}, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.MENU + assert result["step_id"] == "name_conflict" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "name_conflict_migrate"} + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "name_conflict_migrated" + + assert existing_entry.data == { + CONF_HOST: "127.0.0.1", + CONF_PORT: 6053, + CONF_PASSWORD: "", + CONF_NOISE_PSK: "", + CONF_DEVICE_NAME: "test", + } + assert existing_entry.unique_id == "11:22:33:44:55:aa" + + +@pytest.mark.usefixtures("mock_zeroconf") +async def test_user_flow_name_conflict_overwrite( + hass: HomeAssistant, + mock_client, + mock_setup_entry: None, +) -> None: + """Test handle overwrite on name conflict.""" + existing_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_DEVICE_NAME: "test"}, + unique_id="11:22:33:44:55:cc", + ) + existing_entry.add_to_hass(hass) + mock_client.device_info = AsyncMock( + return_value=DeviceInfo( + uses_password=False, + name="test", + mac_address="11:22:33:44:55:AA", + ) + ) + + result = await hass.config_entries.flow.async_init( + "esphome", + context={"source": config_entries.SOURCE_USER}, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053}, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.MENU + assert result["step_id"] == "name_conflict" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"next_step_id": "name_conflict_overwrite"} + ) + assert result["type"] is FlowResultType.CREATE_ENTRY + + assert result["data"] == { + CONF_HOST: "127.0.0.1", + CONF_PORT: 6053, + CONF_PASSWORD: "", + CONF_NOISE_PSK: "", + CONF_DEVICE_NAME: "test", + } + assert result["context"]["unique_id"] == "11:22:33:44:55:aa" From 6a1739e883d69174b59626044711f84387c6011a Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 20:24:44 +0200 Subject: [PATCH 22/24] Use common state for "Auto", fix casing in `mqtt` (#143000) - use the (new) common state for "Auto" - capitalize one occurrence of "mqtt" - (fully) sentence-case two title strings Co-authored-by: Franck Nijhof --- homeassistant/components/mqtt/strings.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json index bc9fd06c78c..4245af2fc95 100644 --- a/homeassistant/components/mqtt/strings.json +++ b/homeassistant/components/mqtt/strings.json @@ -1,7 +1,7 @@ { "issues": { "invalid_platform_config": { - "title": "Invalid config found for mqtt {domain} item", + "title": "Invalid config found for MQTT {domain} item", "description": "Home Assistant detected an invalid config for a manually configured item.\n\nPlatform domain: **{domain}**\nConfiguration file: **{config_file}**\nNear line: **{line}**\nConfiguration found:\n```yaml\n{config}\n```\nError: **{error}**.\n\nMake sure the configuration is valid and [reload](/developer-tools/yaml) the manually configured MQTT items or restart Home Assistant to fix this issue." }, "invalid_unit_of_measurement": { @@ -68,7 +68,7 @@ "title": "Starting add-on" }, "hassio_confirm": { - "title": "MQTT Broker via Home Assistant add-on", + "title": "MQTT broker via Home Assistant add-on", "description": "Do you want to configure Home Assistant to connect to the MQTT broker provided by the add-on {addon}?" }, "reauth_confirm": { @@ -153,7 +153,7 @@ }, "sections": { "mqtt_settings": { - "name": "MQTT Settings", + "name": "MQTT settings", "data": { "qos": "QoS" }, @@ -480,7 +480,7 @@ "set_ca_cert": { "options": { "off": "[%key:common::state::off%]", - "auto": "Auto", + "auto": "[%key:common::state::auto%]", "custom": "Custom" } }, From bb5aefb9e45bfcafefeba3a1f18cbc16cc2cfe14 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Tue, 15 Apr 2025 20:44:41 +0200 Subject: [PATCH 23/24] Use common state for "Manual" in `hive` (#143009) --- homeassistant/components/hive/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hive/strings.json b/homeassistant/components/hive/strings.json index 5fa15b68d1a..58ba949d325 100644 --- a/homeassistant/components/hive/strings.json +++ b/homeassistant/components/hive/strings.json @@ -105,7 +105,7 @@ "sensor": { "heating": { "state": { - "manual": "Manual", + "manual": "[%key:common::state::manual%]", "off": "[%key:common::state::off%]", "schedule": "Schedule" } From f0d81d077f04ed1a3c6b2ce2d64b23019633f5ad Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 15 Apr 2025 22:09:59 +0200 Subject: [PATCH 24/24] Adjust issue template to assign Bug issue type (#143017) --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 80291c73e61..87fed908c6e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,6 @@ name: Report an issue with Home Assistant Core description: Report an issue with Home Assistant Core. +type: Bug body: - type: markdown attributes: