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"/>