Skip to content

Commit 01e773f

Browse files
committed
Begin fixing unit testing infrastructure
1 parent 4bcda8b commit 01e773f

File tree

1 file changed

+194
-88
lines changed

1 file changed

+194
-88
lines changed

tests/conftest.py

Lines changed: 194 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from unittest.mock import Mock, PropertyMock
88

99
import pytest
10+
import zigpy.types
1011
import zigpy.device
1112

1213
try:
@@ -373,6 +374,19 @@ def inner(function):
373374
return inner
374375

375376

377+
def serialize_zdo_command(command_id, **kwargs):
378+
field_names, field_types = zdo_t.CLUSTERS[command_id]
379+
380+
return t.Bytes(zigpy.types.serialize(kwargs.values(), field_types))
381+
382+
383+
def deserialize_zdo_command(command_id, data):
384+
field_names, field_types = zdo_t.CLUSTERS[command_id]
385+
args, data = zigpy.types.deserialize(data, field_types)
386+
387+
return dict(zip(field_names, args))
388+
389+
376390
class BaseZStackDevice(BaseServerZNP):
377391
def __init__(self, *args, **kwargs):
378392
super().__init__(*args, **kwargs)
@@ -381,6 +395,7 @@ def __init__(self, *args, **kwargs):
381395
self._nvram = {}
382396

383397
self.device_state = t.DeviceState.InitializedNotStarted
398+
self.zdo_callbacks = set()
384399

385400
# Handle the decorators
386401
for name in dir(self):
@@ -531,18 +546,79 @@ def _default_nib(self):
531546
nwkUpdateId=0,
532547
)
533548

534-
@reply_to(c.ZDO.ActiveEpReq.Req(DstAddr=0x0000, NWKAddrOfInterest=0x0000))
535-
def active_endpoints_request(self, req):
536-
return [
537-
c.ZDO.ActiveEpReq.Rsp(Status=t.Status.SUCCESS),
549+
@reply_to(c.AF.DataRequestExt.Req(partial=True, DstEndpoint=0))
550+
def on_zdo_request(self, req):
551+
kwargs = deserialize_zdo_command(req.ClusterId, req.Data[1:])
552+
handler_name = f"on_zdo_{zdo_t.ZDOCmd(req.ClusterId).name.lower()}"
553+
handler = getattr(self, handler_name, None)
554+
555+
if handler is None:
556+
LOGGER.warning("No ZDO handler %s, kwargs: %s", handler_name, kwargs)
557+
return
558+
559+
responses = handler(req=req, **kwargs) or []
560+
561+
return [c.AF.DataRequestExt.Rsp(Status=t.Status.SUCCESS)] + responses
562+
563+
def on_zdo_mgmt_permit_joining_req(self, req, PermitDuration, TC_Significant):
564+
if req.DstAddrModeAddress.address != 0x0000:
565+
return
566+
567+
responses = [
568+
c.ZDO.MgmtPermitJoinRsp.Callback(Src=0x0000, Status=t.ZDOStatus.SUCCESS)
569+
]
570+
571+
if zdo_t.ZDOCmd.Mgmt_Permit_Joining_rsp in self.zdo_callbacks:
572+
responses.append(
573+
c.ZDO.MsgCbIncoming.Callback(
574+
Src=0x0000,
575+
IsBroadcast=t.Bool.false,
576+
ClusterId=zdo_t.ZDOCmd.Mgmt_Permit_Joining_rsp,
577+
SecurityUse=0,
578+
TSN=req.TSN,
579+
MacDst=0x0000,
580+
Data=serialize_zdo_command(
581+
command_id=zdo_t.ZDOCmd.Mgmt_Permit_Joining_rsp,
582+
Status=t.ZDOStatus.SUCCESS,
583+
),
584+
)
585+
)
586+
587+
return responses
588+
589+
def on_zdo_active_ep_req(self, req, NWKAddrOfInterest):
590+
if NWKAddrOfInterest != 0x0000:
591+
return
592+
593+
responses = [
538594
c.ZDO.ActiveEpRsp.Callback(
539595
Src=0x0000,
540596
Status=t.ZDOStatus.SUCCESS,
541597
NWK=0x0000,
542598
ActiveEndpoints=[ep.Endpoint for ep in self.active_endpoints],
543-
),
599+
)
544600
]
545601

602+
if zdo_t.ZDOCmd.Active_EP_rsp in self.zdo_callbacks:
603+
responses.append(
604+
c.ZDO.MsgCbIncoming.Callback(
605+
Src=0x0000,
606+
IsBroadcast=t.Bool.false,
607+
ClusterId=zdo_t.ZDOCmd.Active_EP_rsp,
608+
SecurityUse=0,
609+
TSN=req.TSN,
610+
MacDst=0x0000,
611+
Data=serialize_zdo_command(
612+
command_id=zdo_t.ZDOCmd.Active_EP_rsp,
613+
Status=t.ZDOStatus.SUCCESS,
614+
NWKAddrOfInterest=0x0000,
615+
ActiveEPList=[ep.Endpoint for ep in self.active_endpoints],
616+
),
617+
)
618+
)
619+
620+
return responses
621+
546622
@reply_to(c.AF.Register.Req(partial=True))
547623
def on_endpoint_registration(self, req):
548624
self.active_endpoints.insert(0, req)
@@ -555,31 +631,53 @@ def on_endpoint_deletion(self, req):
555631

556632
return c.AF.Delete.Rsp(Status=t.Status.SUCCESS)
557633

558-
@reply_to(
559-
c.ZDO.SimpleDescReq.Req(DstAddr=0x0000, NWKAddrOfInterest=0x0000, partial=True)
560-
)
561-
def on_simple_desc_req(self, req):
634+
def on_zdo_simple_desc_req(self, req, NWKAddrOfInterest, EndPoint):
635+
if NWKAddrOfInterest != 0x0000:
636+
return
637+
562638
for ep in self.active_endpoints:
563-
if ep.Endpoint == req.Endpoint:
564-
return [
565-
c.ZDO.SimpleDescReq.Rsp(Status=t.Status.SUCCESS),
566-
c.ZDO.SimpleDescRsp.Callback(
567-
Src=0x0000,
639+
if ep.Endpoint == EndPoint:
640+
break
641+
else:
642+
# Bad things happen when an invalid endpoint ID is passed in
643+
pytest.fail("Simple descriptor request to invalid endpoint breaks Z-Stack")
644+
return
645+
646+
responses = [
647+
c.ZDO.SimpleDescRsp.Callback(
648+
Src=0x0000,
649+
Status=t.ZDOStatus.SUCCESS,
650+
NWK=0x0000,
651+
SimpleDescriptor=zdo_t.SizePrefixedSimpleDescriptor(
652+
endpoint=ep.Endpoint,
653+
profile=ep.ProfileId,
654+
device_type=ep.DeviceId,
655+
device_version=ep.DeviceVersion,
656+
input_clusters=ep.InputClusters,
657+
output_clusters=ep.OutputClusters,
658+
),
659+
),
660+
]
661+
662+
if zdo_t.ZDOCmd.Simple_Desc_rsp in self.zdo_callbacks:
663+
responses.append(
664+
c.ZDO.MsgCbIncoming.Callback(
665+
Src=0x0000,
666+
IsBroadcast=t.Bool.false,
667+
ClusterId=zdo_t.ZDOCmd.Simple_Desc_rsp,
668+
SecurityUse=0,
669+
TSN=req.TSN,
670+
MacDst=0x0000,
671+
Data=serialize_zdo_command(
672+
command_id=zdo_t.ZDOCmd.Simple_Desc_rsp,
568673
Status=t.ZDOStatus.SUCCESS,
569-
NWK=0x0000,
570-
SimpleDescriptor=zdo_t.SizePrefixedSimpleDescriptor(
571-
endpoint=ep.Endpoint,
572-
profile=ep.ProfileId,
573-
device_type=ep.DeviceId,
574-
device_version=ep.DeviceVersion,
575-
input_clusters=ep.InputClusters,
576-
output_clusters=ep.OutputClusters,
577-
),
674+
NWKAddrOfInterest=0x0000,
675+
SimpleDescriptor=responses[0].SimpleDescriptor,
578676
),
579-
]
677+
)
678+
)
580679

581-
# Bad things happen when an invalid endpoint ID is passed in
582-
pytest.fail("Simple descriptor request to an invalid endpoint breaks Z-Stack")
680+
return responses
583681

584682
@reply_to(c.SYS.OSALNVWrite.Req(partial=True))
585683
@reply_to(c.SYS.OSALNVWriteExt.Req(partial=True))
@@ -714,30 +812,15 @@ def util_device_info(self, request):
714812
AssociatedDevices=[],
715813
)
716814

