From a6f4e1966d7f00a88978f2acaac72ec0c5d1e803 Mon Sep 17 00:00:00 2001
From: schneider <schneider@blinkenlichts.net>
Date: Sat, 16 Jan 2021 03:32:57 +0100
Subject: [PATCH] feat(mp-ble): More MicroPython BLE hacks

---
 epicardium/ble/ble.c                    |   1 -
 epicardium/ble/ble_api.h                |   4 +
 epicardium/ble/ble_main.c               |  41 +--
 epicardium/ble/epic_att_api.c           | 126 ++++++++
 epicardium/ble/hid.c                    |  10 +-
 epicardium/ble/meson.build              |   1 +
 epicardium/ble/stack.c                  |   1 +
 epicardium/epicardium.h                 |  54 +++-
 lib/micropython/meson.build             |   1 +
 pycardium/modules/modbluetooth_card10.c | 412 +++++++++++++++++++-----
 pycardium/mpconfigport.h                |  10 +
 11 files changed, 542 insertions(+), 119 deletions(-)
 create mode 100644 epicardium/ble/epic_att_api.c

diff --git a/epicardium/ble/ble.c b/epicardium/ble/ble.c
index bfb33db01..2ef81b930 100644
--- a/epicardium/ble/ble.c
+++ b/epicardium/ble/ble.c
@@ -428,7 +428,6 @@ void vBleTask(void *pvParameters)
 	setAddress();
 
 	BleStart();
-	AttsDynInit();
 
 	bleuart_init();
 	bleFileTransfer_init();
diff --git a/epicardium/ble/ble_api.h b/epicardium/ble/ble_api.h
index 93261a58d..baad5eaf1 100644
--- a/epicardium/ble/ble_api.h
+++ b/epicardium/ble/ble_api.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "epicardium.h"
+
 #include <stdint.h>
 #include "wsf_types.h"
 
@@ -25,3 +27,5 @@ void BleStart(void);
 /* ATT client module interface. Used by main BLE module */
 void bleValueUpdate(attEvt_t *pMsg);
 void bleDiscCback(dmConnId_t connId, uint8_t status);
+void ble_trigger_event(enum ble_event_type event);
+void ble_epic_att_api_init(void);
diff --git a/epicardium/ble/ble_main.c b/epicardium/ble/ble_main.c
index 05e1da53c..c3f7d86a0 100644
--- a/epicardium/ble/ble_main.c
+++ b/epicardium/ble/ble_main.c
@@ -17,28 +17,19 @@
 #include <string.h>
 #include "wsf_types.h"
 #include "util/bstream.h"
-#include "fs/fs_util.h"
 #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"
 #include "app_api.h"
 #include "app_db.h"
-#include "app_ui.h"
-#include "app_hw.h"
 #include "svc_ch.h"
 #include "svc_core.h"
-#include "svc_hrs.h"
 #include "svc_dis.h"
 #include "svc_batt.h"
 #include "svc_hid.h"
-#include "svc_rscs.h"
-#include "bas/bas_api.h"
-#include "hrps/hrps_api.h"
-#include "rscp/rscp_api.h"
 #include "profiles/gap_api.h"
 #include "cccd.h"
 #include "ess.h"
@@ -435,8 +426,6 @@ static void bleCccCback(attsCccEvt_t *pEvt)
   }
 }
 
-
-
 /*************************************************************************************************/
 /*!
  *  \brief  Process CCC state change.
@@ -642,7 +631,7 @@ void epic_ble_compare_response(bool confirmed)
 		/* error condition */
 	}
 }
-static void trigger_event(enum ble_event_type event)
+void ble_trigger_event(enum ble_event_type event)
 {
 	bool enabled;
 	epic_interrupt_is_enabled(EPIC_INT_BLE, &enabled);
@@ -673,7 +662,7 @@ static void bleHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd)
 	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);
+	ble_trigger_event(BLE_EVENT_HANDLE_NUMERIC_COMPARISON);
 }
 
 int epic_ble_get_scan_report(struct epic_scan_report *rpt)
@@ -694,7 +683,7 @@ static void scannerScanReport(dmEvt_t *pMsg)
 
 	int next_head = (scan_reports_head + 1) % SCAN_REPORTS_NUM;
 	if(next_head == scan_reports_tail) {
-		trigger_event(BLE_EVENT_SCAN_REPORT);
+		ble_trigger_event(BLE_EVENT_SCAN_REPORT);
 		return;
 	}
 	scan_reports_head = next_head;
@@ -710,7 +699,7 @@ static void scannerScanReport(dmEvt_t *pMsg)
 
 	scan_report->directAddrType = pMsg->scanReport.directAddrType;
 	memcpy(scan_report->directAddr, pMsg->scanReport.directAddr, BDA_ADDR_LEN);
