From 21d06fdace368153f71833b2d12debf6523da818 Mon Sep 17 00:00:00 2001 From: Hai-Nam Nguyen Date: Thu, 19 Mar 2026 21:52:59 +0100 Subject: [PATCH] Fix unit when plant power is above 1000W in Hypontech (#165959) Co-authored-by: Joost Lekkerkerker --- homeassistant/components/hypontech/sensor.py | 25 ++- .../hypontech/fixtures/overview.json | 2 +- .../hypontech/fixtures/plant_list.json | 28 ++- .../hypontech/snapshots/test_sensor.ambr | 178 +++++++++++++++++- 4 files changed, 225 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/hypontech/sensor.py b/homeassistant/components/hypontech/sensor.py index a85e77174d0..4552f445543 100644 --- a/homeassistant/components/hypontech/sensor.py +++ b/homeassistant/components/hypontech/sensor.py @@ -21,11 +21,17 @@ from .coordinator import HypontechConfigEntry, HypontechDataCoordinator from .entity import HypontechEntity, HypontechPlantEntity +def _power_unit(data: OverviewData | PlantData) -> str: + """Return the unit of measurement for power based on the API unit.""" + return UnitOfPower.KILO_WATT if data.company.upper() == "KW" else UnitOfPower.WATT + + @dataclass(frozen=True, kw_only=True) class HypontechSensorDescription(SensorEntityDescription): """Describes Hypontech overview sensor entity.""" value_fn: Callable[[OverviewData], float | None] + unit_fn: Callable[[OverviewData], str] | None = None @dataclass(frozen=True, kw_only=True) @@ -33,15 +39,16 @@ class HypontechPlantSensorDescription(SensorEntityDescription): """Describes Hypontech plant sensor entity.""" value_fn: Callable[[PlantData], float | None] + unit_fn: Callable[[PlantData], str] | None = None OVERVIEW_SENSORS: tuple[HypontechSensorDescription, ...] = ( HypontechSensorDescription( key="pv_power", - native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda c: c.power, + unit_fn=_power_unit, ), HypontechSensorDescription( key="lifetime_energy", @@ -64,10 +71,10 @@ OVERVIEW_SENSORS: tuple[HypontechSensorDescription, ...] = ( PLANT_SENSORS: tuple[HypontechPlantSensorDescription, ...] = ( HypontechPlantSensorDescription( key="pv_power", - native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, value_fn=lambda c: c.power, + unit_fn=_power_unit, ), HypontechPlantSensorDescription( key="lifetime_energy", @@ -124,6 +131,13 @@ class HypontechOverviewSensor(HypontechEntity, SensorEntity): self.entity_description = description self._attr_unique_id = f"{coordinator.account_id}_{description.key}" + @property + def native_unit_of_measurement(self) -> str | None: + """Return the unit of measurement.""" + if self.entity_description.unit_fn is not None: + return self.entity_description.unit_fn(self.coordinator.data.overview) + return super().native_unit_of_measurement + @property def native_value(self) -> float | None: """Return the state of the sensor.""" @@ -146,6 +160,13 @@ class HypontechPlantSensor(HypontechPlantEntity, SensorEntity): self.entity_description = description self._attr_unique_id = f"{plant_id}_{description.key}" + @property + def native_unit_of_measurement(self) -> str | None: + """Return the unit of measurement.""" + if self.entity_description.unit_fn is not None: + return self.entity_description.unit_fn(self.plant) + return super().native_unit_of_measurement + @property def native_value(self) -> float | None: """Return the state of the sensor.""" diff --git a/tests/components/hypontech/fixtures/overview.json b/tests/components/hypontech/fixtures/overview.json index 8a58b7bf318..206df95b653 100644 --- a/tests/components/hypontech/fixtures/overview.json +++ b/tests/components/hypontech/fixtures/overview.json @@ -6,7 +6,7 @@ "e_today": 1.54, "total_co2": 0.03, "total_tree": 1.73, - "power": 0, + "power": 1223, "normal_dev_num": 0, "offline_dev_num": 1, "fault_dev_num": 0, diff --git a/tests/components/hypontech/fixtures/plant_list.json b/tests/components/hypontech/fixtures/plant_list.json index bc8cfb5a6b8..08578b29602 100644 --- a/tests/components/hypontech/fixtures/plant_list.json +++ b/tests/components/hypontech/fixtures/plant_list.json @@ -11,19 +11,41 @@ "city": "New York", "e_today": 1.54, "e_total": 48, - "power": 0, + "power": 123, "owner_id": "2123456789123456789", "plant_id": "1123456789123456789", "eid": 0, "top": 0, "micro": 1, "property": 1, - "kwhimp": 0 + "kwhimp": 0, + "company": "W" + }, + { + "status": "online", + "time": "2026-02-16T18:04:50+01:00", + "plant_name": "Rooftop", + "plant_type": "PvGrid", + "photo": "", + "owner_name": "admin@example.com", + "country": "United States", + "city": "New York", + "e_today": 2.5, + "e_total": 100, + "power": 1.1, + "owner_id": "2123456789123456789", + "plant_id": "3123456789123456789", + "eid": 0, + "top": 0, + "micro": 1, + "property": 1, + "kwhimp": 0, + "company": "KW" } ], "message": "ok", "time": 1771277551, "totalPage": 1, - "totalCount": 1, + "totalCount": 2, "code": 20000 } diff --git a/tests/components/hypontech/snapshots/test_sensor.ambr b/tests/components/hypontech/snapshots/test_sensor.ambr index ec18c4d89be..225d5f54fb2 100644 --- a/tests/components/hypontech/snapshots/test_sensor.ambr +++ b/tests/components/hypontech/snapshots/test_sensor.ambr @@ -112,7 +112,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0', + 'state': '123', }) # --- # name: test_sensors[sensor.balcon_today_energy-entry] @@ -286,7 +286,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0', + 'state': '1223', }) # --- # name: test_sensors[sensor.overview_today_energy-entry] @@ -347,3 +347,177 @@ 'state': '1.54', }) # --- +# name: test_sensors[sensor.rooftop_lifetime_energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rooftop_lifetime_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Lifetime energy', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Lifetime energy', + 'platform': 'hypontech', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'lifetime_energy', + 'unique_id': '3123456789123456789_lifetime_energy', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.rooftop_lifetime_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Rooftop Lifetime energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.rooftop_lifetime_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100.0', + }) +# --- +# name: test_sensors[sensor.rooftop_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rooftop_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Power', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power', + 'platform': 'hypontech', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '3123456789123456789_pv_power', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.rooftop_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Rooftop Power', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.rooftop_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1', + }) +# --- +# name: test_sensors[sensor.rooftop_today_energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.rooftop_today_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Today energy', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Today energy', + 'platform': 'hypontech', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'today_energy', + 'unique_id': '3123456789123456789_today_energy', + 'unit_of_measurement': , + }) +# --- +# name: test_sensors[sensor.rooftop_today_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Rooftop Today energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.rooftop_today_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2.5', + }) +# ---