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

Add TransportPayloadCapability flag for GetConnectedDevices() API in ChipDeviceCtrl.py and Script bindings #34450

Merged
merged 1 commit into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions src/controller/python/ChipDeviceController-ScriptBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ PyChipError pychip_DeviceCommissioner_CloseBleConnection(chip::Controller::Devic
const char * pychip_Stack_StatusReportToString(uint32_t profileId, uint16_t statusCode);

PyChipError pychip_GetConnectedDeviceByNodeId(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeId,
chip::Controller::Python::PyObject * context, DeviceAvailableFunc callback);
chip::Controller::Python::PyObject * context, DeviceAvailableFunc callback,
int transportPayloadCapability);
PyChipError pychip_FreeOperationalDeviceProxy(chip::OperationalDeviceProxy * deviceProxy);
PyChipError pychip_GetLocalSessionId(chip::OperationalDeviceProxy * deviceProxy, uint16_t * localSessionId);
PyChipError pychip_GetNumSessionsToPeer(chip::OperationalDeviceProxy * deviceProxy, uint32_t * numSessions);
Expand All @@ -239,6 +240,13 @@ void pychip_Storage_ShutdownAdapter(chip::Controller::Python::StorageAdapter * s
// ICD
//
void pychip_CheckInDelegate_SetOnCheckInCompleteCallback(PyChipCheckInDelegate::OnCheckInCompleteCallback * callback);

//
// LargePayload and TCP
PyChipError pychip_SessionAllowsLargePayload(chip::OperationalDeviceProxy * deviceProxy, bool * allowsLargePayload);
PyChipError pychip_IsSessionOverTCPConnection(chip::OperationalDeviceProxy * deviceProxy, bool * isSessionOverTCP);
PyChipError pychip_IsActiveSession(chip::OperationalDeviceProxy * deviceProxy, bool * isActiveSession);
PyChipError pychip_CloseTCPConnectionWithPeer(chip::OperationalDeviceProxy * deviceProxy);
}

void * pychip_Storage_InitializeStorageAdapter(chip::Controller::Python::PyObject * context,
Expand Down Expand Up @@ -807,11 +815,58 @@ struct GetDeviceCallbacks
} // anonymous namespace

PyChipError pychip_GetConnectedDeviceByNodeId(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeId,
chip::Controller::Python::PyObject * context, DeviceAvailableFunc callback)
chip::Controller::Python::PyObject * context, DeviceAvailableFunc callback,
int transportPayloadCapability)
{
VerifyOrReturnError(devCtrl != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));
auto * callbacks = new GetDeviceCallbacks(context, callback);
return ToPyChipError(devCtrl->GetConnectedDevice(nodeId, &callbacks->mOnSuccess, &callbacks->mOnFailure));
return ToPyChipError(devCtrl->GetConnectedDevice(nodeId, &callbacks->mOnSuccess, &callbacks->mOnFailure,
static_cast<chip::TransportPayloadCapability>(transportPayloadCapability)));
}

PyChipError pychip_SessionAllowsLargePayload(chip::OperationalDeviceProxy * deviceProxy, bool * allowsLargePayload)
{
VerifyOrReturnError(deviceProxy->GetSecureSession().HasValue(), ToPyChipError(CHIP_ERROR_MISSING_SECURE_SESSION));
VerifyOrReturnError(allowsLargePayload != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));

*allowsLargePayload = deviceProxy->GetSecureSession().Value()->AsSecureSession()->AllowsLargePayload();

return ToPyChipError(CHIP_NO_ERROR);
}

PyChipError pychip_IsSessionOverTCPConnection(chip::OperationalDeviceProxy * deviceProxy, bool * isSessionOverTCP)
{
VerifyOrReturnError(deviceProxy->GetSecureSession().HasValue(), ToPyChipError(CHIP_ERROR_MISSING_SECURE_SESSION));
VerifyOrReturnError(isSessionOverTCP != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));

*isSessionOverTCP = deviceProxy->GetSecureSession().Value()->AsSecureSession()->GetTCPConnection() != nullptr;

return ToPyChipError(CHIP_NO_ERROR);
}

PyChipError pychip_IsActiveSession(chip::OperationalDeviceProxy * deviceProxy, bool * isActiveSession)
{
VerifyOrReturnError(isActiveSession != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));

*isActiveSession = false;
if (deviceProxy->GetSecureSession().HasValue())
{
*isActiveSession = deviceProxy->GetSecureSession().Value()->AsSecureSession()->IsActiveSession();
}

return ToPyChipError(CHIP_NO_ERROR);
}

PyChipError pychip_CloseTCPConnectionWithPeer(chip::OperationalDeviceProxy * deviceProxy)
{
VerifyOrReturnError(deviceProxy->GetSecureSession().HasValue(), ToPyChipError(CHIP_ERROR_MISSING_SECURE_SESSION));
VerifyOrReturnError(deviceProxy->GetSecureSession().Value()->AsSecureSession()->AllowsLargePayload(),
ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));

deviceProxy->GetExchangeManager()->GetSessionManager()->TCPDisconnect(
deviceProxy->GetSecureSession().Value()->AsSecureSession()->GetTCPConnection(), /* shouldAbort = */ false);

return ToPyChipError(CHIP_NO_ERROR);
}

PyChipError pychip_FreeOperationalDeviceProxy(chip::OperationalDeviceProxy * deviceProxy)
Expand Down
99 changes: 82 additions & 17 deletions src/controller/python/chip/ChipDeviceCtrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@

_ChipDeviceController_IterateDiscoveredCommissionableNodesFunct = CFUNCTYPE(None, c_char_p, c_size_t)

# Defines for the transport payload types to use to select the suitable
# underlying transport of the session.
# class TransportPayloadCapability(ctypes.c_int):


class TransportPayloadCapability(ctypes.c_int):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the direct equivalent of a C++ enum? If so please documenting that.
Other than that it would be good to use either "LARGE" or "TCP" consistently.
Also should this be a bit mask with one bit each for MRP and TCP?

MRP_PAYLOAD = 0
LARGE_PAYLOAD = 1
MRP_OR_TCP_PAYLOAD = 2


@dataclass
class CommissioningParameters:
Expand Down Expand Up @@ -371,6 +381,53 @@ def attestationChallenge(self) -> bytes:

return bytes(buf)

@property
def sessionAllowsLargePayload(self) -> bool:
pidarped marked this conversation as resolved.
Show resolved Hide resolved
self._dmLib.pychip_SessionAllowsLargePayload.argtypes = [ctypes.c_void_p, POINTER(ctypes.c_bool)]
self._dmLib.pychip_SessionAllowsLargePayload.restype = PyChipError

supportsLargePayload = ctypes.c_bool(False)

builtins.chipStack.Call(
lambda: self._dmLib.pychip_SessionAllowsLargePayload(self._deviceProxy, pointer(supportsLargePayload))
).raise_on_error()

return supportsLargePayload.value

@property
def isSessionOverTCPConnection(self) -> bool:
self._dmLib.pychip_IsSessionOverTCPConnection.argtypes = [ctypes.c_void_p, POINTER(ctypes.c_bool)]
self._dmLib.pychip_IsSessionOverTCPConnection.restype = PyChipError

isSessionOverTCP = ctypes.c_bool(False)

builtins.chipStack.Call(
lambda: self._dmLib.pychip_IsSessionOverTCPConnection(self._deviceProxy, pointer(isSessionOverTCP))
).raise_on_error()

return isSessionOverTCP.value

@property
def isActiveSession(self) -> bool:
self._dmLib.pychip_IsActiveSession.argtypes = [ctypes.c_void_p, POINTER(ctypes.c_bool)]
self._dmLib.pychip_IsActiveSession.restype = PyChipError

isActiveSession = ctypes.c_bool(False)

builtins.chipStack.Call(
lambda: self._dmLib.pychip_IsActiveSession(self._deviceProxy, pointer(isActiveSession))
).raise_on_error()

return isActiveSession.value

def closeTCPConnectionWithPeer(self):
self._dmLib.pychip_CloseTCPConnectionWithPeer.argtypes = [ctypes.c_void_p]
self._dmLib.pychip_CloseTCPConnectionWithPeer.restype = PyChipError

builtins.chipStack.Call(
lambda: self._dmLib.pychip_CloseTCPConnectionWithPeer(self._deviceProxy)
).raise_on_error()


