Skip to content

Commit c2e655c

Browse files
authored
Fix PostAttributeChangeCallback value typing (#36928)
As documented in the TODO, the callback requires the caller to pass a NULL-terminated C-string, but it’s actually a length-buffer pair. The python lighting example receives values with incorrect length due to this issue. This PR fixes the FFI interface by wrapping the callback function, and converts the length-buffer pair to a proper python bytes value.
1 parent cc14a69 commit c2e655c

File tree

2 files changed

+30
-10
lines changed

2 files changed

+30
-10
lines changed

src/controller/python/chip/native/__init__.py

+18-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import ctypes
1818
import enum
19+
import functools
1920
import glob
2021
import os
2122
import platform
@@ -146,21 +147,31 @@ def __ne__(self, other):
146147
return not self == other
147148

148149

149-
PostAttributeChangeCallback = ctypes.CFUNCTYPE(
150+
c_PostAttributeChangeCallback = ctypes.CFUNCTYPE(
150151
None,
151152
ctypes.c_uint16,
152153
ctypes.c_uint16,
153154
ctypes.c_uint16,
154155
ctypes.c_uint8,
155156
ctypes.c_uint16,
156-
# TODO: This should be a pointer to uint8_t, but ctypes does not provide
157-
# such a type. The best approximation is c_char_p, however, this
158-
# requires the caller to pass NULL-terminate C-string which might
159-
# not be the case here.
160-
ctypes.c_char_p,
157+
ctypes.POINTER(ctypes.c_char),
161158
)
162159

163160

161+
def PostAttributeChangeCallback(func):
162+
@functools.wraps(func)
163+
def wrapper(
164+
endpoint: int,
165+
clusterId: int,
166+
attributeId: int,
167+
xx_type: int,
168+
size: int,
169+
value: ctypes.POINTER(ctypes.c_char),
170+
):
171+
return func(endpoint, clusterId, attributeId, xx_type, size, value[:size])
172+
return c_PostAttributeChangeCallback(wrapper)
173+
174+
164175
def FindNativeLibraryPath(library: Library) -> str:
165176
"""Find the native CHIP dll/so path."""
166177

@@ -234,7 +245,7 @@ def _GetLibraryHandle(lib: Library, expectAlreadyInitialized: bool) -> _Handle:
234245
[ctypes.POINTER(PyChipError), ctypes.c_char_p, ctypes.c_uint32])
235246
elif lib == Library.SERVER:
236247
setter.Set("pychip_server_native_init", PyChipError, [])
237-
setter.Set("pychip_server_set_callbacks", None, [PostAttributeChangeCallback])
248+
setter.Set("pychip_server_set_callbacks", None, [c_PostAttributeChangeCallback])
238249

239250
handle = _nativeLibraryHandles[lib]
240251
if expectAlreadyInitialized and not handle.initialized:

src/controller/python/chip/server/__init__.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,20 @@
1717
import ctypes
1818

1919
from chip import native
20-
from chip.native import PostAttributeChangeCallback
20+
from chip.native import PostAttributeChangeCallback, c_PostAttributeChangeCallback
2121

22+
__all__ = [
23+
"GetLibraryHandle",
24+
"PostAttributeChangeCallback",
25+
]
2226

23-
def GetLibraryHandle(cb: PostAttributeChangeCallback) -> ctypes.CDLL:
24-
"""Get a memoized handle to the chip native code dll."""
27+
28+
def GetLibraryHandle(cb: c_PostAttributeChangeCallback) -> ctypes.CDLL:
29+
"""Get a memoized handle to the chip native code dll.
30+
31+
Args:
32+
cb: A callback decorated by PostAttributeChangeCallback decorator.
33+
"""
2534

2635
handle = native._GetLibraryHandle(native.Library.SERVER, False)
2736
if not handle.initialized:

0 commit comments

Comments
 (0)