From 3cf056ddb2c64e8a040d6eb4b6adfbc74a99dec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Fri, 28 Aug 2026 19:02:43 +0200 Subject: [PATCH] Fix TemperatureOffset native min/max unit conversion for Matter thermostats (#180172) Co-authored-by: Claude Sonnet 5 --- homeassistant/components/matter/number.py | 23 ++++++--- .../matter/snapshots/test_number.ambr | 48 +++++++++---------- tests/components/matter/test_number.py | 37 ++++++++++++++ 3 files changed, 77 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/matter/number.py b/homeassistant/components/matter/number.py index 12efc204d748..2dc19d23e820 100644 --- a/homeassistant/components/matter/number.py +++ b/homeassistant/components/matter/number.py @@ -308,7 +308,8 @@ DISCOVERY_SCHEMAS = [ ), featuremap_contains=(clusters.Thermostat.Bitmaps.Feature.kSetback), ), - # Eve temperature offset with higher min/max + # Eve temperature offset; shares the bounds of the generic schema below + # and will be merged into it under that schema's unique ID (follow-up PR) MatterDiscoverySchema( platform=Platform.NUMBER, entity_description=MatterNumberEntityDescription( @@ -316,9 +317,13 @@ DISCOVERY_SCHEMAS = [ device_class=NumberDeviceClass.TEMPERATURE, entity_category=EntityCategory.CONFIG, translation_key="temperature_offset", - native_max_value=50, - native_min_value=-50, - native_step=0.5, + # Matter 1.4 raises this to the SignedTemperature type's usable + # range (±127 in 0.1°C units; -128 is reserved). Matter 1.3 is + # limited to ±2.5°C; that will be enforced via cluster_revision + # filtering in a follow-up PR. + native_max_value=12.7, + native_min_value=-12.7, + native_step=0.1, native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_to_ha=lambda x: None if x is None else x / 10, ha_to_device=lambda x: round(x * 10), @@ -337,9 +342,13 @@ DISCOVERY_SCHEMAS = [ device_class=NumberDeviceClass.TEMPERATURE, entity_category=EntityCategory.CONFIG, translation_key="temperature_offset", - native_max_value=25, # Matter 1.3 limit - native_min_value=-25, # Matter 1.3 limit - native_step=0.5, + # Matter 1.4 raises this to the SignedTemperature type's usable + # range (±127 in 0.1°C units; -128 is reserved). Matter 1.3 is + # limited to ±2.5°C; that will be enforced via cluster_revision + # filtering in a follow-up PR. + native_max_value=12.7, + native_min_value=-12.7, + native_step=0.1, native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_to_ha=lambda x: None if x is None else x / 10, ha_to_device=lambda x: round(x * 10), diff --git a/tests/components/matter/snapshots/test_number.ambr b/tests/components/matter/snapshots/test_number.ambr index 69fad1019d0a..2a9ba770aaeb 100644 --- a/tests/components/matter/snapshots/test_number.ambr +++ b/tests/components/matter/snapshots/test_number.ambr @@ -422,10 +422,10 @@ ]), 'area_id': None, 'capabilities': dict({ - : 25, - : -25, + : 12.7, + : -12.7, : , - : 0.5, + : 0.1, }), 'config_entry_id': , 'config_subentry_id': , @@ -462,10 +462,10 @@ 'attributes': ReadOnlyDict({ : 'temperature', : 'Floor Heating Thermostat Temperature offset', - : 25, - : -25, + : 12.7, + : -12.7, : , - : 0.5, + : 0.1, : , }), 'context': , @@ -720,10 +720,10 @@ ]), 'area_id': None, 'capabilities': dict({ - : 50, - : -50, + : 12.7, + : -12.7, : , - : 0.5, + : 0.1, }), 'config_entry_id': , 'config_subentry_id': , @@ -760,10 +760,10 @@ 'attributes': ReadOnlyDict({ : 'temperature', : 'Eve Thermo 20EBP1701 Temperature offset', - : 50, - : -50, + : 12.7, + : -12.7, : , - : 0.5, + : 0.1, : , }), 'context': , @@ -781,10 +781,10 @@ ]), 'area_id': None, 'capabilities': dict({ - : 50, - : -50, + : 12.7, + : -12.7, : , - : 0.5, + : 0.1, }), 'config_entry_id': , 'config_subentry_id': , @@ -821,10 +821,10 @@ 'attributes': ReadOnlyDict({ : 'temperature', : 'Eve Thermo 20ECD1701 Temperature offset', - : 50, - : -50, + : 12.7, + : -12.7, : , - : 0.5, + : 0.1, : , }), 'context': , @@ -4654,10 +4654,10 @@ ]), 'area_id': None, 'capabilities': dict({ - : 25, - : -25, + : 12.7, + : -12.7, : , - : 0.5, + : 0.1, }), 'config_entry_id': , 'config_subentry_id': , @@ -4694,10 +4694,10 @@ 'attributes': ReadOnlyDict({ : 'temperature', : 'Mock Thermostat Temperature offset', - : 25, - : -25, + : 12.7, + : -12.7, : , - : 0.5, + : 0.1, : , }), 'context': , diff --git a/tests/components/matter/test_number.py b/tests/components/matter/test_number.py index 052dd76f113a..73c20c017d02 100644 --- a/tests/components/matter/test_number.py +++ b/tests/components/matter/test_number.py @@ -339,6 +339,43 @@ async def test_thermostat_occupied_setback( ) +@pytest.mark.parametrize("node_fixture", ["aqara_thermostat_w500"]) +async def test_thermostat_temperature_offset( + hass: HomeAssistant, + matter_client: MagicMock, + matter_node: MatterNode, +) -> None: + """Test thermostat temperature offset number entity.""" + + entity_id = "number.floor_heating_thermostat_temperature_offset" + + state = hass.states.get(entity_id) + assert state + assert state.state == "0.0" + + # Setting value to 0.3 °C writes 3 to LocalTemperatureCalibration (scale x10), + # a value only reachable with the 0.1 °C step + await hass.services.async_call( + "number", + "set_value", + { + "entity_id": entity_id, + "value": 0.3, + }, + blocking=True, + ) + + assert matter_client.write_attribute.call_count == 1 + assert matter_client.write_attribute.call_args == call( + node_id=matter_node.node_id, + attribute_path=create_attribute_path_from_attribute( + endpoint_id=1, + attribute=clusters.Thermostat.Attributes.LocalTemperatureCalibration, + ), + value=3, + ) + + @pytest.mark.parametrize("node_fixture", ["aqara_multi_state_p100"]) async def test_boolean_state_configuration_current_sensitivity_level( hass: HomeAssistant,