Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TC-IDM-1.2 #27024

Merged
merged 11 commits into from
Jul 12, 2023
Next Next commit
TC-IDM-1.2
Adds automation for TC-IDM-1.2
Also Adds suppressResponse to CommandSender as well as a test-only
function to test timedResponse flag with no corresponding
TimedInvoke action + plumbing through the python layers
  • Loading branch information
cecille committed Jun 1, 2023
commit 3059aadf24bab4777cadc6425e5db9ef3e148c0d
1 change: 1 addition & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -508,6 +508,7 @@ jobs:
scripts/run_in_build_env.sh './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_decode 1" --script "src/python_testing/TC_ACE_1_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --int-arg PIXIT.ACE.APPENDPOINT:1 PIXIT.ACE.APPDEVTYPEID:0x0100 --string-arg PIXIT.ACE.APPCLUSTER:OnOff PIXIT.ACE.APPATTRIBUTE:OnOff"'
scripts/run_in_build_env.sh './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_decode 1" --script "src/python_testing/TC_ACE_1_3.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"'
scripts/run_in_build_env.sh './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_decode 1" --script "src/python_testing/TC_CGEN_2_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"'
scripts/run_in_build_env.sh './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_decode 1" --script "src/python_testing/TC_IDM_1_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"'
scripts/run_in_build_env.sh './scripts/tests/run_python_test.py --script "src/python_testing/TestMatterTestingSupport.py"'
- name: Uploading core files
uses: actions/upload-artifact@v3
2 changes: 2 additions & 0 deletions config/python/CHIPProjectConfig.h
Original file line number Diff line number Diff line change
@@ -59,4 +59,6 @@
#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY 1
#define CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE 1

#define CONFIG_BUILD_FOR_HOST_UNIT_TEST 1

#endif /* CHIPPROJECTCONFIG_H */
29 changes: 26 additions & 3 deletions src/app/CommandSender.cpp
Original file line number Diff line number Diff line change
@@ -32,9 +32,10 @@
namespace chip {
namespace app {

CommandSender::CommandSender(Callback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest) :
mExchangeCtx(*this), mpCallback(apCallback), mpExchangeMgr(apExchangeMgr), mSuppressResponse(false),
mTimedRequest(aIsTimedRequest)
CommandSender::CommandSender(Callback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest,
bool suppressResponse) :
cecille marked this conversation as resolved.
Show resolved Hide resolved
mExchangeCtx(*this),
mpCallback(apCallback), mpExchangeMgr(apExchangeMgr), mSuppressResponse(suppressResponse), mTimedRequest(aIsTimedRequest)
{}

CHIP_ERROR CommandSender::AllocateBuffer()
@@ -61,6 +62,28 @@ CHIP_ERROR CommandSender::AllocateBuffer()
return CHIP_NO_ERROR;
}

#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
CHIP_ERROR CommandSender::TestOnlyCommandSenderTimedRequestFlagWithNoTimedInvoke(const SessionHandle & session,
Optional<System::Clock::Timeout> timeout)
{
VerifyOrReturnError(mTimedRequest, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
cecille marked this conversation as resolved.
Show resolved Hide resolved

ReturnErrorOnFailure(Finalize(mPendingInvokeData));

// Create a new exchange context.
auto exchange = mpExchangeMgr->NewContext(session, this);
VerifyOrReturnError(exchange != nullptr, CHIP_ERROR_NO_MEMORY);

mExchangeCtx.Grab(exchange);
VerifyOrReturnError(!mExchangeCtx->IsGroupExchangeContext(), CHIP_ERROR_INVALID_MESSAGE_TYPE);

mExchangeCtx->SetResponseTimeout(timeout.ValueOr(session->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime)));

return SendInvokeRequest();
}
#endif

CHIP_ERROR CommandSender::SendCommandRequest(const SessionHandle & session, Optional<System::Clock::Timeout> timeout)
{
VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
11 changes: 10 additions & 1 deletion src/app/CommandSender.h
Original file line number Diff line number Diff line change
@@ -121,7 +121,8 @@ class CommandSender final : public Messaging::ExchangeDelegate
* If used in a groups setting, callbacks do not need to be passed.
* If callbacks are passed the only one that will be called in a group sesttings is the onDone
*/
CommandSender(Callback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest = false);
CommandSender(Callback * apCallback, Messaging::ExchangeManager * apExchangeMgr, bool aIsTimedRequest = false,
bool suppressResponse = false);
CHIP_ERROR PrepareCommand(const CommandPathParams & aCommandPathParams, bool aStartDataStruct = true);
CHIP_ERROR FinishCommand(bool aEndDataStruct = true);
TLV::TLVWriter * GetCommandDataIBTLVWriter();
@@ -169,6 +170,14 @@ class CommandSender final : public Messaging::ExchangeDelegate
mSuppressResponse = aSuppressResponse;
return AddRequestDataInternal(aCommandPath, aData, aTimedInvokeTimeoutMs);
}

/**
* Version of SendCommandRequest that sets the TimedRequest flag but does not send the TimedInvoke
* action.For use in tests only.
*/
CHIP_ERROR TestOnlyCommandSenderTimedRequestFlagWithNoTimedInvoke(const SessionHandle & session,
Optional<System::Clock::Timeout> timeout = NullOptional);

#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST

private:
24 changes: 22 additions & 2 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
@@ -745,7 +745,27 @@ def ComputeRoundTripTimeout(self, nodeid, upperLayerProcessingTimeoutMs: int = 0
device.deviceProxy, upperLayerProcessingTimeoutMs))
return res

