Skip to content

Commit 246e969

Browse files
committed
Add TransportPayloadCapability flag for GetConnectedDevices and bubble
up the flag to the wrapper IM Python APIs. Add python script binding methods for LargePayload tests --to check if session allows large payload. --to close the underlying TCP connection. --to check if the session is active.
1 parent 138fb4f commit 246e969

File tree

3 files changed

+144
-22
lines changed

3 files changed

+144
-22
lines changed

src/controller/python/ChipDeviceController-ScriptBinding.cpp

+58-3
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ PyChipError pychip_DeviceCommissioner_CloseBleConnection(chip::Controller::Devic
213213
const char * pychip_Stack_StatusReportToString(uint32_t profileId, uint16_t statusCode);
214214

215215
PyChipError pychip_GetConnectedDeviceByNodeId(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeId,
216-
chip::Controller::Python::PyObject * context, DeviceAvailableFunc callback);
216+
chip::Controller::Python::PyObject * context, DeviceAvailableFunc callback,
217+
int transportPayloadCapability);
217218
PyChipError pychip_FreeOperationalDeviceProxy(chip::OperationalDeviceProxy * deviceProxy);
218219
PyChipError pychip_GetLocalSessionId(chip::OperationalDeviceProxy * deviceProxy, uint16_t * localSessionId);
219220
PyChipError pychip_GetNumSessionsToPeer(chip::OperationalDeviceProxy * deviceProxy, uint32_t * numSessions);
@@ -239,6 +240,13 @@ void pychip_Storage_ShutdownAdapter(chip::Controller::Python::StorageAdapter * s
239240
// ICD
240241
//
241242
void pychip_CheckInDelegate_SetOnCheckInCompleteCallback(PyChipCheckInDelegate::OnCheckInCompleteCallback * callback);
243+
244+
//
245+
// LargePayload and TCP
246+
PyChipError pychip_SessionAllowsLargePayload(chip::OperationalDeviceProxy * deviceProxy, bool * allowsLargePayload);
247+
PyChipError pychip_IsSessionOverTCPConnection(chip::OperationalDeviceProxy * deviceProxy, bool * isSessionOverTCP);
248+
PyChipError pychip_IsActiveSession(chip::OperationalDeviceProxy * deviceProxy, bool * isActiveSession);
249+
PyChipError pychip_CloseTCPConnectionWithPeer(chip::OperationalDeviceProxy * deviceProxy);
242250
}
243251

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

809817
PyChipError pychip_GetConnectedDeviceByNodeId(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeId,
810-
chip::Controller::Python::PyObject * context, DeviceAvailableFunc callback)
818+
chip::Controller::Python::PyObject * context, DeviceAvailableFunc callback,
819+
int transportPayloadCapability)
811820
{
812821
VerifyOrReturnError(devCtrl != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));
813822
auto * callbacks = new GetDeviceCallbacks(context, callback);
814-
return ToPyChipError(devCtrl->GetConnectedDevice(nodeId, &callbacks->mOnSuccess, &callbacks->mOnFailure));
823+
return ToPyChipError(devCtrl->GetConnectedDevice(nodeId, &callbacks->mOnSuccess, &callbacks->mOnFailure,
824+
static_cast<chip::TransportPayloadCapability>(transportPayloadCapability)));
825+
}
826+
827+
PyChipError pychip_SessionAllowsLargePayload(chip::OperationalDeviceProxy * deviceProxy, bool * allowsLargePayload)
828+
{
829+
VerifyOrReturnError(deviceProxy->GetSecureSession().HasValue(), ToPyChipError(CHIP_ERROR_MISSING_SECURE_SESSION));
830+
VerifyOrReturnError(allowsLargePayload != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));
831+
832+
*allowsLargePayload = deviceProxy->GetSecureSession().Value()->AsSecureSession()->AllowsLargePayload();
833+
834+
return ToPyChipError(CHIP_NO_ERROR);
835+
}
836+
837+
PyChipError pychip_IsSessionOverTCPConnection(chip::OperationalDeviceProxy * deviceProxy, bool * isSessionOverTCP)
838+
{
839+
VerifyOrReturnError(deviceProxy->GetSecureSession().HasValue(), ToPyChipError(CHIP_ERROR_MISSING_SECURE_SESSION));
840+
VerifyOrReturnError(isSessionOverTCP != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));
841+
842+
*isSessionOverTCP = deviceProxy->GetSecureSession().Value()->AsSecureSession()->GetTCPConnection() != nullptr;
843+
844+
return ToPyChipError(CHIP_NO_ERROR);
845+
}
846+
847+
PyChipError pychip_IsActiveSession(chip::OperationalDeviceProxy * deviceProxy, bool * isActiveSession)
848+
{
849+
VerifyOrReturnError(isActiveSession != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));
850+
851+
*isActiveSession = false;
852+
if (deviceProxy->GetSecureSession().HasValue())
853+
{
854+
*isActiveSession = deviceProxy->GetSecureSession().Value()->AsSecureSession()->IsActiveSession();
855+
}
856+
857+
return ToPyChipError(CHIP_NO_ERROR);
858+
}
859+
860+
PyChipError pychip_CloseTCPConnectionWithPeer(chip::OperationalDeviceProxy * deviceProxy)
861+
{
862+
VerifyOrReturnError(deviceProxy->GetSecureSession().HasValue(), ToPyChipError(CHIP_ERROR_MISSING_SECURE_SESSION));
863+
VerifyOrReturnError(deviceProxy->GetSecureSession().Value()->AsSecureSession()->AllowsLargePayload(),
864+
ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));
865+
866+
deviceProxy->GetExchangeManager()->GetSessionManager()->TCPDisconnect(
867+
deviceProxy->GetSecureSession().Value()->AsSecureSession()->GetTCPConnection(), /* shouldAbort = */ false);
868+
869+
return ToPyChipError(CHIP_NO_ERROR);
815870
}
816871

817872
PyChipError pychip_FreeOperationalDeviceProxy(chip::OperationalDeviceProxy * deviceProxy)

src/controller/python/chip/ChipDeviceCtrl.py

+82-17
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@
8484

8585
_ChipDeviceController_IterateDiscoveredCommissionableNodesFunct = CFUNCTYPE(None, c_char_p, c_size_t)
8686

87+
# Defines for the transport payload types to use to select the suitable
88+
# underlying transport of the session.
89+
# class TransportPayloadCapability(ctypes.c_int):
90+
91+
92+
class TransportPayloadCapability(ctypes.c_int):
93+
MRP_PAYLOAD = 0
94+
LARGE_PAYLOAD = 1
95+
MRP_OR_TCP_PAYLOAD = 2
96+
8797

8898
@dataclass
8999
class CommissioningParameters:
@@ -371,6 +381,53 @@ def attestationChallenge(self) -> bytes:
371381

372382
return bytes(buf)
373383

384+
@property
385+
def sessionAllowsLargePayload(self) -> bool:
386+
self._dmLib.pychip_SessionAllowsLargePayload.argtypes = [ctypes.c_void_p, POINTER(ctypes.c_bool)]
387+
self._dmLib.pychip_SessionAllowsLargePayload.restype = PyChipError
388+
389+
supportsLargePayload = ctypes.c_bool(False)
390+
391+
builtins.chipStack.Call(
392+
lambda: self._dmLib.pychip_SessionAllowsLargePayload(self._deviceProxy, pointer(supportsLargePayload))
393+
).raise_on_error()
394+
395+
return supportsLargePayload.value
396+
397+
@property
398+
def isSessionOverTCPConnection(self) -> bool:
399+
self._dmLib.pychip_IsSessionOverTCPConnection.argtypes = [ctypes.c_void_p, POINTER(ctypes.c_bool)]
400+
self._dmLib.pychip_IsSessionOverTCPConnection.restype = PyChipError
401+
402+
isSessionOverTCP = ctypes.c_bool(False)
403+
404+
builtins.chipStack.Call(
405+
lambda: self._dmLib.pychip_IsSessionOverTCPConnection(self._deviceProxy, pointer(isSessionOverTCP))
406+
).raise_on_error()
407+
408+
return isSessionOverTCP.value
409+
410+
@property
411+
def isActiveSession(self) -> bool:
412+
self._dmLib.pychip_IsActiveSession.argtypes = [ctypes.c_void_p, POINTER(ctypes.c_bool)]
413+
self._dmLib.pychip_IsActiveSession.restype = PyChipError
414+
415+
isActiveSession = ctypes.c_bool(False)
416+
417+
builtins.chipStack.Call(
418+
lambda: self._dmLib.pychip_IsActiveSession(self._deviceProxy, pointer(isActiveSession))
419+
).raise_on_error()
420+
421+
return isActiveSession.value
422+
423+
def closeTCPConnectionWithPeer(self):
424+
self._dmLib.pychip_CloseTCPConnectionWithPeer.argtypes = [ctypes.c_void_p]
425+
self._dmLib.pychip_CloseTCPConnectionWithPeer.restype = PyChipError
426+
427+
builtins.chipStack.Call(
428+
lambda: self._dmLib.pychip_CloseTCPConnectionWithPeer(self._deviceProxy)
429+
).raise_on_error()
430+
374431

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

