def onUnsolicitedMessage(self, pkt, address): """ Called when an unsolicited message is received. We do not know the node id the message is from. Only the ip address. """ zipPkt = Message.decode(pkt) sourceIp = ipaddress.IPv6Address(address[0]) sourceEP = zipPkt.sourceEP if isinstance(zipPkt.command, NetworkManagementProxy.NodeListReport): return asyncio.ensure_future(self.__handleNodeListReport__(zipPkt.command)) # Find the node this was from for nodeId, node in self._nodes.items(): if node.get("ip") != sourceIp: continue if zipPkt.ackRequest: # This message needs an ack response. ackReponse = zipPkt.response(success=True) self._unsolicitedConnection.sendTo(ackReponse.compose(), address) self.speak( "messageReceived", nodeId, sourceEP, zipPkt.command, zipPkt.headerExtension, ) return True _LOGGER.warning( "Got message from unknown sender %s: %s", sourceIp, zipPkt.command ) return False
def test_ListIterationReport(): # pylint: disable=line-too-long pkt = b"x\x04\x02\x01\x10~Mp\x22\xf1\xd9\xb4\xa9\xa8\x13\xd6\nlr\xa4\xe0m\x01\x01i\x01\x00n\x02\x00\x00" msg = Message.decode(pkt) assert msg.seqNo == 2 assert msg.remainingCount == 1 assert msg.dsk == "32333-28706-61913-46249-43027-54794-27762-42208"
def test_groupinforeport(): report = Message.decode(b"Y\x04\x81\x01\x00q\x07\x00\x00\x00") assert report.groupCount == 1 assert report.groups[0].groupingIdentifier == 1 assert report.groups[0].mode == 0 assert report.groups[0].profile == 0x7107 assert report.groups[0].eventCode == 0
def test_node_add_dsk_report(): pkt = b"4\x13\x08\x00T\xcb\xc0}/_\x14\x1ee\x9e\x82\x82\xe67\xc5\x0e" msg: NetworkManagementInclusion.NodeAddDSKReport = Message.decode(pkt) assert isinstance(msg, NetworkManagementInclusion.NodeAddDSKReport) assert msg.seqNo == 8 assert msg.inputDSKLength == 0 assert msg.dsk == "21707-49277-12127-05150-26014-33410-58935-50446"
def test_Report(): pkt = b"\x70\x06\x01\x01\x09" msg = Message.decode(pkt) assert isinstance(msg, Configuration.Report) assert msg.parameterNumber == 1 assert msg.size == 1 assert msg.value == 9
async def test_ipOfNode(gateway: ZIPGateway): # pylint: disable=line-too-long pkt = b"X\x01\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\xa8\x00\xee\xea\xec\xfa\xf9" zipNodeAdvertisement = Message.decode(pkt) [reply, _] = await asyncio.gather( gateway.ipOfNode(6), runDelayed(gateway.commandReceived, zipNodeAdvertisement)) assert reply == ipaddress.IPv6Address("::ffff:c0a8:ee")
def assert_message_sent(self, cmd: Message): for msg in self._sent: if inspect.isclass(cmd): # Not any specific parameters, just make sure any message with this type is sent if msg.hid() == cmd.hid(): return True elif msg == cmd: return True raise Exception("Excpected message {} was not sent".format(cmd))
def test_zipnd_zipnodeadvertisement(): # pylint: disable=line-too-long pkt = b"X\x01\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\xa8\x00\xee\xea\xec\xfa\xf9" msg = Message.decode(pkt) assert isinstance(msg, ZipND.ZipNodeAdvertisement) assert msg.local == False assert msg.nodeId == 6 assert msg.ipv6 == ipaddress.IPv6Address("::ffff:c0a8:ee") assert msg.homeId == 0xEAECFAF9 assert msg.compose() == pkt
async def sendAck(): await asyncio.sleep(0) # pylint: disable=line-too-long pkt = b"#\x020\x90\x01\x00\x00\x16\x01\x03\x00\x06=\x03\x0e\x00\x01\x00\x01\x02\x00\x1a\x02\x05\x00\x00\x00\x00\x02" msg = Message.decode(pkt) adapter.ackReceived(msg) await asyncio.sleep(0) await asyncio.sleep(0) await asyncio.sleep(0) adapter.ackReceived(Zip.ZipPacket(ackResponse=True, seqNo=1))
async def test_getNodeInfo(gateway: ZIPGateway): # pylint: disable=line-too-long cachedNodeInfoReport = Message.decode( b"R\x04\x03\x1b\x9c\x9c\x00\x04\x10\x01^%'\x85\\pru\x86ZYszh#") gateway.send = sendNop [nodeInfo, _] = await asyncio.gather( gateway.getNodeInfo(1), runDelayed(gateway.commandReceived, cachedNodeInfoReport), ) assert isinstance(nodeInfo, NetworkManagementProxy.NodeInfoCachedReport) assert nodeInfo == cachedNodeInfoReport
async def sendAndReceive( self, cmd: Message, waitFor: Message, timeout: int = 3, **kwargs ) -> Message: self._sent.append(cmd) for msg in self._queue: if msg.hid() == waitFor.hid(): return msg raise Exception( "Error in test setup. Node is not setup to answer on message {}".format( waitFor ) )
async def test_getFailedNodeList(gateway: ZIPGateway): # pylint: disable=line-too-long failedNodeListReport = Message.decode( b"R\x0C\x02!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ) gateway.send = sendNop [nodeList, _] = await asyncio.gather( gateway.getFailedNodeList(), runDelayed(gateway.commandReceived, failedNodeListReport), ) assert nodeList == {1, 6}
async def handleMessage(self, message: Message, flags: Zip.HeaderExtension) -> bool: """Handle and incomming message. Route it to the correct handler""" handled = False if self.messageReceived(message) is True: # Message has already been handled handled = True cmdClass: CommandClass = self.supported.get(message.cmdClass()) if cmdClass: retval = await cmdClass.handleMessage(message, flags) if retval: return retval if handled: return True for retval in await self.ask("onMessage", message): if retval: return retval # Message was not handled _LOGGER.warning("Unhandled message %s from node %s", message, self.nodeId) _LOGGER.debug(message.debugString()) return False
def test_node_add_status_partial(): pkt = b"\x34\x02\x0c\x06\x00N\x15\xd3\x9c\x04\x10\x01^%'\x85\\pru\x86" msg = Message.decode(pkt) assert isinstance(msg, NetworkManagementInclusion.NodeAddStatus) assert msg.seqNo == 12 assert msg.newNodeID == 78 assert msg.commandClass == [ 94, 37, 39, 133, 92, 112, 114, 117, 134, ]
async def test_getNodeList(gateway: ZIPGateway): # pylint: disable=line-too-long nodeListReport = Message.decode( b"R\x02\x02\x00\x01!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ) async def dummySend(_msg): pass gateway.send = dummySend assert gateway.nodeId == 0 [nodeList, _] = await asyncio.gather( gateway.getNodeList(), runDelayed(gateway.commandReceived, nodeListReport)) assert nodeList == {1, 6} assert gateway._nodes == {1: {}, 6: {}} assert gateway.nodeId == 1
def test_zip_packet_networkmanagementproxt_nodelistget(): pkt = b"#\x02\x80P\x02\x00\x00R\x01\x02" msg = Message.decode(pkt) assert isinstance(msg, Zip.ZipPacket) assert msg.ackRequest == True assert msg.ackResponse == False assert msg.nackResponse == False assert msg.nackWaiting == False assert msg.nackQueueFull == False assert msg.nackOptionError == False assert msg.headerExtIncluded == False assert msg.zwCmdIncluded == True assert msg.moreInformation == False assert msg.secureOrigin == True assert msg.seqNo == 2 assert msg.sourceEP == 0 assert msg.destEP == 0 assert type(msg.command) is NetworkManagementProxy.NodeListGet assert msg.compose() == pkt
def onPacket(self, pkt): """Called when a packed has recevied from the connection""" try: zipPkt = Message.decode(pkt) except Exception: _LOGGER.error("Could not decode message. Raw message:") _LOGGER.error("%s", pkt) return False if isinstance(zipPkt, Zip.ZipPacket): if zipPkt.ackResponse: self.ackReceived(zipPkt) return True if zipPkt.nackResponse: if zipPkt.nackWaiting: # Waiting: the preceding Z/IP Packet encapsulated Z-Wave Command is not yet # delivered to the destination and delivery will be attempted later on # Threat these as normal acks self.ackReceived(zipPkt) return True _LOGGER.error("Nack response not implemented %s", zipPkt.debugString()) # self.nackReceived(zipPkt.seqNo) return False if zipPkt.ackRequest: _LOGGER.error( "This message needs an ack response. Not implemented") return False if zipPkt.zwCmdIncluded: self.commandReceived(zipPkt) elif isinstance(zipPkt, Zip.ZipKeepAlive): if zipPkt.ackResponse: # Ignore a response return True _LOGGER.error( "This message needs an ack response. Not implemented") return False elif isinstance(zipPkt, ZipND.ZipNodeAdvertisement): self.commandReceived(zipPkt) else: _LOGGER.warning("Received unknown Z/IP packet from zipgateway: %s", zipPkt) return False return True
def test_zip_packet_networkmanagementproxt_nodelistreport(): # pylint: disable=line-too-long pkt = b"#\x02\x00\xd0`\x00\x00\x05\x84\x02\x04\x00R\x02\x01\x00\x01!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" msg = Message.decode(pkt) assert isinstance(msg, Zip.ZipPacket) assert msg.ackRequest == False assert msg.ackResponse == False assert msg.nackResponse == False assert msg.nackWaiting == False assert msg.nackQueueFull == False assert msg.nackOptionError == False assert msg.headerExtIncluded == True assert msg.zwCmdIncluded == True assert msg.moreInformation == False assert msg.secureOrigin == True assert msg.seqNo == 96 assert msg.sourceEP == 0 assert msg.destEP == 0 assert type(msg.command) is NetworkManagementProxy.NodeListReport
def test_zip_packet_response(): # pylint: disable=line-too-long pkt = b"#\x02\x00\xd0`\x00\x00\x05\x84\x02\x04\x00R\x02\x01\x00\x01!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" msg = Message.decode(pkt) assert isinstance(msg, Zip.ZipPacket) reply = msg.response(success=True) assert reply.ackRequest == False assert reply.ackResponse == True assert reply.nackResponse == False assert reply.nackWaiting == False assert reply.nackQueueFull == False assert reply.nackOptionError == False assert reply.seqNo == msg.seqNo reply = msg.response(success=False, nackQueueFull=True) assert reply.ackRequest == False assert reply.ackResponse == False assert reply.nackResponse == True assert reply.nackWaiting == False assert reply.nackQueueFull == True assert reply.nackOptionError == False assert reply.seqNo == msg.seqNo
async def handleMessage(self, message: Message, flags) -> bool: """Handle and incomming message specific to this command class""" # Find internal handlers if not message.NAME: # Not implemented, do not look for handlers return False hid = message.hid() if self.__messageHandlers__: handler = self.__messageHandlers__.get(hid) if handler and await handler(self, message, flags): # Message was handled, stop further processing return True components = message.NAME.lower().split("_") name = "".join(["on", *map(str.title, components)]) for retval in await self.ask(name, message, flags): if retval: return True # No message handlers for this kind of message return False
def test_parse_command(): pkt = b"\x6C\x01\x03\x03\x20\x03\x02" msg = Message.decode(pkt) assert isinstance(msg, Supervision.Get) assert msg.sessionID == 3 assert isinstance(msg.command, Basic.Report)
def test_report(): pkt = b"\x20\x03\x02" msg = Message.decode(pkt) assert isinstance(msg, Basic.Report) assert msg.value == 2 assert msg.compose() == pkt
def test_groupcommandlistreport(): report = Message.decode(b"Y\x06\t\x021\x05") assert report.commandClass == [[49, 5]]
def test_NodeFailing(pkt, queueHandle): msg = Message.decode(pkt) assert msg.queueHandle == queueHandle
def parse_command(stream: BitStreamReader): # pylint: disable=invalid-name """Parse the length prefixed command""" length = stream.byte() return Message.decode(stream.value(length))
def test_onPacket_Zip(connection: ZIPConnection): connection.ackReceived = MagicMock() connection.commandReceived = MagicMock() ackResponse = Zip.ZipPacket( ackRequest=False, ackResponse=True, nackResponse=False, nackWaiting=False, nackQueueFull=False, nackOptionError=False, headerExtIncluded=False, zwCmdIncluded=False, moreInformation=False, secureOrigin=True, seqNo=0, sourceEP=0, destEP=0, command=None, ) assert connection.onPacket(ackResponse.compose()) is True connection.ackReceived.assert_called_once() nackResponse = Zip.ZipPacket( ackRequest=False, ackResponse=False, nackResponse=True, nackWaiting=False, nackQueueFull=False, nackOptionError=False, headerExtIncluded=False, zwCmdIncluded=False, moreInformation=False, secureOrigin=True, seqNo=0, sourceEP=0, destEP=0, command=None, ) msg = Message.decode(nackResponse.compose()) assert msg.ackRequest == False assert msg.ackResponse == False assert msg.nackResponse == True assert connection.onPacket(nackResponse.compose()) is False nackResponse = Zip.ZipPacket( ackRequest=False, ackResponse=False, nackResponse=True, nackWaiting=True, nackQueueFull=False, nackOptionError=False, headerExtIncluded=False, zwCmdIncluded=False, moreInformation=False, secureOrigin=True, seqNo=0, sourceEP=0, destEP=0, command=None, ) msg = Message.decode(nackResponse.compose()) assert msg.ackRequest == False assert msg.ackResponse == False assert msg.nackResponse == True # This should be treated as success assert connection.onPacket(nackResponse.compose()) is True ackRequest = Zip.ZipPacket( ackRequest=True, ackResponse=False, nackResponse=False, nackWaiting=False, nackQueueFull=False, nackOptionError=False, headerExtIncluded=False, zwCmdIncluded=False, moreInformation=False, secureOrigin=True, seqNo=0, sourceEP=0, destEP=0, command=None, ) assert connection.onPacket(ackRequest.compose()) is False pkt = Zip.ZipPacket( ackRequest=False, ackResponse=False, nackResponse=False, nackWaiting=False, nackQueueFull=False, nackOptionError=False, headerExtIncluded=False, zwCmdIncluded=True, moreInformation=False, secureOrigin=True, seqNo=0, sourceEP=0, destEP=0, command=Basic.Get(), ) assert connection.onPacket(pkt.compose()) is True connection.commandReceived.assert_called_once()
def test_decode(pkt, params): msg = Message.decode(bytes.fromhex(pkt)) for key, value in params.items(): msgValue = getattr(msg, key) assert msgValue == value
def test_SupportedSensorReport(): msg = Message.decode(b"\x31\x02\x01") assert msg.bitMask == set([1]) msg = Message.decode(b"\x31\x02\x05") assert msg.bitMask == set([1, 3])
async def send(self, cmd: Message, timeout: int = 3) -> bool: self._sent.append(cmd) for msg, reply in self._replies: if msg.hid() == cmd.hid(): await self.handleMessage(reply, 0) break