[Python] Reuse chip.native to get server lib handle (#31387)
* [Python] Reuse chip.native to get server lib handle
* Raise on initialization error
* Post-review update
* Fix for Python < 3.9
diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn
index 4b84c63..c8d2ac3 100644
--- a/src/controller/python/BUILD.gn
+++ b/src/controller/python/BUILD.gn
@@ -269,10 +269,7 @@
if (chip_controller) {
sources += [ "chip/ChipDeviceCtrl.py" ]
} else {
- sources += [
- "chip/server/__init__.py",
- "chip/server/types.py",
- ]
+ sources += [ "chip/server/__init__.py" ]
}
},
{
diff --git a/src/controller/python/chip/ChipStack.py b/src/controller/python/chip/ChipStack.py
index d64fa34..6df7e41 100644
--- a/src/controller/python/chip/ChipStack.py
+++ b/src/controller/python/chip/ChipStack.py
@@ -448,7 +448,7 @@
def _loadLib(self):
if self._ChipStackLib is None:
self._ChipStackLib = chip.native.GetLibraryHandle()
- self._chipDLLPath = chip.native.FindNativeLibraryPath()
+ self._chipDLLPath = chip.native.FindNativeLibraryPath(chip.native.Library.CONTROLLER)
self._ChipStackLib.pychip_DeviceController_StackInit.argtypes = [c_void_p, c_bool]
self._ChipStackLib.pychip_DeviceController_StackInit.restype = PyChipError
diff --git a/src/controller/python/chip/native/__init__.py b/src/controller/python/chip/native/__init__.py
index 7801129..ce8b762 100644
--- a/src/controller/python/chip/native/__init__.py
+++ b/src/controller/python/chip/native/__init__.py
@@ -1,14 +1,34 @@
+#
+# Copyright (c) 2021 Project CHIP Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
import ctypes
import enum
import glob
import os
import platform
import typing
+from dataclasses import dataclass
import chip.exceptions
import construct
-NATIVE_LIBRARY_BASE_NAME = "_ChipDeviceCtrl.so"
+
+class Library(enum.Enum):
+ CONTROLLER = "_ChipDeviceCtrl.so"
+ SERVER = "_ChipServer.so"
def _AllDirsToRoot(dir):
@@ -119,7 +139,22 @@
return not self == other
-def FindNativeLibraryPath() -> str:
+PostAttributeChangeCallback = ctypes.CFUNCTYPE(
+ None,
+ ctypes.c_uint16,
+ ctypes.c_uint16,
+ ctypes.c_uint16,
+ ctypes.c_uint8,
+ ctypes.c_uint16,
+ # TODO: This should be a pointer to uint8_t, but ctypes does not provide
+ # such a type. The best approximation is c_char_p, however, this
+ # requires the caller to pass NULL-terminate C-string which might
+ # not be the case here.
+ ctypes.c_char_p,
+)
+
+
+def FindNativeLibraryPath(library: Library) -> str:
"""Find the native CHIP dll/so path."""
scriptDir = os.path.dirname(os.path.abspath(__file__))
@@ -129,7 +164,7 @@
# modules.
dmDLLPath = os.path.join(
os.path.dirname(scriptDir), # file should be inside 'chip'
- NATIVE_LIBRARY_BASE_NAME)
+ library.value)
if os.path.exists(dmDLLPath):
return dmDLLPath
@@ -143,7 +178,7 @@
"build",
buildMachineGlob,
"src/controller/python/.libs",
- NATIVE_LIBRARY_BASE_NAME,
+ library.value,
)
for dir in _AllDirsToRoot(scriptDir):
dmDLLPathGlob = os.path.join(dir, relDMDLLPathGlob)
@@ -152,8 +187,7 @@
return dmDLLPath
raise Exception(
- "Unable to locate Chip Device Manager DLL (%s); expected location: %s" %
- (NATIVE_LIBRARY_BASE_NAME, scriptDir))
+ f"Unable to locate CHIP DLL ({library.value}); expected location: {scriptDir}")
class NativeLibraryHandleMethodArguments:
@@ -168,22 +202,38 @@
method.argtype = argumentTypes
-_nativeLibraryHandle: ctypes.CDLL = None
+@dataclass
+class _Handle:
+ dll: ctypes.CDLL = None
+ initialized: bool = False
-def _GetLibraryHandle(shouldInit: bool) -> ctypes.CDLL:
- """Get a memoized handle to the chip native code dll."""
+_nativeLibraryHandles: typing.Dict[Library, _Handle] = {}
- global _nativeLibraryHandle
- if _nativeLibraryHandle is None:
- if shouldInit:
- raise Exception("Common stack has not been initialized!")
- _nativeLibraryHandle = ctypes.CDLL(FindNativeLibraryPath())
- setter = NativeLibraryHandleMethodArguments(_nativeLibraryHandle)
- setter.Set("pychip_CommonStackInit", PyChipError, [ctypes.c_char_p])
- setter.Set("pychip_FormatError", None, [ctypes.POINTER(PyChipError), ctypes.c_char_p, ctypes.c_uint32])
- return _nativeLibraryHandle
+def _GetLibraryHandle(lib: Library, expectAlreadyInitialized: bool) -> ctypes.CDLL:
+ """Get a memoized _Handle to the chip native code dll."""
+
+ global _nativeLibraryHandles
+ if lib not in _nativeLibraryHandles:
+
+ handle = _Handle(ctypes.CDLL(FindNativeLibraryPath(lib)))
+ _nativeLibraryHandles[lib] = handle
+
+ setter = NativeLibraryHandleMethodArguments(handle.dll)
+ if lib == Library.CONTROLLER:
+ setter.Set("pychip_CommonStackInit", PyChipError, [ctypes.c_char_p])
+ setter.Set("pychip_FormatError", None,
+ [ctypes.POINTER(PyChipError), ctypes.c_char_p, ctypes.c_uint32])
+ elif lib == Library.SERVER:
+ setter.Set("pychip_server_native_init", PyChipError, [])
+ setter.Set("pychip_server_set_callbacks", None, [PostAttributeChangeCallback])
+
+ handle = _nativeLibraryHandles[lib]
+ if expectAlreadyInitialized and not handle.initialized:
+ raise Exception("CHIP handle has not been initialized!")
+
+ return handle
def Init(bluetoothAdapter: int = None):
@@ -194,12 +244,15 @@
params.BluetoothAdapterId = bluetoothAdapter if bluetoothAdapter is not None else 0
params = CommonStackParams.build(params)
- _GetLibraryHandle(False).pychip_CommonStackInit(ctypes.c_char_p(params))
+ handle = _GetLibraryHandle(Library.CONTROLLER, False)
+ handle.dll.pychip_CommonStackInit(ctypes.c_char_p(params)).raise_on_error()
+ handle.initialized = True
class HandleFlags(enum.Flag):
REQUIRE_INITIALIZATION = enum.auto()
-def GetLibraryHandle(flags=HandleFlags.REQUIRE_INITIALIZATION):
- return _GetLibraryHandle(HandleFlags.REQUIRE_INITIALIZATION in flags)
+def GetLibraryHandle(flags=HandleFlags.REQUIRE_INITIALIZATION) -> ctypes.CDLL:
+ handle = _GetLibraryHandle(Library.CONTROLLER, HandleFlags.REQUIRE_INITIALIZATION in flags)
+ return handle.dll
diff --git a/src/controller/python/chip/server/ServerInit.cpp b/src/controller/python/chip/server/ServerInit.cpp
index 611d494..39edd7a 100644
--- a/src/controller/python/chip/server/ServerInit.cpp
+++ b/src/controller/python/chip/server/ServerInit.cpp
@@ -27,6 +27,7 @@
#include <setup_payload/QRCodeSetupPayloadGenerator.h>
#include <setup_payload/SetupPayload.h>
+#include <controller/python/chip/native/PyChipError.h>
#include <credentials/examples/DeviceAttestationCredsExample.h>
// #include <support/CHIPMem.h>
@@ -118,21 +119,11 @@
gPythonServerDelegate.SetPostAttributeChangeCallback(cb);
}
-void pychip_server_native_init()
+PyChipError pychip_server_native_init()
{
- CHIP_ERROR err = CHIP_NO_ERROR;
- err = chip::Platform::MemoryInit();
- if (err != CHIP_NO_ERROR)
- {
- ChipLogError(DeviceLayer, "Failed to initialize CHIP stack: memory init failed: %s", chip::ErrorStr(err));
- }
-
- err = chip::DeviceLayer::PlatformMgr().InitChipStack();
- if (err != CHIP_NO_ERROR)
- {
- ChipLogError(DeviceLayer, "Failed to initialize CHIP stack: platform init failed: %s", chip::ErrorStr(err));
- }
+ PyReturnErrorOnFailure(ToPyChipError(Platform::MemoryInit()));
+ PyReturnErrorOnFailure(ToPyChipError(DeviceLayer::PlatformMgr().InitChipStack()));
static chip::DeviceLayer::TestOnlyCommissionableDataProvider TestOnlyCommissionableDataProvider;
chip::DeviceLayer::SetCommissionableDataProvider(&TestOnlyCommissionableDataProvider);
@@ -169,24 +160,20 @@
// Init ZCL Data Model and CHIP App Server
static chip::CommonCaseDeviceServerInitParams initParams;
- (void) initParams.InitializeStaticResourcesBeforeServerInit();
+ PyReturnErrorOnFailure(ToPyChipError(initParams.InitializeStaticResourcesBeforeServerInit()));
initParams.operationalServicePort = CHIP_PORT;
initParams.userDirectedCommissioningPort = CHIP_UDC_PORT;
- chip::Server::GetInstance().Init(initParams);
+ PyReturnErrorOnFailure(ToPyChipError(chip::Server::GetInstance().Init(initParams)));
// Initialize device attestation config
SetDeviceAttestationCredentialsProvider(chip::Credentials::Examples::GetExampleDACProvider());
- err = chip::DeviceLayer::PlatformMgr().StartEventLoopTask();
- if (err != CHIP_NO_ERROR)
- {
- ChipLogError(DeviceLayer, "Failed to initialize CHIP stack: platform init failed: %s", chip::ErrorStr(err));
- }
+ PyReturnErrorOnFailure(ToPyChipError(chip::DeviceLayer::PlatformMgr().StartEventLoopTask()));
atexit(CleanShutdown);
- return /*err*/;
+ return ToPyChipError(CHIP_NO_ERROR);
}
}
diff --git a/src/controller/python/chip/server/__init__.py b/src/controller/python/chip/server/__init__.py
index d0b046f..8280ec0 100644
--- a/src/controller/python/chip/server/__init__.py
+++ b/src/controller/python/chip/server/__init__.py
@@ -1,91 +1,32 @@
+#
+# Copyright (c) 2021 Project CHIP Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
import ctypes
-import glob
-import os
-import platform
-from chip.server.types import PostAttributeChangeCallback
-
-NATIVE_LIBRARY_BASE_NAME = "_ChipServer.so"
-
-
-def _AllDirsToRoot(dir):
- """Return all parent paths of a directory."""
- dir = os.path.abspath(dir)
- while True:
- yield dir
- parent = os.path.dirname(dir)
- if parent == "" or parent == dir:
- break
- dir = parent
-
-
-def FindNativeLibraryPath() -> str:
- """Find the native CHIP dll/so path."""
-
- scriptDir = os.path.dirname(os.path.abspath(__file__))
-
- # When properly installed in the chip package, the Chip Device Manager DLL will
- # be located in the package root directory, along side the package's
- # modules.
- dmDLLPath = os.path.join(
- os.path.dirname(scriptDir), # file should be inside 'chip'
- NATIVE_LIBRARY_BASE_NAME,
- )
- if os.path.exists(dmDLLPath):
- return dmDLLPath
-
- # For the convenience of developers, search the list of parent paths relative to the
- # running script looking for an CHIP build directory containing the Chip Device
- # Manager DLL. This makes it possible to import and use the ChipDeviceMgr module
- # directly from a built copy of the CHIP source tree.
- buildMachineGlob = "%s-*-%s*" % (platform.machine(),
- platform.system().lower())
- relDMDLLPathGlob = os.path.join(
- "build",
- buildMachineGlob,
- "src/controller/python/.libs",
- NATIVE_LIBRARY_BASE_NAME,
- )
- for dir in _AllDirsToRoot(scriptDir):
- dmDLLPathGlob = os.path.join(dir, relDMDLLPathGlob)
- for dmDLLPath in glob.glob(dmDLLPathGlob):
- if os.path.exists(dmDLLPath):
- return dmDLLPath
-
- raise Exception(
- "Unable to locate Chip Server DLL (%s); expected location: %s"
- % (NATIVE_LIBRARY_BASE_NAME, scriptDir)
- )
-
-
-class NativeLibraryHandleMethodArguments:
- """Convenience wrapper to set native method argtype and restype for methods."""
-
- def __init__(self, handle):
- self.handle = handle
-
- def Set(self, methodName: str, resultType, argumentTypes: list):
- method = getattr(self.handle, methodName)
- method.restype = resultType
- method.argtype = argumentTypes
-
-
-_nativeLibraryHandle: ctypes.CDLL = None
+from chip import native
+from chip.native import PostAttributeChangeCallback
def GetLibraryHandle(cb: PostAttributeChangeCallback) -> ctypes.CDLL:
"""Get a memoized handle to the chip native code dll."""
- global _nativeLibraryHandle
- if _nativeLibraryHandle is None:
- _nativeLibraryHandle = ctypes.CDLL(FindNativeLibraryPath())
+ handle = native._GetLibraryHandle(native.Library.SERVER, False)
+ if not handle.initialized:
+ handle.dll.pychip_server_native_init().raise_on_error()
+ handle.dll.pychip_server_set_callbacks(cb)
+ handle.initialized = True
- setter = NativeLibraryHandleMethodArguments(_nativeLibraryHandle)
- setter.Set("pychip_server_native_init", None, [])
- setter.Set("pychip_server_set_callbacks",
- None, [PostAttributeChangeCallback])
-
- _nativeLibraryHandle.pychip_server_native_init()
- _nativeLibraryHandle.pychip_server_set_callbacks(cb)
-
- return _nativeLibraryHandle
+ return handle.dll
diff --git a/src/controller/python/chip/server/types.py b/src/controller/python/chip/server/types.py
deleted file mode 100644
index 3df693d..0000000
--- a/src/controller/python/chip/server/types.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from ctypes import CFUNCTYPE, c_char_p, c_uint8, c_uint16
-
-PostAttributeChangeCallback = CFUNCTYPE(
- None,
- # py_object,
- c_uint16,
- c_uint16,
- c_uint16,
- c_uint8,
- c_uint16,
- c_char_p,
-)