zigpy / zha-device-handlers

ZHA device handlers bridge the functionality gap created when manufacturers deviate from the ZCL specification, handling deviations and exceptions by parsing custom messages to and from Zigbee devices.
Apache License 2.0
734 stars 673 forks source link

[Device Support Request] Aqara H1 Knob (Wireless) - lumi.remote.rkba01 - ZNXNKG02LM #2266

Open oxc opened 1 year ago

oxc commented 1 year ago

This has been reported as https://github.com/zigpy/zha-device-handlers/issues/929 which has been closed as stale. It's still relevant, so I'll try to combine my own information with those from that bug report.

Is your feature request related to a problem? Please describe. When pairing the device to my Home Assistant ZHA integration using ConBee II, it only exposes LQI and RSSI sensors.

Describe the solution you'd like Support for: Battery, Dimmer, Press, Double Press, Press and Hold, Press and turn.

Device signature ```yaml { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4447, maximum_buffer_size=127, maximum_incoming_transfer_size=100, server_mask=11264, maximum_outgoing_transfer_size=100, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=True, *is_full_function_device=False, *is_mains_powered=True, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False)", "endpoints": { "1": { "profile_id": 260, "device_type": "0x0103", "in_clusters": [ "0x0000", "0x0001", "0x0003" ], "out_clusters": [ "0x0003", "0x0006", "0x0019" ] } }, "manufacturer": "LUMI", "model": "lumi.remote.rkba01", "class": "zigpy.device.Device" } ```
Diagnostic information ```yaml { "home_assistant": { "installation_type": "Home Assistant Supervised", "version": "2023.3.3", "dev": false, "hassio": true, "virtualenv": false, "python_version": "3.10.10", "docker": true, "arch": "aarch64", "timezone": "Europe/Berlin", "os_name": "Linux", "os_version": "5.15.76-v8+", "supervisor": "2023.03.1", "host_os": "Debian GNU/Linux 11 (bullseye)", "docker_version": "20.10.21", "chassis": "", "run_as_root": true }, "custom_components": { "google_home": { "version": "1.9.17", "requirements": [ "glocaltokens==0.7.0" ] }, "huesyncbox": { "version": "1.23.0", "requirements": [ "aiohuesyncbox==0.0.21" ] }, "average": { "version": "2.3.0", "requirements": [] }, "hacs": { "version": "1.31.0", "requirements": [ "aiogithubapi>=22.10.1" ] }, "iq_notify": { "version": "1.0.0", "requirements": [] } }, "integration_manifest": { "domain": "zha", "name": "Zigbee Home Automation", "after_dependencies": [ "onboarding", "usb" ], "codeowners": [ "@dmulcahey", "@adminiuga", "@puddly" ], "config_flow": true, "dependencies": [ "file_upload" ], "documentation": "https://www.home-assistant.io/integrations/zha", "iot_class": "local_polling", "loggers": [ "aiosqlite", "bellows", "crccheck", "pure_pcapy3", "zhaquirks", "zigpy", "zigpy_deconz", "zigpy_xbee", "zigpy_zigate", "zigpy_znp" ], "requirements": [ "bellows==0.34.9", "pyserial==3.5", "pyserial-asyncio==0.6", "zha-quirks==0.0.93", "zigpy-deconz==0.19.2", "zigpy==0.53.2", "zigpy-xbee==0.16.2", "zigpy-zigate==0.10.3", "zigpy-znp==0.9.3" ], "usb": [ { "vid": "10C4", "pid": "EA60", "description": "*2652*", "known_devices": [ "slae.sh cc2652rb stick" ] }, { "vid": "1A86", "pid": "55D4", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus v2" ] }, { "vid": "10C4", "pid": "EA60", "description": "*sonoff*plus*", "known_devices": [ "sonoff zigbee dongle plus" ] }, { "vid": "10C4", "pid": "EA60", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*tubeszb*", "known_devices": [ "TubesZB Coordinator" ] }, { "vid": "1A86", "pid": "7523", "description": "*zigstar*", "known_devices": [ "ZigStar Coordinators" ] }, { "vid": "1CF1", "pid": "0030", "description": "*conbee*", "known_devices": [ "Conbee II" ] }, { "vid": "10C4", "pid": "8A2A", "description": "*zigbee*", "known_devices": [ "Nortek HUSBZB-1" ] }, { "vid": "0403", "pid": "6015", "description": "*zigate*", "known_devices": [ "ZiGate+" ] }, { "vid": "10C4", "pid": "EA60", "description": "*zigate*", "known_devices": [ "ZiGate" ] }, { "vid": "10C4", "pid": "8B34", "description": "*bv 2010/10*", "known_devices": [ "Bitron Video AV2010/10" ] } ], "zeroconf": [ { "type": "_esphomelib._tcp.local.", "name": "tube*" }, { "type": "_zigate-zigbee-gateway._tcp.local.", "name": "*zigate*" }, { "type": "_zigstar_gw._tcp.local.", "name": "*zigstar*" }, { "type": "_slzb-06._tcp.local.", "name": "slzb-06*" } ], "is_built_in": true }, "data": { "ieee": "**REDACTED**", "nwk": 36806, "manufacturer": "LUMI", "model": "lumi.remote.rkba01", "name": "LUMI lumi.remote.rkba01", "quirk_applied": false, "quirk_class": "zigpy.device.Device", "manufacturer_code": 4447, "power_source": "Mains", "lqi": 255, "rssi": -52, "last_seen": "2023-03-10T11:37:52", "available": true, "device_type": "EndDevice", "signature": { "node_descriptor": "NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4447, maximum_buffer_size=127, maximum_incoming_transfer_size=100, server_mask=11264, maximum_outgoing_transfer_size=100, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=True, *is_full_function_device=False, *is_mains_powered=True, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False)", "endpoints": { "1": { "profile_id": 260, "device_type": "0x0103", "in_clusters": [ "0x0000", "0x0001", "0x0003" ], "out_clusters": [ "0x0003", "0x0006", "0x0019" ] } } }, "active_coordinator": false, "entities": [ { "entity_id": "button.lumi_lumi_remote_rkba01_identify", "name": "LUMI lumi.remote.rkba01" } ], "neighbors": [], "routes": [], "endpoint_names": [ { "name": "ON_OFF_LIGHT_SWITCH" } ], "user_given_name": null, "device_reg_id": "1a01e6da98077019a2705886bd4c257b", "area_id": null, "cluster_details": { "1": { "device_type": { "name": "ON_OFF_LIGHT_SWITCH", "id": 259 }, "profile_id": 260, "in_clusters": { "0x0000": { "endpoint_attribute": "basic", "attributes": { "0x0004": { "attribute_name": "manufacturer", "value": "LUMI" }, "0x0005": { "attribute_name": "model", "value": "lumi.remote.rkba01" } }, "unsupported_attributes": {} }, "0x0003": { "endpoint_attribute": "identify", "attributes": {}, "unsupported_attributes": {} }, "0x0001": { "endpoint_attribute": "power", "attributes": {}, "unsupported_attributes": {} } }, "out_clusters": { "0x0003": { "endpoint_attribute": "identify", "attributes": {}, "unsupported_attributes": {} }, "0x0019": { "endpoint_attribute": "ota", "attributes": {}, "unsupported_attributes": {} }, "0x0006": { "endpoint_attribute": "on_off", "attributes": {}, "unsupported_attributes": {} } } } } } } ```
Additional logs The following actions trigger a zha_event: Long Press: ``` event_type: zha_event data: device_ieee: 54:ef:44:10:00:7b:c2:b9 unique_id: 54:ef:44:10:00:7b:c2:b9:1:0x0006 device_id: 1a01e6da98077019a2705886bd4c257b endpoint_id: 1 cluster_id: 6 command: "off" args: [] params: {} origin: LOCAL time_fired: "2023-03-10T10:49:59.232221+00:00" context: id: 01GV5J8K60QF3P265DVBWWPS05 parent_id: null user_id: null ``` Double Press: ``` event_type: zha_event data: device_ieee: 54:ef:44:10:00:7b:c2:b9 unique_id: 54:ef:44:10:00:7b:c2:b9:1:0x0006 device_id: 1a01e6da98077019a2705886bd4c257b endpoint_id: 1 cluster_id: 6 command: toggle args: [] params: {} origin: LOCAL time_fired: "2023-03-10T10:50:38.945507+00:00" context: id: 01GV5J9SZ1ES9ZV2JSP9NS5K6Z parent_id: null user_id: null ```

