From 2ff52fd770351c258981b6a6a9dac711834ebdec Mon Sep 17 00:00:00 2001
From: Max Kriegleder <max.kriegleder@gmail.com>
Date: Thu, 31 Oct 2024 00:00:28 +0100
Subject: [PATCH] examples: add blecent and bleprph similar to mynewt-nimble
 apps

These example apps create a BLE central and peripheral based on
mynewt-nimBLE and work in companion. The apps are mostly based
on their equivalents, which can be found at
https://github.com/apache/mynewt-nimble/tree/master/apps

Signed-off-by: Max Kriegleder <max.kriegleder@gmail.com>
---
 examples/nimble_blecent/CMakeLists.txt        |  31 +
 examples/nimble_blecent/Kconfig               |  30 +
 examples/nimble_blecent/Make.defs             |  23 +
 examples/nimble_blecent/Makefile              |  38 +
 examples/nimble_blecent/blecent.h             | 126 +++
 examples/nimble_blecent/misc.c                | 234 +++++
 examples/nimble_blecent/nimble_blecent_main.c | 692 +++++++++++++
 examples/nimble_blecent/peer.c                | 933 ++++++++++++++++++
 examples/nimble_bleprph/CMakeLists.txt        |  31 +
 examples/nimble_bleprph/Kconfig               |  30 +
 examples/nimble_bleprph/Make.defs             |  23 +
 examples/nimble_bleprph/Makefile              |  38 +
 examples/nimble_bleprph/bleprph.h             |  60 ++
 examples/nimble_bleprph/gatt_svr.c            | 238 +++++
 examples/nimble_bleprph/misc.c                |  48 +
 examples/nimble_bleprph/nimble_bleprph_main.c | 440 +++++++++
 16 files changed, 3015 insertions(+)
 create mode 100644 examples/nimble_blecent/CMakeLists.txt
 create mode 100644 examples/nimble_blecent/Kconfig
 create mode 100644 examples/nimble_blecent/Make.defs
 create mode 100644 examples/nimble_blecent/Makefile
 create mode 100644 examples/nimble_blecent/blecent.h
 create mode 100644 examples/nimble_blecent/misc.c
 create mode 100644 examples/nimble_blecent/nimble_blecent_main.c
 create mode 100644 examples/nimble_blecent/peer.c
 create mode 100644 examples/nimble_bleprph/CMakeLists.txt
 create mode 100644 examples/nimble_bleprph/Kconfig
 create mode 100644 examples/nimble_bleprph/Make.defs
 create mode 100644 examples/nimble_bleprph/Makefile
 create mode 100644 examples/nimble_bleprph/bleprph.h
 create mode 100644 examples/nimble_bleprph/gatt_svr.c
 create mode 100644 examples/nimble_bleprph/misc.c
 create mode 100644 examples/nimble_bleprph/nimble_bleprph_main.c

diff --git a/examples/nimble_blecent/CMakeLists.txt b/examples/nimble_blecent/CMakeLists.txt
new file mode 100644
index 00000000000..6b10b76d66b
--- /dev/null
+++ b/examples/nimble_blecent/CMakeLists.txt
@@ -0,0 +1,31 @@
+# ##############################################################################
+# apps/examples/nimble_blecent/CMakeLists.txt
+#
+# 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_EXAMPLES_NIMBLE_BLECENT)
+  nuttx_add_application(
+    NAME
+    nimble
+    SRCS
+    ${CMAKE_CURRENT_LIST_DIR}/nimble_blecent_main.c
+    ${CMAKE_CURRENT_LIST_DIR}/misc.c
+    ${CMAKE_CURRENT_LIST_DIR}/peer.c
+    DEPENDS
+    nimble)
+endif()
diff --git a/examples/nimble_blecent/Kconfig b/examples/nimble_blecent/Kconfig
new file mode 100644
index 00000000000..edaa65aa50e
--- /dev/null
+++ b/examples/nimble_blecent/Kconfig
@@ -0,0 +1,30 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+config EXAMPLES_NIMBLE_BLECENT
+	tristate "NimBLE peripheral"
+	default n
+	---help---
+		Enable the nimble peripheral
+
+if EXAMPLES_NIMBLE_BLECENT
+
+config EXAMPLES_NIMBLE_BLECENT_PROGNAME
+	string "Program name"
+	default "nimble"
+	---help---
+		This is the name of the program that will be used when the NSH ELF
+		program is installed.
+
+config EXAMPLES_NIMBLE_BLECENT_PRIORITY
+	int "NimBLE task priority"
+	default 100
+
+config EXAMPLES_NIMBLE_BLECENT_STACKSIZE
+	int "NimBLE stack size"
+	default DEFAULT_TASK_STACKSIZE
+
+endif # EXAMPLES_NIMBLE_BLECENT
+
diff --git a/examples/nimble_blecent/Make.defs b/examples/nimble_blecent/Make.defs
new file mode 100644
index 00000000000..fb8b4456538
--- /dev/null
+++ b/examples/nimble_blecent/Make.defs
@@ -0,0 +1,23 @@
+############################################################################
+# apps/examples/nimble_blecent/Make.defs
+#
+# 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.
+#
+############################################################################
+
+ifneq ($(CONFIG_EXAMPLES_NIMBLE_BLECENT),)
+CONFIGURED_APPS += $(APPDIR)/examples/nimble
+endif
diff --git a/examples/nimble_blecent/Makefile b/examples/nimble_blecent/Makefile
new file mode 100644
index 00000000000..36f93724e52
--- /dev/null
+++ b/examples/nimble_blecent/Makefile
@@ -0,0 +1,38 @@
+############################################################################
+# apps/examples/nimble_blecent/Makefile
+#
+# 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.
+#
+############################################################################
+
+include $(APPDIR)/Make.defs
+
+# NimBLE built-in application info
+
+PROGNAME  = $(CONFIG_EXAMPLES_NIMBLE_BLECENT_PROGNAME)
+PRIORITY  = $(CONFIG_EXAMPLES_NIMBLE_BLECENT_PRIORITY)
+STACKSIZE = $(CONFIG_EXAMPLES_NIMBLE_BLECENT_STACKSIZE)
+MODULE    = $(CONFIG_EXAMPLES_NIMBLE_BLECENT)
+
+# NimBLE Central Example
+
+CSRCS += misc.c peer.c
+MAINSRC = nimble_blecent_main.c
+
+include $(APPDIR)/wireless/bluetooth/nimble/Makefile.nimble
+
+include $(APPDIR)/Application.mk
+
diff --git a/examples/nimble_blecent/blecent.h b/examples/nimble_blecent/blecent.h
new file mode 100644
index 00000000000..8ae394ecd29
--- /dev/null
+++ b/examples/nimble_blecent/blecent.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+ * apps/examples/nimble_blecent/blecent.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef H_BLECENT_
+#define H_BLECENT_
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct ble_hs_adv_fields;
+struct ble_gap_conn_desc;
+struct ble_hs_cfg;
+union ble_store_value;
+union ble_store_key;
+
+#define BLECENT_SVC_ALERT_UUID              0x1811
+#define BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID  0x2A47
+#define BLECENT_CHR_NEW_ALERT               0x2A46
+#define BLECENT_CHR_SUP_UNR_ALERT_CAT_UUID  0x2A48
+#define BLECENT_CHR_UNR_ALERT_STAT_UUID     0x2A45
+#define BLECENT_CHR_ALERT_NOT_CTRL_PT       0x2A44
+
+/* 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);
+void print_conn_desc(const struct ble_gap_conn_desc *desc);
+void print_adv_fields(const struct ble_hs_adv_fields *fields);
+
+/* 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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/examples/nimble_blecent/misc.c b/examples/nimble_blecent/misc.c
new file mode 100644
index 00000000000..8ac07677f44
--- /dev/null
+++ b/examples/nimble_blecent/misc.c
@@ -0,0 +1,234 @@
+/****************************************************************************
+ * apps/examples/nimble_blecent/misc.c
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include "host/ble_hs.h"
+#include "host/ble_uuid.h"
+#include "blecent.h"
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+void print_bytes(FAR const uint8_t *bytes, int len)
+{
+  int i;
+
+  for (i = 0; i < len; i++)
+    {
+      printf("%s0x%02x", i != 0 ? ":" : "", bytes[i]);
+    }
+}
+
+void print_mbuf(FAR const struct os_mbuf *om)
+{
+  int colon;
+
+  colon = 0;
+  while (om != NULL)
+    {
+      if (colon)
+        {
+          printf(":");
+        }
+      else
+        {
+          colon = 1;
+        }
+
+      print_bytes(om->om_data, om->om_len);
+      om = SLIST_NEXT(om, om_next);
+    }
+}
+
+char * addr_str(FAR 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(FAR const ble_uuid_t *uuid)
+{
+  char buf[BLE_UUID_STR_LEN];
+
+  printf("%s", ble_uuid_to_str(uuid, buf));
+}
+
+void print_conn_desc(FAR const struct ble_gap_conn_desc *desc)
+{
+  printf("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));
+  printf("our_id_addr_type=%d our_id_addr=%s ",
+         desc->our_id_addr.type, addr_str(desc->our_id_addr.val));
+  printf("peer_ota_addr_type=%d peer_ota_addr=%s ",
+         desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val));
+  printf("peer_id_addr_type=%d peer_id_addr=%s ",
+         desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val));
+  printf("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(FAR const struct ble_hs_adv_fields *fields)
+{
+  char s[BLE_HS_ADV_MAX_SZ];
+  const uint8_t *u8p;
+  int i;
+
+  if (fields->flags != 0)
+    {
+      printf("    flags=0x%02x\n", fields->flags);
+    }
+
+  if (fields->uuids16 != NULL)
+    {
+      printf("    uuids16(%scomplete)=",
+            fields->uuids16_is_complete ? "" : "in");
+      for (i = 0; i < fields->num_uuids16; i++)
+        {
+          print_uuid(&fields->uuids16[i].u);
+          printf(" ");
+        }
+
+      printf("\n");
+    }
+
+  if (fields->uuids32 != NULL)
+    {
+      printf("    uuids32(%scomplete)=",
+            fields->uuids32_is_complete ? "" : "in");
+      for (i = 0; i < fields->num_uuids32; i++)
+        {
+          print_uuid(&fields->uuids32[i].u);
+          printf(" ");
+        }
+
+      printf("\n");
+    }
+
+  if (fields->uuids128 != NULL)
+    {
+      printf("    uuids128(%scomplete)=",
+            fields->uuids128_is_complete ? "" : "in");
+      for (i = 0; i < fields->num_uuids128; i++)
+        {
+          print_uuid(&fields->uuids128[i].u);
+          printf(" ");
+        }
+
+      printf("\n");
+    }
+
+  if (fields->name != NULL)
+    {
+      assert(fields->name_len < sizeof s - 1);
+      memcpy(s, fields->name, fields->name_len);
+      s[fields->name_len] = '\0';
+      printf("    name(%scomplete)=%s\n",
+            fields->name_is_complete ? "" : "in", s);
+    }
+
+  if (fields->tx_pwr_lvl_is_present)
+    {
+      printf("    tx_pwr_lvl=%d\n", fields->tx_pwr_lvl);
+    }
+
+  if (fields->slave_itvl_range != NULL)
+    {
+      printf("    slave_itvl_range=");
+      print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN);
+      printf("\n");
+    }
+
+  if (fields->svc_data_uuid16 != NULL)
+    {
+      printf("    svc_data_uuid16=");
+      print_bytes(fields->svc_data_uuid16, fields->svc_data_uuid16_len);
+      printf("\n");
+    }
+
+  if (fields->public_tgt_addr != NULL)
+    {
+      printf("    public_tgt_addr=");
+      u8p = fields->public_tgt_addr;
+      for (i = 0; i < fields->num_public_tgt_addrs; i++)
+        {
+          printf("public_tgt_addr=%s ", addr_str(u8p));
+          u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
+        }
+
+      printf("\n");
+    }
+
+  if (fields->appearance_is_present)
+    {
+      printf("    appearance=0x%04x\n", fields->appearance);
+    }
+
+  if (fields->adv_itvl_is_present)
+    {
+      printf("    adv_itvl=0x%04x\n", fields->adv_itvl);
+    }
+
+  if (fields->svc_data_uuid32 != NULL)
+    {
+      printf("    svc_data_uuid32=");
+      print_bytes(fields->svc_data_uuid32, fields->svc_data_uuid32_len);
+      printf("\n");
+    }
+
+  if (fields->svc_data_uuid128 != NULL)
+    {
+      printf("    svc_data_uuid128=");
+      print_bytes(fields->svc_data_uuid128, fields->svc_data_uuid128_len);
+      printf("\n");
+    }
+
+  if (fields->uri != NULL)
+    {
+      printf("    uri=");
+      print_bytes(fields->uri, fields->uri_len);
+      printf("\n");
+    }
+
+  if (fields->mfg_data != NULL)
+    {
+      printf("    mfg_data=");
+      print_bytes(fields->mfg_data, fields->mfg_data_len);
+      printf("\n");
+    }
+}
diff --git a/examples/nimble_blecent/nimble_blecent_main.c b/examples/nimble_blecent/nimble_blecent_main.c
new file mode 100644
index 00000000000..6e2bd0cb61f
--- /dev/null
+++ b/examples/nimble_blecent/nimble_blecent_main.c
@@ -0,0 +1,692 @@
+/****************************************************************************
+ * apps/examples/nimble_blecent/nimble_blecent_main.c
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <assert.h>
+#include <string.h>
+
+/* BLE */
+
+#include "nimble/ble.h"
+#include "nimble/nimble_port.h"
+#include "host/ble_hs.h"
+#include "host/util/util.h"
+
+/* Mandatory services. */
+
+#include "services/gap/ble_svc_gap.h"
+#include "services/gatt/ble_svc_gatt.h"
+#include "services/bas/ble_svc_bas.h"
+
+/* Application-specified header. */
+
+#include "blecent.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define BLECENT_BAS_UUID 0x180f
+#define BLECENT_CHR_BAS_BL_UUID 0x2a19
+
+/* Not used now */
+
+#define TASK_DEFAULT_PRIORITY CONFIG_EXAMPLES_NIMBLE_BLECENT_PRIORITY
+#define TASK_DEFAULT_STACK NULL
+#define TASK_DEFAULT_STACK_SIZE 0
+
+/****************************************************************************
+ * External Functions Prototypes
+ ****************************************************************************/
+
+void ble_hci_sock_ack_handler(FAR void *param);
+void ble_hci_sock_set_device(int dev);
+
+/****************************************************************************
+ * Private Functions Prototypes
+ ****************************************************************************/
+
+static int blecent_gap_event(FAR struct ble_gap_event *event, FAR void *arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: blecent_on_read
+ ****************************************************************************/
+
+static int
+blecent_on_read(uint16_t conn_handle,
+                FAR const struct ble_gatt_error *error,
+                FAR struct ble_gatt_attr *attr,
+                FAR void *arg)
+{
+  printf("Read complete; status=%d conn_handle=%d", error->status,
+         conn_handle);
+  if (error->status == 0)
+  {
+    printf(" attr_handle=%d value=", attr->handle);
+    print_mbuf(attr->om);
+  }
+  printf("\n");
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: blecent_on_write
+ ****************************************************************************/
+
+static int
+blecent_on_write(uint16_t conn_handle,
+                 FAR const struct ble_gatt_error *error,
+                 FAR struct ble_gatt_attr *attr,
+                 FAR void *arg)
+{
+  printf("Write complete; status=%d conn_handle=%d attr_handle=%d\n",
+         error->status, conn_handle, attr->handle);
+
+  return 0;
+}
+
+/**
+ * Application callback. Called when the attempt to subscribe to
+ * notifications for the ANS Unread Alert Status characteristic
+ * has completed.
+ */
+
+static int
+blecent_on_subscribe(uint16_t conn_handle,
+                     FAR const struct ble_gatt_error *error,
+                     FAR struct ble_gatt_attr *attr,
+                     FAR void *arg)
+{
+  printf("Subscribe complete; status=%d conn_handle=%d "
+         "attr_handle=%d\n",
+         error->status, conn_handle, attr->handle);
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: blecent_read_write_subscribe
+ ****************************************************************************/
+
+static void
+blecent_read_write_subscribe(FAR const struct peer *peer)
+{
+  const struct peer_chr *chr;
+  const struct peer_dsc *dsc;
+  uint8_t value[2];
+  int rc;
+
+  /* Read the supported-new-alert-category characteristic. */
+
+  chr = peer_chr_find_uuid(peer,
+            BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID),
+            BLE_UUID16_DECLARE(BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID));
+  if (chr == NULL)
+  {
+    printf("Error: Peer doesn't support the Supported New "
+           "Alert Category characteristic\n");
+    goto err;
+  }
+
+  rc = ble_gattc_read(peer->conn_handle, chr->chr.val_handle,
+                      blecent_on_read, NULL);
+  if (rc != 0)
+  {
+    printf("Error: Failed to read characteristic; rc=%d\n",
+           rc);
+    goto err;
+  }
+
+  /* Write two bytes (99, 100) to the alert-notification-control-point
+   * characteristic.
+   */
+
+  chr = peer_chr_find_uuid(peer,
+              BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID),
+              BLE_UUID16_DECLARE(BLECENT_CHR_ALERT_NOT_CTRL_PT));
+  if (chr == NULL)
+  {
+    printf("Error: Peer doesn't support the Alert "
+           "Notification Control Point characteristic\n");
+    goto err;
+  }
+
+  value[0] = 99;
+  value[1] = 100;
+  rc = ble_gattc_write_flat(peer->conn_handle, chr->chr.val_handle,
+                            value, sizeof value, blecent_on_write, NULL);
+  if (rc != 0)
+  {
+    printf("Error: Failed to write characteristic; rc=%d\n",
+           rc);
+  }
+
+  /* Subscribe to notifications for the Battery Level characteristic.
+   * A central enables notifications by writing two bytes (1, 0) to the
+   * characteristic's client-characteristic-configuration-descriptor (CCCD).
+   */
+
+  dsc = peer_dsc_find_uuid(peer,
+                BLE_UUID16_DECLARE(BLE_SVC_BAS_UUID16),
+                BLE_UUID16_DECLARE(BLE_SVC_BAS_CHR_UUID16_BATTERY_LEVEL),
+                BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16));
+  if (dsc == NULL)
+  {
+    printf("Error: Peer lacks a CCCD for the \
+          Battery Level characteristic\n");
+    goto err;
+  }
+
+  value[0] = 1;
+  value[1] = 0;
+  rc = ble_gattc_write_flat(peer->conn_handle, dsc->dsc.handle,
+                            value, sizeof value, blecent_on_subscribe, NULL);
+  if (rc != 0)
+  {
+    printf("Error: Failed to subscribe to characteristic; "
+           "rc=%d\n",
+           rc);
+    goto err;
+  }
+
+  return;
+
+err:
+
+  /* Terminate the connection. */
+
+  ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+}
+
+/****************************************************************************
+ * Name: blecent_on_disc_complete
+ ****************************************************************************/
+
+static void
+blecent_on_disc_complete(FAR const struct peer *peer, int status,
+                         FAR void *arg)
+{
+  if (status != 0)
+    {
+      /* Service discovery failed.  Terminate the connection. */
+
+      printf("Error: Service discovery failed; status=%d "
+            "conn_handle=%d\n",
+            status, peer->conn_handle);
+      ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+      return;
+    }
+
+  /* Service discovery has completed successfully.  Now we have a complete
+   * list of services, characteristics, and descriptors that the peer
+   * supports.
+   */
+
+  printf("Service discovery complete; status=%d "
+         "conn_handle=%d\n",
+         status, peer->conn_handle);
+
+  /* Now perform three concurrent GATT procedures against the peer: read,
+   * write, and subscribe to notifications.
+   */
+
+  blecent_read_write_subscribe(peer);
+}
+
+/****************************************************************************
+ * Name: blecent_scan
+ ****************************************************************************/
+
+static void blecent_scan(void)
+{
+  uint8_t own_addr_type;
+  struct ble_gap_disc_params disc_params;
+  int rc;
+
+  /* Figure out address to use while advertising (no privacy for now) */
+
+  rc = ble_hs_id_infer_auto(0, &own_addr_type);
+  if (rc != 0)
+    {
+      printf("error determining address type; rc=%d\n", rc);
+      return;
+    }
+
+  /* Tell the controller to filter duplicates; we don't want to process
+   * repeated advertisements from the same device.
+   */
+
+  disc_params.filter_duplicates = 1;
+
+  /**
+   * Perform a passive scan.  I.e., don't send follow-up scan requests to
+   * each advertiser.
+   */
+
+  disc_params.passive = 1;
+
+  /* Use defaults for the rest of the parameters. */
+
+  disc_params.itvl = 0;
+  disc_params.window = 0;
+  disc_params.filter_policy = 0;
+  disc_params.limited = 0;
+
+  rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params,
+                    blecent_gap_event, NULL);
+  if (rc != 0)
+  {
+    printf("Error initiating GAP discovery procedure; rc=%d\n",
+           rc);
+  }
+}
+
+/****************************************************************************
+ * Name: blecent_should_connect
+ ****************************************************************************/
+
+static int
+blecent_should_connect(const struct ble_gap_disc_desc *disc)
+{
+  struct ble_hs_adv_fields fields;
+  int rc;
+  int i;
+
+  /* The device has to be advertising connectability. */
+
+  if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND &&
+      disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND)
+    {
+      return 0;
+    }
+
+  rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data);
+  if (rc != 0)
+    {
+      return 0;
+    }
+
+  /* The device has to advertise support for the Alert Notification
+   * service (0x1811).
+   */
+
+  for (i = 0; i < fields.num_uuids16; i++)
+    {
+      if (ble_uuid_u16(&fields.uuids16[i].u) == BLECENT_SVC_ALERT_UUID)
+        {
+          return 1;
+        }
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: blecent_connect_if_interesting
+ ****************************************************************************/
+
+static void
+blecent_connect_if_interesting(FAR const struct ble_gap_disc_desc *disc)
+{
+  uint8_t own_addr_type;
+  int rc;
+
+  /* Don't do anything if we don't care about this advertiser. */
+
+  if (!blecent_should_connect(disc))
+  {
+    return;
+  }
+
+  /* Scanning must be stopped before a connection can be initiated. */
+
+  rc = ble_gap_disc_cancel();
+  if (rc != 0)
+  {
+    printf("Failed to cancel scan; rc=%d\n", rc);
+    return;
+  }
+
+  /* Figure out address to use for connect (no privacy for now) */
+
+  rc = ble_hs_id_infer_auto(0, &own_addr_type);
+  if (rc != 0)
+  {
+    printf("error determining address type; rc=%d\n", rc);
+    return;
+  }
+
+  /* Try to connect the the advertiser.  Allow 30 seconds (30000 ms) for
+   * timeout.
+   */
+
+  rc = ble_gap_connect(own_addr_type, &disc->addr, 30000, NULL,
+                       blecent_gap_event, NULL);
+  if (rc != 0)
+  {
+    printf("Error: Failed to connect to device; addr_type=%d "
+           "addr=%s\n; rc=%d",
+           disc->addr.type, addr_str(disc->addr.val), rc);
+    return;
+  }
+}
+
+/****************************************************************************
+ * Name: blecent_gap_event
+ ****************************************************************************/
+
+static int
+blecent_gap_event(FAR struct ble_gap_event *event, FAR void *arg)
+{
+  struct ble_gap_conn_desc desc;
+  struct ble_hs_adv_fields fields;
+  int rc;
+
+  switch (event->type)
+  {
+  case BLE_GAP_EVENT_DISC:
+    rc = ble_hs_adv_parse_fields(&fields, event->disc.data,
+                                 event->disc.length_data);
+    if (rc != 0)
+    {
+      return 0;
+    }
+
+    /* An advertisment report was received during GAP discovery. */
+
+    print_adv_fields(&fields);
+
+    /* Try to connect to the advertiser if it looks interesting. */
+
+    blecent_connect_if_interesting(&event->disc);
+    return 0;
+
+  case BLE_GAP_EVENT_CONNECT:
+
+    /* A new connection was established or a connection attempt failed. */
+
+    if (event->connect.status == 0)
+    {
+      /* Connection successfully established. */
+
+      printf("Connection established ");
+
+      rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
+      assert(rc == 0);
+      print_conn_desc(&desc);
+      printf("\n");
+
+      /* Remember peer. */
+
+      rc = peer_add(event->connect.conn_handle);
+      if (rc != 0)
+      {
+        printf("Failed to add peer; rc=%d\n", rc);
+        return 0;
+      }
+
+      /* Perform service discovery. */
+
+      rc = peer_disc_all(event->connect.conn_handle,
+                         blecent_on_disc_complete, NULL);
+      if (rc != 0)
+      {
+        printf("Failed to discover services; rc=%d\n", rc);
+        return 0;
+      }
+    }
+    else
+    {
+      /* Connection attempt failed; resume scanning. */
+
+      printf("Error: Connection failed; status=%d\n",
+             event->connect.status);
+      blecent_scan();
+    }
+
+    return 0;
+
+  case BLE_GAP_EVENT_DISCONNECT:
+
+    /* Connection terminated. */
+
+    printf("disconnect; reason=%d ", event->disconnect.reason);
+    print_conn_desc(&event->disconnect.conn);
+    printf("\n");
+
+    /* Forget about peer. */
+
+    peer_delete(event->disconnect.conn.conn_handle);
+
+    /* Resume scanning. */
+
+    blecent_scan();
+    return 0;
+
+  case BLE_GAP_EVENT_DISC_COMPLETE:
+    printf("discovery complete; reason=%d\n",
+           event->disc_complete.reason);
+    return 0;
+
+  case BLE_GAP_EVENT_ENC_CHANGE:
+
+    /* Encryption has been enabled or disabled for this connection. */
+
+    printf("encryption change event; status=%d ",
+           event->enc_change.status);
+    rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
+    assert(rc == 0);
+    print_conn_desc(&desc);
+    return 0;
+
+  case BLE_GAP_EVENT_NOTIFY_RX:
+
+    /* Peer sent us a notification or indication. */
+
+    printf("received %s; conn_handle=%d attr_handle=%d "
+           "attr_len=%d\n",
+           event->notify_rx.indication ? "indication" : "notification",
+           event->notify_rx.conn_handle,
+           event->notify_rx.attr_handle,
+           OS_MBUF_PKTLEN(event->notify_rx.om));
+
+    uint8_t notif_data[100]; /* Size depending on the actual size of the notification data you have */
+    uint16_t notif_len;
+    int offset = 0;
+    notif_len = OS_MBUF_PKTLEN(event->notify_rx.om);
+    os_mbuf_copydata(event->notify_rx.om, offset, notif_len, notif_data);
+
+    printf("notification data: ");
+    for (int i = 0; i < notif_len; i++)
+    {
+      printf("%02d ", notif_data[i]);
+    }
+    printf("\n");
+
+    /* Attribute data is contained in event->notify_rx.attr_data. */
+
+    return 0;
+
+  case BLE_GAP_EVENT_MTU:
+    printf("mtu update event; conn_handle=%d cid=%d mtu=%d\n",
+           event->mtu.conn_handle,
+           event->mtu.channel_id,
+           event->mtu.value);
+    return 0;
+
+  case BLE_GAP_EVENT_REPEAT_PAIRING:
+    /* We already have a bond with the peer, but it is attempting to
+     * establish a new secure link.  This app sacrifices security for
+     * convenience: just throw away the old bond and accept the new link.
+     */
+
+    /* Delete the old bond. */
+
+    rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
+    assert(rc == 0);
+    ble_store_util_delete_peer(&desc.peer_id_addr);
+
+    /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
+     * continue with the pairing operation.
+     */
+
+    return BLE_GAP_REPEAT_PAIRING_RETRY;
+
+  default:
+    return 0;
+  }
+}
+
+static void
+blecent_on_reset(int reason)
+{
+  printf("Resetting state; reason=%d\n", reason);
+}
+
+/****************************************************************************
+ * Name: blecent_on_sync
+ ****************************************************************************/
+
+static void
+blecent_on_sync(void)
+{
+  int rc;
+
+  /* Make sure we have proper identity address set (public preferred) */
+
+  rc = ble_hs_util_ensure_addr(0);
+  assert(rc == 0);
+
+  /* Begin scanning for a peripheral to connect to. */
+
+  blecent_scan();
+}
+
+/****************************************************************************
+ * Name: nimble_host_task
+ ****************************************************************************/
+
+static void nimble_host_task(FAR void *param)
+{
+  ble_svc_gap_device_name_set("NuttX NimBLE CENT");
+  nimble_port_run();
+}
+
+/****************************************************************************
+ * Name: ble_hci_sock_task
+ ****************************************************************************/
+
+static FAR void *ble_hci_sock_task(FAR void *param)
+{
+  ble_hci_sock_ack_handler(param);
+  return NULL;
+}
+
+/****************************************************************************
+ * Name: ble_host_task
+ ****************************************************************************/
+
+static FAR void *ble_host_task(FAR void *param)
+{
+  nimble_host_task(param);
+  return NULL;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nimble_main
+ ****************************************************************************/
+
+int main(int argc, FAR char **argv)
+{
+  struct ble_npl_task s_task_host;
+  struct ble_npl_task s_task_hci;
+  int ret = 0;
+
+  /* allow to specify custom hci */
+
+  if (argc > 1)
+    {
+      ble_hci_sock_set_device(atoi(argv[1]));
+    }
+
+#ifndef CONFIG_NSH_ARCHINIT
+  /* Perform architecture-specific initialization */
+
+  boardctl(BOARDIOC_INIT, 0);
+#endif
+
+#ifndef CONFIG_NSH_NETINIT
+  /* Bring up the network */
+
+  netinit_bringup();
+#endif
+
+  nimble_port_init();
+
+  /* Configure the host. */
+
+  ble_hs_cfg.reset_cb = blecent_on_reset;
+  ble_hs_cfg.sync_cb = blecent_on_sync;
+  ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
+
+  /* Initialize data structures to track connected peers. */
+
+  ret = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
+  assert(ret == 0);
+
+  /* Create task which handles HCI socket */
+
+  ret = ble_npl_task_init(&s_task_hci, "hci_sock", ble_hci_sock_task,
+                          NULL, TASK_DEFAULT_PRIORITY, BLE_NPL_TIME_FOREVER,
+                          TASK_DEFAULT_STACK, TASK_DEFAULT_STACK_SIZE);
+  if (ret != 0)
+    {
+      printf("ERROR: starting hci task: %i\n", ret);
+    }
+
+  /* Create task which handles default event queue for host stack. */
+
+  ret = ble_npl_task_init(&s_task_host, "ble_host", ble_host_task,
+                          NULL, TASK_DEFAULT_PRIORITY, BLE_NPL_TIME_FOREVER,
+                          TASK_DEFAULT_STACK, TASK_DEFAULT_STACK_SIZE);
+  if (ret != 0)
+    {
+      printf("ERROR: starting ble task: %i\n", ret);
+    }
+
+  while (true)
+    {
+      sleep(1);
+    }
+
+  return 0;
+}
diff --git a/examples/nimble_blecent/peer.c b/examples/nimble_blecent/peer.c
new file mode 100644
index 00000000000..3ed07729040
--- /dev/null
+++ b/examples/nimble_blecent/peer.c
@@ -0,0 +1,933 @@
+/****************************************************************************
+ * apps/examples/nimble_blecent/peer.c
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <assert.h>
+#include <string.h>
+#include "host/ble_hs.h"
+#include "blecent.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(FAR struct peer *peer, uint16_t attr_handle);
+static struct peer_svc *
+peer_svc_find(FAR 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(FAR const struct peer_svc *svc,
+              FAR const struct peer_chr *chr);
+int chr_is_empty(FAR const struct peer_svc *svc,
+                 FAR const struct peer_chr *chr);
+static struct peer_chr *
+peer_chr_find(FAR const struct peer_svc *svc, uint16_t chr_def_handle,
+              FAR struct peer_chr **out_prev);
+static void
+peer_disc_chrs(FAR struct peer *peer);
+
+static int
+peer_dsc_disced(uint16_t conn_handle, FAR const struct ble_gatt_error *error,
+                uint16_t chr_val_handle, FAR const struct ble_gatt_dsc *dsc,
+                FAR void *arg);
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+static 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(FAR 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(FAR 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(FAR const struct peer_chr *chr, uint16_t dsc_handle,
+              FAR 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(FAR struct peer *peer, uint16_t chr_val_handle,
+             FAR 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(FAR 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, FAR const struct ble_gatt_error *error,
+                uint16_t chr_val_handle, FAR const struct ble_gatt_dsc *dsc,
+                FAR 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(FAR const struct peer_svc *svc,
+               FAR 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(FAR const struct peer_svc *svc,
+                 FAR 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(FAR const struct peer_svc *svc, uint16_t chr_val_handle,
+              FAR 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(FAR 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(FAR struct peer *peer, uint16_t svc_start_handle,
+             FAR 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, FAR const struct ble_gatt_error *error,
+                FAR const struct ble_gatt_chr *chr, FAR 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(FAR 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(FAR const struct peer_svc *svc)
+{
+  return svc->svc.end_handle <= svc->svc.start_handle;
+}
+
+static struct peer_svc *
+peer_svc_find_prev(FAR 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(FAR struct peer *peer, uint16_t svc_start_handle,
+              FAR 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(FAR 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(FAR const struct peer *peer, FAR 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(FAR const struct peer *peer,
+                   FAR const ble_uuid_t *svc_uuid,
+                   FAR 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(FAR const struct peer *peer,
+                   FAR const ble_uuid_t *svc_uuid,
+                   FAR const ble_uuid_t *chr_uuid,
+                   FAR 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(FAR struct peer *peer, FAR 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(FAR 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, FAR const struct ble_gatt_error *error,
+                FAR const struct ble_gatt_svc *service, FAR 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, FAR peer_disc_fn *disc_cb,
+                  FAR 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;
+}
diff --git a/examples/nimble_bleprph/CMakeLists.txt b/examples/nimble_bleprph/CMakeLists.txt
new file mode 100644
index 00000000000..1213cc279bc
--- /dev/null
+++ b/examples/nimble_bleprph/CMakeLists.txt
@@ -0,0 +1,31 @@
+# ##############################################################################
+# apps/examples/nimble_bleprph/CMakeLists.txt
+#
+# 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_EXAMPLES_NIMBLE_BLEPRPH)
+  nuttx_add_application(
+    NAME
+    nimble
+    SRCS
+    ${CMAKE_CURRENT_LIST_DIR}/nimble_bleprph_main.c
+    ${CMAKE_CURRENT_LIST_DIR}/misc.c
+    ${CMAKE_CURRENT_LIST_DIR}/gatt_svr.c
+    DEPENDS
+    nimble)
+endif()
diff --git a/examples/nimble_bleprph/Kconfig b/examples/nimble_bleprph/Kconfig
new file mode 100644
index 00000000000..7ee88ea6a47
--- /dev/null
+++ b/examples/nimble_bleprph/Kconfig
@@ -0,0 +1,30 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+config EXAMPLES_NIMBLE_BLEPRPH
+	tristate "NimBLE peripheral"
+	default n
+	---help---
+		Enable the nimble peripheral
+
+if EXAMPLES_NIMBLE_BLEPRPH
+
+config EXAMPLES_NIMBLE_BLEPRPH_PROGNAME
+	string "Program name"
+	default "nimble"
+	---help---
+		This is the name of the program that will be used when the NSH ELF
+		program is installed.
+
+config EXAMPLES_NIMBLE_BLEPRPH_PRIORITY
+	int "NimBLE task priority"
+	default 100
+
+config EXAMPLES_NIMBLE_BLEPRPH_STACKSIZE
+	int "NimBLE stack size"
+	default DEFAULT_TASK_STACKSIZE
+
+endif # EXAMPLES_NIMBLE_BLEPRPH
+
diff --git a/examples/nimble_bleprph/Make.defs b/examples/nimble_bleprph/Make.defs
new file mode 100644
index 00000000000..af5e367d96b
--- /dev/null
+++ b/examples/nimble_bleprph/Make.defs
@@ -0,0 +1,23 @@
+############################################################################
+# apps/examples/nimble_bleprph/Make.defs
+#
+# 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.
+#
+############################################################################
+
+ifneq ($(CONFIG_EXAMPLES_NIMBLE_BLEPRPH),)
+CONFIGURED_APPS += $(APPDIR)/examples/nimble
+endif
diff --git a/examples/nimble_bleprph/Makefile b/examples/nimble_bleprph/Makefile
new file mode 100644
index 00000000000..7e4e76dd756
--- /dev/null
+++ b/examples/nimble_bleprph/Makefile
@@ -0,0 +1,38 @@
+############################################################################
+# apps/examples/nimble_bleprph/Makefile
+#
+# 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.
+#
+############################################################################
+
+include $(APPDIR)/Make.defs
+
+# NimBLE built-in application info
+
+PROGNAME  = $(CONFIG_EXAMPLES_NIMBLE_BLEPRPH_PROGNAME)
+PRIORITY  = $(CONFIG_EXAMPLES_NIMBLE_BLEPRPH_PRIORITY)
+STACKSIZE = $(CONFIG_EXAMPLES_NIMBLE_BLEPRPH_STACKSIZE)
+MODULE    = $(CONFIG_EXAMPLES_NIMBLE_BLEPRPH)
+
+# NimBLE Peripheral Example
+
+CSRCS += misc.c gatt_svr.c
+MAINSRC = nimble_bleprph_main.c
+
+include $(APPDIR)/wireless/bluetooth/nimble/Makefile.nimble
+
+include $(APPDIR)/Application.mk
+
diff --git a/examples/nimble_bleprph/bleprph.h b/examples/nimble_bleprph/bleprph.h
new file mode 100644
index 00000000000..43995c58942
--- /dev/null
+++ b/examples/nimble_bleprph/bleprph.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+ * apps/examples/nimble_bleprph/bleprph.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef H_BLEPRPH_
+#define H_BLEPRPH_
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdbool.h>
+#include <stdio.h>
+#include "nimble/ble.h"
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+struct ble_hs_cfg;
+struct ble_gatt_register_ctxt;
+
+/* GATT server. */
+
+#define GATT_SVR_SVC_ALERT_UUID               0x1811
+#define GATT_SVR_CHR_SUP_NEW_ALERT_CAT_UUID   0x2A47
+#define GATT_SVR_CHR_NEW_ALERT                0x2A46
+#define GATT_SVR_CHR_SUP_UNR_ALERT_CAT_UUID   0x2A48
+#define GATT_SVR_CHR_UNR_ALERT_STAT_UUID      0x2A45
+#define GATT_SVR_CHR_ALERT_NOT_CTRL_PT        0x2A44
+
+void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
+int gatt_svr_init(void);
+
+/* Misc. */
+
+void print_bytes(const uint8_t *bytes, int len);
+void print_addr(const void *addr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/examples/nimble_bleprph/gatt_svr.c b/examples/nimble_bleprph/gatt_svr.c
new file mode 100644
index 00000000000..fb03d1943fc
--- /dev/null
+++ b/examples/nimble_bleprph/gatt_svr.c
@@ -0,0 +1,238 @@
+/****************************************************************************
+ * apps/examples/nimble_bleprph/gatt_svr.c
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include "host/ble_hs.h"
+#include "host/ble_uuid.h"
+#include "services/ans/ble_svc_ans.h"
+#include "services/bas/ble_svc_bas.h"
+#include "bleprph.h"
+
+/**
+ * The vendor specific security test service consists of two characteristics:
+ *     o random-number-generator: generates a random 32-bit number each time
+ *       it is read.  This characteristic can only be read over an encrypted
+ *       connection.
+ *     o static-value: a single-byte characteristic that can always be read,
+ *       but can only be written over an encrypted connection.
+ */
+
+/* 59462f12-9543-9999-12c8-58b459a2712d */
+
+static const ble_uuid128_t gatt_svr_svc_sec_test_uuid =
+    BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12,
+                     0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59);
+
+/* 5c3a659e-897e-45e1-b016-007107c96df6 */
+
+static const ble_uuid128_t gatt_svr_chr_sec_test_rand_uuid =
+    BLE_UUID128_INIT(0xf6, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0,
+                     0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c);
+
+/* 5c3a659e-897e-45e1-b016-007107c96df7 */
+
+static const ble_uuid128_t gatt_svr_chr_sec_test_static_uuid =
+    BLE_UUID128_INIT(0xf7, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0,
+                     0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c);
+
+static uint8_t gatt_svr_sec_test_static_val;
+
+static int
+gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle,
+                             struct ble_gatt_access_ctxt *ctxt,
+                             void *arg);
+
+static const struct ble_gatt_svc_def gatt_svr_svcs[] =
+{
+  {
+      /* Service: Security test. */
+
+      .type = BLE_GATT_SVC_TYPE_PRIMARY,
+      .uuid = &gatt_svr_svc_sec_test_uuid.u,
+      .characteristics = (struct ble_gatt_chr_def[])
+      {
+        {
+            /* Characteristic: Random number generator. */
+
+            .uuid = &gatt_svr_chr_sec_test_rand_uuid.u,
+            .access_cb = gatt_svr_chr_access_sec_test,
+            .flags = BLE_GATT_CHR_F_READ,
+        },
+        {
+            /* Characteristic: Static value. */
+
+            .uuid = &gatt_svr_chr_sec_test_static_uuid.u,
+            .access_cb = gatt_svr_chr_access_sec_test,
+            .flags = BLE_GATT_CHR_F_READ |
+                    BLE_GATT_CHR_F_WRITE,
+        },
+        {
+            0, /* No more characteristics in this service. */
+        }
+      },
+  },
+
+  {
+      0, /* No more services. */
+  },
+};
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+static int
+gatt_svr_chr_write(FAR struct os_mbuf *om, uint16_t min_len,
+                   uint16_t max_len, FAR void *dst, FAR uint16_t *len)
+{
+  uint16_t om_len;
+  int rc;
+
+  om_len = OS_MBUF_PKTLEN(om);
+  if (om_len < min_len || om_len > max_len)
+    {
+      return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+    }
+
+  rc = ble_hs_mbuf_to_flat(om, dst, max_len, len);
+  if (rc != 0)
+    {
+      return BLE_ATT_ERR_UNLIKELY;
+    }
+
+  return 0;
+}
+
+static int
+gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle,
+                             FAR struct ble_gatt_access_ctxt *ctxt,
+                             FAR void *arg)
+{
+  const ble_uuid_t *uuid;
+  int rand_num;
+  int rc;
+
+  uuid = ctxt->chr->uuid;
+
+  /* Determine which characteristic is being accessed by examining its
+   * 128-bit UUID.
+   */
+
+  if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_rand_uuid.u) == 0)
+    {
+      assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR);
+
+      /* Respond with a 32-bit random number. */
+
+      rand_num = rand();
+      rc = os_mbuf_append(ctxt->om, &rand_num, sizeof rand_num);
+      return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+    }
+
+  if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_static_uuid.u) == 0)
+    {
+      switch (ctxt->op)
+        {
+        case BLE_GATT_ACCESS_OP_READ_CHR:
+          rc = os_mbuf_append(ctxt->om, &gatt_svr_sec_test_static_val,
+                              sizeof gatt_svr_sec_test_static_val);
+          return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+
+        case BLE_GATT_ACCESS_OP_WRITE_CHR:
+          rc = gatt_svr_chr_write(ctxt->om,
+                                  sizeof gatt_svr_sec_test_static_val,
+                                  sizeof gatt_svr_sec_test_static_val,
+                                  &gatt_svr_sec_test_static_val, NULL);
+          return rc;
+
+        default:
+          assert(0);
+          return BLE_ATT_ERR_UNLIKELY;
+        }
+    }
+
+  /* Unknown characteristic; the nimble stack should not have called this
+   * function.
+   */
+
+  assert(0);
+  return BLE_ATT_ERR_UNLIKELY;
+}
+
+void gatt_svr_register_cb(FAR struct ble_gatt_register_ctxt *ctxt,
+                          FAR void *arg)
+{
+  char buf[BLE_UUID_STR_LEN];
+
+  switch (ctxt->op)
+  {
+  case BLE_GATT_REGISTER_OP_SVC:
+    printf("registered service %s with handle=%d\n",
+           ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
+           ctxt->svc.handle);
+    break;
+
+  case BLE_GATT_REGISTER_OP_CHR:
+    printf("registering characteristic %s with "
+           "def_handle=%d val_handle=%d\n",
+           ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
+           ctxt->chr.def_handle,
+           ctxt->chr.val_handle);
+    break;
+
+  case BLE_GATT_REGISTER_OP_DSC:
+    printf("registering descriptor %s with handle=%d\n",
+           ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
+           ctxt->dsc.handle);
+    break;
+
+  default:
+    assert(0);
+    break;
+  }
+}
+
+int gatt_svr_init(void)
+{
+  int rc;
+
+  ble_svc_ans_init();
+  ble_svc_bas_init();
+
+  rc = ble_gatts_count_cfg(gatt_svr_svcs);
+  if (rc != 0)
+    {
+      return rc;
+    }
+
+  rc = ble_gatts_add_svcs(gatt_svr_svcs);
+  if (rc != 0)
+    {
+      return rc;
+    }
+
+  return 0;
+}
diff --git a/examples/nimble_bleprph/misc.c b/examples/nimble_bleprph/misc.c
new file mode 100644
index 00000000000..2a0b40a9297
--- /dev/null
+++ b/examples/nimble_bleprph/misc.c
@@ -0,0 +1,48 @@
+/****************************************************************************
+ * apps/examples/nimble_bleprph/misc.c
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include "bleprph.h"
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+void print_bytes(FAR const uint8_t *bytes, int len)
+{
+  int i;
+
+  for (i = 0; i < len; i++)
+    {
+      printf("%s0x%02x", i != 0 ? ":" : "", bytes[i]);
+    }
+}
+
+void print_addr(FAR const void *addr)
+{
+  const uint8_t *u8p;
+
+  u8p = addr;
+  printf("%02x:%02x:%02x:%02x:%02x:%02x",
+         u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
+}
diff --git a/examples/nimble_bleprph/nimble_bleprph_main.c b/examples/nimble_bleprph/nimble_bleprph_main.c
new file mode 100644
index 00000000000..ad432daa86f
--- /dev/null
+++ b/examples/nimble_bleprph/nimble_bleprph_main.c
@@ -0,0 +1,440 @@
+/****************************************************************************
+ * apps/examples/nimble_bleprph/nimble_bleprph_main.c
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+/* BLE */
+
+#include "nimble/ble.h"
+#include "nimble/nimble_port.h"
+#include "host/ble_hs.h"
+#include "host/util/util.h"
+#include "services/gap/ble_svc_gap.h"
+#include "services/bas/ble_svc_bas.h"
+
+/* Application-specified header. */
+
+#include "bleprph.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Not used now */
+
+#define TASK_DEFAULT_PRIORITY CONFIG_EXAMPLES_NIMBLE_BLEPRPH_PRIORITY
+#define TASK_DEFAULT_STACK NULL
+#define TASK_DEFAULT_STACK_SIZE 0
+
+/****************************************************************************
+ * External Functions Prototypes
+ ****************************************************************************/
+
+void ble_hci_sock_ack_handler(FAR void *param);
+void ble_hci_sock_set_device(int dev);
+
+/****************************************************************************
+ * Private Functions Prototypes
+ ****************************************************************************/
+
+static void bleprph_advertise(void);
+static void nimble_host_task(FAR void *param);
+static FAR void *ble_hci_sock_task(FAR void *param);
+static FAR void *ble_host_task(FAR void *param);
+static int bleprph_gap_event(FAR struct ble_gap_event *event, FAR void *arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: bleprph_print_conn_desc
+ ****************************************************************************/
+
+static void
+bleprph_print_conn_desc(FAR struct ble_gap_conn_desc *desc)
+{
+  printf("handle=%d our_ota_addr_type=%d our_ota_addr=",
+         desc->conn_handle, desc->our_ota_addr.type);
+  print_addr(desc->our_ota_addr.val);
+  printf(" our_id_addr_type=%d our_id_addr=",
+         desc->our_id_addr.type);
+  print_addr(desc->our_id_addr.val);
+  printf(" peer_ota_addr_type=%d peer_ota_addr=",
+         desc->peer_ota_addr.type);
+  print_addr(desc->peer_ota_addr.val);
+  printf(" peer_id_addr_type=%d peer_id_addr=",
+         desc->peer_id_addr.type);
+  print_addr(desc->peer_id_addr.val);
+  printf(" conn_itvl=%d conn_latency=%d supervision_timeout=%d "
+         "encrypted=%d authenticated=%d bonded=%d\n",
+         desc->conn_itvl, desc->conn_latency,
+         desc->supervision_timeout,
+         desc->sec_state.encrypted,
+         desc->sec_state.authenticated,
+         desc->sec_state.bonded);
+}
+
+/****************************************************************************
+ * Name: bleprph_advertise
+ ****************************************************************************/
+
+static void bleprph_advertise(void)
+{
+  uint8_t own_addr_type;
+  struct ble_gap_adv_params adv_params;
+  struct ble_hs_adv_fields fields;
+  const char *name;
+  int rc;
+
+  /* Figure out address to use while advertising (no privacy for now) */
+
+  rc = ble_hs_id_infer_auto(0, &own_addr_type);
+  if (rc != 0)
+  {
+    printf("error determining address type; rc=%d\n", rc);
+    return;
+  }
+
+  /**
+   *  Set the advertisement data included in our advertisements:
+   *     o Flags (indicates advertisement type and other general info).
+   *     o Advertising tx power.
+   *     o Device name.
+   *     o 16-bit service UUIDs (alert notifications).
+   */
+
+  memset(&fields, 0, sizeof fields);
+
+  /* Advertise two flags:
+   *     o Discoverability in forthcoming advertisement (general)
+   *     o BLE-only (BR/EDR unsupported).
+   */
+
+  fields.flags = BLE_HS_ADV_F_DISC_GEN |
+                 BLE_HS_ADV_F_BREDR_UNSUP;
+
+  /* Indicate that the TX power level field should be included; have the
+   * stack fill this value automatically.  This is done by assiging the
+   * special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
+   */
+
+  fields.tx_pwr_lvl_is_present = 1;
+  fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
+
+  name = ble_svc_gap_device_name();
+  fields.name = (uint8_t *)name;
+  fields.name_len = strlen(name);
+  fields.name_is_complete = 1;
+
+  fields.uuids16 = (ble_uuid16_t[])
+  {
+      BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID)
+  };
+  fields.num_uuids16 = 1;
+  fields.uuids16_is_complete = 1;
+
+  rc = ble_gap_adv_set_fields(&fields);
+  if (rc != 0)
+  {
+    printf("error setting advertisement data; rc=%d\n", rc);
+    return;
+  }
+
+  /* Begin advertising. */
+
+  memset(&adv_params, 0, sizeof adv_params);
+  adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
+  adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
+  rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
+                         &adv_params, bleprph_gap_event, NULL);
+  if (rc != 0)
+  {
+    printf("error enabling advertisement; rc=%d\n", rc);
+    return;
+  }
+}
+
+/****************************************************************************
+ * Name: bleprph_gap_event
+ ****************************************************************************/
+
+static int
+bleprph_gap_event(FAR struct ble_gap_event *event, FAR void *arg)
+{
+  struct ble_gap_conn_desc desc;
+  int rc;
+
+  switch (event->type)
+  {
+  case BLE_GAP_EVENT_CONNECT:
+
+    /* A new connection was established or a connection attempt failed. */
+
+    printf("connection %s; status=%d ",
+           event->connect.status == 0 ? "established" : "failed",
+           event->connect.status);
+    if (event->connect.status == 0)
+    {
+      rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
+      assert(rc == 0);
+      bleprph_print_conn_desc(&desc);
+    }
+    printf("\n");
+
+    if (event->connect.status != 0)
+    {
+      /* Connection failed; resume advertising. */
+
+      bleprph_advertise();
+    }
+    return 0;
+
+  case BLE_GAP_EVENT_DISCONNECT:
+    printf("disconnect; reason=%d ", event->disconnect.reason);
+    bleprph_print_conn_desc(&event->disconnect.conn);
+    printf("\n");
+
+    /* Connection terminated; resume advertising. */
+
+    bleprph_advertise();
+    return 0;
+
+  case BLE_GAP_EVENT_CONN_UPDATE:
+
+    /* The central has updated the connection parameters. */
+
+    printf("connection updated; status=%d ",
+           event->conn_update.status);
+    rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
+    assert(rc == 0);
+    bleprph_print_conn_desc(&desc);
+    printf("\n");
+    return 0;
+
+  case BLE_GAP_EVENT_ADV_COMPLETE:
+    printf("advertise complete; reason=%d",
+           event->adv_complete.reason);
+    bleprph_advertise();
+    return 0;
+
+  case BLE_GAP_EVENT_ENC_CHANGE:
+
+    /* Encryption has been enabled or disabled for this connection. */
+
+    printf("encryption change event; status=%d ",
+           event->enc_change.status);
+    rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
+    assert(rc == 0);
+    bleprph_print_conn_desc(&desc);
+    printf("\n");
+    return 0;
+
+  case BLE_GAP_EVENT_SUBSCRIBE:
+    printf("subscribe event; conn_handle=%d attr_handle=%d "
+           "reason=%d prevn=%d curn=%d previ=%d curi=%d\n",
+           event->subscribe.conn_handle,
+           event->subscribe.attr_handle,
+           event->subscribe.reason,
+           event->subscribe.prev_notify,
+           event->subscribe.cur_notify,
+           event->subscribe.prev_indicate,
+           event->subscribe.cur_indicate);
+    return 0;
+
+  case BLE_GAP_EVENT_MTU:
+    printf("mtu update event; conn_handle=%d cid=%d mtu=%d\n",
+           event->mtu.conn_handle,
+           event->mtu.channel_id,
+           event->mtu.value);
+    return 0;
+
+  case BLE_GAP_EVENT_REPEAT_PAIRING:
+    /* We already have a bond with the peer, but it is attempting to
+     * establish a new secure link.  This app sacrifices security for
+     * convenience: just throw away the old bond and accept the new link.
+     */
+
+    /* Delete the old bond. */
+
+    rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
+    assert(rc == 0);
+    ble_store_util_delete_peer(&desc.peer_id_addr);
+
+    /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
+     * continue with the pairing operation.
+     */
+
+    return BLE_GAP_REPEAT_PAIRING_RETRY;
+  }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: bleprph_on_reset
+ ****************************************************************************/
+
+static void
+bleprph_on_reset(int reason)
+{
+  printf("Resetting state; reason=%d\n", reason);
+}
+
+/****************************************************************************
+ * Name: bleprph_on_sync
+ ****************************************************************************/
+
+static void
+bleprph_on_sync(void)
+{
+  int rc;
+
+  /* Make sure we have proper identity address set (public preferred) */
+
+  rc = ble_hs_util_ensure_addr(0);
+  assert(rc == 0);
+
+  /* Begin advertising. */
+
+  bleprph_advertise();
+}
+
+/****************************************************************************
+ * Name: nimble_host_task
+ ****************************************************************************/
+
+static void nimble_host_task(FAR void *param)
+{
+  ble_svc_gap_device_name_set("NuttX NimBLE PRPH");
+  nimble_port_run();
+}
+
+/****************************************************************************
+ * Name: ble_hci_sock_task
+ ****************************************************************************/
+
+static FAR void *ble_hci_sock_task(FAR void *param)
+{
+  ble_hci_sock_ack_handler(param);
+  return NULL;
+}
+
+/****************************************************************************
+ * Name: ble_host_task
+ ****************************************************************************/
+
+static FAR void *ble_host_task(FAR void *param)
+{
+  nimble_host_task(param);
+  return NULL;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nimble_main
+ ****************************************************************************/
+
+int main(int argc, FAR char *argv[])
+{
+  struct ble_npl_task s_task_host;
+  struct ble_npl_task s_task_hci;
+  int ret = 0;
+  uint8_t batt_level = 0;
+
+  /* allow to specify custom hci */
+
+  if (argc > 1)
+    {
+      ble_hci_sock_set_device(atoi(argv[1]));
+    }
+
+#ifndef CONFIG_NSH_ARCHINIT
+  /* Perform architecture-specific initialization */
+
+  boardctl(BOARDIOC_INIT, 0);
+#endif
+
+#ifndef CONFIG_NSH_NETINIT
+  /* Bring up the network */
+
+  netinit_bringup();
+#endif
+
+  nimble_port_init();
+
+  /* Initialize the NimBLE host configuration. */
+
+  ble_hs_cfg.reset_cb = bleprph_on_reset;
+  ble_hs_cfg.sync_cb = bleprph_on_sync;
+  ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
+  ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
+
+  /* Initialize services */
+
+  ret = gatt_svr_init();
+  assert(ret == 0);
+
+  /* Create task which handles HCI socket */
+
+  ret = ble_npl_task_init(&s_task_hci, "hci_sock", ble_hci_sock_task,
+                          NULL, TASK_DEFAULT_PRIORITY, BLE_NPL_TIME_FOREVER,
+                          TASK_DEFAULT_STACK, TASK_DEFAULT_STACK_SIZE);
+  if (ret != 0)
+    {
+      printf("ERROR: starting hci task: %i\n", ret);
+    }
+
+  /* Create task which handles default event queue for host stack. */
+
+  ret = ble_npl_task_init(&s_task_host, "ble_host", ble_host_task,
+                          NULL, TASK_DEFAULT_PRIORITY, BLE_NPL_TIME_FOREVER,
+                          TASK_DEFAULT_STACK, TASK_DEFAULT_STACK_SIZE);
+  if (ret != 0)
+    {
+      printf("ERROR: starting ble task: %i\n", ret);
+    }
+
+  while (true)
+    {
+      /* Simulate battery */
+
+      batt_level += 1;
+      if (batt_level > 100)
+        {
+          batt_level = 0;
+        }
+
+      ble_svc_bas_battery_level_set(batt_level);
+      usleep(100000);
+    }
+
+  return 0;
+}