Skip to content

Commit

Permalink
[python] Add timed request support (#13318)
Browse files Browse the repository at this point in the history
* [python] Add timed request support

* Run Codegen
  • Loading branch information
erjiaqing authored and pull[bot] committed Nov 14, 2023
1 parent 346e75e commit 8663209
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 28 deletions.
4 changes: 2 additions & 2 deletions src/app/CommandSender.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ class CommandSender final : public Messaging::ExchangeDelegate
return AddRequestDataInternal(aCommandPath, aData, aTimedInvokeTimeoutMs);
}

CHIP_ERROR FinishCommand(const Optional<uint16_t> & aTimedInvokeTimeoutMs);

#if CONFIG_IM_BUILD_FOR_UNIT_TEST
/**
* Version of AddRequestData that allows sending a message that is
Expand Down Expand Up @@ -278,8 +280,6 @@ class CommandSender final : public Messaging::ExchangeDelegate
// and mPendingInvokeData is populated.
CHIP_ERROR SendInvokeRequest();

CHIP_ERROR FinishCommand(const Optional<uint16_t> & aTimedInvokeTimeoutMs);

CHIP_ERROR Finalize(System::PacketBufferHandle & commandPacket);

Messaging::ExchangeContext * mpExchangeCtx = nullptr;
Expand Down
11 changes: 7 additions & 4 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,13 @@ def DeviceAvailableCallback(device, err):
raise self._ChipStack.ErrorToException(CHIP_ERROR_INTERNAL)
return returnDevice

async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.ClusterCommand, responseType=None):
async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.ClusterCommand, responseType=None, timedRequestTimeoutMs: int = None):
'''
Send a cluster-object encapsulated command to a node and get returned a future that can be awaited upon to receive the response.
If a valid responseType is passed in, that will be used to deserialize the object. If not, the type will be automatically deduced
from the metadata received over the wire.
timedWriteTimeoutMs: Timeout for a timed invoke request. Omit or set to 'None' to indicate a non-timed request.
'''

eventLoop = asyncio.get_running_loop()
Expand All @@ -417,17 +419,18 @@ async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.
EndpointId=endpoint,
ClusterId=payload.cluster_id,
CommandId=payload.command_id,
), payload)
), payload, timedRequestTimeoutMs=timedRequestTimeoutMs)
)
if res != 0:
future.set_exception(self._ChipStack.ErrorToException(res))
return await future

async def WriteAttribute(self, nodeid: int, attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]]):
async def WriteAttribute(self, nodeid: int, attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]], timedRequestTimeoutMs: int = None):
'''
Write a list of attributes on a target node.
nodeId: Target's Node ID
timedWriteTimeoutMs: Timeout for a timed write request. Omit or set to 'None' to indicate a non-timed request.
attributes: A list of tuples of type (endpoint, cluster-object):
E.g
Expand All @@ -445,7 +448,7 @@ async def WriteAttribute(self, nodeid: int, attributes: typing.List[typing.Tuple

res = self._ChipStack.Call(
lambda: ClusterAttribute.WriteAttributes(
future, eventLoop, device, attrs)
future, eventLoop, device, attrs, timedRequestTimeoutMs=timedRequestTimeoutMs)
)
if res != 0:
raise self._ChipStack.ErrorToException(res)
Expand Down
9 changes: 6 additions & 3 deletions src/controller/python/chip/clusters/Attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,12 +763,14 @@ def _OnWriteDoneCallback(closure):
ctypes.pythonapi.Py_DecRef(ctypes.py_object(closure))


def WriteAttributes(future: Future, eventLoop, device, attributes: List[AttributeWriteRequest]) -> int:
def WriteAttributes(future: Future, eventLoop, device, attributes: List[AttributeWriteRequest], timedRequestTimeoutMs: int = None) -> int:
handle = chip.native.GetLibraryHandle()
transaction = AsyncWriteTransaction(future, eventLoop)

writeargs = []
for attr in attributes:
if attr.Attribute.must_use_timed_write and timedRequestTimeoutMs is None or timedRequestTimeoutMs == 0:
raise ValueError(
f"Attribute {attr.__class__} must use timed write, please specify a valid timedRequestTimeoutMs value.")
path = chip.interaction_model.AttributePathIBstruct.parse(
b'\x00' * chip.interaction_model.AttributePathIBstruct.sizeof())
path.EndpointId = attr.EndpointId
Expand All @@ -780,9 +782,10 @@ def WriteAttributes(future: Future, eventLoop, device, attributes: List[Attribut
writeargs.append(ctypes.c_char_p(bytes(tlv)))
writeargs.append(ctypes.c_int(len(tlv)))

transaction = AsyncWriteTransaction(future, eventLoop)
ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction))
res = handle.pychip_WriteClient_WriteAttributes(
ctypes.py_object(transaction), device, ctypes.c_size_t(len(attributes)), *writeargs)
ctypes.py_object(transaction), device, ctypes.c_uint16(0 if timedRequestTimeoutMs is None else timedRequestTimeoutMs), ctypes.c_size_t(len(attributes)), *writeargs)
if res != 0:
ctypes.pythonapi.Py_DecRef(ctypes.py_object(transaction))
return res
Expand Down
8 changes: 8 additions & 0 deletions src/controller/python/chip/clusters/ClusterObjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ def cluster_id(self) -> int:
def command_id(self) -> int:
raise NotImplementedError()

@ChipUtility.classproperty
def must_use_timed_invoke(cls) -> bool:
return False


class Cluster(ClusterObject):
''' This class does nothing, but a convenient class that generated clusters can inherit from.
Expand Down Expand Up @@ -259,6 +263,10 @@ def attribute_id(self) -> int:
def attribute_type(cls) -> ClusterObjectFieldDescriptor:
raise NotImplementedError()

@ChipUtility.classproperty
def must_use_timed_write(cls) -> bool:
return False

@ChipUtility.classproperty
def _cluster_object(cls) -> ClusterObject:
return make_dataclass('InternalClass',
Expand Down
11 changes: 8 additions & 3 deletions src/controller/python/chip/clusters/Command.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from typing import Type
from ctypes import CFUNCTYPE, c_char_p, c_size_t, c_void_p, c_uint32, c_uint16, c_uint8, py_object

from construct.core import ValidationError

from .ClusterObjects import ClusterCommand
import chip.exceptions
import chip.interaction_model
Expand Down Expand Up @@ -137,7 +139,7 @@ def _OnCommandSenderDoneCallback(closure):
ctypes.pythonapi.Py_DecRef(ctypes.py_object(closure))


def SendCommand(future: Future, eventLoop, responseType: Type, device, commandPath: CommandPath, payload: ClusterCommand) -> int:
def SendCommand(future: Future, eventLoop, responseType: Type, device, commandPath: CommandPath, payload: ClusterCommand, timedRequestTimeoutMs: int = None) -> 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)
Expand All @@ -147,14 +149,17 @@ def SendCommand(future: Future, eventLoop, responseType: Type, device, commandPa
'''
if (responseType is not None) and (not issubclass(responseType, ClusterCommand)):
raise ValueError("responseType must be a ClusterCommand or None")
if payload.must_use_timed_invoke and timedRequestTimeoutMs is None or timedRequestTimeoutMs == 0:
raise ValueError(
f"Command {payload.__class__} must use timed invoke, please specify a valid timedRequestTimeoutMs value")

handle = chip.native.GetLibraryHandle()
transaction = AsyncCommandTransaction(future, eventLoop, responseType)

payloadTLV = payload.ToTLV()
ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction))
return handle.pychip_CommandSender_SendCommand(ctypes.py_object(
transaction), device, commandPath.EndpointId, commandPath.ClusterId, commandPath.CommandId, payloadTLV, len(payloadTLV))
transaction), device, c_uint16(0 if timedRequestTimeoutMs is None else timedRequestTimeoutMs), commandPath.EndpointId, commandPath.ClusterId, commandPath.CommandId, payloadTLV, len(payloadTLV))


_deviceController = None
Expand All @@ -180,7 +185,7 @@ def Init(devCtrl):
setter = chip.native.NativeLibraryHandleMethodArguments(handle)

setter.Set('pychip_CommandSender_SendCommand',
c_uint32, [py_object, c_void_p, c_uint16, c_uint32, c_uint32, c_char_p, c_size_t])
c_uint32, [py_object, c_void_p, c_uint16, c_uint32, c_uint32, c_char_p, c_size_t, c_uint16])
setter.Set('pychip_CommandSender_InitCallbacks', None, [
_OnCommandSenderResponseCallbackFunct, _OnCommandSenderErrorCallbackFunct, _OnCommandSenderDoneCallbackFunct])

Expand Down
20 changes: 20 additions & 0 deletions src/controller/python/chip/clusters/Objects.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions src/controller/python/chip/clusters/attribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ class ReadClientCallback : public ReadClient::Callback

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_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device,
uint16_t timedWriteTimeoutMs, size_t n, ...);
chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, ReadClient ** pReadClient,
ReadClientCallback ** pCallback, DeviceProxy * device,
bool isSubscription, uint32_t minInterval, uint32_t maxInterval,
Expand Down Expand Up @@ -249,7 +250,8 @@ void pychip_ReadClient_InitCallbacks(OnReadAttributeDataCallback onReadAttribute
gOnReportEndCallback = onReportEndCallback;
}

chip::ChipError::StorageType pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device, size_t n, ...)
chip::ChipError::StorageType pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device,
uint16_t timedWriteTimeoutMs, size_t n, ...)
{
CHIP_ERROR err = CHIP_NO_ERROR;

Expand All @@ -259,7 +261,9 @@ chip::ChipError::StorageType pychip_WriteClient_WriteAttributes(void * appContex
va_list args;
va_start(args, n);

SuccessOrExit(err = app::InteractionModelEngine::GetInstance()->NewWriteClient(client, callback.get()));
SuccessOrExit(err = app::InteractionModelEngine::GetInstance()->NewWriteClient(
client, callback.get(),
timedWriteTimeoutMs != 0 ? Optional<uint16_t>(timedWriteTimeoutMs) : Optional<uint16_t>::Missing()));

{
for (size_t i = 0; i < n; i++)
Expand Down
23 changes: 10 additions & 13 deletions src/controller/python/chip/clusters/command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ using namespace chip::app;
using PyObject = void *;

extern "C" {
chip::ChipError::StorageType pychip_CommandSender_SendCommand(void * appContext, DeviceProxy * device, chip::EndpointId endpointId,
chip::ChipError::StorageType pychip_CommandSender_SendCommand(void * appContext, DeviceProxy * device,
uint16_t timedRequestTimeoutMs, chip::EndpointId endpointId,
chip::ClusterId clusterId, chip::CommandId commandId,
const uint8_t * payload, size_t length);
}
Expand Down Expand Up @@ -121,37 +122,33 @@ void pychip_CommandSender_InitCallbacks(OnCommandSenderResponseCallback onComman
gOnCommandSenderDoneCallback = onCommandSenderDoneCallback;
}

chip::ChipError::StorageType pychip_CommandSender_SendCommand(void * appContext, DeviceProxy * device, chip::EndpointId endpointId,
chip::ChipError::StorageType pychip_CommandSender_SendCommand(void * appContext, DeviceProxy * device,
uint16_t timedRequestTimeoutMs, chip::EndpointId endpointId,
chip::ClusterId clusterId, chip::CommandId commandId,
const uint8_t * payload, size_t length)
{
CHIP_ERROR err = CHIP_NO_ERROR;

std::unique_ptr<CommandSenderCallback> callback = std::make_unique<CommandSenderCallback>(appContext);
std::unique_ptr<CommandSender> sender = std::make_unique<CommandSender>(callback.get(), device->GetExchangeManager());
std::unique_ptr<CommandSender> sender = std::make_unique<CommandSender>(callback.get(), device->GetExchangeManager(),
/* is timed request */ timedRequestTimeoutMs != 0);

