blob: e83821242bf58389fd70ee9ab971b9ac4cf25cae [file] [log] [blame]
import ctypes
import enum
import glob
import os
import platform
import typing
import chip.exceptions
import construct
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
class ErrorRange(enum.IntEnum):
''' The enum of chip::ChipError::Range
'''
SDK = 0x0
OS = 0x1
POSIX = 0x2
LWIP = 0x3
OPENTHREAD = 0x4
PLATFROM = 0x5
class ErrorSDKPart(enum.IntEnum):
''' The enum of chip::ChipError::SDKPart
'''
CORE = 0
INET = 1
DEVICE = 2
ASN1 = 3
BLE = 4
IM_GLOBAL_STATUS = 5
IM_CLUSTER_STATUS = 6
APPLICATION = 7
class PyChipError(ctypes.Structure):
''' The ChipError for Python library.
We are using the following struct for passing the infomations of CHIP_ERROR between C++ and Python:
```c
struct PyChipError
{
uint32_t mCode;
uint32_t mLine;
const char * mFile;
};
```
'''
_fields_ = [('code', ctypes.c_uint32), ('line', ctypes.c_uint32), ('file', ctypes.c_void_p)]
def raise_on_error(self) -> None:
if self.code != 0:
raise self.to_exception()
@property
def is_success(self) -> bool:
return self.code == 0
@property
def is_sdk_error(self) -> bool:
return self.range == ErrorRange.SDK
@property
def range(self) -> ErrorRange:
return ErrorRange((self.code >> 24) & 0xFF)
@property
def value(self) -> int:
return (self.code) & 0xFFFFFF
@property
def sdk_part(self) -> ErrorSDKPart:
if not self.is_sdk_error:
return None
return ErrorSDKPart((self.code >> 8) & 0x07)
@property
def sdk_code(self) -> int:
if not self.is_sdk_error:
return None
return self.code & 0xFF
def to_exception(self) -> typing.Union[None, chip.exceptions.ChipStackError]:
if not self.is_success:
return chip.exceptions.ChipStackError(self.code, str(self))
def __str__(self):
buf = ctypes.create_string_buffer(256)
GetLibraryHandle().pychip_FormatError(ctypes.pointer(self), buf, 256)
return buf.value.decode()
def __eq__(self, other):
if isinstance(other, int):
return self.code == other
if isinstance(other, PyChipError):
return self.code == other.code
if isinstance(other, chip.exceptions.ChipStackError):
return self.code == other.err
raise ValueError(f"Cannot compare PyChipError with {type(other)}")
def __ne__(self, other):
return not self == other
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(shouldInit: bool) -> ctypes.CDLL:
"""Get a memoized handle to the chip native code dll."""
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 Init(bluetoothAdapter: int = None):
CommonStackParams = construct.Struct(
"BluetoothAdapterId" / construct.Int32ul,
)
params = CommonStackParams.parse(b'\x00' * CommonStackParams.sizeof())
params.BluetoothAdapterId = bluetoothAdapter if bluetoothAdapter is not None else 0
params = CommonStackParams.build(params)
_GetLibraryHandle(False).pychip_CommonStackInit(ctypes.c_char_p(params))
def GetLibraryHandle():
return _GetLibraryHandle(True)