Skip to content

Commit

Permalink
Add WriteGroupAttributes to chip-repl (#25260)
Browse files Browse the repository at this point in the history
* Add WriteGroupAttributes to chip-repl

* Address PR comments
  • Loading branch information
tehampson authored and pull[bot] committed Nov 28, 2023
1 parent c8870d5 commit 64f60f2
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 10 deletions.
9 changes: 3 additions & 6 deletions scripts/tests/chiptest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,10 @@ def _GetSlowTests() -> Set[str]:
def _GetInDevelopmentTests() -> Set[str]:
"""Tests that fail in YAML for some reason.
Goal is for this set to become empty.
Currently this is empty and returns an empty set, but this is kept around in case
there are tests that are a work in progress.
"""
return {
# Needs group support in repl / The test is incorrect - see https://github.com/CHIP-Specifications/chip-test-plans/issues/2431 for details.
"Test_TC_SC_5_2.yaml",
"TestGroupMessaging.yaml", # Needs group support in repl
}
return set()


def _AllYamlTests():
Expand Down
28 changes: 28 additions & 0 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,34 @@ async def WriteAttribute(self, nodeid: int, attributes: typing.List[typing.Tuple
future, eventLoop, device.deviceProxy, attrs, timedRequestTimeoutMs=timedRequestTimeoutMs, interactionTimeoutMs=interactionTimeoutMs, busyWaitMs=busyWaitMs).raise_on_error()
return await future

def WriteGroupAttribute(self, groupid: int, attributes: typing.List[typing.Tuple[ClusterObjects.ClusterAttributeDescriptor, int]], busyWaitMs: typing.Union[None, int] = None):
'''
Write a list of attributes on a target group.
groupid: Group ID to send write attribute to.
attributes: A list of tuples of type (cluster-object, data-version). The data-version can be omitted.
E.g
(Clusters.UnitTesting.Attributes.XYZAttribute('hello'), 1) -- Group Write 'hello' with data version 1
'''
self.CheckIsActive()

attrs = []
invalid_endpoint = 0xFFFF
for v in attributes:
if len(v) == 2:
attrs.append(ClusterAttribute.AttributeWriteRequest(
invalid_endpoint, v[0], v[1], 1, v[0].value))
else:
attrs.append(ClusterAttribute.AttributeWriteRequest(
invalid_endpoint, v[0], 0, 0, v[0].value))

ClusterAttribute.WriteGroupAttributes(
groupid, self.devCtrl, attrs, busyWaitMs=busyWaitMs).raise_on_error()

# An empty list is the expected return for sending group write attribute.
return []

def _parseAttributePathTuple(self, pathTuple: typing.Union[
None, # Empty tuple, all wildcard
typing.Tuple[int], # Endpoint
Expand Down
27 changes: 27 additions & 0 deletions src/controller/python/chip/clusters/Attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,32 @@ def WriteAttributes(future: Future, eventLoop, device, attributes: List[Attribut
return res


def WriteGroupAttributes(groupId: int, devCtrl: c_void_p, attributes: List[AttributeWriteRequest], busyWaitMs: Union[None, int] = None) -> PyChipError:
handle = chip.native.GetLibraryHandle()

writeargs = []
for attr in attributes:
path = chip.interaction_model.AttributePathIBstruct.parse(
b'\x00' * chip.interaction_model.AttributePathIBstruct.sizeof())
path.EndpointId = attr.EndpointId
path.ClusterId = attr.Attribute.cluster_id
path.AttributeId = attr.Attribute.attribute_id
path.DataVersion = attr.DataVersion
path.HasDataVersion = attr.HasDataVersion
path = chip.interaction_model.AttributePathIBstruct.build(path)
tlv = attr.Attribute.ToTLV(None, attr.Data)
writeargs.append(ctypes.c_char_p(path))
writeargs.append(ctypes.c_char_p(bytes(tlv)))
writeargs.append(ctypes.c_int(len(tlv)))

return builtins.chipStack.Call(
lambda: handle.pychip_WriteClient_WriteGroupAttributes(
ctypes.c_uint16(groupId), devCtrl,
ctypes.c_uint16(0 if busyWaitMs is None else busyWaitMs),
ctypes.c_size_t(len(attributes)), *writeargs)
)


# This struct matches the PyReadAttributeParams in attribute.cpp, for passing various params together.
_ReadParams = construct.Struct(
"MinInterval" / construct.Int32ul,
Expand Down Expand Up @@ -1081,6 +1107,7 @@ def Init():
setter = chip.native.NativeLibraryHandleMethodArguments(handle)

handle.pychip_WriteClient_WriteAttributes.restype = PyChipError
handle.pychip_WriteClient_WriteGroupAttributes.restype = PyChipError
setter.Set('pychip_WriteClient_InitCallbacks', None, [
_OnWriteResponseCallbackFunct, _OnWriteErrorCallbackFunct, _OnWriteDoneCallbackFunct])
handle.pychip_ReadClient_Read.restype = PyChipError
Expand Down
61 changes: 61 additions & 0 deletions src/controller/python/chip/clusters/attribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <app/InteractionModelEngine.h>
#include <app/ReadClient.h>
#include <app/WriteClient.h>
#include <controller/CHIPDeviceController.h>
#include <controller/python/chip/native/PyChipError.h>
#include <lib/support/CodeUtils.h>

Expand Down Expand Up @@ -247,6 +248,8 @@ struct __attribute__((packed)) PyReadAttributeParams
// Encodes n attribute write requests, follows 3 * n arguments, in the (AttributeWritePath*=void *, uint8_t*, size_t) order.
PyChipError pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device, uint16_t timedWriteTimeoutMs,
uint16_t interactionTimeoutMs, uint16_t busyWaitMs, size_t n, ...);
PyChipError pychip_WriteClient_WriteGroupAttributes(chip::GroupId groupId, chip::Controller::DeviceCommissioner * devCtrl,
uint16_t busyWaitMs, size_t n, ...);
PyChipError pychip_ReadClient_ReadAttributes(void * appContext, ReadClient ** pReadClient, ReadClientCallback ** pCallback,
DeviceProxy * device, uint8_t * readParamsBuf, size_t n, size_t total, ...);
}
Expand Down Expand Up @@ -380,6 +383,64 @@ PyChipError pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy *
return ToPyChipError(err);
}

PyChipError pychip_WriteClient_WriteGroupAttributes(chip::GroupId groupId, chip::Controller::DeviceCommissioner * devCtrl,
uint16_t busyWaitMs, size_t n, ...)
{
CHIP_ERROR err = CHIP_NO_ERROR;

chip::Messaging::ExchangeManager * exchangeManager = chip::app::InteractionModelEngine::GetInstance()->GetExchangeManager();
VerifyOrReturnError(exchangeManager != nullptr, ToPyChipError(CHIP_ERROR_INCORRECT_STATE));

std::unique_ptr<WriteClient> client = std::make_unique<WriteClient>(
app::InteractionModelEngine::GetInstance()->GetExchangeManager(), nullptr /* callback */, Optional<uint16_t>::Missing());

va_list args;
va_start(args, n);

{
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<uint8_t *>(tlv);

TLV::TLVReader reader;
reader.Init(tlvBuffer, static_cast<uint32_t>(length));
reader.Next();
Optional<DataVersion> dataVersion;
if (pathObj.hasDataVersion == 1)
{
dataVersion.SetValue(pathObj.dataVersion);
}
// Using kInvalidEndpointId as that used when sending group write requests.
SuccessOrExit(
err = client->PutPreencodedAttribute(
chip::app::ConcreteDataAttributePath(kInvalidEndpointId, pathObj.clusterId, pathObj.attributeId, dataVersion),
reader));
}
}

{
auto fabricIndex = devCtrl->GetFabricIndex();

chip::Transport::OutgoingGroupSession session(groupId, fabricIndex);
SuccessOrExit(err = client->SendWriteRequest(chip::SessionHandle(session), System::Clock::kZero));
}

if (busyWaitMs)
{
usleep(busyWaitMs * 1000);
}

exit:
va_end(args);
return ToPyChipError(err);
}

void pychip_ReadClient_Abort(ReadClient * apReadClient, ReadClientCallback * apCallback)
{
VerifyOrDie(apReadClient != nullptr);
Expand Down
22 changes: 18 additions & 4 deletions src/controller/python/chip/yaml/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,13 @@ def __init__(self, test_step, cluster: str, context: _ExecutionContext):
self._endpoint = test_step.endpoint
self._interation_timeout_ms = test_step.timed_interaction_timeout_ms
self._node_id = test_step.node_id
self._group_id = test_step.group_id
self._request_object = None

if self._node_id is None and self._group_id is None:
raise UnexpectedParsingError(
'Both node_id and group_id are None, at least one needs to be provided')

attribute = context.data_model_lookup.get_attribute(
self._cluster, self._attribute_name)
if attribute is None:
Expand All @@ -575,12 +580,21 @@ def __init__(self, test_step, cluster: str, context: _ExecutionContext):

def run_action(self, dev_ctrl: ChipDeviceController) -> _ActionResult:
try:
resp = asyncio.run(
dev_ctrl.WriteAttribute(self._node_id, [(self._endpoint, self._request_object)],
timedRequestTimeoutMs=self._interation_timeout_ms,
busyWaitMs=self._busy_wait_ms))
if self._group_id:
resp = dev_ctrl.WriteGroupAttribute(self._group_id, [(self._request_object,)],
busyWaitMs=self._busy_wait_ms)
else:
resp = asyncio.run(
dev_ctrl.WriteAttribute(self._node_id, [(self._endpoint, self._request_object)],
timedRequestTimeoutMs=self._interation_timeout_ms,
busyWaitMs=self._busy_wait_ms))
except chip.interaction_model.InteractionModelError as error:
return _ActionResult(status=_ActionStatus.ERROR, response=error)

# Group writes are expected to have no response upon success.
if self._group_id and len(resp) == 0:
return _ActionResult(status=_ActionStatus.SUCCESS, response=None)

if len(resp) == 1 and isinstance(resp[0], AttributeStatus):
if resp[0].Status == chip.interaction_model.Status.Success:
return _ActionResult(status=_ActionStatus.SUCCESS, response=None)
Expand Down

0 comments on commit 64f60f2

Please sign in to comment.