Is it possible to use the Zigbee Home Automation - Home Assistant option to make this change persist HA updates?
Havenât tested this yet with the wyze quirk but you can now have a folder defined in config to have quirks sit in a local folder:
I tested it and it works! You just need to make sure you correct the relative paths in your script. After doing that the start of your script should look like this:
"""Support for the Wyze lock."""
import logging
from typing import Any, List, Optional, Union
from zigpy.profiles import zha
from zigpy.quirks import CustomCluster, CustomDevice
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.closures import DoorLock
from zigpy.zcl.clusters.general import (
Basic,
Identify,
Ota,
PollControl,
PowerConfiguration,
Time,
)
from zigpy.zcl.clusters.homeautomation import Diagnostic
from zigpy.zcl.clusters.security import IasZone
from . import YUNDING
from zhaquirks import Bus, LocalDataCluster
from zhaquirks.const import (
CLUSTER_COMMAND,
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OFF,
ON,
OUTPUT_CLUSTERS,
PROFILE_ID,
ZONE_STATE,
)
Hello,
I am confused what you mean by âUpdate Relative Pathsâ. My script looks exactly like that, but my ZHA fails to load when I have the following in my configuration.yaml:
zha:
enable_quirks: true
custom_quirks_path: /config/custom_zha_quirks/
With this config I have a custom_zha_quirks folder in my config directory where I put the files located here
I also tried putting them in a folder called âwyzeâ, a folder called âYUNDINGâ and a folder called âyundingâ but zha fails to load with any of these as well. I am at a loss on how to get this custom quirk to load.
Can you please share the related logs? Depending on what you copied from here you might have an arguments error as the first script posted here is dated. The logs will help us get to the bottom of w/e is going on here.
From the file you shared the imports do not match what I posted.The last 3 imports are still using relative paths instead of package imports.
Your file has the following:
from . import YUNDING
from .. import Bus, LocalDataCluster
from ..const import (
CLUSTER_COMMAND,
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OFF,
ON,
OUTPUT_CLUSTERS,
PROFILE_ID,
ZONE_STATE,
)
it should be changed to
from . import YUNDING
from zhaquirks import Bus, LocalDataCluster
from zhaquirks.const import (
CLUSTER_COMMAND,
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OFF,
ON,
OUTPUT_CLUSTERS,
PROFILE_ID,
ZONE_STATE,
)
The logs will help me confirm though!
I suppose I shouldâve run a compare before claiming they were the same. My bad. Making the change allowed ZHA to start up, and my locks now have a new entity for Open or Closed although they are stuck on Closed as I havenât changed any of these values:
if args[52] == 180 and args[41] == 165:
self.warning("the lock is unlocked via the app")
self.endpoint.device.lock_bus.listener_event("lock_event", 2)
elif args[52] == 180 and args[41] == 162:
self.warning("the lock is locked via the app")
self.endpoint.device.lock_bus.listener_event("lock_event", 1)
elif args[52] == 176 and args[41] == 165:
self.warning("the lock is unlocked manually")
self.endpoint.device.lock_bus.listener_event("lock_event", 2)
elif args[52] == 176 and args[41] == 162:
self.warning("the lock is locked manually")
self.endpoint.device.lock_bus.listener_event("lock_event", 1)
elif args[52] == 189 and args[41] == 162:
self.warning("the lock is locked via auto lock")
self.endpoint.device.lock_bus.listener_event("lock_event", 1)
if args[52] == 74 and args[41] == 177:
self.warning("the door is open")
self.endpoint.device.motion_bus.listener_event("motion_event", ON)
elif args[52] == 74 and args[41] == 178:
self.warning("the door is closed")
self.endpoint.device.motion_bus.listener_event("motion_event", OFF)
When enabling this in configuration.yaml:
logger:
default: info
logs:
asyncio: debug
homeassistant.core: debug
homeassistant.components.zha: debug
zigpy: debug
bellows: debug
zigpy_xbee: debug
zigpy_deconz: debug
zigpy_zigate: debug
I donât seem to be getting any logging when manually locking, unlocking and opening/closing the door. I am checking in Configurations > ZHA > Add Device > Show Logs, but no events are being reported when I am manually changing the state. I am also spamming refresh in Supervisor > System > Log Provider [Core] but not getting events when changing the lock state.
Also, once I do get this figured out, I have 2 locks on one hub. Will both these locks be reporting the same numbers since they are on the same hub?
What do you mean by hub? You should not longer need the wyze hubs if you are hooking up via ZHA. If you look at other comments within this thread, you can see that two locks will not use the same values in ZHA. It is important to go through the logs to make sure you have the right values.
Others on the thread have shown how to get the logs to work. Have you read these comments and confirmed they do not work for you?
Also please make sure you see the lock connected in your ZHA. I like to make sure I see it in the ZHA visualization. Sometimes my zigbee stuff drop off. Itâs a good want to confirm they are in the network.
Yes, sorry for the delay I was out of town. I restated those techniques in my previous comment. I do not get any âInterestingâ numbers in my log. I do not have events reported when I manually on lock, unlock, or open my door. I am looking in the logs, and looking in the ZHA Add Device > Show Logs section. Here is my logging config:
logger:
default: info
logs:
asyncio: debug
homeassistant.core: debug
homeassistant.components.zha: debug
zigpy: debug
bellows: debug
zigpy_xbee: debug
zigpy_deconz: debug
zigpy_zigate: debug
zigpy_cc: debug
bellows.zigbee.application: debug
bellows.ezsp: debug
zigpy_deconz.zigbee.application: debug
zigpy_deconz.api: debug
zigpy_xbee.ap: debug
zigpy_znp: debug
zhaquirks: debug
and here is my lock.py:
"""Support for the Wyze lock."""
import logging
from typing import Any, List, Optional, Union
from zigpy.profiles import zha
from zigpy.quirks import CustomCluster, CustomDevice
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.closures import DoorLock
from zigpy.zcl.clusters.general import (
Basic,
Identify,
Ota,
PollControl,
PowerConfiguration,
Time,
)
from zigpy.zcl.clusters.homeautomation import Diagnostic
from zigpy.zcl.clusters.security import IasZone
from . import YUNDING
from zhaquirks import Bus, LocalDataCluster
from zhaquirks.const import (
CLUSTER_COMMAND,
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OFF,
ON,
OUTPUT_CLUSTERS,
PROFILE_ID,
ZONE_STATE,
)
WYZE_CLUSTER_ID = 0xFC00
ZONE_TYPE = 0x0001
_LOGGER = logging.getLogger(__name__)
class DoorLockCluster(CustomCluster, DoorLock):
"""DoorLockCluster cluster."""
cluster_id = DoorLock.cluster_id
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.lock_bus.add_listener(self)
def lock_event(self, locked):
"""Motion event."""
self._update_attribute(0x0000, locked)
class MotionCluster(LocalDataCluster, IasZone):
"""Motion cluster."""
cluster_id = IasZone.cluster_id
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.motion_bus.add_listener(self)
super()._update_attribute(ZONE_TYPE, IasZone.ZoneType.Contact_Switch)
def motion_event(self, zone_state):
"""Motion event."""
super().listener_event(CLUSTER_COMMAND, None, ZONE_STATE, [zone_state])
_LOGGER.debug("%s - Received motion event message", self.endpoint.device.ieee)
class WyzeCluster(CustomCluster, Basic):
"""Wyze manufacturer specific cluster implementation."""
cluster_id = WYZE_CLUSTER_ID
ep_attribute = "wyze_lock_cluster"
attributes = {}
server_commands = {}
client_commands = {}
def handle_message(
self,
hdr: foundation.ZCLHeader,
args: List[Any],
*,
dst_addressing: Optional[
Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
] = None,
):
"""Handle a message on this cluster."""
self.debug("ZCL request 0x%04x: %s", hdr.command_id, args)
i = 0
for arg in args:
self.info("index: %s value: %s", i, arg)
i += 1
self.warning("argument: %s", ",".join(map(str, args)))
if len(args) < 70:
return
self.info(
"Interesting attributes - 52: %s, 41: %s, 56: %s, 57: %s",
args[52],
args[41],
args[56],
args[57],
args[0],
)
manual_unlocked_vals = [9,243]
manual_locked_vals = [14,44]
lock_state_identfiers = [50, 58, 62, 54, 51, 57, 53]
open_state_identfiers = [196, 200]
open_vals = [63, 29]
closed_vals = [60, 30]
fakes = [117]
if args[0] in fakes:
return
if args[52] == 180 and args[41] == 165:
self.warning("the lock is unlocked via the app")
self.endpoint.device.lock_bus.listener_event("lock_event", 2)
elif args[52] == 180 and args[41] == 162:
self.warning("the lock is locked via the app")
self.endpoint.device.lock_bus.listener_event("lock_event", 1)
elif args[52] == 176 and args[41] == 165:
self.warning("the lock is unlocked manually")
self.endpoint.device.lock_bus.listener_event("lock_event", 2)
elif args[52] == 176 and args[41] == 162:
self.warning("the lock is locked manually")
self.endpoint.device.lock_bus.listener_event("lock_event", 1)
elif args[52] == 189 and args[41] == 162:
self.warning("the lock is locked via auto lock")
self.endpoint.device.lock_bus.listener_event("lock_event", 1)
if args[52] == 74 and args[41] == 177:
self.warning("the door is open")
self.endpoint.device.motion_bus.listener_event("motion_event", ON)
elif args[52] == 74 and args[41] == 178:
self.warning("the door is closed")
self.endpoint.device.motion_bus.listener_event("motion_event", OFF)
class WyzeLock(CustomDevice):
"""Wyze lock device."""
def __init__(self, *args, **kwargs):
"""Init."""
self.lock_bus = Bus()
self.motion_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
# <SimpleDescriptor endpoint=1 profile=260 device_type=10
# device_version=1
# input_clusters=[0, 1, 3, 32, 257, 2821, 64512]
# output_clusters=[10, 25, 64512]>
MODELS_INFO: [(YUNDING, "Ford")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.DOOR_LOCK,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Identify.cluster_id,
PollControl.cluster_id,
DoorLock.cluster_id,
Diagnostic.cluster_id,
WYZE_CLUSTER_ID,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id, WYZE_CLUSTER_ID],
}
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.DOOR_LOCK,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Identify.cluster_id,
PollControl.cluster_id,
DoorLockCluster,
Diagnostic.cluster_id,
WyzeCluster,
MotionCluster,
],
OUTPUT_CLUSTERS: [Ota.cluster_id, Time.cluster_id, WyzeCluster],
}
}
}
I have an hours worth of logs, with me attempting to lock and unlock and open the door somewhere near the beginning. I am unsure how to send them to you, I donât see a private message option on this site.
You should see something starting with either argument: and/or Interesting attributes - this in your HA logs (/config/logs):
Those are the args are produced by these 2 lines in your lock.py:
self.warning("argument: %s", ",".join(map(str, args)))
self.info(
"Interesting attributes - 52: %s, 41: %s, 56: %s, 57: %s",
args[52],
args[41],
args[56],
args[57],
args[0],
)
Sadly your logs wonât be of much use to me or anyone on this thread. The process requires a lot of manually testing by locking the lock and view logs produced by added warnings in the handle_message method to see what arguments are.
My suggestion to debug is add some self.warnings to handle_message to give you clearer debug messages of the arguments so you can see what the values are as you lock and unlock. Might be a good idea to tail and grep your logs to see events as they come in. I have my set up on a pi and was able to run a tail and grep using the Portainer add-on. I see there is a Log Viewer Addon that might be useful as well. (Whenever I try to load my full logs from /config/logs I crash my browsers. WOOPS!)
Once you have it set up to give you logs do the following:
- lock the lock
- check logs
- wait a few seconds
- unlock the lock
- check the log
- see what values are different between steps 2 and 5
- repeat and confirm what you identified in step 6
Remember the lock also sends events for opening & closing the door and a heartbeat so you may find random ânoiseâ doing the steps I outlined. The values change depending on how it was locked (auto, manual, keypad). It took me a solid day to get it going with some refinement over the following weeks but it does work! I hope that helps you with making a debugging plan.
Has anyone else had issues with the log timestamps not matching with the state history? I have two locks set up identical to mzelmanovich above with the filtering of events with 117 at index 0. This solved one of my locks not reflecting the current state but the other still gets out of sync a few times a day. Whatâs strange is the state history will say unlocked at a certain time but the log wonât have anything with the same timestamp.
I do not get Interesting Attributes or Argument: in my logs. I got argument to show up only when I put the device in pairing mode and repair it with ZHA, and even then 52 and 41 both report â0â and the numbers only go up to 54. I have tried this handle message:
def handle_message(self, hdr, args, dst_addressing):
and this:
def handle_message(
self,
hdr: foundation.ZCLHeader,
args: List[Any],
*,
dst_addressing: Optional[
Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
] = None,
):
But I do not get the Interesting Attributes or Argument: in my log. I know the quirk is at least doing something because if I put a comma in the wrong place in the lock.py zha fails to load, but I do not get the correct entries in my logs. Gonna have to return the damn locks because the WAF is at an all time low.
Have you confirmed you paired the lock? You should see it within your zha map. It might be dropping off
Yep. The controls from HA work on it, it just doesnât report the state. I wonder if this is because I have the Conbee ii. That seems to be the commonality between mine and others issues. I may try a different radio in the coming weeks.
I decided to try to get this working with a Wyze lock I snagged locally for $30. I have added it to my Wyze app, completed calibration, and successfully paired to ZHA via HUSBZB-1. Lock/unlock function works correctly within HA, but Iâm unable to get any of the attributes required for reading the current state of the lock. As others have mentioned, Iâve tried a number of times to get this working, but no luck in seeing any arguments or interesting attributes in the logs.
Just adding my experience to the handful of others that are struggling with this part. Iâm not expecting built in HA support anytime soon, but itâd be fun to get this inexpensive device working nicely with HA!
I also am using an husbzb-1 dongle. I am not able to see either âinteresting attributesâ nor âargumentâ in the logs. I understand that you were able to get this information from the logs. Did you do anything special or different?
I already add wyze lock ZHA quirks and have some problem.
When I manually lock it and a few minutes later will change to unlock.
I dont know how or where to change this âAttribute report received: lock_state=2â
2021-10-27 00:24:38 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=persistent_notification, service=dismiss, service_data=notification_id=config_entry_discovery>
2021-10-27 00:24:38 DEBUG (bellows.thread_0) [bellows.uart] Data frame: b'25f1b157546f15b658954b24ab1593499c1e8b6c48a29874face7183fc7e0fa50d2a7e'
2021-10-27 00:24:38 DEBUG (bellows.thread_0) [bellows.uart] Sending: b'83401b7e'
2021-10-27 00:24:38 DEBUG (MainThread) [bellows.ezsp.protocol] Application frame 69 (incomingMessageHandler) received: b'000401010101014001000050acc7a56cffff0708120a00003002'
2021-10-27 00:24:38 DEBUG (MainThread) [bellows.zigbee.application] Received incomingMessageHandler frame with [<EmberIncomingMessageType.INCOMING_UNICAST: 0>, EmberApsFrame(profileId=260, clusterId=257, sourceEndpoint=1, destinationEndpoint=1, options=<EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY|APS_OPTION_RETRY: 320>, groupId=0, sequence=80), 172, -57, 0x6ca5, 255, 255, b'\x08\x12\n\x00\x000\x02']
2021-10-27 00:24:38 DEBUG (MainThread) [zigpy.zcl] [0x6ca5:1:0x0101] ZCL deserialize: <ZCLHeader frame_control=<FrameControl frame_type=GLOBAL_COMMAND manufacturer_specific=False is_reply=True disable_default_response=False> manufacturer=None tsn=18 command_id=Command.Report_Attributes>
2021-10-27 00:24:38 DEBUG (MainThread) [zigpy.zcl] [0x6ca5:1:0x0101] ZCL request 0x000a: [[Attribute(attrid=0, value=<TypeValue type=enum8, value=enum8.undefined_0x02>)]]
2021-10-27 00:24:38 DEBUG (MainThread) [zigpy.zcl] [0x6ca5:1:0x0101] Attribute report received: lock_state=2
2021-10-27 00:24:38 DEBUG (MainThread) [homeassistant.components.zha.core.channels.base] [0x6CA5:1:0x0101]: Attribute report 'Door Lock'[lock_state] = LockState.Unlocked
2021-10-27 00:24:38 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=lock.wyze_doorlock_3f6161fe_door_lock, old_state=<state lock.wyze_doorlock_3f6161fe_door_lock=locked; friendly_name=wyze_doorlock @ 2021-10-27T00:10:42.823211+08:00>, new_state=<state lock.wyze_doorlock_3f6161fe_door_lock=unlocked; friendly_name=wyze_doorlock @ 2021-10-27T00:24:38.817408+08:00>>
2021-10-27 00:24:38 DEBUG (MainThread) [bellows.ezsp.protocol] Send command sendUnicast: (<EmberOutgoingMessageType.OUTGOING_DIRECT: 0>, 0x6CA5, EmberApsFrame(profileId=260, clusterId=257, sourceEndpoint=1, destinationEndpoint=1, options=<EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY|APS_OPTION_RETRY: 320>, groupId=0, sequence=18), 39, b'\x18\x12\x0b\n\x00')
2021-10-27 00:24:38 DEBUG (bellows.thread_0) [bellows.uart] Sending: b'53f62157541e151735904b24ab5493099d4e27b9cacb7f99f6cc637d5d647e'
2021-10-27 00:24:38 DEBUG (bellows.thread_0) [bellows.uart] Data frame: b'36f6a157541e15c35dfb7e'
2021-10-27 00:24:38 DEBUG (bellows.thread_0) [bellows.uart] Sending: b'8430fc7e'
2021-10-27 00:24:38 DEBUG (MainThread) [bellows.ezsp.protocol] Application frame 52 (sendUnicast) received: b'0071'
2021-10-27 00:24:39 DEBUG (bellows.thread_0) [bellows.uart] Data frame: b'46f6b1575415151735904b24ab5493099d4e27dacace67a56b7e'
2021-10-27 00:24:39 DEBUG (bellows.thread_0) [bellows.uart] Sending: b'8520dd7e'
2021-10-27 00:24:39 DEBUG (MainThread) [bellows.ezsp.protocol] Application frame 63 (messageSentHandler) received: b'00a56c0401010101014001000071270000'
2021-10-27 00:24:39 DEBUG (MainThread) [bellows.zigbee.application] Received messageSentHandler frame with [<EmberOutgoingMessageType.OUTGOING_DIRECT: 0>, 27813, EmberApsFrame(profileId=260, clusterId=257, sourceEndpoint=1, destinationEndpoint=1, options=<EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY|APS_OPTION_RETRY: 320>, groupId=0, sequence=113), 39, <EmberStatus.SUCCESS: 0>, b'']
I can see this line on the proposed quirks, and by watching the logs I canât see any event that has more than 70 args. I can see here events with 20 args, 32 args and 50 args.
So probably none of the code examples works here?
I have it working with this line. It is possible important events are being filtered by this or that the hardware changed. I have noticed no two fire the same events but I have not checked the length. I believe I copied it from an earlier version.
Just added a new Wyze lock and am getting manual lock/unlock reports with some different data. Manual Lock/Unlock is reflected on the frontend of HA after tweaking these lines in the custom quirks:
elif args[52] == 216 and args[41] == 100:
self.warning("the lock is unlocked manually")
self.endpoint.device.lock_bus.listener_event("lock_event", 2)
elif args[52] == 216 and args[41] == 99:
self.warning("the lock is locked manually")
self.endpoint.device.lock_bus.listener_event("lock_event", 1)
Sorry if I had missed it - but will this only with with ZHA or can this also work with Zigbee2mqtt?