app::CommandPathParams cmdParams = { endpointId, /* group id */ 0, clusterId, commandId,
(app::CommandPathFlags::kEndpointIdValid) };

SuccessOrExit(err = sender->PrepareCommand(cmdParams));
SuccessOrExit(err = sender->PrepareCommand(cmdParams, false));

{
auto writer = sender->GetCommandDataIBTLVWriter();
TLV::TLVReader reader;
TLV::TLVType type;
VerifyOrExit(writer != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
reader.Init(payload, length);
reader.Next();
reader.EnterContainer(type);
while (reader.Next() == CHIP_NO_ERROR)
{
TLV::TLVReader tReader;
tReader.Init(reader);
writer->CopyElement(tReader);
}
SuccessOrExit(writer->CopyContainer(TLV::ContextTag(to_underlying(CommandDataIB::Tag::kData)), reader));
}

SuccessOrExit(err = sender->FinishCommand());
SuccessOrExit(err = sender->FinishCommand(timedRequestTimeoutMs != 0 ? Optional<uint16_t>(timedRequestTimeoutMs)
: Optional<uint16_t>::Missing()));
SuccessOrExit(err = device->SendCommands(sender.get()));

sender.release();
Expand Down
12 changes: 12 additions & 0 deletions src/controller/python/templates/python-cluster-Objects-py.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ class {{asUpperCamelCase name}}(Cluster):
{{/zcl_command_arguments}}
])

{{#if mustUseTimedInvoke}}
@ChipUtility.classproperty
def must_use_timed_invoke(cls) -> bool:
return True

{{/if}}
{{#zcl_command_arguments}}
{{ asLowerCamelCase label }}: '{{zapTypeToPythonClusterObjectType type ns=(asUpperCamelCase parent.parent.name)}}' = {{getPythonFieldDefault type ns=(asUpperCamelCase parent.parent.name)}}
{{/zcl_command_arguments}}
Expand All @@ -119,6 +125,12 @@ class {{asUpperCamelCase name}}(Cluster):
def attribute_id(cls) -> int:
return {{ asMEI manufacturerCode code }}

{{#if mustUseTimedWrite}}
@ChipUtility.classproperty
def must_use_timed_write(cls) -> bool:
return True

{{/if}}
@ChipUtility.classproperty
def attribute_type(cls) -> ClusterObjectFieldDescriptor:
{{#if entryType}}
Expand Down
Loading

0 comments on commit 8663209

Please sign in to comment.