Esp32/ble controller (#23921)

* ESP32 as a controller

* Add bluedroid support for esp32/ble-commissioner

* Addressed review comments
diff --git a/config/esp32/components/chip/CMakeLists.txt b/config/esp32/components/chip/CMakeLists.txt
index 0069628..e31baa2 100644
--- a/config/esp32/components/chip/CMakeLists.txt
+++ b/config/esp32/components/chip/CMakeLists.txt
@@ -143,6 +143,18 @@
     chip_gn_arg_append("chip_enable_wifi"                       "false")
 endif()
 
+if (CONFIG_ENABLE_CHIPOBLE)
+    chip_gn_arg_append("chip_enable_chipoble"                "true")
+endif()
+
+if ((CONFIG_BT_ENABLED) AND (CONFIG_ENABLE_CHIPOBLE))
+	if (CONFIG_BT_NIMBLE_ENABLED)
+          chip_gn_arg_append("chip_bt_nimble_enabled"                "true")
+        else()
+          chip_gn_arg_append("chip_bt_bluedroid_enabled"                "true")
+        endif()
+endif()
+
 if (CONFIG_OPENTHREAD_ENABLED)
     chip_gn_arg_append("chip_enable_openthread"                 "true")
 endif()
diff --git a/config/esp32/components/chip/Kconfig b/config/esp32/components/chip/Kconfig
index c5b645b..2b2175f 100644
--- a/config/esp32/components/chip/Kconfig
+++ b/config/esp32/components/chip/Kconfig
@@ -886,6 +886,21 @@
             default 900 
             help
                 The amount of time (in seconds) after which the CHIP platform will close the Commissioning Window
+    endmenu
+
+    menu "Enable ESP32 as a BLE Commissioner"
+        config ENABLE_ESP32_BLE_CONTROLLER
+            bool "Enable ESP32 as a BLE Commissioner"
+            default n
+            help
+                Enable esp32 as a BLE Commissioner.
+
+        config ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
+            bool "Enable Commissionee and Commissioner mode"
+            default n
+            depends on ENABLE_ESP32_BLE_Controller
+            help
+                Enable including commissioner code (CHIPDeviceController.cpp) in the commissionee (Server.cpp) code.
 
     endmenu
 
diff --git a/src/platform/ESP32/BLEManagerImpl.h b/src/platform/ESP32/BLEManagerImpl.h
index 9c3020f..71766ea 100644
--- a/src/platform/ESP32/BLEManagerImpl.h
+++ b/src/platform/ESP32/BLEManagerImpl.h
@@ -33,6 +33,9 @@
 
 #include "esp_bt.h"
 #include "esp_gap_ble_api.h"
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include "esp_gattc_api.h"
+#endif
 #include "esp_gatts_api.h"
 #include <lib/core/CHIPCallback.h>
 #elif CONFIG_BT_NIMBLE_ENABLED
@@ -56,22 +59,85 @@
 
 #endif
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER && CONFIG_BT_NIMBLE_ENABLED
+#include "nimble/blecent.h"
+#endif
+
 #include "ble/Ble.h"
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include <ble/BleLayer.h>
+#include <ble/BleUUID.h>
+#include <platform/ESP32/ChipDeviceScanner.h>
+#endif
 
 namespace chip {
 namespace DeviceLayer {
 namespace Internal {
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+void HandleIncomingBleConnection(Ble::BLEEndPoint * bleEP);
+
+enum class BleScanState : uint8_t
+{
+    kNotScanning,
+    kScanForDiscriminator,
+    kScanForAddress,
+    kConnecting,
+};
+
+struct BLEAdvConfig
+{
+    char * mpBleName;
+    uint32_t mAdapterId;
+    uint8_t mMajor;
+    uint8_t mMinor;
+    uint16_t mVendorId;
+    uint16_t mProductId;
+    uint64_t mDeviceId;
+    uint8_t mPairingStatus;
+    uint8_t mType;
+    uint16_t mDuration;
+    const char * mpAdvertisingUUID;
+};
+
+struct BLEScanConfig
+{
+    // If an active scan for connection is being performed
+    BleScanState mBleScanState = BleScanState::kNotScanning;
+
+    // If scanning by discriminator, what are we scanning for
+    SetupDiscriminator mDiscriminator;
+
+    // If scanning by address, what address are we searching for
+    std::string mAddress;
+
+    // Optional argument to be passed to callback functions provided by the BLE scan/connect requestor
+    void * mAppState = nullptr;
+};
+
+#endif
 /**
  * Concrete implementation of the BLEManager singleton object for the ESP32 platform.
  */
 class BLEManagerImpl final : public BLEManager,
                              private Ble::BleLayer,
                              private Ble::BlePlatformDelegate,
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+                             private Ble::BleApplicationDelegate,
+                             private Ble::BleConnectionDelegate,
+                             private ChipDeviceScannerDelegate
+#else
                              private Ble::BleApplicationDelegate
+#endif
 {
 public:
     BLEManagerImpl() {}
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    CHIP_ERROR ConfigureBle(uint32_t aAdapterId, bool aIsCentral);
+#if CONFIG_BT_BLUEDROID_ENABLED
+    static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t * param);
+#endif
+#endif
 
 private:
     // Allow the BLEManager interface class to delegate method calls to
@@ -90,6 +156,10 @@
     CHIP_ERROR _SetDeviceName(const char * deviceName);
     uint16_t _NumConnections(void);
     void _OnPlatformEvent(const ChipDeviceEvent * event);
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    void HandlePlatformSpecificBLEEvent(const ChipDeviceEvent * event);
+    CHIP_ERROR _SetCHIPoBLEServiceMode(CHIPoBLEServiceMode val);
+#endif
     ::chip::Ble::BleLayer * _GetBleLayer(void);
 
     // ===== Members that implement virtual methods on BlePlatformDelegate.
@@ -112,7 +182,23 @@
     // ===== Members that implement virtual methods on BleApplicationDelegate.
 
     void NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT conId) override;
+    // ===== Members that implement virtual methods on BleConnectionDelegate.
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
 
+    void NewConnection(chip::Ble::BleLayer * bleLayer, void * appState, const SetupDiscriminator & connDiscriminator) override;
+    CHIP_ERROR CancelConnection() override;
+
+    // ===== Members that implement virtual methods on ChipDeviceScannerDelegate
+#if CONFIG_BT_NIMBLE_ENABLED
+    virtual void OnDeviceScanned(const struct ble_hs_adv_fields & fields, const ble_addr_t & addr,
+                                 const chip::Ble::ChipBLEDeviceIdentificationInfo & info) override;
+#elif CONFIG_BT_BLUEDROID_ENABLED
+    virtual void OnDeviceScanned(esp_ble_addr_type_t & addr_type, esp_bd_addr_t & addr,
+                                 const chip::Ble::ChipBLEDeviceIdentificationInfo & info) override;
+#endif
+
+    void OnScanComplete() override;
+#endif
     // ===== Members for internal use by the following friends.
 
     friend BLEManager & BLEMgr(void);
@@ -144,6 +230,9 @@
         kMaxDeviceNameLength = 16
     };
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    BLEAdvConfig mBLEAdvConfig;
+#endif
 #if CONFIG_BT_NIMBLE_ENABLED
     uint16_t mSubscribedConIds[kMaxConnections];
 #endif
@@ -218,7 +307,15 @@
     void HandleDisconnect(esp_ble_gatts_cb_param_t * param);
     CHIPoBLEConState * GetConnectionState(uint16_t conId, bool allocate = false);
     bool ReleaseConnectionState(uint16_t conId);
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    CHIP_ERROR HandleGAPConnect(esp_ble_gattc_cb_param_t p_data);
+    CHIP_ERROR HandleGAPCentralConnect(esp_ble_gattc_cb_param_t p_data);
 
+    static void HandleConnectFailed(CHIP_ERROR error);
+    static void ConnectDevice(esp_bd_addr_t & addr, esp_ble_addr_type_t addr_type, uint16_t timeout);
+    void HandleGAPConnectionFailed();
+    CHIP_ERROR HandleRXNotify(esp_ble_gattc_cb_param_t p_data);
+#endif
     static void HandleGATTEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t * param);
     static void HandleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t * param);
 
@@ -231,10 +328,14 @@
     void HandleTXCharCCCDWrite(struct ble_gap_event * gapEvent);
     CHIP_ERROR HandleTXComplete(struct ble_gap_event * gapEvent);
     CHIP_ERROR HandleGAPConnect(struct ble_gap_event * gapEvent);
+    CHIP_ERROR HandleGAPPeripheralConnect(struct ble_gap_event * gapEvent);
     CHIP_ERROR HandleGAPDisconnect(struct ble_gap_event * gapEvent);
     CHIP_ERROR SetSubscribed(uint16_t conId);
     bool UnsetSubscribed(uint16_t conId);
     bool IsSubscribed(uint16_t conId);
+    static void ConnectDevice(const ble_addr_t & addr, uint16_t timeout);
+    CHIP_ERROR HandleGAPCentralConnect(struct ble_gap_event * gapEvent);
+    void HandleGAPConnectionFailed(struct ble_gap_event * gapEvent, CHIP_ERROR error);
 
     static CHIP_ERROR bleprph_set_random_addr(void);
     static void bleprph_host_task(void * param);
@@ -249,6 +350,28 @@
                                                    void * arg);
     void HandleC3CharRead(struct ble_gatt_char_context * param);
 #endif /* CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING */
+
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    static int btshell_on_mtu(uint16_t conn_handle, const struct ble_gatt_error * error, uint16_t mtu, void * arg);
+
+    bool SubOrUnsubChar(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId,
+                        bool subscribe);
+
+    static void OnGattDiscComplete(const struct peer * peer, int status, void * arg);
+    static void HandleConnectFailed(CHIP_ERROR error);
+    CHIP_ERROR HandleRXNotify(struct ble_gap_event * event);
+#endif
+#endif
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    static void CancelConnect(void);
+    static void HandleConnectTimeout(chip::System::Layer *, void * context);
+    void InitiateScan(BleScanState scanType);
+    static void InitiateScan(intptr_t arg);
+    void HandleAdvertisementTimer(System::Layer * systemLayer, void * context);
+    void HandleAdvertisementTimer();
+    void CleanScanConfig();
+    BLEScanConfig mBLEScanConfig;
+    bool mIsCentral;
 #endif
 
     static void DriveBLEState(intptr_t arg);
diff --git a/src/platform/ESP32/BUILD.gn b/src/platform/ESP32/BUILD.gn
index 29d3464..fce0f93 100644
--- a/src/platform/ESP32/BUILD.gn
+++ b/src/platform/ESP32/BUILD.gn
@@ -24,6 +24,9 @@
   chip_use_factory_data_provider = false
   chip_use_device_info_provider = false
   chip_config_software_version_number = 0