909-
def GetConnectedDeviceSync(self, nodeid, allowPASE=True, timeoutMs: int = None):
966+
def GetConnectedDeviceSync(self, nodeid, allowPASE=True, timeoutMs: int = None, payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
910967
''' Gets an OperationalDeviceProxy or CommissioneeDeviceProxy for the specified Node.
911968
912969
nodeId: Target's Node ID
@@ -943,7 +1000,7 @@ def deviceAvailable(self, device, err):
9431000
closure = DeviceAvailableClosure()
9441001
ctypes.pythonapi.Py_IncRef(ctypes.py_object(closure))
9451002
self._ChipStack.Call(lambda: self._dmLib.pychip_GetConnectedDeviceByNodeId(
946-
self.devCtrl, nodeid, ctypes.py_object(closure), _DeviceAvailableCallback),
1003+
self.devCtrl, nodeid, ctypes.py_object(closure), _DeviceAvailableCallback, payloadCapability),
9471004
timeoutMs).raise_on_error()
9481005

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

978-
async def GetConnectedDevice(self, nodeid, allowPASE: bool = True, timeoutMs: int = None):
1035+
async def GetConnectedDevice(self, nodeid, allowPASE: bool = True, timeoutMs: int = None,
1036+
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
9791037
''' Gets an OperationalDeviceProxy or CommissioneeDeviceProxy for the specified Node.
9801038
9811039
nodeId: Target's Node ID
@@ -1020,7 +1078,7 @@ def deviceAvailable(self, device, err):
10201078
closure = DeviceAvailableClosure(eventLoop, future)
10211079
ctypes.pythonapi.Py_IncRef(ctypes.py_object(closure))
10221080
await self._ChipStack.CallAsync(lambda: self._dmLib.pychip_GetConnectedDeviceByNodeId(
1023-
self.devCtrl, nodeid, ctypes.py_object(closure), _DeviceAvailableCallback),
1081+
self.devCtrl, nodeid, ctypes.py_object(closure), _DeviceAvailableCallback, payloadCapability),
10241082
timeoutMs)
10251083

10261084
# The callback might have been received synchronously (during self._ChipStack.CallAsync()).
@@ -1124,7 +1182,8 @@ async def TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(self, nodeid: int
11241182
async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.ClusterCommand, responseType=None,
11251183
timedRequestTimeoutMs: typing.Union[None, int] = None,
11261184
interactionTimeoutMs: typing.Union[None, int] = None, busyWaitMs: typing.Union[None, int] = None,
1127-
suppressResponse: typing.Union[None, bool] = None):
1185+
suppressResponse: typing.Union[None, bool] = None,
1186+
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
11281187
'''
11291188
Send a cluster-object encapsulated command to a node and get returned a future that can be awaited upon to receive
11301189
the response. If a valid responseType is passed in, that will be used to de-serialize the object. If not,
@@ -1144,7 +1203,7 @@ async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.
11441203
eventLoop = asyncio.get_running_loop()
11451204
future = eventLoop.create_future()
11461205

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

1189-
device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
1249+
device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs, payloadCapability=payloadCapability)
11901250

11911251
res = await ClusterCommand.SendBatchCommands(
11921252
future, eventLoop, device.deviceProxy, commands,
@@ -1215,7 +1275,8 @@ def SendGroupCommand(self, groupid: int, payload: ClusterObjects.ClusterCommand,
12151275
async def WriteAttribute(self, nodeid: int,
12161276
attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]],
12171277
timedRequestTimeoutMs: typing.Union[None, int] = None,
1218-
interactionTimeoutMs: typing.Union[None, int] = None, busyWaitMs: typing.Union[None, int] = None):
1278+
interactionTimeoutMs: typing.Union[None, int] = None, busyWaitMs: typing.Union[None, int] = None,
1279+
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
12191280
'''
12201281
Write a list of attributes on a target node.
12211282
@@ -1237,7 +1298,7 @@ async def WriteAttribute(self, nodeid: int,
12371298
eventLoop = asyncio.get_running_loop()
12381299
future = eventLoop.create_future()
12391300

1240-
device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs)
1301+
device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs, payloadCapability=payloadCapability)
12411302

12421303
attrs = []
12431304
for v in attributes:
@@ -1396,7 +1457,8 @@ async def Read(self, nodeid: int, attributes: typing.List[typing.Union[
13961457
]] = None,
13971458
eventNumberFilter: typing.Optional[int] = None,
13981459
returnClusterObject: bool = False, reportInterval: typing.Tuple[int, int] = None,
1399-
fabricFiltered: bool = True, keepSubscriptions: bool = False, autoResubscribe: bool = True):
1460+
fabricFiltered: bool = True, keepSubscriptions: bool = False, autoResubscribe: bool = True,
1461+
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
14001462
'''
14011463
Read a list of attributes and/or events from a target node
14021464
@@ -1456,7 +1518,7 @@ async def Read(self, nodeid: int, attributes: typing.List[typing.Union[
14561518
eventLoop = asyncio.get_running_loop()
14571519
future = eventLoop.create_future()
14581520

1459-
device = await self.GetConnectedDevice(nodeid)
1521+
device = await self.GetConnectedDevice(nodeid, payloadCapability=payloadCapability)
14601522
attributePaths = [self._parseAttributePathTuple(
14611523
v) for v in attributes] if attributes else None
14621524
clusterDataVersionFilters = [self._parseDataVersionFilterTuple(
@@ -1487,7 +1549,8 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[
14871549
]], dataVersionFilters: typing.List[typing.Tuple[int, typing.Type[ClusterObjects.Cluster], int]] = None,
14881550
returnClusterObject: bool = False,
14891551
reportInterval: typing.Tuple[int, int] = None,
1490-
fabricFiltered: bool = True, keepSubscriptions: bool = False, autoResubscribe: bool = True):
1552+
fabricFiltered: bool = True, keepSubscriptions: bool = False, autoResubscribe: bool = True,
1553+
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
14911554
'''
14921555
Read a list of attributes from a target node, this is a wrapper of DeviceController.Read()
14931556
@@ -1547,7 +1610,8 @@ async def ReadAttribute(self, nodeid: int, attributes: typing.List[typing.Union[
15471610
reportInterval=reportInterval,
15481611
fabricFiltered=fabricFiltered,
15491612
keepSubscriptions=keepSubscriptions,
1550-
autoResubscribe=autoResubscribe)
1613+
autoResubscribe=autoResubscribe,
1614+
payloadCapability=payloadCapability)
15511615
if isinstance(res, ClusterAttribute.SubscriptionTransaction):
15521616
return res
15531617
else:
@@ -1569,7 +1633,8 @@ async def ReadEvent(self, nodeid: int, events: typing.List[typing.Union[
15691633
fabricFiltered: bool = True,
15701634
reportInterval: typing.Tuple[int, int] = None,
15711635
keepSubscriptions: bool = False,
1572-
autoResubscribe: bool = True):
1636+
autoResubscribe: bool = True,
1637+
payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD):
15731638
'''
15741639
Read a list of events from a target node, this is a wrapper of DeviceController.Read()
15751640
@@ -1616,7 +1681,7 @@ async def ReadEvent(self, nodeid: int, events: typing.List[typing.Union[
16161681
'''
16171682
res = await self.Read(nodeid=nodeid, events=events, eventNumberFilter=eventNumberFilter,
16181683
fabricFiltered=fabricFiltered, reportInterval=reportInterval, keepSubscriptions=keepSubscriptions,
1619-
autoResubscribe=autoResubscribe)
1684+
autoResubscribe=autoResubscribe, payloadCapability=payloadCapability)
16201685
if isinstance(res, ClusterAttribute.SubscriptionTransaction):
16211686
return res
16221687
else:
@@ -1764,7 +1829,7 @@ def _InitLib(self):
17641829
self._dmLib.pychip_ScriptDevicePairingDelegate_SetExpectingPairingComplete.restype = PyChipError
17651830

17661831
self._dmLib.pychip_GetConnectedDeviceByNodeId.argtypes = [
1767-
c_void_p, c_uint64, py_object, _DeviceAvailableCallbackFunct]
1832+
c_void_p, c_uint64, py_object, _DeviceAvailableCallbackFunct, c_int]
17681833
self._dmLib.pychip_GetConnectedDeviceByNodeId.restype = PyChipError
17691834

17701835
self._dmLib.pychip_FreeOperationalDeviceProxy.argtypes = [

src/python_testing/matter_testing_support.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -882,15 +882,17 @@ async def read_single_attribute_expect_error(
882882
async def send_single_cmd(
883883
self, cmd: Clusters.ClusterObjects.ClusterCommand,
884884
dev_ctrl: ChipDeviceCtrl = None, node_id: int = None, endpoint: int = None,
885-
timedRequestTimeoutMs: typing.Union[None, int] = None) -> object:
885+
timedRequestTimeoutMs: typing.Union[None, int] = None,
886+
payloadCapability: int = ChipDeviceCtrl.TransportPayloadCapability.MRP_PAYLOAD) -> object:
886887
if dev_ctrl is None:
887888
dev_ctrl = self.default_controller
888889
if node_id is None:
889890
node_id = self.dut_node_id
890891
if endpoint is None:
891892
endpoint = self.matter_test_config.endpoint
892893

893-
result = await dev_ctrl.SendCommand(nodeid=node_id, endpoint=endpoint, payload=cmd, timedRequestTimeoutMs=timedRequestTimeoutMs)
894+
result = await dev_ctrl.SendCommand(nodeid=node_id, endpoint=endpoint, payload=cmd, timedRequestTimeoutMs=timedRequestTimeoutMs,
895+
payloadCapability=payloadCapability)
894896
return result
895897

896898
async def send_test_event_triggers(self, eventTrigger: int, enableKey: bytes = None):

0 commit comments

Comments
 (0)