diff --git a/custom_components/xiaomi_miot/core/device.py b/custom_components/xiaomi_miot/core/device.py index 20ac18547..ebfe51d7e 100644 --- a/custom_components/xiaomi_miot/core/device.py +++ b/custom_components/xiaomi_miot/core/device.py @@ -548,7 +548,7 @@ async def init_miot_coordinators(self, interval=60): all_mapping = {**self.miot_mapping()} chunks = self.custom_config_list('chunk_coordinators') or [] - if self.miio2miot: + if self.miio2miot and not self.miio2miot.config.get('extend_miot_props'): chunks = [] def update_factory(mapping, notify=False, chunk_services=None): @@ -856,7 +856,25 @@ async def update_miot_status( if use_local: try: if self.miio2miot: - results = await self.miio2miot.async_get_miot_props(self.local, mapping) + if self.miio2miot.config.get('extend_miot_props'): + miio_specs = self.miio2miot.specs + local_mapping = { + k: v for k, v in mapping.items() + if MiotSpec.unique_prop(v, valid=True) not in miio_specs + } + if not max_properties: + max_properties = self.custom_config_integer('chunk_properties') + if not max_properties: + max_properties = self.local.get_max_properties(local_mapping) + if local_mapping: + results = await self.local.async_get_properties_for_mapping( + max_properties=max_properties, + did=self.did, + mapping=local_mapping, + ) + results.extend(await self.miio2miot.async_get_miot_props(self.local, mapping)) + else: + results = await self.miio2miot.async_get_miot_props(self.local, mapping) if attrs := self.miio2miot.entity_attrs(): self.props.update(attrs) self.dispatch(self.decode_attrs(attrs)) @@ -1049,13 +1067,19 @@ async def async_set_properties(self, params): if not self._local_state or self.cloud_only or cloud_write: cloud_params = params elif self.miio2miot: + local_params = [] for param in params: siid = param['siid'] piid = param['piid'] if not self.miio2miot.has_setter(siid, piid=piid): - cloud_params.append(param) + if self.miio2miot.config.get('extend_miot_props'): + local_params.append(param) + else: + cloud_params.append(param) continue results.append(await self.miio2miot.async_set_property(self.local, siid, piid, param['value'])) + if self.local and local_params: + results.extend(await self.local.async_send('set_properties', local_params) or []) elif self.local: results = await self.local.async_send('set_properties', params) if self.cloud and cloud_params: @@ -1306,22 +1330,30 @@ async def update_miio_props(self, props=None): return if props is None: props = self.custom_miio_properties + if not props: + return if self.miio2miot: - attrs = self.miio2miot.only_miio_props(props) + raw = self.miio2miot.only_miio_props(props) + if not isinstance(raw, dict): + raw = dict(zip(props, raw)) else: try: num = self.custom_config_integer('chunk_properties') or 15 - attrs = await self.local.async_get_properties(props, max_properties=num) + raw = await self.local.async_get_properties(props, max_properties=num) except DeviceException as exc: self.log.warning('%s: Got miio properties %s failed: %s', self.name_model, props, exc) return - if len(props) != len(attrs): + if len(props) != len(raw): self.props.update({ - 'miio.props': attrs, + 'miio.props': raw, }) return - attrs = dict(zip(map(lambda x: f'miio.{x}', props), attrs)) - self.props.update(attrs) + raw = dict(zip(props, raw)) + attrs = dict(zip(map(lambda x: f'miio.{x}', props), raw.values())) + if attrs: + self.props.update(attrs) + self.data['updated'] = dt.now() + self.dispatch(self.decode_attrs(attrs)) self.log.info('%s: Got miio properties: %s', self.name_model, attrs) @cached_property diff --git a/custom_components/xiaomi_miot/core/device_customizes.py b/custom_components/xiaomi_miot/core/device_customizes.py index 98bc3b575..7d54a78ab 100644 --- a/custom_components/xiaomi_miot/core/device_customizes.py +++ b/custom_components/xiaomi_miot/core/device_customizes.py @@ -2703,6 +2703,9 @@ 'yeelink.curtain.ctmt2': { **CHUNK_1, 'chunk_coordinators': [], + 'sensor_properties': 'battery_level,battery_level_2', + 'miio_cloud_props': 'get_child,battery,battery2', + 'miio_cloud_props_template': 'yeelink_curtain_ctmt2_cloud_props', }, 'yeelink.bhf_light.v1': { 'interval_seconds': 30, diff --git a/custom_components/xiaomi_miot/core/miio2miot_specs.py b/custom_components/xiaomi_miot/core/miio2miot_specs.py index f1fd6383c..36f92ccb5 100644 --- a/custom_components/xiaomi_miot/core/miio2miot_specs.py +++ b/custom_components/xiaomi_miot/core/miio2miot_specs.py @@ -2092,6 +2092,18 @@ def cbk(prop, params, props, **kwargs): }, }, + 'yeelink.curtain.ctmt2': { + 'extend_miot_props': True, + 'miio_props': ['get_child'], + 'miio_specs': { + 'prop.3.100': {'prop': 'battery'}, + 'prop.3.101': { + 'prop': 'battery2', + 'template': '{{ value|int(0) if props.get_child|int(1) == 2 else none }}', + }, + }, + }, + 'yeelink.light._base': { 'miio_specs': { 'prop.2.1': {'prop': 'power', 'setter': True, 'format': 'onoff'}, diff --git a/custom_components/xiaomi_miot/core/miot_specs_extend.json b/custom_components/xiaomi_miot/core/miot_specs_extend.json index 7302185b7..c93f1f923 100644 --- a/custom_components/xiaomi_miot/core/miot_specs_extend.json +++ b/custom_components/xiaomi_miot/core/miot_specs_extend.json @@ -1945,6 +1945,32 @@ } ] } + ], + + "yeelink.curtain.ctmt2": [ + { + "iid": 3, + "properties": [ + { + "iid": 100, + "type": "urn:miot-spec-v2:property:battery-level", + "format": "uint8", + "access": ["read"], + "unit": "percentage", + "value-range": [0, 100, 1], + "description": "Left" + }, + { + "iid": 101, + "type": "urn:miot-spec-v2:property:battery-level-2", + "format": "uint8", + "access": ["read"], + "unit": "percentage", + "value-range": [0, 100, 1], + "description": "Right" + } + ] + } ] } diff --git a/custom_components/xiaomi_miot/core/templates.py b/custom_components/xiaomi_miot/core/templates.py index ab3dfa8ed..3f8558ed5 100644 --- a/custom_components/xiaomi_miot/core/templates.py +++ b/custom_components/xiaomi_miot/core/templates.py @@ -168,6 +168,14 @@ "'coolwind_gear': val[1]," "'venting_gear': val[2]," "} }}", + 'yeelink_curtain_ctmt2_cloud_props': "{%- set child = props.get('prop.get_child', 1) | int(1) %}" + "{%- set bat = props.get('prop.battery') %}" + "{%- set bat2 = props.get('prop.battery2') %}" + "{{ {" + "'yl_curtain.battery_level': bat | int(0) if bat is not none else none," + "'yl_curtain.battery_level_2': bat2 | int(0)" + " if child == 2 and bat2 is not none else none," + "} }}", 'zimi_powerstrip_v2_power_cost': "{%- set val = (result.0 | default({})).get('value','[0]') %}" "{%- set day = now().day %}" "{%- set vls = (val | from_json)[0-day:] %}"