From 7375ccebb639ed5692ee1fb64a4b069248005338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=89=E7=9C=9F?= Date: Sun, 31 May 2026 12:33:00 +0800 Subject: [PATCH 1/5] Fix: Aqara Curtain C4 can not control --- custom_components/aqara_gateway/core/utils.py | 2 +- custom_components/aqara_gateway/cover.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/custom_components/aqara_gateway/core/utils.py b/custom_components/aqara_gateway/core/utils.py index 8af8ce2..453936e 100755 --- a/custom_components/aqara_gateway/core/utils.py +++ b/custom_components/aqara_gateway/core/utils.py @@ -980,10 +980,10 @@ ['0.58.85', 'curtain_ch1_level', 'position', None], ['13.4.85', 'run_status', 'run_state', None], ['13.11.85', 'ch0_run_state', 'run_state', None], + ['13.21.85', 'ch1_run_state', 'run_state', None], ['0.21.85', '0.21.85', '0.21.85', None], ['13.14.85', '13.14.85', '13.14.85', None], ['13.15.85', '13.15.85', '13.15.85', None], - ['13.21.85', 'ch1_run_state', 'run_state', None], ['13.13.85', None, 'mode', None], ['14.11.85', None, 'ch0_polarity', None], ['14.21.85', None, 'ch1_polarity', None], diff --git a/custom_components/aqara_gateway/cover.py b/custom_components/aqara_gateway/cover.py index eb1de20..4ae8231 100755 --- a/custom_components/aqara_gateway/cover.py +++ b/custom_components/aqara_gateway/cover.py @@ -56,6 +56,8 @@ def setup(gateway: Gateway, device: dict, attr: str): async_add_entities([AqaraRollerShadeE1(gateway, device, attr)]) elif device['model'] == 'lumi.curtain.acn011': async_add_entities([AqaraVerticalBlindsController(gateway, device, attr)]) + elif device['model'] == 'lumi.curtain.acn010': + async_add_entities([AqaraCurtainMotorC4(gateway, device, attr)]) else: if device.get('mi_spec') or device['model'] == 'lumi.airer.acn001': async_add_entities([XiaomiCoverMIOT(gateway, device, attr)]) @@ -295,3 +297,31 @@ def set_cover_tilt_position(self, **kwargs): def stop_cover_tilt(self, **kwargs: Any) -> None: self.gateway.send(self.device, {'tilt_motor': 2}) + + +class AqaraCurtainMotorC4(XiaomiGenericCover): + """Aqara curtain motor C4 (lumi.curtain.acn010).""" + + _attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) + + + def close_cover(self, **kwargs): + """Close the cover.""" + self.gateway.send(self.device, {self._attr: 0}) + + def open_cover(self, **kwargs): + """Open the cover.""" + self.gateway.send(self.device, {self._attr: 100}) + + def stop_cover(self, **kwargs): + """Stop is not supported by this device mapping.""" + return None + + def set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + position = kwargs.get(ATTR_POSITION) + self.gateway.send(self.device, {self._attr: position}) From 94c00f566626d7e277a1580ff87959edafafa6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=89=E7=9C=9F?= Date: Sun, 31 May 2026 12:58:19 +0800 Subject: [PATCH 2/5] Fix: state mapping for Aqara Curtain C4 --- custom_components/aqara_gateway/core/utils.py | 8 ++++---- custom_components/aqara_gateway/cover.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/custom_components/aqara_gateway/core/utils.py b/custom_components/aqara_gateway/core/utils.py index 453936e..fb511f0 100755 --- a/custom_components/aqara_gateway/core/utils.py +++ b/custom_components/aqara_gateway/core/utils.py @@ -976,11 +976,11 @@ }, { 'lumi.curtain.acn010': ["Aqara", "Organ™ Smart Curtain Motor C4", "DSKDJ11LM"], 'params': [ - ['0.57.85', 'curtain_ch0_level', 'position', None], - ['0.58.85', 'curtain_ch1_level', 'position', None], + ['0.57.85', 'curtain_ch0_level', 'ch0_position', None], + ['0.58.85', 'curtain_ch1_level', 'ch1_position', None], ['13.4.85', 'run_status', 'run_state', None], - ['13.11.85', 'ch0_run_state', 'run_state', None], - ['13.21.85', 'ch1_run_state', 'run_state', None], + ['13.11.85', 'ch0_run_state', 'ch0_run_state', None], + ['13.21.85', 'ch1_run_state', 'ch1_run_state', None], ['0.21.85', '0.21.85', '0.21.85', None], ['13.14.85', '13.14.85', '13.14.85', None], ['13.15.85', '13.15.85', '13.15.85', None], diff --git a/custom_components/aqara_gateway/cover.py b/custom_components/aqara_gateway/cover.py index 4ae8231..39c4593 100755 --- a/custom_components/aqara_gateway/cover.py +++ b/custom_components/aqara_gateway/cover.py @@ -308,6 +308,23 @@ class AqaraCurtainMotorC4(XiaomiGenericCover): | CoverEntityFeature.SET_POSITION ) + def update(self, data: dict = None): + """Update only the matching channel state for this entity.""" + data = dict(data or {}) + data.pop(RUN_STATE, None) + + if self._attr == 'ch0_motor': + if 'ch0_position' in data: + data[POSITION] = data['ch0_position'] + if 'ch0_run_state' in data: + data[RUN_STATE] = data['ch0_run_state'] + elif self._attr == 'ch1_motor': + if 'ch1_position' in data: + data[POSITION] = data['ch1_position'] + if 'ch1_run_state' in data: + data[RUN_STATE] = data['ch1_run_state'] + + super().update(data) def close_cover(self, **kwargs): """Close the cover.""" From 1a0cb965a9e40467774ba29a3a0b0c6a2d1774ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=89=E7=9C=9F?= Date: Sun, 31 May 2026 13:18:04 +0800 Subject: [PATCH 3/5] Feature: expose virutal motor for simultaneously control two motors --- custom_components/aqara_gateway/core/utils.py | 1 + custom_components/aqara_gateway/cover.py | 59 +++++++++++++------ 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/custom_components/aqara_gateway/core/utils.py b/custom_components/aqara_gateway/core/utils.py index fb511f0..89846ed 100755 --- a/custom_components/aqara_gateway/core/utils.py +++ b/custom_components/aqara_gateway/core/utils.py @@ -988,6 +988,7 @@ ['14.11.85', None, 'ch0_polarity', None], ['14.21.85', None, 'ch1_polarity', None], ['14.35.85', None, 'speed', None], + ['14.2.85', None, 'motor', 'cover'], ['1.11.85', None, 'ch0_motor', 'cover'], ['1.21.85', None, 'ch1_motor', 'cover'], ] diff --git a/custom_components/aqara_gateway/cover.py b/custom_components/aqara_gateway/cover.py index 39c4593..a093f22 100755 --- a/custom_components/aqara_gateway/cover.py +++ b/custom_components/aqara_gateway/cover.py @@ -302,43 +302,64 @@ def stop_cover_tilt(self, **kwargs: Any) -> None: class AqaraCurtainMotorC4(XiaomiGenericCover): """Aqara curtain motor C4 (lumi.curtain.acn010).""" - _attr_supported_features = ( - CoverEntityFeature.OPEN - | CoverEntityFeature.CLOSE - | CoverEntityFeature.SET_POSITION - ) + def __init__(self, gateway, device, atrr): + if atrr == 'motor': + self._attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.STOP + ) + else: + self._attr_supported_features = ( + CoverEntityFeature.OPEN + | CoverEntityFeature.CLOSE + | CoverEntityFeature.SET_POSITION + ) + super().__init__(gateway, device, atrr) def update(self, data: dict = None): - """Update only the matching channel state for this entity.""" - data = dict(data or {}) - data.pop(RUN_STATE, None) - - if self._attr == 'ch0_motor': + """Update only the matching state for this entity.""" + payload = {} + data = data or {} + + if self._attr == 'motor': + if RUN_STATE in data: + payload[RUN_STATE] = data[RUN_STATE] + elif self._attr == 'ch0_motor': if 'ch0_position' in data: - data[POSITION] = data['ch0_position'] + payload[POSITION] = data['ch0_position'] if 'ch0_run_state' in data: - data[RUN_STATE] = data['ch0_run_state'] + payload[RUN_STATE] = data['ch0_run_state'] elif self._attr == 'ch1_motor': if 'ch1_position' in data: - data[POSITION] = data['ch1_position'] + payload[POSITION] = data['ch1_position'] if 'ch1_run_state' in data: - data[RUN_STATE] = data['ch1_run_state'] + payload[RUN_STATE] = data['ch1_run_state'] - super().update(data) + super().update(payload) def close_cover(self, **kwargs): """Close the cover.""" - self.gateway.send(self.device, {self._attr: 0}) + if self._attr == 'motor': + self.gateway.send(self.device, {'motor': 0}) + else: + self.gateway.send(self.device, {self._attr: 0}) def open_cover(self, **kwargs): """Open the cover.""" - self.gateway.send(self.device, {self._attr: 100}) + if self._attr == 'motor': + self.gateway.send(self.device, {'motor': 1}) + else: + self.gateway.send(self.device, {self._attr: 100}) def stop_cover(self, **kwargs): - """Stop is not supported by this device mapping.""" - return None + """Stop the cover.""" + if self._attr == 'motor': + self.gateway.send(self.device, {'motor': 2}) def set_cover_position(self, **kwargs): """Move the cover to a specific position.""" + if self._attr == 'motor': + return None position = kwargs.get(ATTR_POSITION) self.gateway.send(self.device, {self._attr: position}) From d2b47cc0fb347051803b06128d959540c2eb6de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=89=E7=9C=9F?= Date: Sun, 31 May 2026 13:35:24 +0800 Subject: [PATCH 4/5] Feature: support button domain --- custom_components/aqara_gateway/button.py | 41 +++++++++++++++++++ custom_components/aqara_gateway/core/const.py | 1 + 2 files changed, 42 insertions(+) create mode 100644 custom_components/aqara_gateway/button.py diff --git a/custom_components/aqara_gateway/button.py b/custom_components/aqara_gateway/button.py new file mode 100644 index 0000000..f4044cb --- /dev/null +++ b/custom_components/aqara_gateway/button.py @@ -0,0 +1,41 @@ +"""Support for Aqara buttons.""" + +from homeassistant.components.button import ButtonEntity + +from . import DOMAIN, GatewayGenericDevice +from .core.gateway import Gateway + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Perform the setup for Aqara buttons.""" + + def setup(gateway: Gateway, device: dict, attr: str): + async_add_entities([GatewayButton(gateway, device, attr)]) + + aqara_gateway: Gateway = hass.data[DOMAIN][config_entry.entry_id] + aqara_gateway.add_setup('button', setup) + + +async def async_unload_entry(hass, entry): + # pylint: disable=unused-argument + """Unload entry.""" + return True + + +class GatewayButton(GatewayGenericDevice, ButtonEntity): + """Representation of an Aqara button entity.""" + + @property + def icon(self): + """Return icon.""" + if self._attr == 'find_device': + return 'mdi:radar' + return 'mdi:gesture-tap-button' + + def update(self, data: dict): + """Buttons are stateless.""" + return None + + def press(self) -> None: + """Press the button.""" + self.gateway.send(self.device, {self._attr: 1}) diff --git a/custom_components/aqara_gateway/core/const.py b/custom_components/aqara_gateway/core/const.py index 017ebba..b50352c 100755 --- a/custom_components/aqara_gateway/core/const.py +++ b/custom_components/aqara_gateway/core/const.py @@ -189,6 +189,7 @@ 'air_quality', 'alarm_control_panel', 'binary_sensor', + 'button', 'climate', 'cover', 'fan', From 17e622c849d8badca8acd135f04eedf3f4e93133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=89=E7=9C=9F?= Date: Sun, 31 May 2026 13:35:59 +0800 Subject: [PATCH 5/5] Feature: more auxiliary control on Aqara Curtain C4 --- custom_components/aqara_gateway/core/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/custom_components/aqara_gateway/core/utils.py b/custom_components/aqara_gateway/core/utils.py index 89846ed..64a02e9 100755 --- a/custom_components/aqara_gateway/core/utils.py +++ b/custom_components/aqara_gateway/core/utils.py @@ -987,10 +987,13 @@ ['13.13.85', None, 'mode', None], ['14.11.85', None, 'ch0_polarity', None], ['14.21.85', None, 'ch1_polarity', None], - ['14.35.85', None, 'speed', None], + ['14.35.85', None, 'speed', 'select'], ['14.2.85', None, 'motor', 'cover'], ['1.11.85', None, 'ch0_motor', 'cover'], ['1.21.85', None, 'ch1_motor', 'cover'], + ['4.3.85', None, 'manual_enable', 'switch'], # 手动开/关窗帘 + ['8.0.2032', None, 'en_night_tip_light', 'switch'], #指示灯勿扰,1是关闭指示灯 + ['8.0.2096', None, 'find_device', 'button'], # 查找设备 ] }, { 'lumi.curtain.acn011': ["Aqara", "Smart Vertical Blinds Controller H1", "ZNMHLDJ01LM"], @@ -2095,6 +2098,9 @@ def get_select_options(zigbee_model: str, attr: str) -> Optional[dict]: return {"Weak": 0, "Middle Weak": 1, "Middle": 2, "Middle Strong": 3, "Strong": 4} if attr == 'warn dry': return {"Off": 0, "Normal": 1, "Low": 2, "Middle Low": 3, "Middle": 4, "Middle High": 5, "High": 6} + if zigbee_model in ['lumi.curtain.acn010']: + if attr == 'speed': + return {"Low": 2, "Middle": 3, "High": 4} return {"Off": 0, "On": 1} @staticmethod