-	trigger_event(BLE_EVENT_SCAN_REPORT);
+	ble_trigger_event(BLE_EVENT_SCAN_REPORT);
 
 	if((scan_reports_head + 1) % SCAN_REPORTS_NUM == scan_reports_tail) {
 		LOG_WARN("ble", "Application missing scan results");
@@ -726,9 +715,13 @@ static void scannerScanReport(dmEvt_t *pMsg)
  *  \return None.
  */
 /*************************************************************************************************/
+#define ATT_CONNECTION_OPENED         0x81
+#define ATT_CONNECTION_CLOSED         0x82
+void send_att_event(attEvt_t *att_event);
 static void bleProcMsg(bleMsg_t *pMsg)
 {
   hciLeConnCmplEvt_t *connOpen;
+  attEvt_t e;
 
   switch(pMsg->hdr.event)
   {
@@ -773,6 +766,8 @@ static void bleProcMsg(bleMsg_t *pMsg)
                connOpen->peerAddr[1], connOpen->peerAddr[0]);
       bleESS_ccc_update();
       HidProcMsg(&pMsg->hdr);
+      e.hdr.event = ATT_CONNECTION_OPENED; e.hdr.param = connOpen->hdr.param;
+      send_att_event(&e);
       break;
 
     case DM_CONN_CLOSE_IND:
@@ -808,7 +803,8 @@ static void bleProcMsg(bleMsg_t *pMsg)
        */
       advertising_mode = APP_MODE_NONE;
       AppAdvStop();
-
+      e.hdr.param = pMsg->dm.connClose.hdr.param; e.hdr.event = ATT_CONNECTION_CLOSED;
+      send_att_event(&e);
       bleClose(pMsg);
       break;
 
@@ -821,7 +817,7 @@ static void bleProcMsg(bleMsg_t *pMsg)
       AppDbNvmStoreBond(last_pairing);
 
       pair_connId = DM_CONN_ID_NONE;
-      trigger_event(BLE_EVENT_PAIRING_COMPLETE);
+      ble_trigger_event(BLE_EVENT_PAIRING_COMPLETE);
       /* After a successful pairing, bonding is disabled again.
        * We don't want that for now. */
       AppSetBondable(TRUE);
@@ -846,7 +842,7 @@ static void bleProcMsg(bleMsg_t *pMsg)
       DmSecGenerateEccKeyReq();
 
       pair_connId = DM_CONN_ID_NONE;
-      trigger_event(BLE_EVENT_PAIRING_FAILED);
+      ble_trigger_event(BLE_EVENT_PAIRING_FAILED);
       break;
 
     case DM_SEC_ENCRYPT_IND:
@@ -945,9 +941,11 @@ static void BleHandler(wsfEventMask_t event, wsfMsgHdr_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]);
+      //if(!(pMsg->event == ATTS_HANDLE_VALUE_CNF && pMsg->status == ATT_SUCCESS) )
+        LOG_INFO("ble", "Ble got evt %d (%s): %d", pMsg->event, att_events[pMsg->event - ATT_CBACK_START], pMsg->status);
       /* process discovery-related ATT messages */
       AppDiscProcAttMsg((attEvt_t *) pMsg);
+      send_att_event((attEvt_t *)pMsg);
     }
     else if (pMsg->event >= L2C_COC_CBACK_START && pMsg->event <= L2C_COC_CBACK_CBACK_END)
     {
@@ -972,7 +970,6 @@ static void BleHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg)
 /*************************************************************************************************/
 void BleStart(void)
 {
-
   BleHandlerInit();
 
   /* Register for stack callbacks */
@@ -992,7 +989,11 @@ void BleStart(void)
   if(config_get_boolean_with_default("ble_hid_enable", false)) {
     hid_init();
   }
+
+  ble_epic_att_api_init();
+
   /* Reset the device */
   DmDevReset();
 }
+
 /* clang-format on */
