Skip to content

Commit

Permalink
Topographically Structured Attribute Data (#12575)
Browse files Browse the repository at this point in the history
* Topographically Structured Attribute Data

The existing format for how attribute reports were returned to callers
of DeviceController.ReadAttribute() made it quite challenging for them
to consume it in an easy enough manner.

Instead of providing them with an effectively raw stream of attribute
data, this commit organizes and structures that data into a series of
nested dictionaries that mirror the topography and composition of the
data in a given Matter device: Endpoints that contain Clusters that in
turn contain Attributes.

The Python data structure equivalent represents these as nested
dictionaries: Dict[EndpointId, Dict[ClusterType, Dict[AttributeType,
AttributeValue]]]

Tests:

- Updated the existing ReadAttribute and Subscription tests to utilize this new format.
- Added new ones.
- Validated using the REPL

* Reverted back to a lock based scheme due to the inability to post work on to the REPL's main event loop for backgrounded work

* Lots of fixups to make events and attributes work on master
  • Loading branch information
mrjerryjohns authored and pull[bot] committed Oct 20, 2023
1 parent 1f9a9b2 commit 3039457
Show file tree
Hide file tree
Showing 9 changed files with 4,033 additions and 279 deletions.
1 change: 1 addition & 0 deletions src/controller/python/build-chip-wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def finalize_options(self):
'rich',
'stringcase',
'pyyaml',
'ipdb'
]