Additional context Long Press and Double Press yield a zha_event. Single Press, as well as Turning the Knob yields no events.

Zigbee2MQTT implementation https://github.com/Koenkk/zigbee-herdsman-converters/blob/b06b48efee3df32a87bfa1b7f6fb2f00bda7aa15/devices/xiaomi.js#LL2934-L2956 ``` { zigbeeModel: ['lumi.remote.rkba01'], model: 'ZNXNKG02LM', vendor: 'Xiaomi', description: 'Aqara knob H1 (wireless)', meta: {battery: {voltageToPercentage: '3V_2850_3000'}}, exposes: [e.battery(), e.battery_voltage(), e.action(['single', 'double', 'hold', 'release', 'start_rotating', 'rotation', 'stop_rotating']), exposes.enum('operation_mode', ea.ALL, ['event', 'command']).withDescription('Button mode'), exposes.numeric('action_rotation_angle', ea.STATE).withUnit('*').withDescription('Rotation angle'), exposes.numeric('action_rotation_angle_speed', ea.STATE).withUnit('*').withDescription('Rotation angle speed'), exposes.numeric('action_rotation_percent', ea.STATE).withUnit('%').withDescription('Rotation percent'), exposes.numeric('action_rotation_percent_speed', ea.STATE).withUnit('%').withDescription('Rotation percent speed'), exposes.numeric('action_rotation_time', ea.STATE).withUnit('ms').withDescription('Rotation time'), ], fromZigbee: [fz.xiaomi_on_off_action, fz.xiaomi_multistate_action, fz.xiaomi_basic, fz.aqara_opple, fz.aqara_knob_rotation], toZigbee: [tz.aqara_opple_operation_mode], onEvent: preventReset, configure: async (device, coordinatorEndpoint, logger) => { const endpoint1 = device.getEndpoint(1); await endpoint1.write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f, disableResponse: true}); }, }, ```
oxc commented 1 year ago

