Skip to content
Snippets Groups Projects
Select Git revision
  • 5d30cc29dba4e1c64907cfccad1a2b81d612092e
  • master default protected
  • fix-warnings
  • tvbgone-fixes
  • genofire/ble-follow-py
  • schneider/ble-stability-new-phy-adv
  • schneider/ble-stability
  • msgctl/gfx_rle
  • schneider/ble-stability-new-phy
  • add_menu_vibration
  • plaetzchen/ios-workaround
  • blinkisync-as-preload
  • schneider/max30001-pycardium
  • schneider/max30001-epicaridum
  • schneider/max30001
  • schneider/stream-locks
  • schneider/fundamental-test
  • schneider/ble-buffers
  • schneider/maxim-sdk-update
  • ch3/splashscreen
  • koalo/bhi160-works-but-dirty
  • v1.11
  • v1.10
  • v1.9
  • v1.8
  • v1.7
  • v1.6
  • v1.5
  • v1.4
  • v1.3
  • v1.2
  • v1.1
  • v1.0
  • release-1
  • bootloader-v1
  • v0.0
36 results

epc_usb.c

Blame
  • Forked from card10 / firmware
    Source project has a limited visibility.
    modbluetooth_card10.c 22.67 KiB
    #include "extmod/modbluetooth.h"
    #include "py/runtime.h"
    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    
    #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 */
    
    #define DM_CBACK_START 0x20 /*!< \brief DM callback event starting value */
    
    /*! \brief DM callback events */
    enum { DM_RESET_CMPL_IND = DM_CBACK_START, /*!< \brief Reset complete */
           DM_ADV_START_IND,                   /*!< \brief Advertising started */
           DM_ADV_STOP_IND,                    /*!< \brief Advertising stopped */
           DM_ADV_NEW_ADDR_IND, /*!< \brief New resolvable address has been generated */
           DM_SCAN_START_IND,    /*!< \brief Scanning started */
           DM_SCAN_STOP_IND,     /*!< \brief Scanning stopped */
           DM_SCAN_REPORT_IND,   /*!< \brief Scan data received from peer device */
           DM_CONN_OPEN_IND,     /*!< \brief Connection opened */
           DM_CONN_CLOSE_IND,    /*!< \brief Connection closed */
           DM_CONN_UPDATE_IND,   /*!< \brief Connection update complete */
           DM_SEC_PAIR_CMPL_IND, /*!< \brief Pairing completed successfully */
           DM_SEC_PAIR_FAIL_IND, /*!< \brief Pairing failed or other security failure */
           DM_SEC_ENCRYPT_IND,      /*!< \brief Connection encrypted */
           DM_SEC_ENCRYPT_FAIL_IND, /*!< \brief Encryption failed */
           DM_SEC_AUTH_REQ_IND, /*!< \brief PIN or OOB data requested for pairing */
           DM_SEC_KEY_IND,      /*!< \brief Security key indication */
           DM_SEC_LTK_REQ_IND,  /*!< \brief LTK requested for encyption */
           DM_SEC_PAIR_IND,     /*!< \brief Incoming pairing request from master */
           DM_SEC_SLAVE_REQ_IND, /*!< \brief Incoming security request from slave */
           DM_SEC_CALC_OOB_IND, /*!< \brief Result of OOB Confirm Calculation Generation */
           DM_SEC_ECC_KEY_IND, /*!< \brief Result of ECC Key Generation */
           DM_SEC_COMPARE_IND, /*!< \brief Result of Just Works/Numeric Comparison Compare Value Calculation */
           DM_SEC_KEYPRESS_IND, /*!< \brief Keypress indication from peer in passkey security */
           DM_PRIV_RESOLVED_ADDR_IND, /*!< \brief Private address resolved */
           DM_PRIV_GENERATE_ADDR_IND, /*!< \brief Private resolvable address generated */
           DM_CONN_READ_RSSI_IND,           /*!< \brief Connection RSSI read */
           DM_PRIV_ADD_DEV_TO_RES_LIST_IND, /*!< \brief Device added to resolving list */
           DM_PRIV_REM_DEV_FROM_RES_LIST_IND, /*!< \brief Device removed from resolving list */
           DM_PRIV_CLEAR_RES_LIST_IND,     /*!< \brief Resolving list cleared */
           DM_PRIV_READ_PEER_RES_ADDR_IND, /*!< \brief Peer resolving address read */
           DM_PRIV_READ_LOCAL_RES_ADDR_IND, /*!< \brief Local resolving address read */
           DM_PRIV_SET_ADDR_RES_ENABLE_IND, /*!< \brief Address resolving enable set */
           DM_REM_CONN_PARAM_REQ_IND, /*!< \brief Remote connection parameter requested */
           DM_CONN_DATA_LEN_CHANGE_IND, /*!< \brief Data length changed */
           DM_CONN_WRITE_AUTH_TO_IND, /*!< \brief Write authenticated payload complete */
           DM_CONN_AUTH_TO_EXPIRED_IND, /*!< \brief Authenticated payload timeout expired */
           DM_PHY_READ_IND,             /*!< \brief Read PHY */
           DM_PHY_SET_DEF_IND,          /*!< \brief Set default PHY */
           DM_PHY_UPDATE_IND,           /*!< \brief PHY update */
           DM_ADV_SET_START_IND,  /*!< \brief Advertising set(s) started */
           DM_ADV_SET_STOP_IND,   /*!< \brief Advertising set(s) stopped */
           DM_SCAN_REQ_RCVD_IND,  /*!< \brief Scan request received */
           DM_EXT_SCAN_START_IND, /*!< \brief Extended scanning started */
           DM_EXT_SCAN_STOP_IND,  /*!< \brief Extended scanning stopped */
           DM_EXT_SCAN_REPORT_IND, /*!< \brief Extended scan data received from peer device */
           DM_PER_ADV_SET_START_IND, /*!< \brief Periodic advertising set started */
           DM_PER_ADV_SET_STOP_IND,  /*!< \brief Periodic advertising set stopped */
           DM_PER_ADV_SYNC_EST_IND, /*!< \brief Periodic advertising sync established */
           DM_PER_ADV_SYNC_EST_FAIL_IND, /*!< \brief Periodic advertising sync establishment failed */
           DM_PER_ADV_SYNC_LOST_IND, /*!< \brief Periodic advertising sync lost */
           DM_PER_ADV_SYNC_TRSF_EST_IND, /*!< \brief Periodic advertising sync transfer established */
           DM_PER_ADV_SYNC_TRSF_EST_FAIL_IND, /*!< \brief Periodic advertising sync transfer establishment failed */
           DM_PER_ADV_SYNC_TRSF_IND, /*!< \brief Periodic advertising sync info transferred */
           DM_PER_ADV_SET_INFO_TRSF_IND, /*!< \brief Periodic advertising set sync info transferred */
           DM_PER_ADV_REPORT_IND, /*!< \brief Periodic advertising data received from peer device */
           DM_REMOTE_FEATURES_IND, /*!< \brief Remote features from peer device */
           DM_READ_REMOTE_VER_INFO_IND, /*!< \brief Remote LL version information read */
           DM_CONN_IQ_REPORT_IND, /*!< \brief IQ samples from CTE of received packet from peer device */
           DM_CTE_REQ_FAIL_IND,             /*!< \brief CTE request failed */
           DM_CONN_CTE_RX_SAMPLE_START_IND, /*!< \brief Sampling received CTE started */
           DM_CONN_CTE_RX_SAMPLE_STOP_IND, /*!< \brief Sampling received CTE stopped */
           DM_CONN_CTE_TX_CFG_IND, /*!< \brief Connection CTE transmit parameters configured */
           DM_CONN_CTE_REQ_START_IND, /*!< \brief Initiating connection CTE request started */
           DM_CONN_CTE_REQ_STOP_IND, /*!< \brief Initiating connection CTE request stopped */
           DM_CONN_CTE_RSP_START_IND, /*!< \brief Responding to connection CTE request started */
           DM_CONN_CTE_RSP_STOP_IND, /*!< \brief Responding to connection CTE request stopped */
           DM_READ_ANTENNA_INFO_IND, /*!< \brief Antenna information read */
           DM_L2C_CMD_REJ_IND,       /*!< \brief L2CAP Command Reject */
           DM_ERROR_IND,             /*!< \brief General error */
           DM_HW_ERROR_IND,          /*!< \brief Hardware error */
           DM_VENDOR_SPEC_IND        /*!< \brief Vendor specific event */
    };
    
    #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_gatts_db_t gatts_status;
    } mp_bluetooth_card10_root_pointers_t;
    
    #define GATTS_DB (MP_STATE_PORT(bluetooth_card10_root_pointers)->gatts_db)
    #define GATTS_STATUS                                                           \
    	(MP_STATE_PORT(bluetooth_card10_root_pointers)->gatts_status)
    
    typedef struct {
    	enum notification_status notification_status;
    } gatts_status_entry_t;
    
    static void gatts_status_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_status_entry_t *entry = m_new(gatts_status_entry_t, 1);
    	entry->notification_status  = NOTIFICATION_STATUS_UNKNOWN;
    	elem->value                 = MP_OBJ_FROM_PTR(entry);
    }
    
    static gatts_status_entry_t *
    gatts_status_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_ble_event ble_event;
    	int ret;
    
    	do {
    		ret = epic_ble_get_event(&ble_event);
    		if (ret >= 0) {
    			epic_ble_free_event(&ble_event);
    		}
    	} while (ret >= 0);
    }
    
    static void handle_att_event(struct epic_att_event *att_event)
    {
    	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_status_entry_t *e =
    			gatts_status_lookup(GATTS_STATUS, 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;
    		}
    	}
    }
    
    static void handle_dm_event(struct epic_dm_event *dm_event)
    {
    	struct epic_wsf_header *hdr = (struct epic_wsf_header *)dm_event;
    	printf("MP got dm event %d,%d\n", hdr->event, hdr->status);
    
    	if (hdr->event == DM_CONN_OPEN_IND) {
    		struct epic_hciLeConnCmpl_event *e =
    			(struct epic_hciLeConnCmpl_event *)dm_event;
    		mp_bluetooth_gap_on_connected_disconnected(
    			MP_BLUETOOTH_IRQ_CENTRAL_CONNECT,
    			e->hdr.param,
    			e->addrType,
    			e->peerAddr
    		);
    	} else if (hdr->event == DM_CONN_CLOSE_IND) {
    		struct epic_hciDisconnectCmpl_event *e =
    			(struct epic_hciDisconnectCmpl_event *)dm_event;
    		uint8_t addr[6] = {};
    		mp_bluetooth_gap_on_connected_disconnected(
    			MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT,
    			e->hdr.param,
    			0xFF,
    			addr
    		);
    	}
    }
    
    static void handle_att_write(struct epic_att_write *att_write)
    {
    	printf("MP got att write %d,%d\n",
    	       att_write->handle,
    	       att_write->valueLen);
    
    	mp_bluetooth_gatts_db_entry_t *entry =
    		mp_bluetooth_gatts_db_lookup(GATTS_DB, att_write->handle);
    	if (!entry) {
    		printf("ble: epic_att_write handle not found\n");
    		return;
    	}
    
    	// TODO: Use `offset` arg.
    	size_t append_offset = 0;
    	if (entry->append) {
    		append_offset = entry->data_len;
    	}
    	entry->data_len =
    		MIN(entry->data_alloc, att_write->valueLen + append_offset);
    	memcpy(entry->data + append_offset,
    	       att_write->buffer,
    	       entry->data_len - append_offset);
    
    	mp_bluetooth_gatts_on_write(att_write->hdr.param, att_write->handle);
    }
    
    static mp_obj_t mp_ble_poll_events(mp_obj_t interrupt_id)
    {
    	struct epic_ble_event ble_event;
    	int ret;
    
    	do {
    		ret = epic_ble_get_event(&ble_event);
    		if (ret >= 0) {
    			if (ble_event.type == BLE_EVENT_ATT_EVENT) {
    				handle_att_event(ble_event.att_event);
    			}
    			if (ble_event.type == BLE_EVENT_DM_EVENT) {
    				handle_dm_event(ble_event.dm_event);
    			}
    			if (ble_event.type == BLE_EVENT_ATT_WRITE) {
    				handle_att_write(ble_event.att_write);
    			}
    			epic_ble_free_event(&ble_event);
    		}
    	} 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_bluetooth_gatts_db_create(&GATTS_STATUS);
    
    	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();
    	epic_interrupt_disable(EPIC_INT_BLE);
    }
    
    // Returns true when the Bluetooth stack is enabled.
    bool mp_bluetooth_is_active(void)
    {
    	return true;
    }
    
    // Gets the MAC addr of this device in big-endian format.
    void mp_bluetooth_get_device_addr(uint8_t *addr)
    {
    }
    
    size_t mp_bluetooth_gap_get_device_name(const uint8_t **buf)
    {
    	return 0;
    }
    
    int mp_bluetooth_gap_set_device_name(const uint8_t *buf, size_t len)
    {
    	return 0;
    }
    
    // Start advertisement. Will re-start advertisement when already enabled.
    // Returns errno on failure.
    int mp_bluetooth_gap_advertise_start(
    	bool connectable,
    	int32_t interval_us,
    	const uint8_t *adv_data,
    	size_t adv_data_len,
    	const uint8_t *sr_data,
    	size_t sr_data_len
    ) {
    	// Temporarily going to scanner drops the connection
    	// TODO: modify the advertising data
    	epic_ble_set_mode(false, true);
    	epic_ble_set_mode(false, false);
    	return 0;
    }
    
    // Stop advertisement. No-op when already stopped.
    void mp_bluetooth_gap_advertise_stop(void)
    {
    	raise();
    }
    
    // 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;
    }
    
    // // 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(
    	mp_obj_bluetooth_uuid_t *service_uuid,
    	mp_obj_bluetooth_uuid_t **characteristic_uuids,
    	uint8_t *characteristic_flags,
    	mp_obj_bluetooth_uuid_t **descriptor_uuids,
    	uint8_t *descriptor_flags,
    	uint8_t *num_descriptors,
    	uint16_t *handles,
    	size_t num_characteristics
    ) {
    	void *pSvcHandle;
    
    	size_t num_descriptors_total = 0;
    	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] &
    		     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
    	const uint16_t numHandles =
    		1 + num_characteristics * 2 + num_ccds + num_descriptors_total;
    
    	uint16_t startHandle;
    	uint16_t uuid_len =
    		service_uuid->type == MP_BLUETOOTH_UUID_TYPE_16 ? 2 : 16;
    
    	// TODO: handle error
    	epic_atts_dyn_create_service(
    		service_uuid->data,
    		uuid_len,
    		numHandles,
    		&pSvcHandle,
    		&startHandle
    	);
    
    	uint16_t currentHandle = startHandle;
    
    	size_t handle_index = 0;
    	for (size_t i = 0; i < num_characteristics; ++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;
    
    		currentHandle++;
    		epic_atts_dyn_add_characteristic(
    			pSvcHandle,
    			uuid->data,
    			uuid_len,
    			flags,
    			MP_BLUETOOTH_DEFAULT_ATTR_LEN,
    			currentHandle
    		);
    
    		currentHandle++;
    		;
    		handles[handle_index] = currentHandle;
    		mp_bluetooth_gatts_db_create_entry(
    			GATTS_DB, currentHandle, MP_BLUETOOTH_DEFAULT_ATTR_LEN
    		);
    		gatts_status_create_entry(GATTS_STATUS, currentHandle);
    
    		if ((flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) ||
    		    flags & (MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE)) {
    			/* CCCD */
    			currentHandle++;
    			uint8_t initCcc[2] = {};
    			// TODO: activate notification/indications by default
    			// Not according to spec
    			if (flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) {
    				initCcc[0] |= 0x01;
    			}
    			if (flags & MP_BLUETOOTH_CHARACTERISTIC_FLAG_INDICATE) {
    				initCcc[0] |= 0x02;
    			}
    
    			// TODO: Handle CCC data
    			uint8_t ccc_uuid[] = { 0x02, 0x29 };
    			uint8_t flags = MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ |
    					MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE;
    			epic_ble_atts_dyn_add_descriptor(
    				pSvcHandle,
    				ccc_uuid,
    				sizeof(ccc_uuid),
    				flags,
    				initCcc,
    				sizeof(initCcc),
    				sizeof(initCcc)
    			);
    			epic_ble_atts_set_attr(
    				currentHandle, initCcc, sizeof(initCcc)
    			);
    			//mp_bluetooth_gatts_db_create_entry(GATTS_DB, handles[handle_index] + 1, MP_BLUETOOTH_CCCB_LEN);
    			//gatts_status_create_entry(GATTS_STATUS, handles[handle_index] + 1);
    			//int ret = mp_bluetooth_gatts_db_write(GATTS_DB, handles[handle_index] + 1, cccb_buf, sizeof(cccb_buf));
    		}
    
    		handle_index++;
    	}
    
    	return 0;
    }
    // Register any queued services.
    int mp_bluetooth_gatts_register_service_end()
    {
    	epic_atts_dyn_send_service_changed_ind();
    	return 0;
    }
    
    // Read the value from the local gatts db (likely this has been written by a central).
    int mp_bluetooth_gatts_read(
    	uint16_t value_handle, uint8_t **value, size_t *value_len
    ) {
    	return mp_bluetooth_gatts_db_read(
    		GATTS_DB, value_handle, value, value_len
    	);
    #if 0
    	uint16_t pLen;
    	uint8_t ret = AttsGetAttr(value_handle, &pLen, value);
    
    	*value_len = pLen;
    	return ret;
    #endif
    }
    // Write a value to the local gatts db (ready to be queried by a central).
    int mp_bluetooth_gatts_write(
    	uint16_t value_handle, const uint8_t *value, size_t value_len
    ) {
    	// TODO: which return value to choose?
    	mp_bluetooth_gatts_db_write(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)
    {
    	// 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);
    	mp_bluetooth_gatts_db_read(GATTS_DB, 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(
    	uint16_t conn_handle,
    	uint16_t value_handle,
    	const uint8_t *value,
    	size_t value_len
    ) {
    	gatts_status_entry_t *e =
    		gatts_status_lookup(GATTS_STATUS, 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.
    int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle)
    {
    	gatts_status_entry_t *e =
    		gatts_status_lookup(GATTS_STATUS, value_handle);
    	e->notification_status = NOTIFICATION_STATUS_PENDING;
    
    	// Note: cordio doesn't appear to support sending a notification without a value, so include the stored value.
    	uint8_t *value   = NULL;
    	size_t value_len = 0;
    	mp_bluetooth_gatts_read(value_handle, &value, &value_len);
    
    	int ret = epic_ble_atts_handle_value_ind(
    		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;
    	}
    
    	// TODO: How does the cordio stack signal that the indication was acked?
    	// Need to call mp_bluetooth_gatts_on_indicate_complete afterwards
    	return 0;
    }
    
    // Resize and enable/disable append-mode on a value.
    // Append-mode means that remote writes will append and local reads will clear after reading.
    int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append)
    {
    	// TODO; which return value to use?
    	mp_bluetooth_gatts_db_resize(GATTS_DB, value_handle, len, append);
    	return -epic_ble_atts_set_buffer(value_handle, len, append);
    }
    
    // Disconnect from a central or peripheral.
    int mp_bluetooth_gap_disconnect(uint16_t conn_handle)
    {
    	raise();
    	return 0;
    }
    
    #if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
    // Start a discovery (scan). Set duration to zero to run continuously.
    int mp_bluetooth_gap_scan_start(
    	int32_t duration_ms, int32_t interval_us, int32_t window_us
    ) {
    	raise();
    	return 0;
    }
    
    // Stop discovery (if currently active).
    int mp_bluetooth_gap_scan_stop(void)
    {
    	raise();
    	return 0;
    }
    
    // Connect to a found peripheral.
    int mp_bluetooth_gap_peripheral_connect(
    	uint8_t addr_type, const uint8_t *addr, int32_t duration_ms
    ) {
    	raise();
    	return 0;
    }
    
    // Find all primary services on the connected peripheral.
    int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle)
    {
    	raise();
    	return 0;
    }
    
    // Find all characteristics on the specified service on a connected peripheral.
    int mp_bluetooth_gattc_discover_characteristics(
    	uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle
    ) {
    	raise();
    	return 0;
    }
    
    // Find all descriptors on the specified characteristic on a connected peripheral.
    int mp_bluetooth_gattc_discover_descriptors(
    	uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle
    ) {
    	raise();
    	return 0;
    }
    
    // Initiate read of a value from the remote peripheral.
    int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle)
    {
    	raise();
    	return 0;
    }
    
    // Write the value to the remote peripheral.
    int mp_bluetooth_gattc_write(
    	uint16_t conn_handle,
    	uint16_t value_handle,
    	const uint8_t *value,
    	size_t *value_len,
    	unsigned int mode
    ) {
    	raise();
    	return 0;
    }
    #endif