if platform.system() == "Darwin":
Expand Down
39 changes: 22 additions & 17 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[
typing.Tuple[int, typing.Type[ClusterObjects.Cluster]],
# Concrete path
typing.Tuple[int, typing.Type[ClusterObjects.ClusterAttributeDescriptor]]
]], reportInterval: typing.Tuple[int, int] = None):
]], returnClusterObject: bool = False, reportInterval: typing.Tuple[int, int] = None):
'''
Read a list of attributes from a target node
Expand All @@ -456,12 +456,15 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[
Clusters.ClusterA: Endpoint = *, Cluster = specific, Attribute = *
'*' or (): Endpoint = *, Cluster = *, Attribute = *
The cluster and attributes specified above are to be selected from the generated cluster objects.
The cluster and attributes specified above are to be selected from the generated cluster objects.
e.g.
ReadAttribute(1, [ 1 ] ) -- case 4 above.
ReadAttribute(1, [ Clusters.Basic ] ) -- case 5 above.
ReadAttribute(1, [ (1, Clusters.Basic.Attributes.Location ] ) -- case 1 above.
e.g.
ReadAttribute(1, [ 1 ] ) -- case 4 above.
ReadAttribute(1, [ Clusters.Basic ] ) -- case 5 above.
ReadAttribute(1, [ (1, Clusters.Basic.Attributes.Location ] ) -- case 1 above.
returnClusterObject: This returns the data as consolidated cluster objects, with all attributes for a cluster inside
a single cluster-wide cluster object.
reportInterval: A tuple of two int-s for (MinIntervalFloor, MaxIntervalCeiling). Used by establishing subscriptions.
When not provided, a read request will be sent.
Expand All @@ -480,7 +483,6 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[
# Wildcard
pass
elif type(v) is not tuple:
print(type(v))
if type(v) is int:
endpoint = v
elif issubclass(v, ClusterObjects.Cluster):
Expand All @@ -501,7 +503,7 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[
attrs.append(ClusterAttribute.AttributePath(
EndpointId=endpoint, Cluster=cluster, Attribute=attribute))
res = self._ChipStack.Call(
lambda: ClusterAttribute.ReadAttributes(future, eventLoop, device, self, attrs, ClusterAttribute.SubscriptionParameters(reportInterval[0], reportInterval[1]) if reportInterval else None))
lambda: ClusterAttribute.ReadAttributes(future, eventLoop, device, self, attrs, returnClusterObject, ClusterAttribute.SubscriptionParameters(reportInterval[0], reportInterval[1]) if reportInterval else None))
if res != 0:
raise self._ChipStack.ErrorToException(res)
return await future
Expand All @@ -512,11 +514,11 @@ async def ReadEvent(self, nodeid: int, events: typing.List[typing.Union[
# Wildcard endpoint, Cluster id present
typing.Tuple[typing.Type[ClusterObjects.Cluster]],
# Wildcard endpoint, Cluster + Event present
typing.Tuple[typing.Type[ClusterObjects.ClusterEventDescriptor]],
typing.Tuple[typing.Type[ClusterObjects.ClusterEvent]],
# Wildcard event id
typing.Tuple[int, typing.Type[ClusterObjects.Cluster]],
# Concrete path
typing.Tuple[int, typing.Type[ClusterObjects.ClusterEventDescriptor]]
typing.Tuple[int, typing.Type[ClusterObjects.ClusterEvent]]
]], reportInterval: typing.Tuple[int, int] = None):
'''
Read a list of events from a target node
Expand Down Expand Up @@ -559,7 +561,7 @@ async def ReadEvent(self, nodeid: int, events: typing.List[typing.Union[
endpoint = v
elif issubclass(v, ClusterObjects.Cluster):
cluster = v
elif issubclass(v, ClusterObjects.ClusterEventDescriptor):
elif issubclass(v, ClusterObjects.ClusterEvent):
event = v
else:
raise ValueError("Unsupported Event Path")
Expand All @@ -568,7 +570,7 @@ async def ReadEvent(self, nodeid: int, events: typing.List[typing.Union[
endpoint = v[0]
if issubclass(v[1], ClusterObjects.Cluster):
cluster = v[1]
elif issubclass(v[1], ClusterAttribute.ClusterEventDescriptor):
elif issubclass(v[1], ClusterAttribute.ClusterEvent):
event = v[1]
else:
raise ValueError("Unsupported Attribute Path")
Expand Down Expand Up @@ -597,16 +599,19 @@ def ZCLSend(self, cluster, command, nodeid, endpoint, groupid, args, blocking=Fa

def ZCLReadAttribute(self, cluster, attribute, nodeid, endpoint, groupid, blocking=True):
req = None
clusterType = eval(f"GeneratedObjects.{cluster}")

try:
req = eval(f"GeneratedObjects.{cluster}.Attributes.{attribute}")
attributeType = eval(
f"GeneratedObjects.{cluster}.Attributes.{attribute}")
except:
raise UnknownAttribute(cluster, attribute)

result = asyncio.run(self.ReadAttribute(
nodeid, [(endpoint, req)]))['Attributes']
nodeid, [(endpoint, attributeType)]))
path = ClusterAttribute.AttributePath(
EndpointId=endpoint, Attribute=req)
return im.AttributeReadResult(path=im.AttributePath(nodeId=nodeid, endpointId=path.EndpointId, clusterId=path.ClusterId, attributeId=path.AttributeId), status=0, value=result[path].Data.value)
EndpointId=endpoint, Attribute=attributeType)
return im.AttributeReadResult(path=im.AttributePath(nodeId=nodeid, endpointId=path.EndpointId, clusterId=path.ClusterId, attributeId=path.AttributeId), status=0, value=result[endpoint][clusterType][attributeType])

def ZCLWriteAttribute(self, cluster: str, attribute: str, nodeid, endpoint, groupid, value, blocking=True):
req = None
Expand All @@ -624,7 +629,7 @@ def ZCLSubscribeAttribute(self, cluster, attribute, nodeid, endpoint, minInterva
req = eval(f"GeneratedObjects.{cluster}.Attributes.{attribute}")
except:
raise UnknownAttribute(cluster, attribute)
return asyncio.run(self.ReadAttribute(nodeid, [(endpoint, req)], reportInterval=(minInterval, maxInterval)))
return asyncio.run(self.ReadAttribute(nodeid, [(endpoint, req)], False, reportInterval=(minInterval, maxInterval)))

def ZCLShutdownSubscription(self, subscriptionId: int):
res = self._ChipStack.Call(
Expand Down
Loading

0 comments on commit 3039457

Please sign in to comment.