Skip to content

Commit

Permalink
[Python] Update AttributeRead
Browse files Browse the repository at this point in the history
  • Loading branch information
erjiaqing committed Nov 10, 2021
1 parent 9b24191 commit dc64df5
Show file tree
Hide file tree
Showing 6 changed files with 466 additions and 202 deletions.
2 changes: 1 addition & 1 deletion src/controller/python/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
42 changes: 15 additions & 27 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,37 +378,25 @@ 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[typing.Tuple[int, ClusterAttribute.AttributeReadRequest], ClusterAttribute.AttributePath]]):
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:
if isinstance(v, typing.Tuple):
attrs.append(ClusterAttribute.AttributePath(
EndpointId=v[0], Attribute=v[1]))
elif isinstance(v, ClusterAttribute.AttributePath):
attrs.append(v)
else:
raise ArgumentError(
f"ReadAttribute does not support {type(v)} for attribute path")
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):
Expand Down
163 changes: 159 additions & 4 deletions src/controller/python/chip/clusters/Attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -61,6 +88,75 @@ class AttributeReadResult(AttributeStatus):
Data: Any = None


_AttributeIndex = {}


def _BuildAttributeIndex():
''' Locates the right generated cluster object given a set of parameters.
isClientSideCommand: True if it is a client-to-server command, else False.
path: A CommandPath that describes the endpoint, cluster and ID of the command.
Returns the type of the cluster object if one is found. Otherwise, returns None.
'''
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
Expand Down Expand Up @@ -95,6 +191,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(
Expand All @@ -105,7 +227,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
Expand Down Expand Up @@ -144,6 +267,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()

Expand All @@ -155,6 +303,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()
Loading

0 comments on commit dc64df5

Please sign in to comment.