async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.ClusterCommand, responseType=None, timedRequestTimeoutMs: typing.Union[None, int] = None, interactionTimeoutMs: typing.Union[None, int] = None, busyWaitMs: typing.Union[None, int] = None):
async def TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(self, nodeid: int, endpoint: int, payload: ClusterObjects.ClusterCommand, responseType=None):
'''
ONLY FOR TESTING - DO NOT USE FOR NORMAL OPERATION.

Please see SendCommand for description.
'''
self.CheckIsActive()

eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

device = self.GetConnectedDeviceSync(nodeid, timeoutMs=None)
ClusterCommand.TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(
future, eventLoop, responseType, device.deviceProxy, ClusterCommand.CommandPath(
EndpointId=endpoint,
ClusterId=payload.cluster_id,
CommandId=payload.command_id,
), payload).raise_on_error()
return await future

async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.ClusterCommand, responseType=None, timedRequestTimeoutMs: typing.Union[None, int] = None, interactionTimeoutMs: typing.Union[None, int] = None, busyWaitMs: typing.Union[None, int] = None, suppressResponse: typing.Union[None, bool] = 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
@@ -766,7 +786,7 @@ async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.
EndpointId=endpoint,
ClusterId=payload.cluster_id,
CommandId=payload.command_id,
), payload, timedRequestTimeoutMs=timedRequestTimeoutMs, interactionTimeoutMs=interactionTimeoutMs, busyWaitMs=busyWaitMs).raise_on_error()
), payload, timedRequestTimeoutMs=timedRequestTimeoutMs, interactionTimeoutMs=interactionTimeoutMs, busyWaitMs=busyWaitMs, suppressResponse=suppressResponse).raise_on_error()
return await future

def SendGroupCommand(self, groupid: int, payload: ClusterObjects.ClusterCommand, busyWaitMs: typing.Union[None, int] = None):
30 changes: 27 additions & 3 deletions src/controller/python/chip/clusters/Command.py
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@
import logging
import sys
from asyncio.futures import Future
from ctypes import CFUNCTYPE, c_char_p, c_size_t, c_uint8, c_uint16, c_uint32, c_void_p, py_object
from ctypes import CFUNCTYPE, c_char_p, c_size_t, c_uint8, c_uint16, c_uint32, c_void_p, py_object, c_bool
from dataclasses import dataclass
from typing import Type, Union

@@ -142,7 +142,28 @@ def _OnCommandSenderDoneCallback(closure):
ctypes.pythonapi.Py_DecRef(ctypes.py_object(closure))


def SendCommand(future: Future, eventLoop, responseType: Type, device, commandPath: CommandPath, payload: ClusterCommand, timedRequestTimeoutMs: Union[None, int] = None, interactionTimeoutMs: Union[None, int] = None, busyWaitMs: Union[None, int] = None) -> PyChipError:
def TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(future: Future, eventLoop, responseType, device, commandPath, payload):
''' ONLY TO BE USED FOR TEST: Sends the payload with a TimedRequest flag but no TimedInvoke transaction
'''
if (responseType is not None) and (not issubclass(responseType, ClusterCommand)):
raise ValueError("responseType must be a ClusterCommand or None")

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

payloadTLV = payload.ToTLV()
ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction))
return builtins.chipStack.Call(
lambda: handle.pychip_CommandSender_TestOnlySendCommandTimedRequestNoTimedInvoke(
ctypes.py_object(transaction), device,
commandPath.EndpointId, commandPath.ClusterId, commandPath.CommandId, payloadTLV, len(payloadTLV),
ctypes.c_uint16(0), # interactionTimeoutMs
ctypes.c_uint16(0), # busyWaitMs
ctypes.c_bool(False) # suppressResponse
))


