Skip to content

Commit

Permalink
Allow chip-repl to send group commands (#25158)
Browse files Browse the repository at this point in the history
* Allow chip-repl to send group commands

With chip-repl now able to send we have updated the yamltests runner
that uses chip-repl to send group commands

* Address PR comments

* Restyle

* Address PR comments

* Restyle
  • Loading branch information
tehampson authored and pull[bot] committed Jul 3, 2024
1 parent b8fbd78 commit 4915905
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 4 deletions.
4 changes: 4 additions & 0 deletions src/controller/python/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ shared_library("ChipDeviceCtrl") {

sources += [ "chip/native/CommonStackInit.cpp" ]

defines = []

if (chip_controller) {
sources += [
"ChipCommissionableNodeController-ScriptBinding.cpp",
Expand All @@ -76,6 +78,8 @@ shared_library("ChipDeviceCtrl") {
"chip/native/PyChipError.cpp",
"chip/utils/DeviceProxyUtils.cpp",
]
defines += [ "CHIP_CONFIG_MAX_GROUPS_PER_FABRIC=50" ]
defines += [ "CHIP_CONFIG_MAX_GROUP_KEYS_PER_FABRIC=50" ]
} else {
sources += [
"chip/server/Options.cpp",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ PyChipError pychip_DeviceController_StackInit(Controller::Python::StorageAdapter
sGroupDataProvider.SetStorageDelegate(storageAdapter);
sGroupDataProvider.SetSessionKeystore(factoryParams.sessionKeystore);
PyReturnErrorOnFailure(ToPyChipError(sGroupDataProvider.Init()));
Credentials::SetGroupDataProvider(&sGroupDataProvider);
factoryParams.groupDataProvider = &sGroupDataProvider;

PyReturnErrorOnFailure(ToPyChipError(sPersistentStorageOpCertStore.Init(storageAdapter)));
Expand Down
15 changes: 15 additions & 0 deletions src/controller/python/OpCredsBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,21 @@ PyChipError pychip_OpCreds_AllocateController(OpCredsContext * context, chip::Co
return ToPyChipError(CHIP_NO_ERROR);
}

PyChipError pychip_OpCreds_InitGroupTestingData(chip::Controller::DeviceCommissioner * devCtrl)
{
VerifyOrReturnError(devCtrl != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));

uint8_t compressedFabricId[sizeof(uint64_t)] = { 0 };
chip::MutableByteSpan compressedFabricIdSpan(compressedFabricId);

CHIP_ERROR err = devCtrl->GetCompressedFabricIdBytes(compressedFabricIdSpan);
VerifyOrReturnError(err == CHIP_NO_ERROR, ToPyChipError(err));

err = chip::GroupTesting::InitData(&sGroupDataProvider, devCtrl->GetFabricIndex(), compressedFabricIdSpan);

return ToPyChipError(err);
}

PyChipError pychip_OpCreds_SetMaximallyLargeCertsUsed(OpCredsContext * context, bool enabled)
{
VerifyOrReturnError(context != nullptr && context->mAdapter != nullptr, ToPyChipError(CHIP_ERROR_INCORRECT_STATE));
Expand Down
25 changes: 25 additions & 0 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,18 @@ async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.
), payload, timedRequestTimeoutMs=timedRequestTimeoutMs, interactionTimeoutMs=interactionTimeoutMs, busyWaitMs=busyWaitMs).raise_on_error()
return await future

def SendGroupCommand(self, groupid: int, payload: ClusterObjects.ClusterCommand, busyWaitMs: typing.Union[None, int] = None):
'''
Send a group cluster-object encapsulated command to a group_id and get returned a future that can be awaited upon to get confirmation command was sent.
'''
self.CheckIsActive()

ClusterCommand.SendGroupCommand(
groupid, self.devCtrl, payload, busyWaitMs=busyWaitMs).raise_on_error()

# None is the expected return for sending group commands.
return None

async def WriteAttribute(self, nodeid: int, attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor, int]], timedRequestTimeoutMs: typing.Union[None, int] = None, interactionTimeoutMs: typing.Union[None, int] = None, busyWaitMs: typing.Union[None, int] = None):
'''
Write a list of attributes on a target node.
Expand Down Expand Up @@ -1289,6 +1301,15 @@ def IssueNOCChain(self, csr: Clusters.OperationalCredentials.Commands.CSRRespons
self.devCtrl, py_object(self), csr.NOCSRElements, len(csr.NOCSRElements), nodeId)
)

def InitGroupTestingData(self):
"""Populates the Device Controller's GroupDataProvider with known test group info and keys."""
self.CheckIsActive()

self._ChipStack.Call(
lambda: self._dmLib.pychip_OpCreds_InitGroupTestingData(
self.devCtrl)
).raise_on_error()

# ----- Private Members -----
def _InitLib(self):
if self._dmLib is None:
Expand Down Expand Up @@ -1455,6 +1476,10 @@ def _InitLib(self):
]
self._dmLib.pychip_DeviceController_IssueNOCChain.restype = PyChipError

self._dmLib.pychip_OpCreds_InitGroupTestingData.argtypes = [
c_void_p]
self._dmLib.pychip_OpCreds_InitGroupTestingData.restype = PyChipError

self._dmLib.pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback.argtypes = [
_IssueNOCChainCallbackPythonCallbackFunct]
self._dmLib.pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback.restype = None
Expand Down
18 changes: 18 additions & 0 deletions src/controller/python/chip/clusters/Command.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,22 @@ def SendCommand(future: Future, eventLoop, responseType: Type, device, commandPa
))


def SendGroupCommand(groupId: int, devCtrl: c_void_p, payload: ClusterCommand, busyWaitMs: Union[None, int] = None) -> PyChipError:
''' Send a cluster-object encapsulated group command to a device and does the following:
- None (on a successful response containing no data)
- Raises an exception if any errors are encountered.
'''
handle = chip.native.GetLibraryHandle()

payloadTLV = payload.ToTLV()
return builtins.chipStack.Call(
lambda: handle.pychip_CommandSender_SendGroupCommand(
c_uint16(groupId), devCtrl,
payload.cluster_id, payload.command_id, payloadTLV, len(payloadTLV),
ctypes.c_uint16(0 if busyWaitMs is None else busyWaitMs),
))


def Init():
handle = chip.native.GetLibraryHandle()

Expand All @@ -185,6 +201,8 @@ def Init():

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

Expand Down
46 changes: 46 additions & 0 deletions src/controller/python/chip/clusters/command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ PyChipError pychip_CommandSender_SendCommand(void * appContext, DeviceProxy * de
chip::EndpointId endpointId, chip::ClusterId clusterId, chip::CommandId commandId,
const uint8_t * payload, size_t length, uint16_t interactionTimeoutMs,
uint16_t busyWaitMs);

PyChipError pychip_CommandSender_SendGroupCommand(chip::GroupId groupId, chip::Controller::DeviceCommissioner * devCtrl,
chip::ClusterId clusterId, chip::CommandId commandId, const uint8_t * payload,
size_t length, uint16_t busyWaitMs);
}

namespace chip {
Expand Down Expand Up @@ -168,6 +172,48 @@ PyChipError pychip_CommandSender_SendCommand(void * appContext, DeviceProxy * de
usleep(busyWaitMs * 1000);
}

exit:
return ToPyChipError(err);
}

PyChipError pychip_CommandSender_SendGroupCommand(chip::GroupId groupId, chip::Controller::DeviceCommissioner * devCtrl,
chip::ClusterId clusterId, chip::CommandId commandId, const uint8_t * payload,
size_t length, uint16_t busyWaitMs)
{
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<CommandSender> sender = std::make_unique<CommandSender>(nullptr /* callback */, exchangeManager);

app::CommandPathParams cmdParams = { groupId, clusterId, commandId, (app::CommandPathFlags::kGroupIdValid) };

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

{
auto writer = sender->GetCommandDataIBTLVWriter();
TLV::TLVReader reader;
VerifyOrExit(writer != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
reader.Init(payload, length);
reader.Next();
SuccessOrExit(writer->CopyContainer(TLV::ContextTag(to_underlying(CommandDataIB::Tag::kFields)), reader));
}

SuccessOrExit(err = sender->FinishCommand(Optional<uint16_t>::Missing()));

{
auto fabricIndex = devCtrl->GetFabricIndex();

chip::Transport::OutgoingGroupSession session(groupId, fabricIndex);
SuccessOrExit(err = sender->SendGroupCommandRequest(chip::SessionHandle(session)));
}

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

exit:
return ToPyChipError(err);
}
Expand Down
20 changes: 16 additions & 4 deletions src/controller/python/chip/yaml/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ def __init__(self, test_step, cluster: str, context: _ExecutionContext):
self._expected_response_object = None
self._endpoint = test_step.endpoint
self._node_id = test_step.node_id
self._group_id = test_step.group_id

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')

command = context.data_model_lookup.get_command(self._cluster, self._command_name)

Expand All @@ -182,10 +187,15 @@ def __init__(self, test_step, cluster: str, context: _ExecutionContext):

def run_action(self, dev_ctrl: ChipDeviceController) -> _ActionResult:
try:
resp = asyncio.run(dev_ctrl.SendCommand(
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.SendGroupCommand(
self._group_id, self._request_object,
busyWaitMs=self._busy_wait_ms)
else:
resp = asyncio.run(dev_ctrl.SendCommand(
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)

Expand Down Expand Up @@ -736,6 +746,7 @@ def __init__(self, test_spec_definition, certificate_authority_manager, alpha_de
self._certificate_authority_manager = certificate_authority_manager
self._dev_ctrls = {}

alpha_dev_ctrl.InitGroupTestingData()
self._dev_ctrls['alpha'] = alpha_dev_ctrl

def _invoke_action_factory(self, test_step, cluster: str):
Expand Down Expand Up @@ -1014,6 +1025,7 @@ def _get_dev_ctrl(self, action: BaseAction):
fabric = certificate_authority.NewFabricAdmin(vendorId=0xFFF1,
fabricId=fabric_id)
dev_ctrl = fabric.NewController()
dev_ctrl.InitGroupTestingData()
self._dev_ctrls[action.identity] = dev_ctrl
else:
dev_ctrl = self._dev_ctrls['alpha']
Expand Down

0 comments on commit 4915905

Please sign in to comment.