+  chip_enable_chipoble = true
+  chip_bt_nimble_enabled = false
+  chip_bt_bluedroid_enabled = false
 }
 
 defines = [
@@ -33,7 +36,6 @@
 static_library("ESP32") {
   sources = [
     "../SingletonConfigurationManager.cpp",
-    "BLEManagerImpl.h",
     "CHIPDevicePlatformConfig.h",
     "CHIPDevicePlatformEvent.h",
     "ConfigurationManagerImpl.cpp",
@@ -55,8 +57,6 @@
     "PlatformManagerImpl.h",
     "SystemTimeSupport.cpp",
     "SystemTimeSupport.h",
-    "bluedroid/BLEManagerImpl.cpp",
-    "nimble/BLEManagerImpl.cpp",
   ]
 
   deps = [
@@ -70,6 +70,7 @@
     "${chip_root}/src/crypto",
     "${chip_root}/src/platform:platform_base",
   ]
+
   if (chip_enable_ota_requestor) {
     sources += [
       "OTAImageProcessorImpl.cpp",
@@ -77,6 +78,30 @@
     ]
   }
 
+  if (chip_enable_chipoble) {
+    sources += [
+      "BLEManagerImpl.h",
+      "ChipDeviceScanner.h",
+    ]
+  }
+
+  if (chip_bt_nimble_enabled) {
+    sources += [
+      "nimble/BLEManagerImpl.cpp",
+      "nimble/ChipDeviceScanner.cpp",
+      "nimble/blecent.h",
+      "nimble/misc.c",
+      "nimble/peer.c",
+    ]
+  }
+
+  if (chip_bt_bluedroid_enabled) {
+    sources += [
+      "bluedroid/BLEManagerImpl.cpp",
+      "bluedroid/ChipDeviceScanner.cpp",
+    ]
+  }
+
   if (chip_enable_wifi) {
     sources += [
       "ConnectivityManagerImpl_WiFi.cpp",
diff --git a/src/platform/ESP32/CHIPDevicePlatformConfig.h b/src/platform/ESP32/CHIPDevicePlatformConfig.h
index 7f2e4a9..13360bf 100644
--- a/src/platform/ESP32/CHIPDevicePlatformConfig.h
+++ b/src/platform/ESP32/CHIPDevicePlatformConfig.h
@@ -97,3 +97,4 @@
 #define CHIP_DEVICE_CONFIG_CHIP_KVS_NAMESPACE_PARTITION CONFIG_CHIP_KVS_NAMESPACE_PARTITION_LABEL
 #define CHIP_DEVICE_CONFIG_ENABLE_DEVICE_INSTANCE_INFO_PROVIDER CONFIG_ENABLE_ESP32_DEVICE_INSTANCE_INFO_PROVIDER
 #define CHIP_DEVICE_CONFIG_DISCOVERY_TIMEOUT_SECS CONFIG_CHIP_DISCOVERY_TIMEOUT_SECS
+#define CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
diff --git a/src/platform/ESP32/CHIPDevicePlatformEvent.h b/src/platform/ESP32/CHIPDevicePlatformEvent.h
index c4e0df1..b7664ef 100644
--- a/src/platform/ESP32/CHIPDevicePlatformEvent.h
+++ b/src/platform/ESP32/CHIPDevicePlatformEvent.h
@@ -44,6 +44,21 @@
     kESPSystemEvent = kRange_PublicPlatformSpecific,
 };
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+/**
+ * Enumerates ESP32 platform-specific event types that are internal to the Chip Device Layer.
+ */
+enum InternalPlatformSpecificEventTypes
+{
+    kPlatformESP32Event = kRange_InternalPlatformSpecific,
+    kPlatformESP32BLECentralConnected,
+    kPlatformESP32BLECentralConnectFailed,
+    kPlatformESP32BLEWriteComplete,
+    kPlatformESP32BLESubscribeOpComplete,
+    kPlatformESP32BLEIndicationReceived,
+};
+
+#endif
 } // namespace DeviceEventType
 
 /**
@@ -74,6 +89,30 @@
                 wifi_event_ap_probe_req_rx_t WiFiApProbeReqRecved;
             } Data;
         } ESPSystemEvent;
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+        struct
+        {
+            BLE_CONNECTION_OBJECT mConnection;
+        } BLECentralConnected;
+        struct
+        {
+            CHIP_ERROR mError;
+        } BLECentralConnectFailed;
+        struct
+        {
+            BLE_CONNECTION_OBJECT mConnection;
+        } BLEWriteComplete;
+        struct
+        {
+            BLE_CONNECTION_OBJECT mConnection;
+            bool mIsSubscribed;
+        } BLESubscribeOpComplete;
+        struct
+        {
+            BLE_CONNECTION_OBJECT mConnection;
+            chip::System::PacketBuffer * mData;
+        } BLEIndicationReceived;
+#endif
     };
 };
 
diff --git a/src/platform/ESP32/ChipDeviceScanner.h b/src/platform/ESP32/ChipDeviceScanner.h
new file mode 100644
index 0000000..90e866b
--- /dev/null
+++ b/src/platform/ESP32/ChipDeviceScanner.h
@@ -0,0 +1,114 @@
+/*
+ *
+ *    Copyright (c) 2022 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.
+ */
+
+#pragma once
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+
+#include <ble/CHIPBleServiceData.h>
+#include <lib/core/CHIPError.h>
+#include <system/SystemLayer.h>
+
+#if CONFIG_BT_NIMBLE_ENABLED
+#include "host/ble_hs.h"
+#else
+#include "esp_bt.h"
+#include "esp_bt_main.h"
+#include "esp_gap_ble_api.h"
+#include "esp_gatt_common_api.h"
+#include "esp_gatt_defs.h"
+#include "esp_gattc_api.h"
+#include "esp_gatts_api.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include <ble/CHIPBleServiceData.h>
+#include <lib/core/CHIPError.h>
+#include <system/SystemLayer.h>
+#endif
+
+namespace chip {
+namespace DeviceLayer {
+namespace Internal {
+
+/// Receives callbacks when chip devices are being scanned
+class ChipDeviceScannerDelegate
+{
+public:
+    virtual ~ChipDeviceScannerDelegate() {}
+
+    // Called when a CHIP device was found
+#if CONFIG_BT_NIMBLE_ENABLED
+    virtual void OnDeviceScanned(const struct ble_hs_adv_fields & fields, const ble_addr_t & addr,
+                                 const chip::Ble::ChipBLEDeviceIdentificationInfo & info) = 0;
+#else
+    virtual void OnDeviceScanned(esp_ble_addr_type_t & addr_type, esp_bd_addr_t & addr,
+                                 const chip::Ble::ChipBLEDeviceIdentificationInfo & info) = 0;
+#endif
+    // Called when a scan was completed (stopped or timed out)
+    virtual void OnScanComplete() = 0;
+};
+
+/// Allows scanning for CHIP devices
+/// Will perform scan operations and call back whenever a device is discovered.
+class ChipDeviceScanner
+{
+public:
+    ChipDeviceScanner(ChipDeviceScanner &&)      = delete;
+    ChipDeviceScanner(const ChipDeviceScanner &) = delete;
+    ChipDeviceScanner & operator=(const ChipDeviceScanner &) = delete;
+
+    ~ChipDeviceScanner() = default;
+
+    static ChipDeviceScanner & GetInstance()
+    {
+        static ChipDeviceScanner instance;
+        return instance;
+    }
+
+    /// Initializes the scanner
+    CHIP_ERROR Init(ChipDeviceScannerDelegate * delegate)
+    {
+        VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+        mDelegate = delegate;
+        return CHIP_NO_ERROR;
+    }
+
+    /// Initiate a scan for devices, with the given timeout
+    CHIP_ERROR StartScan(uint16_t timeout);
+
+    /// Stop any currently running scan
+    CHIP_ERROR StopScan();
+
+    bool mIsScanning = false;
+#if CONFIG_BT_NIMBLE_ENABLED
+    void ReportDevice(const struct ble_hs_adv_fields & fields, const ble_addr_t & addr);
+#else
+    void ReportDevice(esp_ble_gap_cb_param_t & fields, esp_bd_addr_t & addr);
+#endif
+
+private:
+    ChipDeviceScanner() = default;
+
+    /// Check if a given device is a CHIP device and if yes, report it as discovered
+    static int OnBleCentralEvent(struct ble_gap_event * event, void * arg);
+    void RemoveDevice();
+    ChipDeviceScannerDelegate * mDelegate = nullptr;
+};
+
+} // namespace Internal
+} // namespace DeviceLayer
+} // namespace chip
+#endif // CONFIG_ENABLE_ESP32_BLE_CONTROLLER
diff --git a/src/platform/ESP32/bluedroid/BLEManagerImpl.cpp b/src/platform/ESP32/bluedroid/BLEManagerImpl.cpp
index 1ffbe16..46fca5f 100644
--- a/src/platform/ESP32/bluedroid/BLEManagerImpl.cpp
+++ b/src/platform/ESP32/bluedroid/BLEManagerImpl.cpp
@@ -28,14 +28,29 @@
 #if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE
 
 #include "sdkconfig.h"
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include <lib/support/CodeUtils.h>
+#endif
 
 #if CONFIG_BT_BLUEDROID_ENABLED
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include <ble/BleLayer.h>
+#endif
 #include <ble/CHIPBleServiceData.h>
 #include <lib/support/CodeUtils.h>
 #include <lib/support/logging/CHIPLogging.h>
 #include <platform/CommissionableDataProvider.h>
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include <platform/DeviceInstanceInfoProvider.h>
+#include <platform/ESP32/BLEManagerImpl.h>
+#include <platform/ESP32/ChipDeviceScanner.h>
+#endif
 #include <platform/internal/BLEManager.h>
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include <setup_payload/AdditionalDataPayloadGenerator.h>
+#include <system/SystemTimer.h>
+#endif
 
 #include "esp_bt.h"
 #include "esp_bt_main.h"
@@ -48,6 +63,7 @@
 #define CHIP_ADV_DATA_TYPE_FLAGS 0x01
 #define CHIP_ADV_DATA_FLAGS 0x06
 #define CHIP_ADV_DATA_TYPE_SERVICE_DATA 0x16
+#define CHIP_MAX_MTU_SIZE 256
 
 using namespace ::chip;
 using namespace ::chip::Ble;
@@ -64,18 +80,26 @@
     ChipBLEDeviceIdentificationInfo DeviceIdInfo;
 };
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+static constexpr uint16_t kNewConnectionScanTimeout = 60;
+static constexpr uint16_t kConnectTimeout           = 20;
+#endif
+
 const uint16_t CHIPoBLEAppId = 0x235A;
 
-const uint8_t UUID_PrimaryService[]        = { 0x00, 0x28 };
-const uint8_t UUID_CharDecl[]              = { 0x03, 0x28 };
-const uint8_t UUID_ClientCharConfigDesc[]  = { 0x02, 0x29 };
-const uint8_t UUID_CHIPoBLEService[]       = { 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
+const uint8_t UUID_PrimaryService[]       = { 0x00, 0x28 };
+const uint8_t UUID_CharDecl[]             = { 0x03, 0x28 };
+const uint8_t UUID_ClientCharConfigDesc[] = { 0x02, 0x29 };
+const uint8_t UUID_CHIPoBLEService[]      = { 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
                                          0x00, 0x10, 0x00, 0x00, 0xF6, 0xFF, 0x00, 0x00 };
-const uint8_t ShortUUID_CHIPoBLEService[]  = { 0xF6, 0xFF };
-const uint8_t UUID_CHIPoBLEChar_RX[]       = { 0x11, 0x9D, 0x9F, 0x42, 0x9C, 0x4F, 0x9F, 0x95,
+const uint8_t ShortUUID_CHIPoBLEService[] = { 0xF6, 0xFF };
+const uint8_t UUID_CHIPoBLEChar_RX[]      = { 0x11, 0x9D, 0x9F, 0x42, 0x9C, 0x4F, 0x9F, 0x95,
                                          0x59, 0x45, 0x3D, 0x26, 0xF5, 0x2E, 0xEE, 0x18 };
-const uint8_t UUID_CHIPoBLEChar_TX[]       = { 0x12, 0x9D, 0x9F, 0x42, 0x9C, 0x4F, 0x9F, 0x95,
+const uint8_t UUID_CHIPoBLEChar_TX[]      = { 0x12, 0x9D, 0x9F, 0x42, 0x9C, 0x4F, 0x9F, 0x95,
                                          0x59, 0x45, 0x3D, 0x26, 0xF5, 0x2E, 0xEE, 0x18 };
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+const uint8_t ShortUUID_CHIPoBLE_CharTx_Desc[] = { 0x02, 0x29 };
+#endif
 const ChipBleUUID ChipUUID_CHIPoBLEChar_RX = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, 0x9F,
                                                  0x9D, 0x11 } };
 const ChipBleUUID ChipUUID_CHIPoBLEChar_TX = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, 0x9F,
@@ -124,15 +148,66 @@
 
 } // unnamed namespace
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+ChipDeviceScanner & mDeviceScanner = Internal::ChipDeviceScanner::GetInstance();
+#endif
 BLEManagerImpl BLEManagerImpl::sInstance;
 constexpr System::Clock::Timeout BLEManagerImpl::kFastAdvertiseTimeout;
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+static esp_gattc_char_elem_t * char_elem_result   = NULL;
+static esp_gattc_descr_elem_t * descr_elem_result = NULL;
+
+/// Declare static functions
+static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t * param);
+
+static esp_bt_uuid_t remote_filter_service_uuid = {
+    .len = ESP_UUID_LEN_16,
+    .uuid = {.uuid16 = 0xFFF6,},
+};
+static esp_bt_uuid_t notify_descr_uuid = {
+    .len = ESP_UUID_LEN_16,
+};
+
+static bool connect    = false;
+static bool get_server = false;
+static uint16_t connId;
+
+#define PROFILE_NUM 1
+#define PROFILE_A_APP_ID 0
+#define INVALID_HANDLE 0
+
+struct gattc_profile_inst
+{
+    esp_gattc_cb_t gattc_cb;
+    uint16_t gattc_if;
+    uint16_t app_id;
+    uint16_t conn_id;
+    uint16_t service_start_handle;
+    uint16_t service_end_handle;
+    uint16_t notify_char_handle;
+    uint16_t write_char_handle;
+    esp_bd_addr_t remote_bda;
+};
+
+/* One gatt-based profile one app_id and one , this array will store the gattc_if returned by ESP_GATTS_REG_EVT */
+static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
+    [PROFILE_A_APP_ID] = {
+        .gattc_cb = BLEManagerImpl::gattc_profile_event_handler,
+        .gattc_if = ESP_GATT_IF_NONE,       /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
+    },
+};
+#endif
 
 CHIP_ERROR BLEManagerImpl::_Init()
 {
     CHIP_ERROR err;
 
     // Initialize the Chip BleLayer.
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    err = BleLayer::Init(this, this, this, &DeviceLayer::SystemLayer());
+#else
     err = BleLayer::Init(this, this, &DeviceLayer::SystemLayer());
+#endif
     SuccessOrExit(err);
 
     memset(mCons, 0, sizeof(mCons));
@@ -142,8 +217,15 @@
     mRXCharAttrHandle     = 0;
     mTXCharAttrHandle     = 0;
     mTXCharCCCDAttrHandle = 0;
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    mFlags.ClearAll().Set(Flags::kAdvertisingEnabled, CHIP_DEVICE_CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART && !mIsCentral);
+    mFlags.Set(Flags::kFastAdvertisingEnabled, !mIsCentral);
+    OnChipBleConnectReceived = HandleIncomingBleConnection;
+
+#else
     mFlags.ClearAll().Set(Flags::kAdvertisingEnabled, CHIP_DEVICE_CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART);
     mFlags.Set(Flags::kFastAdvertisingEnabled, true);
+#endif
     memset(mDeviceName, 0, sizeof(mDeviceName));
 
     PlatformMgr().ScheduleWork(DriveBLEState, 0);
@@ -277,22 +359,445 @@
         mFlags.Set(Flags::kAdvertisingRefreshNeeded);
 
         DriveBLEState();
+        break;
 
     default:
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+        HandlePlatformSpecificBLEEvent(event);
+#endif
         break;
     }
 }
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+void BLEManagerImpl::HandlePlatformSpecificBLEEvent(const ChipDeviceEvent * apEvent)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    ChipLogProgress(DeviceLayer, "HandlePlatformSpecificBLEEvent %d", apEvent->Type);
+
+    switch (apEvent->Type)
+    {
+    case DeviceEventType::kPlatformESP32BLECentralConnected:
+        if (BLEManagerImpl::mBLEScanConfig.mBleScanState == BleScanState::kConnecting)
+        {
+            BleConnectionDelegate::OnConnectionComplete(mBLEScanConfig.mAppState,
+                                                        apEvent->Platform.BLECentralConnected.mConnection);
+            CleanScanConfig();
+        }
+        break;
+
+    case DeviceEventType::kPlatformESP32BLECentralConnectFailed:
+        if (BLEManagerImpl::mBLEScanConfig.mBleScanState == BleScanState::kConnecting)
+        {
+            BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, apEvent->Platform.BLECentralConnectFailed.mError);
+            CleanScanConfig();
+        }
+        break;
+
+    case DeviceEventType::kPlatformESP32BLEWriteComplete:
+        HandleWriteConfirmation(apEvent->Platform.BLEWriteComplete.mConnection, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_RX);
+        break;
+
+    case DeviceEventType::kPlatformESP32BLESubscribeOpComplete:
+        if (apEvent->Platform.BLESubscribeOpComplete.mIsSubscribed)
+            HandleSubscribeComplete(apEvent->Platform.BLESubscribeOpComplete.mConnection, &CHIP_BLE_SVC_ID,
+                                    &ChipUUID_CHIPoBLEChar_TX);
+        else
+            HandleUnsubscribeComplete(apEvent->Platform.BLESubscribeOpComplete.mConnection, &CHIP_BLE_SVC_ID,
+                                      &ChipUUID_CHIPoBLEChar_TX);
+        break;
+
+    case DeviceEventType::kPlatformESP32BLEIndicationReceived:
+        HandleIndicationReceived(apEvent->Platform.BLEIndicationReceived.mConnection, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX,
+                                 PacketBufferHandle::Adopt(apEvent->Platform.BLEIndicationReceived.mData));
+        break;
+
+    default:
+        break;
+    }
+
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(DeviceLayer, "Disabling CHIPoBLE service due to error: %s", ErrorStr(err));
+        mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled;
+    }
+}
+
+void BLEManagerImpl::gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
+                                                 esp_ble_gattc_cb_param_t * param)
+{
+    esp_ble_gattc_cb_param_t * p_data = (esp_ble_gattc_cb_param_t *) param;
+    CHIP_ERROR err                    = CHIP_NO_ERROR;
+
+    switch (event)
+    {
+    case ESP_GATTC_REG_EVT:
+        break;
+    case ESP_GATTC_CONNECT_EVT:
+        err = sInstance.HandleGAPConnect(*p_data);
+        SuccessOrExit(err);
+        break;
+    case ESP_GATTC_OPEN_EVT:
+        if (param->open.status != ESP_GATT_OK)
+        {
+            ChipLogProgress(Ble, "open failed, status %d", p_data->open.status);
+            break;
+        }
+        ChipLogProgress(Ble, "open success");
+        break;
+    case ESP_GATTC_DIS_SRVC_CMPL_EVT:
+        if (param->dis_srvc_cmpl.status != ESP_GATT_OK)
+        {
+            ChipLogProgress(Ble, "discover service failed, status %d", param->dis_srvc_cmpl.status);
+            break;
+        }
+        ChipLogProgress(Ble, "discover service complete conn_id %d", param->dis_srvc_cmpl.conn_id);
+        esp_ble_gattc_search_service(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, param->cfg_mtu.conn_id,
+                                     &remote_filter_service_uuid);
+        break;
+    case ESP_GATTC_CFG_MTU_EVT:
+        if (param->cfg_mtu.status != ESP_GATT_OK)
+        {
+            ChipLogProgress(Ble, "config mtu failed, error status = %x", param->cfg_mtu.status);
+        }
+        ChipLogProgress(Ble, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu,
+                        param->cfg_mtu.conn_id);
+        break;
+    case ESP_GATTC_SEARCH_RES_EVT: {
+        ChipLogProgress(Ble, "SEARCH RES: conn_id = %x is primary service %d", p_data->search_res.conn_id,
+                        p_data->search_res.is_primary);
+        ChipLogProgress(Ble, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle,
+                        p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id);
+        if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == 0xFFF6)
+        {
+            ChipLogProgress(Ble, "service found");
+            get_server                                            = true;
+            gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle;
+            gl_profile_tab[PROFILE_A_APP_ID].service_end_handle   = p_data->search_res.end_handle;
+            ChipLogProgress(Ble, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16);
+        }
+        break;
+    }
+    case ESP_GATTC_SEARCH_CMPL_EVT: {
+        if (p_data->search_cmpl.status != ESP_GATT_OK)
+        {
+            ChipLogProgress(Ble, "search service failed, error status = %x", p_data->search_cmpl.status);
+            break;
+        }
+        if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE)
+        {
+            ChipLogProgress(Ble, "Get service information from remote device");
+        }
+        else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH)
+        {
+            ChipLogProgress(Ble, "Get service information from flash");
+        }
+        else
+        {
+            ChipLogProgress(Ble, "unknown service source");
+        }
+        ChipLogProgress(Ble, "ESP_GATTC_SEARCH_CMPL_EVT");
+        if (get_server)
+        {
+            uint16_t count  = 0;
+            uint16_t offset = 0;
+            esp_gatt_status_t status =
+                esp_ble_gattc_get_attr_count(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, p_data->search_cmpl.conn_id,
+                                             ESP_GATT_DB_CHARACTERISTIC, gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
+                                             gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, INVALID_HANDLE, &count);
+            if (status != ESP_GATT_OK)
+            {
+                ChipLogProgress(Ble, "esp_ble_gattc_get_attr_count error");
+            }
+            ChipLogProgress(Ble, "Count : %d", count);
+
+            if (count > 0)
+            {
+                char_elem_result = (esp_gattc_char_elem_t *) malloc(sizeof(esp_gattc_char_elem_t) * count);
+                // memset(char_elem_result, 0xff, sizeof(esp_gattc_char_elem_t) * count);
+                if (!char_elem_result)
+                {
+                    ChipLogProgress(Ble, "gattc no mem");
+                    break;
+                }
+                else
+                {
+                    status = esp_ble_gattc_get_all_char(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, p_data->search_cmpl.conn_id,
+                                                        gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
+                                                        gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, char_elem_result,
+                                                        &count, offset);
+                    if (status != 0)
+                    {
+                        ChipLogProgress(Ble, "esp_ble_gattc_get_char_by_uuid error");
+                    }
+
+                    /*  Every service have only one char in our 'ESP_GATTS_DEMO' demo, so we used first 'char_elem_result' */
+                }
+            }
+            if (count > 0)
+            {
+                for (int i = 0; i < count; i++)
+                {
+                    if (char_elem_result[i].uuid.len == ESP_UUID_LEN_128)
+                    {
+                        if (char_elem_result[i].properties & CharProps_Write)
+                        {
+                            gl_profile_tab[PROFILE_A_APP_ID].write_char_handle = char_elem_result[i].char_handle;
+                        }
+                        else if (char_elem_result[i].properties & CharProps_ReadNotify)
+                        {
+                            gl_profile_tab[PROFILE_A_APP_ID].notify_char_handle = char_elem_result[i].char_handle;
+                            esp_ble_gattc_register_for_notify(gl_profile_tab[PROFILE_A_APP_ID].gattc_if,
+                                                              gl_profile_tab[PROFILE_A_APP_ID].remote_bda,
+                                                              char_elem_result[i].char_handle);
+                        }
+                    }
+                }
+            }
+            free(char_elem_result);
+        }
+        else
+        {
+            ChipLogProgress(Ble, "no char found");
+        }
+    }
+    break;
+    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
+        ChipLogProgress(Ble, "ESP_GATTC_REG_FOR_NOTIFY_EVT");
+        if (p_data->reg_for_notify.status != ESP_GATT_OK)
+        {
+            ChipLogProgress(Ble, "REG FOR NOTIFY failed: error status = %d", p_data->reg_for_notify.status);
+        }
+        else
+        {
+            uint16_t count               = 0;
+            esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(
+                gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id, ESP_GATT_DB_DESCRIPTOR,
+                gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
+                gl_profile_tab[PROFILE_A_APP_ID].notify_char_handle, &count);
+            if (ret_status != ESP_GATT_OK)
+            {
+                ChipLogProgress(Ble, "esp_ble_gattc_get_attr_count error");
+            }
+            if (count > 0)
+            {
+                descr_elem_result = (esp_gattc_descr_elem_t *) malloc(sizeof(esp_gattc_descr_elem_t) * count);
+                if (!descr_elem_result)
+                {
+                    ChipLogProgress(Ble, "malloc error, gattc no mem");
+                }
+                else
+                {
+                    memcpy(&notify_descr_uuid.uuid.uuid16, ShortUUID_CHIPoBLE_CharTx_Desc, 2);
+                    ret_status = esp_ble_gattc_get_descr_by_char_handle(
+                        gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id,
+                        p_data->reg_for_notify.handle, notify_descr_uuid, descr_elem_result, &count);
+                    ChipLogProgress(Ble, "discoverd all chars and discr.........\n\n");
+
+                    ChipDeviceEvent chipEvent;
+                    chipEvent.Type                                     = DeviceEventType::kPlatformESP32BLECentralConnected;
+                    chipEvent.Platform.BLECentralConnected.mConnection = connId;
+                    PlatformMgr().PostEventOrDie(&chipEvent);
+                    if (ret_status != ESP_GATT_OK)
+                    {
+                        ChipLogProgress(Ble, "esp_ble_gattc_get_descr_by_char_handle error");
+                    }
+                    /* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */
+                    if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 &&
+                        descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG)
+                    {
+                    }
+
+                    if (ret_status != ESP_GATT_OK)
+                    {
+                        ChipLogProgress(Ble, "esp_ble_gattc_write_char_descr error");
+                    }
+                    free(descr_elem_result);
+                }
+            }
+        }
+        ChipLogProgress(Ble, "decsr not found");
+    }
+    break;
+    case ESP_GATTC_NOTIFY_EVT:
+        if (p_data->notify.is_notify)
+        {
+            ChipLogProgress(Ble, "ESP_GATTC_NOTIFY_EVT, receive notify value:");
+        }
+        else
+        {
+            ChipLogProgress(Ble, "ESP_GATTC_NOTIFY_EVT, receive indicate value:");
+        }
+        err = sInstance.HandleRXNotify(*p_data);
+        SuccessOrExit(err);
+
+        break;
+    case ESP_GATTC_WRITE_DESCR_EVT:
+        if (p_data->write.status != ESP_GATT_OK)
+        {
+            ChipLogProgress(Ble, "write descr failed, error status = %x", p_data->write.status);
+            break;
+        }
+        ChipLogProgress(Ble, "write descr success ");
+        break;
+    case ESP_GATTC_SRVC_CHG_EVT: {
+        esp_bd_addr_t bda;
+        memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t));
+        ChipLogProgress(Ble, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:");
+        break;
+    }
+    case ESP_GATTC_WRITE_CHAR_EVT:
+        if (p_data->write.status != ESP_GATT_OK)
+        {
+            ChipLogProgress(Ble, "write char failed, error status = %x", p_data->write.status);
+            break;
+        }
+        ChipLogProgress(Ble, "write char success ");
+        break;
+    case ESP_GATTC_DISCONNECT_EVT:
+        connect    = false;
+        get_server = false;
+        ChipLogProgress(Ble, "ESP_GATTC_DISCONNECT_EVT, reason = %d", p_data->disconnect.reason);
+        break;
+    default:
+        break;
+    }
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(DeviceLayer, "Disabling CHIPoBLE service due to error: %s", ErrorStr(err));
+        sInstance.mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled;
+    }
+
+    // Schedule DriveBLEState() to run.
+    PlatformMgr().ScheduleWork(DriveBLEState, 0);
+}
+
+static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t * param)
+{
+    /* If event is register event, store the  for each profile */
+    if (event == ESP_GATTC_REG_EVT)
+    {
+        if (param->reg.status == ESP_GATT_OK)
+        {
+            gl_profile_tab[param->reg.app_id].gattc_if = gattc_if;
+        }
+        else
+        {
+            ChipLogProgress(Ble, "reg app failed, app_id %04x, status %d", param->reg.app_id, param->reg.status);
+            return;
+        }
+    }
+
+    /* If the  equal to profile A, call profile A cb handler,
+     * so here call each profile's callback */
+    do
+    {
+        int idx;
+        for (idx = 0; idx < PROFILE_NUM; idx++)
+        {
+            if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb
+                                                   function */
+                gattc_if == gl_profile_tab[idx].gattc_if)
+            {
+                if (gl_profile_tab[idx].gattc_cb)
+                {
+                    gl_profile_tab[idx].gattc_cb(event, gattc_if, param);
+                }
+            }
+        }
+    } while (0);
+}
+
+void BLEManagerImpl::HandleConnectFailed(CHIP_ERROR error)
+{
+    if (sInstance.mIsCentral)
+    {
+        ChipDeviceEvent event;
+        event.Type                                    = DeviceEventType::kPlatformESP32BLECentralConnectFailed;
+        event.Platform.BLECentralConnectFailed.mError = error;
+        PlatformMgr().PostEventOrDie(&event);
+    }
+}
+
+void BLEManagerImpl::CancelConnect(void)
+{
+    int rc = esp_ble_gattc_close(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, connId);
+    VerifyOrReturn(rc == 0, ChipLogError(Ble, "Failed to cancel connection rc=%d", rc));
+}
+
+void BLEManagerImpl::ConnectDevice(esp_bd_addr_t & addr, esp_ble_addr_type_t addr_type, uint16_t timeout)
+{
+    int rc;
+    rc = esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, addr, addr_type, true);
+    if (rc != 0)
+    {
+        ChipLogError(Ble, "Failed to connect to rc=%d", rc);
+    }
+}
+
+void HandleIncomingBleConnection(BLEEndPoint * bleEP)
+{
+    ChipLogProgress(DeviceLayer, "CHIPoBLE connection received");
+}
+#endif
+
 bool BLEManagerImpl::SubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId)
 {
-    ChipLogProgress(DeviceLayer, "BLEManagerImpl::SubscribeCharacteristic() not supported");
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    uint8_t value[2];
+    int rc;
+
+    value[0] = 0x02;
+    value[1] = 0x00;
+
+    rc = esp_ble_gattc_write_char_descr(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id,
+                                        descr_elem_result[0].handle, sizeof(value), value, ESP_GATT_WRITE_TYPE_RSP,
+                                        ESP_GATT_AUTH_REQ_NONE);
+    if (rc != 0)
+    {
+        ChipLogError(Ble, "esp_ble_gattc_get_descr_by_char_handle failed: %d", rc);
+        esp_ble_gattc_close(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, conId);
+        return false;
+    }
+    ChipDeviceEvent event;
+    event.Type                                          = DeviceEventType::kPlatformESP32BLESubscribeOpComplete;
+    event.Platform.BLESubscribeOpComplete.mConnection   = conId;
+    event.Platform.BLESubscribeOpComplete.mIsSubscribed = true;
+    PlatformMgr().PostEventOrDie(&event);
+    return true;
+#else
     return false;
+#endif
 }
 
 bool BLEManagerImpl::UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId)
 {
-    ChipLogProgress(DeviceLayer, "BLEManagerImpl::UnsubscribeCharacteristic() not supported");
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    uint8_t value[2];
+    int rc;
+
+    value[0] = 0x00;
+    value[1] = 0x00;
+
+    rc = esp_ble_gattc_write_char_descr(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id,
+                                        descr_elem_result[0].handle, sizeof(value), value, ESP_GATT_WRITE_TYPE_RSP,
+                                        ESP_GATT_AUTH_REQ_NONE);
+    if (rc != 0)
+    {
+        ChipLogError(Ble, "ble_gattc_write_flat failed: %d", rc);
+        esp_ble_gattc_close(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, conId);
+        return false;
+    }
+    ChipDeviceEvent event;
+    event.Type                                          = DeviceEventType::kPlatformESP32BLESubscribeOpComplete;
+    event.Platform.BLESubscribeOpComplete.mConnection   = conId;
+    event.Platform.BLESubscribeOpComplete.mIsSubscribed = false;
+    PlatformMgr().PostEventOrDie(&event);
+    return true;
+#else
     return false;
+#endif
 }
 
 bool BLEManagerImpl::CloseConnection(BLE_CONNECTION_OBJECT conId)