717-
@reply_to(
718-
c.ZDO.MgmtNWKUpdateReq.Req(Dst=0x0000, DstAddrMode=t.AddrMode.NWK, partial=True)
719-
)
720-
def nwk_update_req(self, request):
721-
valid_channels = [t.Channels.from_channel_list([i]) for i in range(11, 26 + 1)]
722-
723-
if request.ScanDuration == 0xFE:
724-
assert request.Channels in valid_channels
725-
726-
def update_channel():
727-
nib = self.nib
728-
nib.nwkLogicalChannel = 11 + valid_channels.index(request.Channels)
729-
nib.nwkUpdateId += 1
730-
731-
self.nib = nib
732-
733-
asyncio.get_running_loop().call_later(0.1, update_channel)
734-
735-
return c.ZDO.MgmtNWKUpdateReq.Rsp(Status=t.Status.SUCCESS)
736-
737815
@reply_to(c.ZDO.ExtRouteChk.Req(partial=True))
738816
def zdo_route_check(self, request):
739817
return c.ZDO.ExtRouteChk.Rsp(Status=c.zdo.RoutingStatus.SUCCESS)
740818

819+
@reply_to(c.ZDO.MsgCallbackRegister.Req(partial=True))
820+
def register_zdo_callback(self, request):
821+
self.zdo_callbacks.add(request.ClusterId)
822+
return c.ZDO.MsgCallbackRegister.Rsp(Status=t.Status.SUCCESS)
823+
741824

742825
class BaseZStack1CC2531(BaseZStackDevice):
743826
align_structs = False
@@ -818,10 +901,11 @@ def startup_from_app(self, req):
818901
self.create_nib,
819902
]
820903

