diff --git a/scripts/py_matter_idl/matter_idl/README.md b/scripts/py_matter_idl/matter_idl/README.md index 4446760bc5dc90..1cd4a28cc325bb 100644 --- a/scripts/py_matter_idl/matter_idl/README.md +++ b/scripts/py_matter_idl/matter_idl/README.md @@ -130,6 +130,23 @@ server cluster AccessControl = 31 { // commands may have multiple attributes fabric timed command RequiresTimedInvoke(): DefaultSuccess = 7; + + // Items may have a prefix about api stability. + // - "provisional" are generally subject to change + // - "internal" are for internal SDK development/usage/testing + + provisional critical event StartUp = 0 { + INT32U softwareVersion = 0; + } + internal struct SomeInternalStruct {} + + struct StructThatIsBeingChanged { + CHAR_STRING debugText = 1; + provisional INT32S errorValue = 2; + } + + provisional timedwrite attribute int16u attributeInDevelopment = 10; + internal command FactoryReset(): DefaultSuccess = 10; } // A client cluster represents something that is used by an app @@ -150,6 +167,10 @@ client cluster OtaSoftwareUpdateProvider = 41 { ///.... content removed: it is very similar to a server cluster } +// Clusters may be provisional or internal as well +provisional client cluster SomeClusterInDevelopment = 1234 { + /// ... content removed +} // On every endpoint number (non-dynamic) // a series of clusters can be exposed diff --git a/scripts/py_matter_idl/matter_idl/matter_grammar.lark b/scripts/py_matter_idl/matter_idl/matter_grammar.lark index 03f17472f4c077..9299449f05be95 100644 --- a/scripts/py_matter_idl/matter_idl/matter_grammar.lark +++ b/scripts/py_matter_idl/matter_idl/matter_grammar.lark @@ -2,6 +2,14 @@ struct: struct_qualities "struct"i id "{" (struct_field ";")* "}" struct_quality: "fabric_scoped"i -> struct_fabric_scoped struct_qualities: struct_quality* +// Composing elements may be at different maturity level +// This only contains non-stable items. Items without a maturity +// flag are to be assumed "stable" +?maturity: "provisional"i -> provisional_api_maturity + | "internal"i -> internal_api_maturity + | "deprecated"i -> deprecated_api_maturity + | "stable"i -> stable_api_maturity + enum: "enum"i id ":" type "{" constant_entry* "}" bitmap: "bitmap"i id ":" type "{" constant_entry* "}" @@ -53,9 +61,10 @@ command_with_access: "command"i command_access? id command: command_qualities command_with_access "(" id? ")" ":" id "=" positive_integer ";" -cluster: cluster_side "cluster"i id "=" positive_integer "{" (enum|bitmap|event|attribute|struct|request_struct|response_struct|command)* "}" +cluster: [maturity] cluster_side "cluster"i id "=" positive_integer "{" cluster_content* "}" ?cluster_side: "server"i -> server_cluster | "client"i -> client_cluster +?cluster_content: [maturity] (enum|bitmap|event|attribute|struct|request_struct|response_struct|command) endpoint: "endpoint"i positive_integer "{" endpoint_content* "}" ?endpoint_content: endpoint_cluster_binding | endpoint_server_cluster | endpoint_device_type @@ -78,13 +87,13 @@ bool_default: "true"i -> bool_default_true | "false"i -> bool_default_false ?default_value: "default"i "=" (integer | ESCAPED_STRING | bool_default) -constant_entry: id "=" positive_integer ";" +constant_entry: [maturity] id "=" positive_integer ";" positive_integer: POSITIVE_INTEGER | HEX_INTEGER negative_integer: "-" positive_integer integer: positive_integer | negative_integer -struct_field: member_attribute* field +struct_field: [maturity] member_attribute* field member_attribute: "optional"i -> optional | "nullable"i -> nullable diff --git a/scripts/py_matter_idl/matter_idl/matter_idl_parser.py b/scripts/py_matter_idl/matter_idl/matter_idl_parser.py index 45e180c65c5a88..a88629fb03c994 100755 --- a/scripts/py_matter_idl/matter_idl/matter_idl_parser.py +++ b/scripts/py_matter_idl/matter_idl/matter_idl_parser.py @@ -18,10 +18,11 @@ from matter_idl.matter_idl_types import AccessPrivilege -from matter_idl.matter_idl_types import (Attribute, AttributeInstantiation, AttributeOperation, AttributeQuality, AttributeStorage, - Bitmap, Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, DataType, DeviceType, - Endpoint, Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, - ParseMetaData, ServerClusterInstantiation, Struct, StructQuality, StructTag) +from matter_idl.matter_idl_types import (ApiMaturity, Attribute, AttributeInstantiation, AttributeOperation, AttributeQuality, + AttributeStorage, Bitmap, Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, + DataType, DeviceType, Endpoint, Enum, Event, EventPriority, EventQuality, Field, + FieldQuality, Idl, ParseMetaData, ServerClusterInstantiation, Struct, StructQuality, + StructTag) def UnionOfAllFlags(flags_list): @@ -157,6 +158,18 @@ def bool_default_true(self, _): def bool_default_false(self, _): return False + def provisional_api_maturity(self, _): + return ApiMaturity.PROVISIONAL + + def internal_api_maturity(self, _): + return ApiMaturity.INTERNAL + + def deprecated_api_maturity(self, _): + return ApiMaturity.DEPRECATED + + def stable_api_maturity(self, _): + return ApiMaturity.STABLE + def id(self, tokens): """An id is a string containing an identifier """ @@ -181,8 +194,10 @@ def data_type(self, tokens): raise Exception("Unexpected size for data type") @v_args(inline=True) - def constant_entry(self, id, number): - return ConstantEntry(name=id, code=number) + def constant_entry(self, api_maturity, id, number): + if api_maturity is None: + api_maturity = ApiMaturity.STABLE + return ConstantEntry(name=id, code=number, api_maturity=api_maturity) @v_args(inline=True) def enum(self, id, type, *entries): @@ -254,7 +269,9 @@ def struct_field(self, args): # Last argument is the named_member, the rest # are qualities field = args[-1] - field.qualities = UnionOfAllFlags(args[:-1]) or FieldQuality.NONE + field.qualities = UnionOfAllFlags(args[1:-1]) or FieldQuality.NONE + if args[0] is not None: + field.api_maturity = args[0] return field @v_args(meta=True) @@ -445,15 +462,25 @@ def endpoint_server_cluster(self, meta, id, *content): return AddServerClusterToEndpointTransform( ServerClusterInstantiation(parse_meta=meta, name=id, attributes=attributes, events_emitted=events)) + @v_args(inline=True) + def cluster_content(self, api_maturity, element): + if api_maturity is not None: + element.api_maturity = api_maturity + return element + @v_args(inline=True, meta=True) - def cluster(self, meta, side, name, code, *content): + def cluster(self, meta, api_maturity, side, name, code, *content): meta = None if self.skip_meta else ParseMetaData(meta) # shift actual starting position where the doc comment would start if meta and self._cluster_start_pos: meta.start_pos = self._cluster_start_pos - result = Cluster(parse_meta=meta, side=side, name=name, code=code) + if api_maturity is None: + api_maturity = ApiMaturity.STABLE + + result = Cluster(parse_meta=meta, side=side, name=name, + code=code, api_maturity=api_maturity) for item in content: if type(item) == Enum: diff --git a/scripts/py_matter_idl/matter_idl/matter_idl_types.py b/scripts/py_matter_idl/matter_idl/matter_idl_types.py index 56886abb91d903..f15b35167097a5 100644 --- a/scripts/py_matter_idl/matter_idl/matter_idl_types.py +++ b/scripts/py_matter_idl/matter_idl/matter_idl_types.py @@ -5,8 +5,16 @@ from lark.tree import Meta +class ApiMaturity(enum.Enum): + STABLE = enum.auto() # default + PROVISIONAL = enum.auto() + INTERNAL = enum.auto() + DEPRECATED = enum.auto() + # Information about parsing location for specific items # Helpful when referencing data items in logs when processing + + @dataclass class ParseMetaData: line: Optional[int] @@ -114,6 +122,7 @@ class Field: name: str is_list: bool = False qualities: FieldQuality = FieldQuality.NONE + api_maturity: ApiMaturity = ApiMaturity.STABLE @property def is_optional(self): @@ -131,6 +140,7 @@ class Attribute: readacl: AccessPrivilege = AccessPrivilege.VIEW writeacl: AccessPrivilege = AccessPrivilege.OPERATE default: Optional[Union[str, int]] = None + api_maturity: ApiMaturity = ApiMaturity.STABLE @property def is_readable(self): @@ -156,6 +166,7 @@ class Struct: tag: Optional[StructTag] = None code: Optional[int] = None # for responses only qualities: StructQuality = StructQuality.NONE + api_maturity: ApiMaturity = ApiMaturity.STABLE @dataclass @@ -167,6 +178,7 @@ class Event: readacl: AccessPrivilege = AccessPrivilege.VIEW qualities: EventQuality = EventQuality.NONE description: Optional[str] = None + api_maturity: ApiMaturity = ApiMaturity.STABLE @property def is_fabric_sensitive(self): @@ -177,6 +189,7 @@ def is_fabric_sensitive(self): class ConstantEntry: name: str code: int + api_maturity: ApiMaturity = ApiMaturity.STABLE @dataclass @@ -184,6 +197,7 @@ class Enum: name: str base_type: str entries: List[ConstantEntry] + api_maturity: ApiMaturity = ApiMaturity.STABLE @dataclass @@ -191,6 +205,7 @@ class Bitmap: name: str base_type: str entries: List[ConstantEntry] + api_maturity: ApiMaturity = ApiMaturity.STABLE @dataclass @@ -202,6 +217,7 @@ class Command: qualities: CommandQuality = CommandQuality.NONE invokeacl: AccessPrivilege = AccessPrivilege.OPERATE description: Optional[str] = None + api_maturity: ApiMaturity = ApiMaturity.STABLE # Parsing meta data missing only when skip meta data is requested parse_meta: Optional[ParseMetaData] = field(default=None) @@ -223,6 +239,7 @@ class Cluster: structs: List[Struct] = field(default_factory=list) commands: List[Command] = field(default_factory=list) description: Optional[str] = None + api_maturity: ApiMaturity = ApiMaturity.STABLE # Parsing meta data missing only when skip meta data is requested parse_meta: Optional[ParseMetaData] = field(default=None) diff --git a/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py b/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py index 0cabc435d54f37..449e02c8d3e049 100755 --- a/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py +++ b/scripts/py_matter_idl/matter_idl/test_matter_idl_parser.py @@ -26,10 +26,10 @@ import unittest -from matter_idl.matter_idl_types import (AccessPrivilege, Attribute, AttributeInstantiation, AttributeQuality, AttributeStorage, - Bitmap, Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, DataType, DeviceType, - Endpoint, Enum, Event, EventPriority, EventQuality, Field, FieldQuality, Idl, - ParseMetaData, ServerClusterInstantiation, Struct, StructTag) +from matter_idl.matter_idl_types import (AccessPrivilege, ApiMaturity, Attribute, AttributeInstantiation, AttributeQuality, + AttributeStorage, Bitmap, Cluster, ClusterSide, Command, CommandQuality, ConstantEntry, + DataType, DeviceType, Endpoint, Enum, Event, EventPriority, EventQuality, Field, + FieldQuality, Idl, ParseMetaData, ServerClusterInstantiation, Struct, StructTag) def parseText(txt, skip_meta=True): @@ -298,6 +298,232 @@ def test_cluster_enum(self): )]) self.assertEqual(actual, expected) + def test_event_field_api_maturity(self): + actual = parseText(""" + server cluster MaturityTest = 1 { + critical event TestEvent = 123 { + nullable int16u someStableMember = 0; + provisional nullable int16u someProvisionalMember = 1; + internal nullable int16u someInternalMember = 2; + } + } + """) + expected = Idl(clusters=[ + Cluster(side=ClusterSide.SERVER, + name="MaturityTest", + code=1, + events=[ + Event(priority=EventPriority.CRITICAL, name="TestEvent", code=123, fields=[ + Field(name="someStableMember", code=0, data_type=DataType( + name="int16u"), qualities=FieldQuality.NULLABLE), + Field(name="someProvisionalMember", code=1, data_type=DataType( + name="int16u"), qualities=FieldQuality.NULLABLE, api_maturity=ApiMaturity.PROVISIONAL), + Field(name="someInternalMember", code=2, data_type=DataType( + name="int16u"), qualities=FieldQuality.NULLABLE, api_maturity=ApiMaturity.INTERNAL), + + ]), + ], + )]) + self.assertEqual(actual, expected) + + def test_enum_constant_maturity(self): + actual = parseText(""" + client cluster WithEnums = 0xab { + enum TestEnum : ENUM16 { + kStable = 0x123; + provisional kProvisional = 0x234; + internal kInternal = 0x345; + } + } + """) + expected = Idl(clusters=[ + Cluster(side=ClusterSide.CLIENT, + name="WithEnums", + code=0xab, + enums=[ + Enum(name="TestEnum", base_type="ENUM16", + entries=[ + ConstantEntry(name="kStable", code=0x123), + ConstantEntry( + name="kProvisional", code=0x234, api_maturity=ApiMaturity.PROVISIONAL), + ConstantEntry( + name="kInternal", code=0x345, api_maturity=ApiMaturity.INTERNAL), + ])], + )]) + self.assertEqual(actual, expected) + + def test_bitmap_constant_maturity(self): + actual = parseText(""" + client cluster Test = 0xab { + bitmap TestBitmap : BITMAP32 { + kStable = 0x1; + internal kInternal = 0x2; + provisional kProvisional = 0x4; + } + } + """) + expected = Idl(clusters=[ + Cluster(side=ClusterSide.CLIENT, + name="Test", + code=0xab, + bitmaps=[ + Bitmap(name="TestBitmap", base_type="BITMAP32", + entries=[ + ConstantEntry(name="kStable", code=0x1), + ConstantEntry( + name="kInternal", code=0x2, api_maturity=ApiMaturity.INTERNAL), + ConstantEntry( + name="kProvisional", code=0x4, api_maturity=ApiMaturity.PROVISIONAL), + ])], + )]) + self.assertEqual(actual, expected) + + def test_struct_field_api_maturity(self): + actual = parseText(""" + server cluster MaturityTest = 1 { + struct TestStruct { + nullable int16u someStableMember = 0; + provisional nullable int16u someProvisionalMember = 1; + internal nullable int16u someInternalMember = 2; + } + } + """) + expected = Idl(clusters=[ + Cluster(side=ClusterSide.SERVER, + name="MaturityTest", + code=1, + structs=[ + Struct(name="TestStruct", fields=[ + Field(name="someStableMember", code=0, data_type=DataType( + name="int16u"), qualities=FieldQuality.NULLABLE), + Field(name="someProvisionalMember", code=1, data_type=DataType( + name="int16u"), qualities=FieldQuality.NULLABLE, api_maturity=ApiMaturity.PROVISIONAL), + Field(name="someInternalMember", code=2, data_type=DataType( + name="int16u"), qualities=FieldQuality.NULLABLE, api_maturity=ApiMaturity.INTERNAL), + + ]), + ], + )]) + self.assertEqual(actual, expected) + + def test_cluster_entry_maturity(self): + actual = parseText(""" + client cluster Test = 0xab { + enum StableEnum : ENUM16 {} + provisional enum ProvisionalEnum : ENUM16 {} + internal enum InternalEnum : ENUM16 {} + deprecated enum DeprecatedEnum : ENUM16 {} + + bitmap StableBitmap : BITMAP32 {} + provisional bitmap ProvisionalBitmap : BITMAP32 {} + internal bitmap InternalBitmap : BITMAP32 {} + + struct StableStruct {} + provisional struct ProvisionalStruct {} + internal struct InternalStruct {} + + info event StableEvent = 1 {} + provisional info event ProvisionalEvent = 2 {} + internal info event InternalEvent = 3 {} + + request struct StableCommandRequest {} + response struct StableCommandResponse = 200 {} + + provisional request struct ProvisionalCommandRequest {} + provisional response struct ProvisionalCommandResponse = 201 {} + + internal request struct InternalCommandRequest {} + internal response struct InternalCommandResponse = 202 {} + + command StableCommand(StableCommandRequest): StableCommandResponse = 100; + provisional command ProvisionalCommand(ProvisionalCommandRequest): ProvisionalCommandResponse = 101; + internal command InternalCommand(InternalCommandRequest): InternalCommandResponse = 102; + + readonly attribute int8u roStable = 1; + attribute int32u rwStable[] = 2; + provisional readonly attribute int8u roProvisional = 11; + provisional attribute int32u rwProvisional[] = 12; + internal readonly attribute int8u roInternal = 21; + internal attribute int32u rwInternal[] = 22; + stable attribute int32u rwForcedStable[] = 31; + } + """) + expected = Idl(clusters=[ + Cluster(side=ClusterSide.CLIENT, + name="Test", + code=0xab, + enums=[ + Enum(name="StableEnum", base_type="ENUM16", entries=[]), + Enum(name="ProvisionalEnum", base_type="ENUM16", + entries=[], api_maturity=ApiMaturity.PROVISIONAL), + Enum(name="InternalEnum", base_type="ENUM16", + entries=[], api_maturity=ApiMaturity.INTERNAL), + Enum(name="DeprecatedEnum", base_type="ENUM16", + entries=[], api_maturity=ApiMaturity.DEPRECATED), + ], + bitmaps=[ + Bitmap(name="StableBitmap", + base_type="BITMAP32", entries=[]), + Bitmap(name="ProvisionalBitmap", base_type="BITMAP32", + entries=[], api_maturity=ApiMaturity.PROVISIONAL), + Bitmap(name="InternalBitmap", base_type="BITMAP32", + entries=[], api_maturity=ApiMaturity.INTERNAL), + ], + structs=[ + Struct(name="StableStruct", fields=[]), + Struct(name="ProvisionalStruct", fields=[], + api_maturity=ApiMaturity.PROVISIONAL), + Struct(name="InternalStruct", fields=[], + api_maturity=ApiMaturity.INTERNAL), + + Struct(name="StableCommandRequest", + fields=[], tag=StructTag.REQUEST), + Struct(name="StableCommandResponse", fields=[], + tag=StructTag.RESPONSE, code=200), + Struct(name="ProvisionalCommandRequest", fields=[ + ], tag=StructTag.REQUEST, api_maturity=ApiMaturity.PROVISIONAL), + Struct(name="ProvisionalCommandResponse", fields=[ + ], tag=StructTag.RESPONSE, code=201, api_maturity=ApiMaturity.PROVISIONAL), + Struct(name="InternalCommandRequest", fields=[ + ], tag=StructTag.REQUEST, api_maturity=ApiMaturity.INTERNAL), + Struct(name="InternalCommandResponse", fields=[ + ], tag=StructTag.RESPONSE, code=202, api_maturity=ApiMaturity.INTERNAL), + ], + events=[ + Event(priority=EventPriority.INFO, + name="StableEvent", code=1, fields=[]), + Event(priority=EventPriority.INFO, name="ProvisionalEvent", + code=2, fields=[], api_maturity=ApiMaturity.PROVISIONAL), + Event(priority=EventPriority.INFO, name="InternalEvent", + code=3, fields=[], api_maturity=ApiMaturity.INTERNAL), + ], + commands=[ + Command(name="StableCommand", code=100, input_param="StableCommandRequest", + output_param="StableCommandResponse"), + Command(name="ProvisionalCommand", code=101, input_param="ProvisionalCommandRequest", + output_param="ProvisionalCommandResponse", api_maturity=ApiMaturity.PROVISIONAL), + Command(name="InternalCommand", code=102, input_param="InternalCommandRequest", + output_param="InternalCommandResponse", api_maturity=ApiMaturity.INTERNAL), + ], + attributes=[ + Attribute(qualities=AttributeQuality.READABLE, definition=Field( + data_type=DataType(name="int8u"), code=1, name="roStable")), + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( + data_type=DataType(name="int32u"), code=2, name="rwStable", is_list=True)), + Attribute(qualities=AttributeQuality.READABLE, definition=Field( + data_type=DataType(name="int8u"), code=11, name="roProvisional"), api_maturity=ApiMaturity.PROVISIONAL), + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( + data_type=DataType(name="int32u"), code=12, name="rwProvisional", is_list=True), api_maturity=ApiMaturity.PROVISIONAL), + Attribute(qualities=AttributeQuality.READABLE, definition=Field( + data_type=DataType(name="int8u"), code=21, name="roInternal"), api_maturity=ApiMaturity.INTERNAL), + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( + data_type=DataType(name="int32u"), code=22, name="rwInternal", is_list=True), api_maturity=ApiMaturity.INTERNAL), + Attribute(qualities=AttributeQuality.READABLE | AttributeQuality.WRITABLE, definition=Field( + data_type=DataType(name="int32u"), code=31, name="rwForcedStable", is_list=True), api_maturity=ApiMaturity.STABLE), + ] + )]) + self.assertEqual(actual, expected) + def test_cluster_bitmap(self): actual = parseText(""" client cluster Test = 0xab { @@ -501,6 +727,22 @@ def test_multi_endpoints(self): ]) self.assertEqual(actual, expected) + def test_cluster_api_maturity(self): + actual = parseText(""" + provisional server cluster A = 1 { /* Test comment */ } + internal client cluster B = 2 { } + client cluster C = 3 { } + """) + + expected = Idl(clusters=[ + Cluster(side=ClusterSide.SERVER, name="A", code=1, + api_maturity=ApiMaturity.PROVISIONAL), + Cluster(side=ClusterSide.CLIENT, name="B", code=2, + api_maturity=ApiMaturity.INTERNAL), + Cluster(side=ClusterSide.CLIENT, name="C", code=3), + ]) + self.assertEqual(actual, expected) + def test_emits_events(self): actual = parseText(""" endpoint 1 {