@@ -311,10 +816,12 @@
     // Release the associated connection state record.
     ReleaseConnectionState(conId);
 
+#if !CONFIG_ENABLE_ESP32_BLE_CONTROLLER
     // Force a refresh of the advertising state.
     mFlags.Set(Flags::kAdvertisingRefreshNeeded);
     mFlags.Clear(Flags::kAdvertisingConfigured);
     PlatformMgr().ScheduleWork(DriveBLEState, 0);
+#endif
 
     return (err == CHIP_NO_ERROR);
 }
@@ -336,6 +843,9 @@
     VerifyOrExit(conState != NULL, err = CHIP_ERROR_INVALID_ARGUMENT);
 
     VerifyOrExit(conState->PendingIndBuf.IsNull(), err = CHIP_ERROR_INCORRECT_STATE);
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    ChipLogDetail(Ble, "Sending indication for CHIPoBLE TX characteristic (con %u, len %u)", conId, data->DataLength());
+#endif
 
     err = MapBLEError(esp_ble_gatts_send_indicate(mAppIf, conId, mTXCharAttrHandle, data->DataLength(), data->Start(), false));
     if (err != CHIP_NO_ERROR)
@@ -360,8 +870,26 @@
 bool BLEManagerImpl::SendWriteRequest(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId,
                                       PacketBufferHandle pBuf)
 {
-    ChipLogError(DeviceLayer, "BLEManagerImpl::SendWriteRequest() not supported");
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    ChipLogProgress(Ble, "In send write request\n");
+    int rc;
+
+    rc = esp_ble_gattc_write_char(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id,
+                                  gl_profile_tab[PROFILE_A_APP_ID].write_char_handle, pBuf->DataLength(), pBuf->Start(),
+                                  ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
+    if (rc != 0)
+    {
+        ChipLogError(Ble, "ble_gattc_write_flat failed: %d", rc);
+        return false;
+    }
+    ChipDeviceEvent event;
+    event.Type                                  = DeviceEventType::kPlatformESP32BLEWriteComplete;
+    event.Platform.BLEWriteComplete.mConnection = conId;
+    PlatformMgr().PostEventOrDie(&event);
+    return true;
+#else
     return false;
+#endif
 }
 
 bool BLEManagerImpl::SendReadRequest(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId,
@@ -378,7 +906,10 @@
     return false;
 }
 
-void BLEManagerImpl::NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT conId) {}
+void BLEManagerImpl::NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT conId)
+{
+    ChipLogProgress(Ble, "Got notification regarding chip connection closure");
+}
 
 CHIP_ERROR BLEManagerImpl::MapBLEError(int bleErr)
 {
@@ -610,7 +1141,7 @@
     }
 
     // Set the maximum supported MTU size.
-    err = MapBLEError(esp_ble_gatt_set_local_mtu(ESP_GATT_MAX_MTU_SIZE));
+    err = MapBLEError(esp_ble_gatt_set_local_mtu(CHIP_MAX_MTU_SIZE));
     if (err != CHIP_NO_ERROR)
     {
         ChipLogError(DeviceLayer, "esp_ble_gatt_set_local_mtu() failed: %s", ErrorStr(err));
@@ -636,7 +1167,7 @@
 
     if (!mFlags.Has(Flags::kUseCustomDeviceName))
     {
-        snprintf(mDeviceName, sizeof(mDeviceName), "%s%04u", CHIP_DEVICE_CONFIG_BLE_DEVICE_NAME_PREFIX, discriminator);
+        ChipLogProgress(Ble, mDeviceName, sizeof(mDeviceName), "%s%04u", CHIP_DEVICE_CONFIG_BLE_DEVICE_NAME_PREFIX, discriminator);
         mDeviceName[kMaxDeviceNameLength] = 0;
     }
 
@@ -816,7 +1347,7 @@
         break;
 
     case ESP_GATTS_RESPONSE_EVT:
-        ESP_LOGD(TAG, "ESP_GATTS_RESPONSE_EVT (handle %u, status %d)", param->rsp.handle, (int) param->rsp.status);
+        ChipLogDetail(Ble, "ESP_GATTS_RESPONSE_EVT (handle %u, status %d)", param->rsp.handle, (int) param->rsp.status);
         break;
 
     default:
@@ -898,7 +1429,7 @@
     break;
 
     case ESP_GATTS_MTU_EVT: {
-        ESP_LOGD(TAG, "MTU for con %u: %u", param->mtu.conn_id, param->mtu.mtu);
+        ChipLogDetail(Ble, "MTU for con %u: %u", param->mtu.conn_id, param->mtu.mtu);
         CHIPoBLEConState * conState = GetConnectionState(param->mtu.conn_id);
         if (conState != NULL)
         {
@@ -928,7 +1459,8 @@
     bool needResp  = param->write.need_rsp;
     PacketBufferHandle buf;
 
-    ESP_LOGD(TAG, "Write request received for CHIPoBLE RX characteristic (con %u, len %u)", param->write.conn_id, param->write.len);
+    ChipLogDetail(Ble, "Write request received for CHIPoBLE RX characteristic (con %u, len %u)", param->write.conn_id,
+                  param->write.len);
 
     // Disallow long writes.
     VerifyOrExit(param->write.is_prep == false, err = CHIP_ERROR_INVALID_ARGUMENT);
@@ -970,7 +1502,7 @@
     CHIP_ERROR err;
     esp_gatt_rsp_t rsp;
 
-    ESP_LOGD(TAG, "Read request received for CHIPoBLE TX characteristic (con %u)", param->read.conn_id);
+    ChipLogDetail(Ble, "Read request received for CHIPoBLE TX characteristic (con %u)", param->read.conn_id);
 
     // Send a zero-length response.
     memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
@@ -988,7 +1520,7 @@
     CHIPoBLEConState * conState;
     esp_gatt_rsp_t rsp;
 
-    ESP_LOGD(TAG, "Read request received for CHIPoBLE TX characteristic CCCD (con %u)", param->read.conn_id);
+    ChipLogDetail(Ble, "Read request received for CHIPoBLE TX characteristic CCCD (con %u)", param->read.conn_id);
 
     // Find the connection state record.
     conState = GetConnectionState(param->read.conn_id);
@@ -1016,8 +1548,8 @@
     bool needResp = param->write.need_rsp;
     bool indicationsEnabled;
 
-    ESP_LOGD(TAG, "Write request received for CHIPoBLE TX characteristic CCCD (con %u, len %u)", param->write.conn_id,
-             param->write.len);
+    ChipLogDetail(Ble, "Write request received for CHIPoBLE TX characteristic CCCD (con %u, len %u)", param->write.conn_id,
+                  param->write.len);
 
     // Find the connection state record.
     conState = GetConnectionState(param->read.conn_id);
@@ -1061,8 +1593,8 @@
 
 void BLEManagerImpl::HandleTXCharConfirm(CHIPoBLEConState * conState, esp_ble_gatts_cb_param_t * param)
 {
-    ESP_LOGD(TAG, "Confirm received for CHIPoBLE TX characteristic indication (con %u, status %u)", param->conf.conn_id,
-             param->conf.status);
+    ChipLogDetail(Ble, "Confirm received for CHIPoBLE TX characteristic indication (con %u, status %u)", param->conf.conn_id,
+                  param->conf.status);
 
     // If there is a pending indication buffer for the connection, release it now.
     conState->PendingIndBuf = nullptr;
@@ -1124,6 +1656,178 @@
     }
 }
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+CHIP_ERROR BLEManagerImpl::HandleRXNotify(esp_ble_gattc_cb_param_t param)
+{
+    System::PacketBufferHandle buf = System::PacketBufferHandle::NewWithData(param.notify.value, param.notify.value_len);
+    VerifyOrReturnError(!buf.IsNull(), CHIP_ERROR_NO_MEMORY);
+
+    ChipLogDetail(DeviceLayer, "Indication received, conn = %d", param.notify.conn_id);
+
+    ChipDeviceEvent event;
+    event.Type                                       = DeviceEventType::kPlatformESP32BLEIndicationReceived;
+    event.Platform.BLEIndicationReceived.mConnection = param.notify.conn_id;
+    event.Platform.BLEIndicationReceived.mData       = std::move(buf).UnsafeRelease();
+    PlatformMgr().PostEventOrDie(&event);
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR BLEManagerImpl::ConfigureBle(uint32_t aAdapterId, bool aIsCentral)
+{
+    CHIP_ERROR err                  = CHIP_NO_ERROR;
+    mBLEAdvConfig.mpBleName         = mDeviceName;
+    mBLEAdvConfig.mAdapterId        = aAdapterId;
+    mBLEAdvConfig.mMajor            = 1;
+    mBLEAdvConfig.mMinor            = 1;
+    mBLEAdvConfig.mVendorId         = 1;
+    mBLEAdvConfig.mProductId        = 1;
+    mBLEAdvConfig.mDeviceId         = 1;
+    mBLEAdvConfig.mDuration         = 2;
+    mBLEAdvConfig.mPairingStatus    = 0;
+    mBLEAdvConfig.mType             = 1;
+    mBLEAdvConfig.mpAdvertisingUUID = "0xFFF6";
+
+    mIsCentral = aIsCentral;
+    if (mIsCentral)
+    {
+        err = MapBLEError(esp_ble_gattc_register_callback(esp_gattc_cb));
+        if (err != CHIP_NO_ERROR)
+        {
+            ChipLogError(DeviceLayer, "esp_ble_gattc_register_callback() failed: %s", ErrorStr(err));
+            ExitNow();
+        }
+        ChipLogProgress(Ble, "Before initialising (PROFILE\n");
+
+        int rc = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
+        if (rc != 0)
+        {
+            ChipLogError(DeviceLayer, "esp_ble_gattc_app_register() failed %s", ErrorStr(err));
+            ExitNow();
+        }
+    }
+
+    mFlags.Set(Flags::kESPBLELayerInitialized);
+
+exit:
+    if (err != CHIP_NO_ERROR)
+        return err;
+
+    return CHIP_NO_ERROR;
+}
+
+void BLEManagerImpl::OnDeviceScanned(esp_ble_addr_type_t & addr_type, esp_bd_addr_t & addr,
+                                     const chip::Ble::ChipBLEDeviceIdentificationInfo & info)
+{
+    ChipLogProgress(Ble, "In OnDeviceScanned\n");
+    if (mBLEScanConfig.mBleScanState == BleScanState::kScanForDiscriminator)
+    {
+        if (!mBLEScanConfig.mDiscriminator.MatchesLongDiscriminator(info.GetDeviceDiscriminator()))
+        {
+            return;
+        }
+        ChipLogProgress(Ble, "Device Discriminator match. Attempting to connect");
+    }
+    else if (mBLEScanConfig.mBleScanState == BleScanState::kScanForAddress)
+    {
+        ChipLogProgress(Ble, "Device Address match. Attempting to connect");
+    }
+    else
+    {
+        ChipLogProgress(Ble, "Unknown discovery type. Ignoring");
+    }
+
+    connect                      = true;
+    mBLEScanConfig.mBleScanState = BleScanState::kConnecting;
+    DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(kConnectTimeout), HandleConnectTimeout, nullptr);
+    mDeviceScanner.StopScan();
+    ChipLogProgress(Ble, "Scanned all devices\n");
+
+    ConnectDevice(addr, addr_type, kConnectTimeout);
+}
+
+void BLEManagerImpl::OnScanComplete()
+{
+    ChipLogProgress(Ble, "Stop scan\n");
+    if (mBLEScanConfig.mBleScanState != BleScanState::kScanForDiscriminator &&
+        mBLEScanConfig.mBleScanState != BleScanState::kScanForAddress)
+    {
+        ChipLogProgress(Ble, "Scan complete notification without an active scan");
+        return;
+    }
+
+    BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_TIMEOUT);
+    mBLEScanConfig.mBleScanState = BleScanState::kNotScanning;
+}
+
+void BLEManagerImpl::InitiateScan(BleScanState scanType)
+{
+    DriveBLEState();
+
+    // Check for a valid scan type
+    if (scanType == BleScanState::kNotScanning)
+    {
+        BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INCORRECT_STATE);
+        ChipLogError(Ble, "Invalid scan type requested");
+        return;
+    }
+
+    // Initialize the device scanner
+    CHIP_ERROR err = mDeviceScanner.Init(this);
+    if (err != CHIP_NO_ERROR)
+    {
+        BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, err);
+        ChipLogError(Ble, "Failed to initialize device scanner: %s", ErrorStr(err));
+        return;
+    }
+
+    // Start scanning
+    mBLEScanConfig.mBleScanState = scanType;
+    err                          = mDeviceScanner.StartScan(kNewConnectionScanTimeout);
+    if (err != CHIP_NO_ERROR)
+    {
+        mBLEScanConfig.mBleScanState = BleScanState::kNotScanning;
+        ChipLogError(Ble, "Failed to start a BLE scan: %s", chip::ErrorStr(err));
+        BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, err);
+        return;
+    }
+}
+
+void BLEManagerImpl::HandleConnectTimeout(chip::System::Layer *, void * context)
+{
+    CancelConnect();
+    BLEManagerImpl::HandleConnectFailed(CHIP_ERROR_TIMEOUT);
+}
+
+void BLEManagerImpl::CleanScanConfig()
+{
+    if (BLEManagerImpl::mBLEScanConfig.mBleScanState == BleScanState::kConnecting)
+    {
+        DeviceLayer::SystemLayer().CancelTimer(HandleConnectTimeout, nullptr);
+    }
+    mBLEScanConfig.mBleScanState = BleScanState::kNotScanning;
+}
+
+void BLEManagerImpl::InitiateScan(intptr_t arg)
+{
+    sInstance.InitiateScan(static_cast<BleScanState>(arg));
+}
+
+void BLEManagerImpl::NewConnection(BleLayer * bleLayer, void * appState, const SetupDiscriminator & connDiscriminator)
+{
+    mBLEScanConfig.mDiscriminator = connDiscriminator;
+    mBLEScanConfig.mAppState      = appState;
+
+    // Initiate async scan
+    PlatformMgr().ScheduleWork(InitiateScan, static_cast<intptr_t>(BleScanState::kScanForDiscriminator));
+}
+
+CHIP_ERROR BLEManagerImpl::CancelConnection()
+{
+    return CHIP_ERROR_NOT_IMPLEMENTED;
+}
+#endif
+
 BLEManagerImpl::CHIPoBLEConState * BLEManagerImpl::GetConnectionState(uint16_t conId, bool allocate)
 {
     uint16_t freeIndex = kMaxConnections;
@@ -1186,9 +1890,68 @@
     return numCons;
 }
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+void BLEManagerImpl::HandleGAPConnectionFailed()
+{
+    if (sInstance.mIsCentral)
+    {
+        ChipDeviceEvent event;
+        event.Type                                    = DeviceEventType::kPlatformESP32BLECentralConnectFailed;
+        event.Platform.BLECentralConnectFailed.mError = CHIP_ERROR_INTERNAL;
+        PlatformMgr().PostEventOrDie(&event);
+    }
+}
+
+CHIP_ERROR BLEManagerImpl::HandleGAPCentralConnect(esp_ble_gattc_cb_param_t p_data)
+{
+    if (BLEManagerImpl::mBLEScanConfig.mBleScanState == BleScanState::kConnecting)
+    {
+        {
+            ChipLogProgress(DeviceLayer, "BLE GAP connection established (con %u)", p_data.connect.conn_id);
+
+            // remember the peer
+            connId                                   = p_data.connect.conn_id;
+            gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data.connect.conn_id;
+            memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data.connect.remote_bda, sizeof(esp_bd_addr_t));
+
+            // Start the GATT discovery process
+            int rc = esp_ble_gattc_search_service(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, connId, &remote_filter_service_uuid);
+            if (rc != 0)
+            {
+                HandleGAPConnectionFailed();
+                ChipLogError(DeviceLayer, "peer_disc_al failed: %d", rc);
+                return CHIP_ERROR_INTERNAL;
+            }
+        }
+    }
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR BLEManagerImpl::HandleGAPConnect(esp_ble_gattc_cb_param_t p_data)
+{
+    if (mIsCentral)
+    {
+        int rc;
+        gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data.connect.conn_id;
+        connId                                   = p_data.connect.conn_id;
+        memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data.connect.remote_bda, sizeof(esp_bd_addr_t));
+        rc = esp_ble_gattc_send_mtu_req(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, p_data.connect.conn_id);
+
+        if (rc != 0)
+        {
+            ChipLogProgress(Ble, "MTU error\n");
+            return CHIP_ERROR_INTERNAL;
+        }
+
+        return HandleGAPCentralConnect(p_data);
+    }
+    return CHIP_NO_ERROR;
+}
+#endif
+
 void BLEManagerImpl::HandleGATTEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t * param)
 {
-    ESP_LOGV(TAG, "GATT Event: %d (if %d)", (int) event, (int) gatts_if);
+    ChipLogProgress(Ble, "GATT Event: %d (if %d)", (int) event, (int) gatts_if);
 
     // This method is invoked on the ESP BLE thread.  Therefore we must hold a lock
     // on the Chip stack while processing the event.
@@ -1203,8 +1966,11 @@
 void BLEManagerImpl::HandleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t * param)
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    esp_ble_gap_cb_param_t * scan_result = (esp_ble_gap_cb_param_t *) param;
+#endif
 
-    ESP_LOGV(TAG, "GAP Event: %d", (int) event);
+    ChipLogProgress(Ble, "GAP Event: %d", (int) event);
 
     // This method is invoked on the ESP BLE thread.  Therefore we must hold a lock
     // on the Chip stack while processing the event.
@@ -1212,8 +1978,7 @@
 
     switch (event)
     {
-    case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
-
+    case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: {
         if (param->adv_data_cmpl.status != ESP_BT_STATUS_SUCCESS)
         {
             ChipLogError(DeviceLayer, "ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT error: %d", (int) param->adv_data_cmpl.status);
@@ -1222,11 +1987,10 @@
 
         sInstance.mFlags.Set(Flags::kAdvertisingConfigured);
         sInstance.mFlags.Clear(Flags::kControlOpInProgress);
+    }
+    break;
 
-        break;
-
-    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
-
+    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: {
         if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS)
         {
             ChipLogError(DeviceLayer, "ESP_GAP_BLE_ADV_START_COMPLETE_EVT error: %d", (int) param->adv_start_cmpl.status);
@@ -1251,11 +2015,10 @@
                 err                                        = PlatformMgr().PostEvent(&advChange);
             }
         }
+    }
+    break;
 
-        break;
-
-    case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
-
+    case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: {
         if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS)
         {
             ChipLogError(DeviceLayer, "ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT error: %d", (int) param->adv_stop_cmpl.status);
@@ -1286,9 +2049,19 @@
                 err                                        = PlatformMgr().PostEvent(&advChange);
             }
         }
+    }
+    break;
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    case ESP_GAP_BLE_SCAN_RESULT_EVT: {
+        mDeviceScanner.ReportDevice(*scan_result, scan_result->scan_rst.bda);
+    }
+    break;
+
+    case ESP_GAP_SEARCH_INQ_CMPL_EVT:
+        mDeviceScanner.mIsScanning = false;
         break;
-
+#endif
     default:
         break;
     }
diff --git a/src/platform/ESP32/bluedroid/ChipDeviceScanner.cpp b/src/platform/ESP32/bluedroid/ChipDeviceScanner.cpp
new file mode 100644
index 0000000..cea4b9f
--- /dev/null
+++ b/src/platform/ESP32/bluedroid/ChipDeviceScanner.cpp
@@ -0,0 +1,127 @@
+/*
+ *
+ *    Copyright (c) 2022 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.
+ */
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+
+#include <platform/ESP32/ChipDeviceScanner.h>
+
+#include "esp_bt.h"
+#include "esp_bt_main.h"
+#include "esp_gap_ble_api.h"
+#include "esp_gatt_common_api.h"
+#include "esp_gatt_defs.h"
+#include "esp_gattc_api.h"
+#include "esp_gatts_api.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include <lib/support/CodeUtils.h>
+#include <lib/support/logging/CHIPLogging.h>
+
+#define CHIPoBLE_SERVICE_UUID 0xFFF6
+
+namespace chip {
+namespace DeviceLayer {
+namespace Internal {
+namespace {
+
+/// Retrieve CHIP device identification info from the device advertising data
+bool BluedroidGetChipDeviceInfo(esp_ble_gap_cb_param_t & scan_result, chip::Ble::ChipBLEDeviceIdentificationInfo & deviceInfo)
+{
+    // Check for CHIP Service UUID
+    if (scan_result.scan_rst.ble_adv != NULL)
+    {
+        if (scan_result.scan_rst.adv_data_len > 13 && scan_result.scan_rst.ble_adv[5] == 0xf6 &&
+            scan_result.scan_rst.ble_adv[6] == 0xff)
+        {
+            deviceInfo.OpCode                              = scan_result.scan_rst.ble_adv[7];
+            deviceInfo.DeviceDiscriminatorAndAdvVersion[0] = scan_result.scan_rst.ble_adv[8];
+            deviceInfo.DeviceDiscriminatorAndAdvVersion[1] = scan_result.scan_rst.ble_adv[9];
+            // vendor and product Id from adv
+            deviceInfo.DeviceVendorId[0]  = scan_result.scan_rst.ble_adv[10];
+            deviceInfo.DeviceVendorId[1]  = scan_result.scan_rst.ble_adv[11];
+            deviceInfo.DeviceProductId[0] = scan_result.scan_rst.ble_adv[12];
+            deviceInfo.DeviceProductId[1] = scan_result.scan_rst.ble_adv[13];
+            deviceInfo.AdditionalDataFlag = scan_result.scan_rst.ble_adv[14];
+            return true;
+        }
+    }
+    return false;
+}
+
+} // namespace
+
+void ChipDeviceScanner::ReportDevice(esp_ble_gap_cb_param_t & scan_result, esp_bd_addr_t & addr)
+{
+    chip::Ble::ChipBLEDeviceIdentificationInfo deviceInfo;
+    if (BluedroidGetChipDeviceInfo(scan_result, deviceInfo) == false)
+    {
+        return;
+    }
+    mDelegate->OnDeviceScanned(scan_result.scan_rst.ble_addr_type, addr, deviceInfo);
+}
+
+void ChipDeviceScanner::RemoveDevice()
+{
+    // TODO
+}
+
+CHIP_ERROR ChipDeviceScanner::StartScan(uint16_t timeout)
+{
+    ReturnErrorCodeIf(mIsScanning, CHIP_ERROR_INCORRECT_STATE);
+
+    static esp_ble_scan_params_t ble_scan_params = { .scan_type          = BLE_SCAN_TYPE_PASSIVE,
+                                                     .own_addr_type      = BLE_ADDR_TYPE_RANDOM,
+                                                     .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
+                                                     .scan_interval      = 0x00,
+                                                     .scan_window        = 0x00,
+                                                     .scan_duplicate     = BLE_SCAN_DUPLICATE_DISABLE };
+
+    int rc = esp_ble_gap_set_scan_params(&ble_scan_params);
+    if (rc != 0)
+    {
+        ChipLogError(DeviceLayer, "esp_ble_gap_set_scan_params failed: %d", rc);
+        return CHIP_ERROR_INTERNAL;
+    }
+    /* Start the discovery process. */
+    rc = esp_ble_gap_start_scanning(timeout);
+    if (rc != 0)
+    {
+        ChipLogError(DeviceLayer, "esp_ble_gap_start_scanning failed: %d", rc);
+        return CHIP_ERROR_INTERNAL;
+    }
+    mIsScanning = true;
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ChipDeviceScanner::StopScan()
+{
+    ReturnErrorCodeIf(!mIsScanning, CHIP_NO_ERROR);
+
+    int rc = esp_ble_gap_stop_scanning();
+    if (rc != 0)
+    {
+        ChipLogError(DeviceLayer, "ble_gap_disc_cancel failed: %d", rc);
+        return CHIP_ERROR_INTERNAL;
+    }
+    mIsScanning = false;
+    mDelegate->OnScanComplete();
+    return CHIP_NO_ERROR;
+}
+
+} // namespace Internal
+} // namespace DeviceLayer
+} // namespace chip
+#endif // CONFIG_ENABLE_ESP32_BLE_CONTROLLER
diff --git a/src/platform/ESP32/nimble/BLEManagerImpl.cpp b/src/platform/ESP32/nimble/BLEManagerImpl.cpp
index 3f5318a..048509d 100644
--- a/src/platform/ESP32/nimble/BLEManagerImpl.cpp
+++ b/src/platform/ESP32/nimble/BLEManagerImpl.cpp
@@ -30,11 +30,17 @@
 
 #if CONFIG_BT_NIMBLE_ENABLED
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include <ble/BleLayer.h>
+#endif
 #include <ble/CHIPBleServiceData.h>
 #include <lib/support/CodeUtils.h>
 #include <lib/support/logging/CHIPLogging.h>
 #include <platform/CommissionableDataProvider.h>
 #include <platform/DeviceInstanceInfoProvider.h>
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include <platform/ESP32/BLEManagerImpl.h>
+#endif
 #include <platform/internal/BLEManager.h>
 #include <setup_payload/AdditionalDataPayloadGenerator.h>
 #include <system/SystemTimer.h>
@@ -44,6 +50,9 @@
 #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
 #include "esp_nimble_hci.h"
 #endif
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include "blecent.h"
+#endif
 #include "host/ble_hs.h"
 #include "host/ble_hs_pvcy.h"
 #include "host/ble_uuid.h"
@@ -67,6 +76,11 @@
 
 namespace {
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+static constexpr uint16_t kNewConnectionScanTimeout = 60;
+static constexpr uint16_t kConnectTimeout           = 20;
+#endif
+
 struct ESP32ChipServiceData
 {
     uint8_t ServiceUUID[2];
@@ -75,6 +89,10 @@
 
 const ble_uuid16_t ShortUUID_CHIPoBLEService = { BLE_UUID_TYPE_16, 0xFFF6 };
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+const ble_uuid16_t ShortUUID_CHIPoBLE_CharTx_Desc = { BLE_UUID_TYPE_16, 0x2902 };
+#endif
+
 const ble_uuid128_t UUID128_CHIPoBLEChar_RX = {
     BLE_UUID_TYPE_128, { 0x11, 0x9D, 0x9F, 0x42, 0x9C, 0x4F, 0x9F, 0x95, 0x59, 0x45, 0x3D, 0x26, 0xF5, 0x2E, 0xEE, 0x18 }
 };
@@ -101,6 +119,9 @@
 
 } // unnamed namespace
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+ChipDeviceScanner & mDeviceScanner = Internal::ChipDeviceScanner::GetInstance();
+#endif
 BLEManagerImpl BLEManagerImpl::sInstance;
 constexpr System::Clock::Timeout BLEManagerImpl::kFastAdvertiseTimeout;
 
@@ -139,6 +160,55 @@
     },
 };
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+void BLEManagerImpl::HandleConnectFailed(CHIP_ERROR error)
+{
+    if (sInstance.mIsCentral)
+    {
+        ChipDeviceEvent event;
+        event.Type                                    = DeviceEventType::kPlatformESP32BLECentralConnectFailed;
+        event.Platform.BLECentralConnectFailed.mError = error;
+        PlatformMgr().PostEventOrDie(&event);
+    }
+}
+
+void BLEManagerImpl::CancelConnect(void)
+{
+    int rc = ble_gap_conn_cancel();
+    VerifyOrReturn(rc == 0, ChipLogError(Ble, "Failed to cancel connection rc=%d", rc));
+}
+
+void BLEManagerImpl::HandleConnectTimeout(chip::System::Layer *, void * context)
+{
+    CancelConnect();
+    BLEManagerImpl::HandleConnectFailed(CHIP_ERROR_TIMEOUT);
+}
+
+void BLEManagerImpl::ConnectDevice(const ble_addr_t & addr, uint16_t timeout)
+{
+    int rc;
+    uint8_t ownAddrType;
+
+    rc = ble_hs_id_infer_auto(0, &ownAddrType);
+    if (rc != 0)
+    {
+        ChipLogError(Ble, "Failed to infer own address type rc=%d", rc);
+        return;
+    }
+
+    rc = ble_gap_connect(ownAddrType, &addr, (timeout * 1000), NULL, ble_svr_gap_event, NULL);
+    if (rc != 0)
+    {
+        ChipLogError(Ble, "Failed to connect to rc=%d", rc);
+    }
+}
+
+void HandleIncomingBleConnection(BLEEndPoint * bleEP)
+{
+    ChipLogProgress(DeviceLayer, "CHIPoBLE connection received");
+}
+#endif
+
 CHIP_ERROR BLEManagerImpl::_Init()
 {
 #if CONFIG_USE_BLE_ONLY_FOR_COMMISSIONING
@@ -159,7 +229,11 @@
     CHIP_ERROR err;
 
     // Initialize the Chip BleLayer.
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    err = BleLayer::Init(this, this, this, &DeviceLayer::SystemLayer());
+#else
     err = BleLayer::Init(this, this, &DeviceLayer::SystemLayer());
+#endif
     SuccessOrExit(err);
 
     mRXCharAttrHandle = 0;
@@ -167,8 +241,15 @@
     mC3CharAttrHandle = 0;
 #endif
     mTXCharCCCDAttrHandle = 0;
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    mFlags.ClearAll().Set(Flags::kAdvertisingEnabled, CHIP_DEVICE_CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART && !mIsCentral);
+    mFlags.Set(Flags::kFastAdvertisingEnabled, !mIsCentral);
+    OnChipBleConnectReceived = HandleIncomingBleConnection;
+#else
     mFlags.ClearAll().Set(Flags::kAdvertisingEnabled, CHIP_DEVICE_CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART);
     mFlags.Set(Flags::kFastAdvertisingEnabled, true);
+
+#endif
     mNumGAPCons = 0;
     memset(reinterpret_cast<void *>(mCons), 0, sizeof(mCons));
     mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Enabled;
@@ -311,20 +392,164 @@
         break;
 
     default:
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+        HandlePlatformSpecificBLEEvent(event);
+#endif
         break;
     }
 }
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+void BLEManagerImpl::HandlePlatformSpecificBLEEvent(const ChipDeviceEvent * apEvent)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    ChipLogProgress(DeviceLayer, "HandlePlatformSpecificBLEEvent %d", apEvent->Type);
+
+    switch (apEvent->Type)
+    {
+    case DeviceEventType::kPlatformESP32BLECentralConnected:
+        if (BLEManagerImpl::mBLEScanConfig.mBleScanState == BleScanState::kConnecting)
+        {
+            BleConnectionDelegate::OnConnectionComplete(mBLEScanConfig.mAppState,
+                                                        apEvent->Platform.BLECentralConnected.mConnection);
+            CleanScanConfig();
+        }
+        break;
+
+    case DeviceEventType::kPlatformESP32BLECentralConnectFailed:
+        if (BLEManagerImpl::mBLEScanConfig.mBleScanState == BleScanState::kConnecting)
+        {
+            BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, apEvent->Platform.BLECentralConnectFailed.mError);
+            CleanScanConfig();
+        }
+        break;
+
+    case DeviceEventType::kPlatformESP32BLEWriteComplete:
+        HandleWriteConfirmation(apEvent->Platform.BLEWriteComplete.mConnection, &CHIP_BLE_SVC_ID, &chipUUID_CHIPoBLEChar_RX);
+        break;
+
+    case DeviceEventType::kPlatformESP32BLESubscribeOpComplete:
+        if (apEvent->Platform.BLESubscribeOpComplete.mIsSubscribed)
+            HandleSubscribeComplete(apEvent->Platform.BLESubscribeOpComplete.mConnection, &CHIP_BLE_SVC_ID,
+                                    &chipUUID_CHIPoBLEChar_TX);
+        else
+            HandleUnsubscribeComplete(apEvent->Platform.BLESubscribeOpComplete.mConnection, &CHIP_BLE_SVC_ID,
+                                      &chipUUID_CHIPoBLEChar_TX);
+        break;
+
+    case DeviceEventType::kPlatformESP32BLEIndicationReceived:
+        HandleIndicationReceived(apEvent->Platform.BLEIndicationReceived.mConnection, &CHIP_BLE_SVC_ID, &chipUUID_CHIPoBLEChar_TX,
+                                 PacketBufferHandle::Adopt(apEvent->Platform.BLEIndicationReceived.mData));
+        break;
+
+    default:
+        break;
+    }
+
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(DeviceLayer, "Disabling CHIPoBLE service due to error: %s", ErrorStr(err));
+        mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled;
+    }
+}
+
+static int OnUnsubscribeCharComplete(uint16_t conn_handle, const struct ble_gatt_error * error, struct ble_gatt_attr * attr,
+                                     void * arg)
+{
+    ChipLogProgress(DeviceLayer, "Subscribe complete: conn_handle=%d, error=%d, attr_handle=%d", conn_handle, error->status,
+                    attr->handle);
+
+    ChipDeviceEvent event;
+    event.Type                                          = DeviceEventType::kPlatformESP32BLESubscribeOpComplete;
+    event.Platform.BLESubscribeOpComplete.mConnection   = conn_handle;
+    event.Platform.BLESubscribeOpComplete.mIsSubscribed = false;
+    PlatformMgr().PostEventOrDie(&event);
+
+    return 0;
+}
+
+static int OnSubscribeCharComplete(uint16_t conn_handle, const struct ble_gatt_error * error, struct ble_gatt_attr * attr,
+                                   void * arg)
+{
+    ChipLogProgress(DeviceLayer, "Subscribe complete: conn_handle=%d, error=%d, attr_handle=%d", conn_handle, error->status,
+                    attr->handle);
+
+    ChipDeviceEvent event;
+    event.Type                                          = DeviceEventType::kPlatformESP32BLESubscribeOpComplete;
+    event.Platform.BLESubscribeOpComplete.mConnection   = conn_handle;
+    event.Platform.BLESubscribeOpComplete.mIsSubscribed = true;
+    PlatformMgr().PostEventOrDie(&event);
+
+    return 0;
+}
+#endif
+
 bool BLEManagerImpl::SubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId)
 {
-    ChipLogProgress(DeviceLayer, "BLEManagerImpl::SubscribeCharacteristic() not supported");
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    const struct peer_dsc * dsc;
+    uint8_t value[2];
+    int rc;
+    struct peer * peer = peer_find(conId);
+
+    dsc = peer_dsc_find_uuid(peer, (ble_uuid_t *) (&ShortUUID_CHIPoBLEService), (ble_uuid_t *) (&UUID_CHIPoBLEChar_TX),
+                             (ble_uuid_t *) (&ShortUUID_CHIPoBLE_CharTx_Desc));
+
+    if (dsc == nullptr)
+    {
+        ChipLogError(Ble, "Peer does not have CCCD");
+        ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+        return false;
+    }
+
+    value[0] = 0x02;
+    value[1] = 0x00;
+
+    rc = ble_gattc_write_flat(peer->conn_handle, dsc->dsc.handle, value, sizeof(value), OnSubscribeCharComplete, NULL);
+    if (rc != 0)
+    {
+        ChipLogError(Ble, "ble_gattc_write_flat failed: %d", rc);
+        ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+        return false;
+    }
+    return true;
+#else
     return false;
+#endif
 }
 
 bool BLEManagerImpl::UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId)
 {
-    ChipLogProgress(DeviceLayer, "BLEManagerImpl::UnsubscribeCharacteristic() not supported");
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    const struct peer_dsc * dsc;
+    uint8_t value[2];
+    int rc;
+    struct peer * peer = peer_find(conId);
+
+    dsc = peer_dsc_find_uuid(peer, (ble_uuid_t *) (&ShortUUID_CHIPoBLEService), (ble_uuid_t *) (&UUID_CHIPoBLEChar_TX),
+                             (ble_uuid_t *) (&ShortUUID_CHIPoBLE_CharTx_Desc));
+
+    if (dsc == nullptr)
+    {
+        ChipLogError(Ble, "Peer does not have CCCD");
+        ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+        return false;
+    }
+
+    value[0] = 0x00;
+    value[1] = 0x00;
+
+    rc = ble_gattc_write_flat(peer->conn_handle, dsc->dsc.handle, value, sizeof(value), OnUnsubscribeCharComplete, NULL);
+    if (rc != 0)
+    {
+        ChipLogError(Ble, "ble_gattc_write_flat failed: %d", rc);
+        ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+        return false;
+    }
+    return true;
+#else
     return false;
+#endif
 }
 
 bool BLEManagerImpl::CloseConnection(BLE_CONNECTION_OBJECT conId)
@@ -340,10 +565,12 @@
         ChipLogError(DeviceLayer, "ble_gap_terminate() failed: %s", ErrorStr(err));
     }
 
+#if !CONFIG_ENABLE_ESP32_BLE_CONTROLLER
     // Force a refresh of the advertising state.
     mFlags.Set(Flags::kAdvertisingRefreshNeeded);
     mFlags.Clear(Flags::kAdvertisingConfigured);
     PlatformMgr().ScheduleWork(DriveBLEState, 0);
+#endif
 
     return (err == CHIP_NO_ERROR);
 }
@@ -354,7 +581,7 @@
 }
 
 bool BLEManagerImpl::SendIndication(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId,
-                                    PacketBufferHandle data)
+                                    chip::System::PacketBufferHandle data)
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
     struct os_mbuf * om;
@@ -387,15 +614,54 @@
     return true;
 }
 
-bool BLEManagerImpl::SendWriteRequest(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId,
-                                      PacketBufferHandle pBuf)
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+static int OnWriteComplete(uint16_t conn_handle, const struct ble_gatt_error * error, struct ble_gatt_attr * attr, void * arg)
 {
-    ChipLogError(DeviceLayer, "BLEManagerImpl::SendWriteRequest() not supported");
+    ChipLogDetail(Ble, "Write complete; status:%d conn_handle:%d attr_handle:%d", error->status, conn_handle, attr->handle);
+    ChipDeviceEvent event;
+    event.Type                                  = DeviceEventType::kPlatformESP32BLEWriteComplete;
+    event.Platform.BLEWriteComplete.mConnection = conn_handle;
+    CHIP_ERROR err                              = PlatformMgr().PostEvent(&event);
+    if (err != CHIP_NO_ERROR)
+    {
+        return 1;
+    }
+
+    return 0;
+}
+#endif
+
+bool BLEManagerImpl::SendWriteRequest(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId,
+                                      chip::System::PacketBufferHandle pBuf)
+{
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    const struct peer_chr * chr;
+    int rc;
+    const struct peer * peer = peer_find(conId);
+
+    chr = peer_chr_find_uuid(peer, (ble_uuid_t *) (&ShortUUID_CHIPoBLEService), (ble_uuid_t *) (&UUID128_CHIPoBLEChar_RX));
+    if (chr == nullptr)
+    {
+        ChipLogError(Ble, "Peer does not have RX characteristic");
+        ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+        return false;
+    }
+
+    rc = ble_gattc_write_flat(conId, chr->chr.val_handle, pBuf->Start(), pBuf->DataLength(), OnWriteComplete, this);
+    if (rc != 0)
+    {
+        ChipLogError(Ble, "ble_gattc_write_flat failed: %d", rc);
+        ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+        return false;
+    }
+    return true;
+#else
     return false;
+#endif
 }
 
 bool BLEManagerImpl::SendReadRequest(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId,
-                                     PacketBufferHandle pBuf)
+                                     chip::System::PacketBufferHandle pBuf)
 {
     ChipLogError(DeviceLayer, "BLEManagerImpl::SendReadRequest() not supported");
     return false;
@@ -884,7 +1150,76 @@
     return numCons;
 }
 
-CHIP_ERROR BLEManagerImpl::HandleGAPConnect(struct ble_gap_event * gapEvent)
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+void BLEManagerImpl::HandleGAPConnectionFailed(struct ble_gap_event * gapEvent, CHIP_ERROR error)
+{
+    ChipLogError(Ble, "BLE GAP connection failed; status:%d", gapEvent->connect.status);
+    if (sInstance.mIsCentral)
+    {
+        ChipDeviceEvent event;
+        event.Type                                    = DeviceEventType::kPlatformESP32BLECentralConnectFailed;
+        event.Platform.BLECentralConnectFailed.mError = error;
+        PlatformMgr().PostEventOrDie(&event);
+    }
+}
+
+void BLEManagerImpl::OnGattDiscComplete(const struct peer * peer, int status, void * arg)
+{
+    if (status != 0)
+    {
+        ChipLogError(Ble, "GATT discovery failed; status:%d", status);
+        ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+        return;
+    }
+
+    ChipLogProgress(Ble, "GATT discovery complete status:%d conn_handle:%d", status, peer->conn_handle);
+
+    ChipDeviceEvent event;
+    event.Type                                     = DeviceEventType::kPlatformESP32BLECentralConnected;
+    event.Platform.BLECentralConnected.mConnection = peer->conn_handle;
+    PlatformMgr().PostEventOrDie(&event);
+}
+
+CHIP_ERROR BLEManagerImpl::HandleGAPCentralConnect(struct ble_gap_event * gapEvent)
+{
+    if (BLEManagerImpl::mBLEScanConfig.mBleScanState == BleScanState::kConnecting)
+    {
+        if (gapEvent->connect.status == 0)
+        {
+            // Track the number of active GAP connections.
+            mNumGAPCons++;
+
+            ChipLogProgress(DeviceLayer, "BLE GAP connection established (con %u)", gapEvent->connect.conn_handle);
+
+            // remember the peer
+            int rc = peer_add(gapEvent->connect.conn_handle);
+            if (rc != 0)
+            {
+                HandleGAPConnectionFailed(gapEvent, CHIP_ERROR_INTERNAL);
+                ChipLogError(DeviceLayer, "peer_add failed: %d", rc);
+                return CHIP_ERROR_INTERNAL;
+            }
+
+            // Start the GATT discovery process
+            rc = peer_disc_all(gapEvent->connect.conn_handle, OnGattDiscComplete, NULL);
+            if (rc != 0)
+            {
+                HandleGAPConnectionFailed(gapEvent, CHIP_ERROR_INTERNAL);
+                ChipLogError(DeviceLayer, "peer_disc_al failed: %d", rc);
+                return CHIP_ERROR_INTERNAL;
+            }
+        }
+        else
+        {
+            HandleGAPConnectionFailed(gapEvent, CHIP_ERROR_INTERNAL);
+            return CHIP_ERROR_INTERNAL;
+        }
+    }
+    return CHIP_NO_ERROR;
+}
+#endif
+
+CHIP_ERROR BLEManagerImpl::HandleGAPPeripheralConnect(struct ble_gap_event * gapEvent)
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
     ChipLogProgress(DeviceLayer, "BLE GAP connection established (con %u)", gapEvent->connect.conn_handle);
@@ -902,6 +1237,23 @@
     return err;
 }
 
+CHIP_ERROR BLEManagerImpl::HandleGAPConnect(struct ble_gap_event * gapEvent)
+{
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    int rc;
+
+    rc = ble_gattc_exchange_mtu(gapEvent->connect.conn_handle, NULL, NULL);
+    if (rc != 0)
+    {
+        return CHIP_ERROR_INTERNAL;
+    }
+
+    return HandleGAPCentralConnect(gapEvent);
+#else
+    return HandleGAPPeripheralConnect(gapEvent);
+#endif
+}
+
 CHIP_ERROR BLEManagerImpl::HandleGAPDisconnect(struct ble_gap_event * gapEvent)
 {
     ChipLogProgress(DeviceLayer, "BLE GAP connection terminated (con %u reason 0x%02x)", gapEvent->disconnect.conn.conn_handle,
@@ -913,6 +1265,10 @@
         mNumGAPCons--;
     }
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    peer_delete(gapEvent->disconnect.conn.conn_handle);
+#endif
+
     if (UnsetSubscribed(gapEvent->disconnect.conn.conn_handle))
     {
         CHIP_ERROR disconReason;
@@ -1039,6 +1395,15 @@
         ESP_LOGD(TAG, "BLE_GAP_EVENT_MTU = %d channel id = %d", event->mtu.value, event->mtu.channel_id);
         break;
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+    case BLE_GAP_EVENT_NOTIFY_RX:
+        ESP_LOGD(TAG, "BLE_GAP_EVENT_NOTIFY_RX received %s conn_handle:%d attr_handle:%d attr_len:%d",
+                 event->notify_rx.indication ? "indication" : "notification", event->notify_rx.conn_handle,
+                 event->notify_rx.attr_handle, OS_MBUF_PKTLEN(event->notify_rx.om));
+        err = sInstance.HandleRXNotify(event);
+        SuccessOrExit(err);
+        break;
+#endif
     default:
         break;
     }
@@ -1237,6 +1602,152 @@
     sInstance.DriveBLEState();
 }
 
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+CHIP_ERROR BLEManagerImpl::HandleRXNotify(struct ble_gap_event * ble_event)
+{
+    uint8_t * data                 = OS_MBUF_DATA(ble_event->notify_rx.om, uint8_t *);
+    size_t dataLen                 = OS_MBUF_PKTLEN(ble_event->notify_rx.om);
+    System::PacketBufferHandle buf = System::PacketBufferHandle::NewWithData(data, dataLen);
+    VerifyOrReturnError(!buf.IsNull(), CHIP_ERROR_NO_MEMORY);
+
+    ChipLogDetail(DeviceLayer, "Indication received, conn = %d", ble_event->notify_rx.conn_handle);
+
+    ChipDeviceEvent event;
+    event.Type                                       = DeviceEventType::kPlatformESP32BLEIndicationReceived;
+    event.Platform.BLEIndicationReceived.mConnection = ble_event->notify_rx.conn_handle;
+    event.Platform.BLEIndicationReceived.mData       = std::move(buf).UnsafeRelease();
+    PlatformMgr().PostEventOrDie(&event);
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR BLEManagerImpl::ConfigureBle(uint32_t aAdapterId, bool aIsCentral)
+{
+    mBLEAdvConfig.mpBleName         = mDeviceName;
+    mBLEAdvConfig.mAdapterId        = aAdapterId;
+    mBLEAdvConfig.mMajor            = 1;
+    mBLEAdvConfig.mMinor            = 1;
+    mBLEAdvConfig.mVendorId         = 1;
+    mBLEAdvConfig.mProductId        = 1;
+    mBLEAdvConfig.mDeviceId         = 1;
+    mBLEAdvConfig.mDuration         = 2;
+    mBLEAdvConfig.mPairingStatus    = 0;
+    mBLEAdvConfig.mType             = 1;
+    mBLEAdvConfig.mpAdvertisingUUID = "0xFFF6";
+
+    mIsCentral = aIsCentral;
+    if (mIsCentral)
+    {
+        int rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
+        assert(rc == 0);
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+void BLEManagerImpl::OnDeviceScanned(const struct ble_hs_adv_fields & fields, const ble_addr_t & addr,
+                                     const chip::Ble::ChipBLEDeviceIdentificationInfo & info)
+{
+
+    if (mBLEScanConfig.mBleScanState == BleScanState::kScanForDiscriminator)
+    {
+        if (!mBLEScanConfig.mDiscriminator.MatchesLongDiscriminator(info.GetDeviceDiscriminator()))
+        {
+            return;
+        }
+        ChipLogProgress(Ble, "Device Discriminator match. Attempting to connect");
+    }
+    else if (mBLEScanConfig.mBleScanState == BleScanState::kScanForAddress)
+    {
+        ChipLogProgress(Ble, "Device Address match. Attempting to connect");
+    }
+    else
+    {
+        ChipLogProgress(Ble, "Unknown discovery type. Ignoring");
+    }
+
+    mBLEScanConfig.mBleScanState = BleScanState::kConnecting;
+    DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(kConnectTimeout), HandleConnectTimeout, nullptr);
+    mDeviceScanner.StopScan();
+
+    ConnectDevice(addr, kConnectTimeout);
+}
+
+void BLEManagerImpl::OnScanComplete()
+{
+    if (mBLEScanConfig.mBleScanState != BleScanState::kScanForDiscriminator &&
+        mBLEScanConfig.mBleScanState != BleScanState::kScanForAddress)
+    {
+        ChipLogProgress(Ble, "Scan complete notification without an active scan");
+        return;
+    }
+
+    BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_TIMEOUT);
+    mBLEScanConfig.mBleScanState = BleScanState::kNotScanning;
+}
+
+void BLEManagerImpl::InitiateScan(BleScanState scanType)
+{
+    DriveBLEState();
+
+    // Check for a valid scan type
+    if (scanType == BleScanState::kNotScanning)
+    {
+        BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INCORRECT_STATE);
+        ChipLogError(Ble, "Invalid scan type requested");
+        return;
+    }
+
+    // Initialize the device scanner
+    CHIP_ERROR err = mDeviceScanner.Init(this);
+    if (err != CHIP_NO_ERROR)
+    {
+        BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, err);
+        ChipLogError(Ble, "Failed to initialize device scanner: %s", ErrorStr(err));
+        return;
+    }
+
+    // Start scanning
+    mBLEScanConfig.mBleScanState = scanType;
+    err                          = mDeviceScanner.StartScan(kNewConnectionScanTimeout);
+    if (err != CHIP_NO_ERROR)
+    {
+        mBLEScanConfig.mBleScanState = BleScanState::kNotScanning;
+        ChipLogError(Ble, "Failed to start a BLE scan: %s", chip::ErrorStr(err));
+        BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, err);
+        return;
+    }
+}
+
+void BLEManagerImpl::CleanScanConfig()
+{
+    if (BLEManagerImpl::mBLEScanConfig.mBleScanState == BleScanState::kConnecting)
+    {
+        DeviceLayer::SystemLayer().CancelTimer(HandleConnectTimeout, nullptr);
+    }
+    mBLEScanConfig.mBleScanState = BleScanState::kNotScanning;
+}
+
+void BLEManagerImpl::InitiateScan(intptr_t arg)
+{
+    sInstance.InitiateScan(static_cast<BleScanState>(arg));
+}
+
+void BLEManagerImpl::NewConnection(BleLayer * bleLayer, void * appState, const SetupDiscriminator & connDiscriminator)
+{
+    mBLEScanConfig.mDiscriminator = connDiscriminator;
+    mBLEScanConfig.mAppState      = appState;
+
+    // Initiate async scan
+    PlatformMgr().ScheduleWork(InitiateScan, static_cast<intptr_t>(BleScanState::kScanForDiscriminator));
+}
+
+CHIP_ERROR BLEManagerImpl::CancelConnection()
+{
+    return CHIP_ERROR_NOT_IMPLEMENTED;
+}
+#endif
+
 } // namespace Internal
 } // namespace DeviceLayer
 } // namespace chip
