26
26
import chip .yaml .format_converter as Converter
27
27
import stringcase
28
28
from chip .ChipDeviceCtrl import ChipDeviceController , discovery
29
- from chip .clusters .Attribute import AttributeStatus , SubscriptionTransaction , TypedAttributePath , ValueDecodeFailure
29
+ from chip .clusters .Attribute import AttributeStatus , EventReadResult , SubscriptionTransaction , TypedAttributePath , ValueDecodeFailure
30
30
from chip .exceptions import ChipStackError
31
31
from chip .yaml .errors import ParsingError , UnexpectedParsingError
32
32
from matter_yamltests .pseudo_clusters .clusters .delay_commands import DelayCommands
@@ -56,6 +56,11 @@ class _GetCommissionerNodeIdResult:
56
56
node_id : int
57
57
58
58
59
+ @dataclass
60
+ class EventResponse :
61
+ event_result_list : list [EventReadResult ]
62
+
63
+
59
64
@dataclass
60
65
class _ActionResult :
61
66
status : _ActionStatus
@@ -69,6 +74,12 @@ class _AttributeSubscriptionCallbackResult:
69
74
result : _ActionResult
70
75
71
76
77
+ @dataclass
78
+ class _EventSubscriptionCallbackResult :
79
+ name : str
80
+ result : _ActionResult
81
+
82
+
72
83
@dataclass
73
84
class _ExecutionContext :
74
85
''' Objects that is commonly passed around this file that are vital to test execution.'''
@@ -78,7 +89,8 @@ class _ExecutionContext:
78
89
subscriptions : list = field (default_factory = list )
79
90
# The key is the attribute/event name, and the value is a queue of subscription callback results
80
91
# that been sent by device under test. For attribute subscription the queue is of type
81
- # _AttributeSubscriptionCallbackResult.
92
+ # _AttributeSubscriptionCallbackResult, for event the queue is of type
93
+ # _EventSubscriptionCallbackResult.
82
94
subscription_callback_result_queue : dict = field (default_factory = dict )
83
95
84
96
@@ -266,6 +278,51 @@ def parse_raw_response(self, raw_resp) -> _ActionResult:
266
278
return _ActionResult (status = _ActionStatus .SUCCESS , response = return_val )
267
279
268
280
281
+ class ReadEventAction (BaseAction ):
282
+ ''' Read Event action to be executed.'''
283
+
284
+ def __init__ (self , test_step , cluster : str , context : _ExecutionContext ):
285
+ '''Converts 'test_step' to read event action that can execute with ChipDeviceController.
286
+
287
+ Args:
288
+ 'test_step': Step containing information required to run read event action.
289
+ 'cluster': Name of cluster read event action is targeting.
290
+ 'context': Contains test-wide common objects such as DataModelLookup instance.
291
+ Raises:
292
+ UnexpectedParsingError: Raised if there is an unexpected parsing error.
293
+ '''
294
+ super ().__init__ (test_step )
295
+ self ._event_name = stringcase .pascalcase (test_step .event )
296
+ self ._cluster = cluster
297
+ self ._endpoint = test_step .endpoint
298
+ self ._node_id = test_step .node_id
299
+ self ._cluster_object = None
300
+ self ._request_object = None
301
+ self ._fabric_filtered = True
302
+ self ._event_number_filter = test_step .event_number
303
+
304
+ self ._request_object = context .data_model_lookup .get_event (self ._cluster ,
305
+ self ._event_name )
306
+ if self ._request_object is None :
307
+ raise UnexpectedParsingError (
308
+ f'ReadEvent failed to find cluster:{ self ._cluster } Event:{ self ._event_name } ' )
309
+
310
+ if test_step .arguments :
311
+ raise UnexpectedParsingError (
312
+ f'ReadEvent should not contain arguments. { self .label } ' )
313
+
314
+ def run_action (self , dev_ctrl : ChipDeviceController ) -> _ActionResult :
315
+ try :
316
+ urgent = 0
317
+ request = [(self ._endpoint , self ._request_object , urgent )]
318
+ resp = asyncio .run (dev_ctrl .ReadEvent (self ._node_id , events = request , eventNumberFilter = self ._event_number_filter ))
319
+ except chip .interaction_model .InteractionModelError as error :
320
+ return _ActionResult (status = _ActionStatus .ERROR , response = error )
321
+
322
+ parsed_resp = EventResponse (event_result_list = resp )
323
+ return _ActionResult (status = _ActionStatus .SUCCESS , response = parsed_resp )
324
+
325
+
269
326
class WaitForCommissioneeAction (BaseAction ):
270
327
''' Wait for commissionee action to be executed.'''
271
328
@@ -327,6 +384,27 @@ def name(self) -> str:
327
384
return self ._name
328
385
329
386
387
+ class EventChangeAccumulator :
388
+ def __init__ (self , name : str , expected_event , output_queue : queue .SimpleQueue ):
389
+ self ._name = name
390
+ self ._expected_event = expected_event
391
+ self ._output_queue = output_queue
392
+
393
+ def __call__ (self , event_result : EventReadResult , transaction : SubscriptionTransaction ):
394
+ if (self ._expected_event .cluster_id == event_result .Header .ClusterId and
395
+ self ._expected_event .event_id == event_result .Header .EventId ):
396
+ event_response = EventResponse (event_result_list = [event_result ])
397
+ result = _ActionResult (status = _ActionStatus .SUCCESS , response = event_response )
398
+
399
+ item = _EventSubscriptionCallbackResult (self ._name , result )
400
+ logging .debug (f'Got subscription report on client { self .name } ' )
401
+ self ._output_queue .put (item )
402
+
403
+ @property
404
+ def name (self ) -> str :
405
+ return self ._name
406
+
407
+
330
408
class SubscribeAttributeAction (ReadAttributeAction ):
331
409
'''Single subscribe attribute action to be executed.'''
332
410
@@ -382,6 +460,63 @@ def run_action(self, dev_ctrl: ChipDeviceController) -> _ActionResult:
382
460
return self .parse_raw_response (raw_resp )
383
461
384
462
463
+ class SubscribeEventAction (ReadEventAction ):
464
+ '''Single subscribe event action to be executed.'''
465
+
466
+ def __init__ (self , test_step , cluster : str , context : _ExecutionContext ):
467
+ '''Converts 'test_step' to subscribe event action that can execute with ChipDeviceController.
468
+
469
+ Args:
470
+ 'test_step': Step containing information required to run subscribe event action.
471
+ 'cluster': Name of cluster subscribe event action is targeting.
472
+ 'context': Contains test-wide common objects such as DataModelLookup instance.
473
+ Raises:
474
+ ParsingError: Raised if there is a benign error, and there is currently no
475
+ action to perform for this subscribe event.
476
+ UnexpectedParsingError: Raised if there is an unexpected parsing error.
477
+ '''
478
+ super ().__init__ (test_step , cluster , context )
479
+ self ._context = context
480
+ if test_step .min_interval is None :
481
+ raise UnexpectedParsingError (
482
+ f'SubscribeAttribute action does not have min_interval { self .label } ' )
483
+ self ._min_interval = test_step .min_interval
484
+
485
+ if test_step .max_interval is None :
486
+ raise UnexpectedParsingError (
487
+ f'SubscribeAttribute action does not have max_interval { self .label } ' )
488
+ self ._max_interval = test_step .max_interval
489
+
490
+ def run_action (self , dev_ctrl : ChipDeviceController ) -> _ActionResult :
491
+ try :
492
+ urgent = 0
493
+ request = [(self ._endpoint , self ._request_object , urgent )]
494
+ subscription = asyncio .run (
495
+ dev_ctrl .ReadEvent (self ._node_id , events = request , eventNumberFilter = self ._event_number_filter ,
496
+ reportInterval = (self ._min_interval , self ._max_interval ),
497
+ keepSubscriptions = False ))
498
+ except chip .interaction_model .InteractionModelError as error :
499
+ return _ActionResult (status = _ActionStatus .ERROR , response = error )
500
+
501
+ self ._context .subscriptions .append (subscription )
502
+ output_queue = self ._context .subscription_callback_result_queue .get (self ._event_name ,
503
+ None )
504
+ if output_queue is None :
505
+ output_queue = queue .SimpleQueue ()
506
+ self ._context .subscription_callback_result_queue [self ._event_name ] = output_queue
507
+
508
+ while not output_queue .empty ():
509
+ output_queue .get (block = False )
510
+
511
+ subscription_handler = EventChangeAccumulator (self .label , self ._request_object , output_queue )
512
+
513
+ subscription .SetEventUpdateCallback (subscription_handler )
514
+
515
+ events = subscription .GetEvents ()
516
+ response = EventResponse (event_result_list = events )
517
+ return _ActionResult (status = _ActionStatus .SUCCESS , response = response )
518
+
519
+
385
520
class WriteAttributeAction (BaseAction ):
386
521
'''Single write attribute action to be executed.'''
387
522
@@ -462,9 +597,15 @@ def __init__(self, test_step, context: _ExecutionContext):
462
597
UnexpectedParsingError: Raised if the expected queue does not exist.
463
598
'''
464
599
super ().__init__ (test_step )
465
- self ._attribute_name = stringcase .pascalcase (test_step .attribute )
466
- self ._output_queue = context .subscription_callback_result_queue .get (self ._attribute_name ,
467
- None )
600
+ if test_step .attribute is not None :
601
+ queue_name = stringcase .pascalcase (test_step .attribute )
602
+ elif test_step .event is not None :
603
+ queue_name = stringcase .pascalcase (test_step .event )
604
+ else :
605
+ raise UnexpectedParsingError (
606
+ f'WaitForReport needs to wait on either attribute or event, neither were provided' )
607
+
608
+ self ._output_queue = context .subscription_callback_result_queue .get (queue_name , None )
468
609
if self ._output_queue is None :
469
610
raise UnexpectedParsingError (f'Could not find output queue' )
470
611
@@ -477,6 +618,8 @@ def run_action(self, dev_ctrl: ChipDeviceController) -> _ActionResult:
477
618
except queue .Empty :
478
619
return _ActionResult (status = _ActionStatus .ERROR , response = None )
479
620
621
+ if isinstance (item , _AttributeSubscriptionCallbackResult ):
622
+ return item .result
480
623
return item .result
481
624
482
625
@@ -621,14 +764,15 @@ def _attribute_read_action_factory(self, test_step, cluster: str):
621
764
'cluster': Name of cluster read attribute action is targeting.
622
765
Returns:
623
766
ReadAttributeAction if 'test_step' is a valid read attribute to be executed.
624
- None if we were unable to use the provided 'test_step' for a known reason that is not
625
- fatal to test execution.
626
767
'''
627
768
try :
628
769
return ReadAttributeAction (test_step , cluster , self ._context )
629
770
except ParsingError :
630
771
return None
631
772
773
+ def _event_read_action_factory (self , test_step , cluster : str ):
774
+ return ReadEventAction (test_step , cluster , self ._context )
775
+
632
776
def _attribute_subscribe_action_factory (self , test_step , cluster : str ):
633
777
'''Creates subscribe attribute command from TestStep provided.
634
778
@@ -648,6 +792,17 @@ def _attribute_subscribe_action_factory(self, test_step, cluster: str):
648
792
# propogated.
649
793
return None
650
794
795
+ def _attribute_subscribe_event_factory (self , test_step , cluster : str ):
796
+ '''Creates subscribe event command from TestStep provided.
797
+
798
+ Args:
799
+ 'test_step': Step containing information required to run subscribe attribute action.
800
+ 'cluster': Name of cluster write attribute action is targeting.
801
+ Returns:
802
+ SubscribeEventAction if 'test_step' is a valid subscribe attribute to be executed.
803
+ '''
804
+ return SubscribeEventAction (test_step , cluster , self ._context )
805
+
651
806
def _attribute_write_action_factory (self , test_step , cluster : str ):
652
807
'''Creates write attribute command TestStep.
653
808
@@ -712,11 +867,11 @@ def encode(self, request) -> BaseAction:
712
867
elif command == 'readAttribute' :
713
868
action = self ._attribute_read_action_factory (request , cluster )
714
869
elif command == 'readEvent' :
715
- # TODO need to implement _event_read_action_factory
716
- # action = self._event_read_action_factory(request, cluster)
717
- pass
870
+ action = self ._event_read_action_factory (request , cluster )
718
871
elif command == 'subscribeAttribute' :
719
872
action = self ._attribute_subscribe_action_factory (request , cluster )
873
+ elif command == 'subscribeEvent' :
874
+ action = self ._attribute_subscribe_event_factory (request , cluster )
720
875
elif command == 'waitForReport' :
721
876
action = self ._wait_for_report_action_factory (request )
722
877
else :
@@ -779,6 +934,29 @@ def decode(self, result: _ActionResult):
779
934
}
780
935
return decoded_response
781
936
937
+ if isinstance (response , EventResponse ):
938
+ if not response .event_result_list :
939
+ # This means that the event result we got back was empty, below is how we
940
+ # represent this.
941
+ decoded_response = [{}]
942
+ return decoded_response
943
+ decoded_response = []
944
+ for event in response .event_result_list :
945
+ if event .Status != chip .interaction_model .Status .Success :
946
+ error_message = stringcase .snakecase (event .Status .name ).upper ()
947
+ decoded_response .append ({'error' : error_message })
948
+ continue
949
+ cluster_id = event .Header .ClusterId
950
+ cluster_name = self ._test_spec_definition .get_cluster_name (cluster_id )
951
+ event_id = event .Header .EventId
952
+ event_name = self ._test_spec_definition .get_event_name (cluster_id , event_id )
953
+ event_definition = self ._test_spec_definition .get_event_by_name (cluster_name , event_name )
954
+ is_fabric_scoped = bool (event_definition .is_fabric_sensitive )
955
+ decoded_event = Converter .from_data_model_to_test_definition (
956
+ self ._test_spec_definition , cluster_name , event_definition .fields , event .Data , is_fabric_scoped )
957
+ decoded_response .append ({'value' : decoded_event })
958
+ return decoded_response
959
+
782
960
if isinstance (response , ChipStackError ):
783
961
decoded_response ['error' ] = 'FAILURE'
784
962
return decoded_response
0 commit comments