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