Implementing RESET Tuya function to enable devices which are stuck in a zombie mode to start responding to commands. Added configurable DPS for Reset command as found devices wouldn't 'wake up' unless they had exactly the right entries

This commit is contained in:
sibowler
2022-07-20 06:14:12 +10:00
parent 489a6f09fe
commit ad1f757818
7 changed files with 140 additions and 36 deletions

View File

@@ -62,6 +62,7 @@ TuyaMessage = namedtuple("TuyaMessage", "seqno cmd retcode payload crc")
SET = "set"
STATUS = "status"
HEARTBEAT = "heartbeat"
RESET = "reset"
UPDATEDPS = "updatedps" # Request refresh of DPS
PROTOCOL_VERSION_BYTES_31 = b"3.1"
@@ -96,6 +97,16 @@ PAYLOAD_DICT = {
SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}},
HEARTBEAT: {"hexByte": 0x09, "command": {}},
UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}},
RESET: {
"hexByte": 0x12,
"command": {
"gwId": "",
"devId": "",
"uid": "",
"t": "",
"dpId": [18, 19, 20],
},
},
},
"type_0d": {
STATUS: {"hexByte": 0x0D, "command": {"devId": "", "uid": "", "t": ""}},
@@ -217,6 +228,7 @@ class MessageDispatcher(ContextualLogger):
# Heartbeats always respond with sequence number 0, so they can't be waited for like
# other messages. This is a hack to allow waiting for heartbeats.
HEARTBEAT_SEQNO = -100
RESET_SEQNO = -101
def __init__(self, dev_id, listener):
"""Initialize a new MessageBuffer."""
@@ -301,9 +313,19 @@ class MessageDispatcher(ContextualLogger):
sem.release()
elif msg.cmd == 0x12:
self.debug("Got normal updatedps response")
if self.RESET_SEQNO in self.listeners:
sem = self.listeners[self.RESET_SEQNO]
self.listeners[self.RESET_SEQNO] = msg
sem.release()
elif msg.cmd == 0x08:
self.debug("Got status update")
self.listener(msg)
if self.RESET_SEQNO in self.listeners:
self.debug("Got reset status update")
sem = self.listeners[self.RESET_SEQNO]
self.listeners[self.RESET_SEQNO] = msg
sem.release()
else:
self.debug("Got status update")
self.listener(msg)
else:
self.debug(
"Got message type %d for unknown listener %d: %s",
@@ -381,6 +403,11 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
def connection_made(self, transport):
"""Did connect to the device."""
self.transport = transport
self.on_connected.set_result(True)
def start_heartbeat(self):
"""Start the heartbeat transmissions with the device."""
async def heartbeat_loop():
"""Continuously send heart beat updates."""
@@ -403,8 +430,6 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
self.transport = None
transport.close()
self.transport = transport
self.on_connected.set_result(True)
self.heartbeater = self.loop.create_task(heartbeat_loop())
def data_received(self, data):
@@ -449,12 +474,13 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
payload = self._generate_payload(command, dps)
dev_type = self.dev_type
# Wait for special sequence number if heartbeat
seqno = (
MessageDispatcher.HEARTBEAT_SEQNO
if command == HEARTBEAT
else (self.seqno - 1)
)
# Wait for special sequence number if heartbeat or reset
seqno = self.seqno - 1
if command == HEARTBEAT:
seqno = MessageDispatcher.HEARTBEAT_SEQNO
elif command == RESET:
seqno = MessageDispatcher.RESET_SEQNO
self.transport.write(payload)
msg = await self.dispatcher.wait_for(seqno)
@@ -487,6 +513,15 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
"""Send a heartbeat message."""
return await self.exchange(HEARTBEAT)
async def reset(self, dpIds=None):
"""Send a reset message (3.3 only)."""
if self.version == 3.3:
self.dev_type = "type_0a"
self.debug("reset switching to dev_type %s", self.dev_type)
return await self.exchange(RESET, dpIds)
return True
async def update_dps(self, dps=None):
"""
Request device to update index.