Attached is my custom quirk

What works:

What doesn't work:

Other thoughts:

Source code: xiamo/aqara/remote_h1_knob.py ```python from typing import Optional, Union, List, Any import zigpy.types as t from zigpy.profiles import zha from zigpy.zcl import foundation from zigpy.zcl.clusters.general import Basic, Identify, OnOff, \ PowerConfiguration, Ota from zigpy.zdo.types import NodeDescriptor, LogicalType from zhaquirks.const import ( ALT_DOUBLE_PRESS, ARGS, BUTTON, COMMAND, COMMAND_OFF, COMMAND_TOGGLE, DEVICE_TYPE, DOUBLE_PRESS, ENDPOINT_ID, ENDPOINTS, INPUT_CLUSTERS, LONG_PRESS, MODELS_INFO, OUTPUT_CLUSTERS, PROFILE_ID, SHORT_PRESS, LONG_RELEASE, ZHA_SEND_EVENT, ALT_LONG_PRESS, NODE_DESCRIPTOR, ) from zhaquirks.xiaomi import ( LUMI, BasicCluster, XiaomiAqaraE1Cluster, XiaomiCustomDevice, ) from zhaquirks.xiaomi.aqara.opple_remote import ( COMMAND_1_DOUBLE, COMMAND_1_HOLD, COMMAND_1_SINGLE, MultistateInputCluster, COMMAND_1_RELEASE, ) from zhaquirks.xiaomi.aqara.remote_h1 import PowerConfigurationClusterH1Remote START_ROTATION = "start_rotation" ROTATION = "rotation" STOP_ROTATION = "stop_rotation" HOLD_START_ROTATION = "hold_start_rotation" HOLD_ROTATION = "hold_rotation" HOLD_STOP_ROTATION = "hold_stop_rotation" ROTATE_RIGHT = "rotate_right" ROTATE_LEFT = "rotate_left" HOLD_ROTATE_LEFT = "hold_rotate_left" HOLD_ROTATE_RIGHT = "hold_rotate_right" class KnobAction(t.enum8): """Knob action mode enum.""" off = 0x00 start_rotation = 0x01 rotation = 0x02 stop_rotation = 0x03 hold_start_rotation = 0x81 hold_rotation = 0x82 hold_stop_rotation = 0x83 class AqaraRemoteManuSpecificCluster(XiaomiAqaraE1Cluster): """Aqara manufacturer specific settings.""" ep_attribute = "aqara_cluster" # manufacture override code: 4447 (0x115f) # to get/set this attribute, you might need to click the button 5 times # quickly. attributes = XiaomiAqaraE1Cluster.attributes.copy() attributes.update( { # operation_mode: # 0 means "command" mode. # 1 means "event" mode. 0x0009: ("operation_mode", t.uint8_t, True), } ) class KnobManuSpecificCluster(XiaomiAqaraE1Cluster): """Aqara manufacturer specific settings.""" ep_attribute = "aqara_cluster" attributes = XiaomiAqaraE1Cluster.attributes.copy() attributes.update( { 0x022C: ("rotation_time_delta", t.uint16_t, True), 0x0231: ("rotation_time", t.uint32_t, True), #0x0238: ("unknown_0238", t.uint8_t, True), # always value=12, 0x0230: ("rotation_angle_delta", t.Single, True), 0x022E: ("rotation_angle", t.Single, True), 0x0232: ("rotation_percent_delta", t.Single, True), 0x0233: ("rotation_percent", t.Single, True), 0x023A: ("action", KnobAction, True), } ) def handle_cluster_general_request( self, header: foundation.ZCLHeader, args: List[Any], *, dst_addressing: Optional[ Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK] ] = None, ): """Handle the cluster command.""" self.info( "H1 knob general request - handle_cluster_general_request: header: %s - args: [%s]", header, args, ) super().handle_cluster_general_request(header, args, dst_addressing=dst_addressing) if header.command_id != foundation.GeneralCommand.Report_Attributes: return event_args = {} for attr in args.attribute_reports: if attr.attrid in self.attributes: attr_name = self.attributes[attr.attrid].name try: value = self.attributes[attr.attrid].type(attr.value.value) except ValueError: self.debug( "Couldn't normalize %s attribute with %s value", attr_name, attr.value.value, exc_info=True, ) value = attr.value.value event_args[attr_name] = value action = event_args.get("action") if not action: return command = action.name is_stop_action = action in ( KnobAction.stop_rotation, KnobAction.hold_stop_rotation ) # delta attributes are outdated (or 0) on stop_rotation, don't send them if is_stop_action: for attr in list(event_args.keys()): if attr.endswith("_delta"): del event_args[attr] self.listener_event(ZHA_SEND_EVENT, command, event_args) if is_stop_action: if event_args.get("rotation_angle") > 0: command = HOLD_ROTATE_RIGHT if action == KnobAction.hold_stop_rotation else ROTATE_RIGHT self.listener_event(ZHA_SEND_EVENT, command, event_args) elif event_args.get("rotation_angle") < 0: command = HOLD_ROTATE_LEFT if action == KnobAction.hold_stop_rotation else ROTATE_LEFT self.listener_event(ZHA_SEND_EVENT, command, event_args) class AqaraH1KnobWireless(XiaomiCustomDevice): """Aqara H1 Knob (Wireless)""" signature = { # NodeDescriptor(logical_type=, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=, mac_capability_flags=, manufacturer_code=4447, maximum_buffer_size=127, maximum_incoming_transfer_size=100, server_mask=11264, maximum_outgoing_transfer_size=100, descriptor_capability_field=, *allocate_address=True, *is_alternate_pan_coordinator=False, *is_coordinator=False, *is_end_device=True, *is_full_function_device=False, *is_mains_powered=True, *is_receiver_on_when_idle=False, *is_router=False, *is_security_capable=False) MODELS_INFO: [(LUMI, "lumi.remote.rkba01")], ENDPOINTS: { # "1": { # "profile_id": 260, # "device_type": "0x0103", # "in_clusters": ["0x0000","0x0001","0x0003"], # "out_clusters": ["0x0003","0x0006","0x0019"] 1: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT_SWITCH, INPUT_CLUSTERS: [ Basic.cluster_id, PowerConfiguration.cluster_id, Identify.cluster_id, ], OUTPUT_CLUSTERS: [ Identify.cluster_id, OnOff.cluster_id, Ota.cluster_id, ], }, }, } replacement = { # use custom NodeDescriptor to remove MainsPowered flag NODE_DESCRIPTOR: NodeDescriptor( logical_type=LogicalType.EndDevice, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=NodeDescriptor.FrequencyBand.Freq2400MHz, mac_capability_flags=NodeDescriptor.MACCapabilityFlags.AllocateAddress, # not MainsPowered, manufacturer_code=4447, maximum_buffer_size=127, maximum_incoming_transfer_size=100, server_mask=11264, maximum_outgoing_transfer_size=100, descriptor_capability_field=NodeDescriptor.DescriptorCapability.NONE, ), ENDPOINTS: { 1: { INPUT_CLUSTERS: [ BasicCluster, Identify.cluster_id, PowerConfigurationClusterH1Remote, MultistateInputCluster, AqaraRemoteManuSpecificCluster, ], OUTPUT_CLUSTERS: [ Identify.cluster_id, OnOff.cluster_id, Ota.cluster_id, ], }, 71: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.DIMMER_SWITCH, INPUT_CLUSTERS: [ KnobManuSpecificCluster, ], }, 72: { PROFILE_ID: zha.PROFILE_ID, DEVICE_TYPE: zha.DeviceType.SHADE_CONTROLLER, INPUT_CLUSTERS: [ KnobManuSpecificCluster, ], } }, } device_automation_triggers = { # triggers when operation_mode == event (SHORT_PRESS, BUTTON): {COMMAND: COMMAND_1_SINGLE}, (DOUBLE_PRESS, BUTTON): {COMMAND: COMMAND_1_DOUBLE}, (LONG_PRESS, BUTTON): {COMMAND: COMMAND_1_HOLD}, (LONG_RELEASE, BUTTON): {COMMAND: COMMAND_1_RELEASE}, (ROTATE_LEFT, BUTTON): {COMMAND: ROTATE_LEFT}, (ROTATE_RIGHT, BUTTON): {COMMAND: ROTATE_RIGHT}, (HOLD_ROTATE_LEFT, BUTTON): {COMMAND: HOLD_ROTATE_LEFT}, (HOLD_ROTATE_RIGHT, BUTTON): {COMMAND: HOLD_ROTATE_RIGHT}, # triggers when operation_mode == command ## single press does not trigger anything (ALT_DOUBLE_PRESS, BUTTON): {COMMAND: COMMAND_TOGGLE, ENDPOINT_ID: 1, ARGS: []}, (ALT_LONG_PRESS, BUTTON): {COMMAND: COMMAND_OFF, ENDPOINT_ID: 1, ARGS: []}, } ```
oxc commented 1 year ago