821-
@reply_to(c.ZDO.NodeDescReq.Req(DstAddr=0x0000, NWKAddrOfInterest=0x0000))
822-
def node_desc_responder(self, req):
823-
return [
824-
c.ZDO.NodeDescReq.Rsp(Status=t.Status.SUCCESS),
904+
def on_zdo_node_desc_req(self, req, NWKAddrOfInterest):
905+
if NWKAddrOfInterest != 0x0000:
906+
return
907+
908+
responses = [
825909
c.ZDO.NodeDescRsp.Callback(
826910
Src=0x0000,
827911
Status=t.ZDOStatus.SUCCESS,
@@ -840,25 +924,40 @@ def node_desc_responder(self, req):
840924
),
841925
]
842926

843-
@reply_to(
844-
c.ZDO.MgmtPermitJoinReq.Req(AddrMode=t.AddrMode.NWK, Dst=0x0000, partial=True)
845-
)
846-
@reply_to(
847-
c.ZDO.MgmtPermitJoinReq.Req(
848-
AddrMode=t.AddrMode.Broadcast, Dst=0xFFFC, partial=True
927+
if zdo_t.ZDOCmd.Node_Desc_rsp in self.zdo_callbacks:
928+
responses.append(
929+
c.ZDO.MsgCbIncoming.Callback(
930+
Src=0x0000,
931+
IsBroadcast=t.Bool.false,
932+
ClusterId=zdo_t.ZDOCmd.Node_Desc_rsp,
933+
SecurityUse=0,
934+
TSN=req.TSN,
935+
MacDst=0x0000,
936+
Data=serialize_zdo_command(
937+
command_id=zdo_t.ZDOCmd.Node_Desc_rsp,
938+
Status=t.ZDOStatus.SUCCESS,
939+
NWKAddrOfInterest=0x0000,
940+
NodeDescriptor=zdo_t.NodeDescriptor(
941+
**responses[0].NodeDescriptor.as_dict()
942+
),
943+
),
944+
)
945+
)
946+
947+
return responses
948+
949+
def on_zdo_mgmt_permit_joining_req(self, req, PermitDuration, TC_Significant):
950+
result = super().on_zdo_mgmt_permit_joining_req(
951+
req, PermitDuration, TC_Significant
849952
)
850-
)
851-
def permit_join(self, request):
852-
if request.Duration != 0:
853-
rsp = [c.ZDO.PermitJoinInd.Callback(Duration=request.Duration)]
854-
else:
855-
rsp = []
856953

857-
return rsp + [
858-
c.ZDO.MgmtPermitJoinReq.Rsp(Status=t.Status.SUCCESS),
859-
c.ZDO.MgmtPermitJoinRsp.Callback(Src=0x0000, Status=t.ZDOStatus.SUCCESS),
860-
c.ZDO.PermitJoinInd.Callback(Duration=0),
861-
]
954+
if not result:
955+
return
956+
957+
if PermitDuration != 0:
958+
result = [c.ZDO.PermitJoinInd.Callback(Duration=req.Duration)] + result
959+
960+
return result + [c.ZDO.PermitJoinInd.Callback(Duration=0)]
862961

863962
@reply_to(c.UTIL.LEDControl.Req(partial=True))
864963
def led_responder(self, req):
@@ -884,22 +983,6 @@ def handle_bdb_set_primary_channel(self, request):
884983

885984
return c.AppConfig.BDBSetChannel.Rsp(Status=t.Status.SUCCESS)
886985

887-
@reply_to(
888-
c.ZDO.MgmtPermitJoinReq.Req(
889-
AddrMode=t.AddrMode.NWK, Dst=0x0000, Duration=0, partial=True
890-
)
891-
)
892-
@reply_to(
893-
c.ZDO.MgmtPermitJoinReq.Req(
894-
AddrMode=t.AddrMode.Broadcast, Dst=0xFFFC, Duration=0, partial=True
895-
)
896-
)
897-
def permit_join(self, request):
898-
return [
899-
c.ZDO.MgmtPermitJoinReq.Rsp(Status=t.Status.SUCCESS),
900-
c.ZDO.MgmtPermitJoinRsp.Callback(Src=0x0000, Status=t.ZDOStatus.SUCCESS),
901-
]
902-
903986
def create_nib(self, _=None):
904987
super().create_nib()
905988

@@ -1073,10 +1156,11 @@ def version_replier(self, request):
10731156
BootloaderRevision=0xFFFFFFFF,
10741157
)
10751158

1076-
@reply_to(c.ZDO.NodeDescReq.Req(DstAddr=0x0000, NWKAddrOfInterest=0x0000))
1077-
def node_desc_responder(self, req):
1078-
return [
1079-
c.ZDO.NodeDescReq.Rsp(Status=t.Status.SUCCESS),
1159+
def on_zdo_node_desc_req(self, req, NWKAddrOfInterest):
1160+
if NWKAddrOfInterest != 0x0000:
1161+
return
1162+
1163+
responses = [
10801164
c.ZDO.NodeDescRsp.Callback(
10811165
Src=0x0000,
10821166
Status=t.ZDOStatus.SUCCESS,
@@ -1095,6 +1179,28 @@ def node_desc_responder(self, req):
10951179
),
10961180
]
10971181

1182+
if zdo_t.ZDOCmd.Node_Desc_rsp in self.zdo_callbacks:
1183+
responses.append(
1184+
c.ZDO.MsgCbIncoming.Callback(
1185+
Src=0x0000,
1186+
IsBroadcast=t.Bool.false,
1187+
ClusterId=zdo_t.ZDOCmd.Node_Desc_rsp,
1188+
SecurityUse=0,
1189+
TSN=req.TSN,
1190+
MacDst=0x0000,
1191+
Data=serialize_zdo_command(
1192+
command_id=zdo_t.ZDOCmd.Node_Desc_rsp,
1193+
Status=t.ZDOStatus.SUCCESS,
1194+
NWKAddrOfInterest=0x0000,
1195+
NodeDescriptor=zdo_t.NodeDescriptor.replace(
1196+
responses[0].NodeDescriptor
1197+
),
1198+
),
1199+
)
1200+
)
1201+
1202+
return responses
1203+
10981204
@reply_to(c.UTIL.LEDControl.Req(partial=True))
10991205
def led_responder(self, req):
11001206
# XXX: Yes, there is *no response*
@@ -1147,7 +1253,7 @@ def version_replier(self, request):
11471253
BootloaderRevision=0,
11481254
)
11491255

1150-
node_desc_responder = BaseZStack1CC2531.node_desc_responder
1256+
on_zdo_node_desc_req = BaseZStack1CC2531.on_zdo_node_desc_req
11511257

11521258
@reply_to(c.UTIL.LEDControl.Req(partial=True))
11531259
def led_responder(self, req):

0 commit comments

Comments
 (0)