Skip to content

Commit b1634f2

Browse files
committed
Add support for optional commands and events
1 parent 9cf1485 commit b1634f2

8 files changed

Lines changed: 130 additions & 23 deletions

File tree

scripts/py_matter_idl/matter/idl/data_model_xml/handlers/handlers.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ def GetNextProcessor(self, name: str, attrs: AttributesImpl):
274274
field = AttributesToField(attrs)
275275
self._event.fields.append(field)
276276
return FieldHandler(self.context, field)
277+
if name == "optionalConform":
278+
self._event.qualities |= EventQuality.OPTIONAL
279+
return BaseHandler(self.context, handled=HandledDepth.ENTIRE_TREE)
277280
if name == "mandatoryConform":
278281
# assume handled (we do not record conformance in IDL)
279282
return BaseHandler(self.context, handled=HandledDepth.ENTIRE_TREE)
@@ -500,7 +503,11 @@ def EndProcessing(self):
500503
self._cluster.commands.append(self._command)
501504

502505
def GetNextProcessor(self, name: str, attrs: AttributesImpl):
503-
if name in {"mandatoryConform", "optionalConform", "disallowConform"}:
506+
if name == "optionalConform":
507+
if self._command:
508+
self._command.qualities |= CommandQuality.OPTIONAL
509+
return BaseHandler(self.context, handled=HandledDepth.ENTIRE_TREE)
510+
if name in {"mandatoryConform", "disallowConform"}:
504511
# Unclear how commands may be optional or mandatory
505512
return BaseHandler(self.context, handled=HandledDepth.ENTIRE_TREE)
506513
if name == "access":