I've put the code into a gist to track the changes for now: https://gist.github.com/oxc/754d6436ce62d92af660d3671acd9346

I decided that I don't like the separate events for rotate_left/right, and added a rotation_direction arg. This feels more appropriate.

If I wanted to create a PR for this, what else would be needed?

nwithan8 commented 1 year ago

Just got mine today, good to know someone is working on this (as recently as 7 hours ago, no less!), since I have no idea how to.

EDIT: Quirk works great! One thing I noticed, the direction is determined based on the net gain in either direction. For example, turning the dial +1 click to the left and then +4 clicks to the right in one motion is a +3 net rotation right, so is considered "rotate right", even though I started by rotating left. If I rotate left +4 and then right +1, it's a +3 net rotation left, and that's considered "rotate left".

oxc commented 1 year ago

One thing I noticed, the direction is determined based on the net gain in either direction. For example, turning the dial +1 click to the left and then +4 clicks to the right in one motion is a +3 net rotation right, so is considered "rotate right", even though I started by rotating left. If I rotate left +4 and then right +1, it's a +3 net rotation left, and that's considered "rotate left".

That's correct. This is something that's handled internally by the device itself.

I thought that it sends a stop_rotation and a new start_rotation when the direction changes, but in my latest test (with a different Knob than the one I initially developed the quirk with), this was indeed not the case.

