7
7
from unittest .mock import Mock , PropertyMock
8
8
9
9
import pytest
10
+ import zigpy .types
10
11
import zigpy .device
11
12
12
13
try :
@@ -373,6 +374,19 @@ def inner(function):
373
374
return inner
374
375
375
376
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
+
376
390
class BaseZStackDevice (BaseServerZNP ):
377
391
def __init__ (self , * args , ** kwargs ):
378
392
super ().__init__ (* args , ** kwargs )
@@ -381,6 +395,7 @@ def __init__(self, *args, **kwargs):
381
395
self ._nvram = {}
382
396
383
397
self .device_state = t .DeviceState .InitializedNotStarted
398
+ self .zdo_callbacks = set ()
384
399
385
400
# Handle the decorators
386
401
for name in dir (self ):
@@ -531,18 +546,79 @@ def _default_nib(self):
531
546
nwkUpdateId = 0 ,
532
547
)
533
548
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 = [
538
594
c .ZDO .ActiveEpRsp .Callback (
539
595
Src = 0x0000 ,
540
596
Status = t .ZDOStatus .SUCCESS ,
541
597
NWK = 0x0000 ,
542
598
ActiveEndpoints = [ep .Endpoint for ep in self .active_endpoints ],
543
- ),
599
+ )
544
600
]
545
601
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
+
546
622
@reply_to (c .AF .Register .Req (partial = True ))
547
623
def on_endpoint_registration (self , req ):
548
624
self .active_endpoints .insert (0 , req )
@@ -555,31 +631,53 @@ def on_endpoint_deletion(self, req):
555
631
556
632
return c .AF .Delete .Rsp (Status = t .Status .SUCCESS )
557
633
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
+
562
638
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 ,
568
673
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 ,
578
676
),
579
- ]
677
+ )
678
+ )
580
679
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
583
681
584
682
@reply_to (c .SYS .OSALNVWrite .Req (partial = True ))
585
683
@reply_to (c .SYS .OSALNVWriteExt .Req (partial = True ))
@@ -714,30 +812,15 @@ def util_device_info(self, request):
714
812
AssociatedDevices = [],
715
813
)
716
814
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
-
737
815
@reply_to (c .ZDO .ExtRouteChk .Req (partial = True ))
738
816
def zdo_route_check (self , request ):
739
817
return c .ZDO .ExtRouteChk .Rsp (Status = c .zdo .RoutingStatus .SUCCESS )
740
818
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
+
741
824
742
825
class BaseZStack1CC2531 (BaseZStackDevice ):
743
826
align_structs = False
@@ -818,10 +901,11 @@ def startup_from_app(self, req):
818
901
self .create_nib ,
819
902
]
820
903
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 = [
825
909
c .ZDO .NodeDescRsp .Callback (
826
910
Src = 0x0000 ,
827
911
Status = t .ZDOStatus .SUCCESS ,
@@ -840,25 +924,40 @@ def node_desc_responder(self, req):
840
924
),
841
925
]
842
926
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
849
952
)
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 = []
856
953
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 )]
862
961
863
962
@reply_to (c .UTIL .LEDControl .Req (partial = True ))
864
963
def led_responder (self , req ):
@@ -884,22 +983,6 @@ def handle_bdb_set_primary_channel(self, request):
884
983
885
984
return c .AppConfig .BDBSetChannel .Rsp (Status = t .Status .SUCCESS )
886
985
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
-
903
986
def create_nib (self , _ = None ):
904
987
super ().create_nib ()
905
988
@@ -1073,10 +1156,11 @@ def version_replier(self, request):
1073
1156
BootloaderRevision = 0xFFFFFFFF ,
1074
1157
)
1075
1158
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 = [
1080
1164
c .ZDO .NodeDescRsp .Callback (
1081
1165
Src = 0x0000 ,
1082
1166
Status = t .ZDOStatus .SUCCESS ,
@@ -1095,6 +1179,28 @@ def node_desc_responder(self, req):
1095
1179
),
1096
1180
]
1097
1181
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
+
1098
1204
@reply_to (c .UTIL .LEDControl .Req (partial = True ))
1099
1205
def led_responder (self , req ):
1100
1206
# XXX: Yes, there is *no response*
@@ -1147,7 +1253,7 @@ def version_replier(self, request):
1147
1253
BootloaderRevision = 0 ,
1148
1254
)
1149
1255
1150
- node_desc_responder = BaseZStack1CC2531 .node_desc_responder
1256
+ on_zdo_node_desc_req = BaseZStack1CC2531 .on_zdo_node_desc_req
1151
1257
1152
1258
@reply_to (c .UTIL .LEDControl .Req (partial = True ))
1153
1259
def led_responder (self , req ):
0 commit comments