def SendCommand(future: Future, eventLoop, responseType: Type, device, commandPath: CommandPath, payload: ClusterCommand, timedRequestTimeoutMs: Union[None, int] = None, interactionTimeoutMs: Union[None, int] = None, busyWaitMs: Union[None, int] = None, suppressResponse: Union[None, bool] = None) -> PyChipError:
''' 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)
@@ -172,6 +193,7 @@ def SendCommand(future: Future, eventLoop, responseType: Type, device, commandPa
commandPath.ClusterId, commandPath.CommandId, payloadTLV, len(payloadTLV),
ctypes.c_uint16(0 if interactionTimeoutMs is None else interactionTimeoutMs),
ctypes.c_uint16(0 if busyWaitMs is None else busyWaitMs),
ctypes.c_bool(False if suppressResponse is None else suppressResponse)
))


@@ -200,7 +222,9 @@ def Init():
setter = chip.native.NativeLibraryHandleMethodArguments(handle)

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])
PyChipError, [py_object, c_void_p, c_uint16, c_uint32, c_uint32, c_char_p, c_size_t, c_uint16, c_bool])
setter.Set('pychip_CommandSender_TestOnlySendCommandTimedRequestNoTimedInvoke',
PyChipError, [py_object, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_uint16, c_bool])
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, [
63 changes: 59 additions & 4 deletions src/controller/python/chip/clusters/command.cpp
Original file line number Diff line number Diff line change
@@ -36,7 +36,11 @@ extern "C" {
PyChipError 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, uint16_t interactionTimeoutMs,
uint16_t busyWaitMs);
uint16_t busyWaitMs, bool suppressResponse);

PyChipError pychip_CommandSender_TestOnlySendCommandTimedRequestNoTimedInvoke(
void * appContext, DeviceProxy * device, chip::EndpointId endpointId, chip::ClusterId clusterId, chip::CommandId commandId,
const uint8_t * payload, size_t length, uint16_t interactionTimeoutMs, uint16_t busyWaitMs, bool suppressResponse);

PyChipError pychip_CommandSender_SendGroupCommand(chip::GroupId groupId, chip::Controller::DeviceCommissioner * devCtrl,
chip::ClusterId clusterId, chip::CommandId commandId, const uint8_t * payload,
@@ -132,15 +136,16 @@ void pychip_CommandSender_InitCallbacks(OnCommandSenderResponseCallback onComman
PyChipError 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, uint16_t interactionTimeoutMs,
uint16_t busyWaitMs)
uint16_t busyWaitMs, bool suppressResponse)
{
CHIP_ERROR err = CHIP_NO_ERROR;

VerifyOrReturnError(device->GetSecureSession().HasValue(), ToPyChipError(CHIP_ERROR_MISSING_SECURE_SESSION));

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

app::CommandPathParams cmdParams = { endpointId, /* group id */ 0, clusterId, commandId,
(app::CommandPathFlags::kEndpointIdValid) };
@@ -176,6 +181,56 @@ PyChipError pychip_CommandSender_SendCommand(void * appContext, DeviceProxy * de
return ToPyChipError(err);
}

PyChipError pychip_CommandSender_TestOnlySendCommandTimedRequestNoTimedInvoke(
void * appContext, DeviceProxy * device, chip::EndpointId endpointId, chip::ClusterId clusterId, chip::CommandId commandId,
const uint8_t * payload, size_t length, uint16_t interactionTimeoutMs, uint16_t busyWaitMs, bool suppressResponse)
{
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST

CHIP_ERROR err = CHIP_NO_ERROR;

VerifyOrReturnError(device->GetSecureSession().HasValue(), ToPyChipError(CHIP_ERROR_MISSING_SECURE_SESSION));

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

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

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(err = writer->CopyContainer(TLV::ContextTag(CommandDataIB::Tag::kFields), reader));
}

SuccessOrExit(err = sender->FinishCommand(false));

SuccessOrExit(err = sender->TestOnlyCommandSenderTimedRequestFlagWithNoTimedInvoke(
device->GetSecureSession().Value(),
interactionTimeoutMs != 0 ? MakeOptional(System::Clock::Milliseconds32(interactionTimeoutMs))
: Optional<System::Clock::Timeout>::Missing()));

sender.release();
callback.release();

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

exit:
return ToPyChipError(err);
#else
return ToPyChipError(CHIP_ERROR_NOT_IMPLEMENTED);
#endif
}

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