DiscoveryFilterType = discovery.FilterType
DiscoveryType = discovery.DiscoveryType
Expand Down Expand Up @@ -906,7 +963,7 @@ async def FindOrEstablishPASESession(self, setupCode: str, nodeid: int, timeoutM
if res.is_success:
return DeviceProxyWrapper(returnDevice, DeviceProxyWrapper.DeviceProxyType.COMMISSIONEE, self._dmLib)

def GetConnectedDeviceSync(self, nodeid, allowPASE=True, timeoutMs: int = None):
def GetConnectedDeviceSync(self, nodeid, allowPASE=True, timeoutMs: int = None, payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
pidarped marked this conversation as resolved.
Show resolved Hide resolved
''' Gets an OperationalDeviceProxy or CommissioneeDeviceProxy for the specified Node.

nodeId: Target's Node ID
Expand Down Expand Up @@ -943,7 +1000,7 @@ def deviceAvailable(self, device, err):
closure = DeviceAvailableClosure()
ctypes.pythonapi.Py_IncRef(ctypes.py_object(closure))
self._ChipStack.Call(lambda: self._dmLib.pychip_GetConnectedDeviceByNodeId(
self.devCtrl, nodeid, ctypes.py_object(closure), _DeviceAvailableCallback),
self.devCtrl, nodeid, ctypes.py_object(closure), _DeviceAvailableCallback, payloadCapability),
timeoutMs).raise_on_error()

# The callback might have been received synchronously (during self._ChipStack.Call()).
Expand Down Expand Up @@ -975,7 +1032,8 @@ async def WaitForActive(self, nodeid, *, timeoutSeconds=30.0, stayActiveDuration
await WaitForCheckIn(ScopedNodeId(nodeid, self._fabricIndex), timeoutSeconds=timeoutSeconds)
return await self.SendCommand(nodeid, 0, Clusters.IcdManagement.Commands.StayActiveRequest(stayActiveDuration=stayActiveDurationMs))

async def GetConnectedDevice(self, nodeid, allowPASE: bool = True, timeoutMs: int = None):
async def GetConnectedDevice(self, nodeid, allowPASE: bool = True, timeoutMs: int = None,
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
''' Gets an OperationalDeviceProxy or CommissioneeDeviceProxy for the specified Node.

nodeId: Target's Node ID
Expand Down Expand Up @@ -1020,7 +1078,7 @@ def deviceAvailable(self, device, err):
closure = DeviceAvailableClosure(eventLoop, future)
ctypes.pythonapi.Py_IncRef(ctypes.py_object(closure))
await self._ChipStack.CallAsync(lambda: self._dmLib.pychip_GetConnectedDeviceByNodeId(
self.devCtrl, nodeid, ctypes.py_object(closure), _DeviceAvailableCallback),
self.devCtrl, nodeid, ctypes.py_object(closure), _DeviceAvailableCallback, payloadCapability),
timeoutMs)

# The callback might have been received synchronously (during self._ChipStack.CallAsync()).
Expand Down Expand Up @@ -1124,7 +1182,8 @@ async def TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(self, nodeid: int
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):
suppressResponse: typing.Union[None, bool] = None,
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
'''
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 de-serialize the object. If not,
Expand All @@ -1144,7 +1203,7 @@ async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.
eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs, payloadCapability=payloadCapability)
res = await ClusterCommand.SendCommand(
future, eventLoop, responseType, device.deviceProxy, ClusterCommand.CommandPath(
EndpointId=endpoint,
Expand All @@ -1158,7 +1217,8 @@ async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.
async def SendBatchCommands(self, nodeid: int, commands: typing.List[ClusterCommand.InvokeRequestInfo],
timedRequestTimeoutMs: typing.Optional[int] = None,
interactionTimeoutMs: typing.Optional[int] = None, busyWaitMs: typing.Optional[int] = None,
suppressResponse: typing.Optional[bool] = None):
suppressResponse: typing.Optional[bool] = None,
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
'''
Send a batch of cluster-object encapsulated commands to a node and get returned a future that can be awaited upon to receive
the responses. If a valid responseType is passed in, that will be used to de-serialize the object. If not,
Expand Down Expand Up @@ -1186,7 +1246,7 @@ async def SendBatchCommands(self, nodeid: int, commands: typing.List[ClusterComm
eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs, payloadCapability=payloadCapability)

res = await ClusterCommand.SendBatchCommands(
future, eventLoop, device.deviceProxy, commands,
Expand Down Expand Up @@ -1215,7 +1275,8 @@ def SendGroupCommand(self, groupid: int, payload: ClusterObjects.ClusterCommand,
async def WriteAttribute(self, nodeid: int,
attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]],
timedRequestTimeoutMs: typing.Union[None, int] = None,
interactionTimeoutMs: typing.Union[None, int] = None, busyWaitMs: typing.Union[None, int] = None):
interactionTimeoutMs: typing.Union[None, int] = None, busyWaitMs: typing.Union[None, int] = None,
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
'''
Write a list of attributes on a target node.

Expand All @@ -1237,7 +1298,7 @@ async def WriteAttribute(self, nodeid: int,
eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs, payloadCapability=payloadCapability)

attrs = []
for v in attributes:
Expand Down Expand Up @@ -1396,7 +1457,8 @@ async def Read(self, nodeid: int, attributes: typing.List[typing.Union[
]] = None,
eventNumberFilter: typing.Optional[int] = None,
returnClusterObject: bool = False, reportInterval: typing.Tuple[int, int] = None,
fabricFiltered: bool = True, keepSubscriptions: bool = False, autoResubscribe: bool = True):
fabricFiltered: bool = True, keepSubscriptions: bool = False, autoResubscribe: bool = True,
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
'''
Read a list of attributes and/or events from a target node

Expand Down Expand Up @@ -1456,7 +1518,7 @@ async def Read(self, nodeid: int, attributes: typing.List[typing.Union[
eventLoop = asyncio.get_running_loop()
future = eventLoop.create_future()

device = await self.GetConnectedDevice(nodeid)
device = await self.GetConnectedDevice(nodeid, payloadCapability=payloadCapability)
attributePaths = [self._parseAttributePathTuple(
v) for v in attributes] if attributes else None
clusterDataVersionFilters = [self._parseDataVersionFilterTuple(
Expand Down Expand Up @@ -1487,7 +1549,8 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[
]], dataVersionFilters: typing.List[typing.Tuple[int, typing.Type[ClusterObjects.Cluster], int]] = None,
returnClusterObject: bool = False,
reportInterval: typing.Tuple[int, int] = None,
fabricFiltered: bool = True, keepSubscriptions: bool = False, autoResubscribe: bool = True):
fabricFiltered: bool = True, keepSubscriptions: bool = False, autoResubscribe: bool = True,
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
'''
Read a list of attributes from a target node, this is a wrapper of DeviceController.Read()

Expand Down Expand Up @@ -1547,7 +1610,8 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[
reportInterval=reportInterval,
fabricFiltered=fabricFiltered,
keepSubscriptions=keepSubscriptions,
autoResubscribe=autoResubscribe)
autoResubscribe=autoResubscribe,
payloadCapability=payloadCapability)
if isinstance(res, ClusterAttribute.SubscriptionTransaction):
return res
else:
Expand All @@ -1569,7 +1633,8 @@ async def ReadEvent(self, nodeid: int, events: typing.List[typing.Union[
fabricFiltered: bool = True,
reportInterval: typing.Tuple[int, int] = None,
keepSubscriptions: bool = False,
autoResubscribe: bool = True):
autoResubscribe: bool = True,
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
'''
Read a list of events from a target node, this is a wrapper of DeviceController.Read()

Expand Down Expand Up @@ -1616,7 +1681,7 @@ async def ReadEvent(self, nodeid: int, events: typing.List[typing.Union[
'''
res = await self.Read(nodeid=nodeid, events=events, eventNumberFilter=eventNumberFilter,
fabricFiltered=fabricFiltered, reportInterval=reportInterval, keepSubscriptions=keepSubscriptions,
autoResubscribe=autoResubscribe)
autoResubscribe=autoResubscribe, payloadCapability=payloadCapability)
if isinstance(res, ClusterAttribute.SubscriptionTransaction):
return res
else:
Expand Down Expand Up @@ -1764,7 +1829,7 @@ def _InitLib(self):
self._dmLib.pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete.restype = PyChipError

self._dmLib.pychip_GetConnectedDeviceByNodeId.argtypes = [
c_void_p, c_uint64, py_object, _DeviceAvailableCallbackFunct]
c_void_p, c_uint64, py_object, _DeviceAvailableCallbackFunct, c_int]
self._dmLib.pychip_GetConnectedDeviceByNodeId.restype = PyChipError

self._dmLib.pychip_FreeOperationalDeviceProxy.argtypes = [
Expand Down
6 changes: 4 additions & 2 deletions src/python_testing/matter_testing_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -882,15 +882,17 @@ async def read_single_attribute_expect_error(
async def send_single_cmd(
self, cmd: Clusters.ClusterObjects.ClusterCommand,
dev_ctrl: ChipDeviceCtrl = None, node_id: int = None, endpoint: int = None,
timedRequestTimeoutMs: typing.Union[None, int] = None) -> object:
timedRequestTimeoutMs: typing.Union[None, int] = None,
payloadCapability: int = ChipDeviceCtrl.TransportPayloadCapability.MRP_PAYLOAD) -> object:
if dev_ctrl is None:
dev_ctrl = self.default_controller
if node_id is None:
node_id = self.dut_node_id
if endpoint is None:
endpoint = self.matter_test_config.endpoint

result = await dev_ctrl.SendCommand(nodeid=node_id, endpoint=endpoint, payload=cmd, timedRequestTimeoutMs=timedRequestTimeoutMs)
result = await dev_ctrl.SendCommand(nodeid=node_id, endpoint=endpoint, payload=cmd, timedRequestTimeoutMs=timedRequestTimeoutMs,
payloadCapability=payloadCapability)
return result

async def send_test_event_triggers(self, eventTrigger: int, enableKey: bytes = None):
Expand Down
Loading