diff --git a/epicardium/ble/ble_main.c b/epicardium/ble/ble_main.c index ec7d090de6f3fc817dd284e3a87083c78b0b4b4c..ce33c5bc2e66c6b0fd73157970ddb42f69b58b22 100644 --- a/epicardium/ble/ble_main.c +++ b/epicardium/ble/ble_main.c @@ -39,8 +39,13 @@ #include "rscp/rscp_api.h" #include "cccd.h" +#include "epicardium.h" +#include "api/interrupt-sender.h" #include "modules/log.h" +static bool active; +static enum ble_event_type ble_event; + /************************************************************************************************** Macros **************************************************************************************************/ @@ -191,6 +196,8 @@ 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); /*************************************************************************************************/ @@ -348,27 +355,87 @@ static void bleSetup(bleMsg_t *pMsg) AppAdvSetData(APP_ADV_DATA_CONNECTABLE, 0, NULL); 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 + /* We only want to be bondable when the appropriate dialog is open */ + AppSetBondable(FALSE); + /* TODO: Sadly, not advertising leads to a higher current consumption... */ + if(AppDbCheckBonded() == FALSE) { + AppAdvStop(); + } else { + AppAdvStart(APP_MODE_CONNECTABLE); + } + active = true; +} + +void epic_ble_set_bondable(bool bondable) +{ + if(!active) { + return; + } + + if(bondable) { + LOG_INFO("ble", "Making bondable and discoverable"); + /* We need to stop advertising in between or the + * adv set will not be changed... */ + AppAdvStop(); + AppSetBondable(TRUE); + AppAdvStart(APP_MODE_DISCOVERABLE); + } else { + LOG_INFO("ble", "Making connectable"); + AppAdvStop(); + AppSetBondable(FALSE); + if(AppDbCheckBonded()) { + AppAdvStart(APP_MODE_CONNECTABLE); + } + } } -void bleHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd) +uint32_t epic_ble_get_compare_value(void) { - uint32_t confirm = DmSecGetCompareValue(pCnfInd->confirm); - dmConnId_t pair_connId; - APP_TRACE_INFO1(">>> Confirm Value: %d <<<", confirm); - LOG_INFO("ble", "Confirm Value: %ld", confirm); + 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; - /* TODO: Verify that local and peer confirmation values match */ - LOG_INFO("ble", "Confirming"); - DmSecCompareRsp(pair_connId, TRUE); + pair_confirm_value = DmSecGetCompareValue(pCnfInd->confirm); + LOG_INFO("ble", "Confirm Value: %ld", pair_confirm_value); + trigger_event(BLE_EVENT_HANDLE_NUMERIC_COMPARISON); } /*************************************************************************************************/ @@ -454,6 +521,8 @@ static void bleProcMsg(bleMsg_t *pMsg) case DM_SEC_PAIR_CMPL_IND: LOG_INFO("ble", "Secure pairing successful, auth: 0x%02X", pMsg->dm.pairCmpl.auth); + 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); @@ -474,6 +543,8 @@ static void bleProcMsg(bleMsg_t *pMsg) pMsg->hdr.status); break; } + pair_connId = DM_CONN_ID_NONE; + trigger_event(BLE_EVENT_PAIRING_FAILED); break; case DM_SEC_ENCRYPT_IND: diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 34a5db597b2e78905cbf5c11f572bb4cc2ae5c9e..3eb57d5e62b46b0e334fc52aa22acb86ff48efd2 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -147,6 +147,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; @@ -223,9 +229,10 @@ API(API_INTERRUPT_IS_ENABLED, int epic_interrupt_is_enabled(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 +2102,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/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/preload/apps/ble/__init__.py b/preload/apps/ble/__init__.py index acf0b13530997eefaea87483283d978edf938c89..78fa562202bd14135969fd2c984ac7dd543eaacb 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,74 @@ 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("confirm:", posy=0, fg=[0, 255, 255]) + disp.print("%06d" % compare_value, posy=20, 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_RIGHT: + sys_ble.confirm_compare_value(True) + disp.clear() + disp.print("Wait", posy=0, fg=[0, 255, 255]) + disp.update() + state = 5 + elif v & buttons.BOTTOM_LEFT: + sys_ble.confirm_compare_value(False) + state = 1 + + 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("OK", posy=0, fg=[0, 255, 255]) + disp.update() + time.sleep(5) + state = 1 + + elif state == 6: + # display fail screen and wait 5 seconds + disp.clear() + disp.print("Fail", posy=0, fg=[0, 255, 255]) + 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 3a6e9dfcf47dfd16160b9b0cf76809436176bc55..0c15841d0a7e6965bfba2000189cf761b84669d8 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -189,6 +189,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