It's even funnier if you compare percent to angle: when you do two full turns to the left, you will be at -720deg but only at -100%, because that is capped at 100. If you now do 1.5 turns to the right, you will be at -180deg but +50% :) That's why I thought it would be nice to be able to change the sensitivity, but I'm no longer even sure if that's indeed controlled in the device, or just in the Aqara software that implements this quirk (probably the Hub, I guess?).

nwithan8 commented 1 year ago

In the meantime, was able to crank out a few blueprints for the device: https://community.home-assistant.io/t/aqara-h1-rotary-dimmer-switch-dial-remote/551909 https://community.home-assistant.io/t/aqara-h1-rotary-dimmer-switch-dial-media-controls/551917

Thanks @oxc for the great work, and I vouch that support for this should get adopted ASAP

sleepy-salmon commented 1 year ago

@oxc Thank you so much for your work on this. I think that I am having trouble because I am stuck in command mode but I've not been able to get it into event mode. Do you have any advice on how to achieve this? What I've tried so far is to go on manage zigbee device, do the quick 5 button press and then read attribute, but this fails. I try inputting the value as 1 and the manufacturer override code and writting the attribute, this gives me a tick but the operation of the device doesn't change. How I am I supposed to go about this correctly?

image

sleepy-salmon commented 1 year ago

My problem solved itself, it just decided to allow me to change the operation mode one day and now I've got it into event mode.

Thanks again for your quirk!

oxc commented 1 year ago

Sorry I didn't reply before. I don't remember how I did it exactly, I believe I pressed 5 times afterwards instead of before. IIRC, I had to press at least once before the commands would return. But I'm really not sure how exactly I didn't it 😂

0rangutan commented 1 year ago

@oxc - many thanks for supplying this. I have the quirk successfully applied according to the H1 knob's Device Zigbee info, but it still only shows Battery, Identify, LQI and RSSI in ZHA, no other capabilities. I have restarted HA and removed/re-added the device countless times. Are there any other steps that I need to complete? Many thanks

oxc commented 1 year ago

@0rangutan, if the quirk loaded correctly, you can now configure triggers for the device, and will also receive zha_events. Also have a look at the blueprints from @nwithan8 in the comment above.

0rangutan commented 1 year ago

Thanks - no zhe_events are firing and @nwithan8's blueprints are set up but not working. Any ideas?

karrui commented 1 year ago

Just got this knob too. Quirk works great for events. Cannot figure out how to continuously dim a light as the knob is turned though. Is that possible? Setting Decrease <light> brightness on turn event just reduces it by a static 10%, and only after the knob has stopped turning (and thus only when the Rotated event fires). Am I missing something?

oxc commented 1 year ago

You will have to use the "rotation" zha event for that.

karrui commented 1 year ago

You will have to use the "rotation" zha event for that.

Thanks for the quick reply! Good to know it was user error. Will experiment more.

oxc commented 1 year ago

Perhaps it makes sense to expose the ongoing rotation as events too. I would have appreciated some input from the maintainers on that, but that seems hard to come by.

nwithan8 commented 1 year ago

Thanks - no zhe_events are firing and @nwithan8's blueprints are set up but not working. Any ideas?

Hey, just noticed today that the some of the commands are reporting differently. Specifically, hold and double are now different (will update the blueprint accordingly), but single press isn't registering at all with ZHA (device lights up, so I know something got triggered).

EDIT: I think I figured it out, device got set into a different mode. Potentially the same issue for you @0rangutan

@oxc offhand, do you know how to toggle between the two modes?

https://gist.github.com/oxc/754d6436ce62d92af660d3671acd9346#file-remote_h1_knob-py-L253

EDIT 2: Code comments help, lol: https://gist.github.com/oxc/754d6436ce62d92af660d3671acd9346#file-remote_h1_knob-py-L77

Unfortunately, can't get my devices out of the alt mode. I have three of them and even the ones I haven't touched in forever are now in this alt mode. I'm starting to suspect a firmware update...

nwithan8 commented 1 year ago

Thanks - no zhe_events are firing and @nwithan8's blueprints are set up but not working. Any ideas?

Hey, just noticed today that the some of the commands are reporting differently. Specifically, hold and double are now different (will update the blueprint accordingly), but single press isn't registering at all with ZHA (device lights up, so I know something got triggered).

EDIT: I think I figured it out, device got set into a different mode. Potentially the same issue for you @0rangutan

@oxc offhand, do you know how to toggle between the two modes?

https://gist.github.com/oxc/754d6436ce62d92af660d3671acd9346#file-remote_h1_knob-py-L253

EDIT 2: Code comments help, lol: https://gist.github.com/oxc/754d6436ce62d92af660d3671acd9346#file-remote_h1_knob-py-L77

Unfortunately, can't get my devices out of the alt mode. I have three of them and even the ones I haven't touched in forever are now in this alt mode. I'm starting to suspect a firmware update...

In the meantime, I have made COMMAND mode variants of the blueprints @0rangutan (see "Edit" section of each original post):

https://community.home-assistant.io/t/aqara-h1-rotary-dimmer-switch-dial-remote/551909 https://community.home-assistant.io/t/aqara-h1-rotary-dimmer-switch-dial-media-controls/551917

oxc commented 1 year ago

I believe you have to set the operation_mode attribute and then press 5 times. Or the other way around. I cannot say for sure, but somehow like that I managed to switch. The same is reported for some other Aqara devices, and at least back then I could not find a definitive guide on how to do it.

karrui commented 1 year ago

Can confirm. To set attributes, click "Write" then press the device 5 times quickly and it should go through.

nwithan8 commented 1 year ago

Can confirm. To set attributes, click "Write" then press the device 5 times quickly and it should go through.

Click "Write" where? Is this a Dev Tools service I'm calling, or on the device page?

karrui commented 1 year ago

Click "Write" where? Is this a Dev Tools service I'm calling, or on the device page?

https://github.com/zigpy/zha-device-handlers/issues/2251#issuecomment-1466326352 should help

0rangutan commented 1 year ago

@oxc and @nwithan8 - Thanks for all your efforts on this. I have this working now in ZHA and my dimmer set to Event mode successfully. It works well, but with the one big difference vs native integration in Z2M - the ability to dim on the fly as you rotate the knob, as discussed above. Looking at how this works in Z2M, the rotation % is exposed as a sensor and then dimming can work dynamically as the knob is rotated based on this sensor. Is there any way of exposing the rotation % as a sensor in this quirk?

