New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Device Support Request] Aqara H1 Knob (Wireless) - lumi.remote.rkba01 - ZNXNKG02LM #2266
Comments
Attached is my custom quirk What works:
What doesn't work:
Other thoughts:
Source code: xiamo/aqara/remote_h1_knob.pyfrom 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=<LogicalType.EndDevice: 2>, complex_descriptor_available=0, user_descriptor_available=0, reserved=0, aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, mac_capability_flags=<MACCapabilityFlags.AllocateAddress|MainsPowered: 132>, manufacturer_code=4447, maximum_buffer_size=127, maximum_incoming_transfer_size=100, server_mask=11264, maximum_outgoing_transfer_size=100, descriptor_capability_field=<DescriptorCapability.NONE: 0>, *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: []},
} |
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? |
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". |
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?). |
In the meantime, was able to crank out a few blueprints for the device: Thanks @oxc for the great work, and I vouch that support for this should get adopted ASAP |
@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. |
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! |
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 😂 |
@oxc - many thanks for supplying this. |
@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. |
Thanks - no zhe_events are firing and @nwithan8's blueprints are set up but not working. |
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 |
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. |
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. |
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 |
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. |
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? |
#2251 (comment) should help |
@oxc and @nwithan8 - Thanks for all your efforts on this. This was the code I was using for dimming under Z2M:
|
I'm seeing |
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. |
Many thanks. I can see the start_rotation, rotation and stop_rotation events as zha_events, eg.
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.
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. |
It does for me, and since there are two different events for "percent in this piece of the rotation" |
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 rotationalias: 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 |
First off thanks @oxc for making the automation yaml it works very well. Do you put anything in the "value" field? I tried basically everything. event, EVENT, command, COMMAND, 0 ,1 etc... |
Hello, I can't even get the custom quirk working. I'v done following :
zha:
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? |
This has been reported as #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
Diagnostic information
Additional logs
The following actions trigger a zha_event:
Long Press:
Double Press:
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
The text was updated successfully, but these errors were encountered: