async def test_force_remove(application, mocker): app, znp_server = application await app.startup(auto_form=False) mocker.patch("zigpy_znp.zigbee.application.ZDO_REQUEST_TIMEOUT", new=0.3) device = app.add_device(ieee=t.EUI64(range(8)), nwk=0xAABB) device.status = zigpy.device.Status.ENDPOINTS_INIT device.initializing = False # Reply to zigpy's leave request bad_mgmt_leave_req = znp_server.reply_once_to( request=c.ZDO.MgmtLeaveReq.Req(DstAddr=device.nwk, partial=True), responses=[c.ZDO.MgmtLeaveReq.Rsp(Status=t.Status.FAILURE)], ) # Reply to our own leave request good_mgmt_leave_req = znp_server.reply_once_to( request=c.ZDO.MgmtLeaveReq.Req(DstAddr=0x0000, partial=True), responses=[ c.ZDO.MgmtLeaveReq.Rsp(Status=t.Status.SUCCESS), c.ZDO.MgmtLeaveRsp.Callback(Src=0x000, Status=t.ZDOStatus.SUCCESS), ], ) # Make sure the device exists assert app.get_device(nwk=device.nwk) is device await app.remove(device.ieee) await asyncio.gather(bad_mgmt_leave_req, good_mgmt_leave_req) # Make sure the device is gone once we remove it with pytest.raises(KeyError): app.get_device(nwk=device.nwk)
async def test_zigpy_request(application, mocker): app, znp_server = application await app.startup(auto_form=False) TSN = 1 device = app.add_device(ieee=t.EUI64(range(8)), nwk=0xAABB) device.status = zigpy.device.Status.ENDPOINTS_INIT device.initializing = False device.add_endpoint(1).add_input_cluster(6) # Respond to a light turn on request data_req = znp_server.reply_once_to( request=c.AF.DataRequestExt.Req( DstAddrModeAddress=t.AddrModeAddress(mode=t.AddrMode.NWK, address=device.nwk), DstEndpoint=1, SrcEndpoint=1, ClusterId=6, TSN=TSN, Data=bytes([0x01, TSN, 0x01]), partial=True, ), responses=[ c.AF.DataRequestExt.Rsp(Status=t.Status.SUCCESS), c.AF.DataConfirm.Callback( Status=t.Status.SUCCESS, Endpoint=1, TSN=TSN, ), c.ZDO.SrcRtgInd.Callback(DstAddr=device.nwk, Relays=[]), c.AF.IncomingMsg.Callback( GroupId=0x0000, ClusterId=6, SrcAddr=device.nwk, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=False, LQI=63, SecurityUse=False, TimeStamp=1198515, TSN=0, Data=bytes([0x08, TSN, 0x0B, 0x00, 0x00]), MacSrcAddr=device.nwk, MsgResultRadius=29, ), ], ) # Turn on the light await device.endpoints[1].on_off.on() await data_req
async def test_on_zdo_device_join(application, mocker): app, znp_server = application await app.startup(auto_form=False) mocker.patch.object(app, "handle_join") nwk = 0x1234 ieee = t.EUI64(range(8)) znp_server.send( c.ZDO.TCDevInd.Callback(SrcNwk=nwk, SrcIEEE=ieee, ParentNwk=0x0001)) app.handle_join.assert_called_once_with(nwk=nwk, ieee=ieee, parent_nwk=0x0001)
async def test_on_zdo_device_leave_callback(application, mocker): app, znp_server = application await app.startup(auto_form=False) mocker.patch.object(app, "handle_leave") nwk = 0x1234 ieee = t.EUI64(range(8)) znp_server.send( c.ZDO.LeaveInd.Callback(NWK=nwk, IEEE=ieee, Request=False, Remove=False, Rejoin=False)) app.handle_leave.assert_called_once_with(nwk=nwk, ieee=ieee)
async def test_on_zdo_device_announce(application, mocker): app, znp_server = application await app.startup(auto_form=False) mocker.patch.object(app, "handle_join") nwk = 0x1234 ieee = t.EUI64(range(8)) znp_server.send( c.ZDOCommands.EndDeviceAnnceInd.Callback( Src=0x0001, NWK=nwk, IEEE=ieee, Capabilities=c.zdo.MACCapabilities.Router)) app.handle_join.assert_called_once_with(nwk=nwk, ieee=ieee, parent_nwk=0)
async def test_on_zdo_device_announce(application, mocker): app, znp_server = application await app.startup(auto_form=False) mocker.patch.object(app, "handle_message") device = app.add_device(ieee=t.EUI64(range(8)), nwk=0xFA9E) znp_server.send( c.ZDO.EndDeviceAnnceInd.Callback( Src=0x0001, NWK=device.nwk, IEEE=device.ieee, Capabilities=c.zdo.MACCapabilities.Router, )) app.handle_message.called_once_with(cluster=ZDOCmd.Device_annce)
async def test_request_use_ieee(application, mocker, use_ieee, dev_addr): app, znp_server = application device = app.add_device(ieee=t.EUI64(range(8)), nwk=0xAABB) send_req = mocker.patch.object(app, "_send_request", new=CoroutineMock()) await app.request( device, use_ieee=use_ieee, profile=None, cluster=None, src_ep=None, dst_ep=None, sequence=None, data=None, ) assert send_req.call_count == 1 assert send_req.mock_calls[0][2]["dst_addr"] == dev_addr
async def test_zigpy_request_failure(application, mocker): app, znp_server = application await app.startup(auto_form=False) TSN = 1 device = app.add_device(ieee=t.EUI64(range(8)), nwk=0xAABB) device.status = zigpy.device.Status.ENDPOINTS_INIT device.initializing = False device.add_endpoint(1).add_input_cluster(6) # Fail to respond to a light turn on request znp_server.reply_to( request=c.AF.DataRequestExt.Req( DstAddrModeAddress=t.AddrModeAddress(mode=t.AddrMode.NWK, address=device.nwk), DstEndpoint=1, SrcEndpoint=1, ClusterId=6, TSN=TSN, Data=bytes([0x01, TSN, 0x01]), partial=True, ), responses=[ c.AF.DataRequestExt.Rsp(Status=t.Status.SUCCESS), c.AF.DataConfirm.Callback( Status=t.Status.FAILURE, Endpoint=1, TSN=TSN, ), ], ) mocker.patch.object(app, "_send_request", new=CoroutineMock(wraps=app._send_request)) # Fail to turn on the light with pytest.raises(zigpy.exceptions.DeliveryError): await device.endpoints[1].on_off.on() assert app._send_request.call_count == 1
async def test_zdo_request_interception(application, mocker): app, znp_server = application await app.startup(auto_form=False) device = app.add_device(ieee=t.EUI64(range(8)), nwk=0xFA9E) # Send back a request response active_ep_req = znp_server.reply_once_to( request=c.ZDO.SimpleDescReq.Req(DstAddr=device.nwk, NWKAddrOfInterest=device.nwk, Endpoint=1), responses=[ c.ZDO.SimpleDescReq.Rsp(Status=t.Status.SUCCESS), c.ZDO.SimpleDescRsp.Callback( Src=device.nwk, Status=t.ZDOStatus.SUCCESS, NWK=device.nwk, SimpleDescriptor=SizePrefixedSimpleDescriptor(*dict( endpoint=1, profile=49246, device_type=256, device_version=2, input_clusters=[0, 3, 4, 5, 6, 8, 2821, 4096], output_clusters=[5, 25, 32, 4096], ).values()), ), ], ) status, message = await app.request( device=device, profile=260, cluster=ZDOCmd.Simple_Desc_req, src_ep=0, dst_ep=0, sequence=1, data=b"\x01\x9e\xfa\x01", use_ieee=False, ) await active_ep_req assert status == t.Status.SUCCESS
async def test_update_network(mocker, caplog, application): app, znp_server = application await app.startup(auto_form=False) mocker.patch.object(app, "_reset", new=CoroutineMock()) channel = t.uint8_t(15) pan_id = t.PanId(0x1234) extended_pan_id = t.ExtendedPanId(range(8)) channels = t.Channels.from_channel_list([11, 15, 20]) network_key = t.KeyData(range(16)) channels_updated = znp_server.reply_once_to( request=c.Util.SetChannels.Req(Channels=channels), responses=[c.Util.SetChannels.Rsp(Status=t.Status.SUCCESS)], ) bdb_set_primary_channel = znp_server.reply_once_to( request=c.AppConfig.BDBSetChannel.Req(IsPrimary=True, Channel=channels), responses=[c.AppConfig.BDBSetChannel.Rsp(Status=t.Status.SUCCESS)], ) bdb_set_secondary_channel = znp_server.reply_once_to( request=c.AppConfig.BDBSetChannel.Req(IsPrimary=False, Channel=t.Channels.NO_CHANNELS), responses=[c.AppConfig.BDBSetChannel.Rsp(Status=t.Status.SUCCESS)], ) set_pan_id = znp_server.reply_once_to( request=c.Util.SetPanId.Req(PanId=pan_id), responses=[c.Util.SetPanId.Rsp(Status=t.Status.SUCCESS)], ) set_extended_pan_id = znp_server.reply_once_to( request=c.SYS.OSALNVWrite.Req(Id=NwkNvIds.EXTENDED_PAN_ID, Offset=0, Value=extended_pan_id.serialize()), responses=[c.SYS.OSALNVWrite.Rsp(Status=t.Status.SUCCESS)], ) set_network_key_util = znp_server.reply_once_to( request=c.Util.SetPreConfigKey.Req(PreConfigKey=network_key), responses=[c.Util.SetPreConfigKey.Rsp(Status=t.Status.SUCCESS)], ) set_network_key_nvram = znp_server.reply_once_to( request=c.SYS.OSALNVWrite.Req(Id=NwkNvIds.PRECFGKEYS_ENABLE, Offset=0, Value=t.Bool(True).serialize()), responses=[c.SYS.OSALNVWrite.Rsp(Status=t.Status.SUCCESS)], ) set_nib_nvram = znp_server.reply_once_to( request=c.SYS.OSALNVWrite.Req(Id=NwkNvIds.NIB, Offset=0, partial=True), responses=[c.SYS.OSALNVWrite.Rsp(Status=t.Status.SUCCESS)], ) # But it does succeed with a warning if you explicitly allow it with caplog.at_level(logging.WARNING): await app.update_network( channel=channel, channels=channels, extended_pan_id=extended_pan_id, network_key=network_key, pan_id=pan_id, tc_address=t.EUI64(range(8)), tc_link_key=t.KeyData(range(8)), update_id=0, reset=True, ) # We should receive a few warnings for `tc_` stuff assert len(caplog.records) >= 2 await channels_updated await bdb_set_primary_channel await bdb_set_secondary_channel await set_pan_id await set_extended_pan_id await set_network_key_util await set_network_key_nvram await set_nib_nvram app._reset.assert_called_once_with() # Ensure we set everything we could assert app.nwk_update_id is None # We can't use it assert app.channel == channel assert app.channels == channels assert app.pan_id == pan_id assert app.extended_pan_id == extended_pan_id
def application(znp_server): app = ControllerApplication(config_for_port_path("/dev/ttyFAKE0")) # Handle the entire startup sequence znp_server.reply_to( request=c.SYS.ResetReq.Req(Type=t.ResetType.Soft), responses=[ c.SYS.ResetInd.Callback( Reason=t.ResetReason.PowerUp, TransportRev=2, ProductId=1, MajorRel=2, MinorRel=7, MaintRel=1, ) ], ) active_eps = [100, 13, 12, 11, 8, 1] znp_server.reply_to( request=c.ZDO.ActiveEpReq.Req(DstAddr=0x0000, NWKAddrOfInterest=0x0000), responses=[ c.ZDO.ActiveEpReq.Rsp(Status=t.Status.SUCCESS), c.ZDO.ActiveEpRsp.Callback( Src=0x0000, Status=t.ZDOStatus.SUCCESS, NWK=0x0000, ActiveEndpoints=active_eps, ), ], ) znp_server.reply_to( request=c.ZDO.ActiveEpReq.Req(DstAddr=0x0000, NWKAddrOfInterest=0x0000), responses=[ c.ZDO.ActiveEpReq.Rsp(Status=t.Status.SUCCESS), c.ZDO.ActiveEpRsp.Callback( Src=0x0000, Status=t.ZDOStatus.SUCCESS, NWK=0x0000, ActiveEndpoints=active_eps, ), ], ) def on_endpoint_registration(req): assert req.Endpoint not in active_eps active_eps.append(req.Endpoint) active_eps.sort(reverse=True) return c.AF.Register.Rsp(Status=t.Status.SUCCESS) znp_server.reply_to( request=c.AF.Register.Req(partial=True), responses=[on_endpoint_registration], ) def on_endpoint_deletion(req): assert req.Endpoint in active_eps active_eps.remove(req.Endpoint) return c.AF.Delete.Rsp(Status=t.Status.SUCCESS) znp_server.reply_to( request=c.AF.Delete.Req(partial=True), responses=[on_endpoint_deletion], ) znp_server.reply_to( request=c.AppConfig.BDBStartCommissioning.Req( Mode=c.app_config.BDBCommissioningMode.NwkFormation), responses=[ c.AppConfig.BDBStartCommissioning.Rsp(Status=t.Status.SUCCESS), c.AppConfig.BDBCommissioningNotification.Callback( Status=c.app_config.BDBCommissioningStatus.Success, Mode=c.app_config.BDBCommissioningMode.NwkSteering, RemainingModes=c.app_config.BDBCommissioningMode.NONE, ), ], ) # Reply to the initialization NVID writes for nvid in [ NwkNvIds.CONCENTRATOR_ENABLE, NwkNvIds.CONCENTRATOR_DISCOVERY, NwkNvIds.CONCENTRATOR_RC, NwkNvIds.SRC_RTG_EXPIRY_TIME, NwkNvIds.NWK_CHILD_AGE_ENABLE, NwkNvIds.LOGICAL_TYPE, ]: znp_server.reply_to( request=c.SYS.OSALNVWrite.Req(Id=nvid, Offset=0, partial=True), responses=[c.SYS.OSALNVWrite.Rsp(Status=t.Status.SUCCESS)], ) znp_server.reply_to( request=c.SYS.OSALNVRead.Req(Id=NwkNvIds.HAS_CONFIGURED_ZSTACK3, Offset=0), responses=[ c.SYS.OSALNVRead.Rsp(Status=t.Status.SUCCESS, Value=b"\x55") ], ) znp_server.reply_to( request=c.Util.GetDeviceInfo.Req(), responses=[ c.Util.GetDeviceInfo.Rsp( Status=t.Status.SUCCESS, IEEE=t.EUI64([0x00, 0x12, 0x4B, 0x00, 0x1C, 0xAA, 0xAC, 0x5C]), NWK=t.NWK(0xFFFE), DeviceType=t.DeviceTypeCapabilities(7), DeviceState=t.DeviceState.InitializedNotStarted, AssociatedDevices=[], ) ], ) znp_server.reply_to( request=c.ZDO.StartupFromApp.Req(partial=True), responses=[ c.ZDO.StartupFromApp.Rsp( State=c.zdo.StartupState.RestoredNetworkState), c.ZDO.StateChangeInd.Callback( State=t.DeviceState.StartedAsCoordinator), ], ) # The NIB matches the above device info NIB = bytes.fromhex(""" 790502331433001e0000000105018f00070002051e000000190000000000000000000000FFFE0800 008010020f0f040001000000010000000000124b001caaac5c010000000000000000000000000000 000000000000000000000000000000000000000000000f030001780a0100000020470000 """) znp_server.reply_to( request=c.SYS.OSALNVRead.Req(Id=NwkNvIds.NIB, Offset=0), responses=[c.SYS.OSALNVRead.Rsp(Status=t.Status.SUCCESS, Value=NIB)], ) return app, znp_server
"_send_request", new=CoroutineMock(wraps=app._send_request)) # Fail to turn on the light with pytest.raises(zigpy.exceptions.DeliveryError): await device.endpoints[1].on_off.on() assert app._send_request.call_count == 1 @pytest_mark_asyncio_timeout(seconds=3) @pytest.mark.parametrize( "use_ieee,dev_addr", [ (True, t.AddrModeAddress(mode=t.AddrMode.IEEE, address=t.EUI64(range(8)))), (False, t.AddrModeAddress(mode=t.AddrMode.NWK, address=t.NWK(0xAABB))), ], ) async def test_request_use_ieee(application, mocker, use_ieee, dev_addr): app, znp_server = application device = app.add_device(ieee=t.EUI64(range(8)), nwk=0xAABB) send_req = mocker.patch.object(app, "_send_request", new=CoroutineMock()) await app.request( device, use_ieee=use_ieee, profile=None, cluster=None, src_ep=None,
def test_addr_mode_address(): """Test Addr mode address.""" data = b"\x03\x00\x01\x02\x03\x04\x05\x06\x07" extra = b"The rest of the data\x55\xaa" # IEEE r, rest = t.AddrModeAddress.deserialize(data + extra) assert rest == extra assert r.mode == t.AddrMode.IEEE assert r.address == t.EUI64(range(8)) assert r.serialize() == data # NWK data = b"\x02\xaa\x55\x02\x03\x04\x05\x06\x07" r, rest = t.AddrModeAddress.deserialize(data + extra) assert rest == extra assert r.mode == t.AddrMode.NWK assert r.address == t.NWK(0x55AA) assert r.serialize()[:3] == data[:3] assert len(r.serialize()) == 9 # Group data = b"\x01\xcd\xab\x02\x03\x04\x05\x06\x07" r, rest = t.AddrModeAddress.deserialize(data + extra) assert rest == extra assert r.mode == t.AddrMode.Group assert r.address == t.NWK(0xABCD) assert r.serialize()[:3] == data[:3] assert len(r.serialize()) == 9 # Broadcast data = b"\x0f\xfe\xff\x02\x03\x04\x05\x06\x07" r, rest = t.AddrModeAddress.deserialize(data + extra) assert rest == extra assert r.mode == t.AddrMode.Broadcast assert r.address == t.NWK(0xFFFE) assert r.serialize()[:3] == data[:3] assert len(r.serialize()) == 9 with pytest.raises(ValueError): # 0xab is not a valid mode data = b"\xAB\xaa\x55\x02\x03\x04\x05\x06\x07" t.AddrModeAddress.deserialize(data) with pytest.raises(ValueError): # NOT_PRESENT is a valid AddrMode member but it is not a valid AddrModeAddress data = b"\x00\xaa\x55\x02\x03\x04\x05\x06\x07" t.AddrModeAddress.deserialize(data) # Bytes at the end for NWK address mode are ignored data1 = b"\x02\x0E\xAD" + b"\xC0\x8C\x97\x83\xB0\x20\x33" data2 = b"\x02\x0E\xAD" + b"\x3F\xB9\x5B\x64\x20\x86\xD6" r1, _ = t.AddrModeAddress.deserialize(data1) r2, _ = t.AddrModeAddress.deserialize(data2) assert r1 == r2 # All of the bytes are used for IEEE address mode data1 = b"\x02\x0E\xAD\xC0\x8C\x97\x83\xB0\x20\x33" data2 = b"\x02\x0E\xAD\x3F\xB9\x5B\x64\x20\x86\xD6" r3, _ = t.AddrModeAddress.deserialize(b"\x03" + data1[1:]) r4, _ = t.AddrModeAddress.deserialize(b"\x03" + data2[1:]) assert r3 != r4