This was the code I was using for dimming under Z2M:

alias: Study Lights Dimming
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.study_rotary_dimmer_action_rotation_percent
    from: unknown
condition: []
action:
  - service: light.turn_on
    data_template:
      brightness: >-
        {{((state_attr('light.study_lights','brightness')|int(1))+((trigger.to_state.state|float(2))/2.55)|round(2)*3)|int(1)}}
    target:
      entity_id: light.study_lights
    enabled: true
mode: single
nwithan8 commented 1 year ago

@oxc and @nwithan8 - Thanks for all your efforts on this. I have this working now in ZHA and my dimmer set to Event mode successfully. It works well, but with the one big difference vs native integration in Z2M - the ability to dim on the fly as you rotate the knob, as discussed above. Looking at how this works in Z2M, the rotation % is exposed as a sensor and then dimming can work dynamically as the knob is rotated based on this sensor. Is there any way of exposing the rotation % as a sensor in this quirk?

This was the code I was using for dimming under Z2M:

alias: Study Lights Dimming
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.study_rotary_dimmer_action_rotation_percent
    from: unknown
condition: []
action:
  - service: light.turn_on
    data_template:
      brightness: >-
        {{((state_attr('light.study_lights','brightness')|int(1))+((trigger.to_state.state|float(2))/2.55)|round(2)*3)|int(1)}}
    target:
      entity_id: light.study_lights
    enabled: true
mode: single

I'm seeing rotation_percent and rotation_percent_delta exposed in my ZHA event (granted, I'm still in COMMAND mode; couldn't get the attributes updated 😦 )

image
oxc commented 1 year ago

Yes, they are exposed as zha event properties. Exposing them a sensor would mean having to keep an internal state of the sensor value, which is weird because of the behavior mentioned above. It should be easy to fill a custom state helper with the desired values based on the ZHA events, though.

0rangutan commented 1 year ago

Many thanks.

I can see the start_rotation, rotation and stop_rotation events as zha_events, eg.

event_type: zha_event
data:
  device_ieee: 54:ef:44:10:00:7f:62:23
  unique_id: 54:ef:44:10:00:7f:62:23:71:0xfcc0
  device_id: 5e0a167c93599c08f8fbd5798471976a
  endpoint_id: 71
  cluster_id: 64704
  command: rotation
  args:
    rotation_time_delta: 500
    rotation_time: 2200
    rotation_angle_delta: 24
    rotation_angle: 228
    rotation_percent_delta: 6.666666507720947
    rotation_percent: 63.33333206176758
    action: 2
  params: {}
origin: LOCAL
time_fired: "2023-08-18T07:37:07.238438+00:00"
context:
  id: 01H83S75D6F2FV0X1YQV5T3HDJ
  parent_id: null
  user_id: null

So the data is available :)

I'd like to try to create an automation that adjusts the brightness on the fly as the rotation changes. This should work in theory as there is a stream of events that come in as the knob rotates and the various rotation arguments are available.

I'm definitely beyond my level of expertise here. Any ideas how I might trigger based on these events? I have started with the following but this doesn't trigger in the tracing when I rotate the knob.

alias: Study Lights Dimming
description: ""
trigger:
  - platform: event
    event_type: zha_event
    event_data:
      device_ieee: 54:ef:44:10:00:7f:62:23
      endpoint_id: 71
      command: rotation
      args:
        - rotation_percent 

Any ideas?

nwithan8 commented 1 year ago

Many thanks.

I can see the start_rotation, rotation and stop_rotation events as zha_events, eg.

event_type: zha_event
data:
  device_ieee: 54:ef:44:10:00:7f:62:23
  unique_id: 54:ef:44:10:00:7f:62:23:71:0xfcc0
  device_id: 5e0a167c93599c08f8fbd5798471976a
  endpoint_id: 71
  cluster_id: 64704
  command: rotation
  args:
    rotation_time_delta: 500
    rotation_time: 2200
    rotation_angle_delta: 24
    rotation_angle: 228
    rotation_percent_delta: 6.666666507720947
    rotation_percent: 63.33333206176758
    action: 2
  params: {}
origin: LOCAL
time_fired: "2023-08-18T07:37:07.238438+00:00"
context:
  id: 01H83S75D6F2FV0X1YQV5T3HDJ
  parent_id: null
  user_id: null

So the data is available :)

I'd like to try to create an automation that adjusts the brightness on the fly as the rotation changes. This should work in theory as there is a stream of events that come in as the knob rotates and the various rotation arguments are available.

