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:
@@ -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.
|
||||
|
Reference in New Issue
Block a user