diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn index 06bea6a49b5086..3584261f0c5931 100644 --- a/src/controller/python/BUILD.gn +++ b/src/controller/python/BUILD.gn @@ -45,8 +45,8 @@ shared_library("ChipDeviceCtrl") { "ChipDeviceController-StorageDelegate.cpp", "ChipDeviceController-StorageDelegate.h", "chip/clusters/CHIPClusters.cpp", + "chip/clusters/attribute.cpp", "chip/clusters/command.cpp", - "chip/clusters/write.cpp", "chip/discovery/NodeResolution.cpp", "chip/interaction_model/Delegate.cpp", "chip/interaction_model/Delegate.h", diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 3eee5650981f72..45b003010fe365 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -378,37 +378,54 @@ def WriteAttribute(self, nodeid: int, attributes): raise self._ChipStack.ErrorToException(res) return future - def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Tuple[int, ClusterAttribute.AttributeReadRequest]]): + def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[ + None, # Empty tuple, all wildcard + typing.Tuple[int], # Endpoint + # Wildcard endpoint, Cluster id present + typing.Tuple[typing.Type[ClusterObjects.Cluster]], + # Wildcard endpoint, Cluster + Attribute present + typing.Tuple[typing.Type[ClusterObjects.ClusterAttributeDescriptor]], + # Wildcard attribute id + typing.Tuple[int, typing.Type[ClusterObjects.Cluster]], + # Concrete path + typing.Tuple[int, typing.Type[ClusterObjects.ClusterAttributeDescriptor]] + ]]): eventLoop = asyncio.get_running_loop() future = eventLoop.create_future() device = self.GetConnectedDeviceSync(nodeid) - # TODO: Here, we translates multi attribute read into many individual attribute reads, this should be fixed by implementing Python's attribute read API. - res = [] - for attr in attributes: - endpointId = attr[0] - attribute = attr[1] - clusterInfo = self._Cluster.GetClusterInfoById( - attribute.cluster_id) - if not clusterInfo: - raise UnknownCluster(attribute.cluster_id) - attributeInfo = clusterInfo.get("attributes", {}).get( - attribute.attribute_id, None) - if not attributeInfo: - raise UnknownAttribute( - clusterInfo["clusterName"], attribute.attribute_id) - self._Cluster.ReadAttribute( - device, clusterInfo["clusterName"], attributeInfo["attributeName"], endpointId, 0, False) - readRes = im.GetAttributeReadResponse( - im.DEFAULT_ATTRIBUTEREAD_APPID) - res.append(ClusterAttribute.AttributeReadResult( - Path=ClusterAttribute.AttributePath( - EndpointId=endpointId, ClusterId=attribute.cluster_id, AttributeId=attribute.attribute_id), - Status=readRes.status, - Data=(attribute.FromTagDictOrRawValue( - readRes.value) if readRes.value is not None else None), - )) - future.set_result(res) + attrs = [] + for v in attributes: + endpoint = None + cluster = None + attribute = None + if v == () or v == ('*'): + # Wildcard + pass + elif len(v) == 1: + if v[0] is int: + endpoint = v[0] + elif issubclass(v[0], ClusterObjects.Cluster): + cluster = v[0] + elif issubclass(v[0], ClusterObjects.ClusterAttributeDescriptor): + attribute = v[0] + else: + raise ValueError("Unsupported Attribute Path") + elif len(v) == 2: + # endpoint + (cluster) attribute / endpoint + cluster + endpoint = v[0] + if issubclass(v[1], ClusterObjects.Cluster): + cluster = v[1] + elif issubclass(v[1], ClusterAttribute.ClusterAttributeDescriptor): + attribute = v[1] + else: + raise ValueError("Unsupported Attribute Path") + attrs.append(ClusterAttribute.AttributePath( + EndpointId=endpoint, Cluster=cluster, Attribute=attribute)) + res = self._ChipStack.Call( + lambda: ClusterAttribute.ReadAttributes(future, eventLoop, device, attrs)) + if res != 0: + raise self._ChipStack.ErrorToException(res) return future def ZCLSend(self, cluster, command, nodeid, endpoint, groupid, args, blocking=False): diff --git a/src/controller/python/chip/clusters/Attribute.py b/src/controller/python/chip/clusters/Attribute.py index e8c3f0527e81e5..72fc6331605bf3 100644 --- a/src/controller/python/chip/clusters/Attribute.py +++ b/src/controller/python/chip/clusters/Attribute.py @@ -24,13 +24,40 @@ from .ClusterObjects import ClusterAttributeDescriptor import chip.exceptions import chip.interaction_model +import chip.tlv + +import inspect +import sys +import logging @dataclass class AttributePath: - EndpointId: int - ClusterId: int - AttributeId: int + EndpointId: int = None + ClusterId: int = None + AttributeId: int = None + + def __init__(self, EndpointId: int = None, Cluster=None, Attribute=None, ClusterId=None, AttributeId=None): + self.EndpointId = EndpointId + if Cluster is not None: + # Wildcard read for a specific cluster + if (Attribute is not None) or (ClusterId is not None) or (AttributeId is not None): + raise Warning( + "Attribute, ClusterId and AttributeId is ignored when Cluster is specified") + self.ClusterId = Cluster.id + return + if Attribute is not None: + if (ClusterId is not None) or (AttributeId is not None): + raise Warning( + "ClusterId and AttributeId is ignored when Attribute is specified") + self.ClusterId = Attribute.cluster_id + self.AttributeId = Attribute.attribute_id + return + self.ClusterId = ClusterId + self.AttributeId = AttributeId + + def __str__(self) -> str: + return f"{self.EndpointId}/{self.ClusterId}/{self.AttributeId}" @dataclass @@ -61,6 +88,72 @@ class AttributeReadResult(AttributeStatus): Data: Any = None +_AttributeIndex = {} + + +def _BuildAttributeIndex(): + ''' Build internal attribute index for locating the corresponding cluster object by path in the future. + We do this because this operation will take a long time when there are lots of attributes, it takes about 300ms for a single query. + This is acceptable during init, but unacceptable when the server returns lots of attributes at the same time. + ''' + 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)): + 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) + + +class AsyncReadTransaction: + def __init__(self, future: Future, eventLoop): + self._event_loop = eventLoop + self._future = future + self._res = [] + + def _handleAttributeData(self, path: AttributePath, status: int, data: bytes): + try: + imStatus = status + try: + imStatus = chip.interaction_model.Status(status) + except: + pass + attributeType = _AttributeIndex.get(str(AttributePath( + ClusterId=path.ClusterId, AttributeId=path.AttributeId)), None) + attributeValue = None + if attributeType is None: + 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: + logging.exception(ex) + + def handleAttributeData(self, path: AttributePath, status: int, data: bytes): + self._event_loop.call_soon_threadsafe( + self._handleAttributeData, path, status, data) + + def _handleError(self, chipError: int): + self._future.set_exception( + chip.exceptions.ChipStackError(chipError)) + + def handleError(self, chipError: int): + self._event_loop.call_soon_threadsafe( + self._handleError, chipError + ) + + def _handleDone(self, asd): + if not self._future.done(): + self._future.set_result(self._res) + + def handleDone(self): + self._event_loop.call_soon_threadsafe(self._handleDone, "asdasa") + + class AsyncWriteTransaction: def __init__(self, future: Future, eventLoop): self._event_loop = eventLoop @@ -95,6 +188,32 @@ def handleDone(self): self._event_loop.call_soon_threadsafe(self._handleDone) +_OnReadAttributeDataCallbackFunct = CFUNCTYPE( + None, py_object, c_uint16, c_uint32, c_uint32, c_uint16, c_char_p, c_size_t) +_OnReadErrorCallbackFunct = CFUNCTYPE( + None, py_object, c_uint32) +_OnReadDoneCallbackFunct = CFUNCTYPE( + None, py_object) + + +@_OnReadAttributeDataCallbackFunct +def _OnReadAttributeDataCallback(closure, endpoint: int, cluster: int, attribute: int, status, data, len): + dataBytes = ctypes.string_at(data, len) + closure.handleAttributeData(AttributePath( + EndpointId=endpoint, ClusterId=cluster, AttributeId=attribute), status, dataBytes[:]) + + +@_OnReadErrorCallbackFunct +def _OnReadErrorCallback(closure, chiperror: int): + closure.handleError(chiperror) + + +@_OnReadDoneCallbackFunct +def _OnReadDoneCallback(closure): + closure.handleDone() + ctypes.pythonapi.Py_DecRef(ctypes.py_object(closure)) + + _OnWriteResponseCallbackFunct = CFUNCTYPE( None, py_object, c_uint16, c_uint32, c_uint32, c_uint16) _OnWriteErrorCallbackFunct = CFUNCTYPE( @@ -105,7 +224,8 @@ def handleDone(self): @_OnWriteResponseCallbackFunct def _OnWriteResponseCallback(closure, endpoint: int, cluster: int, attribute: int, status): - closure.handleResponse(AttributePath(endpoint, cluster, attribute), status) + closure.handleResponse(AttributePath( + EndpointId=endpoint, ClusterId=cluster, AttributeId=attribute), status) @_OnWriteErrorCallbackFunct @@ -144,6 +264,31 @@ def WriteAttributes(future: Future, eventLoop, device, attributes: List[Attribut return res +def ReadAttributes(future: Future, eventLoop, device, attributes: List[AttributePath]) -> int: + handle = chip.native.GetLibraryHandle() + transaction = AsyncReadTransaction(future, eventLoop) + + readargs = [] + for attr in attributes: + path = chip.interaction_model.AttributePathIBstruct.parse( + b'\xff' * chip.interaction_model.AttributePathIBstruct.sizeof()) + if attr.EndpointId is not None: + path.EndpointId = attr.EndpointId + if attr.ClusterId is not None: + path.ClusterId = attr.ClusterId + if attr.AttributeId is not None: + path.AttributeId = attr.AttributeId + path = chip.interaction_model.AttributePathIBstruct.build(path) + readargs.append(ctypes.c_char_p(path)) + + ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction)) + res = handle.pychip_ReadClient_ReadAttributes( + ctypes.py_object(transaction), device, ctypes.c_size_t(len(attributes)), *readargs) + if res != 0: + ctypes.pythonapi.Py_DecRef(ctypes.py_object(transaction)) + return res + + def Init(): handle = chip.native.GetLibraryHandle() @@ -155,6 +300,13 @@ def Init(): handle.pychip_WriteClient_WriteAttributes.restype = c_uint32 setter.Set('pychip_WriteClient_InitCallbacks', None, [ _OnWriteResponseCallbackFunct, _OnWriteErrorCallbackFunct, _OnWriteDoneCallbackFunct]) + handle.pychip_ReadClient_ReadAttributes.restype = c_uint32 + setter.Set('pychip_ReadClient_InitCallbacks', None, [ + _OnReadAttributeDataCallbackFunct, _OnReadErrorCallbackFunct, _OnReadDoneCallbackFunct]) handle.pychip_WriteClient_InitCallbacks( _OnWriteResponseCallback, _OnWriteErrorCallback, _OnWriteDoneCallback) + handle.pychip_ReadClient_InitCallbacks( + _OnReadAttributeDataCallback, _OnReadErrorCallback, _OnReadDoneCallback) + + _BuildAttributeIndex() diff --git a/src/controller/python/chip/clusters/ClusterObjects.py b/src/controller/python/chip/clusters/ClusterObjects.py index e441a9e1efdf38..d8d6b78aad0961 100644 --- a/src/controller/python/chip/clusters/ClusterObjects.py +++ b/src/controller/python/chip/clusters/ClusterObjects.py @@ -149,6 +149,13 @@ def command_id(self) -> int: raise NotImplementedError() +class Cluster: + ''' This class does nothing, but a convenient class that generated clusters can inherit from. + This gives the ability that the users can use issubclass(X, Cluster) to determine if the class represnents a Cluster. + ''' + pass + + class ClusterAttributeDescriptor: ''' The ClusterAttributeDescriptor is used for holding an attribute's metadata like its cluster id, attribute id and its type. diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py index 53b8781615cc12..7f3a8a4492e7c2 100644 --- a/src/controller/python/chip/clusters/Objects.py +++ b/src/controller/python/chip/clusters/Objects.py @@ -29,11 +29,11 @@ from chip.tlv import uint -from .ClusterObjects import ClusterObject, ClusterObjectDescriptor, ClusterObjectFieldDescriptor, ClusterCommand, ClusterAttributeDescriptor +from .ClusterObjects import ClusterObject, ClusterObjectDescriptor, ClusterObjectFieldDescriptor, ClusterCommand, ClusterAttributeDescriptor, Cluster @dataclass -class PowerConfiguration: +class PowerConfiguration(Cluster): id: typing.ClassVar[int] = 0x0001 class Attributes: @@ -806,7 +806,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class DeviceTemperatureConfiguration: +class DeviceTemperatureConfiguration(Cluster): id: typing.ClassVar[int] = 0x0002 class Attributes: @@ -955,7 +955,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Identify: +class Identify(Cluster): id: typing.ClassVar[int] = 0x0003 class Enums: @@ -1097,7 +1097,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Groups: +class Groups(Cluster): id: typing.ClassVar[int] = 0x0004 class Commands: @@ -1326,7 +1326,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Scenes: +class Scenes(Cluster): id: typing.ClassVar[int] = 0x0005 class Structs: @@ -1889,7 +1889,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class OnOff: +class OnOff(Cluster): id: typing.ClassVar[int] = 0x0006 class Enums: @@ -2201,7 +2201,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class OnOffSwitchConfiguration: +class OnOffSwitchConfiguration(Cluster): id: typing.ClassVar[int] = 0x0007 class Attributes: @@ -2259,7 +2259,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class LevelControl: +class LevelControl(Cluster): id: typing.ClassVar[int] = 0x0008 class Enums: @@ -2652,7 +2652,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Alarms: +class Alarms(Cluster): id: typing.ClassVar[int] = 0x0009 class Commands: @@ -2797,7 +2797,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Time: +class Time(Cluster): id: typing.ClassVar[int] = 0x000A class Attributes: @@ -2959,7 +2959,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class BinaryInputBasic: +class BinaryInputBasic(Cluster): id: typing.ClassVar[int] = 0x000F class Attributes: @@ -3108,7 +3108,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class PowerProfile: +class PowerProfile(Cluster): id: typing.ClassVar[int] = 0x001A class Structs: @@ -3688,7 +3688,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ApplianceControl: +class ApplianceControl(Cluster): id: typing.ClassVar[int] = 0x001B class Enums: @@ -3932,7 +3932,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Descriptor: +class Descriptor(Cluster): id: typing.ClassVar[int] = 0x001D class Structs: @@ -4032,7 +4032,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class PollControl: +class PollControl(Cluster): id: typing.ClassVar[int] = 0x0020 class Commands: @@ -4231,7 +4231,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class BridgedActions: +class BridgedActions(Cluster): id: typing.ClassVar[int] = 0x0025 class Enums: @@ -4620,7 +4620,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Basic: +class Basic(Cluster): id: typing.ClassVar[int] = 0x0028 class Commands: @@ -4935,7 +4935,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class OtaSoftwareUpdateProvider: +class OtaSoftwareUpdateProvider(Cluster): id: typing.ClassVar[int] = 0x0029 class Enums: @@ -5116,7 +5116,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class OtaSoftwareUpdateRequestor: +class OtaSoftwareUpdateRequestor(Cluster): id: typing.ClassVar[int] = 0x002A class Enums: @@ -5206,7 +5206,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class PowerSource: +class PowerSource(Cluster): id: typing.ClassVar[int] = 0x002F class Attributes: @@ -5641,7 +5641,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class GeneralCommissioning: +class GeneralCommissioning(Cluster): id: typing.ClassVar[int] = 0x0030 class Enums: @@ -5866,7 +5866,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class NetworkCommissioning: +class NetworkCommissioning(Cluster): id: typing.ClassVar[int] = 0x0031 class Enums: @@ -6299,7 +6299,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class DiagnosticLogs: +class DiagnosticLogs(Cluster): id: typing.ClassVar[int] = 0x0032 class Enums: @@ -6396,7 +6396,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class GeneralDiagnostics: +class GeneralDiagnostics(Cluster): id: typing.ClassVar[int] = 0x0033 class Enums: @@ -6605,7 +6605,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class SoftwareDiagnostics: +class SoftwareDiagnostics(Cluster): id: typing.ClassVar[int] = 0x0034 class Structs: @@ -6745,7 +6745,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ThreadNetworkDiagnostics: +class ThreadNetworkDiagnostics(Cluster): id: typing.ClassVar[int] = 0x0035 class Enums: @@ -7780,7 +7780,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class WiFiNetworkDiagnostics: +class WiFiNetworkDiagnostics(Cluster): id: typing.ClassVar[int] = 0x0036 class Enums: @@ -8021,7 +8021,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class EthernetNetworkDiagnostics: +class EthernetNetworkDiagnostics(Cluster): id: typing.ClassVar[int] = 0x0037 class Enums: @@ -8196,7 +8196,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class BridgedDeviceBasic: +class BridgedDeviceBasic(Cluster): id: typing.ClassVar[int] = 0x0039 class Commands: @@ -8459,7 +8459,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Switch: +class Switch(Cluster): id: typing.ClassVar[int] = 0x003B class Attributes: @@ -8530,7 +8530,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class AdministratorCommissioning: +class AdministratorCommissioning(Cluster): id: typing.ClassVar[int] = 0x003C class Enums: @@ -8628,7 +8628,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class OperationalCredentials: +class OperationalCredentials(Cluster): id: typing.ClassVar[int] = 0x003E class Enums: @@ -9017,7 +9017,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class FixedLabel: +class FixedLabel(Cluster): id: typing.ClassVar[int] = 0x0040 class Structs: @@ -9078,7 +9078,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class BooleanState: +class BooleanState(Cluster): id: typing.ClassVar[int] = 0x0045 class Attributes: @@ -9123,7 +9123,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ModeSelect: +class ModeSelect(Cluster): id: typing.ClassVar[int] = 0x0050 class Structs: @@ -9271,7 +9271,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ShadeConfiguration: +class ShadeConfiguration(Cluster): id: typing.ClassVar[int] = 0x0100 class Attributes: @@ -9368,7 +9368,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class DoorLock: +class DoorLock(Cluster): id: typing.ClassVar[int] = 0x0101 class Enums: @@ -11061,7 +11061,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class WindowCovering: +class WindowCovering(Cluster): id: typing.ClassVar[int] = 0x0102 class Commands: @@ -11551,7 +11551,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class BarrierControl: +class BarrierControl(Cluster): id: typing.ClassVar[int] = 0x0103 class Commands: @@ -11742,7 +11742,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class PumpConfigurationAndControl: +class PumpConfigurationAndControl(Cluster): id: typing.ClassVar[int] = 0x0200 class Enums: @@ -12101,7 +12101,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Thermostat: +class Thermostat(Cluster): id: typing.ClassVar[int] = 0x0201 class Enums: @@ -12842,7 +12842,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class FanControl: +class FanControl(Cluster): id: typing.ClassVar[int] = 0x0202 class Attributes: @@ -12900,7 +12900,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class DehumidificationControl: +class DehumidificationControl(Cluster): id: typing.ClassVar[int] = 0x0203 class Attributes: @@ -13036,7 +13036,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ThermostatUserInterfaceConfiguration: +class ThermostatUserInterfaceConfiguration(Cluster): id: typing.ClassVar[int] = 0x0204 class Attributes: @@ -13107,7 +13107,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ColorControl: +class ColorControl(Cluster): id: typing.ClassVar[int] = 0x0300 class Enums: @@ -14375,7 +14375,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class BallastConfiguration: +class BallastConfiguration(Cluster): id: typing.ClassVar[int] = 0x0301 class Attributes: @@ -14615,7 +14615,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class IlluminanceMeasurement: +class IlluminanceMeasurement(Cluster): id: typing.ClassVar[int] = 0x0400 class Enums: @@ -14717,7 +14717,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class TemperatureMeasurement: +class TemperatureMeasurement(Cluster): id: typing.ClassVar[int] = 0x0402 class Attributes: @@ -14801,7 +14801,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class PressureMeasurement: +class PressureMeasurement(Cluster): id: typing.ClassVar[int] = 0x0403 class Attributes: @@ -14950,7 +14950,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class FlowMeasurement: +class FlowMeasurement(Cluster): id: typing.ClassVar[int] = 0x0404 class Attributes: @@ -15034,7 +15034,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class RelativeHumidityMeasurement: +class RelativeHumidityMeasurement(Cluster): id: typing.ClassVar[int] = 0x0405 class Attributes: @@ -15118,7 +15118,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class OccupancySensing: +class OccupancySensing(Cluster): id: typing.ClassVar[int] = 0x0406 class Attributes: @@ -15306,7 +15306,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class CarbonMonoxideConcentrationMeasurement: +class CarbonMonoxideConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x040C class Attributes: @@ -15390,7 +15390,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class CarbonDioxideConcentrationMeasurement: +class CarbonDioxideConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x040D class Attributes: @@ -15474,7 +15474,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class EthyleneConcentrationMeasurement: +class EthyleneConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x040E class Attributes: @@ -15558,7 +15558,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class EthyleneOxideConcentrationMeasurement: +class EthyleneOxideConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x040F class Attributes: @@ -15642,7 +15642,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class HydrogenConcentrationMeasurement: +class HydrogenConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0410 class Attributes: @@ -15726,7 +15726,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class HydrogenSulphideConcentrationMeasurement: +class HydrogenSulphideConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0411 class Attributes: @@ -15810,7 +15810,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class NitricOxideConcentrationMeasurement: +class NitricOxideConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0412 class Attributes: @@ -15894,7 +15894,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class NitrogenDioxideConcentrationMeasurement: +class NitrogenDioxideConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0413 class Attributes: @@ -15978,7 +15978,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class OxygenConcentrationMeasurement: +class OxygenConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0414 class Attributes: @@ -16062,7 +16062,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class OzoneConcentrationMeasurement: +class OzoneConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0415 class Attributes: @@ -16146,7 +16146,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class SulfurDioxideConcentrationMeasurement: +class SulfurDioxideConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0416 class Attributes: @@ -16230,7 +16230,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class DissolvedOxygenConcentrationMeasurement: +class DissolvedOxygenConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0417 class Attributes: @@ -16314,7 +16314,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class BromateConcentrationMeasurement: +class BromateConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0418 class Attributes: @@ -16398,7 +16398,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ChloraminesConcentrationMeasurement: +class ChloraminesConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0419 class Attributes: @@ -16482,7 +16482,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ChlorineConcentrationMeasurement: +class ChlorineConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x041A class Attributes: @@ -16566,7 +16566,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class FecalColiformAndEColiConcentrationMeasurement: +class FecalColiformAndEColiConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x041B class Attributes: @@ -16650,7 +16650,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class FluorideConcentrationMeasurement: +class FluorideConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x041C class Attributes: @@ -16734,7 +16734,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class HaloaceticAcidsConcentrationMeasurement: +class HaloaceticAcidsConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x041D class Attributes: @@ -16818,7 +16818,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class TotalTrihalomethanesConcentrationMeasurement: +class TotalTrihalomethanesConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x041E class Attributes: @@ -16902,7 +16902,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class TotalColiformBacteriaConcentrationMeasurement: +class TotalColiformBacteriaConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x041F class Attributes: @@ -16986,7 +16986,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class TurbidityConcentrationMeasurement: +class TurbidityConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0420 class Attributes: @@ -17070,7 +17070,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class CopperConcentrationMeasurement: +class CopperConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0421 class Attributes: @@ -17154,7 +17154,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class LeadConcentrationMeasurement: +class LeadConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0422 class Attributes: @@ -17238,7 +17238,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ManganeseConcentrationMeasurement: +class ManganeseConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0423 class Attributes: @@ -17322,7 +17322,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class SulfateConcentrationMeasurement: +class SulfateConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0424 class Attributes: @@ -17406,7 +17406,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class BromodichloromethaneConcentrationMeasurement: +class BromodichloromethaneConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0425 class Attributes: @@ -17490,7 +17490,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class BromoformConcentrationMeasurement: +class BromoformConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0426 class Attributes: @@ -17574,7 +17574,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ChlorodibromomethaneConcentrationMeasurement: +class ChlorodibromomethaneConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0427 class Attributes: @@ -17658,7 +17658,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ChloroformConcentrationMeasurement: +class ChloroformConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0428 class Attributes: @@ -17742,7 +17742,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class SodiumConcentrationMeasurement: +class SodiumConcentrationMeasurement(Cluster): id: typing.ClassVar[int] = 0x0429 class Attributes: @@ -17826,7 +17826,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class IasZone: +class IasZone(Cluster): id: typing.ClassVar[int] = 0x0500 class Enums: @@ -18093,7 +18093,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class IasAce: +class IasAce(Cluster): id: typing.ClassVar[int] = 0x0501 class Enums: @@ -18604,7 +18604,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class IasWd: +class IasWd(Cluster): id: typing.ClassVar[int] = 0x0502 class Commands: @@ -18691,7 +18691,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class WakeOnLan: +class WakeOnLan(Cluster): id: typing.ClassVar[int] = 0x0503 class Attributes: @@ -18736,7 +18736,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class TvChannel: +class TvChannel(Cluster): id: typing.ClassVar[int] = 0x0504 class Enums: @@ -18932,7 +18932,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class TargetNavigator: +class TargetNavigator(Cluster): id: typing.ClassVar[int] = 0x0505 class Enums: @@ -19051,7 +19051,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class MediaPlayback: +class MediaPlayback(Cluster): id: typing.ClassVar[int] = 0x0506 class Enums: @@ -19539,7 +19539,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class MediaInput: +class MediaInput(Cluster): id: typing.ClassVar[int] = 0x0507 class Enums: @@ -19694,7 +19694,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class LowPower: +class LowPower(Cluster): id: typing.ClassVar[int] = 0x0508 class Commands: @@ -19739,7 +19739,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class KeypadInput: +class KeypadInput(Cluster): id: typing.ClassVar[int] = 0x0509 class Enums: @@ -19898,7 +19898,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ContentLauncher: +class ContentLauncher(Cluster): id: typing.ClassVar[int] = 0x050A class Enums: @@ -20157,7 +20157,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class AudioOutput: +class AudioOutput(Cluster): id: typing.ClassVar[int] = 0x050B class Enums: @@ -20279,7 +20279,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ApplicationLauncher: +class ApplicationLauncher(Cluster): id: typing.ClassVar[int] = 0x050C class Enums: @@ -20414,7 +20414,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ApplicationBasic: +class ApplicationBasic(Cluster): id: typing.ClassVar[int] = 0x050D class Enums: @@ -20561,7 +20561,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class AccountLogin: +class AccountLogin(Cluster): id: typing.ClassVar[int] = 0x050E class Commands: @@ -20645,7 +20645,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class TestCluster: +class TestCluster(Cluster): id: typing.ClassVar[int] = 0x050F class Enums: @@ -21746,7 +21746,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Messaging: +class Messaging(Cluster): id: typing.ClassVar[int] = 0x0703 class Enums: @@ -22033,7 +22033,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ApplianceIdentification: +class ApplianceIdentification(Cluster): id: typing.ClassVar[int] = 0x0B00 class Attributes: @@ -22221,7 +22221,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class MeterIdentification: +class MeterIdentification(Cluster): id: typing.ClassVar[int] = 0x0B01 class Attributes: @@ -22409,7 +22409,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ApplianceEventsAndAlert: +class ApplianceEventsAndAlert(Cluster): id: typing.ClassVar[int] = 0x0B02 class Enums: @@ -22519,7 +22519,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ApplianceStatistics: +class ApplianceStatistics(Cluster): id: typing.ClassVar[int] = 0x0B03 class Commands: @@ -22694,7 +22694,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class ElectricalMeasurement: +class ElectricalMeasurement(Cluster): id: typing.ClassVar[int] = 0x0B04 class Commands: @@ -24481,7 +24481,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class Binding: +class Binding(Cluster): id: typing.ClassVar[int] = 0xF000 class Commands: @@ -24564,7 +24564,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class GroupKeyManagement: +class GroupKeyManagement(Cluster): id: typing.ClassVar[int] = 0xF004 class Enums: @@ -24670,7 +24670,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class SampleMfgSpecificCluster: +class SampleMfgSpecificCluster(Cluster): id: typing.ClassVar[int] = 0xFC00 class Commands: @@ -24745,7 +24745,7 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: @dataclass -class SampleMfgSpecificCluster2: +class SampleMfgSpecificCluster2(Cluster): id: typing.ClassVar[int] = 0xFC00 class Commands: diff --git a/src/controller/python/chip/clusters/attribute.cpp b/src/controller/python/chip/clusters/attribute.cpp new file mode 100644 index 00000000000000..e7745cc77f03c6 --- /dev/null +++ b/src/controller/python/chip/clusters/attribute.cpp @@ -0,0 +1,265 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace chip; +using namespace chip::app; + +using PyObject = void; + +extern "C" { +// Encodes n attribute write requests, follows 3 * n arguments, in the (AttributeWritePath*=void *, uint8_t*, size_t) order. +chip::ChipError::StorageType pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device, size_t n, ...); +chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, DeviceProxy * device, size_t n, ...); +} + +namespace chip { +namespace python { + +struct __attribute__((packed)) AttributePath +{ + chip::EndpointId endpointId; + chip::ClusterId clusterId; + chip::AttributeId attributeId; +}; + +using OnReadAttributeDataCallback = void (*)(PyObject * appContext, chip::EndpointId endpointId, chip::ClusterId clusterId, + chip::AttributeId attributeId, + std::underlying_type_t imstatus, uint8_t * data, + uint32_t dataLen); +using OnReadErrorCallback = void (*)(PyObject * appContext, uint32_t chiperror); +using OnReadDoneCallback = void (*)(PyObject * appContext); + +OnReadAttributeDataCallback gOnReadAttributeDataCallback = nullptr; +OnReadErrorCallback gOnReadErrorCallback = nullptr; +OnReadDoneCallback gOnReadDoneCallback = nullptr; + +class ReadClientCallback : public ReadClient::Callback +{ +public: + ReadClientCallback(PyObject * appContext) : mAppContext(appContext) {} + + void OnAttributeData(const ReadClient * apReadClient, const ConcreteAttributePath & aPath, TLV::TLVReader * apData, + const StatusIB & aStatus) override + { + uint8_t buffer[CHIP_CONFIG_DEFAULT_UDP_MTU_SIZE]; + uint32_t size = 0; + // When the apData is nullptr, means we did not receive a valid attribute data from server, status will be some error + // status. + if (apData != nullptr) + { + // The TLVReader's read head is not pointing to the first element in the container instead of the container itself, use + // a TLVWriter to get a TLV with a normalized TLV buffer (Wrapped with a anonymous tag, no extra "end of container" tag + // at the end.) + TLV::TLVWriter writer; + writer.Init(buffer); + CHIP_ERROR err = writer.CopyElement(TLV::AnonymousTag, *apData); + if (err != CHIP_NO_ERROR) + { + app::StatusIB status; + status.mStatus = Protocols::InteractionModel::Status::Failure; + this->OnError(apReadClient, err); + return; + } + size = writer.GetLengthWritten(); + } + + gOnReadAttributeDataCallback(mAppContext, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, + to_underlying(aStatus.mStatus), buffer, size); + } + + void OnError(const ReadClient * apReadClient, CHIP_ERROR aError) override + { + gOnReadErrorCallback(mAppContext, aError.AsInteger()); + } + + void OnDone(ReadClient * apReadClient) override + { + gOnReadDoneCallback(mAppContext); + // delete apReadClient; + delete this; + }; + +private: + PyObject * mAppContext; +}; + +using OnWriteResponseCallback = void (*)(PyObject * appContext, chip::EndpointId endpointId, chip::ClusterId clusterId, + chip::AttributeId attributeId, + std::underlying_type_t imstatus); +using OnWriteErrorCallback = void (*)(PyObject * appContext, uint32_t chiperror); +using OnWriteDoneCallback = void (*)(PyObject * appContext); + +OnWriteResponseCallback gOnWriteResponseCallback = nullptr; +OnWriteErrorCallback gOnWriteErrorCallback = nullptr; +OnWriteDoneCallback gOnWriteDoneCallback = nullptr; + +class WriteClientCallback : public WriteClient::Callback +{ +public: + WriteClientCallback(PyObject * appContext) : mAppContext(appContext) {} + + void OnResponse(const WriteClient * apWriteClient, const ConcreteAttributePath & aPath, app::StatusIB aStatus) override + { + gOnWriteResponseCallback(mAppContext, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, + to_underlying(aStatus.mStatus)); + } + + void OnError(const WriteClient * apWriteClient, CHIP_ERROR aProtocolError) override + { + gOnWriteErrorCallback(mAppContext, aProtocolError.AsInteger()); + } + + void OnDone(WriteClient * apWriteClient) override + { + gOnWriteDoneCallback(mAppContext); + // delete apWriteClient; + delete this; + }; + +private: + PyObject * mAppContext = nullptr; +}; + +} // namespace python +} // namespace chip + +using namespace chip::python; + +extern "C" { +void pychip_WriteClient_InitCallbacks(OnWriteResponseCallback onWriteResponseCallback, OnWriteErrorCallback onWriteErrorCallback, + OnWriteDoneCallback onWriteDoneCallback) +{ + gOnWriteResponseCallback = onWriteResponseCallback; + gOnWriteErrorCallback = onWriteErrorCallback; + gOnWriteDoneCallback = onWriteDoneCallback; +} + +void pychip_ReadClient_InitCallbacks(OnReadAttributeDataCallback onReadAttributeDataCallback, + OnReadErrorCallback onReadErrorCallback, OnReadDoneCallback onReadDoneCallback) +{ + gOnReadAttributeDataCallback = onReadAttributeDataCallback; + gOnReadErrorCallback = onReadErrorCallback; + gOnReadDoneCallback = onReadDoneCallback; +} + +chip::ChipError::StorageType pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device, size_t n, ...) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + std::unique_ptr callback = std::make_unique(appContext); + app::WriteClientHandle client; + + va_list args; + va_start(args, n); + + SuccessOrExit(err = app::InteractionModelEngine::GetInstance()->NewWriteClient(client, callback.get())); + + { + for (size_t i = 0; i < n; i++) + { + void * path = va_arg(args, void *); + void * tlv = va_arg(args, void *); + int length = va_arg(args, int); + + python::AttributePath pathObj; + memcpy(&pathObj, path, sizeof(python::AttributePath)); + uint8_t * tlvBuffer = reinterpret_cast(tlv); + + TLV::TLVWriter * writer; + TLV::TLVReader reader; + + SuccessOrExit(err = client->PrepareAttribute( + chip::app::AttributePathParams(pathObj.endpointId, pathObj.clusterId, pathObj.attributeId))); + VerifyOrExit((writer = client->GetAttributeDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + + reader.Init(tlvBuffer, static_cast(length)); + reader.Next(); + SuccessOrExit( + err = writer->CopyElement(chip::TLV::ContextTag(to_underlying(chip::app::AttributeDataIB::Tag::kData)), reader)); + + SuccessOrExit(err = client->FinishAttribute()); + } + } + + SuccessOrExit(err = device->SendWriteAttributeRequest(std::move(client), nullptr, nullptr)); + + callback.release(); + +exit: + va_end(args); + return err.AsInteger(); +} + +chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, DeviceProxy * device, size_t n, ...) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + std::unique_ptr callback = std::make_unique(appContext); + + va_list args; + va_start(args, n); + + std::unique_ptr readPaths(new AttributePathParams[n]); + + { + for (size_t i = 0; i < n; i++) + { + void * path = va_arg(args, void *); + + python::AttributePath pathObj; + memcpy(&pathObj, path, sizeof(python::AttributePath)); + + readPaths[i] = AttributePathParams(pathObj.endpointId, pathObj.clusterId, pathObj.attributeId); + } + } + + Optional session = device->GetSecureSession(); + ReadClient * readClient; + + VerifyOrExit(session.HasValue(), err = CHIP_ERROR_NOT_CONNECTED); + { + app::InteractionModelEngine::GetInstance()->NewReadClient(&readClient, ReadClient::InteractionType::Read, callback.get()); + ReadPrepareParams params(session.Value()); + params.mpAttributePathParamsList = readPaths.get(); + params.mAttributePathParamsListSize = n; + + err = readClient->SendReadRequest(params); + if (err != CHIP_NO_ERROR) + { + readClient->Shutdown(); + } + } + + callback.release(); + +exit: + va_end(args); + return err.AsInteger(); +} +} diff --git a/src/controller/python/chip/clusters/write.cpp b/src/controller/python/chip/clusters/write.cpp deleted file mode 100644 index 9d5549c1164cc4..00000000000000 --- a/src/controller/python/chip/clusters/write.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include -#include -#include - -#include -#include - -using namespace chip; -using namespace chip::app; - -using PyObject = void *; - -extern "C" { -// Encodes n attribute write requests, follows 3 * n arguments, in the (AttributeWritePath*=void *, uint8_t*, size_t) order. -chip::ChipError::StorageType pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device, size_t n, ...); -} - -namespace chip { -namespace python { - -struct __attribute__((packed)) AttributeWritePath -{ - chip::EndpointId endpointId; - chip::ClusterId clusterId; - chip::AttributeId attributeId; -}; - -using OnWriteResponseCallback = void (*)(PyObject appContext, chip::EndpointId endpointId, chip::ClusterId clusterId, - chip::AttributeId attributeId, - std::underlying_type_t imstatus); -using OnWriteErrorCallback = void (*)(PyObject appContext, uint32_t chiperror); -using OnWriteDoneCallback = void (*)(PyObject appContext); - -OnWriteResponseCallback gOnWriteResponseCallback = nullptr; -OnWriteErrorCallback gOnWriteErrorCallback = nullptr; -OnWriteDoneCallback gOnWriteDoneCallback = nullptr; - -class WriteClientCallback : public WriteClient::Callback -{ -public: - WriteClientCallback(PyObject appContext) : mAppContext(appContext) {} - - void OnResponse(const WriteClient * apWriteClient, const ConcreteAttributePath & aPath, app::StatusIB aStatus) override - { - gOnWriteResponseCallback(mAppContext, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, - to_underlying(aStatus.mStatus)); - } - - void OnError(const WriteClient * apWriteClient, CHIP_ERROR aProtocolError) override - { - gOnWriteErrorCallback(mAppContext, aProtocolError.AsInteger()); - } - - void OnDone(WriteClient * apCommandSender) override - { - gOnWriteDoneCallback(mAppContext); - // delete apCommandSender; - delete this; - }; - -private: - PyObject mAppContext = nullptr; -}; - -} // namespace python -} // namespace chip - -using namespace chip::python; - -extern "C" { -void pychip_WriteClient_InitCallbacks(OnWriteResponseCallback onWriteResponseCallback, OnWriteErrorCallback onWriteErrorCallback, - OnWriteDoneCallback onWriteDoneCallback) -{ - gOnWriteResponseCallback = onWriteResponseCallback; - gOnWriteErrorCallback = onWriteErrorCallback; - gOnWriteDoneCallback = onWriteDoneCallback; -} - -chip::ChipError::StorageType pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device, size_t n, ...) -{ - CHIP_ERROR err = CHIP_NO_ERROR; - - std::unique_ptr callback = std::make_unique(appContext); - app::WriteClientHandle client; - - va_list args; - va_start(args, n); - - SuccessOrExit(err = app::InteractionModelEngine::GetInstance()->NewWriteClient(client, callback.get())); - - { - for (size_t i = 0; i < n; i++) - { - void * path = va_arg(args, void *); - void * tlv = va_arg(args, void *); - int length = va_arg(args, int); - - AttributeWritePath pathObj; - memcpy(&pathObj, path, sizeof(AttributeWritePath)); - uint8_t * tlvBuffer = reinterpret_cast(tlv); - - TLV::TLVWriter * writer; - TLV::TLVReader reader; - - SuccessOrExit(err = client->PrepareAttribute( - chip::app::AttributePathParams(pathObj.endpointId, pathObj.clusterId, pathObj.attributeId))); - VerifyOrExit((writer = client->GetAttributeDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE); - - reader.Init(tlvBuffer, static_cast(length)); - reader.Next(); - SuccessOrExit(err = writer->CopyElement( - chip::TLV::ContextTag(chip::to_underlying(chip::app::AttributeDataIB::Tag::kData)), reader)); - - SuccessOrExit(err = client->FinishAttribute()); - } - } - - SuccessOrExit(err = device->SendWriteAttributeRequest(std::move(client), nullptr, nullptr)); - - callback.release(); - -exit: - va_end(args); - return err.AsInteger(); -} -} diff --git a/src/controller/python/templates/python-cluster-Objects-py.zapt b/src/controller/python/templates/python-cluster-Objects-py.zapt index 9a08a833d8ba11..4fad616d1dbed5 100644 --- a/src/controller/python/templates/python-cluster-Objects-py.zapt +++ b/src/controller/python/templates/python-cluster-Objects-py.zapt @@ -12,12 +12,12 @@ from chip import ChipUtility from chip.tlv import uint -from .ClusterObjects import ClusterObject, ClusterObjectDescriptor, ClusterObjectFieldDescriptor, ClusterCommand, ClusterAttributeDescriptor +from .ClusterObjects import ClusterObject, ClusterObjectDescriptor, ClusterObjectFieldDescriptor, ClusterCommand, ClusterAttributeDescriptor, Cluster {{#zcl_clusters}} @dataclass -class {{asUpperCamelCase name}}: +class {{asUpperCamelCase name}}(Cluster): id: typing.ClassVar[int] = {{asHex code 4}} {{#zcl_enums}} diff --git a/src/controller/python/test/test_scripts/cluster_objects.py b/src/controller/python/test/test_scripts/cluster_objects.py index c12fba16ffbce1..2895d612435b43 100644 --- a/src/controller/python/test/test_scripts/cluster_objects.py +++ b/src/controller/python/test/test_scripts/cluster_objects.py @@ -97,40 +97,45 @@ async def SendWriteRequest(cls, devCtrl): @classmethod async def SendReadRequest(cls, devCtrl): - res = await devCtrl.ReadAttribute(nodeid=NODE_ID, - attributes=[ - (0, Clusters.Basic.Attributes.VendorName), - (0, Clusters.Basic.Attributes.VendorID), - (0, Clusters.Basic.Attributes.ProductName), - (0, Clusters.Basic.Attributes.ProductID), - (0, Clusters.Basic.Attributes.UserLabel), - (0, Clusters.Basic.Attributes.Location), - (0, Clusters.Basic.Attributes.HardwareVersion), - (0, Clusters.Basic.Attributes.HardwareVersionString), - (0, Clusters.Basic.Attributes.SoftwareVersion), - (0, Clusters.Basic.Attributes.SoftwareVersionString), - ]) + req = [ + (0, Clusters.Basic.Attributes.VendorName), + (0, Clusters.Basic.Attributes.VendorID), + (0, Clusters.Basic.Attributes.ProductName), + (0, Clusters.Basic.Attributes.ProductID), + (0, Clusters.Basic.Attributes.UserLabel), + (0, Clusters.Basic.Attributes.Location), + (0, Clusters.Basic.Attributes.HardwareVersion), + (0, Clusters.Basic.Attributes.HardwareVersionString), + (0, Clusters.Basic.Attributes.SoftwareVersion), + (0, Clusters.Basic.Attributes.SoftwareVersionString), + ] + + # Note: The server might be too small to handle reading lots of attributes at the same time. + res = [ + await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=[r]) for r in req + ] + expectedRes = [ - AttributeReadResult(Path=AttributePath( - EndpointId=0, ClusterId=40, AttributeId=1), Status=0, Data='TEST_VENDOR'), - AttributeReadResult(Path=AttributePath( - EndpointId=0, ClusterId=40, AttributeId=2), Status=0, Data=9050), - AttributeReadResult(Path=AttributePath( - EndpointId=0, ClusterId=40, AttributeId=3), Status=0, Data='TEST_PRODUCT'), - AttributeReadResult(Path=AttributePath( - EndpointId=0, ClusterId=40, AttributeId=4), Status=0, Data=65279), - AttributeReadResult(Path=AttributePath( - EndpointId=0, ClusterId=40, AttributeId=5), Status=0, Data='Test'), - AttributeReadResult(Path=AttributePath( - EndpointId=0, ClusterId=40, AttributeId=6), Status=0, Data=''), - AttributeReadResult(Path=AttributePath( - EndpointId=0, ClusterId=40, AttributeId=7), Status=0, Data=0), - AttributeReadResult(Path=AttributePath( - EndpointId=0, ClusterId=40, AttributeId=8), Status=0, Data='TEST_VERSION'), - AttributeReadResult(Path=AttributePath( - EndpointId=0, ClusterId=40, AttributeId=9), Status=0, Data=0), - AttributeReadResult(Path=AttributePath( - EndpointId=0, ClusterId=40, AttributeId=10), Status=0, Data='prerelease') + [AttributeReadResult(Path=AttributePath( + EndpointId=0, ClusterId=40, AttributeId=1), Status=chip.interaction_model.Status.Success, Data='TEST_VENDOR'), ], + [AttributeReadResult(Path=AttributePath( + EndpointId=0, ClusterId=40, AttributeId=2), Status=chip.interaction_model.Status.Success, Data=9050), ], + [AttributeReadResult(Path=AttributePath( + EndpointId=0, ClusterId=40, AttributeId=3), Status=chip.interaction_model.Status.Success, Data='TEST_PRODUCT'), ], + [AttributeReadResult(Path=AttributePath( + EndpointId=0, ClusterId=40, AttributeId=4), Status=chip.interaction_model.Status.Success, Data=65279), ], + [AttributeReadResult(Path=AttributePath( + EndpointId=0, ClusterId=40, AttributeId=5), Status=chip.interaction_model.Status.Success, Data='Test'), ], + [AttributeReadResult(Path=AttributePath( + EndpointId=0, ClusterId=40, AttributeId=6), Status=chip.interaction_model.Status.Success, Data=''), ], + [AttributeReadResult(Path=AttributePath( + EndpointId=0, ClusterId=40, AttributeId=7), Status=chip.interaction_model.Status.Success, Data=0), ], + [AttributeReadResult(Path=AttributePath( + EndpointId=0, ClusterId=40, AttributeId=8), Status=chip.interaction_model.Status.Success, Data='TEST_VERSION'), ], + [AttributeReadResult(Path=AttributePath( + EndpointId=0, ClusterId=40, AttributeId=9), Status=chip.interaction_model.Status.Success, Data=0), ], + [AttributeReadResult(Path=AttributePath( + EndpointId=0, ClusterId=40, AttributeId=10), Status=chip.interaction_model.Status.Success, Data='prerelease')], ] if res != expectedRes: