Skip to content

Commit

Permalink
Add TransportPayloadCapability flag for GetConnectedDevices and bubble
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
pidarped committed Jul 22, 2024
1 parent 47cec4e commit cea4978
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 20 deletions.
47 changes: 44 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,12 @@ 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_IsSessionActive(chip::OperationalDeviceProxy * deviceProxy, bool * isSessionActive);
PyChipError pychip_CloseTCPConnectionWithPeer(chip::OperationalDeviceProxy * deviceProxy);
}

void * pychip_Storage_InitializeStorageAdapter(chip::Controller::Python::PyObject * context,
Expand Down Expand Up @@ -805,11 +812,45 @@ 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_IsSessionActive(chip::OperationalDeviceProxy * deviceProxy, bool * isSessionActive)
{
VerifyOrReturnError(deviceProxy->GetSecureSession().HasValue(), ToPyChipError(CHIP_ERROR_MISSING_SECURE_SESSION));
VerifyOrReturnError(isSessionActive != nullptr, ToPyChipError(CHIP_ERROR_INVALID_ARGUMENT));

*isSessionActive = 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
86 changes: 69 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):
MRP_PAYLOAD = 0
LARGE_PAYLOAD = 1
MRP_OR_TCP_PAYLOAD = 2


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

return bytes(buf)

@property
def sessionAllowsLargePayload(self) -> bool:
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 isSessionActive(self) -> bool:
self._dmLib.pychip_IsSessionActive.argtypes = [ctypes.c_void_p, POINTER(ctypes.c_bool)]
self._dmLib.pychip_IsSessionActive.restype = PyChipError

isSessionActive = ctypes.c_bool(False)

builtins.chipStack.Call(
lambda: self._dmLib.pychip_IsSessionActive(self._deviceProxy, pointer(isSessionActive))
).raise_on_error()

return isSessionActive.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 +950,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):
''' Gets an OperationalDeviceProxy or CommissioneeDeviceProxy for the specified Node.
nodeId: Target's Node ID
Expand Down Expand Up @@ -943,7 +987,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 +1019,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 +1065,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 +1169,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 +1190,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 +1204,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 +1233,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 +1262,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 +1285,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 +1444,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 +1505,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 +1536,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 +1597,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 +1620,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 +1668,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 +1816,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

0 comments on commit cea4978

Please sign in to comment.