From 2703d68920a67dd270c9b5e7af221993f836505b Mon Sep 17 00:00:00 2001 From: ted <44641251+zghnwsq@users.noreply.github.com> Date: Fri, 3 Jan 2025 18:59:24 +0800 Subject: [PATCH 1/6] docs: fix table header misplacement (#554) fix table header misplacement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 872808a..642c499 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ In MIoT-Spec-V2 protocol, a product is defined as a device. A device contains se - Property -| format | access | value-list | value-range | Entity in Home Assistant | +| access | format | value-list | value-range | Entity in Home Assistant | | ------------ | --------------------- | ------------ | ----------- | ------------------------ | | writable | string | - | - | Text | | writable | bool | - | - | Switch | From da90e099d19e9838c785714ed90ba05ffb5b1d94 Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Fri, 3 Jan 2025 19:00:11 +0800 Subject: [PATCH 2/6] fix: update miot cloud raise error msg (#551) --- custom_components/xiaomi_home/miot/miot_cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/xiaomi_home/miot/miot_cloud.py b/custom_components/xiaomi_home/miot/miot_cloud.py index 751defd..0cfc272 100644 --- a/custom_components/xiaomi_home/miot/miot_cloud.py +++ b/custom_components/xiaomi_home/miot/miot_cloud.py @@ -166,7 +166,7 @@ class MIoTOauthClient: key in res_obj['result'] for key in ['access_token', 'refresh_token', 'expires_in']) ): - raise MIoTOauthError(f'invalid http response, {http_res.text}') + raise MIoTOauthError(f'invalid http response, {res_str}') return { **res_obj['result'], From 5438698a6e0f91f16f0c8f89a2adc335da89d7c6 Mon Sep 17 00:00:00 2001 From: tiger Date: Fri, 3 Jan 2025 20:42:34 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat=EF=BC=9Aadd=20missing=20parameter=20st?= =?UTF-8?q?ate=5Fclass=20=20(#101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add state_class for sensor * feat: add optional state_class and unit_of_measurement for properties * feat:add support for state_class and unit process * style: fix pylint format * style: fix pylint format * sytle: change logic to suit conversation --------- Co-authored-by: LiShuzhen --- .../xiaomi_home/miot/miot_device.py | 19 +++++-- .../xiaomi_home/miot/miot_spec.py | 2 + .../xiaomi_home/miot/specs/specv2entity.py | 52 ++++++++++++++++++- custom_components/xiaomi_home/sensor.py | 3 ++ 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/custom_components/xiaomi_home/miot/miot_device.py b/custom_components/xiaomi_home/miot/miot_device.py index 47e2bc1..353b28f 100644 --- a/custom_components/xiaomi_home/miot/miot_device.py +++ b/custom_components/xiaomi_home/miot/miot_device.py @@ -515,9 +515,15 @@ class MIoTDevice: prop.icon = self.icon_convert(prop.unit) device_class = SPEC_PROP_TRANS_MAP['properties'][prop_name][ 'device_class'] - prop.platform = device_class - - return {'platform': platform, 'device_class': device_class} + result = {'platform': platform, 'device_class': device_class} + # optional: + 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: """Parse service, property, event, action from device spec.""" @@ -544,6 +550,13 @@ class MIoTDevice: 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: diff --git a/custom_components/xiaomi_home/miot/miot_spec.py b/custom_components/xiaomi_home/miot/miot_spec.py index bae7e79..33022a1 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 @@ class MIoTSpecBase: 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/miot/specs/specv2entity.py b/custom_components/xiaomi_home/miot/specs/specv2entity.py index 4a57867..d79d80a 100644 --- a/custom_components/xiaomi_home/miot/specs/specv2entity.py +++ b/custom_components/xiaomi_home/miot/specs/specv2entity.py @@ -46,8 +46,16 @@ off Xiaomi or its affiliates' products. 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 { @@ -325,7 +333,11 @@ SPEC_SERVICE_TRANS_MAP: dict[str, dict | str] = { 'properties': { '':{ 'device_class': str, - 'entity': str + 'entity': str, + 'optional':{ + 'state_class': str, + 'unit_of_measurement': str + } } } } @@ -381,7 +393,11 @@ SPEC_PROP_TRANS_MAP: dict[str, dict | str] = { }, 'voltage': { 'device_class': SensorDeviceClass.VOLTAGE, - 'entity': 'sensor' + 'entity': 'sensor', + 'optional': { + 'state_class': SensorStateClass.MEASUREMENT, + 'unit_of_measurement': UnitOfElectricPotential.VOLT + } }, 'illumination': { 'device_class': SensorDeviceClass.ILLUMINANCE, @@ -391,6 +407,38 @@ SPEC_PROP_TRANS_MAP: dict[str, dict | str] = { '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' } diff --git a/custom_components/xiaomi_home/sensor.py b/custom_components/xiaomi_home/sensor.py index b5ff00d..39b3bdb 100644 --- a/custom_components/xiaomi_home/sensor.py +++ b/custom_components/xiaomi_home/sensor.py @@ -106,6 +106,9 @@ class Sensor(MIoTPropertyEntity, SensorEntity): # 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 b75cafb18428a82ee730a5767b7b6cc860afd43b Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:43:15 +0800 Subject: [PATCH 4/6] fix: limit *light.mode count (value-range) (#535) * feat: spec filter add lemesh.switch.sw3f13 indicator-light.mode * feat: limit mode count (value-range) * revert: spec-filter.json * fix: fix judgment logic * style: remove unuse code * style: change light log level --- custom_components/xiaomi_home/light.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/custom_components/xiaomi_home/light.py b/custom_components/xiaomi_home/light.py index bcfef6c..666464e 100644 --- a/custom_components/xiaomi_home/light.py +++ b/custom_components/xiaomi_home/light.py @@ -95,6 +95,7 @@ async def async_setup_entry( class Light(MIoTServiceEntity, LightEntity): """Light entities for Xiaomi Home.""" # pylint: disable=unused-argument + _VALUE_RANGE_MODE_COUNT_MAX = 30 _prop_on: Optional[MIoTSpecProperty] _prop_brightness: Optional[MIoTSpecProperty] _prop_color_temp: Optional[MIoTSpecProperty] @@ -147,13 +148,13 @@ class Light(MIoTServiceEntity, LightEntity): self._attr_supported_features |= LightEntityFeature.EFFECT self._prop_mode = prop else: - _LOGGER.error( + _LOGGER.info( 'invalid brightness format, %s', self.entity_id) continue # color-temperature if prop.name == 'color-temperature': if not isinstance(prop.value_range, dict): - _LOGGER.error( + _LOGGER.info( 'invalid color-temperature value_range format, %s', self.entity_id) continue @@ -179,16 +180,29 @@ class Light(MIoTServiceEntity, LightEntity): for item in prop.value_list} elif isinstance(prop.value_range, dict): mode_list = {} - for value in range( - prop.value_range['min'], prop.value_range['max']): - mode_list[value] = f'{value}' + if ( + int(( + prop.value_range['max'] + - prop.value_range['min'] + ) / prop.value_range['step']) + > self._VALUE_RANGE_MODE_COUNT_MAX + ): + _LOGGER.info( + 'too many mode values, %s, %s, %s', + self.entity_id, prop.name, prop.value_range) + else: + for value in range( + prop.value_range['min'], + prop.value_range['max'], + prop.value_range['step']): + mode_list[value] = f'mode {value}' if mode_list: self._mode_list = mode_list self._attr_effect_list = list(self._mode_list.values()) self._attr_supported_features |= LightEntityFeature.EFFECT self._prop_mode = prop else: - _LOGGER.error('invalid mode format, %s', self.entity_id) + _LOGGER.info('invalid mode format, %s', self.entity_id) continue if not self._attr_supported_color_modes: From 50be2c5df97da60b6a7512f740e2155227827d50 Mon Sep 17 00:00:00 2001 From: Feng Wang Date: Fri, 3 Jan 2025 20:45:00 +0800 Subject: [PATCH 5/6] doc: make git update guide more accurate (#561) * Improve installation guide * Improve installation guide (zh) --- README.md | 1 + doc/README_zh.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 642c499..d0cdc97 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ For example, update to version v1.0.0 ```bash cd config/ha_xiaomi_home +git fetch git checkout v1.0.0 ./install.sh /config ``` diff --git a/doc/README_zh.md b/doc/README_zh.md index b237b8b..438771f 100644 --- a/doc/README_zh.md +++ b/doc/README_zh.md @@ -26,6 +26,7 @@ cd ha_xiaomi_home ```bash cd config/ha_xiaomi_home +git fetch git checkout v1.0.0 ./install.sh /config ``` From d65fe32a98058bee100a1759ef3ff732616e16b2 Mon Sep 17 00:00:00 2001 From: Paul Shawn <32349595+topsworld@users.noreply.github.com> Date: Fri, 3 Jan 2025 21:08:39 +0800 Subject: [PATCH 6/6] docs: update changelog and version to v0.1.5b0 (#563) * style: code format * docs: update changelog and version to v0.1.5b0 * docs: update changelog --- CHANGELOG.md | 10 ++++++++++ custom_components/xiaomi_home/manifest.json | 2 +- .../xiaomi_home/miot/specs/specv2entity.py | 16 ++++++++-------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d4f36d..22fdb9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # CHANGELOG +## v0.1.5b0 +### Added +- Add missing parameter state_class [#101](https://github.com/XiaoMi/ha_xiaomi_home/pull/101) +### Changed +- Make git update guide more accurate [#561](https://github.com/XiaoMi/ha_xiaomi_home/pull/561) +### Fixed +- Limit *light.mode count (value-range) [#535](https://github.com/XiaoMi/ha_xiaomi_home/pull/535) +- Update miot cloud raise error msg [#551](https://github.com/XiaoMi/ha_xiaomi_home/pull/551) +- Fix table header misplacement [#554](https://github.com/XiaoMi/ha_xiaomi_home/pull/554) + ## v0.1.4 ### Added - Refactor miot network, add network detection logic, improve devices filter logic. [458](https://github.com/XiaoMi/ha_xiaomi_home/pull/458) [#191](https://github.com/XiaoMi/ha_xiaomi_home/pull/191) diff --git a/custom_components/xiaomi_home/manifest.json b/custom_components/xiaomi_home/manifest.json index 79a6bdb..3e07f1c 100644 --- a/custom_components/xiaomi_home/manifest.json +++ b/custom_components/xiaomi_home/manifest.json @@ -25,7 +25,7 @@ "cryptography", "psutil" ], - "version": "v0.1.4", + "version": "v0.1.5b0", "zeroconf": [ "_miot-central._tcp.local." ] diff --git a/custom_components/xiaomi_home/miot/specs/specv2entity.py b/custom_components/xiaomi_home/miot/specs/specv2entity.py index d79d80a..9763970 100644 --- a/custom_components/xiaomi_home/miot/specs/specv2entity.py +++ b/custom_components/xiaomi_home/miot/specs/specv2entity.py @@ -407,38 +407,38 @@ SPEC_PROP_TRANS_MAP: dict[str, dict | str] = { 'device_class': SensorDeviceClass.DURATION, 'entity': 'sensor' }, - 'electric-power': { + 'electric-power': { 'device_class': SensorDeviceClass.POWER, 'entity': 'sensor', 'optional': { 'state_class': SensorStateClass.MEASUREMENT, 'unit_of_measurement': UnitOfPower.WATT } - }, - 'electric-current': { + }, + 'electric-current': { 'device_class': SensorDeviceClass.CURRENT, 'entity': 'sensor', 'optional': { 'state_class': SensorStateClass.MEASUREMENT, 'unit_of_measurement': UnitOfElectricCurrent.AMPERE } - }, - 'power-consumption': { + }, + 'power-consumption': { 'device_class': SensorDeviceClass.ENERGY, 'entity': 'sensor', 'optional': { 'state_class': SensorStateClass.TOTAL_INCREASING, 'unit_of_measurement': UnitOfEnergy.KILO_WATT_HOUR } - }, - 'total-battery': { + }, + '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' }