From 1c1dcfd95036434777c36668d56a2ae04966d1bf Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 29 Sep 2026 17:59:34 +0200 Subject: [PATCH] Fix myUplink select platform failing on decimal enum values (#182602) --- homeassistant/components/myuplink/select.py | 14 +- .../fixtures/device_points_ctc_i555.json | 208 ++++++++++++++++++ tests/components/myuplink/test_select.py | 40 +++- 3 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 tests/components/myuplink/fixtures/device_points_ctc_i555.json diff --git a/homeassistant/components/myuplink/select.py b/homeassistant/components/myuplink/select.py index d4d2703ce69a..b0023b9ff2c4 100644 --- a/homeassistant/components/myuplink/select.py +++ b/homeassistant/components/myuplink/select.py @@ -66,26 +66,28 @@ class MyUplinkSelect(MyUplinkEntity, SelectEntity): self._attr_name = device_point.parameter_name self._attr_options = [x["text"].capitalize() for x in device_point.enum_values] + # enum values are numeric strings, with decimals on some devices ("0.3"), + # so match them as numbers and keep the raw string to write back self.options_map = { - str(int(x["value"])): x["text"].capitalize() - for x in device_point.enum_values + float(x["value"]): x["text"].capitalize() for x in device_point.enum_values + } + self.options_rev = { + x["text"].capitalize(): x["value"] for x in device_point.enum_values } - self.options_rev = {value: key for key, value in self.options_map.items()} @property @override def current_option(self) -> str | None: """Retrieve currently selected option.""" device_point = self.coordinator.data.points[self.device_id][self.point_id] - value = int(cast(int, device_point.value_t)) - return self.options_map.get(str(value)) + return self.options_map.get(float(cast(float, device_point.value_t))) @override async def async_select_option(self, option: str) -> None: """Set the current option.""" try: await self.coordinator.api.async_set_device_points( - self.device_id, data={self.point_id: str(self.options_rev[option])} + self.device_id, data={self.point_id: self.options_rev[option]} ) except ClientError as err: raise HomeAssistantError( diff --git a/tests/components/myuplink/fixtures/device_points_ctc_i555.json b/tests/components/myuplink/fixtures/device_points_ctc_i555.json new file mode 100644 index 000000000000..706fed356ac7 --- /dev/null +++ b/tests/components/myuplink/fixtures/device_points_ctc_i555.json @@ -0,0 +1,208 @@ +[ + { + "category": "Add. heat", + "parameterId": "61590", + "parameterName": "Lower Immersion heater / heating kW", + "parameterUnit": "", + "writable": true, + "timestamp": "2026-09-17T17:53:27+00:00", + "value": 3.0, + "strVal": "3", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1.0, + "enumValues": [ + { + "value": "0.0", + "text": "0.0", + "icon": "" + }, + { + "value": "3.0", + "text": "3.0", + "icon": "" + } + ], + "scaleValue": "0.1", + "zoneId": null + }, + { + "category": "Add. heat", + "parameterId": "61591", + "parameterName": "Upper Immersion heater / DHW kW", + "parameterUnit": "", + "writable": true, + "timestamp": "2026-09-17T17:53:27+00:00", + "value": 8.7, + "strVal": "8.7", + "smartHomeCategories": [], + "minValue": null, + "maxValue": null, + "stepValue": 1.0, + "enumValues": [ + { + "value": "0.0", + "text": "0.0", + "icon": "" + }, + { + "value": "0.3", + "text": "0.3", + "icon": "" + }, + { + "value": "0.6", + "text": "0.6", + "icon": "" + }, + { + "value": "0.9", + "text": "0.9", + "icon": "" + }, + { + "value": "1.2", + "text": "1.2", + "icon": "" + }, + { + "value": "1.5", + "text": "1.5", + "icon": "" + }, + { + "value": "1.8", + "text": "1.8", + "icon": "" + }, + { + "value": "2.1", + "text": "2.1", + "icon": "" + }, + { + "value": "2.3", + "text": "2.3", + "icon": "" + }, + { + "value": "2.6", + "text": "2.6", + "icon": "" + }, + { + "value": "2.9", + "text": "2.9", + "icon": "" + }, + { + "value": "3.2", + "text": "3.2", + "icon": "" + }, + { + "value": "3.5", + "text": "3.5", + "icon": "" + }, + { + "value": "3.8", + "text": "3.8", + "icon": "" + }, + { + "value": "4.1", + "text": "4.1", + "icon": "" + }, + { + "value": "4.4", + "text": "4.4", + "icon": "" + }, + { + "value": "4.6", + "text": "4.6", + "icon": "" + }, + { + "value": "4.9", + "text": "4.9", + "icon": "" + }, + { + "value": "5.2", + "text": "5.2", + "icon": "" + }, + { + "value": "5.5", + "text": "5.5", + "icon": "" + }, + { + "value": "5.8", + "text": "5.8", + "icon": "" + }, + { + "value": "6.1", + "text": "6.1", + "icon": "" + }, + { + "value": "6.4", + "text": "6.4", + "icon": "" + }, + { + "value": "6.7", + "text": "6.7", + "icon": "" + }, + { + "value": "6.9", + "text": "6.9", + "icon": "" + }, + { + "value": "7.2", + "text": "7.2", + "icon": "" + }, + { + "value": "7.5", + "text": "7.5", + "icon": "" + }, + { + "value": "7.8", + "text": "7.8", + "icon": "" + }, + { + "value": "8.1", + "text": "8.1", + "icon": "" + }, + { + "value": "8.4", + "text": "8.4", + "icon": "" + }, + { + "value": "8.7", + "text": "8.7", + "icon": "" + }, + { + "value": "9.0", + "text": "9.0", + "icon": "" + } + ], + "scaleValue": "0.1", + "zoneId": null + } +] diff --git a/tests/components/myuplink/test_select.py b/tests/components/myuplink/test_select.py index 51c68f8fd2da..fe2aed6a2186 100644 --- a/tests/components/myuplink/test_select.py +++ b/tests/components/myuplink/test_select.py @@ -6,7 +6,8 @@ from aiohttp import ClientError import pytest from syrupy.assertion import SnapshotAssertion -from homeassistant.components.select import DOMAIN as SELECT_DOMAIN +from homeassistant.components.myuplink.const import DOMAIN +from homeassistant.components.select import ATTR_OPTIONS, DOMAIN as SELECT_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_OPTION, @@ -24,6 +25,7 @@ pytestmark = pytest.mark.parametrize("platforms", [(Platform.SELECT,)]) ENTITY_ID = "select.gotham_city_comfort_mode" ENTITY_FRIENDLY_NAME = "Gotham City comfort mode" ENTITY_UID = "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff-47041" +DEVICE_ID = "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff" async def test_selecting( @@ -71,6 +73,42 @@ async def test_entity_registry_smo20( assert entry.unique_id == "robin-r-1234-20240201-123456-aa-bb-cc-dd-ee-ff-47660" +@pytest.mark.parametrize( + "load_device_points_file", + ["device_points_ctc_i555.json"], +) +async def test_decimal_enum_values( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_myuplink_client: MagicMock, + setup_platform: None, +) -> None: + """Test select entities for enum values with decimals, as a CTC i555 reports.""" + lower_heater = entity_registry.async_get_entity_id( + SELECT_DOMAIN, DOMAIN, f"{DEVICE_ID}-61590" + ) + upper_heater = entity_registry.async_get_entity_id( + SELECT_DOMAIN, DOMAIN, f"{DEVICE_ID}-61591" + ) + assert lower_heater is not None + assert upper_heater is not None + + lower_state = hass.states.get(lower_heater) + assert lower_state.state == "3.0" + assert lower_state.attributes[ATTR_OPTIONS] == ["0.0", "3.0"] + assert hass.states.get(upper_heater).state == "8.7" + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + {ATTR_ENTITY_ID: upper_heater, ATTR_OPTION: "0.3"}, + blocking=True, + ) + mock_myuplink_client.async_set_device_points.assert_called_once_with( + DEVICE_ID, data={"61591": "0.3"} + ) + + async def test_select_states( hass: HomeAssistant, mock_myuplink_client: MagicMock,