diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn index 95686b3ce383bc..eaaeb18864e6e1 100644 --- a/src/controller/python/BUILD.gn +++ b/src/controller/python/BUILD.gn @@ -19,6 +19,7 @@ import("//build_overrides/pigweed.gni") import("$dir_pw_build/python.gni") import("${build_root}/chip/tools.gni") +import("${chip_root}/src/platform/device.gni") import("${dir_pw_unit_test}/test.gni") config("controller_wno_deprecate") { @@ -44,6 +45,10 @@ shared_library("ChipDeviceCtrl") { "ChipDeviceController-StorageDelegate.h", ] + if (current_os == "linux" && chip_enable_ble) { + sources += [ "chip/ble/LinuxImpl.cpp" ] + } + public_deps = [ "${chip_root}/src/app", "${chip_root}/src/controller", @@ -75,8 +80,8 @@ pw_python_action("python") { "chip/ChipStack.py", "chip/ChipUtility.py", "chip/__init__.py", - "chip/ble/__init__.py", "chip/exceptions/__init__.py", + "chip/native/__init__.py", "chip/tlv/__init__.py", ] }, @@ -90,6 +95,15 @@ pw_python_action("python") { }, ] + if (chip_enable_ble) { + _py_manifest_files += [ + { + src_dir = "." + sources = [ "chip/ble/__init__.py" ] + }, + ] + } + _py_manifest_file = "${target_gen_dir}/${target_name}.py_manifest.json" inputs = [] diff --git a/src/controller/python/build-chip-wheel.py b/src/controller/python/build-chip-wheel.py index 910d814f46c6fe..4ce9c6c0bac364 100644 --- a/src/controller/python/build-chip-wheel.py +++ b/src/controller/python/build-chip-wheel.py @@ -139,6 +139,7 @@ def finalize_options(self): 'chip', 'chip.ble', 'chip.exceptions', + 'chip.native', 'chip.tlv', ], package_dir={ diff --git a/src/controller/python/chip-repl.py b/src/controller/python/chip-repl.py index 23c4318136dea1..86db6923231753 100644 --- a/src/controller/python/chip-repl.py +++ b/src/controller/python/chip-repl.py @@ -29,9 +29,8 @@ def main(): ######## List available BLE adapters ######### -for adapter in chip.GetBleAdapters(): - print(adapter) - +import chip.ble +print(chip.ble.GetAdapters()) '''.strip()) if __name__ == "__main__": diff --git a/src/controller/python/chip/__init__.py b/src/controller/python/chip/__init__.py index 85b69f4ec1785c..7fb82dc1066687 100644 --- a/src/controller/python/chip/__init__.py +++ b/src/controller/python/chip/__init__.py @@ -21,6 +21,4 @@ # Provides Python APIs for CHIP. # -"""Provides Python APIs for CHIP.""" - -from chip.ble import GetBleAdapters \ No newline at end of file +"""Provides Python APIs for CHIP.""" \ No newline at end of file diff --git a/src/controller/python/chip/ble/LinuxImpl.cpp b/src/controller/python/chip/ble/LinuxImpl.cpp new file mode 100644 index 00000000000000..93d5375686538b --- /dev/null +++ b/src/controller/python/chip/ble/LinuxImpl.cpp @@ -0,0 +1,51 @@ + +#include +#include +#include +#include +#include + +using namespace chip::DeviceLayer::Internal; + +/////////// Listing adapters implementation ////////// + +extern "C" void * pychip_ble_adapter_list_new() +{ + return static_cast(new chip::DeviceLayer::Internal::AdapterIterator()); +} + +extern "C" void pychip_ble_adapter_list_delete(void * adapter) +{ + delete static_cast(adapter); +} + +extern "C" bool pychip_ble_adapter_list_next(void * adapter) +{ + return static_cast(adapter)->Next(); +} + +extern "C" unsigned pychip_ble_adapter_list_get_index(void * adapter) +{ + /// NOTE: returning unsigned because python native has no sized values + return static_cast(adapter)->GetIndex(); +} + +extern "C" const char * pychip_ble_adapter_list_get_address(void * adapter) +{ + return static_cast(adapter)->GetAddress(); +} + +extern "C" const char * pychip_ble_adapter_list_get_alias(void * adapter) +{ + return static_cast(adapter)->GetAlias(); +} + +extern "C" const char * pychip_ble_adapter_list_get_name(void * adapter) +{ + return static_cast(adapter)->GetName(); +} + +extern "C" bool pychip_ble_adapter_list_is_powered(void * adapter) +{ + return static_cast(adapter)->IsPowered(); +} diff --git a/src/controller/python/chip/ble/__init__.py b/src/controller/python/chip/ble/__init__.py index 6db3ab54619e2a..33598657354774 100644 --- a/src/controller/python/chip/ble/__init__.py +++ b/src/controller/python/chip/ble/__init__.py @@ -9,64 +9,78 @@ # 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. - """BLE-related functionality within CHIP""" -from chip import ChipDeviceCtrl import sys import platform +import chip.native +import ctypes +from typing import List +from ctypes import c_bool, c_void_p, c_char_p, c_uint +from dataclasses import dataclass + -class Adapter: - """Generic exposed adapter information.""" +@dataclass +class AdapterInfo: + index: int + address: str + name: str + alias: str + powered_on: bool - def __init__(self, name, macAddress): - self.name = name - self.macAddress = macAddress - def __str__(self): - return 'chip.ble.Adapter(%s, %s)' % (self.name, self.macAddress) +def _GetBleLibraryHandle() -> ctypes.CDLL: + """ Get the native library handle with BLE method initialization. -if platform.system() == 'Darwin': - from chip.ChipCoreBluetoothMgr import CoreBluetoothManager as BleManager + Retreives the CHIP native library handle and attaches signatures to + native methods. + """ - def ConvertNativeAdapter(nativeAdapter): - raise NotImplementedError('Not implemented for Darwin') + handle = chip.native.GetLibraryHandle() -elif sys.platform.startswith('linux'): - from chip.ChipBluezMgr import BluezManager as BleManager + # Uses one of the type decorators as an indicator for everything being + # initialized. Native methods default to c_int return types + if handle.pychip_ble_adapter_list_new.restype != c_void_p: + setter = chip.native.NativeLibraryHandleMethodArguments(handle) - def ConvertNativeAdapter(nativeAdapter): - return Adapter(name = str(nativeAdapter.path), macAddress = str(nativeAdapter.Address)) + setter.Set('pychip_ble_adapter_list_new', c_void_p, []) + setter.Set('pychip_ble_adapter_list_next', c_bool, [c_void_p]) + setter.Set('pychip_ble_adapter_list_get_index', c_uint, [c_void_p]) + setter.Set('pychip_ble_adapter_list_get_address', c_char_p, [c_void_p]) + setter.Set('pychip_ble_adapter_list_get_alias', c_char_p, [c_void_p]) + setter.Set('pychip_ble_adapter_list_get_name', c_char_p, [c_void_p]) + setter.Set('pychip_ble_adapter_list_is_powered', c_bool, [c_void_p]) + setter.Set('pychip_ble_adapter_list_delete', None, [c_void_p]) -class LazyHandles(object): - """Contains a handle to an underlying BLE manager.""" + return handle - def __init__(self): - self._deviceController = None - self._bleManager = None - @property - def deviceController(self): - if self._deviceController is None: - self._deviceController = ChipDeviceCtrl.ChipDeviceController() - return self._deviceController +def GetAdapters() -> List[AdapterInfo]: + """Get a list of BLE adapters available on the system. """ + handle = _GetBleLibraryHandle() - @property - def bleManager(self): - if self._bleManager is None: - self._bleManager = BleManager(self.deviceController) - return self._bleManager + result = [] + nativeList = handle.pychip_ble_adapter_list_new() + if nativeList == 0: + raise Exception('Failed to get BLE adapter list') - def GetAdapters(self): - """Return available BLE adapters on this platform.""" - return [ConvertNativeAdapter(a) for a in self.bleManager.get_adapters()] + try: + while handle.pychip_ble_adapter_list_next(nativeList): + result.append( + AdapterInfo( + index=handle.pychip_ble_adapter_list_get_index(nativeList), + address=handle.pychip_ble_adapter_list_get_address(nativeList).decode('utf8'), + name=handle.pychip_ble_adapter_list_get_name(nativeList).decode('utf8'), + alias=handle.pychip_ble_adapter_list_get_alias(nativeList).decode('utf8'), + powered_on=handle.pychip_ble_adapter_list_is_powered(nativeList), + )) + finally: + handle.pychip_ble_adapter_list_delete(nativeList) -lazyHandles = LazyHandles() + return result -def GetBleAdapters(): - return lazyHandles.GetAdapters() __all__ = [ - "GetBleAdapters", + 'GetAdapters', ] diff --git a/src/controller/python/chip/native/__init__.py b/src/controller/python/chip/native/__init__.py new file mode 100644 index 00000000000000..838411d8bd24d4 --- /dev/null +++ b/src/controller/python/chip/native/__init__.py @@ -0,0 +1,79 @@ +import ctypes +import glob +import os +import platform + +NATIVE_LIBRARY_BASE_NAME = "_ChipDeviceCtrl.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 Device Manager 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 + + +def GetLibraryHandle() -> ctypes.CDLL: + """Get a memoized handle to the chip native code dll.""" + + global _nativeLibraryHandle + if _nativeLibraryHandle is None: + _nativeLibraryHandle = ctypes.CDLL(FindNativeLibraryPath()) + + return _nativeLibraryHandle diff --git a/src/platform/Linux/CHIPBluezHelper.cpp b/src/platform/Linux/CHIPBluezHelper.cpp index 3d855ab699afad..73d3e712fc8434 100644 --- a/src/platform/Linux/CHIPBluezHelper.cpp +++ b/src/platform/Linux/CHIPBluezHelper.cpp @@ -2073,10 +2073,10 @@ bool AdapterIterator::Advance() index = 0; } - mCurrent.index = index; - CopyString(mCurrent.address, bluez_adapter1_get_address(adapter)); - CopyString(mCurrent.alias, bluez_adapter1_get_alias(adapter)); - CopyString(mCurrent.name, bluez_adapter1_get_name(adapter)); + mCurrent.index = index; + mCurrent.address = bluez_adapter1_get_address(adapter); + mCurrent.alias = bluez_adapter1_get_alias(adapter); + mCurrent.name = bluez_adapter1_get_name(adapter); mCurrent.powered = bluez_adapter1_get_powered(adapter); g_object_unref(adapter); diff --git a/src/platform/Linux/CHIPBluezHelper.h b/src/platform/Linux/CHIPBluezHelper.h index 232ef7c8e80ce1..12efd1f0fdce1b 100644 --- a/src/platform/Linux/CHIPBluezHelper.h +++ b/src/platform/Linux/CHIPBluezHelper.h @@ -57,6 +57,7 @@ #include #include +#include namespace chip { namespace DeviceLayer { @@ -213,6 +214,11 @@ CHIP_ERROR ConnectDevice(BluezDevice1 * apDevice); /// while (iterator.Next()) { /// std::cout << iterator.GetAddress() << std::endl; /// } +/// +/// Data is provided through the bluez dbus interface. You can view +/// this data in the commandline using commands such as: +/// +/// busctl introspect org.bluez /org/bluez/hci0 class AdapterIterator { public: @@ -227,9 +233,9 @@ class AdapterIterator // Information about the current value. Safe to call only after // "Next" has returned true. uint32_t GetIndex() const { return mCurrent.index; } - const char * GetAddress() const { return mCurrent.address; } - const char * GetAlias() const { return mCurrent.alias; } - const char * GetName() const { return mCurrent.name; } + const char * GetAddress() const { return mCurrent.address.c_str(); } + const char * GetAlias() const { return mCurrent.alias.c_str(); } + const char * GetName() const { return mCurrent.name.c_str(); } bool IsPowered() const { return mCurrent.powered; } private: @@ -253,9 +259,9 @@ class AdapterIterator struct { uint32_t index; - char address[kMaxAddressLength]; - char alias[kMaxNameLength]; - char name[kMaxNameLength]; + std::string address; + std::string alias; + std::string name; bool powered; } mCurrent = { 0 }; };