Skip to content

Commit

Permalink
Python: Fix Reads + Misc Fixes (#11878)
Browse files Browse the repository at this point in the history
* Fix Reads + Misc Fixes

This PR fixes the following:

- When handing a buffer of bytes from C++ to Python, the usage of
 c_char_p instead of c_void_p resulted in the buffer being treated as a
 null-terminated string, resulting in the clipping of the data anytime
 zero was encountered. This resulted in reads not quite working.

- Fixed up the logic that automatically looked up the right type of
  cluster object when processing a read response.

- Simplified the API for WriteAttribute by converting the generated
  classes for attributes into dataclasses with a 'value' member in them.

- Added Python docstrings for read and write APIs

* Build fixes

* Fixed up ZCLWriteAttribute to work correctly

* Build fix
  • Loading branch information
mrjerryjohns authored and pull[bot] committed Nov 27, 2023
1 parent 49b1838 commit 1627964
Show file tree
Hide file tree
Showing 5 changed files with 3,580 additions and 19 deletions.
43 changes: 37 additions & 6 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,14 +377,29 @@ async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.
future.set_exception(self._ChipStack.ErrorToException(res))
return await future

async def WriteAttribute(self, nodeid: int, attributes):
async def WriteAttribute(self, nodeid: int, attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]]):
'''
Write a list of attributes on a target node.
nodeId: Target's Node ID
attributes: A list of tuples of type (endpoint, cluster-object):
E.g
(1, Clusters.TestCluster.Attributes.XYZAttribute('hello')) -- Write 'hello' to the XYZ attribute on the test cluster to endpoint 1
'''
eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

device = self.GetConnectedDeviceSync(nodeid)

attrs = []
for v in attributes:
attrs.append(ClusterAttribute.AttributeWriteRequest(
v[0], v[1], v[1].value))

res = self._ChipStack.Call(
lambda: ClusterAttribute.WriteAttributes(
future, eventLoop, device, attributes)
future, eventLoop, device, attrs)
)
if res != 0:
raise self._ChipStack.ErrorToException(res)
Expand All @@ -402,6 +417,22 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[
# Concrete path
typing.Tuple[int, typing.Type[ClusterObjects.ClusterAttributeDescriptor]]
]]):
'''
Read a list of attributes from a target node
nodeId: Target's Node ID
attributes: A list of tuples of varying types depending on the type of read being requested:
(): Endpoint = *, Cluster = *, Attribute = *
(Clusters.ClusterA): Endpoint = *, Cluster = specific, Attribute = *
(Clusters.ClusterA.AttributeA): Endpoint = *, Cluster = specific, Attribute = specific
(endpoint, Clusters.ClusterA): Endpoint = specific, Cluster = specific, Attribute = *
(endpoint, Clusters.ClusterA.AttributeA): Endpoint = specific, Cluster = specific, Attribute = specific
The cluster and attributes specified above are to be selected from the generated cluster objects.
NOTE: Only the last variant is currently supported.
'''

eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

Expand Down Expand Up @@ -463,15 +494,15 @@ def ZCLReadAttribute(self, cluster, attribute, nodeid, endpoint, groupid, blocki
if blocking:
return im.GetAttributeReadResponse(im.DEFAULT_ATTRIBUTEREAD_APPID)

def ZCLWriteAttribute(self, cluster, attribute, nodeid, endpoint, groupid, value, blocking=True):
def ZCLWriteAttribute(self, cluster: str, attribute: str, nodeid, endpoint, groupid, value, blocking=True):
req = None
try:
req = ClusterAttribute.AttributeWriteRequest(EndpointId=endpoint, Attribute=eval(
f"GeneratedObjects.{cluster}.Attributes.{attribute}"), Data=value)
req = eval(
f"GeneratedObjects.{cluster}.Attributes.{attribute}")(value)
except:
raise UnknownAttribute(cluster, attribute)

return asyncio.run(self.WriteAttribute(nodeid, [req]))
return asyncio.run(self.WriteAttribute(nodeid, [(endpoint, req)]))

def ZCLSubscribeAttribute(self, cluster, attribute, nodeid, endpoint, minInterval, maxInterval, blocking=True):
device = self.GetConnectedDeviceSync(nodeid)
Expand Down
19 changes: 13 additions & 6 deletions src/controller/python/chip/clusters/Attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,19 @@ def _BuildAttributeIndex():
for clusterName, obj in inspect.getmembers(sys.modules['chip.clusters.Objects']):
if ('chip.clusters.Objects' in str(obj)) and inspect.isclass(obj):
for objName, subclass in inspect.getmembers(obj):
if inspect.isclass(subclass) and (('Attribute') in str(subclass)):
if inspect.isclass(subclass) and (('Attributes') in str(subclass)):
for attributeName, attribute in inspect.getmembers(subclass):
if inspect.isclass(attribute):
for name, field in inspect.getmembers(attribute):
if ('__dataclass_fields__' in name):
_AttributeIndex[str(AttributePath(ClusterId=field['cluster_id'].default, AttributeId=field['attribute_id'].default))] = eval(
'chip.clusters.Objects.' + clusterName + '.Attributes.' + attributeName)
base_classes = inspect.getmro(attribute)

# Only match on classes that extend the ClusterAttributeDescriptor class
matched = [
value for value in base_classes if 'ClusterAttributeDescriptor' in str(value)]
if (matched == []):
continue

_AttributeIndex[str(AttributePath(ClusterId=attribute.cluster_id, AttributeId=attribute.attribute_id))] = eval(
'chip.clusters.Objects.' + clusterName + '.Attributes.' + attributeName)


class AsyncReadTransaction:
Expand All @@ -128,6 +134,7 @@ def _handleAttributeData(self, path: AttributePath, status: int, data: bytes):
attributeValue = chip.tlv.TLVReader(data).get().get("Any", {})
else:
attributeValue = attributeType.FromTLV(data)

self._res.append(AttributeReadResult(
Path=path, Status=imStatus, Data=attributeValue))
except Exception as ex:
Expand Down Expand Up @@ -189,7 +196,7 @@ def handleDone(self):


_OnReadAttributeDataCallbackFunct = CFUNCTYPE(
None, py_object, c_uint16, c_uint32, c_uint32, c_uint16, c_char_p, c_size_t)
None, py_object, c_uint16, c_uint32, c_uint32, c_uint16, c_void_p, c_size_t)
_OnReadErrorCallbackFunct = CFUNCTYPE(
None, py_object, c_uint32)
_OnReadDoneCallbackFunct = CFUNCTYPE(
Expand Down
Loading

0 comments on commit 1627964

Please sign in to comment.