From 6d2cb94284b872b0b3dddbd536800a4a91fadabe Mon Sep 17 00:00:00 2001 From: tiger Date: Tue, 17 Dec 2024 19:24:09 +0800 Subject: [PATCH 1/6] feat: add state_class for sensor --- custom_components/xiaomi_home/miot/miot_spec.py | 2 ++ custom_components/xiaomi_home/sensor.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/custom_components/xiaomi_home/miot/miot_spec.py b/custom_components/xiaomi_home/miot/miot_spec.py index 3df70f1..195f674 100644 --- a/custom_components/xiaomi_home/miot/miot_spec.py +++ b/custom_components/xiaomi_home/miot/miot_spec.py @@ -79,6 +79,7 @@ class MIoTSpecBase: # External params platform: str device_class: any + state_class: any icon: str external_unit: any @@ -96,6 +97,7 @@ def __init__(self, spec: dict) -> None: self.platform = None self.device_class = None + self.state_class = None self.icon = None self.external_unit = None diff --git a/custom_components/xiaomi_home/sensor.py b/custom_components/xiaomi_home/sensor.py index 7d2e074..3ef80b5 100644 --- a/custom_components/xiaomi_home/sensor.py +++ b/custom_components/xiaomi_home/sensor.py @@ -105,6 +105,9 @@ def __init__(self, miot_device: MIoTDevice, spec: MIoTSpecProperty) -> None: # Set icon if spec.icon: self._attr_icon = spec.icon + # Set state_class + if spec.state_class: + self._attr_state_class = spec.state_class @property def native_value(self) -> any: From 9f9226ad29b660e1e573a4ba0687a5da646d72bd Mon Sep 17 00:00:00 2001 From: tiger Date: Tue, 17 Dec 2024 19:24:25 +0800 Subject: [PATCH 2/6] feat: add optional state_class and unit_of_measurement for properties --- .../xiaomi_home/miot/specs/specv2entity.py | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/custom_components/xiaomi_home/miot/specs/specv2entity.py b/custom_components/xiaomi_home/miot/specs/specv2entity.py index 5c64083..6eb3811 100644 --- a/custom_components/xiaomi_home/miot/specs/specv2entity.py +++ b/custom_components/xiaomi_home/miot/specs/specv2entity.py @@ -46,8 +46,16 @@ Conversion rules of MIoT-Spec-V2 instance to Home Assistant entity. """ from homeassistant.components.sensor import SensorDeviceClass +from homeassistant.components.sensor import SensorStateClass from homeassistant.components.event import EventDeviceClass +from homeassistant.const import ( + UnitOfEnergy, + UnitOfPower, + UnitOfElectricCurrent, + UnitOfElectricPotential, +) + # pylint: disable=pointless-string-statement """SPEC_DEVICE_TRANS_MAP { @@ -302,7 +310,11 @@ 'properties': { '':{ 'device_class': str, - 'entity': str + 'entity': str, + 'optional':{ + 'state_class': str, + 'unit_of_measurement': str + } } } } @@ -358,7 +370,11 @@ }, 'voltage': { 'device_class': SensorDeviceClass.VOLTAGE, - 'entity': 'sensor' + 'entity': 'sensor', + 'optional': { + 'state_class': SensorStateClass.MEASUREMENT, + 'unit_of_measurement': UnitOfElectricPotential.VOLT + } }, 'illumination': { 'device_class': SensorDeviceClass.ILLUMINANCE, @@ -368,6 +384,38 @@ 'device_class': SensorDeviceClass.DURATION, 'entity': 'sensor' }, + 'electric-power': { + 'device_class': SensorDeviceClass.POWER, + 'entity': 'sensor', + 'optional': { + 'state_class': SensorStateClass.MEASUREMENT, + 'unit_of_measurement': UnitOfPower.WATT + } + }, + 'electric-current': { + 'device_class': SensorDeviceClass.CURRENT, + 'entity': 'sensor', + 'optional': { + 'state_class': SensorStateClass.MEASUREMENT, + 'unit_of_measurement': UnitOfElectricCurrent.AMPERE + } + }, + 'power-consumption': { + 'device_class': SensorDeviceClass.ENERGY, + 'entity': 'sensor', + 'optional': { + 'state_class': SensorStateClass.TOTAL_INCREASING, + 'unit_of_measurement': UnitOfEnergy.KILO_WATT_HOUR + } + }, + 'total-battery': { + 'device_class': SensorDeviceClass.ENERGY, + 'entity': 'sensor', + 'optional': { + 'state_class': SensorStateClass.TOTAL_INCREASING, + 'unit_of_measurement': UnitOfEnergy.KILO_WATT_HOUR + } + }, 'has-someone-duration': 'no-one-determine-time', 'no-one-duration': 'no-one-determine-time' } From 679af9dd62519ddbc21d06b3050e75d73d7b1a63 Mon Sep 17 00:00:00 2001 From: tiger Date: Tue, 17 Dec 2024 19:25:13 +0800 Subject: [PATCH 3/6] feat:add support for state_class and unit process --- .../xiaomi_home/miot/miot_device.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_device.py b/custom_components/xiaomi_home/miot/miot_device.py index 56637d6..a08d899 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -515,9 +515,21 @@ def parse_miot_property_entity( prop.icon = self.icon_convert(prop.unit) device_class = SPEC_PROP_TRANS_MAP['properties'][prop_name][ 'device_class'] - prop.platform = device_class + + result = {'platform': platform, 'device_class': device_class} + + # optional: + prop_optional = SPEC_PROP_TRANS_MAP['properties'][prop_name].get('optional') + if prop_optional: + prop_optional_state_class = prop_optional.get('state_class') + if prop_optional_state_class: + result['state_class'] = prop_optional_state_class + + prop_optional_unit = prop_optional.get('unit_of_measurement') + if prop_optional_unit and not prop.unit: + result['unit_of_measurement'] = prop_optional_unit - return {'platform': platform, 'device_class': device_class} + return result def spec_transform(self) -> None: """Parse service, property, event, action from device spec.""" @@ -544,6 +556,12 @@ def spec_transform(self) -> None: if prop_entity: prop.platform = prop_entity['platform'] prop.device_class = prop_entity['device_class'] + if 'state_class' in prop_entity: + prop.state_class = prop_entity['state_class'] + if 'unit_of_measurement' in prop_entity: + prop.external_unit = self.unit_convert(prop_entity['unit_of_measurement']) + prop.icon = self.icon_convert(prop_entity['unit_of_measurement']) + # general conversion if not prop.platform: if prop.writable: From fc0dedb3991689da5f1d7c5f38903c23c31d0b51 Mon Sep 17 00:00:00 2001 From: tiger Date: Tue, 17 Dec 2024 21:00:30 +0800 Subject: [PATCH 4/6] style: fix pylint format --- .../xiaomi_home/miot/miot_device.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_device.py b/custom_components/xiaomi_home/miot/miot_device.py index a08d899..994f746 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -515,20 +515,17 @@ def parse_miot_property_entity( prop.icon = self.icon_convert(prop.unit) device_class = SPEC_PROP_TRANS_MAP['properties'][prop_name][ 'device_class'] - result = {'platform': platform, 'device_class': device_class} - # optional: - prop_optional = SPEC_PROP_TRANS_MAP['properties'][prop_name].get('optional') - if prop_optional: - prop_optional_state_class = prop_optional.get('state_class') + optional = SPEC_PROP_TRANS_MAP['properties'][prop_name].get('optional') + if optional: + prop_optional_state_class = optional.get('state_class') if prop_optional_state_class: result['state_class'] = prop_optional_state_class - prop_optional_unit = prop_optional.get('unit_of_measurement') + prop_optional_unit = optional.get('unit_of_measurement') if prop_optional_unit and not prop.unit: - result['unit_of_measurement'] = prop_optional_unit - + result['unit_of_measurement'] = prop_optional_unit return result def spec_transform(self) -> None: @@ -559,9 +556,10 @@ def spec_transform(self) -> None: if 'state_class' in prop_entity: prop.state_class = prop_entity['state_class'] if 'unit_of_measurement' in prop_entity: - prop.external_unit = self.unit_convert(prop_entity['unit_of_measurement']) - prop.icon = self.icon_convert(prop_entity['unit_of_measurement']) - + prop.external_unit = self.unit_convert( + prop_entity['unit_of_measurement']) + prop.icon = self.icon_convert( + prop_entity['unit_of_measurement']) # general conversion if not prop.platform: if prop.writable: From a6a73856c9cc32f03c9ec04d8fc4466ada48c7c0 Mon Sep 17 00:00:00 2001 From: tiger Date: Wed, 18 Dec 2024 17:38:57 +0800 Subject: [PATCH 5/6] style: fix pylint format --- custom_components/xiaomi_home/miot/miot_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/xiaomi_home/miot/miot_device.py b/custom_components/xiaomi_home/miot/miot_device.py index 994f746..5071ca9 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -559,7 +559,7 @@ def spec_transform(self) -> None: prop.external_unit = self.unit_convert( prop_entity['unit_of_measurement']) prop.icon = self.icon_convert( - prop_entity['unit_of_measurement']) + prop_entity['unit_of_measurement']) # general conversion if not prop.platform: if prop.writable: From 07716ec9a8d74ba55467202d6514f9aab93d3b41 Mon Sep 17 00:00:00 2001 From: DoraTiger Date: Thu, 26 Dec 2024 20:12:05 +0800 Subject: [PATCH 6/6] sytle: change logic to suit conversation --- custom_components/xiaomi_home/miot/miot_device.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_device.py b/custom_components/xiaomi_home/miot/miot_device.py index 7af32f0..6356118 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -517,15 +517,12 @@ def parse_miot_property_entity( 'device_class'] result = {'platform': platform, 'device_class': device_class} # optional: - optional = SPEC_PROP_TRANS_MAP['properties'][prop_name].get('optional') - if optional: - prop_optional_state_class = optional.get('state_class') - if prop_optional_state_class: - result['state_class'] = prop_optional_state_class - - prop_optional_unit = optional.get('unit_of_measurement') - if prop_optional_unit and not prop.unit: - result['unit_of_measurement'] = prop_optional_unit + if 'optional' in SPEC_PROP_TRANS_MAP['properties'][prop_name]: + optional = SPEC_PROP_TRANS_MAP['properties'][prop_name]['optional'] + if 'state_class' in optional: + result['state_class'] = optional['state_class'] + if not prop.unit and 'unit_of_measurement' in optional: + result['unit_of_measurement'] = optional['unit_of_measurement'] return result def spec_transform(self) -> None: