-
Notifications
You must be signed in to change notification settings - Fork 2.3k
[Create Test] Creating IDM_3_2 python3 test module #41066
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
base: master
Are you sure you want to change the base?
Changes from 20 commits
67f7371
8f46e31
42e50eb
4c4a4b5
6c3d0aa
669a2a7
d072b90
3ddd434
605c6cc
dcd0548
be9e579
b3d8e2d
bc1626f
8439c8a
7210fae
55716ed
282ebdb
f688582
9a7ca6f
d1f78ea
d095563
cb26e88
b4e13fb
c292e5c
474e423
30de357
6786a1b
ec8efb6
1de2422
92278a2
d0d7e7b
f3bc13c
1dcc960
2a482dc
8c63503
1c4bf3a
0dbbb1f
702e0fd
a9fa14c
d80c2c5
80c4bf4
f4416a2
4a14bc9
6189f8a
ad4c031
6a364bc
d264c6e
b6eb409
6eba285
bde6d01
13ea73f
3ae33ff
79fc25b
4b20b2e
4c5ecf3
3c0d450
e4292a7
7ea0969
d9b4f30
a9356fc
49c1782
b896bb9
610043b
48b1ff7
b9463af
7eeb60c
200bd11
be4547d
1210325
431a12d
2c38877
f3f405e
c13545d
dc88770
d10727e
7d3f430
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
bzbarsky-apple marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1582,6 +1582,48 @@ async def TestOnlySendCommandTimedRequestFlagWithNoTimedInvoke(self, nodeid: int | |
| ), payload).raise_on_error() | ||
| return await future | ||
|
|
||
| def _prepare_write_attribute_requests(self, attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]]) -> typing.List[ClusterAttribute.AttributeWriteRequest]: | ||
| """Helper method to prepare attribute write requests.""" | ||
| attrs = [] | ||
| for v in attributes: | ||
| if len(v) == 2: | ||
| attrs.append(ClusterAttribute.AttributeWriteRequest( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would you mind adding the parameter names here so it's easier to tell what's going on? Is the attribute parameter list supposed to be a union of possible tuples? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added the parameter names to the AttributeWriteRequest calls and updated the type hint to show it accepts either a 2-tuple or 3-tuple. |
||
| v[0], v[1], 0, 0, v[1].value)) # type: ignore[attr-defined] | ||
| else: | ||
| attrs.append(ClusterAttribute.AttributeWriteRequest( | ||
| v[0], v[1], v[2], 1, v[1].value)) | ||
| return attrs | ||
|
|
||
| async def TestOnlyWriteAttributeTimedRequestFlagWithNoTimedAction(self, nodeid: int, | ||
| attributes: typing.List[typing.Tuple[int, ClusterObjects.ClusterAttributeDescriptor]], | ||
| interactionTimeoutMs: typing.Optional[int] = None, busyWaitMs: typing.Optional[int] = None, | ||
| payloadCapability: int = TransportPayloadCapability.MRP_PAYLOAD): | ||
| ''' | ||
| ONLY TO BE USED FOR TEST: Write attributes with TimedRequest flag but no TimedAction transaction. | ||
| This should result in TIMED_REQUEST_MISMATCH error. | ||
|
|
||
| Please see WriteAttribute for description of parameters. | ||
|
|
||
| Returns: | ||
| [AttributeStatus] (list - one for each path). | ||
|
|
||
| Raises: | ||
| InteractionModelError on error (expected: TIMED_REQUEST_MISMATCH) | ||
| ''' | ||
| self.CheckIsActive() | ||
|
|
||
| eventLoop = asyncio.get_running_loop() | ||
| future = eventLoop.create_future() | ||
|
|
||
| device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs, payloadCapability=payloadCapability) | ||
|
|
||
| attrs = self._prepare_write_attribute_requests(attributes) | ||
|
|
||
| ClusterAttribute.TestOnlyWriteAttributeTimedRequestFlagWithNoTimedAction( | ||
| future, eventLoop, device.deviceProxy, attrs, | ||
| interactionTimeoutMs=interactionTimeoutMs, busyWaitMs=busyWaitMs).raise_on_error() | ||
| return await future | ||
|
|
||
| async def SendCommand(self, nodeid: int, endpoint: int, payload: ClusterObjects.ClusterCommand, responseType=None, | ||
| timedRequestTimeoutMs: typing.Optional[int] = None, | ||
| interactionTimeoutMs: typing.Optional[int] = None, busyWaitMs: typing.Optional[int] = None, | ||
|
|
@@ -1727,14 +1769,7 @@ async def _WriteAttribute(self, nodeid: int, | |
|
|
||
| device = await self.GetConnectedDevice(nodeid, timeoutMs=interactionTimeoutMs, payloadCapability=payloadCapability) | ||
|
|
||
| attrs = [] | ||
| for v in attributes: | ||
| if len(v) == 2: | ||
| attrs.append(ClusterAttribute.AttributeWriteRequest( | ||
| v[0], v[1], 0, 0, v[1].value)) # type: ignore[attr-defined] # 'value' added dynamically to ClusterAttributeDescriptor | ||
| else: | ||
| attrs.append(ClusterAttribute.AttributeWriteRequest( | ||
| v[0], v[1], v[2], 1, v[1].value)) | ||
| attrs = self._prepare_write_attribute_requests(attributes) | ||
|
|
||
| ClusterAttribute.WriteAttributes( | ||
| future, eventLoop, device.deviceProxy, attrs, timedRequestTimeoutMs=timedRequestTimeoutMs, | ||
|
|
||
bzbarsky-apple marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not check all the FFI bits in here carefully.... I am assuming @cecille will or has. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1010,35 +1010,38 @@ def _OnWriteDoneCallback(closure): | |
| closure.handleDone() | ||
|
|
||
|
|
||
| def WriteAttributes(future: Future, eventLoop, device, | ||
| attributes: List[AttributeWriteRequest], timedRequestTimeoutMs: Union[None, int] = None, | ||
| interactionTimeoutMs: Union[None, int] = None, busyWaitMs: Union[None, int] = None, forceLegacyListEncoding: bool = False) -> PyChipError: | ||
| handle = GetLibraryHandle() | ||
|
|
||
| def _prepare_write_attributes_data(attributes: List[AttributeWriteRequest], must_use_timed_write_check: bool = True, timedRequestTimeoutMs: Union[None, int] = None): | ||
|
||
| """Helper function to prepare PyWriteAttributeData array from AttributeWriteRequest list.""" | ||
| numberOfAttributes = len(attributes) | ||
| pyWriteAttributesArrayType = PyWriteAttributeData * numberOfAttributes | ||
| pyWriteAttributes = pyWriteAttributesArrayType() | ||
|
|
||
| for idx, attr in enumerate(attributes): | ||
| if attr.Attribute.must_use_timed_write and timedRequestTimeoutMs is None or timedRequestTimeoutMs == 0: | ||
| if must_use_timed_write_check and attr.Attribute.must_use_timed_write and timedRequestTimeoutMs is None or timedRequestTimeoutMs == 0: | ||
| raise InteractionModelError( | ||
| InteractionModelStatus.NeedsTimedInteraction) | ||
|
|
||
| tlv = attr.Attribute.ToTLV(None, attr.Data) | ||
|
|
||
| pyWriteAttributes[idx].attributePath.endpointId = c_uint16( | ||
| attr.EndpointId) | ||
| pyWriteAttributes[idx].attributePath.clusterId = c_uint32( | ||
| attr.Attribute.cluster_id) | ||
| pyWriteAttributes[idx].attributePath.attributeId = c_uint32( | ||
| attr.Attribute.attribute_id) | ||
| pyWriteAttributes[idx].attributePath.dataVersion = c_uint32( | ||
| attr.DataVersion) | ||
| pyWriteAttributes[idx].attributePath.hasDataVersion = c_uint8( | ||
| attr.HasDataVersion) | ||
| pyWriteAttributes[idx].tlvData = cast( | ||
| ctypes.c_char_p(bytes(tlv)), c_void_p) | ||
| pyWriteAttributes[idx].attributePath.endpointId = c_uint16(attr.EndpointId) | ||
| pyWriteAttributes[idx].attributePath.clusterId = c_uint32(attr.Attribute.cluster_id) | ||
| pyWriteAttributes[idx].attributePath.attributeId = c_uint32(attr.Attribute.attribute_id) | ||
| pyWriteAttributes[idx].attributePath.dataVersion = c_uint32(attr.DataVersion) | ||
| pyWriteAttributes[idx].attributePath.hasDataVersion = c_uint8(attr.HasDataVersion) | ||
| pyWriteAttributes[idx].tlvData = cast(ctypes.c_char_p(bytes(tlv)), c_void_p) | ||
| pyWriteAttributes[idx].tlvLength = c_size_t(len(tlv)) | ||
|
|
||
| return pyWriteAttributes, numberOfAttributes | ||
|
|
||
|
|
||
| def WriteAttributes(future: Future, eventLoop, device, | ||
| attributes: List[AttributeWriteRequest], timedRequestTimeoutMs: Union[None, int] = None, | ||
| interactionTimeoutMs: Union[None, int] = None, busyWaitMs: Union[None, int] = None, forceLegacyListEncoding: bool = False) -> PyChipError: | ||
| handle = GetLibraryHandle() | ||
|
|
||
| pyWriteAttributes, numberOfAttributes = _prepare_write_attributes_data( | ||
| attributes, must_use_timed_write_check=True, timedRequestTimeoutMs=timedRequestTimeoutMs) | ||
|
|
||
| transaction = AsyncWriteTransaction(future, eventLoop) | ||
| ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction)) | ||
| res = builtins.chipStack.Call( | ||
|
|
@@ -1057,6 +1060,37 @@ def WriteAttributes(future: Future, eventLoop, device, | |
| return res | ||
|
|
||
|
|
||
| def TestOnlyWriteAttributeTimedRequestFlagWithNoTimedAction(future: Future, eventLoop, device, | ||
| attributes: List[AttributeWriteRequest], | ||
| interactionTimeoutMs: Union[None, int] = None, | ||
| busyWaitMs: Union[None, int] = None) -> PyChipError: | ||
| ''' | ||
| ONLY TO BE USED FOR TEST: Writes attributes with TimedRequest flag but no TimedAction transaction | ||
| This should result in TIMED_REQUEST_MISMATCH error. | ||
| ''' | ||
| handle = GetLibraryHandle() | ||
|
|
||
| # Note: We skip the timed write check here to allow testing the TIMED_REQUEST_MISMATCH scenario | ||
| # In normal WriteAttributes, this would check for must_use_timed_write | ||
| pyWriteAttributes, numberOfAttributes = _prepare_write_attributes_data( | ||
| attributes, must_use_timed_write_check=False) | ||
|
|
||
| transaction = AsyncWriteTransaction(future, eventLoop) | ||
| ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction)) | ||
|
|
||
| # Call the TestOnly C++ function that sets TimedRequest=True but no timed transaction | ||
| res = builtins.chipStack.Call( | ||
| lambda: handle.pychip_WriteClient_TestOnlyWriteAttributesTimedRequestNoTimedAction( | ||
| ctypes.py_object(transaction), device, | ||
| ctypes.c_size_t(0 if interactionTimeoutMs is None else interactionTimeoutMs), | ||
| ctypes.c_size_t(0 if busyWaitMs is None else busyWaitMs), | ||
| pyWriteAttributes, ctypes.c_size_t(numberOfAttributes)) | ||
| ) | ||
| if not res.is_success: | ||
| ctypes.pythonapi.Py_DecRef(ctypes.py_object(transaction)) | ||
| return res | ||
|
|
||
|
|
||
| def WriteGroupAttributes(groupId: int, devCtrl: c_void_p, attributes: List[AttributeWriteRequest], busyWaitMs: Union[None, int] = None) -> PyChipError: | ||
| handle = GetLibraryHandle() | ||
|
|
||
|
|
@@ -1227,6 +1261,9 @@ def Init(): | |
|
|
||
| handle.pychip_WriteClient_WriteAttributes.restype = PyChipError | ||
| handle.pychip_WriteClient_WriteGroupAttributes.restype = PyChipError | ||
| handle.pychip_WriteClient_TestOnlyWriteAttributesTimedRequestNoTimedAction.restype = PyChipError | ||
| handle.pychip_WriteClient_TestOnlyWriteAttributesTimedRequestNoTimedAction.argtypes = [py_object, c_void_p, | ||
| c_size_t, c_size_t, POINTER(PyWriteAttributeData), c_size_t] | ||
|
|
||
| # Both WriteAttributes and WriteGroupAttributes are variadic functions. As per ctype documentation | ||
| # https://docs.python.org/3/library/ctypes.html#calling-varadic-functions, it is critical that we | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Glanced over this, and seems ok, but did not review carefully. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -267,6 +267,9 @@ PyChipError pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * | |
| size_t interactionTimeoutMsSizeT, size_t busyWaitMsSizeT, | ||
| chip::python::PyWriteAttributeData * writeAttributesData, size_t attributeDataLength, | ||
| bool forceLegacyListEncoding); | ||
| PyChipError pychip_WriteClient_TestOnlyWriteAttributesTimedRequestNoTimedAction( | ||
| void * appContext, DeviceProxy * device, size_t interactionTimeoutMsSizeT, size_t busyWaitMsSizeT, | ||
| chip::python::PyWriteAttributeData * writeAttributesData, size_t attributeDataLength); | ||
| PyChipError pychip_WriteClient_WriteGroupAttributes(size_t groupIdSizeT, chip::Controller::DeviceCommissioner * devCtrl, | ||
| size_t busyWaitMsSizeT, | ||
| chip::python::PyWriteAttributeData * writeAttributesData, | ||
|
|
@@ -318,6 +321,40 @@ class WriteClientCallback : public WriteClient::Callback | |
|
|
||
| using namespace chip::python; | ||
|
|
||
| namespace { | ||
| // Helper function to process write attributes data - reduces code duplication | ||
| CHIP_ERROR ProcessWriteAttributesData(WriteClient * client, python::PyWriteAttributeData * writeAttributesData, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the difference between the processing in this function vs. the legacy list encoding? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi Cecille, To answer your question about the difference between the processing in this helper function vs. the legacy list encoding: Here's what was preserved:
The helper function intentionally does not include the listEncodingOverride parameter because:
So Amine's legacy list encoding functionality is 100% preserved - we just extracted the common attribute processing logic that was duplicated between the two WriteClient functions while keeping the legacy-specific code path intact. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, but the code is the same, right? So the code below is doing So why not just have the forcelegacy section also use this function and just pass in the required parameter? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Understood, that is sorta what I had initially, then when I submitted the change and requested a re-review from the Gemini AI bot it suggested doing what I did above here in its comment on the change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that comment, the bot suggests including forceLegacyListEncoding as a parameter. But you have something different than what the bot suggested regardless. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi Cecille, you are correct. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, if you can get it so the legacy list encoding just calls this function with a parameter, that would be cleaner. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it! |
||
| size_t attributeDataLength) | ||
| { | ||
| CHIP_ERROR err = CHIP_NO_ERROR; | ||
|
|
||
| for (size_t i = 0; i < attributeDataLength; i++) | ||
| { | ||
| python::PyAttributePath path = writeAttributesData[i].attributePath; | ||
| void * tlv = writeAttributesData[i].tlvData; | ||
| size_t length = writeAttributesData[i].tlvLength; | ||
|
|
||
| uint8_t * tlvBuffer = reinterpret_cast<uint8_t *>(tlv); | ||
|
|
||
| TLV::TLVReader reader; | ||
| reader.Init(tlvBuffer, static_cast<uint32_t>(length)); | ||
| reader.Next(); | ||
| Optional<DataVersion> dataVersion; | ||
| if (path.hasDataVersion == 1) | ||
| { | ||
| dataVersion.SetValue(path.dataVersion); | ||
| } | ||
|
|
||
| SuccessOrExit( | ||
| err = client->PutPreencodedAttribute( | ||
| chip::app::ConcreteDataAttributePath(path.endpointId, path.clusterId, path.attributeId, dataVersion), reader)); | ||
| } | ||
|
|
||
| exit: | ||
| return err; | ||
| } | ||
| } // namespace | ||
|
|
||
| extern "C" { | ||
| void pychip_WriteClient_InitCallbacks(OnWriteResponseCallback onWriteResponseCallback, OnWriteErrorCallback onWriteErrorCallback, | ||
| OnWriteDoneCallback onWriteDoneCallback) | ||
|
|
@@ -364,31 +401,80 @@ PyChipError pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * | |
|
|
||
| VerifyOrExit(device != nullptr && device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION); | ||
|
|
||
| for (size_t i = 0; i < attributeDataLength; i++) | ||
| // Handle legacy list encoding override if needed | ||
| if (forceLegacyListEncoding) | ||
| { | ||
| python::PyAttributePath path = writeAttributesData[i].attributePath; | ||
| void * tlv = writeAttributesData[i].tlvData; | ||
| size_t length = writeAttributesData[i].tlvLength; | ||
| for (size_t i = 0; i < attributeDataLength; i++) | ||
| { | ||
| python::PyAttributePath path = writeAttributesData[i].attributePath; | ||
| void * tlv = writeAttributesData[i].tlvData; | ||
| size_t length = writeAttributesData[i].tlvLength; | ||
|
|
||
| uint8_t * tlvBuffer = reinterpret_cast<uint8_t *>(tlv); | ||
| uint8_t * tlvBuffer = reinterpret_cast<uint8_t *>(tlv); | ||
|
|
||
| TLV::TLVReader reader; | ||
| reader.Init(tlvBuffer, static_cast<uint32_t>(length)); | ||
| reader.Next(); | ||
| Optional<DataVersion> dataVersion; | ||
| if (path.hasDataVersion == 1) | ||
| { | ||
| dataVersion.SetValue(path.dataVersion); | ||
| TLV::TLVReader reader; | ||
| reader.Init(tlvBuffer, static_cast<uint32_t>(length)); | ||
| reader.Next(); | ||
| Optional<DataVersion> dataVersion; | ||
| if (path.hasDataVersion == 1) | ||
| { | ||
| dataVersion.SetValue(path.dataVersion); | ||
| } | ||
|
|
||
| auto listEncodingOverride = WriteClient::TestListEncodingOverride::kForceLegacyEncoding; | ||
|
|
||
| SuccessOrExit(err = client->PutPreencodedAttribute( | ||
| chip::app::ConcreteDataAttributePath(path.endpointId, path.clusterId, path.attributeId, dataVersion), | ||
| reader, listEncodingOverride)); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| SuccessOrExit(err = ProcessWriteAttributesData(client.get(), writeAttributesData, attributeDataLength)); | ||
| } | ||
|
|
||
| auto listEncodingOverride = forceLegacyListEncoding ? WriteClient::TestListEncodingOverride::kForceLegacyEncoding | ||
| : WriteClient::TestListEncodingOverride::kNoOverride; | ||
| SuccessOrExit(err = client->SendWriteRequest(device->GetSecureSession().Value(), | ||
| interactionTimeoutMs != 0 ? System::Clock::Milliseconds32(interactionTimeoutMs) | ||
| : System::Clock::kZero)); | ||
|
|
||
| SuccessOrExit(err = client->PutPreencodedAttribute( | ||
| chip::app::ConcreteDataAttributePath(path.endpointId, path.clusterId, path.attributeId, dataVersion), | ||
| reader, listEncodingOverride)); | ||
| client.release(); | ||
| callback.release(); | ||
|
|
||
| if (busyWaitMs) | ||
| { | ||
| usleep(busyWaitMs * 1000); | ||
| } | ||
|
|
||
| exit: | ||
| return ToPyChipError(err); | ||
| } | ||
|
|
||
| PyChipError pychip_WriteClient_TestOnlyWriteAttributesTimedRequestNoTimedAction(void * appContext, DeviceProxy * device, | ||
| size_t interactionTimeoutMsSizeT, | ||
| size_t busyWaitMsSizeT, | ||
| python::PyWriteAttributeData * writeAttributesData, | ||
| size_t attributeDataLength) | ||
| { | ||
| CHIP_ERROR err = CHIP_NO_ERROR; | ||
|
|
||
| uint16_t interactionTimeoutMs = static_cast<uint16_t>(interactionTimeoutMsSizeT); | ||
| uint16_t busyWaitMs = static_cast<uint16_t>(busyWaitMsSizeT); | ||
|
|
||
| std::unique_ptr<WriteClientCallback> callback = std::make_unique<WriteClientCallback>(appContext); | ||
|
|
||
| // CRITICAL: Use TestOnly constructor to set TimedRequest flag without timeout. | ||
| // This function intentionally sets TimedRequest=true but does NOT send a TimedRequest action first. | ||
| // This should result in TIMED_REQUEST_MISMATCH error. | ||
| std::unique_ptr<WriteClient> client = std::make_unique<WriteClient>( | ||
| app::InteractionModelEngine::GetInstance()->GetExchangeManager(), callback->GetChunkedCallback(), | ||
| true); // Set TimedRequest flag to true without timeout | ||
|
|
||
| VerifyOrExit(device != nullptr && device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION); | ||
|
|
||
| SuccessOrExit(err = ProcessWriteAttributesData(client.get(), writeAttributesData, attributeDataLength)); | ||
|
|
||
| // Send WriteRequest with TimedRequest flag set but no preceding TimedRequest action | ||
| // This should trigger TIMED_REQUEST_MISMATCH error | ||
| SuccessOrExit(err = client->SendWriteRequest(device->GetSecureSession().Value(), | ||
| interactionTimeoutMs != 0 ? System::Clock::Milliseconds32(interactionTimeoutMs) | ||
| : System::Clock::kZero)); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.