Skip to content

Commit

Permalink
Auto-Deduce Invoke Response Type
Browse files Browse the repository at this point in the history
This modifies the existing ChipDeviceController.SendCommand() Python API
to automatically deduce the cluster object type associated with the
response and automatically return that to the caller after successfully
deserializing the TLV into that object.

This avoids callers having to explicitly pass in an object, making it
that much easier to use.

Tests: Tested using cluster_objects.py, as well as manually
sending/receiving commands at the Python shell.
  • Loading branch information
mrjerryjohns committed Oct 25, 2021
1 parent 3c8b7e6 commit b11b11e
Show file tree
Hide file tree
Showing 5 changed files with 2,274 additions and 1,863 deletions.
54 changes: 44 additions & 10 deletions src/controller/python/chip/clusters/Command.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,64 @@
from typing import Type
from ctypes import CFUNCTYPE, c_char_p, c_size_t, c_void_p, c_uint32, c_uint16, py_object


from .ClusterObjects import ClusterCommand
import chip.exceptions
import chip.interaction_model

import inspect, sys

@dataclass
class CommandPath:
EndpointId: int
ClusterId: int
CommandId: int

def FindCommandClusterObject(isClientSideCommand: bool, path: CommandPath):
''' 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 (('Commands') in str(subclass)):
for commandName, command in inspect.getmembers(subclass):
if inspect.isclass(command):
for name, field in inspect.getmembers(command):
if ('__dataclass_fields__' in name):
if (field['cluster_id'].default == path.ClusterId) and (field['command_id'].default == path.CommandId) and (field['is_client'].default == isClientSideCommand):
return eval('chip.clusters.Objects.' + clusterName + '.Commands.' + commandName)
return None

class AsyncCommandTransaction:
def __init__(self, future: Future, eventLoop, expectType: Type):
self._event_loop = eventLoop
self._future = future
self._expect_type = expectType

def _handleResponse(self, response: bytes):
if self._expect_type:
try:
self._future.set_result(self._expect_type.FromTLV(response))
except Exception as ex:
self._handleError(
chip.interaction_model.Status.Failure, 0, ex)
else:
def _handleResponse(self, path: CommandPath, response: bytes):
if (len(response) == 0):
self._future.set_result(None)
else:
# If a type hasn't been assigned, let's auto-deduce it.
if (self._expect_type == None):
self._expect_type = FindCommandClusterObject(False, path)

if self._expect_type:
try:
self._future.set_result(self._expect_type.FromTLV(response))
except Exception as ex:
self._handleError(
chip.interaction_model.Status.Failure, 0, ex)
else:
self._future.set_result(None)

def handleResponse(self, path: CommandPath, response: bytes):
self._event_loop.call_soon_threadsafe(
self._handleResponse, response)
self._handleResponse, path, response)

def _handleError(self, imError: int, chipError: int, exception: Exception):
if exception:
Expand All @@ -68,6 +94,7 @@ def _handleError(self, imError: int, chipError: int, exception: Exception):
except:
self._future.set_exception(chip.interaction_model.InteractionModelError(
chip.interaction_model.Status.Failure))
pass

def handleError(self, imError: int, chipError: int):
self._event_loop.call_soon_threadsafe(
Expand Down Expand Up @@ -100,6 +127,13 @@ def _OnCommandSenderDoneCallback(closure):


def SendCommand(future: Future, eventLoop, responseType: Type, device, commandPath: CommandPath, payload: ClusterCommand) -> int:
''' Send a cluster-object encapsulated command to a device and does the following:
- On receipt of a successful data response, returns the cluster-object equivalent through the provided future.
- None (on a successful response containing no data)
- Raises an exception if any errors are encountered.
If no response type is provided above, the type will be automatically deduced.
'''
if (responseType is not None) and (not issubclass(responseType, ClusterCommand)):
raise ValueError("responseType must be a ClusterCommand or None")

Expand Down
Loading

0 comments on commit b11b11e

Please sign in to comment.