diff --git a/src/platform/ESP32/nimble/ChipDeviceScanner.cpp b/src/platform/ESP32/nimble/ChipDeviceScanner.cpp
new file mode 100644
index 0000000..5bf90ba
--- /dev/null
+++ b/src/platform/ESP32/nimble/ChipDeviceScanner.cpp
@@ -0,0 +1,156 @@
+/*
+ *
+ *    Copyright (c) 2022 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.
+ */
+
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+
+#include "blecent.h"
+#include <lib/support/CodeUtils.h>
+#include <lib/support/logging/CHIPLogging.h>
+#include <platform/ESP32/ChipDeviceScanner.h>
+
+#define CHIPoBLE_SERVICE_UUID 0xFFF6
+
+namespace chip {
+namespace DeviceLayer {
+namespace Internal {
+namespace {
+
+/// Retrieve CHIP device identification info from the device advertising data
+bool NimbleGetChipDeviceInfo(const ble_hs_adv_fields & fields, chip::Ble::ChipBLEDeviceIdentificationInfo & deviceInfo)
+{
+    // Check for CHIP Service UUID
+
+    if (fields.svc_data_uuid16 != NULL)
+    {
+        if (fields.svc_data_uuid16_len > 8 && fields.svc_data_uuid16[0] == 0xf6 && fields.svc_data_uuid16[1] == 0xff)
+        {
+            deviceInfo.OpCode                              = fields.svc_data_uuid16[2];
+            deviceInfo.DeviceDiscriminatorAndAdvVersion[0] = fields.svc_data_uuid16[3];
+            deviceInfo.DeviceDiscriminatorAndAdvVersion[1] = fields.svc_data_uuid16[4];
+            // vendor and product Id from adv
+            deviceInfo.DeviceVendorId[0]  = fields.svc_data_uuid16[5];
+            deviceInfo.DeviceVendorId[1]  = fields.svc_data_uuid16[6];
+            deviceInfo.DeviceProductId[0] = fields.svc_data_uuid16[7];
+            deviceInfo.DeviceProductId[1] = fields.svc_data_uuid16[8];
+            deviceInfo.AdditionalDataFlag = fields.svc_data_uuid16[9];
+            return true;
+        }
+    }
+    return false;
+}
+
+} // namespace
+
+void ChipDeviceScanner::ReportDevice(const struct ble_hs_adv_fields & fields, const ble_addr_t & addr)
+{
+    chip::Ble::ChipBLEDeviceIdentificationInfo deviceInfo;
+    if (NimbleGetChipDeviceInfo(fields, deviceInfo) == false)
+    {
+        ChipLogDetail(Ble, "Device %s does not look like a CHIP device", addr_str(addr.val));
+        return;
+    }
+    mDelegate->OnDeviceScanned(fields, addr, deviceInfo);
+}
+
+void ChipDeviceScanner::RemoveDevice()
+{
+    // TODO
+}
+
+int ChipDeviceScanner::OnBleCentralEvent(struct ble_gap_event * event, void * arg)
+{
+    ChipDeviceScanner * scanner = (ChipDeviceScanner *) arg;
+
+    switch (event->type)
+    {
+    case BLE_GAP_EVENT_DISC_COMPLETE: {
+        scanner->mIsScanning = false;
+        return 0;
+    }
+
+    case BLE_GAP_EVENT_DISC: {
+
+        /* Try to connect to the advertiser if it looks interesting. */
+        struct ble_hs_adv_fields fields;
+        ble_hs_adv_parse_fields(&fields, event->disc.data, event->disc.length_data);
+        scanner->ReportDevice(fields, event->disc.addr);
+        return 0;
+    }
+    }
+
+    return 0;
+}
+
+CHIP_ERROR ChipDeviceScanner::StartScan(uint16_t timeout)
+{
+    ReturnErrorCodeIf(mIsScanning, CHIP_ERROR_INCORRECT_STATE);
+
+    uint8_t ownAddrType;
+    struct ble_gap_disc_params discParams;
+    int rc;
+
+    /* Figure out address to use while advertising. */
+    rc = ble_hs_id_infer_auto(0, &ownAddrType);
+    if (rc != 0)
+    {
+        ChipLogError(DeviceLayer, "ble_hs_id_infer_auto failed: %d", rc);
+        return CHIP_ERROR_INTERNAL;
+    }
+
+    /* Set up discovery parameters. */
+    memset(&discParams, 0, sizeof(discParams));
+
+    /* Tell the controller to filter the duplicates. */
+    discParams.filter_duplicates = 1;
+    /* Perform passive scanning. */
+    discParams.passive = 1;
+    /* Use defaults for the rest of the parameters. */
+    discParams.itvl          = 0;
+    discParams.window        = 0;
+    discParams.filter_policy = 0;
+    discParams.limited       = 0;
+
+    /* Start the discovery process. */
+    rc = ble_gap_disc(ownAddrType, (timeout * 1000), &discParams, OnBleCentralEvent, this);
+    if (rc != 0)
+    {
+        ChipLogError(DeviceLayer, "ble_gap_disc failed: %d", rc);
+        return CHIP_ERROR_INTERNAL;
+    }
+    mIsScanning = true;
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ChipDeviceScanner::StopScan()
+{
+    ReturnErrorCodeIf(!mIsScanning, CHIP_NO_ERROR);
+
+    int rc = ble_gap_disc_cancel();
+    if (rc != 0)
+    {
+        ChipLogError(DeviceLayer, "ble_gap_disc_cancel failed: %d", rc);
+        return CHIP_ERROR_INTERNAL;
+    }
+    mIsScanning = false;
+    mDelegate->OnScanComplete();
+    return CHIP_NO_ERROR;
+}
+
+} // namespace Internal
+} // namespace DeviceLayer
+} // namespace chip
+#endif // CONFIG_ENABLE_ESP32_BLE_CONTROLLER
diff --git a/src/platform/ESP32/nimble/blecent.h b/src/platform/ESP32/nimble/blecent.h
new file mode 100644
index 0000000..d61eb50
--- /dev/null
+++ b/src/platform/ESP32/nimble/blecent.h
@@ -0,0 +1,110 @@
+/*
+ *
+ *    Copyright (c) 2022 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.
+ */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+#pragma once
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include <host/ble_gatt.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Misc. */
+void print_bytes(const uint8_t * bytes, int len);
+void print_mbuf(const struct os_mbuf * om);
+char * addr_str(const void * addr);
+void print_uuid(const ble_uuid_t * uuid);
+
+/** Peer. */
+struct peer_dsc
+{
+    SLIST_ENTRY(peer_dsc) next;
+    struct ble_gatt_dsc dsc;
+};
+SLIST_HEAD(peer_dsc_list, peer_dsc);
+
+struct peer_chr
+{
+    SLIST_ENTRY(peer_chr) next;
+    struct ble_gatt_chr chr;
+
+    struct peer_dsc_list dscs;
+};
+SLIST_HEAD(peer_chr_list, peer_chr);
+
+struct peer_svc
+{
+    SLIST_ENTRY(peer_svc) next;
+    struct ble_gatt_svc svc;
+
+    struct peer_chr_list chrs;
+};
+SLIST_HEAD(peer_svc_list, peer_svc);
+
+struct peer;
+typedef void peer_disc_fn(const struct peer * peer, int status, void * arg);
+
+struct peer
+{
+    SLIST_ENTRY(peer) next;
+
+    uint16_t conn_handle;
+
+    /** List of discovered GATT services. */
+    struct peer_svc_list svcs;
+
+    /** Keeps track of where we are in the service discovery process. */
+    uint16_t disc_prev_chr_val;
+    struct peer_svc * cur_svc;
+
+    /** Callback that gets executed when service discovery completes. */
+    peer_disc_fn * disc_cb;
+    void * disc_cb_arg;
+};
+
+int peer_disc_all(uint16_t conn_handle, peer_disc_fn * disc_cb, void * disc_cb_arg);
+const struct peer_dsc * peer_dsc_find_uuid(const struct peer * peer, const ble_uuid_t * svc_uuid, const ble_uuid_t * chr_uuid,
+                                           const ble_uuid_t * dsc_uuid);
+const struct peer_chr * peer_chr_find_uuid(const struct peer * peer, const ble_uuid_t * svc_uuid, const ble_uuid_t * chr_uuid);
+const struct peer_svc * peer_svc_find_uuid(const struct peer * peer, const ble_uuid_t * uuid);
+int peer_delete(uint16_t conn_handle);
+int peer_add(uint16_t conn_handle);
+int peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs);
+struct peer * peer_find(uint16_t conn_handle);
+
+#ifdef __cplusplus
+}
+#endif
+#endif // CONFIG_ENABLE_ESP32_BLE_CONTROLLER
diff --git a/src/platform/ESP32/nimble/misc.c b/src/platform/ESP32/nimble/misc.c
new file mode 100644
index 0000000..2c887dc
--- /dev/null
+++ b/src/platform/ESP32/nimble/misc.c
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+#include "blecent.h"
+#include "host/ble_hs.h"
+#include "host/ble_uuid.h"
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * Utility function to log an array of bytes.
+ */
+void print_bytes(const uint8_t * bytes, int len)
+{
+    int i;
+
+    for (i = 0; i < len; i++)
+    {
+        MODLOG_DFLT(DEBUG, "%s0x%02x", i != 0 ? ":" : "", bytes[i]);
+    }
+}
+
+void print_mbuf(const struct os_mbuf * om)
+{
+    int colon, i;
+
+    colon = 0;
+    while (om != NULL)
+    {
+        if (colon)
+        {
+            MODLOG_DFLT(DEBUG, ":");
+        }
+        else
+        {
+            colon = 1;
+        }
+        for (i = 0; i < om->om_len; i++)
+        {
+            MODLOG_DFLT(DEBUG, "%s0x%02x", i != 0 ? ":" : "", om->om_data[i]);
+        }
+        om = SLIST_NEXT(om, om_next);
+    }
+}
+
+char * addr_str(const void * addr)
+{
+    static char buf[6 * 2 + 5 + 1];
+    const uint8_t * u8p;
+
+    u8p = addr;
+    sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
+
+    return buf;
+}
+
+void print_uuid(const ble_uuid_t * uuid)
+{
+    char buf[BLE_UUID_STR_LEN];
+
+    MODLOG_DFLT(DEBUG, "%s", ble_uuid_to_str(uuid, buf));
+}
+
+/**
+ * Logs information about a connection to the console.
+ */
+void print_conn_desc(const struct ble_gap_conn_desc * desc)
+{
+    MODLOG_DFLT(DEBUG, "handle=%d our_ota_addr_type=%d our_ota_addr=%s ", desc->conn_handle, desc->our_ota_addr.type,
+                addr_str(desc->our_ota_addr.val));
+    MODLOG_DFLT(DEBUG, "our_id_addr_type=%d our_id_addr=%s ", desc->our_id_addr.type, addr_str(desc->our_id_addr.val));
+    MODLOG_DFLT(DEBUG, "peer_ota_addr_type=%d peer_ota_addr=%s ", desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val));
+    MODLOG_DFLT(DEBUG, "peer_id_addr_type=%d peer_id_addr=%s ", desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val));
+    MODLOG_DFLT(DEBUG,
+                "conn_itvl=%d conn_latency=%d supervision_timeout=%d "
+                "encrypted=%d authenticated=%d bonded=%d",
+                desc->conn_itvl, desc->conn_latency, desc->supervision_timeout, desc->sec_state.encrypted,
+                desc->sec_state.authenticated, desc->sec_state.bonded);
+}
+
+void print_adv_fields(const struct ble_hs_adv_fields * fields)
+{
+    char s[BLE_HS_ADV_MAX_SZ];
+    const uint8_t * u8p;
+    int i;
+
+    if (fields->flags != 0)
+    {
+        MODLOG_DFLT(DEBUG, "    flags=0x%02x\n", fields->flags);
+    }
+
+    if (fields->uuids16 != NULL)
+    {
+        MODLOG_DFLT(DEBUG, "    uuids16(%scomplete)=", fields->uuids16_is_complete ? "" : "in");
+        for (i = 0; i < fields->num_uuids16; i++)
+        {
+            print_uuid(&fields->uuids16[i].u);
+            MODLOG_DFLT(DEBUG, " ");
+        }
+        MODLOG_DFLT(DEBUG, "\n");
+    }
+
+    if (fields->uuids32 != NULL)
+    {
+        MODLOG_DFLT(DEBUG, "    uuids32(%scomplete)=", fields->uuids32_is_complete ? "" : "in");
+        for (i = 0; i < fields->num_uuids32; i++)
+        {
+            print_uuid(&fields->uuids32[i].u);
+            MODLOG_DFLT(DEBUG, " ");
+        }
+        MODLOG_DFLT(DEBUG, "\n");
+    }
+
+    if (fields->uuids128 != NULL)
+    {
+        MODLOG_DFLT(DEBUG, "    uuids128(%scomplete)=", fields->uuids128_is_complete ? "" : "in");
+        for (i = 0; i < fields->num_uuids128; i++)
+        {
+            print_uuid(&fields->uuids128[i].u);
+            MODLOG_DFLT(DEBUG, " ");
+        }
+        MODLOG_DFLT(DEBUG, "\n");
+    }
+
+    if (fields->name != NULL)
+    {
+        assert(fields->name_len < sizeof s - 1);
+        memcpy(s, fields->name, fields->name_len);
+        s[fields->name_len] = '\0';
+        MODLOG_DFLT(DEBUG, "    name(%scomplete)=%s\n", fields->name_is_complete ? "" : "in", s);
+    }
+
+    if (fields->tx_pwr_lvl_is_present)
+    {
+        MODLOG_DFLT(DEBUG, "    tx_pwr_lvl=%d\n", fields->tx_pwr_lvl);
+    }
+
+    if (fields->slave_itvl_range != NULL)
+    {
+        MODLOG_DFLT(DEBUG, "    slave_itvl_range=");
+        print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN);
+        MODLOG_DFLT(DEBUG, "\n");
+    }
+
+    if (fields->svc_data_uuid16 != NULL)
+    {
+        MODLOG_DFLT(DEBUG, "    svc_data_uuid16=");
+        print_bytes(fields->svc_data_uuid16, fields->svc_data_uuid16_len);
+        MODLOG_DFLT(DEBUG, "\n");
+    }
+
+    if (fields->public_tgt_addr != NULL)
+    {
+        MODLOG_DFLT(DEBUG, "    public_tgt_addr=");
+        u8p = fields->public_tgt_addr;
+        for (i = 0; i < fields->num_public_tgt_addrs; i++)
+        {
+            MODLOG_DFLT(DEBUG, "public_tgt_addr=%s ", addr_str(u8p));
+            u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
+        }
+        MODLOG_DFLT(DEBUG, "\n");
+    }
+
+    if (fields->appearance_is_present)
+    {
+        MODLOG_DFLT(DEBUG, "    appearance=0x%04x\n", fields->appearance);
+    }
+
+    if (fields->adv_itvl_is_present)
+    {
+        MODLOG_DFLT(DEBUG, "    adv_itvl=0x%04x\n", fields->adv_itvl);
+    }
+
+    if (fields->svc_data_uuid32 != NULL)
+    {
+        MODLOG_DFLT(DEBUG, "    svc_data_uuid32=");
+        print_bytes(fields->svc_data_uuid32, fields->svc_data_uuid32_len);
+        MODLOG_DFLT(DEBUG, "\n");
+    }
+
+    if (fields->svc_data_uuid128 != NULL)
+    {
+        MODLOG_DFLT(DEBUG, "    svc_data_uuid128=");
+        print_bytes(fields->svc_data_uuid128, fields->svc_data_uuid128_len);
+        MODLOG_DFLT(DEBUG, "\n");
+    }
+
+    if (fields->uri != NULL)
+    {
+        MODLOG_DFLT(DEBUG, "    uri=");
+        print_bytes(fields->uri, fields->uri_len);
+        MODLOG_DFLT(DEBUG, "\n");
+    }
+
+    if (fields->mfg_data != NULL)
+    {
+        MODLOG_DFLT(DEBUG, "    mfg_data=");
+        print_bytes(fields->mfg_data, fields->mfg_data_len);
+        MODLOG_DFLT(DEBUG, "\n");
+    }
+}
+#endif // CONFIG_ENABLE_ESP32_BLE_CONTROLLER
diff --git a/src/platform/ESP32/nimble/peer.c b/src/platform/ESP32/nimble/peer.c
new file mode 100644
index 0000000..0dbf544
--- /dev/null
+++ b/src/platform/ESP32/nimble/peer.c
@@ -0,0 +1,828 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#if CONFIG_ENABLE_ESP32_BLE_CONTROLLER
+
+#include "blecent.h"
+#include "host/ble_hs.h"
+#include <assert.h>
+#include <string.h>
+
+static void * peer_svc_mem;
+static struct os_mempool peer_svc_pool;
+
+static void * peer_chr_mem;
+static struct os_mempool peer_chr_pool;
+
+static void * peer_dsc_mem;
+static struct os_mempool peer_dsc_pool;
+
+static void * peer_mem;
+static struct os_mempool peer_pool;
+static SLIST_HEAD(, peer) peers;
+
+static struct peer_svc * peer_svc_find_range(struct peer * peer, uint16_t attr_handle);
+static struct peer_svc * peer_svc_find(struct peer * peer, uint16_t svc_start_handle, struct peer_svc ** out_prev);
+int peer_svc_is_empty(const struct peer_svc * svc);
+
+uint16_t chr_end_handle(const struct peer_svc * svc, const struct peer_chr * chr);
+int chr_is_empty(const struct peer_svc * svc, const struct peer_chr * chr);
+static struct peer_chr * peer_chr_find(const struct peer_svc * svc, uint16_t chr_def_handle, struct peer_chr ** out_prev);
+static void peer_disc_chrs(struct peer * peer);
+
+static int peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error * error, uint16_t chr_val_handle,
+                           const struct ble_gatt_dsc * dsc, void * arg);
+
+struct peer * peer_find(uint16_t conn_handle)
+{
+    struct peer * peer;
+
+    SLIST_FOREACH(peer, &peers, next)
+    {
+        if (peer->conn_handle == conn_handle)
+        {
+            return peer;
+        }
+    }
+
+    return NULL;
+}
+
+static void peer_disc_complete(struct peer * peer, int rc)
+{
+    peer->disc_prev_chr_val = 0;
+
+    /* Notify caller that discovery has completed. */
+    if (peer->disc_cb != NULL)
+    {
+        peer->disc_cb(peer, rc, peer->disc_cb_arg);
+    }
+}
+
+static struct peer_dsc * peer_dsc_find_prev(const struct peer_chr * chr, uint16_t dsc_handle)
+{
+    struct peer_dsc * prev;
+    struct peer_dsc * dsc;
+
+    prev = NULL;
+    SLIST_FOREACH(dsc, &chr->dscs, next)
+    {
+        if (dsc->dsc.handle >= dsc_handle)
+        {
+            break;
+        }
+
+        prev = dsc;
+    }
+
+    return prev;
+}
+
+static struct peer_dsc * peer_dsc_find(const struct peer_chr * chr, uint16_t dsc_handle, struct peer_dsc ** out_prev)
+{
+    struct peer_dsc * prev;
+    struct peer_dsc * dsc;
+
+    prev = peer_dsc_find_prev(chr, dsc_handle);
+    if (prev == NULL)
+    {
+        dsc = SLIST_FIRST(&chr->dscs);
+    }
+    else
+    {
+        dsc = SLIST_NEXT(prev, next);
+    }
+
+    if (dsc != NULL && dsc->dsc.handle != dsc_handle)
+    {
+        dsc = NULL;
+    }
+
+    if (out_prev != NULL)
+    {
+        *out_prev = prev;
+    }
+    return dsc;
+}
+
+static int peer_dsc_add(struct peer * peer, uint16_t chr_val_handle, const struct ble_gatt_dsc * gatt_dsc)
+{
+    struct peer_dsc * prev;
+    struct peer_dsc * dsc;
+    struct peer_svc * svc;
+    struct peer_chr * chr;
+
+    svc = peer_svc_find_range(peer, chr_val_handle);
+    if (svc == NULL)
+    {
+        /* Can't find service for discovered descriptor; this shouldn't
+         * happen.
+         */
+        assert(0);
+        return BLE_HS_EUNKNOWN;
+    }
+
+    chr = peer_chr_find(svc, chr_val_handle, NULL);
+    if (chr == NULL)
+    {
+        /* Can't find characteristic for discovered descriptor; this shouldn't
+         * happen.
+         */
+        assert(0);
+        return BLE_HS_EUNKNOWN;
+    }
+
+    dsc = peer_dsc_find(chr, gatt_dsc->handle, &prev);
+    if (dsc != NULL)
+    {
+        /* Descriptor already discovered. */
+        return 0;
+    }
+
+    dsc = os_memblock_get(&peer_dsc_pool);
+    if (dsc == NULL)
+    {
+        /* Out of memory. */
+        return BLE_HS_ENOMEM;
+    }
+    memset(dsc, 0, sizeof *dsc);
+
+    dsc->dsc = *gatt_dsc;
+
+    if (prev == NULL)
+    {
+        SLIST_INSERT_HEAD(&chr->dscs, dsc, next);
+    }
+    else
+    {
+        SLIST_NEXT(prev, next) = dsc;
+    }
+
+    return 0;
+}
+
+static void peer_disc_dscs(struct peer * peer)
+{
+    struct peer_chr * chr;
+    struct peer_svc * svc;
+    int rc;
+
+    /* Search through the list of discovered characteristics for the first
+     * characteristic that contains undiscovered descriptors.  Then, discover
+     * all descriptors belonging to that characteristic.
+     */
+    SLIST_FOREACH(svc, &peer->svcs, next)
+    {
+        SLIST_FOREACH(chr, &svc->chrs, next)
+        {
+            if (!chr_is_empty(svc, chr) && SLIST_EMPTY(&chr->dscs) && peer->disc_prev_chr_val <= chr->chr.def_handle)
+            {
+
+                rc = ble_gattc_disc_all_dscs(peer->conn_handle, chr->chr.val_handle, chr_end_handle(svc, chr), peer_dsc_disced,
+                                             peer);
+                if (rc != 0)
+                {
+                    peer_disc_complete(peer, rc);
+                }
+
+                peer->disc_prev_chr_val = chr->chr.val_handle;
+                return;
+            }
+        }
+    }
+
+    /* All descriptors discovered. */
+    peer_disc_complete(peer, 0);
+}
+
+static int peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error * error, uint16_t chr_val_handle,
+                           const struct ble_gatt_dsc * dsc, void * arg)
+{
+    struct peer * peer;
+    int rc;
+
+    peer = arg;
+    assert(peer->conn_handle == conn_handle);
+
+    switch (error->status)
+    {
+    case 0:
+        rc = peer_dsc_add(peer, chr_val_handle, dsc);
+        break;
+
+    case BLE_HS_EDONE:
+        /* All descriptors in this characteristic discovered; start discovering
+         * descriptors in the next characteristic.
+         */
+        if (peer->disc_prev_chr_val > 0)
+        {
+            peer_disc_dscs(peer);
+        }
+        rc = 0;
+        break;
+
+    default:
+        /* Error; abort discovery. */
+        rc = error->status;
+        break;
+    }
+
+    if (rc != 0)
+    {
+        /* Error; abort discovery. */
+        peer_disc_complete(peer, rc);
+    }
+
+    return rc;
+}
+
+uint16_t chr_end_handle(const struct peer_svc * svc, const struct peer_chr * chr)
+{
+    const struct peer_chr * next_chr;
+
+    next_chr = SLIST_NEXT(chr, next);
+    if (next_chr != NULL)
+    {
+        return next_chr->chr.def_handle - 1;
+    }
+    else
+    {
+        return svc->svc.end_handle;
+    }
+}
+
+int chr_is_empty(const struct peer_svc * svc, const struct peer_chr * chr)
+{
+    return chr_end_handle(svc, chr) <= chr->chr.val_handle;
+}
+
+static struct peer_chr * peer_chr_find_prev(const struct peer_svc * svc, uint16_t chr_val_handle)
+{
+    struct peer_chr * prev;
+    struct peer_chr * chr;
+
+    prev = NULL;
+    SLIST_FOREACH(chr, &svc->chrs, next)
+    {
+        if (chr->chr.val_handle >= chr_val_handle)
+        {
+            break;
+        }
+
+        prev = chr;
+    }
+
+    return prev;
+}
+
+static struct peer_chr * peer_chr_find(const struct peer_svc * svc, uint16_t chr_val_handle, struct peer_chr ** out_prev)
+{
+    struct peer_chr * prev;
+    struct peer_chr * chr;
+
+    prev = peer_chr_find_prev(svc, chr_val_handle);
+    if (prev == NULL)
+    {
+        chr = SLIST_FIRST(&svc->chrs);
+    }
+    else
+    {
+        chr = SLIST_NEXT(prev, next);
+    }
+
+    if (chr != NULL && chr->chr.val_handle != chr_val_handle)
+    {
+        chr = NULL;
+    }
+
+    if (out_prev != NULL)
+    {
+        *out_prev = prev;
+    }
+    return chr;
+}
+
+static void peer_chr_delete(struct peer_chr * chr)
+{
+    struct peer_dsc * dsc;
+
+    while ((dsc = SLIST_FIRST(&chr->dscs)) != NULL)
+    {
+        SLIST_REMOVE_HEAD(&chr->dscs, next);
+        os_memblock_put(&peer_dsc_pool, dsc);
+    }
+
+    os_memblock_put(&peer_chr_pool, chr);
+}
+
+static int peer_chr_add(struct peer * peer, uint16_t svc_start_handle, const struct ble_gatt_chr * gatt_chr)
+{
+    struct peer_chr * prev;
+    struct peer_chr * chr;
+    struct peer_svc * svc;
+
+    svc = peer_svc_find(peer, svc_start_handle, NULL);
+    if (svc == NULL)
+    {
+        /* Can't find service for discovered characteristic; this shouldn't
+         * happen.
+         */
+        assert(0);
+        return BLE_HS_EUNKNOWN;
+    }
+
+    chr = peer_chr_find(svc, gatt_chr->def_handle, &prev);
+    if (chr != NULL)
+    {
+        /* Characteristic already discovered. */
+        return 0;
+    }
+
+    chr = os_memblock_get(&peer_chr_pool);
+    if (chr == NULL)
+    {
+        /* Out of memory. */
+        return BLE_HS_ENOMEM;
+    }
+    memset(chr, 0, sizeof *chr);
+
+    chr->chr = *gatt_chr;
+
+    if (prev == NULL)
+    {
+        SLIST_INSERT_HEAD(&svc->chrs, chr, next);
+    }
+    else
+    {
+        SLIST_NEXT(prev, next) = chr;
+    }
+
+    return 0;
+}
+
+static int peer_chr_disced(uint16_t conn_handle, const struct ble_gatt_error * error, const struct ble_gatt_chr * chr, void * arg)
+{
+    struct peer * peer;
+    int rc;
+
+    peer = arg;
+    assert(peer->conn_handle == conn_handle);
+
+    switch (error->status)
+    {
+    case 0:
+        rc = peer_chr_add(peer, peer->cur_svc->svc.start_handle, chr);
+        break;
+
+    case BLE_HS_EDONE:
+        /* All characteristics in this service discovered; start discovering
+         * characteristics in the next service.
+         */
+        if (peer->disc_prev_chr_val > 0)
+        {
+            peer_disc_chrs(peer);
+        }
+        rc = 0;
+        break;
+
+    default:
+        rc = error->status;
+        break;
+    }
+
+    if (rc != 0)
+    {
+        /* Error; abort discovery. */
+        peer_disc_complete(peer, rc);
+    }
+
+    return rc;
+}
+
+static void peer_disc_chrs(struct peer * peer)
+{
+    struct peer_svc * svc;
+    int rc;
+
+    /* Search through the list of discovered service for the first service that
+     * contains undiscovered characteristics.  Then, discover all
+     * characteristics belonging to that service.
+     */
+    SLIST_FOREACH(svc, &peer->svcs, next)
+    {
+        if (!peer_svc_is_empty(svc) && SLIST_EMPTY(&svc->chrs))
+        {
+            peer->cur_svc = svc;
+            rc = ble_gattc_disc_all_chrs(peer->conn_handle, svc->svc.start_handle, svc->svc.end_handle, peer_chr_disced, peer);
+            if (rc != 0)
+            {
+                peer_disc_complete(peer, rc);
+            }
+            return;
+        }
+    }
+
+    /* All characteristics discovered. */
+    peer_disc_dscs(peer);
+}
+
+int peer_svc_is_empty(const struct peer_svc * svc)
+{
+    return svc->svc.end_handle <= svc->svc.start_handle;
+}
+
+static struct peer_svc * peer_svc_find_prev(struct peer * peer, uint16_t svc_start_handle)
+{
+    struct peer_svc * prev;
+    struct peer_svc * svc;
+
+    prev = NULL;
+    SLIST_FOREACH(svc, &peer->svcs, next)
+    {
+        if (svc->svc.start_handle >= svc_start_handle)
+        {
+            break;
+        }
+
+        prev = svc;
+    }
+
+    return prev;
+}
+
+static struct peer_svc * peer_svc_find(struct peer * peer, uint16_t svc_start_handle, struct peer_svc ** out_prev)
+{
+    struct peer_svc * prev;
+    struct peer_svc * svc;
+
+    prev = peer_svc_find_prev(peer, svc_start_handle);
+    if (prev == NULL)
+    {
+        svc = SLIST_FIRST(&peer->svcs);
+    }
+    else
+    {
+        svc = SLIST_NEXT(prev, next);
+    }
+
+    if (svc != NULL && svc->svc.start_handle != svc_start_handle)
+    {
+        svc = NULL;
+    }
+
+    if (out_prev != NULL)
+    {
+        *out_prev = prev;
+    }
+    return svc;
+}
+
+static struct peer_svc * peer_svc_find_range(struct peer * peer, uint16_t attr_handle)
+{
+    struct peer_svc * svc;
+
+    SLIST_FOREACH(svc, &peer->svcs, next)
+    {
+        if (svc->svc.start_handle <= attr_handle && svc->svc.end_handle >= attr_handle)
+        {
+
+            return svc;
+        }
+    }
+
+    return NULL;
+}
+
+const struct peer_svc * peer_svc_find_uuid(const struct peer * peer, const ble_uuid_t * uuid)
+{
+    const struct peer_svc * svc;
+
+    SLIST_FOREACH(svc, &peer->svcs, next)
+    {
+        if (ble_uuid_cmp(&svc->svc.uuid.u, uuid) == 0)
+        {
+            return svc;
+        }
+    }
+
+    return NULL;
+}
+
+const struct peer_chr * peer_chr_find_uuid(const struct peer * peer, const ble_uuid_t * svc_uuid, const ble_uuid_t * chr_uuid)
+{
+    const struct peer_svc * svc;
+    const struct peer_chr * chr;
+
+    svc = peer_svc_find_uuid(peer, svc_uuid);
+    if (svc == NULL)
+    {
+        return NULL;
+    }
+
+    SLIST_FOREACH(chr, &svc->chrs, next)
+    {
+        if (ble_uuid_cmp(&chr->chr.uuid.u, chr_uuid) == 0)
+        {
+            return chr;
+        }
+    }
+
+    return NULL;
+}
+
+const struct peer_dsc * peer_dsc_find_uuid(const struct peer * peer, const ble_uuid_t * svc_uuid, const ble_uuid_t * chr_uuid,
+                                           const ble_uuid_t * dsc_uuid)
+{
+    const struct peer_chr * chr;
+    const struct peer_dsc * dsc;
+
+    chr = peer_chr_find_uuid(peer, svc_uuid, chr_uuid);
+    if (chr == NULL)
+    {
+        return NULL;
+    }
+
+    SLIST_FOREACH(dsc, &chr->dscs, next)
+    {
+        if (ble_uuid_cmp(&dsc->dsc.uuid.u, dsc_uuid) == 0)
+        {
+            return dsc;
+        }
+    }
+
+    return NULL;
+}
+
+static int peer_svc_add(struct peer * peer, const struct ble_gatt_svc * gatt_svc)
+{
+    struct peer_svc * prev;
+    struct peer_svc * svc;
+
+    svc = peer_svc_find(peer, gatt_svc->start_handle, &prev);
+    if (svc != NULL)
+    {
+        /* Service already discovered. */
+        return 0;
+    }
+
+    svc = os_memblock_get(&peer_svc_pool);
+    if (svc == NULL)
+    {
+        /* Out of memory. */
+        return BLE_HS_ENOMEM;
+    }
+    memset(svc, 0, sizeof *svc);
+
+    svc->svc = *gatt_svc;
+    SLIST_INIT(&svc->chrs);
+
+    if (prev == NULL)
+    {
+        SLIST_INSERT_HEAD(&peer->svcs, svc, next);
+    }
+    else
+    {
+        SLIST_INSERT_AFTER(prev, svc, next);
+    }
+
+    return 0;
+}
+
+static void peer_svc_delete(struct peer_svc * svc)
+{
+    struct peer_chr * chr;
+
+    while ((chr = SLIST_FIRST(&svc->chrs)) != NULL)
+    {
+        SLIST_REMOVE_HEAD(&svc->chrs, next);
+        peer_chr_delete(chr);
+    }
+
+    os_memblock_put(&peer_svc_pool, svc);
+}
+
+static int peer_svc_disced(uint16_t conn_handle, const struct ble_gatt_error * error, const struct ble_gatt_svc * service,
+                           void * arg)
+{
+    struct peer * peer;
+    int rc;
+
+    peer = arg;
+    assert(peer->conn_handle == conn_handle);
+    switch (error->status)
+    {
+
+    case 0:
+        rc = peer_svc_add(peer, service);
+        break;
+
+    case BLE_HS_EDONE:
+        /* All services discovered; start discovering characteristics. */
+        if (peer->disc_prev_chr_val > 0)
+        {
+            peer_disc_chrs(peer);
+        }
+        rc = 0;
+        break;
+
+    default:
+        rc = error->status;
+        break;
+    }
+
+    if (rc != 0)
+    {
+        /* Error; abort discovery. */
+        peer_disc_complete(peer, rc);
+    }
+
+    return rc;
+}
+
+int peer_disc_all(uint16_t conn_handle, peer_disc_fn * disc_cb, void * disc_cb_arg)
+{
+    struct peer_svc * svc;
+    struct peer * peer;
+    int rc;
+    peer = peer_find(conn_handle);
+    if (peer == NULL)
+    {
+        return BLE_HS_ENOTCONN;
+    }
+
+    /* Undiscover everything first. */
+    while ((svc = SLIST_FIRST(&peer->svcs)) != NULL)
+    {
+        SLIST_REMOVE_HEAD(&peer->svcs, next);
+        peer_svc_delete(svc);
+    }
+
+    peer->disc_prev_chr_val = 1;
+    peer->disc_cb           = disc_cb;
+    peer->disc_cb_arg       = disc_cb_arg;
+
+    rc = ble_gattc_disc_all_svcs(conn_handle, peer_svc_disced, peer);
+    if (rc != 0)
+    {
+        return rc;
+    }
+
+    return 0;
+}
+
+int peer_delete(uint16_t conn_handle)
+{
+    struct peer_svc * svc;
+    struct peer * peer;
+    int rc;
+
+    peer = peer_find(conn_handle);
+    if (peer == NULL)
+    {
+        return BLE_HS_ENOTCONN;
+    }
+
+    SLIST_REMOVE(&peers, peer, peer, next);
+
+    while ((svc = SLIST_FIRST(&peer->svcs)) != NULL)
+    {
+        SLIST_REMOVE_HEAD(&peer->svcs, next);
+        peer_svc_delete(svc);
+    }
+
+    rc = os_memblock_put(&peer_pool, peer);
+    if (rc != 0)
+    {
+        return BLE_HS_EOS;
+    }
+
+    return 0;
+}
+
+int peer_add(uint16_t conn_handle)
+{
+    struct peer * peer;
+
+    /* Make sure the connection handle is unique. */
+    peer = peer_find(conn_handle);
+    if (peer != NULL)
+    {
+        return BLE_HS_EALREADY;
+    }
+
+    peer = os_memblock_get(&peer_pool);
+    if (peer == NULL)
+    {
+        /* Out of memory. */
+        return BLE_HS_ENOMEM;
+    }
+
+    memset(peer, 0, sizeof *peer);
+    peer->conn_handle = conn_handle;
+
+    SLIST_INSERT_HEAD(&peers, peer, next);
+
+    return 0;
+}
+
+static void peer_free_mem(void)
+{
+    free(peer_mem);
+    peer_mem = NULL;
+
+    free(peer_svc_mem);
+    peer_svc_mem = NULL;
+
+    free(peer_chr_mem);
+    peer_chr_mem = NULL;
+
+    free(peer_dsc_mem);
+    peer_dsc_mem = NULL;
+}
+
+int peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs)
+{
+    int rc;
+
+    /* Free memory first in case this function gets called more than once. */
+    peer_free_mem();
+
+    peer_mem = malloc(OS_MEMPOOL_BYTES(max_peers, sizeof(struct peer)));
+    if (peer_mem == NULL)
+    {
+        rc = BLE_HS_ENOMEM;
+        goto err;
+    }
+
+    rc = os_mempool_init(&peer_pool, max_peers, sizeof(struct peer), peer_mem, "peer_pool");
+    if (rc != 0)
+    {
+        rc = BLE_HS_EOS;
+        goto err;
+    }
+
+    peer_svc_mem = malloc(OS_MEMPOOL_BYTES(max_svcs, sizeof(struct peer_svc)));
+    if (peer_svc_mem == NULL)
+    {
+        rc = BLE_HS_ENOMEM;
+        goto err;
+    }
+
+    rc = os_mempool_init(&peer_svc_pool, max_svcs, sizeof(struct peer_svc), peer_svc_mem, "peer_svc_pool");
+    if (rc != 0)
+    {
+        rc = BLE_HS_EOS;
+        goto err;
+    }
+
+    peer_chr_mem = malloc(OS_MEMPOOL_BYTES(max_chrs, sizeof(struct peer_chr)));
+    if (peer_chr_mem == NULL)
+    {
+        rc = BLE_HS_ENOMEM;
+        goto err;
+    }
+
+    rc = os_mempool_init(&peer_chr_pool, max_chrs, sizeof(struct peer_chr), peer_chr_mem, "peer_chr_pool");
+    if (rc != 0)
+    {
+        rc = BLE_HS_EOS;
+        goto err;
+    }
+
+    peer_dsc_mem = malloc(OS_MEMPOOL_BYTES(max_dscs, sizeof(struct peer_dsc)));
+    if (peer_dsc_mem == NULL)
+    {
+        rc = BLE_HS_ENOMEM;
+        goto err;
+    }
+
+    rc = os_mempool_init(&peer_dsc_pool, max_dscs, sizeof(struct peer_dsc), peer_dsc_mem, "peer_dsc_pool");
+    if (rc != 0)
+    {
+        rc = BLE_HS_EOS;
+        goto err;
+    }
+
+    return 0;
+
+err:
+    peer_free_mem();
+    return rc;
+}
+#endif