scripts/py_matter_idl/matter/idl/generators/idl/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ def human_text_string(value: Union[StructTag, StructQuality, EventPriority, Even
5757
result = ""
5858
if EventQuality.FABRIC_SENSITIVE in value:
5959
result += "fabric_sensitive "
60+
if EventQuality.OPTIONAL in value:
61+
result += "optional "
6062
return result.strip()
6163
elif type(value) is AccessPrivilege:
6264
if value == AccessPrivilege.VIEW:
@@ -84,6 +86,8 @@ def human_text_string(value: Union[StructTag, StructQuality, EventPriority, Even
8486
result += "fabric "
8587
if CommandQuality.TIMED_INVOKE in value:
8688
result += "timed "
89+
if CommandQuality.OPTIONAL in value:
90+
result += "optional "
8791
return result
8892
elif type(value) is ApiMaturity:
8993
if value == ApiMaturity.STABLE:

scripts/py_matter_idl/matter/idl/matter_grammar.lark

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@ event_access: "access" "(" ("read" ":" access_privilege)? ")"
2828

2929
event_with_access: "event" event_access? id
3030

31-
event: event_qualities event_priority event_with_access "=" positive_integer "{" (struct_field ";")* "}"
31+
event: prefix_qualities event_priority event_with_access "=" positive_integer "{" (struct_field ";")* "}"
3232

33-
event_quality: "fabric_sensitive" -> event_fabric_sensitive
34-
event_qualities: event_quality*
33+
prefix_quality: "fabric_sensitive" -> prefix_fabric_sensitive
34+
| "fabric"i -> prefix_fabric_scoped
35+
| "timed"i -> prefix_timed
36+
| "optional"i -> prefix_optional
37+
prefix_qualities: prefix_quality* -> prefix_qualities
3538

3639
?event_priority: "critical"i -> critical_priority
3740
| "info"i -> info_priority
@@ -55,15 +58,11 @@ request_struct: "request"i struct
5558
// Response structures must have a response id
5659
response_struct: "response"i "struct"i id "=" positive_integer "{" (struct_field ";") * "}"
5760

58-
command_quality: "timed"i -> timed_command
59-
| "fabric"i -> fabric_scoped_command
60-
command_qualities: command_quality*
61-
6261
command_access: "access"i "(" ("invoke"i ":" access_privilege)? ")"
6362

6463
command_with_access: "command"i command_access? id
6564

66-
command: command_qualities command_with_access "(" id? ")" ":" id "=" positive_integer ";"
65+
command: prefix_qualities command_with_access "(" id? ")" ":" id "=" positive_integer ";"
6766

6867
cluster_revision: "revision"i positive_integer ";"
6968

scripts/py_matter_idl/matter/idl/matter_idl_parser.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def UnionOfAllFlags(flags_list):
3838
return functools.reduce(lambda a, b: a | b, flags_list)
3939

4040

41+
42+
4143
class PrefixCppDocComment:
4244
def __init__(self, token):
4345
self.start_pos = token.start_pos
@@ -284,20 +286,20 @@ def info_priority(self, _):
284286
def debug_priority(self, _):
285287
return EventPriority.DEBUG
286288

287-
def event_fabric_sensitive(self, _):
288-
return EventQuality.FABRIC_SENSITIVE
289+
def prefix_fabric_sensitive(self, _):
290+
return "fabric_sensitive"
289291

290-
def event_qualities(selt, qualities):
291-
return UnionOfAllFlags(qualities) or EventQuality.NONE
292+
def prefix_fabric_scoped(self, _):
293+
return "fabric"
292294

293-
def timed_command(self, _):
294-
return CommandQuality.TIMED_INVOKE
295+
def prefix_timed(self, _):
296+
return "timed"
295297

296-
def fabric_scoped_command(self, _):
297-
return CommandQuality.FABRIC_SCOPED
298+
def prefix_optional(self, _):
299+
return "optional"
298300

299-
def command_qualities(self, attrs):
300-
return UnionOfAllFlags(attrs) or CommandQuality.NONE
301+
def prefix_qualities(self, qualities):
302+
return set(qualities)
301303

302304
@v_args(meta=True)
303305
def struct_field(self, meta, args):
@@ -336,11 +338,20 @@ def command(self, meta, *tuple_args):
336338
if len(args) != 5:
337339
args.insert(2, None)
338340

341+
qualities_set = args[0] or set()
342+
qualities = CommandQuality.NONE
343+
if "fabric" in qualities_set:
344+
qualities |= CommandQuality.FABRIC_SCOPED
345+
if "timed" in qualities_set:
346+
qualities |= CommandQuality.TIMED_INVOKE
347+
if "optional" in qualities_set:
348+
qualities |= CommandQuality.OPTIONAL
349+
339350
meta = None if self.skip_meta else ParseMetaData(meta)
340351

341352
return Command(
342353
parse_meta=meta,
343-
qualities=args[0],
354+
qualities=qualities,
344355
input_param=args[2], output_param=args[3], code=args[4],
345356
**args[1],
346357
)
@@ -366,8 +377,15 @@ def cluster_revision(self, revision):
366377

367378
@v_args(meta=True)
368379
def event(self, meta, args):
380+
qualities_set = args[0] or set()
381+
qualities = EventQuality.NONE
382+
if "fabric_sensitive" in qualities_set:
383+
qualities |= EventQuality.FABRIC_SENSITIVE
384+
if "optional" in qualities_set:
385+
qualities |= EventQuality.OPTIONAL
386+
369387
meta = None if self.skip_meta else ParseMetaData(meta)
370-
return Event(qualities=args[0], priority=args[1], code=args[3], fields=args[4:], parse_meta=meta, **args[2])
388+
return Event(qualities=qualities, priority=args[1], code=args[3], fields=args[4:], parse_meta=meta, **args[2])
371389

372390
def view_privilege(self, args):
373391
return AccessPrivilege.VIEW

scripts/py_matter_idl/matter/idl/matter_idl_types.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class CommandQuality(enum.Flag):
6262
NONE = 0
6363
TIMED_INVOKE = enum.auto()
6464
FABRIC_SCOPED = enum.auto()
65+
OPTIONAL = enum.auto()
6566

6667

6768
class AttributeQuality(enum.Flag):
@@ -87,6 +88,7 @@ class EventPriority(enum.Enum):
8788
class EventQuality(enum.Flag):
8889
NONE = 0
8990
FABRIC_SENSITIVE = enum.auto()
91+
OPTIONAL = enum.auto()
9092

9193

9294
class StructTag(enum.Enum):
@@ -171,6 +173,14 @@ def is_subscribable(self) -> bool:
171173
def requires_timed_write(self) -> bool:
172174
return AttributeQuality.TIMED_WRITE in self.qualities
173175

176+
@property
177+
def is_optional(self) -> bool:
178+
return self.definition.is_optional
179+
180+
@property
181+
def is_nullable(self) -> bool:
182+
return self.definition.is_nullable
183+
174184

175185
@dataclass
176186
class Struct:
@@ -206,6 +216,10 @@ class Event:
206216
def is_fabric_sensitive(self) -> bool:
207217
return EventQuality.FABRIC_SENSITIVE in self.qualities
208218

219+
@property
220+
def is_optional(self) -> bool:
221+
return EventQuality.OPTIONAL in self.qualities
222+
209223

210224
@dataclass
211225
class ConstantEntry:
@@ -266,6 +280,10 @@ class Command:
266280
def is_timed_invoke(self) -> bool:
267281
return CommandQuality.TIMED_INVOKE in self.qualities
268282

283+
@property
284+
def is_optional(self) -> bool:
285+
return CommandQuality.OPTIONAL in self.qualities
286+
269287

270288
@dataclass
271289
class Cluster:

scripts/py_matter_idl/matter/idl/test_matter_idl_parser.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,60 @@ def test_handle_commands(self):
11811181

11821182
self.assertIdlEqual(actual, expected)
11831183

1184+
def test_optional_nullable_support(self):
1185+
actual = parseText("""
1186+
server cluster MyCluster = 0x123 {
1187+
attribute optional int8u optAttr = 1;
1188+
attribute nullable int8u nullAttr = 2;
1189+
attribute optional nullable int8u optNullAttr = 3;
1190+
1191+
optional info event OptionalEvent = 1 {}
1192+
optional critical event OptionalCriticalEvent = 2 {}
1193+
1194+
optional command OptionalCommand(): DefaultSuccess = 10;
1195+
optional timed command OptionalTimedCommand(): DefaultSuccess = 11;
1196+
}
1197+
""")
1198+
1199+
expected = Idl(clusters=[
1200+
Cluster(name="MyCluster",
1201+
code=0x123,
1202+
attributes=[
1203+
Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field(
1204+
data_type=DataType(name="int8u"), code=1, name="optAttr", qualities=FieldQuality.OPTIONAL)),
1205+
Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field(
1206+
data_type=DataType(name="int8u"), code=2, name="nullAttr", qualities=FieldQuality.NULLABLE)),
1207+
Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field(
1208+
data_type=DataType(name="int8u"), code=3, name="optNullAttr", qualities=FieldQuality.OPTIONAL | FieldQuality.NULLABLE)),
1209+
],
1210+
events=[
1211+
Event(priority=EventPriority.INFO, name="OptionalEvent", code=1, fields=[], qualities=EventQuality.OPTIONAL),
1212+
Event(priority=EventPriority.CRITICAL, name="OptionalCriticalEvent", code=2, fields=[], qualities=EventQuality.OPTIONAL),
1213+
],
1214+
commands=[
1215+
Command(name="OptionalCommand", code=10, input_param=None, output_param="DefaultSuccess", qualities=CommandQuality.OPTIONAL),
1216+
Command(name="OptionalTimedCommand", code=11, input_param=None, output_param="DefaultSuccess", qualities=CommandQuality.TIMED_INVOKE | CommandQuality.OPTIONAL),
1217+
]
1218+
)])
1219+
1220+
self.assertIdlEqual(actual, expected)
1221+
1222+
# Also verify properties
1223+
self.assertTrue(actual.clusters[0].attributes[0].is_optional)
1224+
self.assertFalse(actual.clusters[0].attributes[0].is_nullable)
1225+
1226+
self.assertFalse(actual.clusters[0].attributes[1].is_optional)
1227+
self.assertTrue(actual.clusters[0].attributes[1].is_nullable)
1228+
1229+
self.assertTrue(actual.clusters[0].attributes[2].is_optional)
1230+
self.assertTrue(actual.clusters[0].attributes[2].is_nullable)
1231+
1232+
self.assertTrue(actual.clusters[0].events[0].is_optional)
1233+
self.assertTrue(actual.clusters[0].events[1].is_optional)
1234+
1235+
self.assertTrue(actual.clusters[0].commands[0].is_optional)
1236+
self.assertTrue(actual.clusters[0].commands[1].is_optional)
1237+
11841238

11851239
if __name__ == '__main__':
11861240
unittest.main()

scripts/py_matter_idl/matter/idl/test_zapxml.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
sys.path.append(str(Path(__file__).resolve().parent / ".." / ".."))
2626
from matter.idl.zapxml import ParseSource, ParseXmls
2727

28-
from matter.idl.matter_idl_types import (AccessPrivilege, Attribute, AttributeQuality, Bitmap, Cluster, Command, ConstantEntry,
28+
from matter.idl.matter_idl_types import (AccessPrivilege, Attribute, AttributeQuality, Bitmap, Cluster, Command, CommandQuality, ConstantEntry,
2929
DataType, Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, Struct,
3030
StructQuality, StructTag)
3131

@@ -153,7 +153,8 @@ def testCluster(self):
153153
Command(name='GetSomeData', code=33,
154154
input_param='GetSomeDataRequest', output_param='GetSomeDataResponse',
155155
description='This is just a test: client to server',
156-
invokeacl=AccessPrivilege.ADMINISTER)
156+
invokeacl=AccessPrivilege.ADMINISTER,
157+
qualities=CommandQuality.OPTIONAL)
157158
])
158159
]))
159160

scripts/py_matter_idl/matter/idl/zapxml/handlers/handlers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ def __init__(self, context: Context, cluster: Cluster, attrs):
8888
if attrs.get('isFabricSensitive', "false").lower() == 'true':
8989
self._event.qualities |= EventQuality.FABRIC_SENSITIVE
9090

91+
if attrs.get('optional', "false").lower() == 'true':
92+
self._event.qualities |= EventQuality.OPTIONAL
93+
9194
def GetNextProcessor(self, name: str, attrs):
9295
if name.lower() == 'field':
9396
data_type = DataType(name=attrs['type'])
@@ -428,6 +431,9 @@ def __init__(self, context: Context, cluster: Cluster, attrs):
428431
if attrs.get('mustUseTimedInvoke', 'false') == 'true':
429432
self._command.qualities |= CommandQuality.TIMED_INVOKE
430433

434+
if attrs.get('optional', 'false').lower() == 'true':
435+
self._command.qualities |= CommandQuality.OPTIONAL
436+
431437
else:
432438
self._struct.tag = StructTag.RESPONSE
433439
self._struct.code = ParseInt(attrs['code'])

0 commit comments

Comments
 (0)