Add initial python chip device controller and Bluez Manager for Linux (#2951)
Problem:
We need python CLI to create BLE connection and do BTP traffic and
beyond.
Summary of Changes
-- Add python chip device controller and Bluez Manager
-- Add initial python wheel generation for test use
-- Test using BLE scan, connect, BTP connection
-- In BLE BTP, BLE_CONNECTION_OBJECT has been broadly used as void *, in central Linux controler tool side, we use python bluez and
link to BTP using ctype, BLE_CONNECTION_OBJECT cannot be chip::DeviceLayer::Internal::BluezConnection.
-- Minor fix for ble uuid dbus value in linux peripheral implementation
fix #2027
diff --git a/BUILD.gn b/BUILD.gn
index c82d4ac..10879ce 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -64,9 +64,13 @@
if (chip_build_tools) {
deps += [
"${chip_root}/examples/shell",
+ "${chip_root}/src/controller/python",
"${chip_root}/src/qrcodetool",
"${chip_root}/src/setup_payload",
]
+ if (current_os == "linux") {
+ deps += [ "${chip_root}/src/controller/python" ]
+ }
}
if (current_os == "android") {
diff --git a/gn/chip/python_gen_tags.py b/gn/chip/python_gen_tags.py
new file mode 100644
index 0000000..d89be3a
--- /dev/null
+++ b/gn/chip/python_gen_tags.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# Copyright (c) 2020 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.
+
+# Utility script to genertae the python tag + the abi tag + platform tag for a Python
+
+import distutils.util
+import sys
+import sysconfig
+
+# TODO: pigweed virtualenv don't have pep425tags, will revisit it after figuring out with pigwee
+# The below is temporary solution.
+#from wheel.pep425tags import get_abbr_impl, get_impl_ver, get_abi_tag, get_platform
+#print(get_abbr_impl() + get_impl_ver() + "-" + get_abi_tag() + "-" + get_platform())
+
+abbr_ver = "cp"
+impl_ver = str(sys.version_info[0]) + str(sys.version_info[1])
+if sysconfig.get_config_var('SOABI') is None:
+ abi_tag = abbr_ver + impl_ver
+else:
+ abi_tag = 'cp' + sysconfig.get_config_var('SOABI').split('-')[1]
+platform_tag = distutils.util.get_platform().replace('.', '_').replace('-', '_')
+
+print(abbr_ver + impl_ver + "-" + abi_tag + "-" + platform_tag)
diff --git a/scripts/requirements.in b/scripts/requirements.in
index 12003b4..9628b8b 100644
--- a/scripts/requirements.in
+++ b/scripts/requirements.in
@@ -11,3 +11,6 @@
# cirque tests
requests>=2.24.0
+
+# device controller wheel package
+wheel>=0.34.2
diff --git a/scripts/requirements.txt b/scripts/requirements.txt
index 6470e3b..5052a3d 100644
--- a/scripts/requirements.txt
+++ b/scripts/requirements.txt
@@ -18,6 +18,7 @@
pyserial==3.4 # via -r requirements.in
requests==2.24.0 # via -r requirements.in
six==1.15.0 # via cryptography, pip-tools
+wheel==0.34.2 # via -r requirements.in
urllib3==1.25.10 # via requests
# The following packages are considered to be unsafe in a requirements file:
diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn
new file mode 100644
index 0000000..c527b13
--- /dev/null
+++ b/src/controller/python/BUILD.gn
@@ -0,0 +1,112 @@
+# Copyright (c) 2020 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("//build_overrides/chip.gni")
+import("${chip_root}/gn/chip/tools.gni")
+
+config("controller_wno_deprecate") {
+ cflags = [ "-Wno-deprecated-declarations" ]
+}
+
+config("includes") {
+ include_dirs = [ "${chip_root}/src/controller/CHIPDeviceController.h" ]
+}
+
+shared_library("ChipDeviceCtrl") {
+ output_name = "_ChipDeviceCtrl"
+ output_dir = "$root_out_dir/controller/python/chip"
+ include_dirs = [
+ ".",
+ "${chip_root}/src/controller",
+ ]
+
+ sources = [
+ "ChipDeviceController-BleApplicationDelegate.cpp",
+ "ChipDeviceController-BleApplicationDelegate.h",
+ "ChipDeviceController-BlePlatformDelegate.cpp",
+ "ChipDeviceController-BlePlatformDelegate.h",
+ "ChipDeviceController-ScriptBinding.cpp",
+ ]
+
+ public_deps = [
+ "${chip_root}/src/controller",
+ "${chip_root}/src/lib",
+ "${chip_root}/src/lib/core",
+ "${chip_root}/src/lib/support",
+ "${chip_root}/src/platform",
+ "${chip_root}/src/transport",
+ ]
+ allow_circular_includes_from = [ "${chip_root}/src/controller" ]
+ configs += [ ":controller_wno_deprecate" ]
+}
+
+copy("deviceControllerPyCopy1") {
+ sources = [
+ "chip/ChipBleBase.py",
+ "chip/ChipBleUtility.py",
+ "chip/ChipBluezMgr.py",
+ "chip/ChipDeviceCtrl.py",
+ "chip/ChipStack.py",
+ "chip/ChipTLV.py",
+ "chip/ChipUtility.py",
+ "chip/__init__.py",
+ ]
+
+ public_deps = [ ":ChipDeviceCtrl" ]
+
+ outputs = [ "$root_out_dir/controller/python/chip/{{source_file_part}}" ]
+}
+
+copy("deviceControllerPyCopy2") {
+ sources = [
+ "build-chip-wheel.py",
+ "chip-device-ctrl.py",
+ ]
+
+ public_deps = [ ":ChipDeviceCtrl" ]
+
+ outputs = [ "$root_out_dir/controller/python/{{source_name_part}}" ]
+}
+
+action("python") {
+ script = "$root_out_dir/controller/python/build-chip-wheel"
+
+ inputs = [
+ "chip/ChipBleBase.py",
+ "chip/ChipBleUtility.py",
+ "chip/ChipBluezMgr.py",
+ "chip/ChipDeviceCtrl.py",
+ "chip/ChipStack.py",
+ "chip/ChipTLV.py",
+ "chip/ChipUtility.py",
+ "chip/__init__.py",
+ ]
+
+ args = [
+ "--package_name",
+ "chip",
+ "--build_number",
+ "0.0",
+ ]
+
+ public_deps = [
+ ":deviceControllerPyCopy1",
+ ":deviceControllerPyCopy2",
+ ]
+
+ pythontags = exec_script("//chip/python_gen_tags.py", [], "list lines", [])
+ pythontagsStr = string_join("", pythontags)
+ output_name = "chip-0.0-$pythontagsStr.whl"
+ outputs = [ "$root_out_dir/controller/python/chip/$output_name" ]
+}
diff --git a/src/controller/python/ChipDeviceController-BleApplicationDelegate.cpp b/src/controller/python/ChipDeviceController-BleApplicationDelegate.cpp
new file mode 100644
index 0000000..962323c
--- /dev/null
+++ b/src/controller/python/ChipDeviceController-BleApplicationDelegate.cpp
@@ -0,0 +1,27 @@
+/*
+ *
+ * Copyright (c) 2020 Project CHIP Authors
+ * Copyright (c) 2014-2017 Nest Labs, Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+#include "ChipDeviceController-BleApplicationDelegate.h"
+#include <ble/BleApplicationDelegate.h>
+
+void DeviceController_BleApplicationDelegate::NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT connObj)
+{
+ // NO-OP
+
+ // TODO Python queue-based implementation
+}
diff --git a/src/controller/python/ChipDeviceController-BleApplicationDelegate.h b/src/controller/python/ChipDeviceController-BleApplicationDelegate.h
new file mode 100644
index 0000000..e5f5c21
--- /dev/null
+++ b/src/controller/python/ChipDeviceController-BleApplicationDelegate.h
@@ -0,0 +1,29 @@
+/*
+ *
+ * Copyright (c) 2020 Project CHIP Authors
+ * Copyright (c) 2014-2017 Nest Labs, Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+#ifndef DEVICEMANAGER_BLEAPPLICATIONDELEGATE_H_
+#define DEVICEMANAGER_BLEAPPLICATIONDELEGATE_H_
+
+#include <ble/BleApplicationDelegate.h>
+
+class DeviceController_BleApplicationDelegate : public chip::Ble::BleApplicationDelegate
+{
+ void NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT connObj);
+};
+
+#endif /* DEVICEMANAGER_BLEAPPLICATIONDELEGATE_H_ */
diff --git a/src/controller/python/ChipDeviceController-BlePlatformDelegate.cpp b/src/controller/python/ChipDeviceController-BlePlatformDelegate.cpp
new file mode 100644
index 0000000..925ebeb
--- /dev/null
+++ b/src/controller/python/ChipDeviceController-BlePlatformDelegate.cpp
@@ -0,0 +1,121 @@
+/*
+ *
+ * Copyright (c) 2020 Project CHIP Authors
+ * Copyright (c) 2014-2017 Nest Labs, Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+#include "ChipDeviceController-BlePlatformDelegate.h"
+#include <ble/BlePlatformDelegate.h>
+
+using namespace chip::Ble;
+
+DeviceController_BlePlatformDelegate::DeviceController_BlePlatformDelegate(BleLayer * ble)
+{
+ Ble = ble;
+ writeCB = NULL;
+ subscribeCB = NULL;
+ closeCB = NULL;
+}
+
+bool DeviceController_BlePlatformDelegate::SubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj,
+ const chip::Ble::ChipBleUUID * svcId,
+ const chip::Ble::ChipBleUUID * charId)
+{
+ const bool subscribe = true;
+
+ if (subscribeCB && svcId && charId)
+ {
+ return subscribeCB(connObj, (void *) svcId->bytes, (void *) charId->bytes, subscribe);
+ }
+
+ return false;
+}
+
+bool DeviceController_BlePlatformDelegate::UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj,
+ const chip::Ble::ChipBleUUID * svcId,
+ const chip::Ble::ChipBleUUID * charId)
+{
+ const bool subscribe = true;
+
+ if (subscribeCB && svcId && charId)
+ {
+ return subscribeCB(connObj, (void *) svcId->bytes, (void *) charId->bytes, !subscribe);
+ }
+
+ return false;
+}
+
+bool DeviceController_BlePlatformDelegate::CloseConnection(BLE_CONNECTION_OBJECT connObj)
+{
+ if (closeCB)
+ {
+ return closeCB(connObj);
+ }
+
+ return false;
+}
+
+uint16_t DeviceController_BlePlatformDelegate::GetMTU(BLE_CONNECTION_OBJECT connObj) const
+{
+ // TODO Python queue-based implementation
+ return 0;
+}
+
+bool DeviceController_BlePlatformDelegate::SendIndication(BLE_CONNECTION_OBJECT connObj, const chip::Ble::ChipBleUUID * svcId,
+ const chip::Ble::ChipBleUUID * charId, chip::System::PacketBuffer * pBuf)
+{
+ // TODO Python queue-based implementation
+
+ // Release delegate's reference to pBuf. pBuf will be freed when both platform delegate and Chip stack free their references to
+ // it.
+ chip::System::PacketBuffer::Free(pBuf);
+
+ return false;
+}
+
+bool DeviceController_BlePlatformDelegate::SendWriteRequest(BLE_CONNECTION_OBJECT connObj, const chip::Ble::ChipBleUUID * svcId,
+ const chip::Ble::ChipBleUUID * charId,
+ chip::System::PacketBuffer * pBuf)
+{
+ bool ret = false;
+
+ if (writeCB && svcId && charId && pBuf)
+ {
+ ret = writeCB(connObj, (void *) svcId->bytes, (void *) charId->bytes, (void *) pBuf->Start(), pBuf->DataLength());
+ }
+
+ // Release delegate's reference to pBuf. pBuf will be freed when both platform delegate and Chip stack free their references to
+ // it. We release pBuf's reference here since its payload bytes were copied into a new NSData object by ChipBleMgr.py's writeCB,
+ // and in both the error and succees cases this code has no further use for the pBuf PacketBuffer.
+ chip::System::PacketBuffer::Free(pBuf);
+
+ return ret;
+}
+
+bool DeviceController_BlePlatformDelegate::SendReadRequest(BLE_CONNECTION_OBJECT connObj, const chip::Ble::ChipBleUUID * svcId,
+ const chip::Ble::ChipBleUUID * charId, chip::System::PacketBuffer * pBuf)
+{
+ // TODO Python queue-based implementation
+ return false;
+}
+
+bool DeviceController_BlePlatformDelegate::SendReadResponse(BLE_CONNECTION_OBJECT connObj, BLE_READ_REQUEST_CONTEXT requestContext,
+ const chip::Ble::ChipBleUUID * svcId,
+ const chip::Ble::ChipBleUUID * charId)
+{
+ // TODO Python queue-based implementation
+ return false;
+}
diff --git a/src/controller/python/ChipDeviceController-BlePlatformDelegate.h b/src/controller/python/ChipDeviceController-BlePlatformDelegate.h
new file mode 100644
index 0000000..a245ccb
--- /dev/null
+++ b/src/controller/python/ChipDeviceController-BlePlatformDelegate.h
@@ -0,0 +1,64 @@
+/*
+ *
+ *
+ * Copyright (c) 2020 Project CHIP Authors
+ * Copyright (c) 2014-2017 Nest Labs, Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+#ifndef DEVICEMANAGER_BLEPLATFORMDELEGATE_H_
+#define DEVICEMANAGER_BLEPLATFORMDELEGATE_H_
+
+#include <ble/BleLayer.h>
+#include <ble/BlePlatformDelegate.h>
+
+typedef bool (*WriteBleCharacteristicCBFunct)(void * connObj, void * svcId, void * charId, void * buffer, uint16_t length);
+typedef bool (*SubscribeBleCharacteristicCBFunct)(void * connObj, void * svcId, void * charId, bool subscribe);
+typedef bool (*CloseBleCBFunct)(void * connObj);
+
+class DeviceController_BlePlatformDelegate : public chip::Ble::BlePlatformDelegate
+{
+public:
+ // ctor
+ DeviceController_BlePlatformDelegate(chip::Ble::BleLayer * ble);
+
+ // Set callback used to send GATT write request
+ inline void SetWriteCharCB(WriteBleCharacteristicCBFunct cb) { writeCB = cb; };
+ inline void SetSubscribeCharCB(SubscribeBleCharacteristicCBFunct cb) { subscribeCB = cb; };
+ inline void SetCloseCB(CloseBleCBFunct cb) { closeCB = cb; };
+
+ // Virtuals from BlePlatformDelegate:
+ bool SubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const chip::Ble::ChipBleUUID * svcId,
+ const chip::Ble::ChipBleUUID * charId);
+ bool UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const chip::Ble::ChipBleUUID * svcId,
+ const chip::Ble::ChipBleUUID * charId);
+ bool CloseConnection(BLE_CONNECTION_OBJECT connObj);
+ uint16_t GetMTU(BLE_CONNECTION_OBJECT connObj) const;
+ bool SendIndication(BLE_CONNECTION_OBJECT connObj, const chip::Ble::ChipBleUUID * svcId, const chip::Ble::ChipBleUUID * charId,
+ chip::System::PacketBuffer * pBuf);
+ bool SendWriteRequest(BLE_CONNECTION_OBJECT connObj, const chip::Ble::ChipBleUUID * svcId,
+ const chip::Ble::ChipBleUUID * charId, chip::System::PacketBuffer * pBuf);
+ bool SendReadRequest(BLE_CONNECTION_OBJECT connObj, const chip::Ble::ChipBleUUID * svcId, const chip::Ble::ChipBleUUID * charId,
+ chip::System::PacketBuffer * pBuf);
+ bool SendReadResponse(BLE_CONNECTION_OBJECT connObj, BLE_READ_REQUEST_CONTEXT requestContext,
+ const chip::Ble::ChipBleUUID * svcId, const chip::Ble::ChipBleUUID * charId);
+
+private:
+ chip::Ble::BleLayer * Ble;
+ WriteBleCharacteristicCBFunct writeCB;
+ SubscribeBleCharacteristicCBFunct subscribeCB;
+ CloseBleCBFunct closeCB;
+};
+
+#endif /* DEVICEMANAGER_BLEPLATFORMDELEGATE_H_ */
diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp
new file mode 100644
index 0000000..459455f
--- /dev/null
+++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp
@@ -0,0 +1,693 @@
+/*
+ *
+ * Copyright (c) 2020 Project CHIP Authors
+ * Copyright (c) 2019-2020 Google LLC.
+ * Copyright (c) 2013-2018 Nest Labs, Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * @file
+ * Implementation of the native methods expected by the Python
+ * version of Chip Device Manager.
+ *
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <system/SystemError.h>
+#include <system/SystemLayer.h>
+
+#include <inttypes.h>
+#include <net/if.h>
+
+#if CONFIG_NETWORK_LAYER_BLE
+#include "ChipDeviceController-BleApplicationDelegate.h"
+#include "ChipDeviceController-BlePlatformDelegate.h"
+#endif /* CONFIG_NETWORK_LAYER_BLE */
+
+#include "CHIPDeviceController.h"
+
+#include <support/CodeUtils.h>
+#include <support/DLLUtil.h>
+#include <support/logging/CHIPLogging.h>
+
+using namespace chip::Ble;
+using namespace chip::DeviceController;
+
+extern "C" {
+typedef void * (*GetBleEventCBFunct)(void);
+typedef void (*ConstructBytesArrayFunct)(const uint8_t * dataBuf, uint32_t dataLen);
+typedef void (*LogMessageFunct)(uint64_t time, uint64_t timeUS, const char * moduleName, uint8_t category, const char * msg);
+}
+
+enum BleEventType
+{
+ kBleEventType_Rx = 1,
+ kBleEventType_Tx = 2,
+ kBleEventType_Subscribe = 3,
+ kBleEventType_Disconnect = 4
+};
+
+enum BleSubscribeOperation
+{
+ kBleSubOp_Subscribe = 1,
+ kBleSubOp_Unsubscribe = 2
+};
+
+class BleEventBase
+{
+public:
+ int32_t eventType;
+};
+
+class BleRxEvent : public BleEventBase
+{
+public:
+ void * connObj;
+ void * svcId;
+ void * charId;
+ void * buffer;
+ uint16_t length;
+};
+
+class BleTxEvent : public BleEventBase
+{
+public:
+ void * connObj;
+ void * svcId;
+ void * charId;
+ bool status;
+};
+
+class BleSubscribeEvent : public BleEventBase
+{
+public:
+ void * connObj;
+ void * svcId;
+ void * charId;
+ int32_t operation;
+ bool status;
+};
+
+class BleDisconnectEvent : public BleEventBase
+{
+public:
+ void * connObj;
+ int32_t error;
+};
+
+static chip::System::Layer sSystemLayer;
+static chip::InetLayer sInetLayer;
+
+// NOTE: Remote device ID is in sync with the echo server device id
+// At some point, we may want to add an option to connect to a device without
+// knowing its id, because the ID can be learned on the first response that is received.
+chip::NodeId kLocalDeviceId = 112233;
+chip::NodeId kRemoteDeviceId = 12344321;
+
+#if CONFIG_NETWORK_LAYER_BLE
+static BleLayer sBle;
+static BLEEndPoint * spBleEndPoint = NULL;
+static DeviceController_BlePlatformDelegate sBlePlatformDelegate(&sBle);
+static DeviceController_BleApplicationDelegate sBleApplicationDelegate;
+
+static volatile GetBleEventCBFunct GetBleEventCB = NULL;
+
+static int BleWakePipe[2];
+
+union
+{
+ CompleteHandler General;
+} sOnComplete;
+
+ErrorHandler sOnError;
+
+#endif /* CONFIG_NETWORK_LAYER_BLE */
+
+extern "C" {
+// Trampolined callback types
+CHIP_ERROR nl_Chip_DeviceController_DriveIO(uint32_t sleepTimeMS);
+
+#if CONFIG_NETWORK_LAYER_BLE
+CHIP_ERROR nl_Chip_DeviceController_WakeForBleIO();
+CHIP_ERROR nl_Chip_DeviceController_SetBleEventCB(GetBleEventCBFunct getBleEventCB);
+CHIP_ERROR nl_Chip_DeviceController_SetBleWriteCharacteristic(WriteBleCharacteristicCBFunct writeBleCharacteristicCB);
+CHIP_ERROR nl_Chip_DeviceController_SetBleSubscribeCharacteristic(SubscribeBleCharacteristicCBFunct subscribeBleCharacteristicCB);
+CHIP_ERROR nl_Chip_DeviceController_SetBleClose(CloseBleCBFunct closeBleCB);
+#endif /* CONFIG_NETWORK_LAYER_BLE */
+
+CHIP_ERROR nl_Chip_DeviceController_NewDeviceController(chip::DeviceController::ChipDeviceController ** outDevCtrl);
+CHIP_ERROR nl_Chip_DeviceController_DeleteDeviceController(chip::DeviceController::ChipDeviceController * devCtrl);
+
+#if CONFIG_NETWORK_LAYER_BLE
+CHIP_ERROR nl_Chip_DeviceController_ValidateBTP(chip::DeviceController::ChipDeviceController * devCtrl,
+ BLE_CONNECTION_OBJECT connObj, CompleteHandler onComplete, ErrorHandler onError);
+#endif /* CONFIG_NETWORK_LAYER_BLE */
+
+bool nl_Chip_DeviceController_IsConnected(chip::DeviceController::ChipDeviceController * devCtrl);
+void nl_Chip_DeviceController_Close(chip::DeviceController::ChipDeviceController * devCtrl);
+uint8_t nl_Chip_DeviceController_GetLogFilter();
+void nl_Chip_DeviceController_SetLogFilter(uint8_t category);
+
+CHIP_ERROR nl_Chip_Stack_Init();
+CHIP_ERROR nl_Chip_Stack_Shutdown();
+const char * nl_Chip_Stack_ErrorToString(CHIP_ERROR err);
+const char * nl_Chip_Stack_StatusReportToString(uint32_t profileId, uint16_t statusCode);
+void nl_Chip_Stack_SetLogFunct(LogMessageFunct logFunct);
+}
+
+CHIP_ERROR nl_Chip_DeviceController_NewDeviceController(chip::DeviceController::ChipDeviceController ** outDevCtrl)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+
+ *outDevCtrl = new chip::DeviceController::ChipDeviceController();
+ VerifyOrExit(*outDevCtrl != NULL, err = CHIP_ERROR_NO_MEMORY);
+
+ err = (*outDevCtrl)->Init(kLocalDeviceId, &sSystemLayer, &sInetLayer);
+ SuccessOrExit(err);
+
+exit:
+ if (err != CHIP_NO_ERROR && *outDevCtrl != NULL)
+ {
+ delete *outDevCtrl;
+ *outDevCtrl = NULL;
+ }
+ return err;
+}
+
+CHIP_ERROR nl_Chip_DeviceController_DeleteDeviceController(chip::DeviceController::ChipDeviceController * devCtrl)
+{
+ if (devCtrl != NULL)
+ {
+ devCtrl->Shutdown();
+ delete devCtrl;
+ }
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR nl_Chip_DeviceController_DriveIO(uint32_t sleepTimeMS)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+
+#if !CHIP_SYSTEM_CONFIG_USE_SOCKETS
+
+ ExitNow(err = CHIP_ERROR_NOT_IMPLEMENTED);
+
+#else /* CHIP_SYSTEM_CONFIG_USE_SOCKETS */
+ struct timeval sleepTime;
+ fd_set readFDs, writeFDs, exceptFDs;
+ int maxFDs = 0;
+#if CONFIG_NETWORK_LAYER_BLE
+ uint8_t bleWakeByte;
+ bool result = false;
+ chip::System::PacketBuffer * msgBuf;
+ ChipBleUUID svcId, charId;
+ union
+ {
+ const BleEventBase * ev;
+ const BleTxEvent * txEv;
+ const BleRxEvent * rxEv;
+ const BleSubscribeEvent * subscribeEv;
+ const BleDisconnectEvent * dcEv;
+ } evu;
+#endif /* CONFIG_NETWORK_LAYER_BLE */
+
+ FD_ZERO(&readFDs);
+ FD_ZERO(&writeFDs);
+ FD_ZERO(&exceptFDs);
+
+ sleepTime.tv_sec = sleepTimeMS / 1000;
+ sleepTime.tv_usec = (sleepTimeMS % 1000) * 1000;
+
+ if (sSystemLayer.State() == chip::System::kLayerState_Initialized)
+ sSystemLayer.PrepareSelect(maxFDs, &readFDs, &writeFDs, &exceptFDs, sleepTime);
+
+ if (sInetLayer.State == chip::InetLayer::kState_Initialized)
+ sInetLayer.PrepareSelect(maxFDs, &readFDs, &writeFDs, &exceptFDs, sleepTime);
+
+#if CONFIG_NETWORK_LAYER_BLE
+ // Add read end of BLE wake pipe to readFDs.
+ FD_SET(BleWakePipe[0], &readFDs);
+
+ if (BleWakePipe[0] + 1 > maxFDs)
+ maxFDs = BleWakePipe[0] + 1;
+#endif /* CONFIG_NETWORK_LAYER_BLE */
+
+ int selectRes = select(maxFDs, &readFDs, &writeFDs, &exceptFDs, &sleepTime);
+ VerifyOrExit(selectRes >= 0, err = chip::System::MapErrorPOSIX(errno));
+
+#if CONFIG_NETWORK_LAYER_BLE
+ // Drive IO to InetLayer and/or BleLayer.
+ if (FD_ISSET(BleWakePipe[0], &readFDs))
+ {
+ while (true)
+ {
+ if (read(BleWakePipe[0], &bleWakeByte, 1) == -1)
+ {
+ if (errno == EAGAIN)
+ break;
+ else
+ {
+ err = errno;
+ printf("bleWakePipe calling ExitNow()\n");
+ ExitNow();
+ }
+ }
+
+ if (GetBleEventCB)
+ {
+ evu.ev = (const BleEventBase *) GetBleEventCB();
+
+ if (evu.ev)
+ {
+ switch (evu.ev->eventType)
+ {
+ case kBleEventType_Rx:
+ // build a packet buffer from the rxEv and send to blelayer.
+ msgBuf = chip::System::PacketBuffer::New();
+ VerifyOrExit(msgBuf != NULL, err = CHIP_ERROR_NO_MEMORY);
+
+ memcpy(msgBuf->Start(), evu.rxEv->buffer, evu.rxEv->length);
+ msgBuf->SetDataLength(evu.rxEv->length);
+
+ // copy the svcId and charId from the event.
+ memcpy(svcId.bytes, evu.rxEv->svcId, sizeof(svcId.bytes));
+ memcpy(charId.bytes, evu.rxEv->charId, sizeof(charId.bytes));
+
+ result = sBle.HandleIndicationReceived(evu.txEv->connObj, &svcId, &charId, msgBuf);
+
+ if (!result)
+ {
+ chip::System::PacketBuffer::Free(msgBuf);
+ }
+
+ msgBuf = NULL;
+ break;
+
+ case kBleEventType_Tx:
+ // copy the svcId and charId from the event.
+ memcpy(svcId.bytes, evu.txEv->svcId, sizeof(svcId.bytes));
+ memcpy(charId.bytes, evu.txEv->charId, sizeof(charId.bytes));
+
+ result = sBle.HandleWriteConfirmation(evu.txEv->connObj, &svcId, &charId);
+ break;
+
+ case kBleEventType_Subscribe:
+ memcpy(svcId.bytes, evu.subscribeEv->svcId, sizeof(svcId.bytes));
+ memcpy(charId.bytes, evu.subscribeEv->charId, sizeof(charId.bytes));
+
+ switch (evu.subscribeEv->operation)
+ {
+ case kBleSubOp_Subscribe:
+ if (evu.subscribeEv->status)
+ {
+ result = sBle.HandleSubscribeComplete(evu.subscribeEv->connObj, &svcId, &charId);
+ }
+ else
+ {
+ sBle.HandleConnectionError(evu.subscribeEv->connObj, BLE_ERROR_GATT_SUBSCRIBE_FAILED);
+ }
+ break;
+
+ case kBleSubOp_Unsubscribe:
+ if (evu.subscribeEv->status)
+ {
+ result = sBle.HandleUnsubscribeComplete(evu.subscribeEv->connObj, &svcId, &charId);
+ }
+ else
+ {
+ sBle.HandleConnectionError(evu.subscribeEv->connObj, BLE_ERROR_GATT_UNSUBSCRIBE_FAILED);
+ }
+ break;
+
+ default:
+ printf("Error: unhandled subscribe operation. Calling ExitNow()\n");
+ ExitNow();
+ break;
+ }
+ break;
+
+ case kBleEventType_Disconnect:
+ sBle.HandleConnectionError(evu.dcEv->connObj, evu.dcEv->error);
+ break;
+
+ default:
+ printf("Error: unhandled sBle EventType. Calling ExitNow()\n");
+ ExitNow();
+ break;
+ }
+ }
+ else
+ {
+ printf("no event\n");
+ }
+ }
+ }
+
+ // Don't bother InetLayer if we only got BLE IO.
+ selectRes--;
+ }
+#endif /* CONFIG_NETWORK_LAYER_BLE */
+
+ if (sSystemLayer.State() == chip::System::kLayerState_Initialized)
+ sSystemLayer.HandleSelectResult(selectRes, &readFDs, &writeFDs, &exceptFDs);
+
+ if (sInetLayer.State == chip::InetLayer::kState_Initialized)
+ sInetLayer.HandleSelectResult(selectRes, &readFDs, &writeFDs, &exceptFDs);
+
+#endif /* CHIP_SYSTEM_CONFIG_USE_SOCKETS */
+
+exit:
+ return err;
+}
+
+#if CONFIG_NETWORK_LAYER_BLE
+CHIP_ERROR nl_Chip_DeviceController_WakeForBleIO()
+{
+ if (BleWakePipe[1] == 0)
+ {
+ return CHIP_ERROR_INCORRECT_STATE;
+ }
+ // Write a single byte to the BLE wake pipe. This wakes the IO thread's select loop for BLE input.
+ if (write(BleWakePipe[1], "x", 1) == -1 && errno != EAGAIN)
+ {
+ return errno;
+ }
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR nl_Chip_DeviceController_SetBleEventCB(GetBleEventCBFunct getBleEventCB)
+{
+ GetBleEventCB = getBleEventCB;
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR nl_Chip_DeviceController_SetBleWriteCharacteristic(WriteBleCharacteristicCBFunct writeBleCharacteristicCB)
+{
+ sBlePlatformDelegate.SetWriteCharCB(writeBleCharacteristicCB);
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR nl_Chip_DeviceController_SetBleSubscribeCharacteristic(SubscribeBleCharacteristicCBFunct subscribeBleCharacteristicCB)
+{
+ sBlePlatformDelegate.SetSubscribeCharCB(subscribeBleCharacteristicCB);
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR nl_Chip_DeviceController_SetBleClose(CloseBleCBFunct closeBleCB)
+{
+ sBlePlatformDelegate.SetCloseCB(closeBleCB);
+ return CHIP_NO_ERROR;
+}
+
+#endif /* CONFIG_NETWORK_LAYER_BLE */
+
+void nl_Chip_DeviceController_Close(chip::DeviceController::ChipDeviceController * devCtrl)
+{
+#if CONFIG_NETWORK_LAYER_BLE
+ if (spBleEndPoint != NULL)
+ {
+ spBleEndPoint->Close();
+ spBleEndPoint = NULL;
+ }
+#endif
+}
+
+#if CONFIG_NETWORK_LAYER_BLE
+static void HandleBleConnectComplete(BLEEndPoint * endPoint, BLE_ERROR err)
+{
+ if (err != BLE_NO_ERROR)
+ {
+ ChipLogError(MessageLayer, "ChipoBle con failed %d", err);
+ spBleEndPoint->Abort();
+ sOnError(NULL, NULL, err, NULL);
+ }
+ else
+ {
+ ChipLogProgress(Controller, "ChipoBle con complete\n");
+ sOnComplete.General(NULL, NULL);
+ spBleEndPoint->Close();
+ }
+ spBleEndPoint = NULL;
+}
+
+static void HandleBleConnectionClosed(BLEEndPoint * endPoint, BLE_ERROR err)
+{
+ ChipLogProgress(Controller, "ChipoBle con close\n");
+}
+
+CHIP_ERROR nl_Chip_DeviceController_ValidateBTP(chip::DeviceController::ChipDeviceController * devCtrl,
+ BLE_CONNECTION_OBJECT connObj, CompleteHandler onComplete, ErrorHandler onError)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+
+ ChipLogProgress(Controller, "begin BTPConnect validation");
+
+ err = sBle.NewBleEndPoint(&spBleEndPoint, connObj, kBleRole_Central, false);
+ SuccessOrExit(err);
+
+ spBleEndPoint->mAppState = NULL;
+ spBleEndPoint->OnConnectComplete = HandleBleConnectComplete;
+ spBleEndPoint->OnConnectionClosed = HandleBleConnectionClosed;
+
+ sOnComplete.General = onComplete;
+ sOnError = onError;
+ err = spBleEndPoint->StartConnect();
+ SuccessOrExit(err);
+
+exit:
+ return err;
+}
+#endif /* CONFIG_NETWORK_LAYER_BLE */
+
+bool nl_Chip_DeviceController_IsConnected(chip::DeviceController::ChipDeviceController * devCtrl)
+{
+ return devCtrl->IsConnected();
+}
+
+const char * nl_Chip_DeviceController_ErrorToString(CHIP_ERROR err)
+{
+ return chip::ErrorStr(err);
+}
+
+const char * nl_Chip_DeviceController_StatusReportToString(uint32_t profileId, uint16_t statusCode)
+{
+ // return chip::StatusReportStr(profileId, statusCode);
+ return NULL;
+}
+
+uint8_t nl_Chip_DeviceController_GetLogFilter()
+{
+#if _CHIP_USE_LOGGING
+ return chip::Logging::GetLogFilter();
+#else
+ return chip::Logging::kLogCategory_None;
+#endif
+}
+
+void nl_Chip_DeviceController_SetLogFilter(uint8_t category)
+{
+#if _CHIP_USE_LOGGING
+ chip::Logging::SetLogFilter(category);
+#endif
+}
+
+CHIP_ERROR nl_Chip_Stack_Init()
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+
+#if CHIP_SYSTEM_CONFIG_USE_SOCKETS && CONFIG_NETWORK_LAYER_BLE
+ int flags;
+#endif /* CHIP_SYSTEM_CONFIG_USE_SOCKETS && CONFIG_NETWORK_LAYER_BLE */
+
+#if !CHIP_SYSTEM_CONFIG_USE_SOCKETS
+
+ ExitNow(err = CHIP_ERROR_NOT_IMPLEMENTED);
+
+#else /* CHIP_SYSTEM_CONFIG_USE_SOCKETS */
+
+ if (sSystemLayer.State() == chip::System::kLayerState_Initialized)
+ ExitNow();
+
+ err = sSystemLayer.Init(NULL);
+ SuccessOrExit(err);
+
+ if (sInetLayer.State == chip::InetLayer::kState_Initialized)
+ ExitNow();
+
+ // Initialize the InetLayer object.
+ err = sInetLayer.Init(sSystemLayer, NULL);
+ SuccessOrExit(err);
+
+#if CONFIG_NETWORK_LAYER_BLE
+ // Initialize the BleLayer object. For now, assume Device Controller is always a central.
+ err = sBle.Init(&sBlePlatformDelegate, &sBleApplicationDelegate, &sSystemLayer);
+ SuccessOrExit(err);
+
+ // Create BLE wake pipe and make it non-blocking.
+ if (pipe(BleWakePipe) == -1)
+ {
+ err = chip::System::MapErrorPOSIX(errno);
+ ExitNow();
+ }
+
+ // Make read end non-blocking.
+ flags = fcntl(BleWakePipe[0], F_GETFL);
+ if (flags == -1)
+ {
+ err = chip::System::MapErrorPOSIX(errno);
+ ExitNow();
+ }
+
+ flags |= O_NONBLOCK;
+ if (fcntl(BleWakePipe[0], F_SETFL, flags) == -1)
+ {
+ err = chip::System::MapErrorPOSIX(errno);
+ ExitNow();
+ }
+
+ // Make write end non-blocking.
+ flags = fcntl(BleWakePipe[1], F_GETFL);
+ if (flags == -1)
+ {
+ err = chip::System::MapErrorPOSIX(errno);
+ ExitNow();
+ }
+
+ flags |= O_NONBLOCK;
+ if (fcntl(BleWakePipe[1], F_SETFL, flags) == -1)
+ {
+ err = chip::System::MapErrorPOSIX(errno);
+ ExitNow();
+ }
+#endif /* CONFIG_NETWORK_LAYER_BLE */
+#endif /* CHIP_SYSTEM_CONFIG_USE_SOCKETS */
+
+exit:
+ if (err != CHIP_NO_ERROR)
+ nl_Chip_Stack_Shutdown();
+
+ return err;
+}
+
+CHIP_ERROR nl_Chip_Stack_Shutdown()
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+
+ if (sInetLayer.State == chip::InetLayer::kState_NotInitialized)
+ ExitNow();
+
+ if (sSystemLayer.State() == chip::System::kLayerState_NotInitialized)
+ ExitNow();
+
+exit:
+ return err;
+}
+
+const char * nl_Chip_Stack_ErrorToString(CHIP_ERROR err)
+{
+ return chip::ErrorStr(err);
+}
+
+const char * nl_Chip_Stack_StatusReportToString(uint32_t profileId, uint16_t statusCode)
+{
+ // return chip::StatusReportStr(profileId, statusCode);
+ return NULL;
+}
+
+#if _CHIP_USE_LOGGING && CHIP_LOG_ENABLE_DYNAMIC_LOGING_FUNCTION
+
+// A pointer to the python logging function.
+static LogMessageFunct sLogMessageFunct = NULL;
+
+// This function is called by the Chip logging code whenever a developer message
+// is logged. It serves as glue to adapt the logging arguments to what is expected
+// by the python code.
+// NOTE that this function MUST be thread-safe.
+static void LogMessageToPython(uint8_t module, uint8_t category, const char * msg, va_list ap)
+{
+ if (IsCategoryEnabled(category))
+ {
+ // Capture the timestamp of the log message.
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+
+ // Get the module name
+ char moduleName[nlChipLoggingModuleNameLen + 1];
+ ::chip:: ::Logging::GetModuleName(moduleName, module);
+
+ // Format the log message into a dynamic memory buffer, growing the
+ // buffer as needed to fit the message.
+ char * msgBuf = NULL;
+ size_t msgBufSize = 0;
+ size_t msgSize = 0;
+ constexpr size_t kInitialBufSize = 120;
+ do
+ {
+ va_list apCopy;
+ va_copy(apCopy, ap);
+
+ msgBufSize = max(msgSize + 1, kInitialBufSize);
+ msgBuf = (char *) realloc(msgBuf, msgBufSize);
+ if (msgBuf == NULL)
+ {
+ return;
+ }
+
+ int res = vsnprintf(msgBuf, msgBufSize, msg, apCopy);
+ if (res < 0)
+ {
+ return;
+ }
+ msgSize = (size_t) res;
+
+ va_end(apCopy);
+ } while (msgSize >= msgBufSize);
+
+ // Call the configured python logging function.
+ sLogMessageFunct((int64_t) tv.tv_sec, (int64_t) tv.tv_usec, moduleName, category, msgBuf);
+
+ // Release the message buffer.
+ free(msgBuf);
+ }
+}
+
+void nl_Chip_Stack_SetLogFunct(LogMessageFunct logFunct)
+{
+ if (logFunct != NULL)
+ {
+ sLogMessageFunct = logFunct;
+ ::chip::Logging::SetLogFunct(LogMessageToPython);
+ }
+ else
+ {
+ sLogMessageFunct = NULL;
+ ::chip::Logging::SetLogFunct(NULL);
+ }
+}
+
+#else // CHIP_LOG_ENABLE_DYNAMIC_LOGING_FUNCTION
+
+void nl_Chip_Stack_SetLogFunct(LogMessageFunct logFunct) {}
+
+#endif // CHIP_LOG_ENABLE_DYNAMIC_LOGING_FUNCTION
diff --git a/src/controller/python/build-chip-wheel.py b/src/controller/python/build-chip-wheel.py
new file mode 100644
index 0000000..0577ceb
--- /dev/null
+++ b/src/controller/python/build-chip-wheel.py
@@ -0,0 +1,196 @@
+#
+# Copyright (c) 2020 Project CHIP Authors
+# Copyright (c) 2019 Google LLC.
+# All rights reserved.
+#
+# 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.
+#
+
+#
+# Description:
+# Builds a Python wheel package for CHIP.
+#
+
+from __future__ import absolute_import
+from datetime import datetime
+from setuptools import setup
+from wheel.bdist_wheel import bdist_wheel
+
+import argparse
+import getpass
+import os
+import platform
+import shutil
+import stat
+
+
+parser = argparse.ArgumentParser(description='build the pip package for chip using chip components generated during the build and python source code')
+parser.add_argument('--package_name', default='chip', help='configure the python package name')
+parser.add_argument('--build_number', default='0.0', help='configure the chip build number')
+
+args = parser.parse_args()
+
+chipDLLName = '_ChipDeviceCtrl.so'
+deviceManagerShellName = 'chip-device-ctrl'
+chipControllerShellInstalledName = os.path.splitext(deviceManagerShellName)[0]
+packageName = args.package_name
+chipPackageVer = args.build_number
+
+# Record the current directory at the start of execution.
+curDir = os.curdir
+
+# Presume that the current directory is the build directory.
+buildDir = os.path.dirname(os.path.abspath(__file__))
+
+# Use a temporary directory within the build directory to assemble the components
+# for the installable package.
+tmpDir = os.path.join(buildDir, 'chip-wheel-components')
+
+try:
+
+ #
+ # Perform a series of setup steps prior to creating the chip package...
+ #
+
+ # Create the temporary components directory.
+ if os.path.isdir(tmpDir):
+ shutil.rmtree(tmpDir)
+ os.mkdir(tmpDir)
+
+ # Switch to the temporary directory. (Foolishly, setuptools relies on the current directory
+ # for many of its features.)
+ os.chdir(tmpDir)
+
+ # Make a copy of the chip package in the tmp directory and ensure the copied
+ # directory is writable.
+ chipPackageDir = os.path.join(tmpDir, packageName)
+ if os.path.isdir(chipPackageDir):
+ shutil.rmtree(chipPackageDir)
+ shutil.copytree(os.path.join(buildDir, 'chip'), chipPackageDir)
+ os.chmod(chipPackageDir, os.stat(chipPackageDir).st_mode|stat.S_IWUSR)
+
+ # Copy the chip wrapper DLL from where libtool places it (.libs) into
+ # the root of the chip package directory. This is necessary because
+ # setuptools will only add package data files that are relative to the
+ # associated package source root.
+ shutil.copy2(os.path.join(buildDir, 'chip', chipDLLName),
+ os.path.join(chipPackageDir, chipDLLName))
+
+ # Make a copy of the Chip Device Controller Shell script in the tmp directory,
+ # but without the .py suffix. This is how we want it to appear when installed
+ # by the wheel.
+ shutil.copy2(os.path.join(buildDir, deviceManagerShellName),
+ os.path.join(tmpDir, chipControllerShellInstalledName))
+
+ # Search for the CHIP LICENSE file in the parents of the source
+ # directory and make a copy of the file called LICENSE.txt in the tmp
+ # directory.
+ def _AllDirsToRoot(dir):
+ dir = os.path.abspath(dir)
+ while True:
+ yield dir
+ parent = os.path.dirname(dir)
+ if parent == '' or parent == dir:
+ break
+ dir = parent
+ for dir in _AllDirsToRoot(buildDir):
+ licFileName = os.path.join(dir, 'LICENSE')
+ if os.path.isfile(licFileName):
+ shutil.copy2(licFileName,
+ os.path.join(tmpDir, 'LICENSE.txt'))
+ break
+ else:
+ raise FileNotFoundError('Unable to find CHIP LICENSE file')
+
+ # Define a custom version of the bdist_wheel command that configures the
+ # resultant wheel as platform-specific (i.e. not "pure").
+ class bdist_wheel_override(bdist_wheel):
+ def finalize_options(self):
+ bdist_wheel.finalize_options(self)
+ self.root_is_pure = False
+
+ # Generate a description string with information on how/when the package
+ # was built.
+
+ buildDescription = 'Build by %s on %s\n' % (
+ getpass.getuser(),
+ datetime.now().strftime('%Y/%m/%d %H:%M:%S'))
+
+ # Select required packages based on the target system.
+ if platform.system() == 'Linux':
+ requiredPackages = [
+ 'dbus-python',
+ 'pgi'
+ ]
+ else:
+ requiredPackages = []
+
+ #
+ # Build the chip package...
+ #
+
+ # Invoke the setuptools 'bdist_wheel' command to generate a wheel containing
+ # the CHIP python packages, shared libraries and scripts.
+ setup(
+ name=packageName,
+ version=chipPackageVer,
+ description='Python-base APIs and tools for CHIP.',
+ long_description=buildDescription,
+ url='https://github.com/project-chip/connectedhomeip',
+ license='Apache',
+ classifiers=[
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Apache Software License',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ ],
+ python_requires='>=2.7',
+ packages=[
+ packageName # Arrange to install a package named "chip"
+ ],
+ package_dir={
+ '':tmpDir, # By default, look in the tmp directory for packages/modules to be included.
+ },
+ package_data={
+ packageName:[
+ chipDLLName # Include the wrapper DLL as package data in the "chip" package.
+ ]
+ },
+ scripts=[ # Install the Device controller Shell as an executable script in the 'bin' directory.
+ os.path.join(tmpDir, chipControllerShellInstalledName)
+ ],
+ install_requires=requiredPackages,
+ options={
+ 'bdist_wheel':{
+ 'universal':False,
+ 'dist_dir':buildDir # Place the generated .whl in the build directory.
+ },
+ 'egg_info':{
+ 'egg_base':tmpDir # Place the .egg-info subdirectory in the tmp directory.
+ }
+ },
+ cmdclass={
+ 'bdist_wheel':bdist_wheel_override
+ },
+ script_args=[ 'clean', '--all', 'bdist_wheel' ]
+ )
+
+finally:
+
+ # Switch back to the initial current directory.
+ os.chdir(curDir)
+
+ # Remove the temporary directory.
+ if os.path.isdir(tmpDir):
+ shutil.rmtree(tmpDir)
diff --git a/src/controller/python/chip-device-ctrl.py b/src/controller/python/chip-device-ctrl.py
new file mode 100755
index 0000000..bd084e4
--- /dev/null
+++ b/src/controller/python/chip-device-ctrl.py
@@ -0,0 +1,429 @@
+#!/usr/bin/env python
+
+#
+# Copyright (c) 2020 Project CHIP Authors
+# Copyright (c) 2013-2018 Nest Labs, Inc.
+# All rights reserved.
+#
+# 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.
+#
+
+#
+# @file
+# This file implements the Python-based Chip Device Controller Shell.
+#
+
+from __future__ import absolute_import
+from __future__ import print_function
+from builtins import range
+import sys
+import os
+import platform
+from optparse import OptionParser, OptionValueError
+import shlex
+import base64
+import textwrap
+import string
+from cmd import Cmd
+from six.moves import range
+
+# Extend sys.path with one or more directories, relative to the location of the
+# running script, in which the chip package might be found . This makes it
+# possible to run the device manager shell from a non-standard install location,
+# as well as directly from its location the CHIP source tree.
+#
+# Note that relative package locations are prepended to sys.path so as to give
+# the local version of the package higher priority over any version installed in
+# a standard location.
+#
+scriptDir = os.path.dirname(os.path.abspath(__file__))
+relChipPackageInstallDirs = [
+ ".",
+ "../lib/python",
+ "../lib/python%s.%s" % (sys.version_info.major, sys.version_info.minor),
+ "../lib/Python%s%s" % (sys.version_info.major, sys.version_info.minor),
+]
+for relInstallDir in relChipPackageInstallDirs:
+ absInstallDir = os.path.realpath(os.path.join(scriptDir, relInstallDir))
+ if os.path.isdir(os.path.join(absInstallDir, "chip")):
+ sys.path.insert(0, absInstallDir)
+
+from chip import ChipDeviceCtrl
+from chip import ChipStack
+
+if platform.system() == "Darwin":
+ print("ChipCoreBluetoothMgr not yet ready")
+ # from chip.ChipCoreBluetoothMgr import CoreBluetoothManager as BleManager
+elif sys.platform.startswith("linux"):
+ from chip.ChipBluezMgr import BluezManager as BleManager
+from chip.ChipBleUtility import FAKE_CONN_OBJ_VALUE
+
+
+def DecodeBase64Option(option, opt, value):
+ try:
+ return base64.standard_b64decode(value)
+ except TypeError:
+ raise OptionValueError("option %s: invalid base64 value: %r" % (opt, value))
+
+
+def DecodeHexIntOption(option, opt, value):
+ try:
+ return int(value, 16)
+ except ValueError:
+ raise OptionValueError("option %s: invalid value: %r" % (opt, value))
+
+
+class DeviceMgrCmd(Cmd):
+ def __init__(self, rendezvousAddr=None):
+ self.lastNetworkId = None
+
+ Cmd.__init__(self)
+
+ Cmd.identchars = string.ascii_letters + string.digits + "-"
+
+ if sys.stdin.isatty():
+ self.prompt = "chip-device-ctrl > "
+ else:
+ self.use_rawinput = 0
+ self.prompt = ""
+
+ DeviceMgrCmd.command_names.sort()
+
+ self.bleMgr = None
+
+ self.devCtrl = ChipDeviceCtrl.ChipDeviceController()
+
+ self.historyFileName = os.path.expanduser("~/.chip-device-ctrl-history")
+
+ try:
+ import readline
+
+ if "libedit" in readline.__doc__:
+ readline.parse_and_bind("bind ^I rl_complete")
+ readline.set_completer_delims(" ")
+ try:
+ readline.read_history_file(self.historyFileName)
+ except IOError:
+ pass
+ except ImportError:
+ pass
+
+ command_names = [
+ "close",
+ "btp-connect",
+ "ble-scan",
+ "ble-connect",
+ "ble-disconnect",
+ "ble-scan-connect",
+ "ble-adapter-select",
+ "ble-adapter-print",
+ "ble-debug-log",
+ ]
+
+ def parseline(self, line):
+ cmd, arg, line = Cmd.parseline(self, line)
+ if cmd:
+ cmd = self.shortCommandName(cmd)
+ line = cmd + " " + arg
+ return cmd, arg, line
+
+ def completenames(self, text, *ignored):
+ return [
+ name + " "
+ for name in DeviceMgrCmd.command_names
+ if name.startswith(text) or self.shortCommandName(name).startswith(text)
+ ]
+
+ def shortCommandName(self, cmd):
+ return cmd.replace("-", "")
+
+ def precmd(self, line):
+ if not self.use_rawinput and line != "EOF" and line != "":
+ print(">>> " + line)
+ return line
+
+ def postcmd(self, stop, line):
+ if not stop and self.use_rawinput:
+ self.prompt = "chip-device-ctrl > "
+ return stop
+
+ def postloop(self):
+ try:
+ import readline
+
+ try:
+ readline.write_history_file(self.historyFileName)
+ except IOError:
+ pass
+ except ImportError:
+ pass
+
+ def do_help(self, line):
+ if line:
+ cmd, arg, unused = self.parseline(line)
+ try:
+ doc = getattr(self, "do_" + cmd).__doc__
+ except AttributeError:
+ doc = None
+ if doc:
+ self.stdout.write("%s\n" % textwrap.dedent(doc))
+ else:
+ self.stdout.write("No help on %s\n" % (line))
+ else:
+ self.print_topics(
+ "\nAvailable commands (type help <name> for more information):",
+ DeviceMgrCmd.command_names,
+ 15,
+ 80,
+ )
+
+ def do_close(self, line):
+ """
+ close
+
+ Close the connection to the device.
+ """
+
+ args = shlex.split(line)
+
+ if len(args) != 0:
+ print("Usage:")
+ self.do_help("close")
+ return
+
+ try:
+ self.devCtrl.Close()
+ except ChipStack.ChipStackException as ex:
+ print(str(ex))
+
+ def do_setlogoutput(self, line):
+ """
+ set-log-output [ none | error | progress | detail ]
+
+ Set the level of Chip logging output.
+ """
+
+ args = shlex.split(line)
+
+ if len(args) == 0:
+ print("Usage:")
+ self.do_help("set-log-output")
+ return
+ if len(args) > 1:
+ print("Unexpected argument: " + args[1])
+ return
+
+ category = args[0].lower()
+ if category == "none":
+ category = 0
+ elif category == "error":
+ category = 1
+ elif category == "progress":
+ category = 2
+ elif category == "detail":
+ category = 3
+ else:
+ print("Invalid argument: " + args[0])
+ return
+
+ try:
+ self.devCtrl.SetLogFilter(category)
+ except ChipStack.ChipStackException as ex:
+ print(str(ex))
+ return
+
+ print("Done.")
+
+ def do_bleadapterselect(self, line):
+ """
+ ble-adapter-select
+
+ Start BLE adapter select.
+ """
+ if sys.platform.startswith("linux"):
+ if not self.bleMgr:
+ self.bleMgr = BleManager(self.devCtrl)
+
+ self.bleMgr.ble_adapter_select(line)
+ else:
+ print(
+ "ble-adapter-select only works in Linux, ble-adapter-select mac_address"
+ )
+
+ return
+
+ def do_bleadapterprint(self, line):
+ """
+ ble-adapter-print
+
+ Print attached BLE adapter.
+ """
+ if sys.platform.startswith("linux"):
+ if not self.bleMgr:
+ self.bleMgr = BleManager(self.devCtrl)
+
+ self.bleMgr.ble_adapter_print()
+ else:
+ print("ble-adapter-print only works in Linux")
+
+ return
+
+ def do_bledebuglog(self, line):
+ """
+ ble-debug-log 0:1
+ 0: disable BLE debug log
+ 1: enable BLE debug log
+ """
+ if not self.bleMgr:
+ self.bleMgr = BleManager(self.devCtrl)
+
+ self.bleMgr.ble_debug_log(line)
+
+ return
+
+ def do_blescan(self, line):
+ """
+ ble-scan
+
+ Start BLE scanning operations.
+ """
+
+ if not self.bleMgr:
+ self.bleMgr = BleManager(self.devCtrl)
+
+ self.bleMgr.scan(line)
+
+ return
+
+ def do_bleconnect(self, line):
+ """
+ ble-connect
+
+ Connect to a BLE peripheral identified by line.
+ """
+
+ if not self.bleMgr:
+ self.bleMgr = BleManager(self.devCtrl)
+ self.bleMgr.connect(line)
+
+ return
+
+ def do_blescanconnect(self, line):
+ """
+ ble-scan-connect
+
+ Scan and connect to a BLE peripheral identified by line.
+ """
+
+ if not self.bleMgr:
+ self.bleMgr = BleManager(self.devCtrl)
+
+ self.bleMgr.scan_connect(line)
+
+ return
+
+ def do_bledisconnect(self, line):
+ """
+ ble-disconnect
+
+ Disconnect from a BLE peripheral.
+ """
+
+ if not self.bleMgr:
+ self.bleMgr = BleManager(self.devCtrl)
+
+ self.bleMgr.disconnect()
+
+ return
+
+ def do_btpconnect(self, line):
+ """
+ connect .
+
+ """
+ try:
+ self.devCtrl.ConnectBle(bleConnection=FAKE_CONN_OBJ_VALUE)
+ except ChipStack.ChipStackException as ex:
+ print(str(ex))
+ return
+
+ print("BTP Connected")
+
+ def do_history(self, line):
+ """
+ history
+
+ Show previously executed commands.
+ """
+
+ try:
+ import readline
+
+ h = readline.get_current_history_length()
+ for n in range(1, h + 1):
+ print(readline.get_history_item(n))
+ except ImportError:
+ pass
+
+ def do_h(self, line):
+ self.do_history(line)
+
+ def do_exit(self, line):
+ return True
+
+ def do_quit(self, line):
+ return True
+
+ def do_q(self, line):
+ return True
+
+ def do_EOF(self, line):
+ print()
+ return True
+
+ def emptyline(self):
+ pass
+
+
+def main():
+ optParser = OptionParser()
+ optParser.add_option(
+ "-r",
+ "--rendezvous-addr",
+ action="store",
+ dest="rendezvousAddr",
+ help="Device rendezvous address",
+ metavar="<ip-address>",
+ )
+ (options, remainingArgs) = optParser.parse_args(sys.argv[1:])
+
+ if len(remainingArgs) != 0:
+ print("Unexpected argument: %s" % remainingArgs[0])
+ sys.exit(-1)
+
+ devMgrCmd = DeviceMgrCmd(rendezvousAddr=options.rendezvousAddr)
+ print("Chip Device Controller Shell")
+ if options.rendezvousAddr:
+ print("Rendezvous address set to %s" % options.rendezvousAddr)
+ print()
+
+ try:
+ devMgrCmd.cmdloop()
+ except KeyboardInterrupt:
+ print("\nQuitting")
+
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/controller/python/chip/ChipBleBase.py b/src/controller/python/chip/ChipBleBase.py
new file mode 100644
index 0000000..8128d34
--- /dev/null
+++ b/src/controller/python/chip/ChipBleBase.py
@@ -0,0 +1,144 @@
+#
+# Copyright (c) 2020 Project CHIP Authors
+# Copyright (c) 2015-2017 Nest Labs, Inc.
+# All rights reserved.
+#
+# 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.
+#
+
+#
+# @file
+# This file is Chip BLE Base class file
+#
+
+from __future__ import absolute_import
+import abc
+import optparse
+import shlex
+import six
+
+
+class ChipBleBase(six.with_metaclass(abc.ABCMeta, object)):
+ @abc.abstractmethod
+ def scan(self, line):
+ """ API to initiatae BLE scanning for -t user_timeout seconds."""
+ return
+
+ @abc.abstractmethod
+ def SubscribeBleCharacteristic(self, connObj, svcId, charId, subscribe):
+ """ Called by Chip to (un-)subscribe to a characteristic of a service."""
+ return
+
+ @abc.abstractmethod
+ def WriteBleCharacteristic(self, connObj, svcId, charId, buffer, length):
+ """ Called by ChipDeviceMgr.py to satisfy a request by Chip to transmit a packet over BLE."""
+ return
+
+ @abc.abstractmethod
+ def scan_connect(self, line):
+ """ API to perform both scan and connect operations in one call."""
+ return
+
+ @abc.abstractmethod
+ def disconnect(self):
+ """ API to initiate BLE disconnect procedure."""
+ return
+
+ @abc.abstractmethod
+ def connect(self, identifier):
+ """ API to initiate BLE connection to peripheral device whose identifier == identifier."""
+ return
+
+ @abc.abstractmethod
+ def CloseBle(self, connObj):
+ """ Called by Chip to close the BLE connection."""
+ return
+
+ @abc.abstractmethod
+ def GetBleEvent(self):
+ """ Called by ChipDeviceMgr.py on behalf of Chip to retrieve a queued message."""
+ return
+
+ @abc.abstractmethod
+ def devMgrCB(self):
+ """A callback used by ChipDeviceMgr.py to drive the runloop while the
+ main thread waits for the Chip thread to complete its operation."""
+ return
+
+ @abc.abstractmethod
+ def readlineCB(self):
+ """A callback used by readline to drive the runloop while the main thread
+ waits for commandline input from the user."""
+ return
+
+ @abc.abstractmethod
+ def setInputHook(self, hookFunc):
+ """Set the PyOS_InputHook to call the specific function."""
+ return
+
+ @abc.abstractmethod
+ def runLoopUntil(self, should_tuple):
+ """helper function to drive runloop until an expected event is received or
+ the timeout expires."""
+ return
+
+ def Usage(self, cmd):
+ if cmd is None:
+ return
+
+ line = "USAGE: "
+
+ if cmd == "scan":
+ line += "ble-scan [-t <timeout>] [<name>|<identifier>] [-q <quiet>]"
+ elif cmd == "scan-connect":
+ line += "ble-scan-connect [-t <timeout>] <name> [-q <quiet>]"
+
+ self.logger.info(line)
+
+ def ParseInputLine(self, line, cmd=None):
+ args = shlex.split(line)
+ optParser = optparse.OptionParser(usage=optparse.SUPPRESS_USAGE)
+
+ if cmd == "scan" or cmd == "scan-connect":
+ optParser.add_option(
+ "-t",
+ "--timeout",
+ action="store",
+ dest="timeout",
+ type="float",
+ default=10.0,
+ )
+ optParser.add_option("-q", "--quiet", action="store_true", dest="quiet")
+
+ try:
+ (options, remainingArgs) = optParser.parse_args(args)
+ except SystemExit:
+ self.Usage(cmd)
+ return None
+
+ if cmd is None:
+ return remainingArgs
+
+ if len(remainingArgs) > 1:
+ self.Usage(cmd)
+ return None
+
+ name = None
+
+ if len(remainingArgs):
+ name = str(remainingArgs[0])
+ elif cmd == "scan-connect":
+ self.Usage(cmd)
+ return None
+
+ return (options.timeout, options.quiet, name)
diff --git a/src/controller/python/chip/ChipBleUtility.py b/src/controller/python/chip/ChipBleUtility.py
new file mode 100644
index 0000000..166edb4
--- /dev/null
+++ b/src/controller/python/chip/ChipBleUtility.py
@@ -0,0 +1,353 @@
+#
+# Copyright (c) 2020 Project CHIP Authors
+# Copyright (c) 2019-2020 Google LLC.
+# Copyright (c) 2015-2018 Nest Labs, Inc.
+# All rights reserved.
+#
+# 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.
+#
+
+#
+# @file
+# This file is utility for Chip BLE
+#
+
+from __future__ import absolute_import
+from __future__ import print_function
+from ctypes import *
+from .ChipUtility import ChipUtility
+import six
+
+
+# Duplicates of BLE definitions in ChipDeviceController-ScriptBinding.cpp
+BLE_EVENT_TYPE_RX = 1
+BLE_EVENT_TYPE_TX = 2
+BLE_EVENT_TYPE_SUBSCRIBE = 3
+BLE_EVENT_TYPE_DISCONNECT = 4
+
+BLE_SUBSCRIBE_OPERATION_SUBSCRIBE = 1
+BLE_SUBSCRIBE_OPERATION_UNSUBSCRIBE = 2
+
+# From BleError.h:
+BLE_ERROR_REMOTE_DEVICE_DISCONNECTED = 12
+
+# Internal representation of the BLE peripheral State enum.
+BLE_PERIPHERAL_STATE_DISCONNECTED = 0
+
+FAKE_CONN_OBJ_VALUE = 12121212
+
+CBCharacteristicWriteWithResponse = 0
+CBCharacteristicWriteWithoutResponse = 1
+
+
+def VoidPtrToUUIDString(ptr, len):
+ try:
+ ptr = ChipUtility.VoidPtrToByteArray(ptr, len)
+ ptr = ChipUtility.Hexlify(ptr)
+ ptr = (
+ ptr[:8]
+ + "-"
+ + ptr[8:12]
+ + "-"
+ + ptr[12:16]
+ + "-"
+ + ptr[16:20]
+ + "-"
+ + ptr[20:]
+ )
+ ptr = str(ptr)
+ except Exception as ex:
+ print("ERROR: failed to convert void * to UUID")
+ ptr = None
+
+ return ptr
+
+
+def ParseBleEventType(val):
+ if isinstance(val, six.integer_types):
+ return val
+ if val.lower() == "rx":
+ return BLE_EVENT_TYPE_RX
+ if val.lower() == "tx":
+ return BLE_EVENT_TYPE_TX
+ raise Exception("Invalid Ble Event Type: " + str(val))
+
+
+class BleTxEvent:
+ def __init__(self, svcId=None, charId=None, status=False):
+ self.EventType = BLE_EVENT_TYPE_TX
+ self.ConnObj = None
+ self.SvcId = svcId
+ self.CharId = charId
+ self.Status = status
+
+ def Print(self, prefix=""):
+ print(
+ "%sBleEvent Type: %s"
+ % (prefix, ("TX" if self.EventType == BLE_EVENT_TYPE_TX else "ERROR"))
+ )
+ print("%sStatus: %s" % (prefix, str(self.Status)))
+
+ if self.SvcId:
+ print("%sSvcId:" % (prefix))
+ print(ChipUtility.Hexlify(self.SvcId))
+
+ if self.CharId:
+ print("%sCharId:" % (prefix))
+ print(ChipUtility.Hexlify(self.CharId))
+
+ def SetField(self, name, val):
+ name = name.lower()
+ if name == "eventtype" or name == "event-type" or name == "type":
+ self.EventType = ParseBleEventType(val)
+ elif name == "status":
+ self.Status = val
+ elif name == "svcid":
+ self.SvcId = val
+ elif name == "charid":
+ self.CharId = val
+ else:
+ raise Exception("Invalid BleTxEvent field: " + str(name))
+
+
+class BleDisconnectEvent:
+ def __init__(self, error=0):
+ self.EventType = BLE_EVENT_TYPE_DISCONNECT
+ self.ConnObj = None
+ self.Error = error
+
+ def Print(self, prefix=""):
+ print(
+ "%sBleEvent Type: %s"
+ % (prefix, ("DC" if self.EventType == BLE_EVENT_TYPE_DISCONNECT else "ERROR"))
+ )
+ print("%sError: %s" % (prefix, str(self.Error)))
+
+ def SetField(self, name, val):
+ name = name.lower()
+ if name == "eventtype" or name == "event-type" or name == "type":
+ self.EventType = ParseBleEventType(val)
+ elif name == "error":
+ self.Error = val
+ else:
+ raise Exception("Invalid BleDisconnectEvent field: " + str(name))
+
+
+class BleRxEvent:
+ def __init__(self, svcId=None, charId=None, buffer=None):
+ self.EventType = BLE_EVENT_TYPE_RX
+ self.ConnObj = None
+ self.SvcId = svcId
+ self.CharId = charId
+ self.Buffer = buffer
+
+ def Print(self, prefix=""):
+ print(
+ "%sBleEvent Type: %s"
+ % (prefix, ("RX" if self.EventType == BLE_EVENT_TYPE_RX else "ERROR"))
+ )
+ if self.Buffer:
+ print("%sBuffer:" % (prefix))
+ print(ChipUtility.Hexlify(self.Buffer))
+
+ if self.SvcId:
+ print("%sSvcId:" % (prefix))
+ print(ChipUtility.Hexlify(self.SvcId))
+
+ if self.CharId:
+ print("%sCharId:" % (prefix))
+ print(ChipUtility.Hexlify(self.CharId))
+
+ def SetField(self, name, val):
+ name = name.lower()
+ if name == "eventtype" or name == "event-type" or name == "type":
+ self.EventType = ParseBleEventType(val)
+ elif name == "buffer":
+ self.Buffer = val
+ elif name == "svcid":
+ self.SvcId = val
+ elif name == "charid":
+ self.CharId = val
+ else:
+ raise Exception("Invalid BleRxEvent field: " + str(name))
+
+
+class BleSubscribeEvent:
+ def __init__(
+ self,
+ svcId=None,
+ charId=None,
+ status=True,
+ operation=BLE_SUBSCRIBE_OPERATION_SUBSCRIBE,
+ ):
+ self.EventType = BLE_EVENT_TYPE_SUBSCRIBE
+ self.ConnObj = None
+ self.SvcId = svcId
+ self.CharId = charId
+ self.Status = status
+ self.Operation = operation
+
+ def Print(self, prefix=""):
+ print(
+ "%sBleEvent Type: %s"
+ % (
+ prefix,
+ ("SUBSCRIBE" if self.EventType == BLE_EVENT_TYPE_SUBSCRIBE else "ERROR"),
+ )
+ )
+ print("%sStatus: %s" % (prefix, str(self.Status)))
+ print(
+ "%sOperation: %s"
+ % (
+ prefix,
+ (
+ "UNSUBSCRIBE"
+ if self.Operation == BLE_SUBSCRIBE_OPERATION_UNSUBSCRIBE
+ else "SUBSCRIBE"
+ ),
+ )
+ )
+
+ if self.SvcId:
+ print("%sSvcId:" % (prefix))
+ print(ChipUtility.Hexlify(self.SvcId))
+
+ if self.CharId:
+ print("%sCharId:" % (prefix))
+ print(ChipUtility.Hexlify(self.CharId))
+
+ def SetField(self, name, val):
+ name = name.lower()
+ if name == "eventtype" or name == "event-type" or name == "type":
+ self.EventType = ParseBleEventType(val)
+ elif name == "status":
+ self.Status = val
+ elif name == "svcid":
+ self.SvcId = val
+ elif name == "charid":
+ self.CharId = val
+ elif name == "operation":
+ self.Operation = val
+ else:
+ raise Exception("Invalid BleSubscribeEvent field: " + str(name))
+
+
+class BleTxEventStruct(Structure):
+ _fields_ = [
+ ("EventType", c_int32), # The type of event.
+ ("ConnObj", c_void_p), # a Handle back to the connection object or None.
+ ("SvcId", c_void_p), # the byte array of the service UUID.
+ ("CharId", c_void_p), # the byte array of the characteristic UUID.
+ ("Status", c_bool), # The status of the previous Tx request
+ ]
+
+ def toBleTxEvent(self):
+ return BleTxEvent(
+ svcId=ChipUtility.VoidPtrToByteArray(self.SvcId, 16),
+ charId=ChipUtility.VoidPtrToByteArray(self.CharId, 16),
+ status=self.Status,
+ )
+
+ @classmethod
+ def fromBleTxEvent(cls, bleTxEvent):
+ bleTxEventStruct = cls()
+ bleTxEventStruct.EventType = bleTxEvent.EventType
+ bleTxEventStruct.ConnObj = c_void_p(FAKE_CONN_OBJ_VALUE)
+ bleTxEventStruct.SvcId = ChipUtility.ByteArrayToVoidPtr(bleTxEvent.SvcId)
+ bleTxEventStruct.CharId = ChipUtility.ByteArrayToVoidPtr(bleTxEvent.CharId)
+ bleTxEventStruct.Status = bleTxEvent.Status
+ return bleTxEventStruct
+
+
+class BleDisconnectEventStruct(Structure):
+ _fields_ = [
+ ("EventType", c_int32), # The type of event.
+ ("ConnObj", c_void_p), # a Handle back to the connection object or None.
+ ("Error", c_int32), # The disconnect error code.
+ ]
+
+ def toBleDisconnectEvent(self):
+ return BleDisconnectEvent(error=self.Error)
+
+ @classmethod
+ def fromBleDisconnectEvent(cls, bleDisconnectEvent):
+ bleDisconnectEventStruct = cls()
+ bleDisconnectEventStruct.EventType = bleDisconnectEvent.EventType
+ bleDisconnectEventStruct.ConnObj = c_void_p(FAKE_CONN_OBJ_VALUE)
+ bleDisconnectEventStruct.Error = bleDisconnectEvent.Error
+ return bleDisconnectEventStruct
+
+
+class BleRxEventStruct(Structure):
+ _fields_ = [
+ ("EventType", c_int32), # The type of event.
+ ("ConnObj", c_void_p), # a Handle back to the connection object or None.
+ ("SvcId", c_void_p), # the byte array of the service UUID.
+ ("CharId", c_void_p), # the byte array of the characteristic UUID.
+ ("Buffer", c_void_p), # the byte array of the Rx packet.
+ ("Length", c_uint16), # the length of the byte array (buffer).
+ ]
+
+ def toBleRxEvent(self):
+ return BleRxEvent(
+ svcId=ChipUtility.VoidPtrToByteArray(self.SvcId, 16),
+ charId=ChipUtility.VoidPtrToByteArray(self.CharId, 16),
+ buffer=ChipUtility.VoidPtrToByteArray(self.Buffer, self.Length),
+ )
+
+ @classmethod
+ def fromBleRxEvent(cls, bleRxEvent):
+ bleRxEventStruct = cls()
+ bleRxEventStruct.EventType = bleRxEvent.EventType
+ bleRxEventStruct.ConnObj = c_void_p(FAKE_CONN_OBJ_VALUE)
+ bleRxEventStruct.SvcId = ChipUtility.ByteArrayToVoidPtr(bleRxEvent.SvcId)
+ bleRxEventStruct.CharId = ChipUtility.ByteArrayToVoidPtr(bleRxEvent.CharId)
+ bleRxEventStruct.Buffer = ChipUtility.ByteArrayToVoidPtr(bleRxEvent.Buffer)
+ bleRxEventStruct.Length = (
+ len(bleRxEvent.Buffer) if (bleRxEvent.Buffer != None) else 0
+ )
+ return bleRxEventStruct
+
+
+class BleSubscribeEventStruct(Structure):
+ _fields_ = [
+ ("EventType", c_int32), # The type of event.
+ ("ConnObj", c_void_p), # a Handle back to the connection object or None.
+ ("SvcId", c_void_p), # the byte array of the service UUID.
+ ("CharId", c_void_p), # the byte array of the characteristic UUID.
+ ("Operation", c_int32), # The subscribe operation.
+ ("Status", c_bool), # The status of the previous Tx request
+ ]
+
+ def toBleSubscribeEvent(self):
+ return BleSubscribeEvent(
+ svcId=ChipUtility.VoidPtrToByteArray(self.SvcId, 16),
+ charId=ChipUtility.VoidPtrToByteArray(self.CharId, 16),
+ status=self.Status,
+ operation=self.Operation,
+ )
+
+ @classmethod
+ def fromBleSubscribeEvent(cls, bleSubscribeEvent):
+ bleSubscribeEventStruct = cls()
+ bleSubscribeEventStruct.EventType = bleSubscribeEvent.EventType
+ bleSubscribeEventStruct.ConnObj = c_void_p(FAKE_CONN_OBJ_VALUE)
+ bleSubscribeEventStruct.SvcId = ChipUtility.ByteArrayToVoidPtr(
+ bleSubscribeEvent.SvcId
+ )
+ bleSubscribeEventStruct.CharId = ChipUtility.ByteArrayToVoidPtr(
+ bleSubscribeEvent.CharId
+ )
+ bleSubscribeEventStruct.Operation = bleSubscribeEvent.Operation
+ bleSubscribeEventStruct.Status = bleSubscribeEvent.Status
+ return bleSubscribeEventStruct
diff --git a/src/controller/python/chip/ChipBluezMgr.py b/src/controller/python/chip/ChipBluezMgr.py
new file mode 100644
index 0000000..57af58c
--- /dev/null
+++ b/src/controller/python/chip/ChipBluezMgr.py
@@ -0,0 +1,1282 @@
+#
+# Copyright (c) 2020 Project CHIP Authors
+# Copyright (c) 2019-2020 Google LLC.
+# Copyright (c) 2015-2018 Nest Labs, Inc.
+# All rights reserved.
+#
+# 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.
+#
+
+#
+# @file
+# BLE Central support for Chip Device Manager via BlueZ APIs.
+#
+
+from __future__ import absolute_import
+from __future__ import print_function
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+import gc
+import logging
+import pprint
+import sys
+import threading
+import time
+import traceback
+import uuid
+import six.moves.queue
+
+
+from ctypes import *
+import six
+from six.moves import range
+
+try:
+ from gi.repository import GObject
+except Exception as ex:
+ log(ex)
+ from pgi.repository import GObject
+
+from .ChipUtility import ChipUtility
+
+from .ChipBleUtility import (
+ BLE_SUBSCRIBE_OPERATION_SUBSCRIBE,
+ BLE_SUBSCRIBE_OPERATION_UNSUBSCRIBE,
+ BLE_ERROR_REMOTE_DEVICE_DISCONNECTED,
+ VoidPtrToUUIDString,
+ BleTxEvent,
+ BleDisconnectEvent,
+ BleRxEvent,
+ BleSubscribeEvent,
+ BleTxEventStruct,
+ BleDisconnectEventStruct,
+ BleRxEventStruct,
+ BleSubscribeEventStruct,
+)
+
+from .ChipBleBase import ChipBleBase
+
+chip_service = uuid.UUID("0000FEAF-0000-1000-8000-00805F9B34FB")
+chip_tx = uuid.UUID("18EE2EF5-263D-4559-959F-4F9C429F9D11")
+chip_rx = uuid.UUID("18EE2EF5-263D-4559-959F-4F9C429F9D12")
+chip_service_short = uuid.UUID("0000FEAF-0000-0000-0000-000000000000")
+chromecast_setup_service = uuid.UUID("0000FEA0-0000-1000-8000-00805F9B34FB")
+chromecast_setup_service_short = uuid.UUID("0000FEA0-0000-0000-0000-000000000000")
+
+BLUEZ_NAME = "org.bluez"
+ADAPTER_INTERFACE = BLUEZ_NAME + ".Adapter1"
+DEVICE_INTERFACE = BLUEZ_NAME + ".Device1"
+SERVICE_INTERFACE = BLUEZ_NAME + ".GattService1"
+CHARACTERISTIC_INTERFACE = BLUEZ_NAME + ".GattCharacteristic1"
+DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"
+
+BLE_SCAN_CONNECT_GUARD_SEC = 2.0
+BLE_STATUS_TRANSITION_TIMEOUT_SEC = 5.0
+BLE_CONNECT_TIMEOUT_SEC = 15.0
+BLE_SERVICE_DISCOVERY_TIMEOUT_SEC = 5.0
+BLE_CHAR_DISCOVERY_TIMEOUT_SEC = 5.0
+BLE_SUBSCRIBE_TIMEOUT_SEC = 5.0
+BLE_WRITE_CHARACTERISTIC_TIMEOUT_SEC = 10.0
+BLE_IDLE_DELTA = 0.1
+
+
+def get_bluez_objects(bluez, bus, interface, prefix_path):
+ results = []
+ if bluez is None or bus is None or interface is None or prefix_path is None:
+ return results
+ for item in six.iteritems(bluez.GetManagedObjects()):
+ delegates = item[1].get(interface)
+ if not delegates:
+ continue
+ slice = {}
+ if item[0].startswith(prefix_path):
+ slice["object"] = bus.get_object(BLUEZ_NAME, item[0])
+ slice["path"] = item[0]
+ results.append(slice)
+ return results
+
+
+class BluezDbusAdapter:
+ def __init__(self, bluez_obj, bluez, bus, logger=None):
+ self.logger = logger if logger else logging.getLogger("ChipBLEMgr")
+ self.object = bluez_obj
+ self.adapter = dbus.Interface(bluez_obj, ADAPTER_INTERFACE)
+ self.adapter_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
+ self.adapter_event = threading.Event()
+ self.bluez = bluez
+ self.bus = bus
+ self.path = self.adapter.object_path
+ self.signalReceiver = None
+
+ def __del__(self):
+ self.destroy()
+
+ def destroy(self):
+ self.logger.debug("destroy adapter")
+ self.adapter_unregister_signal()
+ self.adapter = None
+ self.adapter_properties = None
+ self.adapter_event.clear()
+ self.bluez = None
+ self.bus = None
+ self.object = None
+ self.path = None
+ self.signalReceiver = None
+
+ def adapter_register_signal(self):
+ if self.signalReceiver is None:
+ self.logger.debug("add adapter signal")
+ self.signalReceiver = self.bus.add_signal_receiver(
+ self.adapter_on_prop_changed_cb,
+ bus_name=BLUEZ_NAME,
+ dbus_interface=DBUS_PROPERTIES,
+ signal_name="PropertiesChanged",
+ path=self.path,
+ )
+
+ def adapter_unregister_signal(self):
+ if self.signalReceiver is not None:
+ self.logger.debug(" remove adapter signal")
+ self.bus.remove_signal_receiver(
+ self.signalReceiver,
+ signal_name="PropertiesChanged",
+ dbus_interface="org.freedesktop.DBus.Properties",
+ )
+
+ def adapter_on_prop_changed_cb(
+ self, interface, changed_properties, invalidated_properties
+ ):
+ if len(changed_properties) == 0:
+ self.logger.debug("changed_properties is empty")
+ return
+
+ if len(invalidated_properties) > 0:
+ self.logger.debug(
+ "invalidated_properties is not empty %s" % str(invalidated_properties)
+ )
+ return
+
+ if interface == ADAPTER_INTERFACE:
+ if "Discovering" in changed_properties:
+ self.adapter_event.set()
+
+ def adapter_bg_scan(self, enable):
+ self.adapter_event.clear()
+ action_flag = False
+ try:
+ if enable:
+ if not self.Discovering:
+ action_flag = True
+ self.logger.info("scanning started")
+ self.adapter.StartDiscovery()
+ else:
+ self.logger.info("it has started scanning")
+ else:
+ if self.Discovering:
+ action_flag = True
+ self.adapter.StopDiscovery()
+ self.logger.info("scanning stopped")
+ else:
+ print("it has stopped scanning")
+ if action_flag:
+ if not self.adapter_event.wait(BLE_STATUS_TRANSITION_TIMEOUT_SEC):
+ if enable:
+ self.logger.debug("scan start error")
+ else:
+ self.logger.debug("scan stop error")
+ self.adapter_event.clear()
+ except dbus.exceptions.DBusException as ex:
+ self.adapter_event.clear()
+ self.logger.debug(str(ex))
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+
+ @property
+ def Address(self):
+ try:
+ result = self.adapter_properties.Get(ADAPTER_INTERFACE, "Address")
+ return result
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ @property
+ def UUIDs(self):
+ try:
+ return self.adapter_properties.Get(ADAPTER_INTERFACE, "UUIDs")
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ def SetDiscoveryFilter(self, dict):
+ try:
+ self.adapter.SetDiscoveryFilter(dict)
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+
+ @property
+ def Discovering(self):
+ try:
+ result = self.adapter_properties.Get(ADAPTER_INTERFACE, "Discovering")
+ return bool(result)
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return False
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return False
+
+ def DiscoverableTimeout(self, timeoutSec):
+ try:
+ result = self.adapter_properties.Set(
+ ADAPTER_INTERFACE, "DiscoverableTimeout", timeoutSec
+ )
+ return bool(result)
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return False
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return False
+
+ def Powered(self, enable):
+ try:
+ result = self.adapter_properties.Set(ADAPTER_INTERFACE, "Powered", enable)
+ return bool(result)
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return False
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return False
+
+ def find_devices(self, uuids):
+ devices = [
+ BluezDbusDevice(p["object"], self.bluez, self.bus, self.logger)
+ for p in get_bluez_objects(
+ self.bluez, self.bus, DEVICE_INTERFACE, self.path
+ )
+ ]
+ found = []
+ for device in devices:
+ for i in device.uuids:
+ if i in uuids:
+ found.append(device)
+ break
+ return found
+
+ def clear_adapter(self):
+ devices = [
+ BluezDbusDevice(p["object"], self.bluez, self.bus, self.logger)
+ for p in get_bluez_objects(
+ self.bluez, self.bus, DEVICE_INTERFACE, self.path
+ )
+ ]
+ for device in devices:
+ try:
+ if device.Connected:
+ device.device_bg_connect(False)
+ self.adapter.RemoveDevice(device.device.object_path)
+ except Exception as ex:
+ pass
+
+
+class BluezDbusDevice:
+ def __init__(self, bluez_obj, bluez, bus, logger=None):
+ self.logger = logger if logger else logging.getLogger("ChipBLEMgr")
+ self.object = bluez_obj
+ self.device = dbus.Interface(bluez_obj, DEVICE_INTERFACE)
+ self.device_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
+ self.path = self.device.object_path
+ self.device_event = threading.Event()
+ if self.Name:
+ try:
+ self.device_id = uuid.uuid3(uuid.NAMESPACE_DNS, self.Name)
+ except UnicodeDecodeError:
+ self.device_id = uuid.uuid3(
+ uuid.NAMESPACE_DNS, self.Name.encode("utf-8")
+ )
+ else:
+ self.device_id = uuid.uuid4()
+ self.bluez = bluez
+ self.bus = bus
+ self.signalReceiver = None
+ self.path = self.device.object_path
+
+ def __del__(self):
+ self.destroy()
+
+ def destroy(self):
+ self.logger.debug("destroy device")
+ self.device_unregister_signal()
+ self.device = None
+ self.device_properties = None
+ self.device_event = None
+ self.device_id = None
+ self.bluez = None
+ self.bus = None
+ self.object = None
+ self.signalReceiver = None
+
+ def device_register_signal(self):
+ if self.signalReceiver is None:
+ self.logger.debug("add device signal")
+ self.signalReceiver = self.bus.add_signal_receiver(
+ self.device_on_prop_changed_cb,
+ bus_name=BLUEZ_NAME,
+ dbus_interface=DBUS_PROPERTIES,
+ signal_name="PropertiesChanged",
+ path=self.path,
+ )
+
+ def device_unregister_signal(self):
+ if self.signalReceiver is not None:
+ self.logger.debug("remove device signal")
+ self.bus.remove_signal_receiver(
+ self.signalReceiver,
+ signal_name="PropertiesChanged",
+ dbus_interface=DBUS_PROPERTIES,
+ )
+
+ def device_on_prop_changed_cb(
+ self, interface, changed_properties, invalidated_properties
+ ):
+ if len(changed_properties) == 0:
+ self.logger.debug("changed_properties is empty")
+ return
+
+ if len(invalidated_properties) > 0:
+ self.logger.debug(
+ "invalidated_properties is not empty %s" % str(invalidated_properties)
+ )
+ return
+
+ if interface == DEVICE_INTERFACE:
+ if "Connected" in changed_properties:
+ self.device_event.set()
+
+ def device_bg_connect(self, enable):
+ time.sleep(BLE_SCAN_CONNECT_GUARD_SEC)
+ action_flag = False
+ self.device_event.clear()
+ try:
+ if enable:
+ if not self.Connected:
+ action_flag = True
+ self.device.Connect()
+ self.logger.info("BLE connecting")
+ else:
+ self.logger.info("BLE has connected")
+ else:
+ if self.Connected:
+ action_flag = True
+ self.device.Disconnect()
+ self.logger.info("BLE disconnected")
+ else:
+ self.logger.info("BLE has disconnected")
+ if action_flag:
+ if not self.device_event.wait(BLE_STATUS_TRANSITION_TIMEOUT_SEC):
+ if enable:
+ self.logger.info("BLE connect error")
+ else:
+ self.logger.info("BLE disconnect error")
+ self.device_event.clear()
+ except dbus.exceptions.DBusException as ex:
+ self.device_event.clear()
+ self.logger.info(str(ex))
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+
+ def service_discover(self, gatt_dic):
+ self.logger.info("Discovering services")
+ try:
+ expired = time.time() + BLE_SERVICE_DISCOVERY_TIMEOUT_SEC
+ while time.time() < expired:
+ if self.ServicesResolved:
+ services = [
+ BluezDbusGattService(
+ p["object"], self.bluez, self.bus, self.logger
+ )
+ for p in get_bluez_objects(
+ self.bluez, self.bus, SERVICE_INTERFACE, self.path
+ )
+ ]
+ for service in services:
+ if service.uuid in gatt_dic["services"]:
+ self.logger.info("Service discovering success")
+ return service
+ time.sleep(BLE_IDLE_DELTA)
+ self.logger.error("Service discovering fail")
+ return None
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ @property
+ def uuids(self):
+ try:
+ uuids = self.device_properties.Get(DEVICE_INTERFACE, "UUIDs")
+ uuid_result = []
+ for i in uuids:
+ if len(str(i)) == 4:
+ uuid_normal = "0000%s-0000-0000-0000-000000000000" % i
+ else:
+ uuid_normal = i
+ uuid_result.append(uuid.UUID(str(uuid_normal)))
+ return uuid_result
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ @property
+ def Address(self):
+ try:
+ return self.device_properties.Get(DEVICE_INTERFACE, "Address")
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ @property
+ def Name(self):
+ try:
+ name = self.device_properties.Get(DEVICE_INTERFACE, "Name")
+ return name
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ @property
+ def Connected(self):
+ try:
+ result = self.device_properties.Get(DEVICE_INTERFACE, "Connected")
+ return bool(result)
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return False
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return False
+
+ @property
+ def TxPower(self):
+ try:
+ return self.device_properties.Get(DEVICE_INTERFACE, "TxPower")
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ @property
+ def RSSI(self):
+ try:
+ result = self.device_properties.Get(DEVICE_INTERFACE, "RSSI")
+ return result
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ @property
+ def Adapter(self):
+ try:
+ return self.device_properties.Get(DEVICE_INTERFACE, "Adapter")
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ @property
+ def ServiceData(self):
+ try:
+ return self.device_properties.Get(DEVICE_INTERFACE, "ServiceData")
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ @property
+ def ServicesResolved(self):
+ try:
+ result = self.device_properties.Get(DEVICE_INTERFACE, "ServicesResolved")
+ return bool(result)
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return False
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return False
+
+
+class BluezDbusGattService:
+ def __init__(self, bluez_obj, bluez, bus, logger=None):
+ self.logger = logger if logger else logging.getLogger("ChipBLEMgr")
+ self.object = bluez_obj
+ self.service = dbus.Interface(bluez_obj, SERVICE_INTERFACE)
+ self.service_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
+ self.bluez = bluez
+ self.bus = bus
+ self.path = self.service.object_path
+
+ def __del__(self):
+ self.destroy()
+
+ def destroy(self):
+ self.logger.debug("destroy GattService")
+ self.service = None
+ self.service_properties = None
+ self.bluez = None
+ self.bus = None
+ self.object = None
+ self.path = None
+
+ @property
+ def uuid(self):
+ try:
+ result = uuid.UUID(
+ str(self.service_properties.Get(SERVICE_INTERFACE, "UUID"))
+ )
+ return result
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ @property
+ def Primary(self):
+ try:
+ result = bool(self.service_properties.Get(SERVICE_INTERFACE, "Primary"))
+ return result
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return False
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return False
+
+ @property
+ def Device(self):
+ try:
+ result = self.service_properties.Get(SERVICE_INTERFACE, "Device")
+ return result
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ def find_characteristic(self, uuid):
+ try:
+ expired = time.time() + BLE_CHAR_DISCOVERY_TIMEOUT_SEC
+ while time.time() < expired:
+ characteristics = [
+ BluezDbusGattCharacteristic(
+ p["object"], self.bluez, self.bus, self.logger
+ )
+ for p in get_bluez_objects(
+ self.bluez, self.bus, CHARACTERISTIC_INTERFACE, self.path
+ )
+ ]
+ for characteristic in characteristics:
+ if characteristic.uuid == uuid:
+ return characteristic
+ time.sleep(BLE_IDLE_DELTA)
+ self.logger.error("Char discovering fail")
+ return None
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+
+class BluezDbusGattCharacteristic:
+ def __init__(self, bluez_obj, bluez, bus, logger=None):
+ self.logger = logger if logger else logging.getLogger("ChipBLEMgr")
+ self.object = bluez_obj
+ self.characteristic = dbus.Interface(bluez_obj, CHARACTERISTIC_INTERFACE)
+ self.characteristic_properties = dbus.Interface(bluez_obj, DBUS_PROPERTIES)
+ self.received = None
+ self.path = self.characteristic.object_path
+ self.bluez = bluez
+ self.bus = bus
+ self.signalReceiver = None
+
+ def __del__(self):
+ self.destroy()
+
+ def destroy(self):
+ self.logger.debug("destroy GattCharacteristic")
+ self.gattCharacteristic_unregister_signal()
+ self.characteristic = None
+ self.object = None
+ self.characteristic_properties = None
+ self.received = None
+ self.bluez = None
+ self.bus = None
+ self.path = None
+ self.signalReceiver = None
+
+ def gattCharacteristic_register_signal(self):
+ if not self.signalReceiver:
+ self.logger.debug("add GattCharacteristic signal")
+ self.signalReceiver = self.bus.add_signal_receiver(
+ self.gatt_on_characteristic_changed_cb,
+ bus_name=BLUEZ_NAME,
+ dbus_interface=DBUS_PROPERTIES,
+ signal_name="PropertiesChanged",
+ path=self.path,
+ )
+
+ def gattCharacteristic_unregister_signal(self):
+ if self.signalReceiver:
+ self.logger.debug("remove GattCharacteristic signal")
+
+ self.bus.remove_signal_receiver(
+ self.signalReceiver,
+ bus_name=BLUEZ_NAME,
+ signal_name="PropertiesChanged",
+ dbus_interface=DBUS_PROPERTIES,
+ path=self.path,
+ )
+ self.signalReceiver = None
+
+ def gatt_on_characteristic_changed_cb(
+ self, interface, changed_properties, invalidated_properties
+ ):
+ self.logger.debug(
+ "property change in" + str(self.characteristic) + str(changed_properties)
+ )
+
+ if len(changed_properties) == 0:
+ return
+
+ if len(invalidated_properties) > 0:
+ return
+
+ if interface == CHARACTERISTIC_INTERFACE:
+ if "Value" in changed_properties:
+ if self.received:
+ self.received(changed_properties["Value"])
+
+ def WriteValue(self, value, options, reply_handler, error_handler, timeout):
+ try:
+ self.characteristic.WriteValue(
+ value,
+ options,
+ reply_handler=reply_handler,
+ error_handler=error_handler,
+ timeout=timeout,
+ )
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+
+ @property
+ def uuid(self):
+ try:
+ result = uuid.UUID(
+ str(
+ self.characteristic_properties.Get(CHARACTERISTIC_INTERFACE, "UUID")
+ )
+ )
+ return result
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return None
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return None
+
+ def StartNotify(self, cbfunct, reply_handler, error_handler, timeout):
+ try:
+ if not cbfunct:
+ self.logger.info("please provide the notify callback function")
+ self.received = cbfunct
+ self.gattCharacteristic_register_signal()
+ self.characteristic.StartNotify(
+ reply_handler=reply_handler,
+ error_handler=error_handler,
+ timeout=timeout,
+ )
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+
+ def StopNotify(self, reply_handler, error_handler, timeout):
+ try:
+ self.logger.debug("stopping notifying")
+ self.characteristic.StopNotify(
+ reply_handler=reply_handler,
+ error_handler=error_handler,
+ timeout=timeout,
+ )
+ self.gattCharacteristic_unregister_signal()
+ self.received = None
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+
+ @property
+ def Notifying(self):
+ try:
+ result = self.characteristic_properties.Get(
+ CHARACTERISTIC_INTERFACE, "Notifying"
+ )
+ return bool(result)
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+ return False
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ return False
+
+
+class BluezManager(ChipBleBase):
+ def __init__(self, devMgr, logger=None):
+ if logger:
+ self.logger = logger
+ else:
+ self.logger = logging.getLogger("ChipBLEMgr")
+ logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s",
+ )
+ self.scan_quiet = False
+ self.peripheral_list = []
+ self.chip_queue = six.moves.queue.Queue()
+ self.Gmainloop = None
+ self.daemon_thread = None
+ self.adapter = None
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+ GObject.threads_init()
+ dbus.mainloop.glib.threads_init()
+ self.bus = dbus.SystemBus()
+ self.bluez = dbus.Interface(
+ self.bus.get_object(BLUEZ_NAME, "/"), "org.freedesktop.DBus.ObjectManager"
+ )
+ self.target = None
+ self.service = None
+ self.orig_input_hook = None
+ self.hookFuncPtr = None
+ self.connect_state = False
+ self.tx = None
+ self.rx = None
+ self.setInputHook(self.readlineCB)
+ self.devMgr = devMgr
+ self.devMgr.SetBlockingCB(self.devMgrCB)
+
+ def HandleBleEventCB():
+ return self.GetBleEvent()
+
+ def HandleBleWriteCharCB(connObj, svcId, charId, buffer, length):
+ return self.WriteBleCharacteristic(connObj, svcId, charId, buffer, length)
+
+ def HandleBleSubscribeCB(connObj, svcId, charId, subscribe):
+ return self.SubscribeBleCharacteristic(connObj, svcId, charId, subscribe)
+
+ def HandleBleCloseCB(connObj):
+ return self.CloseBle(connObj)
+
+ self.devMgr.SetBleEventCB(HandleBleEventCB)
+ self.devMgr.SetBleWriteCharCB(HandleBleWriteCharCB)
+ self.devMgr.SetBleSubscribeCharCB(HandleBleSubscribeCB)
+ self.devMgr.SetBleCloseCB(HandleBleCloseCB)
+
+ def __del__(self):
+ self.disconnect()
+ self.setInputHook(self.orig_input_hook)
+ self.devMgr.SetBlockingCB(None)
+ self.devMgr.SetBleEventCB(None)
+
+ def ble_adapter_select(self, identifier=None):
+ if self.adapter:
+ self.adapter.destroy()
+ self.adapter = None
+ self.adapter = self.get_adapter_by_addr(identifier)
+ self.adapter.adapter_register_signal()
+ self.adapter.Powered(False)
+ self.adapter.Powered(True)
+
+ def ble_adapter_print(self):
+ try:
+ adapters = [
+ BluezDbusAdapter(p["object"], self.bluez, self.bus, self.logger)
+ for p in get_bluez_objects(
+ self.bluez, self.bus, ADAPTER_INTERFACE, "/org/bluez"
+ )
+ ]
+ for i in range(len(adapters)):
+ self.logger.info("adapter %s = %s" % (i, adapters[i].Address))
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+
+ def get_adapter_by_addr(self, identifier):
+ try:
+ adapters = [
+ BluezDbusAdapter(p["object"], self.bluez, self.bus, self.logger)
+ for p in get_bluez_objects(
+ self.bluez, self.bus, ADAPTER_INTERFACE, "/org/bluez"
+ )
+ ]
+ if identifier is None:
+ return adapters[0]
+ if len(adapters) > 0:
+ for adapter in adapters:
+ if str(adapter.Address).upper() == str(identifier).upper():
+ return adapter
+ self.logger.info(
+ "adapter %s cannot be found, expect the ble mac address" % (identifier)
+ )
+ return None
+
+ except dbus.exceptions.DBusException as ex:
+ self.logger.debug(str(ex))
+
+ def runLoopUntil(self, target=None, **kwargs):
+ if target:
+ self.daemon_thread = threading.Thread(
+ target=self.running_thread, args=(target, kwargs)
+ )
+ self.daemon_thread.daemon = True
+ self.daemon_thread.start()
+
+ try:
+ self.Gmainloop = GObject.MainLoop()
+ self.Gmainloop.run()
+ except KeyboardInterrupt:
+ self.Gmainloop.quit()
+ sys.exit(1)
+
+ def running_thread(self, target, kwargs):
+ try:
+ while not self.Gmainloop or not self.Gmainloop.is_running():
+ time.sleep(0.00001)
+ target(**kwargs)
+ except Exception as err:
+ traceback.print_exc()
+ finally:
+ self.Gmainloop.quit()
+
+ def setInputHook(self, hookFunc):
+ """Set the PyOS_InputHook to call the specific function."""
+ hookFunctionType = CFUNCTYPE(None)
+ self.hookFuncPtr = hookFunctionType(hookFunc)
+ pyos_inputhook_ptr = c_void_p.in_dll(pythonapi, "PyOS_InputHook")
+ # save the original so that on del we can revert it back to the way it was.
+ self.orig_input_hook = cast(pyos_inputhook_ptr.value, PYFUNCTYPE(c_int))
+ # set the new hook. readLine will call this periodically as it polls for input.
+ pyos_inputhook_ptr.value = cast(self.hookFuncPtr, c_void_p).value
+
+ def runIdleLoop(self, **kwargs):
+ time.sleep(0)
+
+ def devMgrCB(self):
+ self.runLoopUntil(self.runIdleLoop)
+
+ def readlineCB(self):
+ self.runLoopUntil(self.runIdleLoop)
+
+ if self.orig_input_hook:
+ self.orig_input_hook()
+
+ def scan_connect(self, line):
+ """ API to perform both scan and connect operations in one call."""
+ args = self.ParseInputLine(line, "scan-connect")
+ if not args:
+ return False
+ if not self.adapter:
+ self.logger.info("use default adapter")
+ self.ble_adapter_select()
+ self.scan_quiet = args[1]
+ self.scan(line)
+ if self.target:
+ return self.connect(args[2])
+ else:
+ self.logger.info(
+ "Failed to scan device named: " + args[2] + ". Connection skipped."
+ )
+ return False
+
+ def dump_scan_result(self, device):
+ self.logger.info("{0:<10}{1}".format("Name =", device.Name))
+ self.logger.info("{0:<10}{1}".format("ID =", device.device_id))
+ self.logger.info("{0:<10}{1}".format("RSSI =", device.RSSI))
+ self.logger.info("{0:<10}{1}".format("address =", device.Address))
+ self.logger.info(
+ "ADV data: " + ("".join([str(i) for i in dict(device.ServiceData).keys()]))
+ if device.ServiceData
+ else ""
+ )
+ self.logger.info("")
+
+ def scan_bg_implementation(self, **kwargs):
+ self.adapter.clear_adapter()
+ with self.chip_queue.mutex:
+ self.chip_queue.queue.clear()
+ self.adapter.adapter_bg_scan(True)
+ found = False
+ identifier = kwargs["identifier"]
+ timeout = kwargs["timeout"] + time.time()
+
+ while time.time() < timeout:
+ self.peripheral_list = self.adapter.find_devices(
+ [
+ chip_service,
+ chip_service_short,
+ chromecast_setup_service,
+ chromecast_setup_service_short,
+ ]
+ )
+ for device in self.peripheral_list:
+ try:
+ if not self.scan_quiet:
+ # display all scanned results
+ self.dump_scan_result(device)
+ if device.Name == identifier or str(device.Address).upper() == str(
+ identifier.upper()
+ ):
+ if self.scan_quiet:
+ # only display the scanned target's info when quiet
+ self.dump_scan_result(device)
+ self.target = device
+ found = True
+ break
+ except Exception as ex:
+ pass
+ if found:
+ break
+
+ time.sleep(BLE_IDLE_DELTA)
+ self.adapter.adapter_bg_scan(False)
+
+ def scan(self, line):
+ args = self.ParseInputLine(line, "scan")
+ if not args:
+ return False
+ self.target = None
+ if not self.adapter:
+ self.logger.info("use default adapter")
+ self.ble_adapter_select()
+ del self.peripheral_list[:]
+ self.scan_quiet = args[1]
+ self.runLoopUntil(
+ self.scan_bg_implementation, timeout=args[0], identifier=args[2]
+ )
+ return True
+
+ def chipServieCharConnect(self):
+ gatt_dic = {
+ "services": [chip_service, chip_service_short],
+ "chars": [chip_tx, chip_rx],
+ }
+ self.service = self.target.service_discover(gatt_dic)
+ if self.service is None:
+ self.logger.info("chip service cannot be found")
+ return False
+
+ self.rx = self.service.find_characteristic(chip_rx)
+ if self.rx is None:
+ self.logger.info("chip rx char cannot be found")
+ return False
+
+ self.tx = self.service.find_characteristic(chip_tx)
+ if self.tx is None:
+ self.logger.info("chip tx char cannot be found")
+ self.connect_state = False
+ return False
+
+ return True
+
+ def connect_bg_implementation(self, **kwargs):
+ self.connect_state = False
+ if self.adapter is None:
+ self.logger.info("adapter is not configured")
+ return
+ self.target.device_register_signal()
+ self.target.device_bg_connect(True)
+ if self.chipServieCharConnect():
+ self.logger.info("connect success")
+ self.connect_state = True
+ else:
+ self.logger.info("connect fail")
+ self.connect_state = False
+
+ def disconnect_bg_implementation(self, **kwargs):
+ if self.target:
+ self.target.device_bg_connect(False)
+ if self.tx:
+ self.tx.destroy()
+ self.tx = None
+ if self.rx:
+ self.rx.destroy()
+ self.rx = None
+ if self.service:
+ self.service.destroy()
+ self.service = None
+
+ def connect(self, identifier):
+ found = False
+ self.logger.info("trying to connect to " + identifier)
+ for p in self.peripheral_list:
+ p_id = str(p.device_id)
+ p_name = str(p.Name)
+ p_address = str(p.Address)
+ self.logger.debug(p_id + " vs " + str(identifier))
+ self.logger.debug(p_name + " vs " + str(identifier))
+ self.logger.debug(p_address + " vs " + str(identifier))
+ if (
+ p_id == str(identifier)
+ or p_name == str(identifier)
+ or p_address.upper() == str(identifier).upper()
+ ):
+ self.target = p
+ found = True
+ break
+ if found:
+ self.runLoopUntil(
+ self.connect_bg_implementation,
+ identifier=identifier,
+ timeout=BLE_CONNECT_TIMEOUT_SEC,
+ )
+ if self.connect_state:
+ return True
+ else:
+ return False
+ else:
+ print("device cannot be found")
+ return False
+
+ def disconnect(self):
+ self.runLoopUntil(self.disconnect_bg_implementation)
+ for i in range(2):
+ n = gc.collect()
+ self.logger.debug("Unreached objects: %d", n)
+ self.logger.debug("Final Garbage:")
+ self.logger.debug(pprint.pformat(gc.garbage))
+
+ def WriteCharactertisticSuccessCB(self, *args):
+ self.logger.debug("write complete")
+ if self.devMgr:
+ txEvent = BleTxEvent(
+ charId=self.charId_tx, svcId=self.svcId_tx, status=True
+ )
+ self.chip_queue.put(txEvent)
+ self.devMgr.DriveBleIO()
+
+ def WriteCharactertisticErrorCB(self, *args):
+ self.logger.debug("write fail, error:" + repr(args))
+ if self.devMgr:
+ txEvent = BleTxEvent(
+ charId=self.charId_tx, svcId=self.svcId_tx, status=False
+ )
+ self.chip_queue.put(txEvent)
+ self.devMgr.DriveBleIO()
+
+ def WriteBleCharacteristic(self, connObj, svcId, charId, buffer, length):
+ self.logger.debug("write start")
+ result = False
+ if self.target and self.target.Connected:
+ converted_data = ChipUtility.VoidPtrToByteArray(buffer, length)
+ self.charId_tx = bytearray(
+ uuid.UUID(str(VoidPtrToUUIDString(charId, 16))).bytes
+ )
+ self.svcId_tx = bytearray(
+ uuid.UUID(str(VoidPtrToUUIDString(svcId, 16))).bytes
+ )
+ self.tx.WriteValue(
+ dbus.Array([dbus.Byte(i) for i in converted_data], "y"),
+ options="",
+ reply_handler=self.WriteCharactertisticSuccessCB,
+ error_handler=self.WriteCharactertisticErrorCB,
+ timeout=BLE_WRITE_CHARACTERISTIC_TIMEOUT_SEC,
+ )
+ result = True
+ else:
+ self.logger.warning("WARNING: peripheral is no longer connected.")
+ return result
+
+ def receivedNotificationCB(self, data):
+ self.logger.debug("received data")
+ bytes = bytearray(data)
+ if self.devMgr:
+ rxEvent = BleRxEvent(
+ charId=self.charId_rx, svcId=self.svcId_rx, buffer=bytes
+ )
+ self.chip_queue.put(rxEvent)
+ self.devMgr.DriveBleIO()
+
+ def subscribeSuccessCb(self, *args):
+ self.logger.debug("subscribe complete")
+ if self.rx.Notifying:
+ success = True
+ else:
+ success = False
+ operation = BLE_SUBSCRIBE_OPERATION_SUBSCRIBE
+ if self.devMgr:
+ subscribeEvent = BleSubscribeEvent(
+ charId=self.charId_rx,
+ svcId=self.svcId_rx,
+ status=success,
+ operation=operation,
+ )
+ self.chip_queue.put(subscribeEvent)
+ self.devMgr.DriveBleIO()
+
+ def subscribeErrorCb(self, *args):
+ self.logger.error("subscribe fail, error:" + repr(args))
+ success = False
+ operation = BLE_SUBSCRIBE_OPERATION_SUBSCRIBE
+ if self.devMgr:
+ subscribeEvent = BleSubscribeEvent(
+ charId=self.charId_rx,
+ svcId=self.svcId_rx,
+ status=success,
+ operation=operation,
+ )
+ self.chip_queue.put(subscribeEvent)
+ self.devMgr.DriveBleIO()
+
+ def unsubscribeSuccessCb(self, *args):
+ self.logger.debug("unsubscribe complete")
+ success = True
+ operation = BLE_SUBSCRIBE_OPERATION_UNSUBSCRIBE
+ if self.devMgr:
+ subscribeEvent = BleSubscribeEvent(
+ charId=self.charId_rx,
+ svcId=self.svcId_rx,
+ status=success,
+ operation=operation,
+ )
+ self.chip_queue.put(subscribeEvent)
+ self.devMgr.DriveBleIO()
+
+ def unsubscribeErrorCb(self, *args):
+ self.logger.error("unsubscribe fail, error:" + repr(args))
+ success = False
+ operation = BLE_SUBSCRIBE_OPERATION_UNSUBSCRIBE
+ if self.devMgr:
+ subscribeEvent = BleSubscribeEvent(
+ charId=self.charId_rx,
+ svcId=self.svcId_rx,
+ status=success,
+ operation=operation,
+ )
+ self.chip_queue.put(subscribeEvent)
+ self.devMgr.DriveBleIO()
+
+ def SubscribeBleCharacteristic(self, connObj, svcId, charId, subscribe):
+ result = False
+ self.charId_rx = bytearray(uuid.UUID(VoidPtrToUUIDString(charId, 16)).bytes)
+ self.svcId_rx = bytearray(uuid.UUID(str(VoidPtrToUUIDString(svcId, 16))).bytes)
+
+ if self.target and self.target.Connected:
+ try:
+ if subscribe:
+ self.logger.debug("try to subscribe")
+ self.rx.StartNotify(
+ cbfunct=self.receivedNotificationCB,
+ reply_handler=self.subscribeSuccessCb,
+ error_handler=self.subscribeErrorCb,
+ timeout=BLE_SUBSCRIBE_TIMEOUT_SEC,
+ )
+ else:
+ self.logger.debug("try to unsubscribe")
+ self.rx.StopNotify(
+ reply_handler=self.unsubscribeSuccessCb,
+ error_handler=self.unsubscribeErrorCb,
+ timeout=BLE_SUBSCRIBE_TIMEOUT_SEC,
+ )
+ except Exception as ex:
+ self.logger.debug(traceback.format_exc())
+ self.logger.debug("(un)subscribe error")
+ result = True
+ else:
+ self.logger.warning("WARNING: peripheral is no longer connected.")
+ return result
+
+ def GetBleEvent(self):
+ """ Called by ChipDeviceMgr.py on behalf of Chip to retrieve a queued message."""
+ if not self.chip_queue.empty():
+ ev = self.chip_queue.get()
+
+ if isinstance(ev, BleRxEvent):
+ eventStruct = BleRxEventStruct.fromBleRxEvent(ev)
+ return cast(pointer(eventStruct), c_void_p).value
+ elif isinstance(ev, BleTxEvent):
+ eventStruct = BleTxEventStruct.fromBleTxEvent(ev)
+ return cast(pointer(eventStruct), c_void_p).value
+ elif isinstance(ev, BleSubscribeEvent):
+ eventStruct = BleSubscribeEventStruct.fromBleSubscribeEvent(ev)
+ return cast(pointer(eventStruct), c_void_p).value
+ elif isinstance(ev, BleDisconnectEvent):
+ eventStruct = BleDisconnectEventStruct.fromBleDisconnectEvent(ev)
+ return cast(pointer(eventStruct), c_void_p).value
+
+ return None
+
+ def ble_debug_log(self, line):
+ args = self.ParseInputLine(line)
+ if int(args[0]) == 1:
+ self.logger.setLevel(logging.DEBUG)
+ self.logger.debug("current logging level is debug")
+ else:
+ self.logger.setLevel(logging.INFO)
+ self.logger.info("current logging level is info")
+ return True
+
+ def CloseBle(self, connObj):
+ """ Called by Chip to close the BLE connection."""
+ # Workaround: comment out disconnect because of hang when close, plz call disconnect explicitly after close
+ # Need to fix it
+ # self.disconnect()
+ if self.devMgr:
+ dcEvent = BleDisconnectEvent(BLE_ERROR_REMOTE_DEVICE_DISCONNECTED)
+ self.chip_queue.put(dcEvent)
+ self.devMgr.DriveBleIO()
+ return True
diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py
new file mode 100644
index 0000000..a16073b
--- /dev/null
+++ b/src/controller/python/chip/ChipDeviceCtrl.py
@@ -0,0 +1,245 @@
+#
+# Copyright (c) 2020 Project CHIP Authors
+# Copyright (c) 2019-2020 Google, LLC.
+# Copyright (c) 2013-2018 Nest Labs, Inc.
+# All rights reserved.
+#
+# 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.
+#
+
+#
+# @file
+# Python interface for Chip Device Manager
+#
+
+"""Chip Device Controller interface
+"""
+
+from __future__ import absolute_import
+from __future__ import print_function
+import time
+from threading import Thread
+from ctypes import *
+from .ChipStack import *
+
+
+__all__ = ["ChipDeviceController"]
+
+
+_CompleteFunct = CFUNCTYPE(None, c_void_p, c_void_p)
+_ErrorFunct = CFUNCTYPE(None, c_void_p, c_void_p, c_ulong, POINTER(DeviceStatusStruct))
+_GetBleEventFunct = CFUNCTYPE(c_void_p)
+_WriteBleCharacteristicFunct = CFUNCTYPE(
+ c_bool, c_void_p, c_void_p, c_void_p, c_void_p, c_uint16
+)
+_SubscribeBleCharacteristicFunct = CFUNCTYPE(
+ c_bool, c_void_p, c_void_p, c_void_p, c_bool
+)
+_CloseBleFunct = CFUNCTYPE(c_bool, c_void_p)
+
+# This is a fix for WEAV-429. Jay Logue recommends revisiting this at a later
+# date to allow for truely multiple instances so this is temporary.
+def _singleton(cls):
+ instance = [None]
+
+ def wrapper(*args, **kwargs):
+ if instance[0] is None:
+ instance[0] = cls(*args, **kwargs)
+ return instance[0]
+
+ return wrapper
+
+
+@_singleton
+class ChipDeviceController(object):
+ def __init__(self, startNetworkThread=True):
+ self.devCtrl = None
+ self.networkThread = None
+ self.networkThreadRunable = False
+ self._ChipStack = ChipStack()
+ self._dmLib = None
+
+ self._InitLib()
+
+ devCtrl = c_void_p(None)
+ res = self._dmLib.nl_Chip_DeviceController_NewDeviceController(pointer(devCtrl))
+ if res != 0:
+ raise self._ChipStack.ErrorToException(res)
+
+ self.devCtrl = devCtrl
+ self._ChipStack.devCtrl = devCtrl
+
+ self.blockingCB = None # set by other modules(BLE) that require service by thread while thread blocks.
+ self.cbHandleBleEvent = (
+ None # set by other modules (BLE) that provide event callback to Chip.
+ )
+ self.cbHandleBleWriteChar = None
+ self.cbHandleBleSubscribeChar = None
+ self.cbHandleBleClose = None
+
+ if startNetworkThread:
+ self.StartNetworkThread()
+
+ def __del__(self):
+ if self.devCtrl != None:
+ self._dmLib.nl_Chip_DeviceController_DeleteDeviceManager(self.devCtrl)
+ self.devCtrl = None
+
+ def DriveBleIO(self):
+ # perform asynchronous write to pipe in IO thread's select() to wake for BLE input
+ res = self._dmLib.nl_Chip_DeviceController_WakeForBleIO()
+ if res != 0:
+ raise self._ChipStack.ErrorToException(res)
+
+ def SetBleEventCB(self, bleEventCB):
+ if self.devCtrl != None:
+ self.cbHandleBleEvent = _GetBleEventFunct(bleEventCB)
+ self._dmLib.nl_Chip_DeviceController_SetBleEventCB(self.cbHandleBleEvent)
+
+ def SetBleWriteCharCB(self, bleWriteCharCB):
+ if self.devCtrl != None:
+ self.cbHandleBleWriteChar = _WriteBleCharacteristicFunct(bleWriteCharCB)
+ self._dmLib.nl_Chip_DeviceController_SetBleWriteCharacteristic(
+ self.cbHandleBleWriteChar
+ )
+
+ def SetBleSubscribeCharCB(self, bleSubscribeCharCB):
+ if self.devCtrl != None:
+ self.cbHandleBleSubscribeChar = _SubscribeBleCharacteristicFunct(
+ bleSubscribeCharCB
+ )
+ self._dmLib.nl_Chip_DeviceController_SetBleSubscribeCharacteristic(
+ self.cbHandleBleSubscribeChar
+ )
+
+ def SetBleCloseCB(self, bleCloseCB):
+ if self.devCtrl != None:
+ self.cbHandleBleClose = _CloseBleFunct(bleCloseCB)
+ self._dmLib.nl_Chip_DeviceController_SetBleClose(self.cbHandleBleClose)
+
+ def StartNetworkThread(self):
+ if self.networkThread != None:
+ return
+
+ def RunNetworkThread():
+ while self.networkThreadRunable:
+ self._ChipStack.networkLock.acquire()
+ self._dmLib.nl_Chip_DeviceController_DriveIO(50)
+ self._ChipStack.networkLock.release()
+ time.sleep(0.005)
+
+ self.networkThread = Thread(target=RunNetworkThread, name="ChipNetworkThread")
+ self.networkThread.daemon = True
+ self.networkThreadRunable = True
+ self.networkThread.start()
+
+ def IsConnected(self):
+ return self._ChipStack.Call(
+ lambda: self._dmLib.nl_Chip_DeviceController_IsConnected(self.devCtrl)
+ )
+
+ def ConnectBle(self, bleConnection):
+ self._ChipStack.CallAsync(
+ lambda: self._dmLib.nl_Chip_DeviceController_ValidateBTP(
+ self.devCtrl,
+ bleConnection,
+ self._ChipStack.cbHandleComplete,
+ self._ChipStack.cbHandleError,
+ )
+ )
+
+ def Close(self):
+ self._ChipStack.Call(
+ lambda: self._dmLib.nl_Chip_DeviceController_Close(self.devCtrl)
+ )
+
+ def SetLogFilter(self, category):
+ if category < 0 or category > pow(2, 8):
+ raise ValueError("category must be an unsigned 8-bit integer")
+
+ self._ChipStack.Call(
+ lambda: self._dmLib.nl_Chip_DeviceController_SetLogFilter(category)
+ )
+
+ def GetLogFilter(self):
+ self._ChipStack.Call(
+ lambda: self._dmLib.nl_Chip_DeviceController_GetLogFilter()
+ )
+
+ def SetBlockingCB(self, blockingCB):
+ self._ChipStack.blockingCB = blockingCB
+
+ # ----- Private Members -----
+ def _InitLib(self):
+ if self._dmLib is None:
+ self._dmLib = CDLL(self._ChipStack.LocateChipDLL())
+
+ self._dmLib.nl_Chip_DeviceController_NewDeviceController.argtypes = [
+ POINTER(c_void_p)
+ ]
+ self._dmLib.nl_Chip_DeviceController_NewDeviceController.restype = c_uint32
+
+ self._dmLib.nl_Chip_DeviceController_DeleteDeviceController.argtypes = [
+ c_void_p
+ ]
+ self._dmLib.nl_Chip_DeviceController_DeleteDeviceController.restype = (
+ c_uint32
+ )
+
+ self._dmLib.nl_Chip_DeviceController_Close.argtypes = [c_void_p]
+ self._dmLib.nl_Chip_DeviceController_Close.restype = None
+
+ self._dmLib.nl_Chip_DeviceController_DriveIO.argtypes = [c_uint32]
+ self._dmLib.nl_Chip_DeviceController_DriveIO.restype = c_uint32
+
+ self._dmLib.nl_Chip_DeviceController_WakeForBleIO.argtypes = []
+ self._dmLib.nl_Chip_DeviceController_WakeForBleIO.restype = c_uint32
+
+ self._dmLib.nl_Chip_DeviceController_SetBleEventCB.argtypes = [
+ _GetBleEventFunct
+ ]
+ self._dmLib.nl_Chip_DeviceController_SetBleEventCB.restype = c_uint32
+
+ self._dmLib.nl_Chip_DeviceController_SetBleWriteCharacteristic.argtypes = [
+ _WriteBleCharacteristicFunct
+ ]
+ self._dmLib.nl_Chip_DeviceController_SetBleWriteCharacteristic.restype = (
+ c_uint32
+ )
+
+ self._dmLib.nl_Chip_DeviceController_SetBleSubscribeCharacteristic.argtypes = [
+ _SubscribeBleCharacteristicFunct
+ ]
+ self._dmLib.nl_Chip_DeviceController_SetBleSubscribeCharacteristic.restype = (
+ c_uint32
+ )
+
+ self._dmLib.nl_Chip_DeviceController_SetBleClose.argtypes = [_CloseBleFunct]
+ self._dmLib.nl_Chip_DeviceController_SetBleClose.restype = c_uint32
+
+ self._dmLib.nl_Chip_DeviceController_IsConnected.argtypes = [c_void_p]
+ self._dmLib.nl_Chip_DeviceController_IsConnected.restype = c_bool
+
+ self._dmLib.nl_Chip_DeviceController_ValidateBTP.argtypes = [
+ c_void_p,
+ c_void_p,
+ _CompleteFunct,
+ _ErrorFunct,
+ ]
+ self._dmLib.nl_Chip_DeviceController_ValidateBTP.restype = c_uint32
+
+ self._dmLib.nl_Chip_DeviceController_GetLogFilter.argtypes = []
+ self._dmLib.nl_Chip_DeviceController_GetLogFilter.restype = c_uint8
+
+ self._dmLib.nl_Chip_DeviceController_SetLogFilter.argtypes = [c_uint8]
+ self._dmLib.nl_Chip_DeviceController_SetLogFilter.restype = None
diff --git a/src/controller/python/chip/ChipStack.py b/src/controller/python/chip/ChipStack.py
new file mode 100644
index 0000000..6c1107b
--- /dev/null
+++ b/src/controller/python/chip/ChipStack.py
@@ -0,0 +1,410 @@
+#
+# Copyright (c) 2020 Project CHIP Authors
+# Copyright (c) 2020 Google LLC.
+# All rights reserved.
+#
+# 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.
+#
+
+#
+# @file
+# Python interface for Chip Stack
+#
+
+"""Chip Stack interface
+"""
+
+from __future__ import absolute_import
+from __future__ import print_function
+import sys
+import os
+import time
+import glob
+import platform
+import logging
+from threading import Lock, Event
+from ctypes import *
+from .ChipUtility import ChipUtility
+
+__all__ = [
+ "DeviceStatusStruct",
+ "ChipStackException",
+ "DeviceError",
+ "ChipStackError",
+ "ChipStack",
+]
+
+ChipStackDLLBaseName = "_ChipDeviceCtrl.so"
+
+
+def _singleton(cls):
+ instance = [None]
+
+ def wrapper(*args, **kwargs):
+ if instance[0] is None:
+ instance[0] = cls(*args, **kwargs)
+ return instance[0]
+
+ return wrapper
+
+
+class DeviceStatusStruct(Structure):
+ _fields_ = [
+ ("ProfileId", c_uint32),
+ ("StatusCode", c_uint16),
+ ("SysErrorCode", c_uint32),
+ ]
+
+
+class ChipStackException(Exception):
+ pass
+
+
+class ChipStackError(ChipStackException):
+ def __init__(self, err, msg=None):
+ self.err = err
+ if msg != None:
+ self.msg = msg
+ else:
+ self.msg = "Chip Stack Error %ld" % (err)
+
+ def __str__(self):
+ return self.msg
+
+
+class DeviceError(ChipStackException):
+ def __init__(self, profileId, statusCode, systemErrorCode, msg=None):
+ self.profileId = profileId
+ self.statusCode = statusCode
+ self.systemErrorCode = systemErrorCode
+ if msg is None:
+ if systemErrorCode:
+ self.msg = "[ %08X:%d ] (system err %d)" % (
+ profileId,
+ statusCode,
+ systemErrorCode,
+ )
+ else:
+ self.msg = "[ %08X:%d ]" % (profileId, statusCode)
+ else:
+ self.msg = msg
+
+ def __str__(self):
+ return "Device Error: " + self.msg
+
+
+class LogCategory(object):
+ """Debug logging categories used by chip."""
+
+ # NOTE: These values must correspond to those used in the chip C++ code.
+ Disabled = 0
+ Error = 1
+ Progress = 2
+ Detail = 3
+ Retain = 4
+
+ @staticmethod
+ def categoryToLogLevel(cat):
+ if cat == LogCategory.Error:
+ return logging.ERROR
+ elif cat == LogCategory.Progress:
+ return logging.INFO
+ elif cat == LogCategory.Detail:
+ return logging.DEBUG
+ elif cat == LogCategory.Retain:
+ return logging.CRITICAL
+ else:
+ return logging.NOTSET
+
+
+class ChipLogFormatter(logging.Formatter):
+ """A custom logging.Formatter for logging chip library messages."""
+
+ def __init__(
+ self,
+ datefmt=None,
+ logModulePrefix=False,
+ logLevel=False,
+ logTimestamp=False,
+ logMSecs=True,
+ ):
+ fmt = "%(message)s"
+ if logModulePrefix:
+ fmt = "WEAVE:%(chip-module)s: " + fmt
+ if logLevel:
+ fmt = "%(levelname)s:" + fmt
+ if datefmt is not None or logTimestamp:
+ fmt = "%(asctime)s " + fmt
+ super(ChipLogFormatter, self).__init__(fmt=fmt, datefmt=datefmt)
+ self.logMSecs = logMSecs
+
+ def formatTime(self, record, datefmt=None):
+ if datefmt is None:
+ timestampStr = time.strftime("%Y-%m-%d %H:%M:%S%z")
+ if self.logMSecs:
+ timestampUS = record.__dict__.get("timestamp-usec", 0)
+ timestampStr = "%s.%03ld" % (timestampStr, timestampUS / 1000)
+ return timestampStr
+
+
+_CompleteFunct = CFUNCTYPE(None, c_void_p, c_void_p)
+_ErrorFunct = CFUNCTYPE(None, c_void_p, c_void_p, c_ulong, POINTER(DeviceStatusStruct))
+_LogMessageFunct = CFUNCTYPE(None, c_int64, c_int64, c_char_p, c_uint8, c_char_p)
+
+
+@_singleton
+class ChipStack(object):
+ def __init__(self, installDefaultLogHandler=True):
+ self.networkLock = Lock()
+ self.completeEvent = Event()
+ self._ChipStackLib = None
+ self._chipDLLPath = None
+ self.devMgr = None
+ self.callbackRes = None
+ self._activeLogFunct = None
+ self.addModulePrefixToLogMessage = True
+
+ # Locate and load the chip shared library.
+ self._loadLib()
+
+ # Arrange to log output from the chip library to a python logger object with the
+ # name 'chip.ChipStack'. If desired, applications can override this behavior by
+ # setting self.logger to a different python logger object, or by calling setLogFunct()
+ # with their own logging function.
+ self.logger = logging.getLogger(__name__)
+ self.setLogFunct(self.defaultLogFunct)
+
+ # Determine if there are already handlers installed for the logger. Python 3.5+
+ # has a method for this; on older versions the check has to be done manually.
+ if hasattr(self.logger, "hasHandlers"):
+ hasHandlers = self.logger.hasHandlers()
+ else:
+ hasHandlers = False
+ logger = self.logger
+ while logger is not None:
+ if len(logger.handlers) > 0:
+ hasHandlers = True
+ break
+ if not logger.propagate:
+ break
+ logger = logger.parent
+
+ # If a logging handler has not already been initialized for 'chip.ChipStack',
+ # or any one of its parent loggers, automatically configure a handler to log to
+ # stdout. This maintains compatibility with a number of applications which expect
+ # chip log output to go to stdout by default.
+ #
+ # This behavior can be overridden in a variety of ways:
+ # - Initialize a different log handler before ChipStack is initialized.
+ # - Pass installDefaultLogHandler=False when initializing ChipStack.
+ # - Replace the StreamHandler on self.logger with a different handler object.
+ # - Set a different Formatter object on the existing StreamHandler object.
+ # - Reconfigure the existing ChipLogFormatter object.
+ # - Configure chip to call an application-specific logging function by
+ # calling self.setLogFunct().
+ # - Call self.setLogFunct(None), which will configure the chip library
+ # to log directly to stdout, bypassing python altogether.
+ #
+ if installDefaultLogHandler and not hasHandlers:
+ logHandler = logging.StreamHandler(stream=sys.stdout)
+ logHandler.setFormatter(ChipLogFormatter())
+ self.logger.addHandler(logHandler)
+ self.logger.setLevel(logging.DEBUG)
+
+ def HandleComplete(appState, reqState):
+ self.callbackRes = True
+ self.completeEvent.set()
+
+ def HandleError(appState, reqState, err, devStatusPtr):
+ self.callbackRes = self.ErrorToException(err, devStatusPtr)
+ self.completeEvent.set()
+
+ self.cbHandleComplete = _CompleteFunct(HandleComplete)
+ self.cbHandleError = _ErrorFunct(HandleError)
+ self.blockingCB = None # set by other modules(BLE) that require service by thread while thread blocks.
+
+ # Initialize the chip library
+ res = self._ChipStackLib.nl_Chip_Stack_Init()
+ if res != 0:
+ raise self._ChipStack.ErrorToException(res)
+
+ @property
+ def defaultLogFunct(self):
+ """Returns a python callable which, when called, logs a message to the python logger object
+ currently associated with the ChipStack object.
+ The returned function is suitable for passing to the setLogFunct() method."""
+
+ def logFunct(timestamp, timestampUSec, moduleName, logCat, message):
+ moduleName = ChipUtility.CStringToString(moduleName)
+ message = ChipUtility.CStringToString(message)
+ if self.addModulePrefixToLogMessage:
+ message = "WEAVE:%s: %s" % (moduleName, message)
+ logLevel = LogCategory.categoryToLogLevel(logCat)
+ msgAttrs = {
+ "chip-module": moduleName,
+ "timestamp": timestamp,
+ "timestamp-usec": timestampUSec,
+ }
+ self.logger.log(logLevel, message, extra=msgAttrs)
+
+ return logFunct
+
+ def setLogFunct(self, logFunct):
+ """Set the function used by the chip library to log messages.
+ The supplied object must be a python callable that accepts the following
+ arguments:
+ timestamp (integer)
+ timestampUS (integer)
+ module name (encoded UTF-8 string)
+ log category (integer)
+ message (encoded UTF-8 string)
+ Specifying None configures the chip library to log directly to stdout."""
+ if logFunct is None:
+ logFunct = 0
+ if not isinstance(logFunct, _LogMessageFunct):
+ logFunct = _LogMessageFunct(logFunct)
+ with self.networkLock:
+ # NOTE: ChipStack must hold a reference to the CFUNCTYPE object while it is
+ # set. Otherwise it may get garbage collected, and logging calls from the
+ # chip library will fail.
+ self._activeLogFunct = logFunct
+ self._ChipStackLib.nl_Chip_Stack_SetLogFunct(logFunct)
+
+ def Shutdown(self):
+ self._ChipStack.Call(lambda: self._dmLib.nl_Chip_Stack_Shutdown())
+ self.networkLock = None
+ self.completeEvent = None
+ self._ChipStackLib = None
+ self._chipDLLPath = None
+ self.devMgr = None
+ self.callbackRes = None
+
+ def Call(self, callFunct):
+ # throw error if op in progress
+ self.callbackRes = None
+ self.completeEvent.clear()
+ with self.networkLock:
+ res = callFunct()
+ self.completeEvent.set()
+ if res == 0 and self.callbackRes != None:
+ return self.callbackRes
+ return res
+
+ def CallAsync(self, callFunct):
+ # throw error if op in progress
+ self.callbackRes = None
+ self.completeEvent.clear()
+ with self.networkLock:
+ res = callFunct()
+
+ if res != 0:
+ self.completeEvent.set()
+ raise self.ErrorToException(res)
+ while not self.completeEvent.isSet():
+ if self.blockingCB:
+ self.blockingCB()
+
+ self.completeEvent.wait(0.05)
+ if isinstance(self.callbackRes, ChipStackException):
+ raise self.callbackRes
+ return self.callbackRes
+
+ def ErrorToException(self, err, devStatusPtr=None):
+ if err == 4044 and devStatusPtr:
+ devStatus = devStatusPtr.contents
+ msg = ChipUtility.CStringToString(
+ (
+ self._ChipStackLib.nl_Chip_Stack_StatusReportToString(
+ devStatus.ProfileId, devStatus.StatusCode
+ )
+ )
+ )
+ sysErrorCode = (
+ devStatus.SysErrorCode if (devStatus.SysErrorCode != 0) else None
+ )
+ if sysErrorCode != None:
+ msg = msg + " (system err %d)" % (sysErrorCode)
+ return DeviceError(
+ devStatus.ProfileId, devStatus.StatusCode, sysErrorCode, msg
+ )
+ else:
+ return ChipStackError(
+ err,
+ ChipUtility.CStringToString(
+ (self._ChipStackLib.nl_Chip_Stack_ErrorToString(err))
+ ),
+ )
+
+ def LocateChipDLL(self):
+ if self._chipDLLPath:
+ return self._chipDLLPath
+
+ 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(scriptDir, ChipStackDLLBaseName)
+ if os.path.exists(dmDLLPath):
+ self._chipDLLPath = dmDLLPath
+ return self._chipDLLPath
+
+ # 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",
+ ChipStackDLLBaseName,
+ )
+ for dir in self._AllDirsToRoot(scriptDir):
+ dmDLLPathGlob = os.path.join(dir, relDMDLLPathGlob)
+ for dmDLLPath in glob.glob(dmDLLPathGlob):
+ if os.path.exists(dmDLLPath):
+ self._chipDLLPath = dmDLLPath
+ return self._chipDLLPath
+
+ raise Exception(
+ "Unable to locate Chip Device Manager DLL (%s); expected location: %s"
+ % (ChipStackDLLBaseName, scriptDir)
+ )
+
+ # ----- Private Members -----
+ def _AllDirsToRoot(self, dir):
+ dir = os.path.abspath(dir)
+ while True:
+ yield dir
+ parent = os.path.dirname(dir)
+ if parent == "" or parent == dir:
+ break
+ dir = parent
+
+ def _loadLib(self):
+ if self._ChipStackLib is None:
+ self._ChipStackLib = CDLL(self.LocateChipDLL())
+ self._ChipStackLib.nl_Chip_Stack_Init.argtypes = []
+ self._ChipStackLib.nl_Chip_Stack_Init.restype = c_uint32
+ self._ChipStackLib.nl_Chip_Stack_Shutdown.argtypes = []
+ self._ChipStackLib.nl_Chip_Stack_Shutdown.restype = c_uint32
+ self._ChipStackLib.nl_Chip_Stack_StatusReportToString.argtypes = [
+ c_uint32,
+ c_uint16,
+ ]
+ self._ChipStackLib.nl_Chip_Stack_StatusReportToString.restype = c_char_p
+ self._ChipStackLib.nl_Chip_Stack_ErrorToString.argtypes = [c_uint32]
+ self._ChipStackLib.nl_Chip_Stack_ErrorToString.restype = c_char_p
+ self._ChipStackLib.nl_Chip_Stack_SetLogFunct.argtypes = [_LogMessageFunct]
+ self._ChipStackLib.nl_Chip_Stack_SetLogFunct.restype = c_uint32
diff --git a/src/controller/python/chip/ChipTLV.py b/src/controller/python/chip/ChipTLV.py
new file mode 100644
index 0000000..bf74c34
--- /dev/null
+++ b/src/controller/python/chip/ChipTLV.py
@@ -0,0 +1,681 @@
+#!/usr/bin/env python3
+# coding=utf-8
+
+#
+# Copyright (c) 2020 Project CHIP Authors
+# Copyright (c) 2019-2020 Google LLC.
+# All rights reserved.
+#
+# 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.
+#
+
+#
+# @file
+# This file contains definitions for working with data encoded in Chip TLV format
+#
+
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+import struct
+from collections import Mapping, Sequence, OrderedDict
+
+
+TLV_TYPE_SIGNED_INTEGER = 0x00
+TLV_TYPE_UNSIGNED_INTEGER = 0x04
+TLV_TYPE_BOOLEAN = 0x08
+TLV_TYPE_FLOATING_POINT_NUMBER = 0x0A
+TLV_TYPE_UTF8_STRING = 0x0C
+TLV_TYPE_BYTE_STRING = 0x10
+TLV_TYPE_NULL = 0x14
+TLV_TYPE_STRUCTURE = 0x15
+TLV_TYPE_ARRAY = 0x16
+TLV_TYPE_PATH = 0x17
+
+TLV_TAG_CONTROL_ANONYMOUS = 0x00
+TLV_TAG_CONTROL_CONTEXT_SPECIFIC = 0x20
+TLV_TAG_CONTROL_COMMON_PROFILE_2Bytes = 0x40
+TLV_TAG_CONTROL_COMMON_PROFILE_4Bytes = 0x60
+TLV_TAG_CONTROL_IMPLICIT_PROFILE_2Bytes = 0x80
+TLV_TAG_CONTROL_IMPLICIT_PROFILE_4Bytes = 0xA0
+TLV_TAG_CONTROL_FULLY_QUALIFIED_6Bytes = 0xC0
+TLV_TAG_CONTROL_FULLY_QUALIFIED_8Bytes = 0xE0
+
+TLVBoolean_False = TLV_TYPE_BOOLEAN
+TLVBoolean_True = TLV_TYPE_BOOLEAN + 1
+
+TLVEndOfContainer = 0x18
+
+INT8_MIN = -128
+INT16_MIN = -32768
+INT32_MIN = -2147483648
+INT64_MIN = -9223372036854775808
+
+INT8_MAX = 127
+INT16_MAX = 32767
+INT32_MAX = 2147483647
+INT64_MAX = 9223372036854775807
+
+UINT8_MAX = 255
+UINT16_MAX = 65535
+UINT32_MAX = 4294967295
+UINT64_MAX = 18446744073709551615
+
+ElementTypes = {
+ 0x00: "Signed Integer 1-byte value",
+ 0x01: "Signed Integer 2-byte value",
+ 0x02: "Signed Integer 4-byte value",
+ 0x03: "Signed Integer 8-byte value",
+ 0x04: "Unsigned Integer 1-byte value",
+ 0x05: "Unsigned Integer 2-byte value",
+ 0x06: "Unsigned Integer 4-byte value",
+ 0x07: "Unsigned Integer 8-byte value",
+ 0x08: "Boolean False",
+ 0x09: "Boolean True",
+ 0x0A: "Floating Point 4-byte value",
+ 0x0B: "Floating Point 8-byte value",
+ 0x0C: "UTF-8 String 1-byte length",
+ 0x0D: "UTF-8 String 2-byte length",
+ 0x0E: "UTF-8 String 4-byte length",
+ 0x0F: "UTF-8 String 8-byte length",
+ 0x10: "Byte String 1-byte length",
+ 0x11: "Byte String 2-byte length",
+ 0x12: "Byte String 4-byte length",
+ 0x13: "Byte String 8-byte length",
+ 0x14: "Null",
+ 0x15: "Structure",
+ 0x16: "Array",
+ 0x17: "Path",
+ 0x18: "End of Collection",
+}
+
+TagControls = {
+ 0x00: "Anonymous",
+ 0x20: "Context 1-byte",
+ 0x40: "Common Profile 2-byte",
+ 0x60: "Common Profile 4-byte",
+ 0x80: "Implicit Profile 2-byte",
+ 0xA0: "Implicit Profile 4-byte",
+ 0xC0: "Fully Qualified 6-byte",
+ 0xE0: "Fully Qualified 8-byte",
+}
+
+
+class TLVWriter(object):
+ def __init__(self, encoding=None, implicitProfile=None):
+ self._encoding = encoding if encoding is not None else bytearray()
+ self._implicitProfile = implicitProfile
+ self._containerStack = []
+
+ @property
+ def encoding(self):
+ """The object into which encoded TLV data is written.
+
+ By default this is a bytearray object.
+ """
+ return self._encoding
+
+ @encoding.setter
+ def encoding(self, val):
+ self._encoding = val
+
+ @property
+ def implicitProfile(self):
+ """The Chip profile id used when encoding implicit profile tags.
+
+ Setting this value will result in an implicit profile tag being encoded
+ whenever the profile of the tag to be encoded matches the specified implicit
+ profile id.
+
+ Setting this value to None (the default) disabled encoding of implicit
+ profile tags.
+ """
+ return self._implicitProfile
+
+ @implicitProfile.setter
+ def implicitProfile(self, val):
+ self._implicitProfile = val
+
+ def put(self, tag, val):
+ """Write a value in TLV format with the specified TLV tag.
+
+ val can be a Python object which will be encoded as follows:
+ - Python bools, floats and strings are encoded as their respective TLV types.
+ - Python ints are encoded as unsigned TLV integers if zero or positive; signed TLV
+ integers if negative.
+ - None is encoded as a TLV Null.
+ - bytes and bytearray objects are encoded as TVL byte strings.
+ - Mapping-like objects (e.g. dict) are encoded as TLV structures. The keys of the
+ map object are expected to be tag values, as described below for the tag argument.
+ Map values are encoded recursively, using the same rules as defined for the val
+ argument. The encoding order of elements depends on the type of the map object.
+ Elements within a dict are automatically encoded tag numerical order. Elements
+ within other forms of mapping object (e.g. OrderedDict) are encoded in the
+ object's natural iteration order.
+ - Sequence-like objects (e.g. arrays) are written as TLV arrays. Elements within
+ the array are encoded recursively, using the same rules as defined for the val
+ argument.
+
+ tag can be a small int (0-255), a tuple of two integers, or None.
+ If tag is an integer, it is encoded as a TLV context-specific tag.
+ If tag is a two-integer tuple, it is encoded as a TLV profile-specific tag, with
+ the first integer encoded as the profile id and the second as the tag number.
+ If tag is None, it is encoded as a TLV anonymous tag.
+ """
+ if val is None:
+ self.putNull(tag)
+ elif isinstance(val, bool):
+ self.putBool(tag, val)
+ elif isinstance(val, int):
+ if val < 0:
+ self.putSignedInt(tag, val)
+ else:
+ self.putUnsignedInt(tag, val)
+ elif isinstance(val, float):
+ self.putFloat(tag, val)
+ elif isinstance(val, str):
+ self.putString(tag, val)
+ elif isinstance(val, bytes) or isinstance(val, bytearray):
+ self.putBytes(tag, val)
+ elif isinstance(val, Mapping):
+ self.startStructure(tag)
+ if type(val) == dict:
+ val = OrderedDict(
+ sorted(val.items(), key=lambda item: tlvTagToSortKey(item[0]))
+ )
+ for containedTag, containedVal in val.items():
+ self.put(containedTag, containedVal)
+ self.endContainer()
+ elif isinstance(val, Sequence):
+ self.startArray(tag)
+ for containedVal in val:
+ self.put(None, containedVal)
+ self.endContainer()
+ else:
+ raise ValueError("Attempt to TLV encode unsupported value")
+
+ def putSignedInt(self, tag, val):
+ """Write a value as a TLV signed integer with the specified TLV tag."""
+ if val >= INT8_MIN and val <= INT8_MAX:
+ format = "<b"
+ elif val >= INT16_MIN and val <= INT16_MAX:
+ format = "<h"
+ elif val >= INT32_MIN and val <= INT32_MAX:
+ format = "<l"
+ elif val >= INT64_MIN and val <= INT64_MAX:
+ format = "<q"
+ else:
+ raise ValueError("Integer value out of range")
+ val = struct.pack(format, val)
+ controlAndTag = self._encodeControlAndTag(
+ TLV_TYPE_SIGNED_INTEGER, tag, lenOfLenOrVal=len(val)
+ )
+ self._encoding.extend(controlAndTag)
+ self._encoding.extend(val)
+
+ def putUnsignedInt(self, tag, val):
+ """Write a value as a TLV unsigned integer with the specified TLV tag."""
+ val = self._encodeUnsignedInt(val)
+ controlAndTag = self._encodeControlAndTag(
+ TLV_TYPE_UNSIGNED_INTEGER, tag, lenOfLenOrVal=len(val)
+ )
+ self._encoding.extend(controlAndTag)
+ self._encoding.extend(val)
+
+ def putFloat(self, tag, val):
+ """Write a value as a TLV float with the specified TLV tag."""
+ val = struct.pack("d", val)
+ controlAndTag = self._encodeControlAndTag(
+ TLV_TYPE_FLOATING_POINT_NUMBER, tag, lenOfLenOrVal=len(val)
+ )
+ self._encoding.extend(controlAndTag)
+ self._encoding.extend(val)
+
+ def putString(self, tag, val):
+ """Write a value as a TLV string with the specified TLV tag."""
+ val = val.encode("utf-8")
+ valLen = self._encodeUnsignedInt(len(val))
+ controlAndTag = self._encodeControlAndTag(
+ TLV_TYPE_UTF8_STRING, tag, lenOfLenOrVal=len(valLen)
+ )
+ self._encoding.extend(controlAndTag)
+ self._encoding.extend(valLen)
+ self._encoding.extend(val)
+
+ def putBytes(self, tag, val):
+ """Write a value as a TLV byte string with the specified TLV tag."""
+ valLen = self._encodeUnsignedInt(len(val))
+ controlAndTag = self._encodeControlAndTag(
+ TLV_TYPE_BYTE_STRING, tag, lenOfLenOrVal=len(valLen)
+ )
+ self._encoding.extend(controlAndTag)
+ self._encoding.extend(valLen)
+ self._encoding.extend(val)
+
+ def putBool(self, tag, val):
+ """Write a value as a TLV boolean with the specified TLV tag."""
+ if val:
+ type = TLVBoolean_True
+ else:
+ type = TLVBoolean_False
+ controlAndTag = self._encodeControlAndTag(type, tag)
+ self._encoding.extend(controlAndTag)
+
+ def putNull(self, tag):
+ """Write a TLV null with the specified TLV tag."""
+ controlAndTag = self._encodeControlAndTag(TLV_TYPE_NULL, tag)
+ self._encoding.extend(controlAndTag)
+
+ def startContainer(self, tag, containerType):
+ """Start writing a TLV container with the specified TLV tag.
+
+ containerType can be one of TLV_TYPE_STRUCTURE, TLV_TYPE_ARRAY or
+ TLV_TYPE_PATH.
+ """
+ self._verifyValidContainerType(containerType)
+ controlAndTag = self._encodeControlAndTag(containerType, tag)
+ self._encoding.extend(controlAndTag)
+ self._containerStack.insert(0, containerType)
+
+ def startStructure(self, tag):
+ """Start writing a TLV structure with the specified TLV tag."""
+ self.startContainer(tag, containerType=TLV_TYPE_STRUCTURE)
+
+ def startArray(self, tag):
+ """Start writing a TLV array with the specified TLV tag."""
+ self.startContainer(tag, containerType=TLV_TYPE_ARRAY)
+
+ def startPath(self, tag):
+ """Start writing a TLV path with the specified TLV tag."""
+ self.startContainer(tag, containerType=TLV_TYPE_PATH)
+
+ def endContainer(self):
+ """End writing the current TLV container."""
+ self._containerStack.pop(0)
+ controlAndTag = self._encodeControlAndTag(TLVEndOfContainer, None)
+ self._encoding.extend(controlAndTag)
+
+ def _encodeControlAndTag(self, type, tag, lenOfLenOrVal=0):
+ controlByte = type
+ if lenOfLenOrVal == 2:
+ controlByte |= 1
+ elif lenOfLenOrVal == 4:
+ controlByte |= 2
+ elif lenOfLenOrVal == 8:
+ controlByte |= 3
+ if tag is None:
+ if (
+ type != TLVEndOfContainer
+ and len(self._containerStack) != 0
+ and self._containerStack[0] == TLV_TYPE_STRUCTURE
+ ):
+ raise ValueError("Attempt to encode anonymous tag within TLV structure")
+ controlByte |= TLV_TAG_CONTROL_ANONYMOUS
+ return struct.pack("<B", controlByte)
+ if isinstance(tag, int):
+ if tag < 0 or tag > UINT8_MAX:
+ raise ValueError("Context-specific TLV tag number out of range")
+ if len(self._containerStack) == 0:
+ raise ValueError(
+ "Attempt to encode context-specific TLV tag at top level"
+ )
+ if self._containerStack[0] == TLV_TYPE_ARRAY:
+ raise ValueError(
+ "Attempt to encode context-specific tag within TLV array"
+ )
+ controlByte |= TLV_TAG_CONTROL_CONTEXT_SPECIFIC
+ return struct.pack("<BB", controlByte, tag)
+ if isinstance(tag, tuple):
+ (profile, tagNum) = tag
+ if not isinstance(tagNum, int):
+ raise ValueError("Invalid object given for TLV tag")
+ if tagNum < 0 or tagNum > UINT32_MAX:
+ raise ValueError("TLV tag number out of range")
+ if profile != None:
+ if not isinstance(profile, int):
+ raise ValueError("Invalid object given for TLV profile id")
+ if profile < 0 or profile > UINT32_MAX:
+ raise ValueError("TLV profile id value out of range")
+ if (
+ len(self._containerStack) != 0
+ and self._containerStack[0] == TLV_TYPE_ARRAY
+ ):
+ raise ValueError(
+ "Attempt to encode profile-specific tag within TLV array"
+ )
+ if profile is None or profile == self._implicitProfile:
+ if tagNum <= UINT16_MAX:
+ controlByte |= TLV_TAG_CONTROL_IMPLICIT_PROFILE_2Bytes
+ return struct.pack("<BH", controlByte, tagNum)
+ else:
+ controlByte |= TLV_TAG_CONTROL_IMPLICIT_PROFILE_4Bytes
+ return struct.pack("<BL", controlByte, tagNum)
+ elif profile == 0:
+ if tagNum <= UINT16_MAX:
+ controlByte |= TLV_TAG_CONTROL_COMMON_PROFILE_2Bytes
+ return struct.pack("<BH", controlByte, tagNum)
+ else:
+ controlByte |= TLV_TAG_CONTROL_COMMON_PROFILE_4Bytes
+ return struct.pack("<BL", controlByte, tagNum)
+ else:
+ if tagNum <= UINT16_MAX:
+ controlByte |= TLV_TAG_CONTROL_FULLY_QUALIFIED_6Bytes
+ return struct.pack("<BLH", controlByte, profile, tagNum)
+ else:
+ controlByte |= TLV_TAG_CONTROL_FULLY_QUALIFIED_8Bytes
+ return struct.pack("<BLL", controlByte, profile, tagNum)
+ raise ValueError("Invalid object given for TLV tag")
+
+ @staticmethod
+ def _encodeUnsignedInt(val):
+ if val < 0:
+ raise ValueError("Integer value out of range")
+ if val <= UINT8_MAX:
+ format = "<B"
+ elif val <= UINT16_MAX:
+ format = "<H"
+ elif val <= UINT32_MAX:
+ format = "<L"
+ elif val <= UINT64_MAX:
+ format = "<Q"
+ else:
+ raise ValueError("Integer value out of range")
+ return struct.pack(format, val)
+
+ @staticmethod
+ def _verifyValidContainerType(containerType):
+ if (
+ containerType != TLV_TYPE_STRUCTURE
+ and containerType != TLV_TYPE_ARRAY
+ and containerType != TLV_TYPE_PATH
+ ):
+ raise ValueError("Invalid TLV container type")
+
+
+class TLVReader(object):
+ def __init__(self, tlv):
+ self._tlv = tlv
+ self._bytesRead = 0
+ self._decodings = []
+
+ @property
+ def decoding(self):
+ return self._decodings
+
+ def get(self):
+ """Get the dictionary representation of tlv data"""
+ out = {}
+ self._get(self._tlv, self._decodings, out)
+ return out
+
+ def _decodeControlByte(self, tlv, decoding):
+ (controlByte,) = struct.unpack("<B", tlv[self._bytesRead : self._bytesRead + 1])
+ controlTypeIndex = controlByte & 0xE0
+ decoding["tagControl"] = TagControls[controlTypeIndex]
+ elementtypeIndex = controlByte & 0x1F
+ decoding["type"] = ElementTypes[elementtypeIndex]
+ self._bytesRead += 1
+
+ def _decodeControlAndTag(self, tlv, decoding):
+ """The control byte specifies the type of a TLV element and how its tag, length and value fields are encoded.
+ The control byte consists of two subfields: an element type field which occupies the lower 5 bits,
+ and a tag control field which occupies the upper 3 bits. The element type field encodes the element’s type
+ as well as how the corresponding length and value fields are encoded. In the case of Booleans and the
+ null value, the element type field also encodes the value itself."""
+
+ self._decodeControlByte(tlv, decoding)
+
+ if decoding["tagControl"] == "Anonymous":
+ decoding["tag"] = None
+ decoding["tagLen"] = 0
+ elif decoding["tagControl"] == "Context 1-byte":
+ (decoding["tag"],) = struct.unpack(
+ "<B", tlv[self._bytesRead : self._bytesRead + 1]
+ )
+ decoding["tagLen"] = 1
+ self._bytesRead += 1
+ elif decoding["tagControl"] == "Common Profile 2-byte":
+ profile = 0
+ (tag,) = struct.unpack("<H", tlv[self._bytesRead : self._bytesRead + 2])
+ decoding["profileTag"] = (profile, tag)
+ decoding["tagLen"] = 2
+ self._bytesRead += 2
+ elif decoding["tagControl"] == "Common Profile 4-byte":
+ profile = 0
+ (tag,) = struct.unpack("<L", tlv[self._bytesRead : self._bytesRead + 4])
+ decoding["profileTag"] = (profile, tag)
+ decoding["tagLen"] = 4
+ self._bytesRead += 4
+ elif decoding["tagControl"] == "Implicit Profile 2-byte":
+ profile = None
+ (tag,) = struct.unpack("<H", tlv[self._bytesRead : self._bytesRead + 2])
+ decoding["profileTag"] = (profile, tag)
+ decoding["tagLen"] = 2
+ self._bytesRead += 2
+ elif decoding["tagControl"] == "Implicit Profile 4-byte":
+ profile = None
+ (tag,) = struct.unpack("<L", tlv[self._bytesRead : self._bytesRead + 4])
+ decoding["profileTag"] = (profile, tag)
+ decoding["tagLen"] = 4
+ self._bytesRead += 4
+ elif decoding["tagControl"] == "Fully Qualified 6-byte":
+ (profile,) = struct.unpack("<L", tlv[self._bytesRead : self._bytesRead + 4])
+ (tag,) = struct.unpack("<H", tlv[self._bytesRead + 4 : self._bytesRead + 6])
+ decoding["profileTag"] = (profile, tag)
+ decoding["tagLen"] = 2
+ self._bytesRead += 6
+ elif decoding["tagControl"] == "Fully Qualified 8-byte":
+ (profile,) = struct.unpack("<L", tlv[self._bytesRead : self._bytesRead + 4])
+ (tag,) = struct.unpack("<L", tlv[self._bytesRead + 4 : self._bytesRead + 8])
+ decoding["profileTag"] = (profile, tag)
+ decoding["tagLen"] = 4
+ self._bytesRead += 8
+
+ def _decodeStrLength(self, tlv, decoding):
+ """UTF-8 or Byte StringLength fields are encoded in 0, 1, 2 or 4 byte widths, as specified by
+ the element type field. If the element type needs a length field grab the next bytes as length"""
+ if "length" in decoding["type"]:
+ if "1-byte" in decoding["type"]:
+ (decoding["strDataLen"],) = struct.unpack(
+ "<B", tlv[self._bytesRead : self._bytesRead + 1]
+ )
+ decoding["strDataLenLen"] = 1
+ self._bytesRead += 1
+ elif "2-byte" in decoding["type"]:
+ (decoding["strDataLen"],) = struct.unpack(
+ "<H", tlv[self._bytesRead : self._bytesRead + 2]
+ )
+ decoding["strDataLenLen"] = 2
+ self._bytesRead += 2
+ elif "4-byte" in decoding["type"]:
+ (decoding["strDataLen"],) = struct.unpack(
+ "<L", tlv[self._bytesRead : self._bytesRead + 4]
+ )
+ decoding["strDataLenLen"] = 4
+ self._bytesRead += 4
+ elif "8-byte" in decoding["type"]:
+ (decoding["strDataLen"],) = struct.unpack(
+ "<Q", tlv[self._bytesRead : self._bytesRead + 8]
+ )
+ decoding["strDataLenLen"] = 8
+ self._bytesRead += 8
+ else:
+ decoding["strDataLen"] = 0
+ decoding["strDataLenLen"] = 0
+
+ def _decodeVal(self, tlv, decoding):
+ """decode primitive tlv value to the corresponding python value, tlv array and path are decoded as
+ python list, tlv structure is decoded as python dictionary"""
+ if decoding["type"] == "Structure":
+ decoding["value"] = {}
+ decoding["Structure"] = []
+ self._get(tlv, decoding["Structure"], decoding["value"])
+ elif decoding["type"] == "Array":
+ decoding["value"] = []
+ decoding["Array"] = []
+ self._get(tlv, decoding["Array"], decoding["value"])
+ elif decoding["type"] == "Path":
+ decoding["value"] = []
+ decoding["Path"] = []
+ self._get(tlv, decoding["Path"], decoding["value"])
+ elif decoding["type"] == "Null":
+ decoding["value"] = None
+ elif decoding["type"] == "End of Collection":
+ decoding["value"] = None
+ elif decoding["type"] == "Boolean True":
+ decoding["value"] = True
+ elif decoding["type"] == "Boolean False":
+ decoding["value"] = False
+ elif decoding["type"] == "Unsigned Integer 1-byte value":
+ (decoding["value"],) = struct.unpack(
+ "<B", tlv[self._bytesRead : self._bytesRead + 1]
+ )
+ self._bytesRead += 1
+ elif decoding["type"] == "Signed Integer 1-byte value":
+ (decoding["value"],) = struct.unpack(
+ "<b", tlv[self._bytesRead : self._bytesRead + 1]
+ )
+ self._bytesRead += 1
+ elif decoding["type"] == "Unsigned Integer 2-byte value":
+ (decoding["value"],) = struct.unpack(
+ "<H", tlv[self._bytesRead : self._bytesRead + 2]
+ )
+ self._bytesRead += 2
+ elif decoding["type"] == "Signed Integer 2-byte value":
+ (decoding["value"],) = struct.unpack(
+ "<h", tlv[self._bytesRead : self._bytesRead + 2]
+ )
+ self._bytesRead += 2
+ elif decoding["type"] == "Unsigned Integer 4-byte value":
+ (decoding["value"],) = struct.unpack(
+ "<L", tlv[self._bytesRead : self._bytesRead + 4]
+ )
+ self._bytesRead += 4
+ elif decoding["type"] == "Signed Integer 4-byte value":
+ (decoding["value"],) = struct.unpack(
+ "<l", tlv[self._bytesRead : self._bytesRead + 4]
+ )
+ self._bytesRead += 4
+ elif decoding["type"] == "Unsigned Integer 8-byte value":
+ (decoding["value"],) = struct.unpack(
+ "<Q", tlv[self._bytesRead : self._bytesRead + 8]
+ )
+ self._bytesRead += 8
+ elif decoding["type"] == "Signed Integer 8-byte value":
+ (decoding["value"],) = struct.unpack(
+ "<q", tlv[self._bytesRead : self._bytesRead + 8]
+ )
+ self._bytesRead += 8
+ elif decoding["type"] == "Floating Point 4-byte value":
+ (decoding["value"],) = struct.unpack(
+ "<f", tlv[self._bytesRead : self._bytesRead + 4]
+ )
+ self._bytesRead += 4
+ elif decoding["type"] == "Floating Point 8-byte value":
+ (decoding["value"],) = struct.unpack(
+ "<d", tlv[self._bytesRead : self._bytesRead + 8]
+ )
+ self._bytesRead += 8
+ elif "UTF-8 String" in decoding["type"]:
+ (val,) = struct.unpack(
+ "<%ds" % decoding["strDataLen"],
+ tlv[self._bytesRead : self._bytesRead + decoding["strDataLen"]],
+ )
+ try:
+ decoding["value"] = str(val, "utf-8")
+ except Exception as ex:
+ decoding["value"] = val
+ self._bytesRead += decoding["strDataLen"]
+ elif "Byte String" in decoding["type"]:
+ (val,) = struct.unpack(
+ "<%ds" % decoding["strDataLen"],
+ tlv[self._bytesRead : self._bytesRead + decoding["strDataLen"]],
+ )
+
+ decoding["value"] = val
+ self._bytesRead += decoding["strDataLen"]
+ else:
+ raise ValueError("Attempt to decode unsupported TLV type")
+
+ def _get(self, tlv, decodings, out):
+ endOfEncoding = False
+
+ while len(tlv[self._bytesRead :]) > 0 and endOfEncoding == False:
+ decoding = {}
+ self._decodeControlAndTag(tlv, decoding)
+ self._decodeStrLength(tlv, decoding)
+ self._decodeVal(tlv, decoding)
+ decodings.append(decoding)
+
+ if decoding["type"] == "End of Collection":
+ endOfEncoding = True
+ else:
+ if "profileTag" in list(decoding.keys()):
+ out[decoding["profileTag"]] = decoding["value"]
+ elif "tag" in list(decoding.keys()):
+ if decoding["tag"] is not None:
+ out[decoding["tag"]] = decoding["value"]
+ else:
+ if isinstance(out, Mapping):
+ out["Any"] = decoding["value"]
+ elif isinstance(out, Sequence):
+ out.append(decoding["value"])
+ else:
+ raise ValueError("Attempt to decode unsupported TLV tag")
+
+
+def tlvTagToSortKey(tag):
+ if tag is None:
+ return -1
+ if isinstance(tag, int):
+ majorOrder = 0
+ elif isinstance(tag, tuple):
+ (profileId, tag) = tag
+ if profileId is None:
+ majorOrder = 1
+ else:
+ majorOrder = profileId + 2
+ else:
+ raise ValueError("Invalid TLV tag")
+ return (majorOrder << 32) + tag
+
+
+if __name__ == "__main__":
+ val = dict(
+ [
+ (1, 0),
+ (2, 65536),
+ (3, True),
+ (4, None),
+ (5, "Hello!"),
+ (6, bytearray([0xDE, 0xAD, 0xBE, 0xEF])),
+ (7, ["Goodbye!", 71024724507, False]),
+ ((0x235A0000, 42), "FOO"),
+ ((None, 42), "BAR"),
+ ]
+ )
+
+ writer = TLVWriter()
+ encodedVal = writer.put(None, val)
+
+ reader = TLVReader(writer.encoding)
+ out = reader.get()
+
+ print("TLVReader input: " + str(val))
+ print("TLVReader output: " + str(out["Any"]))
+
+ if val == out["Any"]:
+ print("Test Success")
+ else:
+ print("Test Failure")
diff --git a/src/controller/python/chip/ChipUtility.py b/src/controller/python/chip/ChipUtility.py
new file mode 100644
index 0000000..bbbe88d
--- /dev/null
+++ b/src/controller/python/chip/ChipUtility.py
@@ -0,0 +1,70 @@
+#
+# Copyright (c) 2020 Project CHIP Authors
+# Copyright (c) 2020 Google LLC.
+# All rights reserved.
+#
+# 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.
+#
+
+#
+# @file
+# This file is utility for Chip
+#
+
+from __future__ import absolute_import
+from __future__ import print_function
+import binascii
+from ctypes import *
+
+
+class ChipUtility(object):
+ @staticmethod
+ def Hexlify(val):
+ return binascii.hexlify(val).decode()
+
+ @staticmethod
+ def VoidPtrToByteArray(ptr, len):
+ if ptr:
+ v = bytearray(len)
+ memmove((c_byte * len).from_buffer(v), ptr, len)
+ return v
+ else:
+ return None
+
+ @staticmethod
+ def ByteArrayToVoidPtr(array):
+ if array != None:
+ if not (isinstance(array, bytes) or isinstance(array, bytearray)):
+ raise TypeError("Array must be an str or a bytearray")
+ return cast((c_byte * len(array)).from_buffer_copy(array), c_void_p)
+ else:
+ return c_void_p(0)
+
+ @staticmethod
+ def IsByteArrayAllZeros(array):
+ for i in range(len(array)):
+ if array[i] != 0:
+ return False
+ return True
+
+ @staticmethod
+ def ByteArrayToHex(array):
+ return ChipUtility.Hexlify(bytes(array))
+
+ @staticmethod
+ def CStringToString(s):
+ return None if s is None else s.decode()
+
+ @staticmethod
+ def StringToCString(s):
+ return None if s is None else s.encode()
diff --git a/src/controller/python/chip/__init__.py b/src/controller/python/chip/__init__.py
new file mode 100644
index 0000000..741ceaf
--- /dev/null
+++ b/src/controller/python/chip/__init__.py
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2020 Project CHIP Authors
+# Copyright (c) 2019 Google LLC.
+# All rights reserved.
+#
+# 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.
+#
+
+#
+# @file
+# Provides Python APIs for CHIP.
+#
+
+"""Provides Python APIs for CHIP."""
diff --git a/src/platform/Linux/BLEManagerImpl.cpp b/src/platform/Linux/BLEManagerImpl.cpp
index 2cc6f90..f3f6d96 100644
--- a/src/platform/Linux/BLEManagerImpl.cpp
+++ b/src/platform/Linux/BLEManagerImpl.cpp
@@ -187,12 +187,12 @@
CHIP_ERROR BLEManagerImpl::StartBLEAdvertising()
{
- return StartBluezAdv(mpAppState);
+ return StartBluezAdv((BluezEndpoint *) mpAppState);
}
CHIP_ERROR BLEManagerImpl::StopBLEAdvertising()
{
- return StopBluezAdv(mpAppState);
+ return StopBluezAdv((BluezEndpoint *) mpAppState);
}
void BLEManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event)
@@ -315,7 +315,8 @@
uint16_t BLEManagerImpl::GetMTU(BLE_CONNECTION_OBJECT conId) const
{
- return conId != nullptr ? conId->mMtu : 0;
+ BluezConnection * connection = static_cast<BluezConnection *>(conId);
+ return (connection != nullptr) ? connection->mMtu : 0;
}
bool BLEManagerImpl::SubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId)
@@ -424,19 +425,21 @@
{
CHIP_ERROR err = CHIP_NO_ERROR;
- VerifyOrExit(conId != nullptr, ChipLogProgress(DeviceLayer, "Connection is NULL in HandleTXCharCCCDWrite"));
- VerifyOrExit(conId->mpC2 != nullptr, ChipLogProgress(DeviceLayer, "C2 is NULL in HandleTXCharCCCDWrite"));
+ BluezConnection * connection = static_cast<BluezConnection *>(conId);
+
+ VerifyOrExit(connection != nullptr, ChipLogProgress(DeviceLayer, "Connection is NULL in HandleTXCharCCCDWrite"));
+ VerifyOrExit(connection->mpC2 != nullptr, ChipLogProgress(DeviceLayer, "C2 is NULL in HandleTXCharCCCDWrite"));
// Post an event to the Chip queue to process either a CHIPoBLE Subscribe or Unsubscribe based on
// whether the client is enabling or disabling indications.
{
ChipDeviceEvent event;
- event.Type = conId->mIsNotify ? DeviceEventType::kCHIPoBLESubscribe : DeviceEventType::kCHIPoBLEUnsubscribe;
- event.CHIPoBLESubscribe.ConId = conId;
+ event.Type = connection->mIsNotify ? DeviceEventType::kCHIPoBLESubscribe : DeviceEventType::kCHIPoBLEUnsubscribe;
+ event.CHIPoBLESubscribe.ConId = connection;
PlatformMgr().PostEvent(&event);
}
- ChipLogProgress(DeviceLayer, "CHIPoBLE %s received", conId->mIsNotify ? "subscribe" : "unsubscribe");
+ ChipLogProgress(DeviceLayer, "CHIPoBLE %s received", connection->mIsNotify ? "subscribe" : "unsubscribe");
exit:
if (err != CHIP_NO_ERROR)
@@ -490,7 +493,7 @@
// Register the CHIPoBLE application with the Bluez BLE layer if needed.
if (mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_Enabled && !GetFlag(mFlags, kFlag_AppRegistered))
{
- err = BluezGattsAppRegister(mpAppState);
+ err = BluezGattsAppRegister((BluezEndpoint *) mpAppState);
SetFlag(mFlags, kFlag_ControlOpInProgress);
ExitNow();
}
@@ -507,7 +510,7 @@
// be called again, and execution will proceed to the code below.
if (!GetFlag(mFlags, kFlag_AdvertisingConfigured))
{
- err = BluezAdvertisementSetup(mpAppState);
+ err = BluezAdvertisementSetup((BluezEndpoint *) mpAppState);
ExitNow();
}
diff --git a/src/platform/Linux/BLEManagerImpl.h b/src/platform/Linux/BLEManagerImpl.h
index acb5bf7..bd46fd3 100644
--- a/src/platform/Linux/BLEManagerImpl.h
+++ b/src/platform/Linux/BLEManagerImpl.h
@@ -33,7 +33,6 @@
namespace Internal {
using namespace chip::Ble;
-using namespace chip::Ble;
struct BluezEndpoint;
@@ -84,11 +83,12 @@
CHIP_ERROR ConfigureBle(uint32_t aNodeId, bool aIsCentral);
// Driven by BlueZ IO
- static void CHIPoBluez_NewConnection(BLE_CONNECTION_OBJECT conId);
- static void HandleRXCharWrite(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len);
- static void CHIPoBluez_ConnectionClosed(BLE_CONNECTION_OBJECT conId);
- static void HandleTXCharCCCDWrite(BLE_CONNECTION_OBJECT conId);
- static void HandleTXComplete(BLE_CONNECTION_OBJECT conId);
+ static void CHIPoBluez_NewConnection(BLE_CONNECTION_OBJECT user_data);
+ static void HandleRXCharWrite(BLE_CONNECTION_OBJECT user_data, const uint8_t * value, size_t len);
+ static void CHIPoBluez_ConnectionClosed(BLE_CONNECTION_OBJECT user_data);
+ static void HandleTXCharCCCDWrite(BLE_CONNECTION_OBJECT user_data);
+ static void HandleTXComplete(BLE_CONNECTION_OBJECT user_data);
+ static bool WoBLEz_TimerCb(BLE_CONNECTION_OBJECT user_data);
static void NotifyBLEPeripheralRegisterAppComplete(bool aIsSuccess, void * apAppstate);
static void NotifyBLEPeripheralAdvConfiguredComplete(bool aIsSuccess, void * apAppstate);
@@ -173,7 +173,7 @@
uint16_t mFlags;
char mDeviceName[kMaxDeviceNameLength + 1];
bool mIsCentral;
- BluezEndpoint * mpAppState;
+ void * mpAppState;
};
/**
diff --git a/src/platform/Linux/BlePlatformConfig.h b/src/platform/Linux/BlePlatformConfig.h
index a618ab0..0c56a3b 100644
--- a/src/platform/Linux/BlePlatformConfig.h
+++ b/src/platform/Linux/BlePlatformConfig.h
@@ -34,10 +34,7 @@
} // namespace chip
// ==================== Platform Adaptations ====================
-
-#define BLE_CONNECTION_OBJECT chip::DeviceLayer::Internal::BluezConnection *
#define BLE_CONNECTION_UNINITIALIZED nullptr
-
// ========== Platform-specific Configuration Overrides =========
/* none so far */
diff --git a/src/platform/Linux/CHIPBluezHelper.cpp b/src/platform/Linux/CHIPBluezHelper.cpp
index d677cac..ec2e6eb 100644
--- a/src/platform/Linux/CHIPBluezHelper.cpp
+++ b/src/platform/Linux/CHIPBluezHelper.cpp
@@ -95,18 +95,16 @@
BluezLEAdvertisement1 * adv = nullptr;
BluezObjectSkeleton * object;
GVariant * serviceData;
+ GVariant * serviceUUID;
gchar * localName;
GVariantBuilder serviceDataBuilder;
GVariantBuilder serviceUUIDsBuilder;
char * debugStr;
- const gchar * array[1];
VerifyOrExit(apEndpoint != nullptr, ChipLogProgress(DeviceLayer, "endpoint is NULL in %s", __func__));
if (apEndpoint->mpAdvPath == nullptr)
apEndpoint->mpAdvPath = g_strdup_printf("%s/advertising", apEndpoint->mpRootPath);
- array[0] = g_strdup_printf("%s", apEndpoint->mpAdvertisingUUID);
-
ChipLogProgress(DeviceLayer, "Create adv object at %s", apEndpoint->mpAdvPath);
object = bluez_object_skeleton_new(apEndpoint->mpAdvPath);
@@ -126,7 +124,9 @@
localName = g_strdup_printf("%s%04x", CHIP_DEVICE_CONFIG_BLE_DEVICE_NAME_PREFIX, getpid() & 0xffff);
serviceData = g_variant_builder_end(&serviceDataBuilder);
- debugStr = g_variant_print(serviceData, TRUE);
+ serviceUUID = g_variant_builder_end(&serviceUUIDsBuilder);
+
+ debugStr = g_variant_print(serviceData, TRUE);
ChipLogProgress(DeviceLayer, "SET service data to %s", debugStr);
g_free(debugStr);
@@ -140,7 +140,7 @@
// advertising name corresponding to the PID and object path, for debug purposes
bluez_leadvertisement1_set_local_name(adv, localName);
- bluez_leadvertisement1_set_service_uuids(adv, array);
+ bluez_leadvertisement1_set_service_uuids(adv, serviceUUID);
// 0xffff means no appearance
bluez_leadvertisement1_set_appearance(adv, 0xffff);
@@ -183,7 +183,7 @@
ChipLogProgress(DeviceLayer, "RegisterAdvertisement complete");
exit:
- BLEManagerImpl::NotifyBLEPeripheralAdvStopComplete(success == TRUE ? true : false, nullptr);
+ BLEManagerImpl::NotifyBLEPeripheralAdvStartComplete(success == TRUE ? true : false, nullptr);
if (error != nullptr)
g_error_free(error);
}
@@ -1516,7 +1516,7 @@
return G_SOURCE_REMOVE;
}
-bool SendBluezIndication(BluezConnection * apConn, chip::System::PacketBuffer * apBuf)
+bool SendBluezIndication(BLE_CONNECTION_OBJECT apConn, chip::System::PacketBuffer * apBuf)
{
ConnectionDataBundle * closure;
const char * msg = nullptr;
@@ -1529,7 +1529,7 @@
len = apBuf->DataLength();
closure = g_new(ConnectionDataBundle, 1);
- closure->mpConn = apConn;
+ closure->mpConn = static_cast<BluezConnection *>(apConn);
closure->mpVal = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, buffer, len * sizeof(uint8_t), sizeof(uint8_t));
@@ -1575,7 +1575,7 @@
return G_SOURCE_REMOVE;
}
-bool CloseBluezConnection(BluezConnection * apConn)
+bool CloseBluezConnection(BLE_CONNECTION_OBJECT apConn)
{
return BluezRunOnBluezThread(CloseBleconnectionCB, apConn);
}
@@ -1661,7 +1661,7 @@
return err;
}
-CHIP_ERROR InitBluezBleLayer(bool aIsCentral, char * apBleAddr, BLEAdvConfig & aBleAdvConfig, BluezEndpoint *& apEndpoint)
+CHIP_ERROR InitBluezBleLayer(bool aIsCentral, char * apBleAddr, BLEAdvConfig & aBleAdvConfig, void *& apEndpoint)
{
CHIP_ERROR err = CHIP_NO_ERROR;
bool retval = false;
diff --git a/src/platform/Linux/CHIPBluezHelper.h b/src/platform/Linux/CHIPBluezHelper.h
index 2d49b6b..f2d3d67 100644
--- a/src/platform/Linux/CHIPBluezHelper.h
+++ b/src/platform/Linux/CHIPBluezHelper.h
@@ -189,10 +189,10 @@
GVariant * mpVal;
};
-CHIP_ERROR InitBluezBleLayer(bool aIsCentral, char * apBleAddr, BLEAdvConfig & aBleAdvConfig, BluezEndpoint *& apEndpoint);
+CHIP_ERROR InitBluezBleLayer(bool aIsCentral, char * apBleAddr, BLEAdvConfig & aBleAdvConfig, void *& apEndpoint);
bool BluezRunOnBluezThread(int (*aCallback)(void *), void * apClosure);
-bool SendBluezIndication(BluezConnection * apConn, chip::System::PacketBuffer * apBuf);
-bool CloseBluezConnection(BluezConnection * apConn);
+bool SendBluezIndication(BLE_CONNECTION_OBJECT apConn, chip::System::PacketBuffer * apBuf);
+bool CloseBluezConnection(BLE_CONNECTION_OBJECT apConn);
CHIP_ERROR StartBluezAdv(BluezEndpoint * apEndpoint);
CHIP_ERROR StopBluezAdv(BluezEndpoint * apEndpoint);
CHIP_ERROR BluezGattsAppRegister(BluezEndpoint * apEndpoint);
diff --git a/src/platform/Linux/dbus/bluez/DbusBluez.xml b/src/platform/Linux/dbus/bluez/DbusBluez.xml
index 6f1b819..ac0952b 100644
--- a/src/platform/Linux/dbus/bluez/DbusBluez.xml
+++ b/src/platform/Linux/dbus/bluez/DbusBluez.xml
@@ -175,7 +175,9 @@
<interface name="org.bluez.LEAdvertisement1">
<method name="Release"/>
<property name="Type" type="s" access="read"/>
- <property name="ServiceUUIDs" type="as" access="read"/>
+ <property name="ServiceUUIDs" type="as" access="read">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </property>
<property name="ManufacturerData" type="a{qv}" access="read"/>
<property name="SolicitUUIDs" type="as" access="read"/>
<property name="ServiceData" type="a{sv}" access="read"/>