diff --git a/epicardium/ble/app/app_main.c b/epicardium/ble/app/app_main.c
deleted file mode 100644
index b279c758bb5857eb3d649fe7cc8c36f7e16ef35f..0000000000000000000000000000000000000000
--- a/epicardium/ble/app/app_main.c
+++ /dev/null
@@ -1,427 +0,0 @@
-/*************************************************************************************************/
-/*!
- *  \file
- *
- *  \brief  Application framework main module.
- *
- *  Copyright (c) 2011-2018 Arm Ltd. All Rights Reserved.
- *  ARM Ltd. confidential and proprietary.
- *
- *  IMPORTANT.  Your use of this file is governed by a Software License Agreement
- *  ("Agreement") that must be accepted in order to download or otherwise receive a
- *  copy of this file.  You may not use or copy this file for any purpose other than
- *  as described in the Agreement.  If you do not agree to all of the terms of the
- *  Agreement do not use this file and delete all copies in your possession or control;
- *  if you do not have a copy of the Agreement, you must contact ARM Ltd. prior
- *  to any use, copying or further distribution of this software.
- */
-/*************************************************************************************************/
-
-/* card10:
- * copied from: lib/sdk/Libraries/BTLE/stack/ble-profiles/sources/apps/app/app_main.c
- *
- * Reason: we need to correctly implement AppHandleNumericComparison
- */
-/* clang-format off */
-/* clang-formet turned off for easier diffing against orginal file */
-#include <string.h>
-#include "wsf_types.h"
-#include "wsf_msg.h"
-#include "sec_api.h"
-#include "wsf_trace.h"
-#include "wsf_timer.h"
-#include "wsf_assert.h"
-#include "util/bstream.h"
-#include "dm_api.h"
-#include "app_api.h"
-#include "app_main.h"
-#include "app_ui.h"
-
-#include "modules/log.h"
-
-/**************************************************************************************************
-  Global Variables
-**************************************************************************************************/
-
-/*! Configuration pointer for advertising */
-appAdvCfg_t *pAppAdvCfg;
-
-/*! Configuration pointer for extended and periodic advertising */
-appExtAdvCfg_t *pAppExtAdvCfg;
-
-/*! Configuration pointer for slave */
-appSlaveCfg_t *pAppSlaveCfg;
-
-/*! Configuration pointer for master */
-appMasterCfg_t *pAppMasterCfg;
-
-/*! Configuration pointer for extended master */
-appExtMasterCfg_t *pAppExtMasterCfg;
-
-/*! Configuration pointer for security */
-appSecCfg_t *pAppSecCfg;
-
-/*! Configuration pointer for connection parameter update */
-appUpdateCfg_t *pAppUpdateCfg;
-
-/*! Configuration pointer for discovery */
-appDiscCfg_t *pAppDiscCfg;
-
-/*! Configuration pointer for application */
-appCfg_t *pAppCfg;
-
-/*! Connection control block array */
-appConnCb_t appConnCb[DM_CONN_MAX];
-
-/*! WSF handler ID */
-wsfHandlerId_t appHandlerId;
-
-/*! Main control block */
-appCb_t appCb;
-
-/*! Configuration structure for incoming request actions */
-const appReqActCfg_t appReqActCfg =
-{
-  APP_ACT_ACCEPT        /*! Action for the remote connection parameter request */
-};
-
-/*! Configuration pointer for incoming request actions on master */
-appReqActCfg_t *pAppMasterReqActCfg = (appReqActCfg_t *) &appReqActCfg;
-
-/*! Configurable pointer for incoming request actions on slave */
-appReqActCfg_t *pAppSlaveReqActCfg = (appReqActCfg_t *) &appReqActCfg;
-
-/*************************************************************************************************/
-/*!
- *  \brief  Process messages from the event handler.
- *
- *  \param  pMsg    Pointer to message.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-static void appProcMsg(wsfMsgHdr_t *pMsg)
-{
-  switch(pMsg->event)
-  {
-    case APP_BTN_POLL_IND:
-      appUiBtnPoll();
-      break;
-
-    case APP_UI_TIMER_IND:
-      appUiTimerExpired(pMsg);
-      break;
-
-    default:
-      break;
-  }
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Check the bonded state of a connection.
- *
- *  \param  connId      DM connection ID.
- *
- *  \return Bonded state.
- */
-/*************************************************************************************************/
-bool_t appCheckBonded(dmConnId_t connId)
-{
-  WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
-
-  return appConnCb[connId - 1].bonded;
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Check the bond-by-LTK state of a connection.
- *
- *  \param  connId      DM connection ID.
- *
- *  \return Bond-by-LTK state.
- */
-/*************************************************************************************************/
-bool_t appCheckBondByLtk(dmConnId_t connId)
-{
-  WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));
-
-  return appConnCb[connId - 1].bondByLtk;
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Return the number of existing connections of the given role.
- *
- *  \param  role      Connection role
- *
- *  \return Number of connections.
- */
-/*************************************************************************************************/
-uint8_t appNumConns(uint8_t role)
-{
-  appConnCb_t   *pCcb = appConnCb;
-  uint8_t       i, j;
-
-  for (i = DM_CONN_MAX, j = 0; i > 0; i--, pCcb++)
-  {
-    if ((pCcb->connId != DM_CONN_ID_NONE) && (DmConnRole(pCcb->connId) == role))
-    {
-      j++;
-    }
-  }
-
-  return j;
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  App framework handler init function called during system initialization.
- *
- *  \param  handlerID  WSF handler ID for App.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppInit(void)
-{
-  appHandlerId = WsfOsSetNextHandler(AppHandler);
-
-  AppDbInit();
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  WSF event handler for app framework.
- *
- *  \param  event   WSF event mask.
- *  \param  pMsg    WSF message.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg)
-{
-  if (pMsg != NULL)
-  {
-    APP_TRACE_INFO1("App got evt %d", pMsg->event);
-
-    if (pMsg->event >= APP_MASTER_MSG_START)
-    {
-      /* pass event to master handler */
-      (*appCb.masterCback)(pMsg);
-    }
-    else if (pMsg->event >= APP_SLAVE_MSG_START)
-    {
-      /* pass event to slave handler */
-      (*appCb.slaveCback)(pMsg);
-    }
-    else
-    {
-      appProcMsg(pMsg);
-    }
-  }
-  else
-  {
-    if (event & APP_BTN_DOWN_EVT)
-    {
-      AppUiBtnPressed();
-    }
-  }
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Handle a passkey request during pairing.  If the passkey is to displayed, a
- *          random passkey is generated and displayed.  If the passkey is to be entered
- *          the user is prompted to enter the passkey.
- *
- *  \param  pAuthReq  DM authentication requested event structure.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppHandlePasskey(dmSecAuthReqIndEvt_t *pAuthReq)
-{
-  uint32_t passkey;
-  uint8_t  buf[SMP_PIN_LEN];
-
-  if (pAuthReq->display)
-  {
-    /* generate random passkey, limit to 6 digit max */
-    SecRand((uint8_t *) &passkey, sizeof(uint32_t));
-    passkey %= 1000000;
-
-    /* convert to byte buffer */
-    buf[0] = UINT32_TO_BYTE0(passkey);
-    buf[1] = UINT32_TO_BYTE1(passkey);
-    buf[2] = UINT32_TO_BYTE2(passkey);
-
-    /* send authentication response to DM */
-    DmSecAuthRsp((dmConnId_t) pAuthReq->hdr.param, SMP_PIN_LEN, buf);
-
-    /* display passkey */
-    AppUiDisplayPasskey(passkey);
-  }
-  else
-  {
-    /* prompt user to enter passkey */
-    AppUiAction(APP_UI_PASSKEY_PROMPT);
-  }
-}
-
-/*************************************************************************************************/
-/*!
-*  \brief  Handle a numeric comparison indication during pairing.  The confirmation value is
-*          displayed and the user is prompted to verify that the local and peer confirmation
-*          values match.
-*
-*  \param  pCnfInd  DM confirmation indication event structure.
-*
-*  \return None.
-*/
-/*************************************************************************************************/
-void AppHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd)
-{
-  uint32_t confirm = DmSecGetCompareValue(pCnfInd->confirm);
-
-  LOG_INFO("ble", "Confirm Value: %ld", confirm);
-
-  /* display confirmation value */
-  AppUiDisplayConfirmValue(confirm);
-
-  /* TODO: Verify that local and peer confirmation values match */
-  DmSecCompareRsp((dmConnId_t)pCnfInd->hdr.param, TRUE);
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Close the connection with the give connection identifier.
- *
- *  \param  connId    Connection identifier.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppConnClose(dmConnId_t connId)
-{
-  DmConnClose(DM_CLIENT_ID_APP, connId, HCI_ERR_REMOTE_TERMINATED);
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Get a list of connection identifiers of open connections.
- *
- *  \param  pConnIdList    Buffer to hold connection IDs (must be DM_CONN_MAX bytes).
- *
- *  \return Number of open connections.
- *
- */
-/*************************************************************************************************/
-uint8_t AppConnOpenList(dmConnId_t *pConnIdList)
-{
-  appConnCb_t   *pCcb = appConnCb;
-  uint8_t       i;
-  uint8_t       pos = 0;
-
-  memset(pConnIdList, DM_CONN_ID_NONE, DM_CONN_MAX);
-
-  for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
-  {
-    if (pCcb->connId != DM_CONN_ID_NONE)
-    {
-      pConnIdList[pos++] = pCcb->connId;
-    }
-  }
-
-  return pos;
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Check if a connection is open.
- *
- *  \return Connection ID of open connection or DM_CONN_ID_NONE if no open connections.
- */
-/*************************************************************************************************/
-dmConnId_t AppConnIsOpen(void)
-{
-  appConnCb_t   *pCcb = appConnCb;
-  uint8_t       i;
-
-  for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
-  {
-    if (pCcb->connId != DM_CONN_ID_NONE)
-    {
-      return pCcb->connId;
-    }
-  }
-
-  return DM_CONN_ID_NONE;
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Get the device database record handle associated with an open connection.
- *
- *  \param  connId    Connection identifier.
- *
- *  \return Database record handle or APP_DB_HDL_NONE.
- */
-/*************************************************************************************************/
-appDbHdl_t AppDbGetHdl(dmConnId_t connId)
-{
-  return appConnCb[connId-1].dbHdl;
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Add device to resolving list.
- *
- *  \param  pMsg    Pointer to DM callback event message.
- *  \param  connId  Connection identifier.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppAddDevToResList(dmEvt_t *pMsg, dmConnId_t connId)
-{
-  dmSecKey_t *pPeerKey;
-  appDbHdl_t hdl = appConnCb[connId - 1].dbHdl;
-
-  /* if LL Privacy is supported and the peer device has distributed its IRK */
-  if (HciLlPrivacySupported() && ((pPeerKey = AppDbGetKey(hdl, DM_KEY_IRK, NULL))!= NULL))
-  {
-    /* add peer device to resolving list. If all-zero local or peer IRK is used then
-       LL will only use or accept local or peer identity address respectively. */
-    DmPrivAddDevToResList(pPeerKey->irk.addrType, pPeerKey->irk.bdAddr, pPeerKey->irk.key,
-                          DmSecGetLocalIrk(), TRUE, pMsg->hdr.param);
-  }
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Update privacy mode for a given peer device.
- *
- *  \param  hdl     Database record handle.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppUpdatePrivacyMode(appDbHdl_t hdl)
-{
-  /* if peer device's been added to resolving list but RPA Only attribute not found on peer device */
-  if ((hdl != APP_DB_HDL_NONE) && AppDbGetPeerAddedToRl(hdl) && !AppDbGetPeerRpao(hdl))
-  {
-    dmSecKey_t *pPeerKey = AppDbGetKey(hdl, DM_KEY_IRK, NULL);
-    if (pPeerKey != NULL)
-    {
-      /* set device privacy mode for this peer device */
-      DmPrivSetPrivacyMode(pPeerKey->irk.addrType, pPeerKey->irk.bdAddr, DM_PRIV_MODE_DEVICE);
-
-      /* make sure resolving list flag cleared */
-      AppDbSetPeerAddedToRl(hdl, FALSE);
-    }
-  }
-}
-/* clang-format on */
diff --git a/epicardium/ble/app/common/app_db.c b/epicardium/ble/app/common/app_db.c
index cf054afd206ebd22563dec986f99fdc7254ba359..550077784b4b120f430c8933c4a075660aba438e 100644
--- a/epicardium/ble/app/common/app_db.c
+++ b/epicardium/ble/app/common/app_db.c
@@ -95,12 +95,6 @@ static appDb_t appDb;
 /*! When all records are allocated use this index to determine which to overwrite */
 static appDbRec_t *pAppDbNewRec = appDb.rec;
 
-/* Timer to delay writing to persistent storage until a burst of store() calls has finished */
-static TimerHandle_t store_timer;
-static StaticTimer_t store_timer_buffer;
-static void store_callback();
-#define STORE_DELAY pdMS_TO_TICKS(5000)
-
 /*************************************************************************************************/
 /*!
  *  \brief  Initialize the device database.
@@ -117,32 +111,12 @@ void AppDbInit(void)
         memset(&appDb, 0, sizeof(appDb));
     }
     epic_file_close(fd);
-  }
-
-	store_timer = xTimerCreateStatic(
-                                     "appdb_store_timer",
-                                     STORE_DELAY,
-                                     pdFALSE,
-                                     NULL,
-                                     store_callback,
-                                     &store_timer_buffer
-                                     );
-}
-
-/* TODO this should actually put tasks into a queue. On the other end of the queue */
-/* a worker task can read tasks off the queue and execute them */
-static void store(void)
-{
-  LOG_INFO("appDb", "store() called, resetting timer");
-  if (xTimerReset(store_timer, 10) != pdPASS) {     /* (Re)start the timer */
-    /* Store timer could not be reset, write to persistent storage anyway */
-    store_callback();
   }
 }
 
-static void store_callback()
+static void AppDbStore(void)
 {
-  LOG_INFO("appDb", "STORE_DELAY passed, writing to persistent storage");
+  LOG_INFO("appDb", "writing to persistent storage");
 
   int fd = epic_file_open("pairings.bin", "w");
   if(fd >= 0) {
@@ -198,7 +172,6 @@ appDbHdl_t AppDbNewRecord(uint8_t addrType, uint8_t *pAddr)
   pRec->peerAddedToRl = FALSE;
   pRec->peerRpao = FALSE;
 
-  store();
   return (appDbHdl_t) pRec;
 }
 
@@ -263,7 +236,6 @@ appDbHdl_t AppDbGetNextRecord(appDbHdl_t hdl)
 void AppDbDeleteRecord(appDbHdl_t hdl)
 {
   ((appDbRec_t *) hdl)->inUse = FALSE;
-  store();
 }
 
 /*************************************************************************************************/
@@ -281,7 +253,7 @@ void AppDbValidateRecord(appDbHdl_t hdl, uint8_t keyMask)
 {
   ((appDbRec_t *) hdl)->valid = TRUE;
   ((appDbRec_t *) hdl)->keyValidMask = keyMask;
-  store();
+  AppDbStore();
 }
 
 /*************************************************************************************************/
@@ -371,7 +343,6 @@ void AppDbDeleteAllRecords(void)
   {
     pRec->inUse = FALSE;
   }
-  store();
 }
 
 /*************************************************************************************************/
@@ -518,7 +489,6 @@ void AppDbSetKey(appDbHdl_t hdl, dmSecKeyIndEvt_t *pKey)
     default:
       break;
   }
-  store();
 }
 
 /*************************************************************************************************/
@@ -551,7 +521,6 @@ void AppDbSetCccTblValue(appDbHdl_t hdl, uint16_t idx, uint16_t value)
   WSF_ASSERT(idx < APP_DB_NUM_CCCD);
 
   ((appDbRec_t *) hdl)->cccTbl[idx] = value;
-  store();
 }
 
 /*************************************************************************************************/
@@ -581,7 +550,6 @@ uint8_t AppDbGetDiscStatus(appDbHdl_t hdl)
 void AppDbSetDiscStatus(appDbHdl_t hdl, uint8_t status)
 {
   ((appDbRec_t *) hdl)->discStatus = status;
-  store();
 }
 
 /*************************************************************************************************/
@@ -611,7 +579,6 @@ uint16_t *AppDbGetHdlList(appDbHdl_t hdl)
 void AppDbSetHdlList(appDbHdl_t hdl, uint16_t *pHdlList)
 {
   memcpy(((appDbRec_t *) hdl)->hdlList, pHdlList, sizeof(((appDbRec_t *) hdl)->hdlList));
-  store();
 }
 
 /*************************************************************************************************/
@@ -654,7 +621,6 @@ void AppDbSetDevName(uint8_t len, char *pStr)
   len = (len <= sizeof(appDb.devName)) ? len : sizeof(appDb.devName);
 
   memcpy(appDb.devName, pStr, len);
-  store();
 }
 
 /*************************************************************************************************/
@@ -684,7 +650,6 @@ bool_t AppDbGetPeerAddrRes(appDbHdl_t hdl)
 void AppDbSetPeerAddrRes(appDbHdl_t hdl, uint8_t addrRes)
 {
   ((appDbRec_t *)hdl)->peerAddrRes = addrRes;
-  store();
 }
 
 /*************************************************************************************************/
@@ -715,7 +680,6 @@ void AppDbSetPeerSignCounter(appDbHdl_t hdl, uint32_t signCounter)
 {
   if(((appDbRec_t *)hdl)->peerSignCounter != signCounter) {
     ((appDbRec_t *)hdl)->peerSignCounter = signCounter;
-    store();
   }
 }
 
@@ -746,7 +710,6 @@ bool_t AppDbGetPeerAddedToRl(appDbHdl_t hdl)
 void AppDbSetPeerAddedToRl(appDbHdl_t hdl, bool_t peerAddedToRl)
 {
   ((appDbRec_t *)hdl)->peerAddedToRl = peerAddedToRl;
-  store();
 }
 
 /*************************************************************************************************/
@@ -776,6 +739,5 @@ bool_t AppDbGetPeerRpao(appDbHdl_t hdl)
 void AppDbSetPeerRpao(appDbHdl_t hdl, bool_t peerRpao)
 {
   ((appDbRec_t *)hdl)->peerRpao = peerRpao;
-  store();
 }
 /* clang-format on */
diff --git a/epicardium/ble/app/common/app_ui.c b/epicardium/ble/app/common/app_ui.c
deleted file mode 100644
index f5c6f568a2159de62aa9707dab5d1032414d4119..0000000000000000000000000000000000000000
--- a/epicardium/ble/app/common/app_ui.c
+++ /dev/null
@@ -1,349 +0,0 @@
-/*************************************************************************************************/
-/*!
- *  \file
- *
- *  \brief  Application framework user interface.
- *
- *  Copyright (c) 2011-2018 Arm Ltd. All Rights Reserved.
- *  ARM Ltd. confidential and proprietary.
- *
- *  IMPORTANT.  Your use of this file is governed by a Software License Agreement
- *  ("Agreement") that must be accepted in order to download or otherwise receive a
- *  copy of this file.  You may not use or copy this file for any purpose other than
- *  as described in the Agreement.  If you do not agree to all of the terms of the
- *  Agreement do not use this file and delete all copies in your possession or control;
- *  if you do not have a copy of the Agreement, you must contact ARM Ltd. prior
- *  to any use, copying or further distribution of this software.
- */
-/*************************************************************************************************/
-
-#include "wsf_types.h"
-#include "wsf_os.h"
-#include "wsf_trace.h"
-#include "app_ui.h"
-
-/* card10:
- * copied from: lib/sdk/Libraries/BTLE/stack/ble-profiles/sources/apps/app/common/app_ui.c
- *
- * Reason: has several user interactions which we likley have to implement
- */
-
-/* clang-format off */
-/* clang-formet turned off for easier diffing against orginal file */
-/**************************************************************************************************
-  Global Variables
-**************************************************************************************************/
-
-/*! \brief Callback struct */
-appUiCback_t appUiCbackTbl;
-
-
-/*************************************************************************************************/
-/*!
- *  \brief  card10 - Should disable encryption. MAXIM bug reported to us in current static library. Requires
- *                   this to be called before the BTLE app starts making advertisements. Avoids encryption
- *                   rendering the frame unreadable.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void llc_api_crypto_disable_tx();
-
-
-/*************************************************************************************************/
-/*!
- *  \brief  Perform a user interface action based on the event value passed to the function.
- *
- *  \param  event   User interface event value.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppUiAction(uint8_t event)
-{
-  switch (event)
-  {
-    case APP_UI_NONE:
-      /* no action */
-      break;
-
-    case APP_UI_RESET_CMPL:
-      APP_TRACE_INFO0(">>> Reset complete <<<");
-      break;
-
-    case APP_UI_ADV_START:
-      llc_api_crypto_disable_tx();
-      APP_TRACE_INFO0(">>> Advertising started <<<");
-      break;
-
-    case APP_UI_ADV_STOP:
-      APP_TRACE_INFO0(">>> Advertising stopped <<<");
-      break;
-
-    case APP_UI_SCAN_START:
-      APP_TRACE_INFO0(">>> Scanning started <<<");
-      break;
-
-    case APP_UI_SCAN_STOP:
-      APP_TRACE_INFO0(">>> Scanning stopped <<<");
-      break;
-
-    case APP_UI_SCAN_REPORT:
-      APP_TRACE_INFO0(">>> Scan data received from peer <<<");
-      break;
-
-    case APP_UI_CONN_OPEN:
-      APP_TRACE_INFO0(">>> Connection opened <<<");
-      break;
-
-    case APP_UI_CONN_CLOSE:
-      APP_TRACE_INFO0(">>> Connection closed <<<");
-      break;
-
-    case APP_UI_SEC_PAIR_CMPL:
-      APP_TRACE_INFO0(">>> Pairing completed successfully <<<");
-      break;
-
-    case APP_UI_SEC_PAIR_FAIL:
-      APP_TRACE_INFO0(">>> Pairing failed <<<");
-      break;
-
-    case APP_UI_SEC_ENCRYPT:
-      APP_TRACE_INFO0(">>> Connection encrypted <<<");
-      break;
-
-    case APP_UI_SEC_ENCRYPT_FAIL:
-      APP_TRACE_INFO0(">>> Encryption failed <<<");
-      break;
-
-    case APP_UI_PASSKEY_PROMPT:
-      APP_TRACE_INFO0(">>> Prompt user to enter passkey <<<");
-      break;
-
-    case APP_UI_ALERT_CANCEL:
-      APP_TRACE_INFO0(">>> Cancel a low or high alert <<<");
-      break;
-
-    case APP_UI_ALERT_LOW:
-      APP_TRACE_INFO0(">>> Low alert <<<");
-      break;
-
-    case APP_UI_ALERT_HIGH:
-      APP_TRACE_INFO0(">>> High alert <<<");
-      break;
-
-    case APP_UI_ADV_SET_START_IND:
-      APP_TRACE_INFO0(">>> Advertising set(s) started <<<");
-      break;
-
-    case APP_UI_ADV_SET_STOP_IND:
-      APP_TRACE_INFO0(">>> Advertising set(s) stopped <<<");
-      break;
-
-    case APP_UI_SCAN_REQ_RCVD_IND:
-      APP_TRACE_INFO0(">>> Scan request received <<<");
-      break;
-
-    case APP_UI_EXT_SCAN_START_IND:
-      APP_TRACE_INFO0(">>> Extended scanning started <<<");
-      break;
-
-    case APP_UI_EXT_SCAN_STOP_IND:
-      APP_TRACE_INFO0(">>> Extended scanning stopped <<<");
-      break;
-
-    case APP_UI_PER_ADV_SET_START_IND:
-      APP_TRACE_INFO0(">>> Periodic advertising set started <<<");
-      break;
-
-    case APP_UI_PER_ADV_SET_STOP_IND:
-      APP_TRACE_INFO0(">>> Periodic advertising set stopped <<<");
-      break;
-
-    case APP_UI_PER_ADV_SYNC_EST_IND:
-      APP_TRACE_INFO0(">>> Periodic advertising sync established <<<");
-      break;
-
-    case APP_UI_PER_ADV_SYNC_LOST_IND:
-      APP_TRACE_INFO0(">>> Periodic advertising sync lost <<<");
-      break;
-
-    default:
-      break;
-  }
-
-  if (appUiCbackTbl.actionCback)
-  {
-    (*appUiCbackTbl.actionCback)(event);
-  }
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Display a passkey.
- *
- *  \param  passkey   Passkey to display.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppUiDisplayPasskey(uint32_t passkey)
-{
-  APP_TRACE_INFO1(">>> Passkey: %d <<<", passkey);
-}
-
-/*************************************************************************************************/
-/*!
-*  \brief  Display a confirmation value.
-*
-*  \param  confirm    Confirm value to display.
-*
-*  \return None.
-*/
-/*************************************************************************************************/
-void AppUiDisplayConfirmValue(uint32_t confirm)
-{
-  APP_TRACE_INFO1(">>> Confirm Value: %d <<<", confirm);
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Display an RSSI value.
- *
- *  \param  rssi   Rssi value to display.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppUiDisplayRssi(int8_t rssi)
-{
-  APP_TRACE_INFO1(">>> RSSI: %d dBm<<<", rssi);
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Handle a UI timer expiration event.
- *
- *  \param  pMsg    Pointer to message.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void appUiTimerExpired(wsfMsgHdr_t *pMsg)
-{
-
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Perform button press polling.  This function is called to handle WSF
- *          message APP_BTN_POLL_IND.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void appUiBtnPoll(void)
-{
-  if (appUiCbackTbl.btnPollCback)
-  {
-    (*appUiCbackTbl.btnPollCback)();
-  }
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Handle a hardware button press.  This function is called to handle WSF
- *          event APP_BTN_DOWN_EVT.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppUiBtnPressed(void)
-{
-
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Register a callback function to receive application button press events.
- *
- *  \return None.
- *
- *  \note   Registered by application to receive button events
- */
-/*************************************************************************************************/
-void AppUiBtnRegister(appUiBtnCback_t btnCback)
-{
-  appUiCbackTbl.btnCback = btnCback;
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Register a callback function to receive action events.
- *
- *  \return None.
- *
- *  \note   Registered by platform
- */
-/*************************************************************************************************/
-void AppUiActionRegister(appUiActionCback_t actionCback)
-{
-  appUiCbackTbl.actionCback = actionCback;
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Register a callback function to receive APP_BTN_POLL_IND events.
- *
- *  \return None.
- *
- *  \note   Registered by platform
- */
-/*************************************************************************************************/
-void AppUiBtnPollRegister(appUiBtnPollCback_t btnPollCback)
-{
-  appUiCbackTbl.btnPollCback = btnPollCback;
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Play a sound.
- *
- *  \param  pSound   Pointer to sound tone/duration array.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppUiSoundPlay(const appUiSound_t *pSound)
-{
-
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Stop the sound that is currently playing.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppUiSoundStop(void)
-{
-
-}
-
-/*************************************************************************************************/
-/*!
- *  \brief  Button test function-- for test purposes only.
- *
- *  \return None.
- */
-/*************************************************************************************************/
-void AppUiBtnTest(uint8_t btn)
-{
-  if (appUiCbackTbl.btnCback)
-  {
-    (*appUiCbackTbl.btnCback)(btn);
-  }
-}
-
-/* clang-format on */
diff --git a/epicardium/ble/ble.c b/epicardium/ble/ble.c
index 02c0af400efeb96cccb1f919917dc6111d0a54f6..59b25ef07b761dd4570076cc6994d84b52310ff8 100644
--- a/epicardium/ble/ble.c
+++ b/epicardium/ble/ble.c
@@ -65,7 +65,6 @@ static int log_lastflushtick = 0;
 
 /*! \brief  Stack initialization for app. */
 extern void StackInit(void);
-extern void AppInit(void);
 extern void bleuart_init(void);
 extern void bleFileTransfer_init(void);
 extern void bleCard10_init(void);
@@ -441,7 +440,6 @@ void vBleTask(void *pvParameters)
 	BbBleDrvSetTxPower(0);
 	setAddress();
 
-	AppInit();
 	BleStart();
 	AttsDynInit();
 
diff --git a/epicardium/ble/ble_api.h b/epicardium/ble/ble_api.h
index 9040f8e436100660b2bbb7c1ef6079cdde07082a..84f27ef5d8de9caa4097524327e9cd372ec3067d 100644
--- a/epicardium/ble/ble_api.h
+++ b/epicardium/ble/ble_api.h
@@ -1,4 +1,9 @@
 #pragma once
+
+#define CARD10_UUID_SUFFIX                                                     \
+	0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23
+#define CARD10_UUID_PREFIX 0x02, 0x23, 0x42
+
 /**************************************************************************************************
   Function Declarations
 **************************************************************************************************/
diff --git a/epicardium/ble/ble_main.c b/epicardium/ble/ble_main.c
index a274f270cbd0d5a74327314eef19c7c7f7dbf75a..c8fef1f669663a21453630743d6861ebee44ace1 100644
--- a/epicardium/ble/ble_main.c
+++ b/epicardium/ble/ble_main.c
@@ -21,6 +21,7 @@
 #include "wsf_msg.h"
 #include "wsf_trace.h"
 #include "hci_api.h"
+#include "l2c_api.h"
 #include "dm_api.h"
 #include "att_api.h"
 #include "smp_api.h"
@@ -39,8 +40,16 @@
 #include "rscp/rscp_api.h"
 #include "cccd.h"
 
+#include "ble_api.h"
+#include "epicardium.h"
+#include "api/interrupt-sender.h"
 #include "modules/log.h"
 
+static bool active;
+static uint8_t advertising_mode = APP_MODE_NONE;
+static uint8_t advertising_mode_target = APP_MODE_NONE;
+static enum ble_event_type ble_event;
+
 /**************************************************************************************************
   Macros
 **************************************************************************************************/
@@ -74,8 +83,8 @@ typedef union
 /*! configurable parameters for advertising */
 static const appAdvCfg_t bleAdvCfg =
 {
-  {0,             0,              0},                  /*! Advertising durations in ms */
-  {500/0.625,     4000/0.625,     0}                   /*! Advertising intervals in 0.625 ms units */
+  {0,             0},                  /*! Advertising durations in ms */
+  {500/0.625,     0}                   /*! Advertising intervals in 0.625 ms units */
 };
 
 /*! configurable parameters for slave */
@@ -148,19 +157,17 @@ static const uint8_t bleAdvDataDisc[] =
   /*! flags */
   2,                                      /*! length */
   DM_ADV_TYPE_FLAGS,                      /*! AD type */
-  DM_FLAG_LE_GENERAL_DISC |               /*! flags */
+  DM_FLAG_LE_LIMITED_DISC |               /*! flags */
   DM_FLAG_LE_BREDR_NOT_SUP,
 
-  /*! tx power */
-  2,                                      /*! length */
-  DM_ADV_TYPE_TX_POWER,                   /*! AD type */
-  0,                                      /*! tx power */
+  3,
+  DM_ADV_TYPE_APPEARANCE,
+  UINT16_TO_BYTES(CH_APPEAR_WATCH),
 
   /*! service UUID list */
-  5,                                      /*! length */
-  DM_ADV_TYPE_16_UUID,                    /*! AD type */
-  UINT16_TO_BYTES(ATT_UUID_DEVICE_INFO_SERVICE),
-  UINT16_TO_BYTES(ATT_UUID_BATTERY_SERVICE)
+  17,
+  DM_ADV_TYPE_128_UUID_PART,
+  CARD10_UUID_SUFFIX, 0x0, CARD10_UUID_PREFIX
 };
 
 /*! scan data, discoverable mode */
@@ -172,6 +179,16 @@ uint8_t bleScanDataDisc[] =
   'c','a','r','d','1','0','-','0','0','0','0','0','0'
 };
 
+/*! advertising data, connectable mode */
+static const uint8_t bleAdvDataConn[] =
+{
+  /*! flags */
+  2,                                      /*! length */
+  DM_ADV_TYPE_FLAGS,                      /*! AD type */
+  DM_FLAG_LE_BREDR_NOT_SUP,
+};
+
+
 /**************************************************************************************************
   Client Characteristic Configuration Descriptors
 **************************************************************************************************/
@@ -191,8 +208,113 @@ static const attsCccSet_t bleCccSet[BLE_NUM_CCC_IDX] =
 /*! WSF handler ID */
 wsfHandlerId_t bleHandlerId;
 
+static dmConnId_t pair_connId = DM_CONN_ID_NONE;
+static uint32_t pair_confirm_value;
 
 static void BleHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg);
+
+static const char * const att_events[] = {
+	"ATTC_FIND_INFO_RSP",
+	"ATTC_FIND_BY_TYPE_VALUE_RSP",
+	"ATTC_READ_BY_TYPE_RSP",
+	"ATTC_READ_RSP",
+	"ATTC_READ_LONG_RSP",
+	"ATTC_READ_MULTIPLE_RSP",
+	"ATTC_READ_BY_GROUP_TYPE_RSP",
+	"ATTC_WRITE_RSP",
+	"ATTC_WRITE_CMD_RSP",
+	"ATTC_PREPARE_WRITE_RSP",
+	"ATTC_EXECUTE_WRITE_RSP",
+	"ATTC_HANDLE_VALUE_NTF",
+	"ATTC_HANDLE_VALUE_IND",
+	/* ATT server callback events */
+	"ATTS_HANDLE_VALUE_CNF",
+	"ATTS_CCC_STATE_IND",
+	"ATTS_DB_HASH_CALC_CMPL_IND",
+	/* ATT common callback events */
+	"ATT_MTU_UPDATE_IND"
+};
+
+static const char * const dm_events[] = {
+    "DM_RESET_CMPL_IND",
+    "DM_ADV_START_IND",
+    "DM_ADV_STOP_IND",
+    "DM_ADV_NEW_ADDR_IND",
+    "DM_SCAN_START_IND",
+    "DM_SCAN_STOP_IND",
+    "DM_SCAN_REPORT_IND",
+    "DM_CONN_OPEN_IND",
+    "DM_CONN_CLOSE_IND",
+    "DM_CONN_UPDATE_IND",
+    "DM_SEC_PAIR_CMPL_IND",
+    "DM_SEC_PAIR_FAIL_IND",
+    "DM_SEC_ENCRYPT_IND",
+    "DM_SEC_ENCRYPT_FAIL_IND",
+    "DM_SEC_AUTH_REQ_IND",
+    "DM_SEC_KEY_IND",
+    "DM_SEC_LTK_REQ_IND",
+    "DM_SEC_PAIR_IND",
+    "DM_SEC_SLAVE_REQ_IND",
+    "DM_SEC_CALC_OOB_IND",
+    "DM_SEC_ECC_KEY_IND",
+    "DM_SEC_COMPARE_IND",
+    "DM_SEC_KEYPRESS_IND",
+    "DM_PRIV_RESOLVED_ADDR_IND",
+    "DM_PRIV_GENERATE_ADDR_IND",
+    "DM_CONN_READ_RSSI_IND",
+    "DM_PRIV_ADD_DEV_TO_RES_LIST_IND",
+    "DM_PRIV_REM_DEV_FROM_RES_LIST_IND",
+    "DM_PRIV_CLEAR_RES_LIST_IND",
+    "DM_PRIV_READ_PEER_RES_ADDR_IND",
+    "DM_PRIV_READ_LOCAL_RES_ADDR_IND",
+    "DM_PRIV_SET_ADDR_RES_ENABLE_IND",
+    "DM_REM_CONN_PARAM_REQ_IND",
+    "DM_CONN_DATA_LEN_CHANGE_IND",
+    "DM_CONN_WRITE_AUTH_TO_IND",
+    "DM_CONN_AUTH_TO_EXPIRED_IND",
+    "DM_PHY_READ_IND",
+    "DM_PHY_SET_DEF_IND",
+    "DM_PHY_UPDATE_IND",
+    "DM_ADV_SET_START_IND",
+    "DM_ADV_SET_STOP_IND",
+    "DM_SCAN_REQ_RCVD_IND",
+    "DM_EXT_SCAN_START_IND",
+    "DM_EXT_SCAN_STOP_IND",
+    "DM_EXT_SCAN_REPORT_IND",
+    "DM_PER_ADV_SET_START_IND",
+    "DM_PER_ADV_SET_STOP_IND",
+    "DM_PER_ADV_SYNC_EST_IND",
+    "DM_PER_ADV_SYNC_EST_FAIL_IND",
+    "DM_PER_ADV_SYNC_LOST_IND",
+    "DM_PER_ADV_SYNC_TRSF_EST_IND",
+    "DM_PER_ADV_SYNC_TRSF_EST_FAIL_IND",
+    "DM_PER_ADV_SYNC_TRSF_IND",
+    "DM_PER_ADV_SET_INFO_TRSF_IND",
+    "DM_PER_ADV_REPORT_IND",
+    "DM_REMOTE_FEATURES_IND",
+    "DM_READ_REMOTE_VER_INFO_IND",
+    "DM_CONN_IQ_REPORT_IND",
+    "DM_CTE_REQ_FAIL_IND",
+    "DM_CONN_CTE_RX_SAMPLE_START_IND",
+    "DM_CONN_CTE_RX_SAMPLE_STOP_IND",
+    "DM_CONN_CTE_TX_CFG_IND",
+    "DM_CONN_CTE_REQ_START_IND",
+    "DM_CONN_CTE_REQ_STOP_IND",
+    "DM_CONN_CTE_RSP_START_IND",
+    "DM_CONN_CTE_RSP_STOP_IND",
+    "DM_READ_ANTENNA_INFO_IND",
+    "DM_L2C_CMD_REJ_IND",
+    "DM_ERROR_IND",
+    "DM_HW_ERROR_IND",
+    "DM_VENDOR_SPEC_IND"
+};
+
+static const char * const l2c_coc_events[] = {
+	"L2C_COC_CONNECT_IND",
+	"L2C_COC_DISCONNECT_IND",
+	"L2C_COC_DATA_IND",
+	"L2C_COC_DATA_CNF"
+};
 /*************************************************************************************************/
 /*!
  *  \brief  Application DM callback.
@@ -345,20 +467,114 @@ static void bleSetup(bleMsg_t *pMsg)
   AppAdvSetData(APP_SCAN_DATA_DISCOVERABLE, sizeof(bleScanDataDisc), (uint8_t *) bleScanDataDisc);
 
   /* set advertising and scan response data for connectable mode */
-  AppAdvSetData(APP_ADV_DATA_CONNECTABLE, 0, NULL);
+  AppAdvSetData(APP_ADV_DATA_CONNECTABLE, sizeof(bleAdvDataConn), (uint8_t *) bleAdvDataConn);
   AppAdvSetData(APP_SCAN_DATA_CONNECTABLE, 0, NULL);
 
-#if 0
-  /* TODO: card10: until we have an BLE dialog, be discoverable and bondable always */
-  /* start advertising; automatically set connectable/discoverable mode and bondable mode */
-  AppAdvStart(APP_MODE_AUTO_INIT);
-#else
-  /* enter discoverable and bondable mode mode by default */
-  AppSetBondable(TRUE);
-  AppAdvStart(APP_MODE_DISCOVERABLE);
-#endif
+  active = true;
+  /* TODO: Sadly, not advertising leads to a higher current consumption... */
+  epic_ble_set_bondable(false);
+}
+
+void epic_ble_set_bondable(bool bondable)
+{
+	if(!active) {
+		return;
+	}
+
+	if(bondable) {
+		/* We need to stop advertising in between or the
+		 * adv set will not be changed.
+		 * Also need to wait for the stop operation to finish
+		 * before we can start again
+		 * Also need to set the variables first as we don't
+		 * have a lock on the stack.*/
+		AppSetBondable(TRUE);
+		if(advertising_mode != APP_MODE_DISCOVERABLE) {
+			LOG_INFO("ble", "Making bondable and discoverable");
+			if(advertising_mode != APP_MODE_NONE) {
+				advertising_mode_target = APP_MODE_DISCOVERABLE;
+				advertising_mode = APP_MODE_NONE;
+				AppAdvStop();
+			} else {
+				advertising_mode = APP_MODE_DISCOVERABLE;
+				advertising_mode_target = APP_MODE_DISCOVERABLE;
+				AppAdvStart(advertising_mode);
+			}
+		}
+	} else {
+		AppSetBondable(FALSE);
+		if(AppDbCheckBonded()) {
+			if(advertising_mode != APP_MODE_CONNECTABLE) {
+				LOG_INFO("ble", "Bonded. Making connectable");
+				if(advertising_mode != APP_MODE_NONE) {
+					advertising_mode_target = APP_MODE_CONNECTABLE;
+					advertising_mode = APP_MODE_NONE;
+					AppAdvStop();
+				} else {
+					advertising_mode = APP_MODE_CONNECTABLE;
+					advertising_mode_target = APP_MODE_CONNECTABLE;
+					AppAdvStart(advertising_mode);
+				}
+			}
+		} else {
+			if(advertising_mode != APP_MODE_NONE) {
+				LOG_INFO("ble", "Not bonded. Stop advertising");
+				advertising_mode = APP_MODE_NONE;
+				advertising_mode_target = APP_MODE_NONE;
+				AppAdvStop();
+			}
+		}
+	}
+}
+
+uint32_t epic_ble_get_compare_value(void)
+{
+	return pair_confirm_value;
+}
+
+void epic_ble_compare_response(bool confirmed)
+{
+	if(!active) {
+		return;
+	}
+
+	if(pair_connId != DM_CONN_ID_NONE) {
+		LOG_INFO("ble", "Value confirmed: %u", confirmed);
+		DmSecCompareRsp(pair_connId, confirmed);
+	} else {
+		/* error condition */
+	}
+}
+static void trigger_event(enum ble_event_type event)
+{
+	bool enabled;
+	epic_interrupt_is_enabled(EPIC_INT_BLE, &enabled);
+	if(ble_event && enabled) {
+		LOG_WARN("ble", "Application missed event %u", ble_event);
+	}
+
+	ble_event = event;
+	api_interrupt_trigger(EPIC_INT_BLE);
+}
+
+enum ble_event_type epic_ble_get_event(void)
+{
+	enum ble_event_type event = ble_event;
+	ble_event = 0;
+	return event;
 }
 
+static void bleHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd)
+{
+	if(!active) {
+		return;
+	}
+
+	pair_connId = (dmConnId_t)pCnfInd->hdr.param;
+	pair_confirm_value = DmSecGetCompareValue(pCnfInd->confirm);
+	LOG_INFO("ble", "Confirm Value: %ld", pair_confirm_value);
+	trigger_event(BLE_EVENT_HANDLE_NUMERIC_COMPARISON);
+}
 
 /*************************************************************************************************/
 /*!
@@ -371,7 +587,6 @@ static void bleSetup(bleMsg_t *pMsg)
 /*************************************************************************************************/
 static void bleProcMsg(bleMsg_t *pMsg)
 {
-  uint8_t uiEvent = APP_UI_NONE;
   hciLeConnCmplEvt_t *connOpen;
 
   switch(pMsg->hdr.event)
@@ -391,17 +606,22 @@ static void bleProcMsg(bleMsg_t *pMsg)
     case DM_RESET_CMPL_IND:
       DmSecGenerateEccKeyReq();
       bleSetup(pMsg);
-      uiEvent = APP_UI_RESET_CMPL;
       break;
 
     case DM_ADV_START_IND:
-      LOG_INFO("ble", "Advertisement started");
-      uiEvent = APP_UI_ADV_START;
+      LOG_INFO("ble", "Advertisement started %u %u", advertising_mode, advertising_mode_target);
+      if(advertising_mode != advertising_mode_target) {
+        AppAdvStop();
+      }
+
       break;
 
     case DM_ADV_STOP_IND:
-      LOG_INFO("ble", "Advertisement stopped");
-      uiEvent = APP_UI_ADV_STOP;
+      LOG_INFO("ble", "Advertisement stopped %u %u", advertising_mode, advertising_mode_target);
+      if(advertising_mode != advertising_mode_target) {
+        advertising_mode = advertising_mode_target;
+        AppAdvStart(advertising_mode);
+      }
       break;
 
     case DM_CONN_OPEN_IND:
@@ -411,7 +631,6 @@ static void bleProcMsg(bleMsg_t *pMsg)
                connOpen->peerAddr[3], connOpen->peerAddr[2],
                connOpen->peerAddr[1], connOpen->peerAddr[0]);
       BasProcMsg(&pMsg->hdr);
-      uiEvent = APP_UI_CONN_OPEN;
       break;
 
     case DM_CONN_CLOSE_IND:
@@ -442,14 +661,20 @@ static void bleProcMsg(bleMsg_t *pMsg)
                    pMsg->dm.connClose.reason);
           break;
       }
+      /* Stack overwrites advertising mode after connection close.
+       * Force our desired mode.
+       */
+      advertising_mode = APP_MODE_NONE;
+      AppAdvStop();
+
       bleClose(pMsg);
-      uiEvent = APP_UI_CONN_CLOSE;
       break;
 
     case DM_SEC_PAIR_CMPL_IND:
       LOG_INFO("ble", "Secure pairing successful, auth: 0x%02X",
                pMsg->dm.pairCmpl.auth);
-      uiEvent = APP_UI_SEC_PAIR_CMPL;
+      pair_connId = DM_CONN_ID_NONE;
+      trigger_event(BLE_EVENT_PAIRING_COMPLETE);
       /* After a successful pairing, bonding is disabled again.
        * We don't want that for now. */
       AppSetBondable(TRUE);
@@ -470,17 +695,16 @@ static void bleProcMsg(bleMsg_t *pMsg)
                    pMsg->hdr.status);
           break;
       }
-      uiEvent = APP_UI_SEC_PAIR_FAIL;
+      pair_connId = DM_CONN_ID_NONE;
+      trigger_event(BLE_EVENT_PAIRING_FAILED);
       break;
 
     case DM_SEC_ENCRYPT_IND:
       LOG_INFO("ble", "Encrypted handshake successful");
-      uiEvent = APP_UI_SEC_ENCRYPT;
       break;
 
     case DM_SEC_ENCRYPT_FAIL_IND:
       LOG_INFO("ble", "Encrypted handshake failed");
-      uiEvent = APP_UI_SEC_ENCRYPT_FAIL;
       break;
 
     case DM_SEC_AUTH_REQ_IND:
@@ -492,22 +716,16 @@ static void bleProcMsg(bleMsg_t *pMsg)
       break;
 
     case DM_SEC_COMPARE_IND:
-      AppHandleNumericComparison(&pMsg->dm.cnfInd);
+      bleHandleNumericComparison(&pMsg->dm.cnfInd);
       break;
 
     case DM_HW_ERROR_IND:
       LOG_ERR("ble", "HW Error");
-      uiEvent = APP_UI_HW_ERROR;
       break;
 
     default:
       break;
   }
-
-  if (uiEvent != APP_UI_NONE)
-  {
-    AppUiAction(uiEvent);
-  }
 }
 
 /*************************************************************************************************/
@@ -561,12 +779,25 @@ static void BleHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg)
 
     if (pMsg->event >= DM_CBACK_START && pMsg->event <= DM_CBACK_END)
     {
+      LOG_INFO("ble", "Ble got evt %d: %s", pMsg->event, dm_events[pMsg->event - DM_CBACK_START]);
       /* process advertising and connection-related messages */
       AppSlaveProcDmMsg((dmEvt_t *) pMsg);
 
       /* process security-related messages */
       AppSlaveSecProcDmMsg((dmEvt_t *) pMsg);
     }
+    else if (pMsg->event >= ATT_CBACK_START && pMsg->event <= ATT_CBACK_END)
+    {
+      LOG_INFO("ble", "Ble got evt %d: %s", pMsg->event, att_events[pMsg->event - ATT_CBACK_START]);
+    }
+    else if (pMsg->event >= L2C_COC_CBACK_START && pMsg->event <= L2C_COC_CBACK_CBACK_END)
+    {
+      LOG_INFO("ble", "Ble got evt %d: %s", pMsg->event, l2c_coc_events[pMsg->event - L2C_COC_CBACK_START]);
+    }
+    else
+    {
+      LOG_INFO("ble", "Ble got evt %d", pMsg->event);
+    }
 
     /* perform profile and user interface-related operations */
     bleProcMsg((bleMsg_t *) pMsg);
diff --git a/epicardium/ble/card10.c b/epicardium/ble/card10.c
index c7fa728a42afdf5049268980a1d77deb15019524..eb9c0a2beb70fb4b7f5c4edacd24149f0c8a6b29 100644
--- a/epicardium/ble/card10.c
+++ b/epicardium/ble/card10.c
@@ -1,19 +1,17 @@
+#include "ble_api.h"
+
+#include "epicardium.h"
+
 #include "wsf_types.h"
 #include "util/bstream.h"
 #include "wsf_assert.h"
 #include "att_api.h"
 
-#include "epicardium.h"
-
 #include <stdio.h>
 #include <string.h>
 #include <stdbool.h>
 #include <machine/endian.h>
 
-#define CARD10_UUID_SUFFIX                                                     \
-	0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23, 0x42, 0x23
-#define CARD10_UUID_PREFIX 0x02, 0x23, 0x42
-
 /*!< \brief Service start handle. */
 #define CARD10_START_HDL 0x920
 /*!< \brief Service end handle. */
diff --git a/epicardium/ble/meson.build b/epicardium/ble/meson.build
index c531ae9a95d310bc4fa3b80fbac513505cb09c8f..41280358b05f0d04c470049a6d841fbea0c92fe2 100644
--- a/epicardium/ble/meson.build
+++ b/epicardium/ble/meson.build
@@ -4,9 +4,7 @@ ble_sources = files(
   'ble_main.c',
   'svc_dis.c',
   'svc_core.c',
-  'app/app_main.c',
   'app/common/app_db.c',
-  'app/common/app_ui.c',
   'uart.c',
   'card10.c',
   'filetransfer.c',
diff --git a/epicardium/ble/stack.c b/epicardium/ble/stack.c
index f96f2c113ae01f849a0c3b942d76c630b25e534b..a7896b68fa7c35a9d85a173b67d495ae95204e8d 100644
--- a/epicardium/ble/stack.c
+++ b/epicardium/ble/stack.c
@@ -186,5 +186,8 @@ void StackInit(void)
 
   /*TODO card10: Probably want to adjust this */
   HciSetMaxRxAclLen(100);
+
+  handlerId = WsfOsSetNextHandler(AppHandler);
+  AppHandlerInit(handlerId);
 }
 /* clang-format off */
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 65a29744e752e4bf4f50f507cdae11f60df1d4d2..2ab3126f3400a95b92a8765389dd0173218b9904 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -36,6 +36,7 @@ typedef _Bool bool;
 
 #define API_INTERRUPT_ENABLE        0xA
 #define API_INTERRUPT_DISABLE       0xB
+#define API_INTERRUPT_IS_ENABLED    0xC
 
 #define API_UART_WRITE_STR         0x10
 #define API_UART_READ_CHAR         0x11
@@ -147,6 +148,12 @@ typedef _Bool bool;
 #define API_CONFIG_GET_INTEGER     0x131
 #define API_CONFIG_GET_BOOLEAN     0x132
 #define API_CONFIG_SET_STRING      0x133
+
+#define API_BLE_GET_COMPARE_VALUE  0x140
+#define API_BLE_COMPARE_RESPONSE   0x141
+#define API_BLE_SET_BONDABLE       0x142
+#define API_BLE_GET_EVENT          0x143
+
 /* clang-format on */
 
 typedef uint32_t api_int_id_t;
@@ -185,6 +192,19 @@ API(API_INTERRUPT_ENABLE, int epic_interrupt_enable(api_int_id_t int_id));
  */
 API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id));
 
+/**
+ * Check if an API interrupt is enabled.
+ *
+ * :param int int_id: The interrupt to be checked
+ * :param bool* enabled: ``true`` will be stored here if the interrupt is enabled.
+ *   ``false`` otherwise.
+ *
+ * :return: 0 on success, ``-EINVAL`` if the interrupt is unknown.
+ *
+ * .. versionadded:: 1.16
+ */
+API(API_INTERRUPT_IS_ENABLED, int epic_interrupt_is_enabled(api_int_id_t int_id, bool *enabled));
+
 /**
  * The following interrupts are defined:
  */
@@ -210,9 +230,10 @@ API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id));
 #define EPIC_INT_BHI160_MAGNETOMETER    8
 /** MAX86150 ECG and PPG sensor.  See :c:func:`epic_isr_max86150`. */
 #define EPIC_INT_MAX86150		9
-
+/** Bluetooth Low Energy event.  See :c:func:`epic_isr_ble`. */
+#define EPIC_INT_BLE                    10
 /* Number of defined interrupts. */
-#define EPIC_INT_NUM                    10
+#define EPIC_INT_NUM                    11
 /* clang-format on */
 
 /*
@@ -2095,5 +2116,108 @@ API(API_CONFIG_GET_STRING, int epic_config_get_string(const char *key, char *buf
  * .. versionadded:: 1.16
  */
 API(API_CONFIG_SET_STRING, int epic_config_set_string(const char *key, const char *value));
+
+
+/**
+ * Bluetooth Low Energy (BLE)
+ * ==========================
+ */
+
+/**
+ * BLE event type
+ */
+enum ble_event_type {
+	/** No event pending */
+	BLE_EVENT_NONE                                    = 0,
+	/** Numeric comparison requested */
+	BLE_EVENT_HANDLE_NUMERIC_COMPARISON               = 1,
+	/** A pairing procedure has failed */
+	BLE_EVENT_PAIRING_FAILED                          = 2,
+	/** A pairing procedure has successfully completed */
+	BLE_EVENT_PAIRING_COMPLETE                        = 3,
+};
+
+
+/**
+ * **Interrupt Service Routine** for :c:data:`EPIC_INT_BLE`
+ *
+ * :c:func:`epic_isr_ble` is called when the BLE stack wants to signal an
+ * event to the application. You can use :c:func:`epic_ble_get_event` to obtain
+ * the event which triggered this interrupt.
+ *
+ * Currently supported events:
+ *
+ * :c:data:`BLE_EVENT_HANDLE_NUMERIC_COMPARISON`:
+ *    An ongoing pairing procedure requires a numeric comparison to complete.
+ *    The compare value can be retreived using :c:func:`epic_ble_get_compare_value`.
+ *
+ * :c:data:`BLE_EVENT_PAIRING_FAILED`:
+ *    A pairing procedure failed. The stack automatically went back advertising
+ *    and accepting new pairings.
+ *
+ * :c:data:`BLE_EVENT_PAIRING_COMPLETE`:
+ *    A pairing procedure has completed sucessfully.
+ *    The stack automatically persists the pairing information, creating a bond.
+ *
+ * .. versionadded:: 1.16
+ */
+API_ISR(EPIC_INT_BLE, epic_isr_ble);
+
+/**
+ * Retreive the event which triggered :c:func:`epic_isr_ble`
+ *
+ * The handling code needs to ensure to handle interrupts in a timely
+ * manner as new events will overwrite each other. Reading the event
+ * automatically resets it to :c:data:`BLE_EVENT_NONE`.
+ *
+ * :return: Event which triggered the interrupt.
+ *
+ * .. versionadded:: 1.16
+ */
+API(API_BLE_GET_EVENT, enum ble_event_type epic_ble_get_event(void));
+
+/**
+ * Retrieve the compare value of an ongoing pairing procedure.
+ *
+ * If no pairing procedure is ongoing, the returned value is undefined.
+ *
+ * :return: 6 digit long compare value
+ *
+ * .. versionadded:: 1.16
+ */
+API(API_BLE_GET_COMPARE_VALUE, uint32_t epic_ble_get_compare_value(void));
+
+/**
+ * Indicate wether the user confirmed the compare value.
+ *
+ * If a pariring procedure involving a compare value is ongoing and this
+ * function is called with confirmed set to ``true``, it will try to
+ * proceed and complete the pairing process. If called with ``false``, the
+ * pairing procedure will be aborted.
+ *
+ * :param bool confirmed: `true` if the user confirmed the compare value.
+ *
+ * .. versionadded:: 1.16
+ */
+API(API_BLE_COMPARE_RESPONSE, void epic_ble_compare_response(bool confirmed));
+
+/**
+ * Allow or disallow new bondings to happen
+ *
+ * By default the card10 will not allow new bondings to be made. New
+ * bondings have to explicitly allowed by calling this function.
+ *
+ * While bonadable the card10 will change its advertisements to
+ * indicate to scanning hosts that it is available for discovery.
+ *
+ * When switching applications new bondings are automatically
+ * disallowed.
+ *
+ * :param bool bondable: `true` if new bondings should be allowed.
+ *
+ * .. versionadded:: 1.16
+ */
+API(API_BLE_SET_BONDABLE, void epic_ble_set_bondable(bool bondable));
+
 #endif /* _EPICARDIUM_H */
 
diff --git a/epicardium/modules/config.c b/epicardium/modules/config.c
index d4c2b74e0b2fed4915ae2670a8da39b078f6042c..cae7bde65f4e6b563fa24bb964c698f054ee3764 100644
--- a/epicardium/modules/config.c
+++ b/epicardium/modules/config.c
@@ -511,7 +511,7 @@ int epic_config_set_string(const char *key, const char *value_in)
 			slot->value_offset, buf, sizeof(buf)
 		);
 		if (nread == 0) {
-			LOG_DEBUG("card10.cfg", "could not read old value", );
+			LOG_DEBUG("card10.cfg", "could not read old value");
 			goto complex_out;
 		}
 
diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c
index 6d118fe73ef59007c4e3cb915afdba004e678d56..1168f5a756d81ee3e3d16a15e5e4c703314b2b7a 100644
--- a/epicardium/modules/hardware.c
+++ b/epicardium/modules/hardware.c
@@ -289,5 +289,10 @@ int hardware_reset(void)
 
 	epic_max86150_disable_sensor();
 
+	/*
+	 * BLE
+	 */
+	epic_ble_set_bondable(false);
+
 	return 0;
 }
diff --git a/epicardium/modules/interrupts.c b/epicardium/modules/interrupts.c
index 0ead54fc58b1d6341071cd377a8ab239bbbff8e7..d65505ea5a0629b5934057e81599d9c35d6ec454 100644
--- a/epicardium/modules/interrupts.c
+++ b/epicardium/modules/interrupts.c
@@ -78,6 +78,17 @@ static void interrupt_set_enabled(api_int_id_t id, bool enabled)
 	mutex_unlock(&interrupt_mutex);
 }
 
+static bool interrupt_get_enabled(api_int_id_t id)
+{
+	assert(id < EPIC_INT_NUM);
+
+	bool enabled;
+	mutex_lock(&interrupt_mutex);
+	enabled = interrupt_data.int_enabled[id];
+	mutex_unlock(&interrupt_mutex);
+	return enabled;
+}
+
 void interrupt_init(void)
 {
 	if (interrupt_mutex.name == NULL)
@@ -114,6 +125,17 @@ int epic_interrupt_disable(api_int_id_t int_id)
 	interrupt_set_enabled(int_id, false);
 	return 0;
 }
+
+int epic_interrupt_is_enabled(api_int_id_t int_id, bool *enabled)
+{
+	if (int_id >= EPIC_INT_NUM) {
+		return -EINVAL;
+	}
+
+	*enabled = interrupt_get_enabled(int_id);
+	return 0;
+}
+
 /* }}} */
 
 void vInterruptsTask(void *pvParameters)
diff --git a/lib/sdk/Libraries/BTLE/meson.build b/lib/sdk/Libraries/BTLE/meson.build
index ab0db720687c87a7cc2763511090fbcfd2370104..6dd10ee6a04dc62c8036da9681b739fa64ecf328 100644
--- a/lib/sdk/Libraries/BTLE/meson.build
+++ b/lib/sdk/Libraries/BTLE/meson.build
@@ -59,11 +59,11 @@ sources = files(
 'stack/ble-profiles/sources/apps/app/app_master_ae.c',
 'stack/ble-profiles/sources/apps/app/app_slave_leg.c',
 'stack/ble-profiles/sources/apps/app/app_slave_ae.c',
-#'stack/ble-profiles/sources/apps/app/app_main.c',
+'stack/ble-profiles/sources/apps/app/app_main.c',
 'stack/ble-profiles/sources/apps/app/app_server.c',
 'stack/ble-profiles/sources/apps/app/app_master_leg.c',
 #'stack/ble-profiles/sources/apps/app/common/app_db.c',
-#'stack/ble-profiles/sources/apps/app/common/app_ui.c',
+'stack/ble-profiles/sources/apps/app/common/app_ui.c',
 'stack/ble-profiles/sources/apps/app/common/app_hw.c',
 'stack/ble-profiles/sources/apps/app/app_terminal.c',
 'stack/ble-profiles/sources/apps/wdxs/wdxs_oad.c',
diff --git a/lib/sdk/Libraries/BTLE/stack/ble-profiles/sources/apps/app/app_slave.c b/lib/sdk/Libraries/BTLE/stack/ble-profiles/sources/apps/app/app_slave.c
index bc298c74595898cbbbd05fa3dc9333e41c66505b..fa2a857c1357310749330ef63f36e00a25dfecac 100644
--- a/lib/sdk/Libraries/BTLE/stack/ble-profiles/sources/apps/app/app_slave.c
+++ b/lib/sdk/Libraries/BTLE/stack/ble-profiles/sources/apps/app/app_slave.c
@@ -206,6 +206,12 @@ static void appSetAdvScanDataFrag(uint8_t advHandle, uint8_t location)
     remainLen = appSlaveCb.maxAdvDataLen[advHandle];
   }
 
+  if(remainLen == 0)
+  {
+    op = HCI_ADV_DATA_OP_COMP_FRAG;
+    DmAdvSetData(advHandle, op, APP_LOC_2_DM_LOC(location), 0, NULL);
+  }
+
   /* while there remains data to be sent */
   while (remainLen > 0)
   {
@@ -257,13 +263,15 @@ static void appSetAdvScanData(uint8_t advHandle, uint8_t mode)
   scanLoc = APP_MODE_2_SCAN_LOC(mode);
 
   /* set advertising data */
-  if (appSlaveCb.advDataOffset[advHandle][advLoc] < appSlaveCb.advDataLen[advHandle][advLoc])
+  if (appSlaveCb.advDataOffset[advHandle][advLoc] < appSlaveCb.advDataLen[advHandle][advLoc] ||
+       appSlaveCb.advDataLen[advHandle][advLoc] == 0)
   {
     appSetAdvScanDataFrag(advHandle, advLoc);
   }
 
   /* set scan data */
-  if (appSlaveCb.advDataOffset[advHandle][scanLoc] < appSlaveCb.advDataLen[advHandle][scanLoc])
+  if (appSlaveCb.advDataOffset[advHandle][scanLoc] < appSlaveCb.advDataLen[advHandle][scanLoc] ||
+       appSlaveCb.advDataLen[advHandle][scanLoc] == 0)
   {
     appSetAdvScanDataFrag(advHandle, scanLoc);
   }
diff --git a/preload/apps/ble/__init__.py b/preload/apps/ble/__init__.py
index acf0b13530997eefaea87483283d978edf938c89..d5b0b7163a0ef44664a587bce87d8acb47af0628 100644
--- a/preload/apps/ble/__init__.py
+++ b/preload/apps/ble/__init__.py
@@ -2,17 +2,28 @@ import os
 import display
 import time
 import buttons
+import sys_ble
+import interrupt
 
 CONFIG_NAME = "ble.txt"
 MAC_NAME = "mac.txt"
 ACTIVE_STRING = "active=true"
 INACTIVE_STRING = "active=false"
+ble_event = None
+
+
+def ble_callback(_):
+    global ble_event
+    ble_event = sys_ble.get_event()
 
 
 def init():
     if CONFIG_NAME not in os.listdir("."):
         with open(CONFIG_NAME, "w") as f:
             f.write(INACTIVE_STRING)
+    interrupt.set_callback(interrupt.BLE, ble_callback)
+    interrupt.enable_callback(interrupt.BLE)
+    sys_ble.set_bondable(True)
 
 
 def load_mac():
@@ -67,21 +78,81 @@ def selector():
     disp.print("toggle", posx=25, posy=40, fg=[255, 255, 255])
 
 
-disp = display.open()
-button_pressed = True
 init()
+disp = display.open()
+state = 1
+v_old = buttons.read()
 
 while True:
-    disp.clear()
-    headline()
-    v = buttons.read(buttons.TOP_RIGHT)
-    if v == 0:
-        button_pressed = False
-
-    if not button_pressed and v & buttons.TOP_RIGHT != 0:
-        button_pressed = True
-        toggle()
+    v_new = buttons.read()
+    v = ~v_old & v_new
+    v_old = v_new
+
+    if state == 1:
+        # print config screen
+        disp.clear()
+        headline()
+        selector()
+        disp.update()
+        state = 2
+    elif state == 2:
+        # wait for button press or ble_event
+        if ble_event == sys_ble.EVENT_HANDLE_NUMERIC_COMPARISON:
+            ble_event = None
+            state = 3
+        if v & buttons.TOP_RIGHT:
+            toggle()
+            state = 1
+
+    elif state == 3:
+        # print confirmation value
+        compare_value = sys_ble.get_compare_value()
+        disp.clear()
+        disp.print("BLE: Bond?", posy=0, fg=[0, 0, 255])
+        disp.print("Code:", posy=20, fg=[0, 255, 255])
+        disp.print("   %06d" % compare_value, posy=40, fg=[255, 255, 255])
+        disp.print("Yes", posy=60, fg=[0, 255, 0])
+        disp.print("No", posx=120, posy=60, fg=[255, 0, 0])
+
+        disp.update()
+        state = 4
+    elif state == 4:
+        # wait for button press or ble_event
+        if ble_event == sys_ble.EVENT_PAIRING_FAILED:
+            ble_event = None
+            state = 6
+        if v & buttons.BOTTOM_LEFT:
+            sys_ble.confirm_compare_value(True)
+            disp.clear()
+            disp.print("BLE Bonding", posy=0, fg=[0, 0, 255])
+            disp.print("Please Wait", posy=40, fg=[255, 255, 255])
+            disp.update()
+            state = 5
+        elif v & (buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT):
+            sys_ble.confirm_compare_value(False)
+            state = 6
+
+    elif state == 5:
+        # Wait for pairing to complete
+        if ble_event == sys_ble.EVENT_PAIRING_FAILED:
+            ble_event = None
+            state = 6
+        elif ble_event == sys_ble.EVENT_PAIRING_COMPLETE:
+            ble_event = None
+            disp.clear()
+            disp.print("BLE Bonding", posy=0, fg=[0, 0, 255])
+            disp.print("  Success", posy=40, fg=[0, 255, 0])
+            disp.update()
+            time.sleep(5)
+            os.exec("main.py")
+
+    elif state == 6:
+        # display fail screen and wait 5 seconds
+        disp.clear()
+        disp.print("BLE Bonding", posy=0, fg=[0, 0, 255])
+        disp.print("   Fail", posy=40, fg=[255, 0, 0])
+        disp.update()
+        time.sleep(5)
+        state = 1
 
-    selector()
-    disp.update()
     time.sleep(0.1)
diff --git a/pycardium/meson.build b/pycardium/meson.build
index d12e62f5ed6e58cd8699890a404ffc23a4b33f15..bd01afb49f403ec057b533285aef86da173cc334 100644
--- a/pycardium/meson.build
+++ b/pycardium/meson.build
@@ -14,6 +14,7 @@ modsrc = files(
   'modules/os.c',
   'modules/personal_state.c',
   'modules/power.c',
+  'modules/sys_ble.c',
   'modules/sys_bme680.c',
   'modules/sys_display.c',
   'modules/sys_leds.c',
diff --git a/pycardium/modules/interrupt.c b/pycardium/modules/interrupt.c
index ad3b86d73424c91bfe6dde576f92dd4a842da968..1e7fcd89ff85d0940611f37ae170e0321aa04a27 100644
--- a/pycardium/modules/interrupt.c
+++ b/pycardium/modules/interrupt.c
@@ -99,6 +99,7 @@ static const mp_rom_map_elem_t interrupt_module_globals_table[] = {
 	  MP_OBJ_NEW_SMALL_INT(EPIC_INT_MAX30001_ECG) },
 	{ MP_ROM_QSTR(MP_QSTR_MAX86150),
 	  MP_OBJ_NEW_SMALL_INT(EPIC_INT_MAX86150) },
+	{ MP_ROM_QSTR(MP_QSTR_BLE), MP_OBJ_NEW_SMALL_INT(EPIC_INT_BLE) },
 
 };
 static MP_DEFINE_CONST_DICT(
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index 2fed1b2c12052473bf878c2fb1536304df3be6fd..eb52eba6bb0334b3c02ef5857b64d7c86cf58800 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -190,6 +190,20 @@ Q(MAX86150)
 Q(ws2812)
 Q(set_all)
 
+/* config */
 Q(config)
 Q(set_string)
 Q(get_string)
+
+/* BLE */
+Q(BLE)
+Q(ble)
+Q(get_compare_value)
+Q(confirm_compare_value)
+Q(set_bondable)
+Q(get_event)
+Q(EVENT_NONE)
+Q(EVENT_HANDLE_NUMERIC_COMPARISON)
+Q(EVENT_PAIRING_COMPLETE)
+Q(EVENT_PAIRING_FAILED)
+
diff --git a/pycardium/modules/sys_ble.c b/pycardium/modules/sys_ble.c
new file mode 100644
index 0000000000000000000000000000000000000000..d262ce4100889f5c8489384a448b12b14ced653a
--- /dev/null
+++ b/pycardium/modules/sys_ble.c
@@ -0,0 +1,71 @@
+#include "epicardium.h"
+
+#include "py/obj.h"
+#include "py/objlist.h"
+#include "py/runtime.h"
+
+#include <stdint.h>
+
+static mp_obj_t mp_ble_confirm_compare_value(mp_obj_t confirmed_obj)
+{
+	bool confirmed = mp_obj_is_true(confirmed_obj);
+	epic_ble_compare_response(confirmed);
+	return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(
+	ble_confirm_compare_value_obj, mp_ble_confirm_compare_value
+);
+
+static mp_obj_t mp_ble_get_compare_value(void)
+{
+	return mp_obj_new_int(epic_ble_get_compare_value());
+}
+static MP_DEFINE_CONST_FUN_OBJ_0(
+	ble_get_compare_value_obj, mp_ble_get_compare_value
+);
+
+static mp_obj_t mp_ble_get_event(void)
+{
+	return mp_obj_new_int(epic_ble_get_event());
+}
+static MP_DEFINE_CONST_FUN_OBJ_0(ble_get_event_obj, mp_ble_get_event);
+
+static mp_obj_t mp_ble_set_bondable(mp_obj_t bondable_obj)
+{
+	bool bondable = mp_obj_is_true(bondable_obj);
+	epic_ble_set_bondable(bondable);
+	return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(ble_set_bondable_obj, mp_ble_set_bondable);
+
+static const mp_rom_map_elem_t ble_module_globals_table[] = {
+	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys_ble) },
+	{ MP_ROM_QSTR(MP_QSTR_confirm_compare_value),
+	  MP_ROM_PTR(&ble_confirm_compare_value_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_get_compare_value),
+	  MP_ROM_PTR(&ble_get_compare_value_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_get_event), MP_ROM_PTR(&ble_get_event_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_set_bondable),
+	  MP_ROM_PTR(&ble_set_bondable_obj) },
+
+	/* Event Numbers */
+	{ MP_ROM_QSTR(MP_QSTR_EVENT_NONE),
+	  MP_OBJ_NEW_SMALL_INT(BLE_EVENT_NONE) },
+	{ MP_ROM_QSTR(MP_QSTR_EVENT_HANDLE_NUMERIC_COMPARISON),
+	  MP_OBJ_NEW_SMALL_INT(BLE_EVENT_HANDLE_NUMERIC_COMPARISON) },
+	{ MP_ROM_QSTR(MP_QSTR_EVENT_PAIRING_FAILED),
+	  MP_OBJ_NEW_SMALL_INT(BLE_EVENT_PAIRING_FAILED) },
+	{ MP_ROM_QSTR(MP_QSTR_EVENT_PAIRING_COMPLETE),
+	  MP_OBJ_NEW_SMALL_INT(BLE_EVENT_PAIRING_COMPLETE) },
+
+};
+static MP_DEFINE_CONST_DICT(ble_module_globals, ble_module_globals_table);
+
+const mp_obj_module_t ble_module = {
+	.base    = { &mp_type_module },
+	.globals = (mp_obj_dict_t *)&ble_module_globals,
+};
+
+/* Register the module to make it available in Python */
+/* clang-format off */
+MP_REGISTER_MODULE(MP_QSTR_sys_ble, ble_module, MODULE_BLE_ENABLED);
diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h
index 463891e0b3fb7db8fcdf33a4255a1218b65e2f82..7a57e34f645b0695cb1a0bb7d712caca65ea89b8 100644
--- a/pycardium/mpconfigport.h
+++ b/pycardium/mpconfigport.h
@@ -71,6 +71,7 @@ int mp_hal_trng_read_int(void);
 #define MODULE_VIBRA_ENABLED                (1)
 #define MODULE_WS2812_ENABLED               (1)
 #define MODULE_CONFIG_ENABLED               (1)
+#define MODULE_BLE_ENABLED                  (1)
 
 /*
  * This port is intended to be 32-bit, but unfortunately, int32_t for