I'm definitely beyond my level of expertise here. Any ideas how I might trigger based on these events? I have started with the following but this doesn't trigger in the tracing when I rotate the knob.

alias: Study Lights Dimming
description: ""
trigger:
  - platform: event
    event_type: zha_event
    event_data:
      device_ieee: 54:ef:44:10:00:7f:62:23
      endpoint_id: 71
      command: rotation
      args:
        - rotation_percent 

Any ideas?

I belive the arg you have there is misconfigured.

You can look at a blueprint I made to trigger and handle the ongoing rotation of another ZHA dial device for maybe reference: https://github.com/nwithan8/configs/blob/9ab215f54a3d6ed0e42de6ddc5f57cd132a3a243/home_assistant/blueprints/automations/ers_rotary_dial_light_control_zha.yaml#L158

That said, I don't recall this device consistently sending our rotation events. I remember it being only one per rotation (you'd have to stop and start a new rotation), but I could be wrong.

oxc commented 1 year ago

That said, I don't recall this device consistently sending our rotation events. I remember it being only one per rotation (you'd have to stop and start a new rotation), but I could be wrong.

It does for me, and since there are two different events for "percent in this piece of the rotation" rotation_delta and "percent in the full rotation" rotation, I strongly assume that's how it works for everybody (unless yours/its firmware does not report these properties).

oxc commented 1 year ago

Here is a (slightly redacted) Automation I'm using in production, it controls the light when rotating (uses ongoing rotation), and a curtain when hold+rotating (no ongoing rotation)

Automation YAML for controlling a light on rotation ```yaml alias: Knob description: "" trigger: - platform: event event_type: zha_event event_data: device_id: xxxxxxxxxx condition: [] action: - variables: command: "{{ trigger.event.data.command }}" - choose: - conditions: - alias: Hold and rotate condition: template value_template: | {{ command == "hold_stop_rotation" }} sequence: - variables: percent: "{{ trigger.event.data.args.rotation_percent | int(0) }}" current_position: | {{ state_attr('cover.cover', 'current_position') }} target_position: | {{ min(max(current_position - percent*2, 0), 100) }} - service: cover.set_cover_position data: position: "{{ target_position }}" target: entity_id: cover.cover - conditions: - alias: Rotate condition: template value_template: > {{ command == "start_rotation" or command == "rotation" or command == "stop_rotation" }} sequence: - variables: percent_delta: "{{ trigger.event.data.args.rotation_percent_delta | int(0) }}" - service: light.turn_on data: brightness_step_pct: "{{ percent_delta }}" target: entity_id: - light.light1 - light.light2 - conditions: - alias: Single press condition: template value_template: | {{ command == "1_single" }} sequence: - if: - condition: or conditions: - condition: state entity_id: light.light1 state: "on" - condition: state entity_id: light.light2 state: "on" then: - service: light.turn_off data: {} target: entity_id: - light.light1 - light.light2 else: - service: light.turn_on data: {} target: entity_id: - light.light1 - light.light2 mode: queued max: 10 ```
ToracX commented 11 months ago

First off thanks @oxc for making the automation yaml it works very well. Except for single press. Im having trouble changing operating modes of my H1 dimmer. Does anyone know how to check what mode it is in now? Also trying te write attributes im getting this error. Failed to set attribute: value: EVENT attribute: 9 cluster_id: 64704 endpoint_id: 1

Do you put anything in the "value" field? I tried basically everything. event, EVENT, command, COMMAND, 0 ,1 etc...

arthpeps commented 9 months ago

Hello,

I can't even get the custom quirk working. I'v done following :

zha: custom_quirks_path: /config/custom_zha_quirks/ database_path: /config/zigbee.db enable_quirks: true

I can add the H1 device but interaction goes only from ZHA to the device, not the opposite.

Can anyone help ?

Thanks

arthpeps

ToracX commented 9 months ago

Hello,

I can't even get the custom quirk working. I'v done following :

  • I added the following lines to my configuration.yaml :

zha: custom_quirks_path: /config/custom_zha_quirks/ database_path: /config/zigbee.db enable_quirks: true

  • I created a file named lumi.remote.rkba01.py into the folder custom_zha_quirks with the content of the quirk

I can add the H1 device but interaction goes only from ZHA to the device, not the opposite.

Can anyone help ?

Thanks

arthpeps

It is pretty likely you added a space in the .py file when copy pasting everything. You could go into system logs filter zha and post the output here?

github-actions[bot] commented 3 months ago

There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.

oxc commented 3 months ago

This is not stale and I very much would still like to get it added to the code base