diff --git a/epicardium/ble/epic_att_api.c b/epicardium/ble/epic_att_api.c
new file mode 100644
index 000000000..b492d32fb
--- /dev/null
+++ b/epicardium/ble/epic_att_api.c
@@ -0,0 +1,126 @@
+#include "ble_api.h"
+#include "epicardium.h"
+#include "modules/log.h"
+
+#include "wsf_types.h"
+#include "util/bstream.h"
+#include "wsf_msg.h"
+#include "att_api.h"
+
+#include "FreeRTOS.h"
+#include "queue.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#define ATT_QUEUE_SIZE 10
+static QueueHandle_t att_queue;
+static uint8_t att_queue_buffer[sizeof(attEvt_t) * ATT_QUEUE_SIZE];
+static StaticQueue_t att_queue_data;
+
+int epic_ble_get_att_event(struct epic_att_event *e)
+{
+	if (xQueueReceive(att_queue, e, 0) != pdTRUE) {
+		return -ENOENT;
+	}
+	return uxQueueMessagesWaiting(att_queue);
+}
+
+// TODO: make static again once ble_main does not use it anymore
+void send_att_event(attEvt_t *att_event)
+{
+	if (xQueueSend(att_queue, att_event, 0) != pdTRUE) {
+		LOG_WARN("ble", "could not queue att event");
+	}
+	ble_trigger_event(BLE_EVENT_ATT_EVENT);
+}
+
+#define ATT_WRITE_CALLBACK 0x80
+
+static uint8_t DynAttsWriteCback(
+	dmConnId_t connId,
+	uint16_t handle,
+	uint8_t operation,
+	uint16_t offset,
+	uint16_t len,
+	uint8_t *pValue,
+	attsAttr_t *pAttr
+) {
+	if (AttsSetAttr(handle, len, pValue) == ATT_SUCCESS) {
+		attEvt_t att_event;
+		att_event.hdr.event = ATT_WRITE_CALLBACK;
+		att_event.hdr.param = connId;
+		att_event.handle    = handle;
+		att_event.valueLen  = len;
+		send_att_event(&att_event);
+	}
+
+	return ATT_SUCCESS;
+}
+
+#define ATTS_DYN_GROUP_COUNT 8
+#define ATTS_DYN_START_HANDLE 0x200
+static int next_start_handle                  = ATTS_DYN_START_HANDLE;
+static void *dyn_groups[ATTS_DYN_GROUP_COUNT] = {};
+static int next_dyn_group                     = 0;
+
+int epic_atts_dyn_create_group(
+	uint16_t group_size, void **pSvcHandle, uint16_t *start_handle
+) {
+	*start_handle  = next_start_handle;
+	int end_handle = *start_handle + group_size - 1;
+	next_start_handle += group_size;
+
+	*pSvcHandle = AttsDynCreateGroup(*start_handle, end_handle);
+	dyn_groups[next_dyn_group++] = *pSvcHandle;
+
+	//AttsDynRegister(pSvcHandle, DynAttsReadCback, DynAttsWriteCback);
+	AttsDynRegister(*pSvcHandle, NULL, DynAttsWriteCback);
+
+	// TODO: validate parameters and pointer and current service count
+	return 0;
+}
+
+int epic_atts_dyn_send_service_changed_ind(void)
+{
+	// TODO: This is copied from an upstream stack version
+	// TODO: Handling of CCCDs in pairings is still broken
+	//GattSendServiceChangedInd(1, ATT_HANDLE_START, ATT_HANDLE_MAX);
+	return 0;
+}
+
+int epic_ble_atts_dyn_delete_groups(void)
+{
+	for (int i = 0; i < ATTS_DYN_GROUP_COUNT; i++) {
+		if (dyn_groups[i]) {
+			AttsDynDeleteGroup(dyn_groups[i]);
+			dyn_groups[i] = NULL;
+		}
+	}
+	next_dyn_group    = 0;
+	next_start_handle = ATTS_DYN_START_HANDLE;
+	return 0;
+}
+
+void ble_epic_att_api_init(void)
+{
+	att_queue = xQueueCreateStatic(
+		ATT_QUEUE_SIZE,
+		sizeof(attEvt_t),
+		att_queue_buffer,
+		&att_queue_data
+	);
+}
+
+int epic_ble_atts_handle_value_ntf(
+	uint8_t connId, uint16_t handle, uint16_t valueLen, uint8_t *pValue
+) {
+	if (!DmConnInUse(connId)) {
+		return -EIO;
+	}
+
+	// TODO: There is a race condition here. Ideally AttsHandleValueNtf would return an error or
+	// raise a callback
+	AttsHandleValueNtf(connId, handle, valueLen, pValue);
+	return 0;
+}
diff --git a/epicardium/ble/hid.c b/epicardium/ble/hid.c
index 4386f5b1d..56f511421 100644
--- a/epicardium/ble/hid.c
+++ b/epicardium/ble/hid.c
@@ -208,17 +208,9 @@ static void hidAppReportInit(void)
 void hid_work_init(void);
 void hid_init(void)
 {
-#ifdef HID_ATT_DYNAMIC
-	/* Initialize the dynamic service system */
-	AttsDynInit();
-	/* Add the HID service dynamically */
-	pSHdl = SvcHidAddGroupDyn();
-	AttsDynRegister(pSHdl, NULL, HidAttsWriteCback);
-#else
-	/* Add the HID service statically */
 	SvcHidAddGroup();
 	SvcHidRegister(HidAttsWriteCback, NULL);
-#endif /* HID_ATT_DYNAMIC */
+
 	/* Initialize the HID profile */
 	HidInit(&hidAppHidConfig);
 
diff --git a/epicardium/ble/meson.build b/epicardium/ble/meson.build
index 341ad2257..66a4566ad 100644
--- a/epicardium/ble/meson.build
+++ b/epicardium/ble/meson.build
@@ -1,5 +1,6 @@
 ble_sources = files(
   'ble.c',
+  'epic_att_api.c',
   'stack.c',
   'ble_main.c',
   'ble_attc.c',
diff --git a/epicardium/ble/stack.c b/epicardium/ble/stack.c
index d5e092179..4bf4696fd 100644
--- a/epicardium/ble/stack.c
+++ b/epicardium/ble/stack.c
@@ -184,6 +184,7 @@ void StackInit(void)
   AttHandlerInit(handlerId);
   AttsInit();
   AttsIndInit();
+  AttsDynInit();
   AttcInit();
 
   handlerId = WsfOsSetNextHandler(SmpHandler);
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 39cf79daa..f56b6c002 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -162,13 +162,18 @@ typedef _Bool bool;
 
 #define API_BLE_HID_SEND_REPORT        0x150
 
-#define API_BLE_ATTS_DYN_CREATE_GROUP    0x160
-#define API_BLE_ATTS_DYN_DELETE_GROUP    0x161
-#define API_BLE_ATTS_DYN_ADD_ATTR_DYN    0x163
-#define API_BLE_ATTS_DYN_ADD_ATTR        0x164
-#define API_BLE_ATTS_SET_ATTR            0x165
-#define API_BLE_ATTS_HANDLE_VALUE_NTF    0x166
-#define API_BLE_ATTS_GET_ATTR            0x167
+#define API_BLE_ATTS_DYN_CREATE_GROUP         0x160
+//#define API_BLE_ATTS_DYN_DELETE_GROUP         0x160
+#define API_BLE_ATTS_DYN_REGISTER             0x162
+#define API_BLE_ATTS_DYN_ADD_ATTR_DYN         0x163
+#define API_BLE_ATTS_DYN_ADD_ATTR             0x164
+#define API_BLE_ATTS_SET_ATTR                 0x165
+#define API_BLE_ATTS_HANDLE_VALUE_NTF         0x166
+#define API_BLE_ATTS_GET_ATTR                 0x167
+#define API_BLE_ATT_GET_EVENT                 0x168
+#define API_BLE_ATTS_SEND_SERVICE_CHANGED_IND 0x169
+#define API_BLE_ATTS_DYN_DELETE_GROUPS        0x16A
+
 /* clang-format on */
 
 typedef uint32_t api_int_id_t;
@@ -2337,6 +2342,7 @@ enum ble_event_type {
 	BLE_EVENT_PAIRING_COMPLETE                        = 3,
 	/** New scan data is available  */
 	BLE_EVENT_SCAN_REPORT                             = 4,
+	BLE_EVENT_ATT_EVENT                               = 5,
 };
 
 
@@ -2506,12 +2512,40 @@ API(API_BLE_GET_SCAN_REPORT, int epic_ble_get_scan_report(struct epic_scan_repor
  */
 API(API_BLE_HID_SEND_REPORT, int epic_ble_hid_send_report(uint8_t report_id, uint8_t *data, uint8_t len));
 
-API(API_BLE_ATTS_DYN_CREATE_GROUP, void *AttsDynCreateGroup(uint16_t startHandle, uint16_t endHandle));
-API(API_BLE_ATTS_DYN_DELETE_GROUP, void AttsDynDeleteGroup(void *pSvcHandle));
+API(API_BLE_ATTS_DYN_CREATE_GROUP, int epic_atts_dyn_create_group(uint16_t group_size, void **pSvcHandle, uint16_t *start_handle));
+//API(API_BLE_ATTS_DYN_DELETE_GROUP, void AttsDynDeleteGroup(void *pSvcHandle));
+API(API_BLE_ATTS_DYN_DELETE_GROUPS, int epic_ble_atts_dyn_delete_groups(void));
+
 API(API_BLE_ATTS_DYN_ADD_ATTR_DYN, void AttsDynAddAttrDyn(void *pSvcHandle, const uint8_t *pUuid, uint8_t uuidLen, const uint8_t *pValue, uint16_t len, uint16_t maxLen, uint8_t settings, uint8_t permissions));
 API(API_BLE_ATTS_DYN_ADD_ATTR, void AttsDynAddAttr(void *pSvcHandle, const uint8_t *pUuid, const uint8_t *pValue, uint16_t len, uint16_t maxLen, uint8_t settings, uint8_t permissions));
+
+API(API_BLE_ATTS_SEND_SERVICE_CHANGED_IND, int epic_atts_dyn_send_service_changed_ind(void));
+
 API(API_BLE_ATTS_SET_ATTR, uint8_t AttsSetAttr(uint16_t handle, uint16_t valueLen, uint8_t *pValue));
-API(API_BLE_ATTS_HANDLE_VALUE_NTF, void AttsHandleValueNtf(uint8_t connId, uint16_t handle, uint16_t valueLen, uint8_t *pValue));
 API(API_BLE_ATTS_GET_ATTR, uint8_t AttsGetAttr(uint16_t handle, uint16_t *pLen, uint8_t **pValue));
+API(API_BLE_ATTS_HANDLE_VALUE_NTF, int epic_ble_atts_handle_value_ntf(uint8_t connId, uint16_t handle, uint16_t valueLen, uint8_t *pValue));
+
+
+struct epic_wsf_header
+{
+  uint16_t        param;          /*!< \brief General purpose parameter passed to event handler */
+  uint8_t         event;          /*!< \brief General purpose event value passed to event handler */
+  uint8_t         status;         /*!< \brief General purpose status value passed to event handler */
+};
+
+struct epic_att_event
+{
+  struct epic_wsf_header hdr;          /*!< \brief Header structure */
+  uint8_t               *pValue;      /*!< \brief Value */
+  uint16_t              valueLen;     /*!< \brief Value length */
+  uint16_t              handle;       /*!< \brief Attribute handle */
+  uint8_t                continuing;   /*!< \brief TRUE if more response packets expected */
+  uint16_t              mtu;          /*!< \brief Negotiated MTU value */
+};
+
+
+API(API_BLE_ATT_GET_EVENT, int epic_ble_get_att_event(struct epic_att_event *e));
+
+
 #endif /* _EPICARDIUM_H */
 
diff --git a/lib/micropython/meson.build b/lib/micropython/meson.build
index bf021db1a..f85af62c9 100644
--- a/lib/micropython/meson.build
+++ b/lib/micropython/meson.build
@@ -157,6 +157,7 @@ micropython_sources = files(
   'micropython/py/pystack.c',
   'micropython/py/qstr.c',
   'micropython/py/reader.c',
+  'micropython/py/ringbuf.c',
   'micropython/py/repl.c',
   'micropython/py/runtime.c',
   'micropython/py/runtime_utils.c',
diff --git a/pycardium/modules/modbluetooth_card10.c b/pycardium/modules/modbluetooth_card10.c
index b59b2f192..b86bcf4c9 100644
--- a/pycardium/modules/modbluetooth_card10.c
+++ b/pycardium/modules/modbluetooth_card10.c
@@ -1,34 +1,252 @@
 #include "extmod/modbluetooth.h"
 #include "py/runtime.h"
 #include <stdint.h>
+#include <stdio.h>
 #include <string.h>
 
+#define ATTS_SET_WRITE_CBACK                                                   \
+	0x02 /*!< \brief Set if the group callback is executed when
+                                                             this attribute is written by a client device */
+#define ATTS_SET_READ_CBACK                                                    \
+	0x04 /*!< \brief Set if the group callback is executed when
+                                                             this attribute is read by a client device */
+#define ATTS_SET_VARIABLE_LEN                                                  \
+	0x08 /*!< \brief Set if the attribute has a variable length */
+#define ATTS_SET_ALLOW_OFFSET                                                  \
+	0x10 /*!< \brief Set if writes are allowed with an offset */
+
+#define ATTS_PERMIT_READ 0x01 /*!< \brief Set if attribute can be read */
+#define ATTS_PERMIT_READ_AUTH                                                  \
+	0x02 /*!< \brief Set if attribute read requires authentication */
+#define ATTS_PERMIT_READ_AUTHORIZ                                              \
+	0x04 /*!< \brief Set if attribute read requires authorization */
+#define ATTS_PERMIT_READ_ENC                                                   \
+	0x08 /*!< \brief Set if attribute read requires encryption */
+#define ATTS_PERMIT_WRITE 0x10 /*!< \brief Set if attribute can be written */
+#define ATTS_PERMIT_WRITE_AUTH                                                 \
+	0x20 /*!< \brief Set if attribute write requires authentication */
+#define ATTS_PERMIT_WRITE_AUTHORIZ                                             \
+	0x40 /*!< \brief Set if attribute write requires authorization */
+#define ATTS_PERMIT_WRITE_ENC                                                  \
+	0x80 /*!< \brief Set if attribute write requires encryption */
+
+#define UINT16_TO_BYTES(n) ((uint8_t)(n)), ((uint8_t)((n) >> 8))
+#define ATT_UUID_PRIMARY_SERVICE 0x2800
+#define ATT_UUID_CHARACTERISTIC 0x2803
+#define ATT_UUID_CLIENT_CHAR_CONFIG 0x2902
+
+const uint8_t attPrimSvcUuid[]  = { UINT16_TO_BYTES(ATT_UUID_PRIMARY_SERVICE) };
+const uint8_t attChUuid[]       = { UINT16_TO_BYTES(ATT_UUID_CHARACTERISTIC) };
+const uint8_t attCliChCfgUuid[] = { UINT16_TO_BYTES(
+	ATT_UUID_CLIENT_CHAR_CONFIG) };
+
+#define ATT_SUCCESS 0x00         /*!< \brief Operation successful */
+#define ATT_ERR_HANDLE 0x01      /*!< \brief Invalid handle */
+#define ATT_ERR_READ 0x02        /*!< \brief Read not permitted */
+#define ATT_ERR_WRITE 0x03       /*!< \brief Write not permitted */
+#define ATT_ERR_INVALID_PDU 0x04 /*!< \brief Invalid pdu */
+#define ATT_ERR_AUTH 0x05        /*!< \brief Insufficient authentication */
+#define ATT_ERR_NOT_SUP 0x06     /*!< \brief Request not supported */
+#define ATT_ERR_OFFSET 0x07      /*!< \brief Invalid offset */
+#define ATT_ERR_AUTHOR 0x08      /*!< \brief Insufficient authorization */
+#define ATT_ERR_QUEUE_FULL 0x09  /*!< \brief Prepare queue full */
+#define ATT_ERR_NOT_FOUND 0x0A   /*!< \brief Attribute not found */
+#define ATT_ERR_NOT_LONG 0x0B    /*!< \brief Attribute not long */
+#define ATT_ERR_KEY_SIZE 0x0C    /*!< \brief Insufficient encryption key size */
+#define ATT_ERR_LENGTH 0x0D      /*!< \brief Invalid attribute value length */
+#define ATT_ERR_UNLIKELY 0x0E    /*!< \brief Other unlikely error */
+#define ATT_ERR_ENC 0x0F         /*!< \brief Insufficient encryption */
+#define ATT_ERR_GROUP_TYPE 0x10  /*!< \brief Unsupported group type */
+#define ATT_ERR_RESOURCES 0x11   /*!< \brief Insufficient resources */
+#define ATT_ERR_DATABASE_OUT_OF_SYNC                                           \
+	0x12 /*!< \brief Client out of synch with database */
+#define ATT_ERR_VALUE_NOT_ALLOWED 0x13 /*!< \brief Value not allowed */
+#define ATT_ERR_WRITE_REJ 0xFC         /*!< \brief Write request rejected */
+#define ATT_ERR_CCCD 0xFD              /*!< \brief CCCD improperly configured */
+#define ATT_ERR_IN_PROGRESS 0xFE /*!< \brief Procedure already in progress */
+#define ATT_ERR_RANGE 0xFF       /*!< \brief Value out of range */
+/**@}*/
+
+/** \name Proprietary Internal Error Codes
+ * These codes may be sent to application but are not present in any ATT PDU.
+ */
+/**@{*/
+#define ATT_ERR_MEMORY 0x70      /*!< \brief Out of memory */
+#define ATT_ERR_TIMEOUT 0x71     /*!< \brief Transaction timeout */
+#define ATT_ERR_OVERFLOW 0x72    /*!< \brief Transaction overflow */
+#define ATT_ERR_INVALID_RSP 0x73 /*!< \brief Invalid response PDU */
+#define ATT_ERR_CANCELLED 0x74   /*!< \brief Request cancelled */
+#define ATT_ERR_UNDEFINED 0x75   /*!< \brief Other undefined error */
+#define ATT_ERR_REQ_NOT_FOUND                                                  \
+	0x76 /*!< \brief Required characteristic not found */
+#define ATT_ERR_MTU_EXCEEDED                                                   \
+	0x77 /*!< \brief Attribute PDU length exceeded MTU size */
+#define ATT_CONTINUING 0x78 /*!< \brief Procedure continuing */
+#define ATT_RSP_PENDING                                                        \
+	0x79 /*!< \brief Responsed delayed pending higher layer */
+
+// card10 att interface specific events
+#define ATT_WRITE_CALLBACK 0x80
+#define ATT_CONNECTION_OPENED 0x81
+#define ATT_CONNECTION_CLOSED 0x82
+
+#define ATTS_HANDLE_VALUE_CNF 15
+
+enum notification_status {
+	NOTIFICATION_STATUS_UNKNOWN,
+	NOTIFICATION_STATUS_PENDING,
+	NOTIFICATION_STATUS_SUCCESS,
+	NOTIFICATION_STATUS_OVERFLOW
+};
+
 const char *const not_implemented_message =
 	"Not (yet) implemented on card10. See https://git.card10.badge.events.ccc.de/card10/firmware/-/issues/8";
 
+typedef struct _mp_bluetooth_card10_root_pointers_t {
+	// Characteristic (and descriptor) value storage.
+	mp_gatts_db_t gatts_db;
+} mp_bluetooth_card10_root_pointers_t;
+
+#define GATTS_DB (MP_STATE_PORT(bluetooth_card10_root_pointers)->gatts_db)
+
+typedef struct {
+	enum notification_status notification_status;
+} gatts_db_entry_t;
+
+static void gatts_db_create_entry(mp_gatts_db_t db, uint16_t handle)
+{
+	mp_map_elem_t *elem = mp_map_lookup(
+		db,
+		MP_OBJ_NEW_SMALL_INT(handle),
+		MP_MAP_LOOKUP_ADD_IF_NOT_FOUND
+	);
+	gatts_db_entry_t *entry    = m_new(gatts_db_entry_t, 1);
+	entry->notification_status = NOTIFICATION_STATUS_UNKNOWN;
+	elem->value                = MP_OBJ_FROM_PTR(entry);
+}
+
+static gatts_db_entry_t *gatts_db_lookup(mp_gatts_db_t db, uint16_t handle)
+{
+	mp_map_elem_t *elem =
+		mp_map_lookup(db, MP_OBJ_NEW_SMALL_INT(handle), MP_MAP_LOOKUP);
+	if (!elem) {
+		mp_raise_OSError(-EACCES);
+	}
+	return MP_OBJ_TO_PTR(elem->value);
+}
+
 static void raise(void)
 {
 	mp_raise_NotImplementedError(not_implemented_message);
 }
 
+static void clear_events(void)
+{
+	struct epic_att_event att_event;
+	epic_ble_get_event();
+	while (epic_ble_get_att_event(&att_event) >= 0)
+		;
+}
+
+static mp_obj_t mp_ble_poll_events(mp_obj_t interrupt_id)
+{
+	struct epic_att_event att_event;
+	enum ble_event_type event = epic_ble_get_event();
+
+	if (event == BLE_EVENT_ATT_EVENT) {
+		int ret;
+		do {
+			ret = epic_ble_get_att_event(&att_event);
+
+			if (ret >= 0) {
+				printf("MP got att event %d,%d,%d\n",
+				       att_event.hdr.event,
+				       att_event.hdr.status,
+				       att_event.handle);
+				if (att_event.hdr.event ==
+				    ATTS_HANDLE_VALUE_CNF) {
+					gatts_db_entry_t *e = gatts_db_lookup(
+						GATTS_DB, att_event.handle
+					);
+					if (att_event.hdr.status ==
+					    ATT_SUCCESS) {
+						e->notification_status =
+							NOTIFICATION_STATUS_SUCCESS;
+					} else if (
+						att_event.hdr.status ==
+						ATT_ERR_OVERFLOW) {
+						e->notification_status =
+							NOTIFICATION_STATUS_OVERFLOW;
+					}
+				} else if (
+					att_event.hdr.event ==
+					ATT_WRITE_CALLBACK) {
+					mp_bluetooth_gatts_on_write(
+						att_event.hdr.param,
+						att_event.handle
+					);
+				} else if (
+					att_event.hdr.event ==
+					ATT_CONNECTION_OPENED) {
+					uint8_t event =
+						MP_BLUETOOTH_IRQ_CENTRAL_CONNECT;
+					uint16_t conn_handle =
+						att_event.hdr.param;
+					uint8_t addr_type = 0;  // TODO
+					uint8_t addr[8]   = {}; // TODO
+					mp_bluetooth_gap_on_connected_disconnected(
+						event,
+						conn_handle,
+						addr_type,
+						addr
+					);
+				} else if (
+					att_event.hdr.event ==
+					ATT_CONNECTION_CLOSED) {
+					uint8_t event =
+						MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT;
+					uint16_t conn_handle =
+						att_event.hdr.param;
+					uint8_t addr_type = 0;  // TODO
+					uint8_t addr[8]   = {}; // TODO
+					mp_bluetooth_gap_on_connected_disconnected(
+						event,
+						conn_handle,
+						addr_type,
+						addr
+					);
+				}
+			}
+		} while (ret >= 0);
+	}
+	return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(ble_event_obj, mp_ble_poll_events);
+
+mp_obj_t mp_interrupt_set_callback(mp_obj_t id_in, mp_obj_t callback_obj);
+
 // Enables the Bluetooth stack.
 int mp_bluetooth_init(void)
 {
+	MP_STATE_PORT(bluetooth_card10_root_pointers) =
+		m_new0(mp_bluetooth_card10_root_pointers_t, 1);
+	mp_bluetooth_gatts_db_create(&GATTS_DB);
+	mp_interrupt_set_callback(
+		MP_ROM_INT(EPIC_INT_BLE), (mp_obj_t *)&ble_event_obj
+	);
+	clear_events();
+	epic_interrupt_enable(EPIC_INT_BLE);
 	return 0;
 }
 
 // Disables the Bluetooth stack. Is a no-op when not enabled.
 void mp_bluetooth_deinit(void)
 {
-	raise();
+	//raise();
+	epic_interrupt_disable(EPIC_INT_BLE);
 }
 
 // Returns true when the Bluetooth stack is enabled.
-bool mp_bluetooth_is_enabled(void)
-{
-	return true;
-}
-
 bool mp_bluetooth_is_active(void)
 {
 	return true;
@@ -72,50 +290,12 @@ void mp_bluetooth_gap_advertise_stop(void)
 // Start adding services. Must be called before mp_bluetooth_register_service.
 int mp_bluetooth_gatts_register_service_begin(bool append)
 {
+	if (!append) {
+		epic_ble_atts_dyn_delete_groups();
+	}
 	return 0;
 }
 
-#define ATTS_SET_UUID_128                0x01    /*!< \brief Set if the UUID is 128 bits in length */
-#define ATTS_SET_WRITE_CBACK             0x02    /*!< \brief Set if the group callback is executed when
-                                                             this attribute is written by a client device */
-#define ATTS_SET_READ_CBACK              0x04    /*!< \brief Set if the group callback is executed when
-                                                             this attribute is read by a client device */
-#define ATTS_SET_VARIABLE_LEN            0x08    /*!< \brief Set if the attribute has a variable length */
-#define ATTS_SET_ALLOW_OFFSET            0x10    /*!< \brief Set if writes are allowed with an offset */
-#define ATTS_SET_CCC                     0x20    /*!< \brief Set if the attribute is a client characteristic
-                                                             configuration descriptor */
-#define ATTS_SET_ALLOW_SIGNED            0x40    /*!< \brief Set if signed writes are allowed */
-#define ATTS_SET_REQ_SIGNED              0x80    /*!< \brief Set if signed writes are required if link
-                                                             is not encrypted */
-
-#define ATTS_PERMIT_READ                 0x01    /*!< \brief Set if attribute can be read */
-#define ATTS_PERMIT_READ_AUTH            0x02    /*!< \brief Set if attribute read requires authentication */
-#define ATTS_PERMIT_READ_AUTHORIZ        0x04    /*!< \brief Set if attribute read requires authorization */
-#define ATTS_PERMIT_READ_ENC             0x08    /*!< \brief Set if attribute read requires encryption */
-#define ATTS_PERMIT_WRITE                0x10    /*!< \brief Set if attribute can be written */
-#define ATTS_PERMIT_WRITE_AUTH           0x20    /*!< \brief Set if attribute write requires authentication */
-#define ATTS_PERMIT_WRITE_AUTHORIZ       0x40    /*!< \brief Set if attribute write requires authorization */
-#define ATTS_PERMIT_WRITE_ENC            0x80    /*!< \brief Set if attribute write requires encryption */
-
-#define ATT_PROP_BROADCAST            0x01      /*!< \brief Permit broadcasts */
-#define ATT_PROP_READ                 0x02      /*!< \brief Permit reads */
-#define ATT_PROP_WRITE_NO_RSP         0x04      /*!< \brief Permit writes without response */
-#define ATT_PROP_WRITE                0x08      /*!< \brief Permit writes with response */
-#define ATT_PROP_NOTIFY               0x10      /*!< \brief Permit notifications */
-#define ATT_PROP_INDICATE             0x20      /*!< \brief Permit indications */
-#define ATT_PROP_AUTHENTICATED        0x40      /*!< \brief Permit signed writes */
-#define ATT_PROP_EXTENDED             0x80      /*!< \brief More properties defined in extended properties */
-
-#define ATT_16_UUID_LEN                            2
-#define UINT16_TO_BYTES(n)                         ((uint8_t) (n)), ((uint8_t)((n) >> 8))
-#define ATT_UUID_PRIMARY_SERVICE                   0x2800    /*!< \brief Primary Service */
-#define ATT_UUID_CHARACTERISTIC                    0x2803    /*!< \brief Characteristic */
-#define ATT_UUID_CLIENT_CHAR_CONFIG                0x2902    /*!< \brief Client Characteristic Configuration */
-
-const uint8_t attPrimSvcUuid[ATT_16_UUID_LEN] =    {UINT16_TO_BYTES(ATT_UUID_PRIMARY_SERVICE)};
-const uint8_t attChUuid[ATT_16_UUID_LEN] =         {UINT16_TO_BYTES(ATT_UUID_CHARACTERISTIC)};
-const uint8_t attCliChCfgUuid[ATT_16_UUID_LEN] =   {UINT16_TO_BYTES(ATT_UUID_CLIENT_CHAR_CONFIG)};
-
 // // Add a service with the given list of characteristics to the queue to be registered.
 // The value_handles won't be valid until after mp_bluetooth_register_service_end is called.
 int mp_bluetooth_gatts_register_service(
@@ -128,70 +308,120 @@ int mp_bluetooth_gatts_register_service(
 	uint16_t *handles,
 	size_t num_characteristics
 ) {
-	void *pSHdl;
+	void *pSvcHandle;
 
 	size_t num_descriptors_total = 0;
-	size_t num_ccds = 0;
-	for(size_t i=0; i<num_characteristics; i++) {
+	size_t num_ccds              = 0;
+	for (size_t i = 0; i < num_characteristics; i++) {
+		if (num_descriptors[i]) {
+			// TODO: more specific error
+			raise();
+		}
 		num_descriptors_total += num_descriptors[i];
-		if(characteristic_flags[i] & ATT_PROP_NOTIFY) {
-			// TODO: Also handle indications
+		if ((characteristic_flags[i] &
+		     MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) ||
+		    (characteristic_flags[i] &
+		     MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) {
 			num_ccds++;
 		}
 	}
 
 	// One handle for the service, one per characteristic, one per value, one per (CCC) descriptor
-	// TODO: Need to account for CCCDs
-	const uint16_t numHandles = 1 + num_characteristics * 2 + num_ccds + num_descriptors_total;
-
-	const uint16_t startHandle = 0x200;
-	const uint16_t endHandle = startHandle + numHandles - 1;
+	const uint16_t numHandles =
+		1 + num_characteristics * 2 + num_ccds + num_descriptors_total;
 
-	pSHdl = AttsDynCreateGroup(startHandle, endHandle);
-	// TODO NULL check
+	uint16_t startHandle;
+	// TODO: handle error
+	epic_atts_dyn_create_group(numHandles, &pSvcHandle, &startHandle);
 
 	/* Primary service */
-	uint16_t uuid_len = service_uuid->type == MP_BLUETOOTH_UUID_TYPE_16 ? 2 : 16;
+	uint16_t uuid_len =
+		service_uuid->type == MP_BLUETOOTH_UUID_TYPE_16 ? 2 : 16;
 	uint16_t currentHandle = startHandle;
-	AttsDynAddAttr(pSHdl, attPrimSvcUuid, service_uuid->data, uuid_len, uuid_len, 0, ATTS_PERMIT_READ);
+	AttsDynAddAttr(
+		pSvcHandle,
+		attPrimSvcUuid,
+		service_uuid->data,
+		uuid_len,
+		uuid_len,
+		0,
+		ATTS_PERMIT_READ
+	);
 
 	size_t handle_index = 0;
 	for (size_t i = 0; i < num_characteristics; ++i) {
-		uint8_t flags = characteristic_flags[i];
+		uint8_t flags                 = characteristic_flags[i];
 		mp_obj_bluetooth_uuid_t *uuid = characteristic_uuids[i];
-		uint8_t uuid_len = uuid->type == MP_BLUETOOTH_UUID_TYPE_16 ? 2 : 16;
-
+		uint8_t uuid_len =
+			uuid->type == MP_BLUETOOTH_UUID_TYPE_16 ? 2 : 16;
 
 		/* Characteristic */
 		currentHandle++;
-		uint8_t characteristic[1 + 2 + 16] = {flags, UINT16_TO_BYTES(currentHandle + 1)};
+		uint8_t characteristic[1 + 2 + 16] = {
+			flags, UINT16_TO_BYTES(currentHandle + 1)
+		};
 		memcpy(characteristic + 3, uuid->data, uuid_len);
 		uint8_t characteristic_len = 1 + 2 + uuid_len;
-		AttsDynAddAttr(pSHdl, attChUuid, characteristic, characteristic_len, characteristic_len, 0, ATTS_PERMIT_READ);
+		AttsDynAddAttr(
+			pSvcHandle,
+			attChUuid,
+			characteristic,
+			characteristic_len,
+			characteristic_len,
+			0,
+			ATTS_PERMIT_READ
+		);
 
 		/* Value */
 		currentHandle++;
 		uint8_t permissions = 0;
-		uint8_t settings = ATTS_SET_VARIABLE_LEN;
-		if(flags & ATT_PROP_READ) {
+		uint8_t settings    = ATTS_SET_VARIABLE_LEN;
+		if (flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) {
 			permissions |= ATTS_PERMIT_READ;
 		}
-		if(flags & ATT_PROP_WRITE) {
+		if (flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE) {
 			permissions |= ATTS_PERMIT_WRITE;
+			settings |= ATTS_SET_WRITE_CBACK;
 		}
 
-		AttsDynAddAttrDyn(pSHdl, uuid->data, uuid_len, NULL, 0, MP_BLUETOOTH_DEFAULT_ATTR_LEN,
-				settings, permissions);
+		AttsDynAddAttrDyn(
+			pSvcHandle,
+			uuid->data,
+			uuid_len,
+			NULL,
+			0,
+			MP_BLUETOOTH_DEFAULT_ATTR_LEN + 10,
+			settings,
+			permissions
+		);
 		handles[handle_index] = currentHandle;
+		//mp_bluetooth_gatts_db_create_entry(GATTS_DB, currentHandle, MP_BLUETOOTH_DEFAULT_ATTR_LEN);
+		gatts_db_create_entry(GATTS_DB, currentHandle);
 
-		if(flags & ATT_PROP_NOTIFY) {
+		if ((flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) ||
+		    flags & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) {
 			/* CCCD */
 			currentHandle++;
 			//uint8_t initCcc[] = {UINT16_TO_BYTES(0x0000)};
-			uint8_t initCcc[] = {UINT16_TO_BYTES(0x0001)};
-			AttsDynAddAttr(pSHdl, attCliChCfgUuid, initCcc, sizeof(initCcc), sizeof(initCcc),
-					//ATTS_SET_READ_CBACK | ATTS_SET_WRITE_CBACK, ATTS_PERMIT_READ | ATTS_PERMIT_WRITE);
-					0, ATTS_PERMIT_READ | ATTS_PERMIT_WRITE);
+			uint8_t initCcc[] = { UINT16_TO_BYTES(0x0001) };
+			// TODO: Handle CCC data
+			// Settings is 0 on purpose. If set to ATTS_SET_CCC the stack starts to try and
+			// manage the CCC data itself.
+			settings    = 0;
+			permissions = ATTS_PERMIT_READ | ATTS_PERMIT_WRITE;
+			AttsDynAddAttr(
+				pSvcHandle,
+				attCliChCfgUuid,
+				initCcc,
+				sizeof(initCcc),
+				sizeof(initCcc),
+				settings,
+				permissions
+			);
+
+			//mp_bluetooth_gatts_db_create_entry(GATTS_DB, handles[handle_index] + 1, MP_BLUETOOTH_CCCB_LEN);
+			//gatts_db_create_entry(GATTS_DB, handles[handle_index] + 1);
+			//int ret = mp_bluetooth_gatts_db_write(GATTS_DB, handles[handle_index] + 1, cccb_buf, sizeof(cccb_buf));
 		}
 
 		handle_index++;
@@ -202,6 +432,7 @@ int mp_bluetooth_gatts_register_service(
 // Register any queued services.
 int mp_bluetooth_gatts_register_service_end()
 {
+	epic_atts_dyn_send_service_changed_ind();
 	return 0;
 }
 
@@ -219,14 +450,20 @@ int mp_bluetooth_gatts_read(
 int mp_bluetooth_gatts_write(
 	uint16_t value_handle, const uint8_t *value, size_t value_len
 ) {
+	//return mp_bluetooth_gatts_db_write(MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db, value_handle, value, value_len);
 	uint8_t ret = AttsSetAttr(value_handle, value_len, (uint8_t *)value);
 	return ret;
 }
 // Notify the central that it should do a read.
 int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle)
 {
-	raise();
-	return 0;
+	// Note: cordio doesn't appear to support sending a notification without a value, so include the stored value.
+	uint8_t *data = NULL;
+	size_t len    = 0;
+	mp_bluetooth_gatts_read(value_handle, &data, &len);
+	return mp_bluetooth_gatts_notify_send(
+		conn_handle, value_handle, data, len
+	);
 }
 // Notify the central, including a data payload. (Note: does not set the gatts db value).
 int mp_bluetooth_gatts_notify_send(
@@ -235,7 +472,24 @@ int mp_bluetooth_gatts_notify_send(
 	const uint8_t *value,
 	size_t value_len
 ) {
-	AttsHandleValueNtf(conn_handle, value_handle, value_len, (uint8_t *)value);
+	gatts_db_entry_t *e    = gatts_db_lookup(GATTS_DB, value_handle);
+	e->notification_status = NOTIFICATION_STATUS_PENDING;
+	int ret                = epic_ble_atts_handle_value_ntf(
+                conn_handle, value_handle, value_len, (uint8_t *)value
+	);
+
+	if (ret < 0) {
+		return ret;
+	}
+
+	while (e->notification_status == NOTIFICATION_STATUS_PENDING) {
+		mp_ble_poll_events(0);
+	}
+
+	if (e->notification_status != NOTIFICATION_STATUS_SUCCESS) {
+		// TODO: better error mapping
+		return -EIO;
+	}
 	return 0;
 }
 // Indicate the central.
diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h
index 8749da694..1bcb79a18 100644
--- a/pycardium/mpconfigport.h
+++ b/pycardium/mpconfigport.h
@@ -77,6 +77,7 @@ int mp_hal_csprng_read_int(void);
 #define MODULE_CONFIG_ENABLED               (1)
 #define MODULE_BLE_ENABLED                  (1)
 
+#define MICROPY_BLUETOOTH_CARD10            (1)
 /*
  * This port is intended to be 32-bit, but unfortunately, int32_t for
  * different targets may be defined in different ways - either as int
@@ -112,9 +113,18 @@ typedef long mp_off_t;
 #define EPIC_INT_NUM 1
 #endif
 
+#if MICROPY_BLUETOOTH_CARD10
+struct _mp_bluetooth_card10_root_pointers_t;
+#define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_CARD10 struct _mp_bluetooth_card10_root_pointers_t *bluetooth_card10_root_pointers;
+#else
+#define MICROPY_PORT_ROOT_POINTER_BLUETOOTH_CARD10
+#endif
+
+
 /* For some reason, we need to define readline history manually */
 #define MICROPY_PORT_ROOT_POINTERS \
     const char *readline_hist[16]; \
     mp_obj_t interrupt_callbacks[EPIC_INT_NUM]; \
     void *spo2_memory; \
+    MICROPY_PORT_ROOT_POINTER_BLUETOOTH_CARD10 \
 
-- 
GitLab