diff --git a/applications/applications.c b/applications/applications.c index f8e7f422..82a0c833 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -40,6 +40,7 @@ extern int32_t subghz_app(void* p); extern int32_t usb_mouse_app(void* p); extern int32_t usb_test_app(void* p); extern int32_t vibro_test_app(void* p); +extern int32_t ble_keyboard_app(void* p); // Plugins extern int32_t music_player_app(void* p); @@ -218,6 +219,10 @@ const size_t FLIPPER_PLUGINS_COUNT = sizeof(FLIPPER_PLUGINS) / sizeof(FlipperApp // Plugin menu const FlipperApplication FLIPPER_DEBUG_APPS[] = { +#ifdef APP_BLE_KEYBOARD + {.app = ble_keyboard_app, .name = "BLE keyboard demo", .stack_size = 1024, .icon = NULL}, +#endif + #ifdef APP_BLINK {.app = blink_test_app, .name = "Blink Test", .stack_size = 1024, .icon = NULL}, #endif diff --git a/applications/applications.mk b/applications/applications.mk index 093da7bb..e067faef 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -47,7 +47,7 @@ APP_SD_TEST = 1 APP_VIBRO_TEST = 1 APP_USB_TEST = 1 APP_DISPLAY_TEST = 1 - +APP_BLE_KEYBOARD = 1 APP_USB_MOUSE = 1 APP_BAD_USB = 1 APP_UART_ECHO = 1 @@ -167,6 +167,12 @@ CFLAGS += -DAPP_BAD_USB SRV_GUI = 1 endif +APP_BLE_KEYBOARD ?=0 +ifeq ($(APP_BLE_KEYBOARD), 1) +CFLAGS += -DAPP_BLE_KEYBOARD +SRV_GUI = 1 +endif + APP_KEYPAD_TEST ?= 0 ifeq ($(APP_KEYPAD_TEST), 1) CFLAGS += -DAPP_KEYPAD_TEST diff --git a/applications/bt/bt_service/bt.c b/applications/bt/bt_service/bt.c index bcf37fa0..074ab980 100755 --- a/applications/bt/bt_service/bt.c +++ b/applications/bt/bt_service/bt.c @@ -39,6 +39,19 @@ static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) { string_clear(pin_str); } +static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { + furi_assert(bt); + string_t pin_str; + dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); + string_init_printf(pin_str, "Verify code\n%06d", pin); + dialog_message_set_text( + bt->dialog_message, string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop); + dialog_message_set_buttons(bt->dialog_message, "Cancel", "Ok", NULL); + DialogMessageButton button = dialog_message_show(bt->dialogs, bt->dialog_message); + string_clear(pin_str); + return button == DialogMessageButtonCenter; +} + static void bt_battery_level_changed_callback(const void* _event, void* context) { furi_assert(_event); furi_assert(context); @@ -56,7 +69,8 @@ static void bt_battery_level_changed_callback(const void* _event, void* context) Bt* bt_alloc() { Bt* bt = furi_alloc(sizeof(Bt)); // Init default maximum packet size - bt->max_packet_size = FURI_HAL_BT_PACKET_SIZE_MAX; + bt->max_packet_size = FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX; + bt->profile = BtProfileSerial; // Load settings if(!bt_settings_load(&bt->bt_settings)) { bt_settings_save(&bt->bt_settings); @@ -83,27 +97,30 @@ Bt* bt_alloc() { bt->rpc = furi_record_open("rpc"); bt->rpc_event = osEventFlagsNew(NULL); + // API evnent + bt->api_event = osEventFlagsNew(NULL); + return bt; } // Called from GAP thread from Serial service -static uint16_t bt_on_data_received_callback(uint8_t* data, uint16_t size, void* context) { +static uint16_t bt_serial_event_callback(SerialServiceEvent event, void* context) { furi_assert(context); Bt* bt = context; + uint16_t ret = 0; - size_t bytes_processed = rpc_session_feed(bt->rpc_session, data, size, 1000); - if(bytes_processed != size) { - FURI_LOG_E(TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size); + if(event.event == SerialServiceEventTypeDataReceived) { + size_t bytes_processed = + rpc_session_feed(bt->rpc_session, event.data.buffer, event.data.size, 1000); + if(bytes_processed != event.data.size) { + FURI_LOG_E( + TAG, "Only %d of %d bytes processed by RPC", bytes_processed, event.data.size); + } + ret = rpc_session_get_available_size(bt->rpc_session); + } else if(event.event == SerialServiceEventTypeDataSent) { + osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_BUFF_SENT); } - return rpc_session_get_available_size(bt->rpc_session); -} - -// Called from GAP thread from Serial service -static void bt_on_data_sent_callback(void* context) { - furi_assert(context); - Bt* bt = context; - - osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_BUFF_SENT); + return ret; } // Called from RPC thread @@ -115,11 +132,11 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt size_t bytes_sent = 0; while(bytes_sent < bytes_len) { size_t bytes_remain = bytes_len - bytes_sent; - if(bytes_remain > bt->max_packet_size) { - furi_hal_bt_tx(&bytes[bytes_sent], bt->max_packet_size); - bytes_sent += bt->max_packet_size; + if(bytes_remain > FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX) { + furi_hal_bt_serial_tx(&bytes[bytes_sent], FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX); + bytes_sent += FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX; } else { - furi_hal_bt_tx(&bytes[bytes_sent], bytes_remain); + furi_hal_bt_serial_tx(&bytes[bytes_sent], bytes_remain); bytes_sent += bytes_remain; } uint32_t event_flag = @@ -130,58 +147,65 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt } } -static void bt_rpc_buffer_is_empty_callback(void* context) { - furi_assert(context); - furi_hal_bt_notify_buffer_is_empty(); -} - // Called from GAP thread -static void bt_on_gap_event_callback(BleEvent event, void* context) { +static bool bt_on_gap_event_callback(BleEvent event, void* context) { furi_assert(context); Bt* bt = context; + bool ret = false; if(event.type == BleEventTypeConnected) { // Update status bar bt->status = BtStatusConnected; BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); - // Open RPC session - FURI_LOG_I(TAG, "Open RPC connection"); - bt->rpc_session = rpc_session_open(bt->rpc); - rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback); - rpc_session_set_buffer_is_empty_callback(bt->rpc_session, bt_rpc_buffer_is_empty_callback); - rpc_session_set_context(bt->rpc_session, bt); - furi_hal_bt_set_data_event_callbacks( - RPC_BUFFER_SIZE, bt_on_data_received_callback, bt_on_data_sent_callback, bt); + if(bt->profile == BtProfileSerial) { + // Open RPC session + FURI_LOG_I(TAG, "Open RPC connection"); + bt->rpc_session = rpc_session_open(bt->rpc); + rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback); + rpc_session_set_buffer_is_empty_callback( + bt->rpc_session, furi_hal_bt_serial_notify_buffer_is_empty); + rpc_session_set_context(bt->rpc_session, bt); + furi_hal_bt_serial_set_event_callback(RPC_BUFFER_SIZE, bt_serial_event_callback, bt); + } // Update battery level PowerInfo info; power_get_info(bt->power, &info); message.type = BtMessageTypeUpdateBatteryLevel; message.data.battery_level = info.charge; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); + ret = true; } else if(event.type == BleEventTypeDisconnected) { - if(bt->rpc_session) { + if(bt->profile == BtProfileSerial && bt->rpc_session) { FURI_LOG_I(TAG, "Close RPC connection"); osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); rpc_session_close(bt->rpc_session); - furi_hal_bt_set_data_event_callbacks(0, NULL, NULL, NULL); + furi_hal_bt_serial_set_event_callback(0, NULL, NULL); bt->rpc_session = NULL; } + ret = true; } else if(event.type == BleEventTypeStartAdvertising) { bt->status = BtStatusAdvertising; BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); + ret = true; } else if(event.type == BleEventTypeStopAdvertising) { bt->status = BtStatusOff; BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); + ret = true; } else if(event.type == BleEventTypePinCodeShow) { BtMessage message = { .type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code}; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); + ret = true; + } else if(event.type == BleEventTypePinCodeVerify) { + ret = bt_pin_code_verify_event_handler(bt, event.data.pin_code); } else if(event.type == BleEventTypeUpdateMTU) { bt->max_packet_size = event.data.max_packet_size; + ret = true; } + return ret; } static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) { @@ -204,29 +228,56 @@ static void bt_statusbar_update(Bt* bt) { } } +static void bt_change_profile(Bt* bt, BtMessage* message) { + if(bt->profile == BtProfileSerial && bt->rpc_session) { + FURI_LOG_I(TAG, "Close RPC connection"); + osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); + rpc_session_close(bt->rpc_session); + furi_hal_bt_serial_set_event_callback(0, NULL, NULL); + bt->rpc_session = NULL; + } + + FuriHalBtProfile furi_profile; + if(message->data.profile == BtProfileHidKeyboard) { + furi_profile = FuriHalBtProfileHidKeyboard; + } else { + furi_profile = FuriHalBtProfileSerial; + } + + if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) { + FURI_LOG_I(TAG, "Bt App started"); + if(bt->bt_settings.enabled) { + furi_hal_bt_start_advertising(); + } + furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); + bt->profile = message->data.profile; + *message->result = true; + } else { + FURI_LOG_E(TAG, "Failed to start Bt App"); + *message->result = false; + } + osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT); +} + int32_t bt_srv() { Bt* bt = bt_alloc(); furi_record_create("bt", bt); // Read keys if(!bt_load_key_storage(bt)) { - FURI_LOG_W(TAG, "Failed to load saved bonding keys"); + FURI_LOG_W(TAG, "Failed to load bonding keys"); } - // Start 2nd core - if(!furi_hal_bt_start_core2()) { - FURI_LOG_E(TAG, "Core2 startup failed"); - } else { - view_port_enabled_set(bt->statusbar_view_port, true); - if(furi_hal_bt_init_app(bt_on_gap_event_callback, bt)) { - FURI_LOG_I(TAG, "BLE stack started"); - if(bt->bt_settings.enabled) { - furi_hal_bt_start_advertising(); - } - } else { - FURI_LOG_E(TAG, "BT App start failed"); + + // Start BLE stack + if(furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) { + FURI_LOG_I(TAG, "BLE stack started"); + if(bt->bt_settings.enabled) { + furi_hal_bt_start_advertising(); } + furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); + } else { + FURI_LOG_E(TAG, "BT App start failed"); } - furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); // Update statusbar bt_statusbar_update(bt); @@ -239,14 +290,14 @@ int32_t bt_srv() { bt_statusbar_update(bt); } else if(message.type == BtMessageTypeUpdateBatteryLevel) { // Update battery level - if(furi_hal_bt_is_active()) { - battery_svc_update_level(message.data.battery_level); - } + furi_hal_bt_update_battery_level(message.data.battery_level); } else if(message.type == BtMessageTypePinCodeShow) { // Display PIN code bt_pin_code_show_event_handler(bt, message.data.pin_code); } else if(message.type == BtMessageTypeKeysStorageUpdated) { bt_save_key_storage(bt); + } else if(message.type == BtMessageTypeSetProfile) { + bt_change_profile(bt, &message); } } return 0; diff --git a/applications/bt/bt_service/bt.h b/applications/bt/bt_service/bt.h index 0a2a924c..9f609937 100644 --- a/applications/bt/bt_service/bt.h +++ b/applications/bt/bt_service/bt.h @@ -9,6 +9,22 @@ extern "C" { typedef struct Bt Bt; +typedef enum { + BtProfileSerial, + BtProfileHidKeyboard, +} BtProfile; + +/** + * Change BLE Profile + * @note Call of this function leads to 2nd core restart + * + * @param bt Bt instance + * @param profile BtProfile + * + * @return true on success + */ +bool bt_set_profile(Bt* bt, BtProfile profile); + #ifdef __cplusplus } #endif diff --git a/applications/bt/bt_service/bt_api.c b/applications/bt/bt_service/bt_api.c new file mode 100755 index 00000000..9ff9017a --- /dev/null +++ b/applications/bt/bt_service/bt_api.c @@ -0,0 +1,15 @@ +#include "bt_i.h" + +bool bt_set_profile(Bt* bt, BtProfile profile) { + furi_assert(bt); + + // Send message + bool result = false; + BtMessage message = { + .type = BtMessageTypeSetProfile, .data.profile = profile, .result = &result}; + furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); + // Wait for unlock + osEventFlagsWait(bt->api_event, BT_API_UNLOCK_EVENT, osFlagsWaitAny, osWaitForever); + + return result; +} diff --git a/applications/bt/bt_service/bt_i.h b/applications/bt/bt_service/bt_i.h index 840d920e..4961f041 100644 --- a/applications/bt/bt_service/bt_i.h +++ b/applications/bt/bt_service/bt_i.h @@ -15,6 +15,8 @@ #include "../bt_settings.h" +#define BT_API_UNLOCK_EVENT (1UL << 0) + typedef enum { BtStatusOff, BtStatusAdvertising, @@ -26,16 +28,19 @@ typedef enum { BtMessageTypeUpdateBatteryLevel, BtMessageTypePinCodeShow, BtMessageTypeKeysStorageUpdated, + BtMessageTypeSetProfile, } BtMessageType; typedef union { uint32_t pin_code; uint8_t battery_level; + BtProfile profile; } BtMessageData; typedef struct { BtMessageType type; BtMessageData data; + bool* result; } BtMessage; struct Bt { @@ -44,6 +49,7 @@ struct Bt { uint16_t max_packet_size; BtSettings bt_settings; BtStatus status; + BtProfile profile; osMessageQueueId_t message_queue; Gui* gui; ViewPort* statusbar_view_port; @@ -53,4 +59,5 @@ struct Bt { Rpc* rpc; RpcSession* rpc_session; osEventFlagsId_t rpc_event; + osEventFlagsId_t api_event; }; diff --git a/applications/debug_tools/ble_keyboard/ble_keyboard.c b/applications/debug_tools/ble_keyboard/ble_keyboard.c new file mode 100755 index 00000000..07854382 --- /dev/null +++ b/applications/debug_tools/ble_keyboard/ble_keyboard.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include +#include + +#define TAG "BleKeyboardApp" + +typedef enum { + EventTypeInput, +} EventType; + +typedef struct { + union { + InputEvent input; + }; + EventType type; +} BleKeyboardEvent; + +static void ble_keyboard_render_callback(Canvas* canvas, void* ctx) { + canvas_clear(canvas); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 0, 10, "BLE keypad demo"); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 0, 63, "Hold [back] to exit"); +} + +static void ble_keyboard_input_callback(InputEvent* input_event, void* ctx) { + osMessageQueueId_t event_queue = ctx; + + BleKeyboardEvent event; + event.type = EventTypeInput; + event.input = *input_event; + osMessageQueuePut(event_queue, &event, 0, osWaitForever); +} + +int32_t ble_keyboard_app(void* p) { + Bt* bt = furi_record_open("bt"); + if(!bt_set_profile(bt, BtProfileHidKeyboard)) { + FURI_LOG_E(TAG, "Failed to switch profile"); + furi_record_close("bt"); + return -1; + } + bool bt_turned_on = furi_hal_bt_is_active(); + if(!bt_turned_on) { + furi_hal_bt_start_advertising(); + } + + osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(BleKeyboardEvent), NULL); + furi_check(event_queue); + ViewPort* view_port = view_port_alloc(); + + view_port_draw_callback_set(view_port, ble_keyboard_render_callback, NULL); + view_port_input_callback_set(view_port, ble_keyboard_input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + BleKeyboardEvent event; + while(1) { + osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, osWaitForever); + + if(event_status == osOK) { + if(event.type == EventTypeInput) { + if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) { + furi_hal_bt_hid_kb_release_all(); + break; + } + + if(event.input.key == InputKeyBack) { + if(event.input.type == InputTypePress) { + furi_hal_bt_hid_kb_press(KEY_ESC); + } else if(event.input.type == InputTypeRelease) { + furi_hal_bt_hid_kb_release(KEY_ESC); + } + } + + if(event.input.key == InputKeyOk) { + if(event.input.type == InputTypePress) { + furi_hal_bt_hid_kb_press(KEY_ENTER); + } else if(event.input.type == InputTypeRelease) { + furi_hal_bt_hid_kb_release(KEY_ENTER); + } + } + + if(event.input.key == InputKeyRight) { + if(event.input.type == InputTypePress) { + furi_hal_bt_hid_kb_press(KEY_RIGHT_ARROW); + } else if(event.input.type == InputTypeRelease) { + furi_hal_bt_hid_kb_release(KEY_RIGHT_ARROW); + } + } + + if(event.input.key == InputKeyLeft) { + if(event.input.type == InputTypePress) { + furi_hal_bt_hid_kb_press(KEY_LEFT_ARROW); + } else if(event.input.type == InputTypeRelease) { + furi_hal_bt_hid_kb_release(KEY_LEFT_ARROW); + } + } + + if(event.input.key == InputKeyDown) { + if(event.input.type == InputTypePress) { + furi_hal_bt_hid_kb_press(KEY_DOWN_ARROW); + } else if(event.input.type == InputTypeRelease) { + furi_hal_bt_hid_kb_release(KEY_DOWN_ARROW); + } + } + + if(event.input.key == InputKeyUp) { + if(event.input.type == InputTypePress) { + furi_hal_bt_hid_kb_press(KEY_UP_ARROW); + } else if(event.input.type == InputTypeRelease) { + furi_hal_bt_hid_kb_release(KEY_UP_ARROW); + } + } + } + } + view_port_update(view_port); + } + + if(bt_turned_on) { + furi_hal_bt_stop_advertising(); + } + // remove & free all stuff created by app + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + osMessageQueueDelete(event_queue); + furi_record_close("gui"); + bt_set_profile(bt, BtProfileSerial); + furi_record_close("bt"); + return 0; +} diff --git a/firmware/targets/f6/ble-glue/battery_service.c b/firmware/targets/f6/ble-glue/battery_service.c index 2a5dad5e..08dec734 100644 --- a/firmware/targets/f6/ble-glue/battery_service.c +++ b/firmware/targets/f6/ble-glue/battery_service.c @@ -59,6 +59,10 @@ void battery_svc_stop() { } } +bool battery_svc_is_started() { + return battery_svc != NULL; +} + bool battery_svc_update_level(uint8_t battery_charge) { // Check if service was started if(battery_svc == NULL) { diff --git a/firmware/targets/f6/ble-glue/battery_service.h b/firmware/targets/f6/ble-glue/battery_service.h index a50e607c..2d35e252 100644 --- a/firmware/targets/f6/ble-glue/battery_service.h +++ b/firmware/targets/f6/ble-glue/battery_service.h @@ -11,6 +11,8 @@ void battery_svc_start(); void battery_svc_stop(); +bool battery_svc_is_started(); + bool battery_svc_update_level(uint8_t battery_level); #ifdef __cplusplus diff --git a/firmware/targets/f6/ble-glue/ble_app.c b/firmware/targets/f6/ble-glue/ble_app.c index 40b34679..3f09d1f0 100644 --- a/firmware/targets/f6/ble-glue/ble_app.c +++ b/firmware/targets/f6/ble-glue/ble_app.c @@ -10,20 +10,24 @@ #define TAG "Bt" +#define BLE_APP_FLAG_HCI_EVENT (1UL << 0) +#define BLE_APP_FLAG_KILL_THREAD (1UL << 1) +#define BLE_APP_FLAG_ALL (BLE_APP_FLAG_HCI_EVENT | BLE_APP_FLAG_KILL_THREAD) + PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE]; typedef struct { osMutexId_t hci_mtx; osSemaphoreId_t hci_sem; - osThreadId_t hci_thread_id; - osThreadAttr_t hci_thread_attr; + FuriThread* thread; + osEventFlagsId_t event_flags; } BleApp; -static BleApp* ble_app; +static BleApp* ble_app = NULL; -static void ble_app_hci_thread(void *arg); -static void ble_app_hci_event_handler(void * pPayload); +static int32_t ble_app_hci_thread(void* context); +static void ble_app_hci_event_handler(void* pPayload); static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status); bool ble_app_init() { @@ -32,10 +36,14 @@ bool ble_app_init() { // Allocate semafore and mutex for ble command buffer access ble_app->hci_mtx = osMutexNew(NULL); ble_app->hci_sem = osSemaphoreNew(1, 0, NULL); + ble_app->event_flags = osEventFlagsNew(NULL); // HCI transport layer thread to handle user asynch events - ble_app->hci_thread_attr.name = "BleHciWorker"; - ble_app->hci_thread_attr.stack_size = 1024; - ble_app->hci_thread_id = osThreadNew(ble_app_hci_thread, NULL, &ble_app->hci_thread_attr); + ble_app->thread = furi_thread_alloc(); + furi_thread_set_name(ble_app->thread, "BleHciWorker"); + furi_thread_set_stack_size(ble_app->thread, 1024); + furi_thread_set_context(ble_app->thread, ble_app); + furi_thread_set_callback(ble_app->thread, ble_app_hci_thread); + furi_thread_start(ble_app->thread); // Initialize Ble Transport Layer HCI_TL_HciInitConf_t hci_tl_config = { @@ -92,35 +100,68 @@ void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size) { *size = sizeof(ble_app_nvm); } -static void ble_app_hci_thread(void *arg) { - while(1) { - osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); - hci_user_evt_proc(); +void ble_app_thread_stop() { + if(ble_app) { + osEventFlagsSet(ble_app->event_flags, BLE_APP_FLAG_KILL_THREAD); + furi_thread_join(ble_app->thread); + furi_thread_free(ble_app->thread); + // Wait to make sure that EventFlags delivers pending events before memory free + osDelay(50); + // Free resources + osMutexDelete(ble_app->hci_mtx); + osSemaphoreDelete(ble_app->hci_sem); + osEventFlagsDelete(ble_app->event_flags); + free(ble_app); + ble_app = NULL; + memset(&ble_app_cmd_buffer, 0, sizeof(ble_app_cmd_buffer)); } } +static int32_t ble_app_hci_thread(void *arg) { + uint32_t flags = 0; + while(1) { + flags = osEventFlagsWait(ble_app->event_flags, BLE_APP_FLAG_ALL, osFlagsWaitAny, osWaitForever); + if(flags & BLE_APP_FLAG_KILL_THREAD) { + break; + } + if(flags & BLE_APP_FLAG_HCI_EVENT) { + hci_user_evt_proc(); + } + } + + return 0; +} + // Called by WPAN lib void hci_notify_asynch_evt(void* pdata) { - osThreadFlagsSet(ble_app->hci_thread_id, 1); + if(ble_app) { + osEventFlagsSet(ble_app->event_flags, BLE_APP_FLAG_HCI_EVENT); + } } void hci_cmd_resp_release(uint32_t flag) { - osSemaphoreRelease(ble_app->hci_sem); + if(ble_app) { + osSemaphoreRelease(ble_app->hci_sem); + } } void hci_cmd_resp_wait(uint32_t timeout) { - osSemaphoreAcquire(ble_app->hci_sem, osWaitForever); + if(ble_app) { + osSemaphoreAcquire(ble_app->hci_sem, osWaitForever); + } } static void ble_app_hci_event_handler( void * pPayload ) { SVCCTL_UserEvtFlowStatus_t svctl_return_status; tHCI_UserEvtRxParam *pParam = (tHCI_UserEvtRxParam *)pPayload; - svctl_return_status = SVCCTL_UserEvtRx((void *)&(pParam->pckt->evtserial)); - if (svctl_return_status != SVCCTL_UserEvtFlowDisable) { - pParam->status = HCI_TL_UserEventFlow_Enable; - } else { - pParam->status = HCI_TL_UserEventFlow_Disable; + if(ble_app) { + svctl_return_status = SVCCTL_UserEvtRx((void *)&(pParam->pckt->evtserial)); + if (svctl_return_status != SVCCTL_UserEvtFlowDisable) { + pParam->status = HCI_TL_UserEventFlow_Enable; + } else { + pParam->status = HCI_TL_UserEventFlow_Disable; + } } } diff --git a/firmware/targets/f6/ble-glue/ble_app.h b/firmware/targets/f6/ble-glue/ble_app.h index 64000bde..062154e9 100644 --- a/firmware/targets/f6/ble-glue/ble_app.h +++ b/firmware/targets/f6/ble-glue/ble_app.h @@ -9,6 +9,7 @@ extern "C" { bool ble_app_init(); void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size); +void ble_app_thread_stop(); #ifdef __cplusplus } diff --git a/firmware/targets/f6/ble-glue/ble_glue.c b/firmware/targets/f6/ble-glue/ble_glue.c index 45503683..007f3645 100644 --- a/firmware/targets/f6/ble-glue/ble_glue.c +++ b/firmware/targets/f6/ble-glue/ble_glue.c @@ -12,6 +12,10 @@ #define TAG "Core2" +#define BLE_GLUE_FLAG_SHCI_EVENT (1UL << 0) +#define BLE_GLUE_FLAG_KILL_THREAD (1UL << 1) +#define BLE_GLUE_FLAG_ALL (BLE_GLUE_FLAG_SHCI_EVENT | BLE_GLUE_FLAG_KILL_THREAD) + #define POOL_SIZE (CFG_TLBLE_EVT_QUEUE_LENGTH*4U*DIVC(( sizeof(TL_PacketHeader_t) + TL_BLE_EVENT_FRAME_SIZE ), 4U)) PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_event_pool[POOL_SIZE]; @@ -32,8 +36,8 @@ typedef enum { typedef struct { osMutexId_t shci_mtx; osSemaphoreId_t shci_sem; - osThreadId_t shci_user_event_thread_id; - osThreadAttr_t shci_user_event_thread_attr; + osEventFlagsId_t event_flags; + FuriThread* thread; BleGlueStatus status; BleGlueKeyStorageChangedCallback callback; void* context; @@ -41,7 +45,7 @@ typedef struct { static BleGlue* ble_glue = NULL; -static void ble_glue_user_event_thread(void *argument); +static int32_t ble_glue_shci_thread(void *argument); static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status); static void ble_glue_sys_user_event_callback(void* pPayload); @@ -55,8 +59,6 @@ void ble_glue_set_key_storage_changed_callback(BleGlueKeyStorageChangedCallback void ble_glue_init() { ble_glue = furi_alloc(sizeof(BleGlue)); ble_glue->status = BleGlueStatusStartup; - ble_glue->shci_user_event_thread_attr.name = "BleShciWorker"; - ble_glue->shci_user_event_thread_attr.stack_size = 1024; // Configure the system Power Mode // Select HSI as system clock source after Wake Up from Stop mode @@ -75,9 +77,15 @@ void ble_glue_init() { ble_glue->shci_mtx = osMutexNew(NULL); ble_glue->shci_sem = osSemaphoreNew(1, 0, NULL); + ble_glue->event_flags = osEventFlagsNew(NULL); // FreeRTOS system task creation - ble_glue->shci_user_event_thread_id = osThreadNew(ble_glue_user_event_thread, NULL, &ble_glue->shci_user_event_thread_attr); + ble_glue->thread = furi_thread_alloc(); + furi_thread_set_name(ble_glue->thread, "BleShciWorker"); + furi_thread_set_stack_size(ble_glue->thread, 1024); + furi_thread_set_context(ble_glue->thread, ble_glue); + furi_thread_set_callback(ble_glue->thread, ble_glue_shci_thread); + furi_thread_start(ble_glue->thread); // System channel initialization SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&ble_glue_system_cmd_buff; @@ -205,26 +213,63 @@ static void ble_glue_sys_user_event_callback( void * pPayload ) { } } -// Wrap functions -static void ble_glue_user_event_thread(void *argument) { - UNUSED(argument); - for(;;) { - osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); - shci_user_evt_proc(); +static void ble_glue_clear_shared_memory() { + memset(ble_glue_event_pool, 0, sizeof(ble_glue_event_pool)); + memset(&ble_glue_system_cmd_buff, 0, sizeof(ble_glue_system_cmd_buff)); + memset(ble_glue_system_spare_event_buff, 0, sizeof(ble_glue_system_spare_event_buff)); + memset(ble_glue_ble_spare_event_buff, 0, sizeof(ble_glue_ble_spare_event_buff)); +} + +void ble_glue_thread_stop() { + if(ble_glue) { + osEventFlagsSet(ble_glue->event_flags, BLE_GLUE_FLAG_KILL_THREAD); + furi_thread_join(ble_glue->thread); + furi_thread_free(ble_glue->thread); + // Wait to make sure that EventFlags delivers pending events before memory free + osDelay(50); + // Free resources + osMutexDelete(ble_glue->shci_mtx); + osSemaphoreDelete(ble_glue->shci_sem); + osEventFlagsDelete(ble_glue->event_flags); + ble_glue_clear_shared_memory(); + free(ble_glue); + ble_glue = NULL; } } +// Wrap functions +static int32_t ble_glue_shci_thread(void* context) { + uint32_t flags = 0; + while(true) { + flags = osEventFlagsWait(ble_glue->event_flags, BLE_GLUE_FLAG_ALL, osFlagsWaitAny, osWaitForever); + if(flags & BLE_GLUE_FLAG_SHCI_EVENT) { + shci_user_evt_proc(); + } + if(flags & BLE_GLUE_FLAG_KILL_THREAD) { + break; + } + } + + return 0; +} + void shci_notify_asynch_evt(void* pdata) { UNUSED(pdata); - osThreadFlagsSet(ble_glue->shci_user_event_thread_id, 1); + if(ble_glue) { + osEventFlagsSet(ble_glue->event_flags, BLE_GLUE_FLAG_SHCI_EVENT); + } } void shci_cmd_resp_release(uint32_t flag) { UNUSED(flag); - osSemaphoreRelease(ble_glue->shci_sem); + if(ble_glue) { + osSemaphoreRelease(ble_glue->shci_sem); + } } void shci_cmd_resp_wait(uint32_t timeout) { UNUSED(timeout); - osSemaphoreAcquire(ble_glue->shci_sem, osWaitForever); + if(ble_glue) { + osSemaphoreAcquire(ble_glue->shci_sem, osWaitForever); + } } diff --git a/firmware/targets/f6/ble-glue/ble_glue.h b/firmware/targets/f6/ble-glue/ble_glue.h index ac668c42..a95d633c 100644 --- a/firmware/targets/f6/ble-glue/ble_glue.h +++ b/firmware/targets/f6/ble-glue/ble_glue.h @@ -38,6 +38,8 @@ bool ble_glue_is_radio_stack_ready(); */ void ble_glue_set_key_storage_changed_callback(BleGlueKeyStorageChangedCallback callback, void* context); +void ble_glue_thread_stop(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f6/ble-glue/dev_info_service.c b/firmware/targets/f6/ble-glue/dev_info_service.c index 64ff0509..e47fdf66 100644 --- a/firmware/targets/f6/ble-glue/dev_info_service.c +++ b/firmware/targets/f6/ble-glue/dev_info_service.c @@ -154,3 +154,7 @@ void dev_info_svc_stop() { dev_info_svc = NULL; } } + +bool dev_info_svc_is_started() { + return dev_info_svc != NULL; +} diff --git a/firmware/targets/f6/ble-glue/dev_info_service.h b/firmware/targets/f6/ble-glue/dev_info_service.h index 62eccefa..f5531fc7 100644 --- a/firmware/targets/f6/ble-glue/dev_info_service.h +++ b/firmware/targets/f6/ble-glue/dev_info_service.h @@ -16,6 +16,8 @@ void dev_info_svc_start(); void dev_info_svc_stop(); +bool dev_info_svc_is_started(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f6/ble-glue/gap.c b/firmware/targets/f6/ble-glue/gap.c index a2381653..7f574f0b 100644 --- a/firmware/targets/f6/ble-glue/gap.c +++ b/firmware/targets/f6/ble-glue/gap.c @@ -3,20 +3,14 @@ #include "ble.h" #include "cmsis_os.h" -#include "otp.h" -#include "dev_info_service.h" -#include "battery_service.h" -#include "serial_service.h" - #include +#include #define TAG "BtGap" #define FAST_ADV_TIMEOUT 30000 #define INITIAL_ADV_TIMEOUT 60000 -#define BD_ADDR_SIZE_LOCAL 6 - typedef struct { uint16_t gap_svc_handle; uint16_t dev_name_char_handle; @@ -24,18 +18,18 @@ typedef struct { uint16_t connection_handle; uint8_t adv_svc_uuid_len; uint8_t adv_svc_uuid[20]; + char* adv_name; } GapSvc; typedef struct { - GapSvc gap_svc; + GapSvc service; + GapConfig* config; GapState state; osMutexId_t state_mutex; - uint8_t mac_address[BD_ADDR_SIZE_LOCAL]; BleEventCallback on_event_cb; void* context; osTimerId advertise_timer; - osThreadAttr_t thread_attr; - osThreadId_t thread_id; + FuriThread* thread; osMessageQueueId_t command_queue; bool enable_adv; } Gap; @@ -44,47 +38,44 @@ typedef enum { GapCommandAdvFast, GapCommandAdvLowPower, GapCommandAdvStop, + GapCommandKillThread, } GapCommand; // Identity root key static const uint8_t gap_irk[16] = {0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0,0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0}; // Encryption root key static const uint8_t gap_erk[16] = {0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21,0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21}; -// Appearence characteristic UUID -static const uint8_t gap_appearence_char_uuid[] = {0x00, 0x86}; -// Default MAC address -static const uint8_t gap_default_mac_addr[] = {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72}; static Gap* gap = NULL; static void gap_advertise_start(GapState new_state); -static void gap_app(void *arg); +static int32_t gap_app(void* context); SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) { - hci_event_pckt *event_pckt; - evt_le_meta_event *meta_evt; - evt_blue_aci *blue_evt; - hci_le_phy_update_complete_event_rp0 *evt_le_phy_update_complete; + hci_event_pckt* event_pckt; + evt_le_meta_event* meta_evt; + evt_blue_aci* blue_evt; + hci_le_phy_update_complete_event_rp0* evt_le_phy_update_complete; uint8_t tx_phy; uint8_t rx_phy; tBleStatus ret = BLE_STATUS_INVALID_PARAMS; - event_pckt = (hci_event_pckt*) ((hci_uart_pckt *) pckt)->data; + event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data; osMutexAcquire(gap->state_mutex, osWaitForever); switch (event_pckt->evt) { case EVT_DISCONN_COMPLETE: { hci_disconnection_complete_event_rp0 *disconnection_complete_event = (hci_disconnection_complete_event_rp0 *) event_pckt->data; - if (disconnection_complete_event->Connection_Handle == gap->gap_svc.connection_handle) { - gap->gap_svc.connection_handle = 0; + if (disconnection_complete_event->Connection_Handle == gap->service.connection_handle) { + gap->service.connection_handle = 0; gap->state = GapStateIdle; - FURI_LOG_I(TAG, "Disconnect from client. Reason: %d", disconnection_complete_event->Reason); + FURI_LOG_I(TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason); } if(gap->enable_adv) { // Restart advertising - gap_start_advertising(); + gap_advertise_start(GapCommandAdvFast); furi_hal_power_insomnia_exit(); } BleEvent event = {.type = BleEventTypeDisconnected}; @@ -106,7 +97,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) } else { FURI_LOG_I(TAG, "Update PHY succeed"); } - ret = hci_le_read_phy(gap->gap_svc.connection_handle,&tx_phy,&rx_phy); + ret = hci_le_read_phy(gap->service.connection_handle,&tx_phy,&rx_phy); if(ret) { FURI_LOG_E(TAG, "Read PHY failed, status: %d", ret); } else { @@ -124,7 +115,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) // Update connection status and handle gap->state = GapStateConnected; - gap->gap_svc.connection_handle = connection_complete_event->Connection_Handle; + gap->service.connection_handle = connection_complete_event->Connection_Handle; // Start pairing by sending security request aci_gap_slave_security_req(connection_complete_event->Connection_Handle); @@ -148,8 +139,8 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) { // Generate random PIN code uint32_t pin = rand() % 999999; - aci_gap_pass_key_resp(gap->gap_svc.connection_handle, pin); - FURI_LOG_I(TAG, "Pass key request event. Pin: %d", pin); + aci_gap_pass_key_resp(gap->service.connection_handle, pin); + FURI_LOG_I(TAG, "Pass key request event. Pin: %06d", pin); BleEvent event = {.type = BleEventTypePinCodeShow, .data.pin_code = pin}; gap->on_event_cb(event, gap->context); } @@ -175,7 +166,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) case EVT_BLUE_GAP_BOND_LOST: FURI_LOG_I(TAG, "Bond lost event. Start rebonding"); - aci_gap_allow_rebond(gap->gap_svc.connection_handle); + aci_gap_allow_rebond(gap->service.connection_handle); break; case EVT_BLUE_GAP_DEVICE_FOUND: @@ -191,16 +182,20 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) break; case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: - FURI_LOG_I(TAG, "Hex_value = %lx", - ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value); - aci_gap_numeric_comparison_value_confirm_yesno(gap->gap_svc.connection_handle, 1); + { + uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value; + FURI_LOG_I(TAG, "Verify numeric comparison: %06d", pin); + BleEvent event = {.type = BleEventTypePinCodeVerify, .data.pin_code = pin}; + bool result = gap->on_event_cb(event, gap->context); + aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); break; + } case EVT_BLUE_GAP_PAIRING_CMPLT: pairing_complete = (aci_gap_pairing_complete_event_rp0*)blue_evt->data; if (pairing_complete->Status) { FURI_LOG_E(TAG, "Pairing failed with status: %d. Terminating connection", pairing_complete->Status); - aci_gap_terminate(gap->gap_svc.connection_handle, 5); + aci_gap_terminate(gap->service.connection_handle, 5); } else { FURI_LOG_I(TAG, "Pairing complete"); BleEvent event = {.type = BleEventTypeConnected}; @@ -220,46 +215,15 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) } static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) { - gap->gap_svc.adv_svc_uuid_len = 1; if(uid_len == 2) { - gap->gap_svc.adv_svc_uuid[0] = AD_TYPE_16_BIT_SERV_UUID; + gap->service.adv_svc_uuid[0] = AD_TYPE_16_BIT_SERV_UUID; } else if (uid_len == 4) { - gap->gap_svc.adv_svc_uuid[0] = AD_TYPE_32_BIT_SERV_UUID; + gap->service.adv_svc_uuid[0] = AD_TYPE_32_BIT_SERV_UUID; } else if(uid_len == 16) { - gap->gap_svc.adv_svc_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST; - } - memcpy(&gap->gap_svc.adv_svc_uuid[1], uid, uid_len); - gap->gap_svc.adv_svc_uuid_len += uid_len; -} - -GapState gap_get_state() { - return gap->state; -} - -void gap_init_mac_address(Gap* gap) { - uint8_t *otp_addr; - uint32_t udn; - uint32_t company_id; - uint32_t device_id; - - udn = LL_FLASH_GetUDN(); - if(udn != 0xFFFFFFFF) { - company_id = LL_FLASH_GetSTCompanyID(); - device_id = LL_FLASH_GetDeviceID(); - gap->mac_address[0] = (uint8_t)(udn & 0x000000FF); - gap->mac_address[1] = (uint8_t)( (udn & 0x0000FF00) >> 8 ); - gap->mac_address[2] = (uint8_t)( (udn & 0x00FF0000) >> 16 ); - gap->mac_address[3] = (uint8_t)device_id; - gap->mac_address[4] = (uint8_t)(company_id & 0x000000FF);; - gap->mac_address[5] = (uint8_t)( (company_id & 0x0000FF00) >> 8 ); - } else { - otp_addr = OTP_Read(0); - if(otp_addr) { - memcpy(gap->mac_address, ((OTP_ID0_t*)otp_addr)->bd_address, sizeof(gap->mac_address)); - } else { - memcpy(gap->mac_address, gap_default_mac_addr, sizeof(gap->mac_address)); - } + gap->service.adv_svc_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST; } + memcpy(&gap->service.adv_svc_uuid[gap->service.adv_svc_uuid_len], uid, uid_len); + gap->service.adv_svc_uuid_len += uid_len; } static void gap_init_svc(Gap* gap) { @@ -269,8 +233,7 @@ static void gap_init_svc(Gap* gap) { // HCI Reset to synchronise BLE Stack hci_reset(); // Configure mac address - gap_init_mac_address(gap); - aci_hal_write_config_data(CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, (uint8_t*)gap->mac_address); + aci_hal_write_config_data(CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, gap->config->mac_address); /* Static random Address * The two upper bits shall be set to 1 @@ -289,25 +252,42 @@ static void gap_init_svc(Gap* gap) { // Initialize GATT interface aci_gatt_init(); // Initialize GAP interface - const char *name = furi_hal_version_get_device_name_ptr(); + // Skip fist symbol AD_TYPE_COMPLETE_LOCAL_NAME + char *name = gap->service.adv_name + 1; aci_gap_init(GAP_PERIPHERAL_ROLE, 0, strlen(name), - &gap->gap_svc.gap_svc_handle, &gap->gap_svc.dev_name_char_handle, &gap->gap_svc.appearance_char_handle); + &gap->service.gap_svc_handle, &gap->service.dev_name_char_handle, &gap->service.appearance_char_handle); // Set GAP characteristics - status = aci_gatt_update_char_value(gap->gap_svc.gap_svc_handle, gap->gap_svc.dev_name_char_handle, 0, strlen(name), (uint8_t *) name); + status = aci_gatt_update_char_value(gap->service.gap_svc_handle, gap->service.dev_name_char_handle, 0, strlen(name), (uint8_t *) name); if (status) { FURI_LOG_E(TAG, "Failed updating name characteristic: %d", status); } - status = aci_gatt_update_char_value(gap->gap_svc.gap_svc_handle, gap->gap_svc.appearance_char_handle, 0, 2, gap_appearence_char_uuid); + uint8_t gap_appearence_char_uuid[2] = {gap->config->appearance_char & 0xff, gap->config->appearance_char >> 8}; + status = aci_gatt_update_char_value(gap->service.gap_svc_handle, gap->service.appearance_char_handle, 0, 2, gap_appearence_char_uuid); if(status) { FURI_LOG_E(TAG, "Failed updating appearence characteristic: %d", status); } // Set default PHY hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); // Set I/O capability - aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); + bool keypress_supported = false; + if(gap->config->pairing_method == GapPairingPinCodeShow) { + aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); + } else if(gap->config->pairing_method == GapPairingPinCodeVerifyYesNo){ + aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); + keypress_supported = true; + } // Setup authentication - aci_gap_set_authentication_requirement(1, 1, 1, 0, 8, 16, 1, 0, PUBLIC_ADDR); + aci_gap_set_authentication_requirement( + gap->config->bonding_mode, + CFG_MITM_PROTECTION, + CFG_SC_SUPPORT, + keypress_supported, + CFG_ENCRYPTION_KEY_SIZE_MIN, + CFG_ENCRYPTION_KEY_SIZE_MAX, + CFG_USED_FIXED_PIN, + 0, + PUBLIC_ADDR); // Configure whitelist aci_gap_configure_whitelist(); } @@ -336,10 +316,9 @@ static void gap_advertise_start(GapState new_state) } } // Configure advertising - const char* name = furi_hal_version_get_ble_local_device_name_ptr(); status = aci_gap_set_discoverable(ADV_IND, min_interval, max_interval, PUBLIC_ADDR, 0, - strlen(name), (uint8_t*)name, - gap->gap_svc.adv_svc_uuid_len, gap->gap_svc.adv_svc_uuid, 0, 0); + strlen(gap->service.adv_name), (uint8_t*)gap->service.adv_name, + gap->service.adv_svc_uuid_len, gap->service.adv_svc_uuid, 0, 0); if(status) { FURI_LOG_E(TAG, "Set discoverable err: %d", status); } @@ -350,11 +329,11 @@ static void gap_advertise_start(GapState new_state) } static void gap_advertise_stop() { - if(gap->state == GapStateConnected) { - // Terminate connection - aci_gap_terminate(gap->gap_svc.connection_handle, 0x13); - } if(gap->state > GapStateIdle) { + if(gap->state == GapStateConnected) { + // Terminate connection + aci_gap_terminate(gap->service.connection_handle, 0x13); + } // Stop advertising osTimerStop(gap->advertise_timer); aci_gap_set_non_discoverable(); @@ -365,17 +344,26 @@ static void gap_advertise_stop() { } void gap_start_advertising() { - FURI_LOG_I(TAG, "Start advertising"); - gap->enable_adv = true; - GapCommand command = GapCommandAdvFast; - furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); + osMutexAcquire(gap->state_mutex, osWaitForever); + if(gap->state == GapStateIdle) { + gap->state = GapStateStartingAdv; + FURI_LOG_I(TAG, "Start advertising"); + gap->enable_adv = true; + GapCommand command = GapCommandAdvFast; + furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); + } + osMutexRelease(gap->state_mutex); } void gap_stop_advertising() { - FURI_LOG_I(TAG, "Stop advertising"); - gap->enable_adv = false; - GapCommand command = GapCommandAdvStop; - furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); + osMutexAcquire(gap->state_mutex, osWaitForever); + if(gap->state > GapStateIdle) { + FURI_LOG_I(TAG, "Stop advertising"); + gap->enable_adv = false; + GapCommand command = GapCommandAdvStop; + furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); + } + osMutexRelease(gap->state_mutex); } static void gap_advetise_timer_callback(void* context) { @@ -383,44 +371,42 @@ static void gap_advetise_timer_callback(void* context) { furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); } -bool gap_init(BleEventCallback on_event_cb, void* context) { +bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) { if (!ble_glue_is_radio_stack_ready()) { return false; } gap = furi_alloc(sizeof(Gap)); + gap->config = config; srand(DWT->CYCCNT); // Create advertising timer gap->advertise_timer = osTimerNew(gap_advetise_timer_callback, osTimerOnce, NULL, NULL); // Initialization of GATT & GAP layer + gap->service.adv_name = config->adv_name; gap_init_svc(gap); // Initialization of the BLE Services SVCCTL_Init(); // Initialization of the GAP state gap->state_mutex = osMutexNew(NULL); gap->state = GapStateIdle; - gap->gap_svc.connection_handle = 0xFFFF; + gap->service.connection_handle = 0xFFFF; gap->enable_adv = true; // Thread configuration - gap->thread_attr.name = "BleGapWorker"; - gap->thread_attr.stack_size = 1024; - gap->thread_id = osThreadNew(gap_app, NULL, &gap->thread_attr); + gap->thread = furi_thread_alloc(); + furi_thread_set_name(gap->thread, "BleGapWorker"); + furi_thread_set_stack_size(gap->thread, 1024); + furi_thread_set_context(gap->thread, gap); + furi_thread_set_callback(gap->thread, gap_app); + furi_thread_start(gap->thread); // Command queue allocation gap->command_queue = osMessageQueueNew(8, sizeof(GapCommand), NULL); - // Start Device Information service - dev_info_svc_start(); - // Start Battery service - battery_svc_start(); - // Start Serial application - serial_svc_start(); - // Configure advirtise service UUID uint8_t adv_service_uid[2]; - adv_service_uid[0] = 0x80 | furi_hal_version_get_hw_color(); - adv_service_uid[1] = 0x30; - + gap->service.adv_svc_uuid_len = 1; + adv_service_uid[0] = gap->config->adv_service_uuid & 0xff; + adv_service_uid[1] = gap->config->adv_service_uuid >> 8; set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); // Set callback @@ -429,11 +415,42 @@ bool gap_init(BleEventCallback on_event_cb, void* context) { return true; } -static void gap_app(void *arg) { +GapState gap_get_state() { + GapState state; + osMutexAcquire(gap->state_mutex, osWaitForever); + state = gap->state; + osMutexRelease(gap->state_mutex ); + return state; +} + +void gap_thread_stop() { + if(gap) { + osMutexAcquire(gap->state_mutex, osWaitForever); + gap->enable_adv = false; + GapCommand command = GapCommandKillThread; + osMessageQueuePut(gap->command_queue, &command, 0, osWaitForever); + osMutexRelease(gap->state_mutex); + furi_thread_join(gap->thread); + furi_thread_free(gap->thread); + // Free resources + osMutexDelete(gap->state_mutex); + osMessageQueueDelete(gap->command_queue); + osTimerStop(gap->advertise_timer); + while(xTimerIsTimerActive(gap->advertise_timer) == pdTRUE) osDelay(1); + furi_check(osTimerDelete(gap->advertise_timer) == osOK); + free(gap); + gap = NULL; + } +} + +static int32_t gap_app(void *context) { GapCommand command; while(1) { furi_check(osMessageQueueGet(gap->command_queue, &command, NULL, osWaitForever) == osOK); osMutexAcquire(gap->state_mutex, osWaitForever); + if(command == GapCommandKillThread) { + break; + } if(command == GapCommandAdvFast) { gap_advertise_start(GapStateAdvFast); } else if(command == GapCommandAdvLowPower) { @@ -443,4 +460,6 @@ static void gap_app(void *arg) { } osMutexRelease(gap->state_mutex); } + + return 0; } diff --git a/firmware/targets/f6/ble-glue/gap.h b/firmware/targets/f6/ble-glue/gap.h index dfeffd63..03d9bbfc 100644 --- a/firmware/targets/f6/ble-glue/gap.h +++ b/firmware/targets/f6/ble-glue/gap.h @@ -3,6 +3,10 @@ #include #include +#include + +#define GAP_MAC_ADDR_SIZE (6) + #ifdef __cplusplus extern "C" { #endif @@ -13,6 +17,7 @@ typedef enum { BleEventTypeStartAdvertising, BleEventTypeStopAdvertising, BleEventTypePinCodeShow, + BleEventTypePinCodeVerify, BleEventTypeUpdateMTU, } BleEventType; @@ -26,16 +31,31 @@ typedef struct { BleEventData data; } BleEvent; -typedef void(*BleEventCallback) (BleEvent event, void* context); +typedef bool(*BleEventCallback) (BleEvent event, void* context); typedef enum { GapStateIdle, + GapStateStartingAdv, GapStateAdvFast, GapStateAdvLowPower, GapStateConnected, } GapState; -bool gap_init(BleEventCallback on_event_cb, void* context); +typedef enum { + GapPairingPinCodeShow, + GapPairingPinCodeVerifyYesNo, +} GapPairing; + +typedef struct { + uint16_t adv_service_uuid; + uint16_t appearance_char; + bool bonding_mode; + GapPairing pairing_method; + uint8_t mac_address[GAP_MAC_ADDR_SIZE]; + char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; +} GapConfig; + +bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context); void gap_start_advertising(); @@ -43,6 +63,8 @@ void gap_stop_advertising(); GapState gap_get_state(); +void gap_thread_stop(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f6/ble-glue/hid_service.c b/firmware/targets/f6/ble-glue/hid_service.c new file mode 100644 index 00000000..dc9a7fa5 --- /dev/null +++ b/firmware/targets/f6/ble-glue/hid_service.c @@ -0,0 +1,268 @@ +#include "hid_service.h" +#include "app_common.h" +#include "ble.h" + +#include + +#define TAG "BtHid" + +typedef struct { + uint16_t svc_handle; + uint16_t protocol_mode_char_handle; + uint16_t report_char_handle; + uint16_t report_ref_desc_handle; + uint16_t report_map_char_handle; + uint16_t keyboard_boot_char_handle; + uint16_t info_char_handle; + uint16_t ctrl_point_char_handle; +} HIDSvc; + +static HIDSvc* hid_svc = NULL; + +static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void *event) { + SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; + hci_event_pckt* event_pckt = (hci_event_pckt *)(((hci_uart_pckt*)event)->data); + evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; + // aci_gatt_attribute_modified_event_rp0* attribute_modified; + if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { + if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { + // Process modification events + ret = SVCCTL_EvtAckFlowEnable; + } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { + // Process notification confirmation + ret = SVCCTL_EvtAckFlowEnable; + } + } + return ret; +} + +void hid_svc_start() { + tBleStatus status; + hid_svc = furi_alloc(sizeof(HIDSvc)); + Service_UUID_t svc_uuid = {}; + Char_Desc_Uuid_t desc_uuid = {}; + Char_UUID_t char_uuid = {}; + + // Register event handler + SVCCTL_RegisterSvcHandler(hid_svc_event_handler); + // Add service + svc_uuid.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID; + status = aci_gatt_add_service(UUID_TYPE_16, + &svc_uuid, + PRIMARY_SERVICE, + 30, + &hid_svc->svc_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add HID service: %d", status); + } + // Add Protocol mode characterstics + char_uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + 1, + CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP, + ATTR_PERMISSION_NONE, + GATT_NOTIFY_ATTRIBUTE_WRITE, + 10, + CHAR_VALUE_LEN_CONSTANT, + &hid_svc->protocol_mode_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add protocol mode characteristic: %d", status); + } + // Update Protocol mode characteristic + uint8_t protocol_mode = 1; + status = aci_gatt_update_char_value(hid_svc->svc_handle, + hid_svc->protocol_mode_char_handle, + 0, + 1, + &protocol_mode); + if(status) { + FURI_LOG_E(TAG, "Failed to update protocol mode characteristic: %d", status); + } + // Add Report characterstics + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &hid_svc->report_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + // Add Report descriptor + uint8_t desc_val[] = {0x00, 0x01}; + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc(hid_svc->svc_handle, + hid_svc->report_char_handle, + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + desc_val, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_ONLY, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &hid_svc->report_ref_desc_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + // Add Report Map characteristic + char_uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAP_MAX_LEN, + CHAR_PROP_READ, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &hid_svc->report_map_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); + } + // Add Boot Keyboard characteristic + char_uuid.Char_UUID_16 = BOOT_KEYBOARD_INPUT_REPORT_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_BOOT_KEYBOARD_INPUT_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP, + 10, + CHAR_VALUE_LEN_VARIABLE, + &hid_svc->keyboard_boot_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); + } + // Add Information characteristic + char_uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_INFO_LEN, + CHAR_PROP_READ, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_CONSTANT, + &hid_svc->info_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add information characteristic: %d", status); + } + // Add Control Point characteristic + char_uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_CONTROL_POINT_LEN, + CHAR_PROP_WRITE_WITHOUT_RESP, + ATTR_PERMISSION_NONE, + GATT_NOTIFY_ATTRIBUTE_WRITE, + 10, + CHAR_VALUE_LEN_CONSTANT, + &hid_svc->ctrl_point_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add control point characteristic: %d", status); + } +} + +bool hid_svc_update_report_map(uint8_t* data, uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + + tBleStatus status = aci_gatt_update_char_value(hid_svc->svc_handle, + hid_svc->report_map_char_handle, + 0, + len, + data); + if(status) { + FURI_LOG_E(TAG, "Failed updating report map characteristic"); + return false; + } + return true; +} + +bool hid_svc_update_input_report(uint8_t* data, uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + + tBleStatus status = aci_gatt_update_char_value(hid_svc->svc_handle, + hid_svc->report_char_handle, + 0, + len, + data); + if(status) { + FURI_LOG_E(TAG, "Failed updating report characteristic"); + return false; + } + return true; +} + +bool hid_svc_update_info(uint8_t* data, uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + + tBleStatus status = aci_gatt_update_char_value(hid_svc->svc_handle, + hid_svc->info_char_handle, + 0, + len, + data); + if(status) { + FURI_LOG_E(TAG, "Failed updating info characteristic"); + return false; + } + return true; +} + +bool hid_svc_is_started() { + return hid_svc != NULL; +} + +void hid_svc_stop() { + tBleStatus status; + if(hid_svc) { + // Delete characteristics + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_map_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Report Map characteristic: %d", status); + } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Report characteristic: %d", status); + } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->protocol_mode_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Protocol Mode characteristic: %d", status); + } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->keyboard_boot_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Keyboard Boot characteristic: %d", status); + } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->info_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Information characteristic: %d", status); + } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->ctrl_point_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Control Point characteristic: %d", status); + } + // Delete service + status = aci_gatt_del_service(hid_svc->svc_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete HID service: %d", status); + } + // Delete buffer size mutex + free(hid_svc); + hid_svc = NULL; + } +} diff --git a/firmware/targets/f6/ble-glue/hid_service.h b/firmware/targets/f6/ble-glue/hid_service.h new file mode 100644 index 00000000..ab4a2bb9 --- /dev/null +++ b/firmware/targets/f6/ble-glue/hid_service.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#define HID_SVC_REPORT_MAP_MAX_LEN (80) +#define HID_SVC_REPORT_MAX_LEN (8) +#define HID_SVC_BOOT_KEYBOARD_INPUT_REPORT_MAX_LEN (8) +#define HID_SVC_REPORT_REF_LEN (2) +#define HID_SVC_INFO_LEN (4) +#define HID_SVC_CONTROL_POINT_LEN (1) + +void hid_svc_start(); + +void hid_svc_stop(); + +bool hid_svc_is_started(); + +bool hid_svc_update_report_map(uint8_t* data, uint16_t len); + +bool hid_svc_update_input_report(uint8_t* data, uint16_t len); + +bool hid_svc_update_info(uint8_t* data, uint16_t len); diff --git a/firmware/targets/f6/ble-glue/serial_service.c b/firmware/targets/f6/ble-glue/serial_service.c index c7ea6db2..4c70ccb0 100644 --- a/firmware/targets/f6/ble-glue/serial_service.c +++ b/firmware/targets/f6/ble-glue/serial_service.c @@ -14,8 +14,7 @@ typedef struct { osMutexId_t buff_size_mtx; uint32_t buff_size; uint16_t bytes_ready_to_receive; - SerialSvcDataReceivedCallback on_received_cb; - SerialSvcDataSentCallback on_sent_cb; + SerialServiceEventCallback callback; void* context; } SerialSvc; @@ -40,7 +39,7 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { FURI_LOG_D(TAG, "RX descriptor event"); } else if(attribute_modified->Attr_Handle == serial_svc->rx_char_handle + 1) { FURI_LOG_D(TAG, "Received %d bytes", attribute_modified->Attr_Data_Length); - if(serial_svc->on_received_cb) { + if(serial_svc->callback) { furi_check(osMutexAcquire(serial_svc->buff_size_mtx, osWaitForever) == osOK); if(attribute_modified->Attr_Data_Length > serial_svc->bytes_ready_to_receive) { FURI_LOG_W( @@ -48,8 +47,15 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { attribute_modified->Attr_Data_Length, serial_svc->bytes_ready_to_receive); } serial_svc->bytes_ready_to_receive -= MIN(serial_svc->bytes_ready_to_receive, attribute_modified->Attr_Data_Length); + SerialServiceEvent event = { + .event = SerialServiceEventTypeDataReceived, + .data = { + .buffer = attribute_modified->Attr_Data, + .size = attribute_modified->Attr_Data_Length, + } + }; uint32_t buff_free_size = - serial_svc->on_received_cb(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length, serial_svc->context); + serial_svc->callback(event, serial_svc->context); FURI_LOG_D(TAG, "Available buff size: %d", buff_free_size); furi_check(osMutexRelease(serial_svc->buff_size_mtx) == osOK); } @@ -57,8 +63,11 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { } } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { FURI_LOG_D(TAG, "Ack received", blecore_evt->ecode); - if(serial_svc->on_sent_cb) { - serial_svc->on_sent_cb(serial_svc->context); + if(serial_svc->callback) { + SerialServiceEvent event = { + .event = SerialServiceEventTypeDataSent, + }; + serial_svc->callback(event, serial_svc->context); } ret = SVCCTL_EvtAckFlowEnable; } @@ -119,10 +128,9 @@ void serial_svc_start() { serial_svc->buff_size_mtx = osMutexNew(NULL); } -void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { +void serial_svc_set_callbacks(uint16_t buff_size, SerialServiceEventCallback callback, void* context) { furi_assert(serial_svc); - serial_svc->on_received_cb = on_received_cb; - serial_svc->on_sent_cb = on_sent_cb; + serial_svc->callback = callback; serial_svc->context = context; serial_svc->buff_size = buff_size; serial_svc->bytes_ready_to_receive = buff_size; @@ -172,6 +180,10 @@ void serial_svc_stop() { } } +bool serial_svc_is_started() { + return serial_svc != NULL; +} + bool serial_svc_update_tx(uint8_t* data, uint8_t data_len) { if(data_len > SERIAL_SVC_DATA_LEN_MAX) { return false; diff --git a/firmware/targets/f6/ble-glue/serial_service.h b/firmware/targets/f6/ble-glue/serial_service.h index 5be13f1b..3ea548af 100644 --- a/firmware/targets/f6/ble-glue/serial_service.h +++ b/firmware/targets/f6/ble-glue/serial_service.h @@ -9,17 +9,33 @@ extern "C" { #endif -typedef uint16_t(*SerialSvcDataReceivedCallback)(uint8_t* buff, uint16_t size, void* context); -typedef void(*SerialSvcDataSentCallback)(void* context); +typedef enum { + SerialServiceEventTypeDataReceived, + SerialServiceEventTypeDataSent, +} SerialServiceEventType; + +typedef struct { + uint8_t* buffer; + uint16_t size; +} SerialServiceData; + +typedef struct { + SerialServiceEventType event; + SerialServiceData data; +} SerialServiceEvent; + +typedef uint16_t(*SerialServiceEventCallback)(SerialServiceEvent event, void* context); void serial_svc_start(); -void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context); +void serial_svc_set_callbacks(uint16_t buff_size, SerialServiceEventCallback callback, void* context); void serial_svc_notify_buffer_is_empty(); void serial_svc_stop(); +bool serial_svc_is_started(); + bool serial_svc_update_tx(uint8_t* data, uint8_t data_len); #ifdef __cplusplus diff --git a/firmware/targets/f6/furi-hal/furi-hal-bt-hid.c b/firmware/targets/f6/furi-hal/furi-hal-bt-hid.c new file mode 100644 index 00000000..5d5e89ed --- /dev/null +++ b/firmware/targets/f6/furi-hal/furi-hal-bt-hid.c @@ -0,0 +1,141 @@ +#include "furi-hal-bt-hid.h" +#include "dev_info_service.h" +#include "battery_service.h" +#include "hid_service.h" + +#include + +#define FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION (0x0101) +#define FURI_HAL_BT_INFO_COUNTRY_CODE (0x00) +#define FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) +#define FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) + +#define FURI_HAL_BT_HID_KB_KEYS_MAX (6) + +typedef struct { + uint8_t mods; + uint8_t reserved; + uint8_t key[FURI_HAL_BT_HID_KB_KEYS_MAX]; +} FuriHalBtHidKbReport; + +// TODO rework with HID defines +static uint8_t furi_hal_bt_hid_report_map_data[] = { + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x06, // Usage (Keyboard) + 0xA1, 0x01, // Collection (Application) + 0x05, 0x07, // Usage Page (Key Codes) + 0x19, 0xe0, // Usage Minimum (224) + 0x29, 0xe7, // Usage Maximum (231) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x81, 0x02, // Input (Data, Variable, Absolute) + + 0x95, 0x01, // Report Count (1) + 0x75, 0x08, // Report Size (8) + 0x81, 0x01, // Input (Constant) reserved byte(1) + + 0x95, 0x05, // Report Count (5) + 0x75, 0x01, // Report Size (1) + 0x05, 0x08, // Usage Page (Page# for LEDs) + 0x19, 0x01, // Usage Minimum (1) + 0x29, 0x05, // Usage Maximum (5) + 0x91, 0x02, // Output (Data, Variable, Absolute), Led report + 0x95, 0x01, // Report Count (1) + 0x75, 0x03, // Report Size (3) + 0x91, 0x01, // Output (Data, Variable, Absolute), Led report padding + + 0x95, 0x06, // Report Count (6) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x65, // Logical Maximum (101) + 0x05, 0x07, // Usage Page (Key codes) + 0x19, 0x00, // Usage Minimum (0) + 0x29, 0x65, // Usage Maximum (101) + 0x81, 0x00, // Input (Data, Array) Key array(6 bytes) + + 0x09, 0x05, // Usage (Vendor Defined) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8 bit) + 0x95, 0x02, // Report Count (2) + 0xB1, 0x02, // Feature (Data, Variable, Absolute) + + 0xC0 // End Collection (Application) +}; + +FuriHalBtHidKbReport* kb_report = NULL; + +void furi_hal_bt_hid_start() { + // Start device info + if(!dev_info_svc_is_started()) { + dev_info_svc_start(); + } + // Start battery service + if(!battery_svc_is_started()) { + battery_svc_start(); + } + // Start HID service + if(!hid_svc_is_started()) { + hid_svc_start(); + } + // Configure HID Keyboard + kb_report = furi_alloc(sizeof(FuriHalBtHidKbReport)); + // Configure Report Map characteristic + hid_svc_update_report_map(furi_hal_bt_hid_report_map_data, sizeof(furi_hal_bt_hid_report_map_data)); + // Configure HID Information characteristic + uint8_t hid_info_val[4] = { + FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION & 0x00ff, + (FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8, + FURI_HAL_BT_INFO_COUNTRY_CODE, + FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK | FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK, + }; + hid_svc_update_info(hid_info_val, sizeof(hid_info_val)); +} + +void furi_hal_bt_hid_stop() { + furi_assert(kb_report); + // Stop all services + if(dev_info_svc_is_started()) { + dev_info_svc_stop(); + } + if(battery_svc_is_started()) { + battery_svc_stop(); + } + if(hid_svc_is_started()) { + hid_svc_stop(); + } + free(kb_report); + kb_report = NULL; +} + +bool furi_hal_bt_hid_kb_press(uint16_t button) { + furi_assert(kb_report); + for (uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { + if (kb_report->key[i] == 0) { + kb_report->key[i] = button & 0xFF; + break; + } + } + kb_report->mods |= (button >> 8); + return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); +} + +bool furi_hal_bt_hid_kb_release(uint16_t button) { + furi_assert(kb_report); + for (uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { + if (kb_report->key[i] == (button & 0xFF)) { + kb_report->key[i] = 0; + break; + } + } + kb_report->mods &= ~(button >> 8); + return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); +} + +bool furi_hal_bt_hid_kb_release_all() { + furi_assert(kb_report); + memset(kb_report, 0, sizeof(FuriHalBtHidKbReport)); + return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); +} diff --git a/firmware/targets/f6/furi-hal/furi-hal-bt-serial.c b/firmware/targets/f6/furi-hal/furi-hal-bt-serial.c new file mode 100644 index 00000000..d5ea869b --- /dev/null +++ b/firmware/targets/f6/furi-hal/furi-hal-bt-serial.c @@ -0,0 +1,51 @@ +#include "furi-hal-bt-serial.h" +#include "dev_info_service.h" +#include "battery_service.h" +#include "serial_service.h" + +#include + +void furi_hal_bt_serial_start() { + // Start device info + if(!dev_info_svc_is_started()) { + dev_info_svc_start(); + } + // Start battery service + if(!battery_svc_is_started()) { + battery_svc_start(); + } + // Start Serial service + if(!serial_svc_is_started()) { + serial_svc_start(); + } +} + +void furi_hal_bt_serial_set_event_callback(uint16_t buff_size, FuriHalBtSerialCallback callback, void* context) { + serial_svc_set_callbacks(buff_size, callback, context); +} + +void furi_hal_bt_serial_notify_buffer_is_empty() { + serial_svc_notify_buffer_is_empty(); +} + +bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size) { + if(size > FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX) { + return false; + } + return serial_svc_update_tx(data, size); +} + +void furi_hal_bt_serial_stop() { + // Stop all services + if(dev_info_svc_is_started()) { + dev_info_svc_stop(); + } + // Start battery service + if(battery_svc_is_started()) { + battery_svc_stop(); + } + // Start Serial service + if(serial_svc_is_started()) { + serial_svc_stop(); + } +} diff --git a/firmware/targets/f6/furi-hal/furi-hal-bt.c b/firmware/targets/f6/furi-hal/furi-hal-bt.c index b74a9e29..714d894f 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-bt.c +++ b/firmware/targets/f6/furi-hal/furi-hal-bt.c @@ -4,18 +4,66 @@ #include #include +#include +#include +#include +#include "battery_service.h" + #include #define TAG "FuriHalBt" +#define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72} + osMutexId_t furi_hal_bt_core2_mtx = NULL; +typedef void (*FuriHalBtProfileStart)(void); +typedef void (*FuriHalBtProfileStop)(void); + +typedef struct { + FuriHalBtProfileStart start; + FuriHalBtProfileStart stop; + GapConfig config; + uint16_t appearance_char; + uint16_t advertise_service_uuid; +} FuriHalBtProfileConfig; + +FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { + [FuriHalBtProfileSerial] = { + .start = furi_hal_bt_serial_start, + .stop = furi_hal_bt_serial_stop, + .config = { + .adv_service_uuid = 0x3080, + .appearance_char = 0x8600, + .bonding_mode = true, + .pairing_method = GapPairingPinCodeShow, + .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, + }, + }, + [FuriHalBtProfileHidKeyboard] = { + .start = furi_hal_bt_hid_start, + .stop = furi_hal_bt_hid_stop, + .config = { + .adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, + .appearance_char = GAP_APPEARANCE_KEYBOARD, + .bonding_mode = true, + .pairing_method = GapPairingPinCodeVerifyYesNo, + .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, + }, + } +}; +FuriHalBtProfileConfig* current_profile = NULL; + void furi_hal_bt_init() { - furi_hal_bt_core2_mtx = osMutexNew(NULL); - furi_assert(furi_hal_bt_core2_mtx); + if(!furi_hal_bt_core2_mtx) { + furi_hal_bt_core2_mtx = osMutexNew(NULL); + furi_assert(furi_hal_bt_core2_mtx); + } // Explicitly tell that we are in charge of CLK48 domain - HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); + if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { + HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); + } // Start Core2 ble_glue_init(); @@ -31,12 +79,14 @@ void furi_hal_bt_unlock_core2() { furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); } -bool furi_hal_bt_start_core2() { +static bool furi_hal_bt_start_core2() { furi_assert(furi_hal_bt_core2_mtx); osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); // Explicitly tell that we are in charge of CLK48 domain - HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); + if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { + HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); + } // Start Core2 bool ret = ble_glue_start(); osMutexRelease(furi_hal_bt_core2_mtx); @@ -44,9 +94,80 @@ bool furi_hal_bt_start_core2() { return ret; } -bool furi_hal_bt_init_app(BleEventCallback event_cb, void* context) { +bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) { furi_assert(event_cb); - return gap_init(event_cb, context); + furi_assert(profile < FuriHalBtProfileNumber); + bool ret = true; + + do { + // Start 2nd core + ret = furi_hal_bt_start_core2(); + if(!ret) { + ble_app_thread_stop(); + FURI_LOG_E(TAG, "Failed to start 2nd core"); + break; + } + // Set mac address + memcpy( + profile_config[profile].config.mac_address, + furi_hal_version_get_ble_mac(), + sizeof(profile_config[profile].config.mac_address) + ); + // Set advertise name + strlcpy( + profile_config[profile].config.adv_name, + furi_hal_version_get_ble_local_device_name_ptr(), + FURI_HAL_VERSION_DEVICE_NAME_LENGTH + ); + // Configure GAP + GapConfig* config = &profile_config[profile].config; + if(profile == FuriHalBtProfileSerial) { + config->adv_service_uuid |= furi_hal_version_get_hw_color(); + } else if(profile == FuriHalBtProfileHidKeyboard) { + // Change MAC address for HID profile + config->mac_address[2]++; + // Change name Flipper -> Clicker + const char* clicker_str = "Clicker"; + memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str) - 1); + } + ret = gap_init(config, event_cb, context); + if(!ret) { + gap_thread_stop(); + FURI_LOG_E(TAG, "Failed to init GAP"); + break; + } + // Start selected profile services + profile_config[profile].start(); + } while(false); + current_profile = &profile_config[profile]; + + return ret; +} + +bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) { + furi_assert(event_cb); + furi_assert(profile < FuriHalBtProfileNumber); + bool ret = true; + + FURI_LOG_I(TAG, "Stop current profile services"); + current_profile->stop(); + FURI_LOG_I(TAG, "Disconnect and stop advertising"); + furi_hal_bt_stop_advertising(); + FURI_LOG_I(TAG, "Shutdow 2nd core"); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + FURI_LOG_I(TAG, "Stop BLE related RTOS threads"); + ble_app_thread_stop(); + gap_thread_stop(); + FURI_LOG_I(TAG, "Reset SHCI"); + SHCI_C2_Reinit(); + ble_glue_thread_stop(); + FURI_LOG_I(TAG, "Start BT initialization"); + furi_hal_bt_init(); + ret = furi_hal_bt_start_app(profile, event_cb, context); + if(ret) { + current_profile = &profile_config[profile]; + } + return ret; } void furi_hal_bt_start_advertising() { @@ -64,19 +185,10 @@ void furi_hal_bt_stop_advertising() { } } -void furi_hal_bt_set_data_event_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { - serial_svc_set_callbacks(buff_size, on_received_cb, on_sent_cb, context); -} - -void furi_hal_bt_notify_buffer_is_empty() { - serial_svc_notify_buffer_is_empty(); -} - -bool furi_hal_bt_tx(uint8_t* data, uint16_t size) { - if(size > FURI_HAL_BT_PACKET_SIZE_MAX) { - return false; +void furi_hal_bt_update_battery_level(uint8_t battery_level) { + if(battery_svc_is_started()) { + battery_svc_update_level(battery_level); } - return serial_svc_update_tx(data, size); } void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size) { diff --git a/firmware/targets/f6/furi-hal/furi-hal-flash.c b/firmware/targets/f6/furi-hal/furi-hal-flash.c index 156a26a9..8a1d2d06 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-flash.c +++ b/firmware/targets/f6/furi-hal/furi-hal-flash.c @@ -113,9 +113,11 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) { } // Take sempahopre and prevent core2 from anyting funky - if (HAL_HSEM_FastTake(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != HAL_OK) { - taskEXIT_CRITICAL(); - continue; + if(!HAL_HSEM_IsSemTaken(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID)) { + if (HAL_HSEM_FastTake(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != HAL_OK) { + taskEXIT_CRITICAL(); + continue; + } } break; diff --git a/firmware/targets/f7/ble-glue/battery_service.c b/firmware/targets/f7/ble-glue/battery_service.c index 2a5dad5e..08dec734 100644 --- a/firmware/targets/f7/ble-glue/battery_service.c +++ b/firmware/targets/f7/ble-glue/battery_service.c @@ -59,6 +59,10 @@ void battery_svc_stop() { } } +bool battery_svc_is_started() { + return battery_svc != NULL; +} + bool battery_svc_update_level(uint8_t battery_charge) { // Check if service was started if(battery_svc == NULL) { diff --git a/firmware/targets/f7/ble-glue/battery_service.h b/firmware/targets/f7/ble-glue/battery_service.h index a50e607c..2d35e252 100644 --- a/firmware/targets/f7/ble-glue/battery_service.h +++ b/firmware/targets/f7/ble-glue/battery_service.h @@ -11,6 +11,8 @@ void battery_svc_start(); void battery_svc_stop(); +bool battery_svc_is_started(); + bool battery_svc_update_level(uint8_t battery_level); #ifdef __cplusplus diff --git a/firmware/targets/f7/ble-glue/ble_app.c b/firmware/targets/f7/ble-glue/ble_app.c index 40b34679..3f09d1f0 100644 --- a/firmware/targets/f7/ble-glue/ble_app.c +++ b/firmware/targets/f7/ble-glue/ble_app.c @@ -10,20 +10,24 @@ #define TAG "Bt" +#define BLE_APP_FLAG_HCI_EVENT (1UL << 0) +#define BLE_APP_FLAG_KILL_THREAD (1UL << 1) +#define BLE_APP_FLAG_ALL (BLE_APP_FLAG_HCI_EVENT | BLE_APP_FLAG_KILL_THREAD) + PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE]; typedef struct { osMutexId_t hci_mtx; osSemaphoreId_t hci_sem; - osThreadId_t hci_thread_id; - osThreadAttr_t hci_thread_attr; + FuriThread* thread; + osEventFlagsId_t event_flags; } BleApp; -static BleApp* ble_app; +static BleApp* ble_app = NULL; -static void ble_app_hci_thread(void *arg); -static void ble_app_hci_event_handler(void * pPayload); +static int32_t ble_app_hci_thread(void* context); +static void ble_app_hci_event_handler(void* pPayload); static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status); bool ble_app_init() { @@ -32,10 +36,14 @@ bool ble_app_init() { // Allocate semafore and mutex for ble command buffer access ble_app->hci_mtx = osMutexNew(NULL); ble_app->hci_sem = osSemaphoreNew(1, 0, NULL); + ble_app->event_flags = osEventFlagsNew(NULL); // HCI transport layer thread to handle user asynch events - ble_app->hci_thread_attr.name = "BleHciWorker"; - ble_app->hci_thread_attr.stack_size = 1024; - ble_app->hci_thread_id = osThreadNew(ble_app_hci_thread, NULL, &ble_app->hci_thread_attr); + ble_app->thread = furi_thread_alloc(); + furi_thread_set_name(ble_app->thread, "BleHciWorker"); + furi_thread_set_stack_size(ble_app->thread, 1024); + furi_thread_set_context(ble_app->thread, ble_app); + furi_thread_set_callback(ble_app->thread, ble_app_hci_thread); + furi_thread_start(ble_app->thread); // Initialize Ble Transport Layer HCI_TL_HciInitConf_t hci_tl_config = { @@ -92,35 +100,68 @@ void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size) { *size = sizeof(ble_app_nvm); } -static void ble_app_hci_thread(void *arg) { - while(1) { - osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); - hci_user_evt_proc(); +void ble_app_thread_stop() { + if(ble_app) { + osEventFlagsSet(ble_app->event_flags, BLE_APP_FLAG_KILL_THREAD); + furi_thread_join(ble_app->thread); + furi_thread_free(ble_app->thread); + // Wait to make sure that EventFlags delivers pending events before memory free + osDelay(50); + // Free resources + osMutexDelete(ble_app->hci_mtx); + osSemaphoreDelete(ble_app->hci_sem); + osEventFlagsDelete(ble_app->event_flags); + free(ble_app); + ble_app = NULL; + memset(&ble_app_cmd_buffer, 0, sizeof(ble_app_cmd_buffer)); } } +static int32_t ble_app_hci_thread(void *arg) { + uint32_t flags = 0; + while(1) { + flags = osEventFlagsWait(ble_app->event_flags, BLE_APP_FLAG_ALL, osFlagsWaitAny, osWaitForever); + if(flags & BLE_APP_FLAG_KILL_THREAD) { + break; + } + if(flags & BLE_APP_FLAG_HCI_EVENT) { + hci_user_evt_proc(); + } + } + + return 0; +} + // Called by WPAN lib void hci_notify_asynch_evt(void* pdata) { - osThreadFlagsSet(ble_app->hci_thread_id, 1); + if(ble_app) { + osEventFlagsSet(ble_app->event_flags, BLE_APP_FLAG_HCI_EVENT); + } } void hci_cmd_resp_release(uint32_t flag) { - osSemaphoreRelease(ble_app->hci_sem); + if(ble_app) { + osSemaphoreRelease(ble_app->hci_sem); + } } void hci_cmd_resp_wait(uint32_t timeout) { - osSemaphoreAcquire(ble_app->hci_sem, osWaitForever); + if(ble_app) { + osSemaphoreAcquire(ble_app->hci_sem, osWaitForever); + } } static void ble_app_hci_event_handler( void * pPayload ) { SVCCTL_UserEvtFlowStatus_t svctl_return_status; tHCI_UserEvtRxParam *pParam = (tHCI_UserEvtRxParam *)pPayload; - svctl_return_status = SVCCTL_UserEvtRx((void *)&(pParam->pckt->evtserial)); - if (svctl_return_status != SVCCTL_UserEvtFlowDisable) { - pParam->status = HCI_TL_UserEventFlow_Enable; - } else { - pParam->status = HCI_TL_UserEventFlow_Disable; + if(ble_app) { + svctl_return_status = SVCCTL_UserEvtRx((void *)&(pParam->pckt->evtserial)); + if (svctl_return_status != SVCCTL_UserEvtFlowDisable) { + pParam->status = HCI_TL_UserEventFlow_Enable; + } else { + pParam->status = HCI_TL_UserEventFlow_Disable; + } } } diff --git a/firmware/targets/f7/ble-glue/ble_app.h b/firmware/targets/f7/ble-glue/ble_app.h index 64000bde..062154e9 100644 --- a/firmware/targets/f7/ble-glue/ble_app.h +++ b/firmware/targets/f7/ble-glue/ble_app.h @@ -9,6 +9,7 @@ extern "C" { bool ble_app_init(); void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size); +void ble_app_thread_stop(); #ifdef __cplusplus } diff --git a/firmware/targets/f7/ble-glue/ble_glue.c b/firmware/targets/f7/ble-glue/ble_glue.c index 45503683..007f3645 100644 --- a/firmware/targets/f7/ble-glue/ble_glue.c +++ b/firmware/targets/f7/ble-glue/ble_glue.c @@ -12,6 +12,10 @@ #define TAG "Core2" +#define BLE_GLUE_FLAG_SHCI_EVENT (1UL << 0) +#define BLE_GLUE_FLAG_KILL_THREAD (1UL << 1) +#define BLE_GLUE_FLAG_ALL (BLE_GLUE_FLAG_SHCI_EVENT | BLE_GLUE_FLAG_KILL_THREAD) + #define POOL_SIZE (CFG_TLBLE_EVT_QUEUE_LENGTH*4U*DIVC(( sizeof(TL_PacketHeader_t) + TL_BLE_EVENT_FRAME_SIZE ), 4U)) PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_event_pool[POOL_SIZE]; @@ -32,8 +36,8 @@ typedef enum { typedef struct { osMutexId_t shci_mtx; osSemaphoreId_t shci_sem; - osThreadId_t shci_user_event_thread_id; - osThreadAttr_t shci_user_event_thread_attr; + osEventFlagsId_t event_flags; + FuriThread* thread; BleGlueStatus status; BleGlueKeyStorageChangedCallback callback; void* context; @@ -41,7 +45,7 @@ typedef struct { static BleGlue* ble_glue = NULL; -static void ble_glue_user_event_thread(void *argument); +static int32_t ble_glue_shci_thread(void *argument); static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status); static void ble_glue_sys_user_event_callback(void* pPayload); @@ -55,8 +59,6 @@ void ble_glue_set_key_storage_changed_callback(BleGlueKeyStorageChangedCallback void ble_glue_init() { ble_glue = furi_alloc(sizeof(BleGlue)); ble_glue->status = BleGlueStatusStartup; - ble_glue->shci_user_event_thread_attr.name = "BleShciWorker"; - ble_glue->shci_user_event_thread_attr.stack_size = 1024; // Configure the system Power Mode // Select HSI as system clock source after Wake Up from Stop mode @@ -75,9 +77,15 @@ void ble_glue_init() { ble_glue->shci_mtx = osMutexNew(NULL); ble_glue->shci_sem = osSemaphoreNew(1, 0, NULL); + ble_glue->event_flags = osEventFlagsNew(NULL); // FreeRTOS system task creation - ble_glue->shci_user_event_thread_id = osThreadNew(ble_glue_user_event_thread, NULL, &ble_glue->shci_user_event_thread_attr); + ble_glue->thread = furi_thread_alloc(); + furi_thread_set_name(ble_glue->thread, "BleShciWorker"); + furi_thread_set_stack_size(ble_glue->thread, 1024); + furi_thread_set_context(ble_glue->thread, ble_glue); + furi_thread_set_callback(ble_glue->thread, ble_glue_shci_thread); + furi_thread_start(ble_glue->thread); // System channel initialization SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&ble_glue_system_cmd_buff; @@ -205,26 +213,63 @@ static void ble_glue_sys_user_event_callback( void * pPayload ) { } } -// Wrap functions -static void ble_glue_user_event_thread(void *argument) { - UNUSED(argument); - for(;;) { - osThreadFlagsWait(1, osFlagsWaitAny, osWaitForever); - shci_user_evt_proc(); +static void ble_glue_clear_shared_memory() { + memset(ble_glue_event_pool, 0, sizeof(ble_glue_event_pool)); + memset(&ble_glue_system_cmd_buff, 0, sizeof(ble_glue_system_cmd_buff)); + memset(ble_glue_system_spare_event_buff, 0, sizeof(ble_glue_system_spare_event_buff)); + memset(ble_glue_ble_spare_event_buff, 0, sizeof(ble_glue_ble_spare_event_buff)); +} + +void ble_glue_thread_stop() { + if(ble_glue) { + osEventFlagsSet(ble_glue->event_flags, BLE_GLUE_FLAG_KILL_THREAD); + furi_thread_join(ble_glue->thread); + furi_thread_free(ble_glue->thread); + // Wait to make sure that EventFlags delivers pending events before memory free + osDelay(50); + // Free resources + osMutexDelete(ble_glue->shci_mtx); + osSemaphoreDelete(ble_glue->shci_sem); + osEventFlagsDelete(ble_glue->event_flags); + ble_glue_clear_shared_memory(); + free(ble_glue); + ble_glue = NULL; } } +// Wrap functions +static int32_t ble_glue_shci_thread(void* context) { + uint32_t flags = 0; + while(true) { + flags = osEventFlagsWait(ble_glue->event_flags, BLE_GLUE_FLAG_ALL, osFlagsWaitAny, osWaitForever); + if(flags & BLE_GLUE_FLAG_SHCI_EVENT) { + shci_user_evt_proc(); + } + if(flags & BLE_GLUE_FLAG_KILL_THREAD) { + break; + } + } + + return 0; +} + void shci_notify_asynch_evt(void* pdata) { UNUSED(pdata); - osThreadFlagsSet(ble_glue->shci_user_event_thread_id, 1); + if(ble_glue) { + osEventFlagsSet(ble_glue->event_flags, BLE_GLUE_FLAG_SHCI_EVENT); + } } void shci_cmd_resp_release(uint32_t flag) { UNUSED(flag); - osSemaphoreRelease(ble_glue->shci_sem); + if(ble_glue) { + osSemaphoreRelease(ble_glue->shci_sem); + } } void shci_cmd_resp_wait(uint32_t timeout) { UNUSED(timeout); - osSemaphoreAcquire(ble_glue->shci_sem, osWaitForever); + if(ble_glue) { + osSemaphoreAcquire(ble_glue->shci_sem, osWaitForever); + } } diff --git a/firmware/targets/f7/ble-glue/ble_glue.h b/firmware/targets/f7/ble-glue/ble_glue.h index ac668c42..a95d633c 100644 --- a/firmware/targets/f7/ble-glue/ble_glue.h +++ b/firmware/targets/f7/ble-glue/ble_glue.h @@ -38,6 +38,8 @@ bool ble_glue_is_radio_stack_ready(); */ void ble_glue_set_key_storage_changed_callback(BleGlueKeyStorageChangedCallback callback, void* context); +void ble_glue_thread_stop(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/ble-glue/dev_info_service.c b/firmware/targets/f7/ble-glue/dev_info_service.c index 64ff0509..e47fdf66 100644 --- a/firmware/targets/f7/ble-glue/dev_info_service.c +++ b/firmware/targets/f7/ble-glue/dev_info_service.c @@ -154,3 +154,7 @@ void dev_info_svc_stop() { dev_info_svc = NULL; } } + +bool dev_info_svc_is_started() { + return dev_info_svc != NULL; +} diff --git a/firmware/targets/f7/ble-glue/dev_info_service.h b/firmware/targets/f7/ble-glue/dev_info_service.h index 62eccefa..f5531fc7 100644 --- a/firmware/targets/f7/ble-glue/dev_info_service.h +++ b/firmware/targets/f7/ble-glue/dev_info_service.h @@ -16,6 +16,8 @@ void dev_info_svc_start(); void dev_info_svc_stop(); +bool dev_info_svc_is_started(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/ble-glue/gap.c b/firmware/targets/f7/ble-glue/gap.c index a2381653..7f574f0b 100644 --- a/firmware/targets/f7/ble-glue/gap.c +++ b/firmware/targets/f7/ble-glue/gap.c @@ -3,20 +3,14 @@ #include "ble.h" #include "cmsis_os.h" -#include "otp.h" -#include "dev_info_service.h" -#include "battery_service.h" -#include "serial_service.h" - #include +#include #define TAG "BtGap" #define FAST_ADV_TIMEOUT 30000 #define INITIAL_ADV_TIMEOUT 60000 -#define BD_ADDR_SIZE_LOCAL 6 - typedef struct { uint16_t gap_svc_handle; uint16_t dev_name_char_handle; @@ -24,18 +18,18 @@ typedef struct { uint16_t connection_handle; uint8_t adv_svc_uuid_len; uint8_t adv_svc_uuid[20]; + char* adv_name; } GapSvc; typedef struct { - GapSvc gap_svc; + GapSvc service; + GapConfig* config; GapState state; osMutexId_t state_mutex; - uint8_t mac_address[BD_ADDR_SIZE_LOCAL]; BleEventCallback on_event_cb; void* context; osTimerId advertise_timer; - osThreadAttr_t thread_attr; - osThreadId_t thread_id; + FuriThread* thread; osMessageQueueId_t command_queue; bool enable_adv; } Gap; @@ -44,47 +38,44 @@ typedef enum { GapCommandAdvFast, GapCommandAdvLowPower, GapCommandAdvStop, + GapCommandKillThread, } GapCommand; // Identity root key static const uint8_t gap_irk[16] = {0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0,0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0}; // Encryption root key static const uint8_t gap_erk[16] = {0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21,0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21}; -// Appearence characteristic UUID -static const uint8_t gap_appearence_char_uuid[] = {0x00, 0x86}; -// Default MAC address -static const uint8_t gap_default_mac_addr[] = {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72}; static Gap* gap = NULL; static void gap_advertise_start(GapState new_state); -static void gap_app(void *arg); +static int32_t gap_app(void* context); SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) { - hci_event_pckt *event_pckt; - evt_le_meta_event *meta_evt; - evt_blue_aci *blue_evt; - hci_le_phy_update_complete_event_rp0 *evt_le_phy_update_complete; + hci_event_pckt* event_pckt; + evt_le_meta_event* meta_evt; + evt_blue_aci* blue_evt; + hci_le_phy_update_complete_event_rp0* evt_le_phy_update_complete; uint8_t tx_phy; uint8_t rx_phy; tBleStatus ret = BLE_STATUS_INVALID_PARAMS; - event_pckt = (hci_event_pckt*) ((hci_uart_pckt *) pckt)->data; + event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data; osMutexAcquire(gap->state_mutex, osWaitForever); switch (event_pckt->evt) { case EVT_DISCONN_COMPLETE: { hci_disconnection_complete_event_rp0 *disconnection_complete_event = (hci_disconnection_complete_event_rp0 *) event_pckt->data; - if (disconnection_complete_event->Connection_Handle == gap->gap_svc.connection_handle) { - gap->gap_svc.connection_handle = 0; + if (disconnection_complete_event->Connection_Handle == gap->service.connection_handle) { + gap->service.connection_handle = 0; gap->state = GapStateIdle; - FURI_LOG_I(TAG, "Disconnect from client. Reason: %d", disconnection_complete_event->Reason); + FURI_LOG_I(TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason); } if(gap->enable_adv) { // Restart advertising - gap_start_advertising(); + gap_advertise_start(GapCommandAdvFast); furi_hal_power_insomnia_exit(); } BleEvent event = {.type = BleEventTypeDisconnected}; @@ -106,7 +97,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) } else { FURI_LOG_I(TAG, "Update PHY succeed"); } - ret = hci_le_read_phy(gap->gap_svc.connection_handle,&tx_phy,&rx_phy); + ret = hci_le_read_phy(gap->service.connection_handle,&tx_phy,&rx_phy); if(ret) { FURI_LOG_E(TAG, "Read PHY failed, status: %d", ret); } else { @@ -124,7 +115,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) // Update connection status and handle gap->state = GapStateConnected; - gap->gap_svc.connection_handle = connection_complete_event->Connection_Handle; + gap->service.connection_handle = connection_complete_event->Connection_Handle; // Start pairing by sending security request aci_gap_slave_security_req(connection_complete_event->Connection_Handle); @@ -148,8 +139,8 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) { // Generate random PIN code uint32_t pin = rand() % 999999; - aci_gap_pass_key_resp(gap->gap_svc.connection_handle, pin); - FURI_LOG_I(TAG, "Pass key request event. Pin: %d", pin); + aci_gap_pass_key_resp(gap->service.connection_handle, pin); + FURI_LOG_I(TAG, "Pass key request event. Pin: %06d", pin); BleEvent event = {.type = BleEventTypePinCodeShow, .data.pin_code = pin}; gap->on_event_cb(event, gap->context); } @@ -175,7 +166,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) case EVT_BLUE_GAP_BOND_LOST: FURI_LOG_I(TAG, "Bond lost event. Start rebonding"); - aci_gap_allow_rebond(gap->gap_svc.connection_handle); + aci_gap_allow_rebond(gap->service.connection_handle); break; case EVT_BLUE_GAP_DEVICE_FOUND: @@ -191,16 +182,20 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) break; case EVT_BLUE_GAP_NUMERIC_COMPARISON_VALUE: - FURI_LOG_I(TAG, "Hex_value = %lx", - ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value); - aci_gap_numeric_comparison_value_confirm_yesno(gap->gap_svc.connection_handle, 1); + { + uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value; + FURI_LOG_I(TAG, "Verify numeric comparison: %06d", pin); + BleEvent event = {.type = BleEventTypePinCodeVerify, .data.pin_code = pin}; + bool result = gap->on_event_cb(event, gap->context); + aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); break; + } case EVT_BLUE_GAP_PAIRING_CMPLT: pairing_complete = (aci_gap_pairing_complete_event_rp0*)blue_evt->data; if (pairing_complete->Status) { FURI_LOG_E(TAG, "Pairing failed with status: %d. Terminating connection", pairing_complete->Status); - aci_gap_terminate(gap->gap_svc.connection_handle, 5); + aci_gap_terminate(gap->service.connection_handle, 5); } else { FURI_LOG_I(TAG, "Pairing complete"); BleEvent event = {.type = BleEventTypeConnected}; @@ -220,46 +215,15 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) } static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) { - gap->gap_svc.adv_svc_uuid_len = 1; if(uid_len == 2) { - gap->gap_svc.adv_svc_uuid[0] = AD_TYPE_16_BIT_SERV_UUID; + gap->service.adv_svc_uuid[0] = AD_TYPE_16_BIT_SERV_UUID; } else if (uid_len == 4) { - gap->gap_svc.adv_svc_uuid[0] = AD_TYPE_32_BIT_SERV_UUID; + gap->service.adv_svc_uuid[0] = AD_TYPE_32_BIT_SERV_UUID; } else if(uid_len == 16) { - gap->gap_svc.adv_svc_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST; - } - memcpy(&gap->gap_svc.adv_svc_uuid[1], uid, uid_len); - gap->gap_svc.adv_svc_uuid_len += uid_len; -} - -GapState gap_get_state() { - return gap->state; -} - -void gap_init_mac_address(Gap* gap) { - uint8_t *otp_addr; - uint32_t udn; - uint32_t company_id; - uint32_t device_id; - - udn = LL_FLASH_GetUDN(); - if(udn != 0xFFFFFFFF) { - company_id = LL_FLASH_GetSTCompanyID(); - device_id = LL_FLASH_GetDeviceID(); - gap->mac_address[0] = (uint8_t)(udn & 0x000000FF); - gap->mac_address[1] = (uint8_t)( (udn & 0x0000FF00) >> 8 ); - gap->mac_address[2] = (uint8_t)( (udn & 0x00FF0000) >> 16 ); - gap->mac_address[3] = (uint8_t)device_id; - gap->mac_address[4] = (uint8_t)(company_id & 0x000000FF);; - gap->mac_address[5] = (uint8_t)( (company_id & 0x0000FF00) >> 8 ); - } else { - otp_addr = OTP_Read(0); - if(otp_addr) { - memcpy(gap->mac_address, ((OTP_ID0_t*)otp_addr)->bd_address, sizeof(gap->mac_address)); - } else { - memcpy(gap->mac_address, gap_default_mac_addr, sizeof(gap->mac_address)); - } + gap->service.adv_svc_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST; } + memcpy(&gap->service.adv_svc_uuid[gap->service.adv_svc_uuid_len], uid, uid_len); + gap->service.adv_svc_uuid_len += uid_len; } static void gap_init_svc(Gap* gap) { @@ -269,8 +233,7 @@ static void gap_init_svc(Gap* gap) { // HCI Reset to synchronise BLE Stack hci_reset(); // Configure mac address - gap_init_mac_address(gap); - aci_hal_write_config_data(CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, (uint8_t*)gap->mac_address); + aci_hal_write_config_data(CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, gap->config->mac_address); /* Static random Address * The two upper bits shall be set to 1 @@ -289,25 +252,42 @@ static void gap_init_svc(Gap* gap) { // Initialize GATT interface aci_gatt_init(); // Initialize GAP interface - const char *name = furi_hal_version_get_device_name_ptr(); + // Skip fist symbol AD_TYPE_COMPLETE_LOCAL_NAME + char *name = gap->service.adv_name + 1; aci_gap_init(GAP_PERIPHERAL_ROLE, 0, strlen(name), - &gap->gap_svc.gap_svc_handle, &gap->gap_svc.dev_name_char_handle, &gap->gap_svc.appearance_char_handle); + &gap->service.gap_svc_handle, &gap->service.dev_name_char_handle, &gap->service.appearance_char_handle); // Set GAP characteristics - status = aci_gatt_update_char_value(gap->gap_svc.gap_svc_handle, gap->gap_svc.dev_name_char_handle, 0, strlen(name), (uint8_t *) name); + status = aci_gatt_update_char_value(gap->service.gap_svc_handle, gap->service.dev_name_char_handle, 0, strlen(name), (uint8_t *) name); if (status) { FURI_LOG_E(TAG, "Failed updating name characteristic: %d", status); } - status = aci_gatt_update_char_value(gap->gap_svc.gap_svc_handle, gap->gap_svc.appearance_char_handle, 0, 2, gap_appearence_char_uuid); + uint8_t gap_appearence_char_uuid[2] = {gap->config->appearance_char & 0xff, gap->config->appearance_char >> 8}; + status = aci_gatt_update_char_value(gap->service.gap_svc_handle, gap->service.appearance_char_handle, 0, 2, gap_appearence_char_uuid); if(status) { FURI_LOG_E(TAG, "Failed updating appearence characteristic: %d", status); } // Set default PHY hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); // Set I/O capability - aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); + bool keypress_supported = false; + if(gap->config->pairing_method == GapPairingPinCodeShow) { + aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); + } else if(gap->config->pairing_method == GapPairingPinCodeVerifyYesNo){ + aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); + keypress_supported = true; + } // Setup authentication - aci_gap_set_authentication_requirement(1, 1, 1, 0, 8, 16, 1, 0, PUBLIC_ADDR); + aci_gap_set_authentication_requirement( + gap->config->bonding_mode, + CFG_MITM_PROTECTION, + CFG_SC_SUPPORT, + keypress_supported, + CFG_ENCRYPTION_KEY_SIZE_MIN, + CFG_ENCRYPTION_KEY_SIZE_MAX, + CFG_USED_FIXED_PIN, + 0, + PUBLIC_ADDR); // Configure whitelist aci_gap_configure_whitelist(); } @@ -336,10 +316,9 @@ static void gap_advertise_start(GapState new_state) } } // Configure advertising - const char* name = furi_hal_version_get_ble_local_device_name_ptr(); status = aci_gap_set_discoverable(ADV_IND, min_interval, max_interval, PUBLIC_ADDR, 0, - strlen(name), (uint8_t*)name, - gap->gap_svc.adv_svc_uuid_len, gap->gap_svc.adv_svc_uuid, 0, 0); + strlen(gap->service.adv_name), (uint8_t*)gap->service.adv_name, + gap->service.adv_svc_uuid_len, gap->service.adv_svc_uuid, 0, 0); if(status) { FURI_LOG_E(TAG, "Set discoverable err: %d", status); } @@ -350,11 +329,11 @@ static void gap_advertise_start(GapState new_state) } static void gap_advertise_stop() { - if(gap->state == GapStateConnected) { - // Terminate connection - aci_gap_terminate(gap->gap_svc.connection_handle, 0x13); - } if(gap->state > GapStateIdle) { + if(gap->state == GapStateConnected) { + // Terminate connection + aci_gap_terminate(gap->service.connection_handle, 0x13); + } // Stop advertising osTimerStop(gap->advertise_timer); aci_gap_set_non_discoverable(); @@ -365,17 +344,26 @@ static void gap_advertise_stop() { } void gap_start_advertising() { - FURI_LOG_I(TAG, "Start advertising"); - gap->enable_adv = true; - GapCommand command = GapCommandAdvFast; - furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); + osMutexAcquire(gap->state_mutex, osWaitForever); + if(gap->state == GapStateIdle) { + gap->state = GapStateStartingAdv; + FURI_LOG_I(TAG, "Start advertising"); + gap->enable_adv = true; + GapCommand command = GapCommandAdvFast; + furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); + } + osMutexRelease(gap->state_mutex); } void gap_stop_advertising() { - FURI_LOG_I(TAG, "Stop advertising"); - gap->enable_adv = false; - GapCommand command = GapCommandAdvStop; - furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); + osMutexAcquire(gap->state_mutex, osWaitForever); + if(gap->state > GapStateIdle) { + FURI_LOG_I(TAG, "Stop advertising"); + gap->enable_adv = false; + GapCommand command = GapCommandAdvStop; + furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); + } + osMutexRelease(gap->state_mutex); } static void gap_advetise_timer_callback(void* context) { @@ -383,44 +371,42 @@ static void gap_advetise_timer_callback(void* context) { furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); } -bool gap_init(BleEventCallback on_event_cb, void* context) { +bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) { if (!ble_glue_is_radio_stack_ready()) { return false; } gap = furi_alloc(sizeof(Gap)); + gap->config = config; srand(DWT->CYCCNT); // Create advertising timer gap->advertise_timer = osTimerNew(gap_advetise_timer_callback, osTimerOnce, NULL, NULL); // Initialization of GATT & GAP layer + gap->service.adv_name = config->adv_name; gap_init_svc(gap); // Initialization of the BLE Services SVCCTL_Init(); // Initialization of the GAP state gap->state_mutex = osMutexNew(NULL); gap->state = GapStateIdle; - gap->gap_svc.connection_handle = 0xFFFF; + gap->service.connection_handle = 0xFFFF; gap->enable_adv = true; // Thread configuration - gap->thread_attr.name = "BleGapWorker"; - gap->thread_attr.stack_size = 1024; - gap->thread_id = osThreadNew(gap_app, NULL, &gap->thread_attr); + gap->thread = furi_thread_alloc(); + furi_thread_set_name(gap->thread, "BleGapWorker"); + furi_thread_set_stack_size(gap->thread, 1024); + furi_thread_set_context(gap->thread, gap); + furi_thread_set_callback(gap->thread, gap_app); + furi_thread_start(gap->thread); // Command queue allocation gap->command_queue = osMessageQueueNew(8, sizeof(GapCommand), NULL); - // Start Device Information service - dev_info_svc_start(); - // Start Battery service - battery_svc_start(); - // Start Serial application - serial_svc_start(); - // Configure advirtise service UUID uint8_t adv_service_uid[2]; - adv_service_uid[0] = 0x80 | furi_hal_version_get_hw_color(); - adv_service_uid[1] = 0x30; - + gap->service.adv_svc_uuid_len = 1; + adv_service_uid[0] = gap->config->adv_service_uuid & 0xff; + adv_service_uid[1] = gap->config->adv_service_uuid >> 8; set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); // Set callback @@ -429,11 +415,42 @@ bool gap_init(BleEventCallback on_event_cb, void* context) { return true; } -static void gap_app(void *arg) { +GapState gap_get_state() { + GapState state; + osMutexAcquire(gap->state_mutex, osWaitForever); + state = gap->state; + osMutexRelease(gap->state_mutex ); + return state; +} + +void gap_thread_stop() { + if(gap) { + osMutexAcquire(gap->state_mutex, osWaitForever); + gap->enable_adv = false; + GapCommand command = GapCommandKillThread; + osMessageQueuePut(gap->command_queue, &command, 0, osWaitForever); + osMutexRelease(gap->state_mutex); + furi_thread_join(gap->thread); + furi_thread_free(gap->thread); + // Free resources + osMutexDelete(gap->state_mutex); + osMessageQueueDelete(gap->command_queue); + osTimerStop(gap->advertise_timer); + while(xTimerIsTimerActive(gap->advertise_timer) == pdTRUE) osDelay(1); + furi_check(osTimerDelete(gap->advertise_timer) == osOK); + free(gap); + gap = NULL; + } +} + +static int32_t gap_app(void *context) { GapCommand command; while(1) { furi_check(osMessageQueueGet(gap->command_queue, &command, NULL, osWaitForever) == osOK); osMutexAcquire(gap->state_mutex, osWaitForever); + if(command == GapCommandKillThread) { + break; + } if(command == GapCommandAdvFast) { gap_advertise_start(GapStateAdvFast); } else if(command == GapCommandAdvLowPower) { @@ -443,4 +460,6 @@ static void gap_app(void *arg) { } osMutexRelease(gap->state_mutex); } + + return 0; } diff --git a/firmware/targets/f7/ble-glue/gap.h b/firmware/targets/f7/ble-glue/gap.h index dfeffd63..03d9bbfc 100644 --- a/firmware/targets/f7/ble-glue/gap.h +++ b/firmware/targets/f7/ble-glue/gap.h @@ -3,6 +3,10 @@ #include #include +#include + +#define GAP_MAC_ADDR_SIZE (6) + #ifdef __cplusplus extern "C" { #endif @@ -13,6 +17,7 @@ typedef enum { BleEventTypeStartAdvertising, BleEventTypeStopAdvertising, BleEventTypePinCodeShow, + BleEventTypePinCodeVerify, BleEventTypeUpdateMTU, } BleEventType; @@ -26,16 +31,31 @@ typedef struct { BleEventData data; } BleEvent; -typedef void(*BleEventCallback) (BleEvent event, void* context); +typedef bool(*BleEventCallback) (BleEvent event, void* context); typedef enum { GapStateIdle, + GapStateStartingAdv, GapStateAdvFast, GapStateAdvLowPower, GapStateConnected, } GapState; -bool gap_init(BleEventCallback on_event_cb, void* context); +typedef enum { + GapPairingPinCodeShow, + GapPairingPinCodeVerifyYesNo, +} GapPairing; + +typedef struct { + uint16_t adv_service_uuid; + uint16_t appearance_char; + bool bonding_mode; + GapPairing pairing_method; + uint8_t mac_address[GAP_MAC_ADDR_SIZE]; + char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; +} GapConfig; + +bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context); void gap_start_advertising(); @@ -43,6 +63,8 @@ void gap_stop_advertising(); GapState gap_get_state(); +void gap_thread_stop(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/ble-glue/hid_service.c b/firmware/targets/f7/ble-glue/hid_service.c new file mode 100644 index 00000000..dc9a7fa5 --- /dev/null +++ b/firmware/targets/f7/ble-glue/hid_service.c @@ -0,0 +1,268 @@ +#include "hid_service.h" +#include "app_common.h" +#include "ble.h" + +#include + +#define TAG "BtHid" + +typedef struct { + uint16_t svc_handle; + uint16_t protocol_mode_char_handle; + uint16_t report_char_handle; + uint16_t report_ref_desc_handle; + uint16_t report_map_char_handle; + uint16_t keyboard_boot_char_handle; + uint16_t info_char_handle; + uint16_t ctrl_point_char_handle; +} HIDSvc; + +static HIDSvc* hid_svc = NULL; + +static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void *event) { + SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; + hci_event_pckt* event_pckt = (hci_event_pckt *)(((hci_uart_pckt*)event)->data); + evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; + // aci_gatt_attribute_modified_event_rp0* attribute_modified; + if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { + if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { + // Process modification events + ret = SVCCTL_EvtAckFlowEnable; + } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { + // Process notification confirmation + ret = SVCCTL_EvtAckFlowEnable; + } + } + return ret; +} + +void hid_svc_start() { + tBleStatus status; + hid_svc = furi_alloc(sizeof(HIDSvc)); + Service_UUID_t svc_uuid = {}; + Char_Desc_Uuid_t desc_uuid = {}; + Char_UUID_t char_uuid = {}; + + // Register event handler + SVCCTL_RegisterSvcHandler(hid_svc_event_handler); + // Add service + svc_uuid.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID; + status = aci_gatt_add_service(UUID_TYPE_16, + &svc_uuid, + PRIMARY_SERVICE, + 30, + &hid_svc->svc_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add HID service: %d", status); + } + // Add Protocol mode characterstics + char_uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + 1, + CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP, + ATTR_PERMISSION_NONE, + GATT_NOTIFY_ATTRIBUTE_WRITE, + 10, + CHAR_VALUE_LEN_CONSTANT, + &hid_svc->protocol_mode_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add protocol mode characteristic: %d", status); + } + // Update Protocol mode characteristic + uint8_t protocol_mode = 1; + status = aci_gatt_update_char_value(hid_svc->svc_handle, + hid_svc->protocol_mode_char_handle, + 0, + 1, + &protocol_mode); + if(status) { + FURI_LOG_E(TAG, "Failed to update protocol mode characteristic: %d", status); + } + // Add Report characterstics + char_uuid.Char_UUID_16 = REPORT_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &hid_svc->report_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add report characteristic: %d", status); + } + // Add Report descriptor + uint8_t desc_val[] = {0x00, 0x01}; + desc_uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID; + status = aci_gatt_add_char_desc(hid_svc->svc_handle, + hid_svc->report_char_handle, + UUID_TYPE_16, + &desc_uuid, + HID_SVC_REPORT_REF_LEN, + HID_SVC_REPORT_REF_LEN, + desc_val, + ATTR_PERMISSION_NONE, + ATTR_ACCESS_READ_ONLY, + GATT_DONT_NOTIFY_EVENTS, + MIN_ENCRY_KEY_SIZE, + CHAR_VALUE_LEN_CONSTANT, + &hid_svc->report_ref_desc_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add report reference descriptor: %d", status); + } + // Add Report Map characteristic + char_uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_REPORT_MAP_MAX_LEN, + CHAR_PROP_READ, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_VARIABLE, + &hid_svc->report_map_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); + } + // Add Boot Keyboard characteristic + char_uuid.Char_UUID_16 = BOOT_KEYBOARD_INPUT_REPORT_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_BOOT_KEYBOARD_INPUT_REPORT_MAX_LEN, + CHAR_PROP_READ | CHAR_PROP_NOTIFY, + ATTR_PERMISSION_NONE, + GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP, + 10, + CHAR_VALUE_LEN_VARIABLE, + &hid_svc->keyboard_boot_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add report map characteristic: %d", status); + } + // Add Information characteristic + char_uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_INFO_LEN, + CHAR_PROP_READ, + ATTR_PERMISSION_NONE, + GATT_DONT_NOTIFY_EVENTS, + 10, + CHAR_VALUE_LEN_CONSTANT, + &hid_svc->info_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add information characteristic: %d", status); + } + // Add Control Point characteristic + char_uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID; + status = aci_gatt_add_char(hid_svc->svc_handle, + UUID_TYPE_16, + &char_uuid, + HID_SVC_CONTROL_POINT_LEN, + CHAR_PROP_WRITE_WITHOUT_RESP, + ATTR_PERMISSION_NONE, + GATT_NOTIFY_ATTRIBUTE_WRITE, + 10, + CHAR_VALUE_LEN_CONSTANT, + &hid_svc->ctrl_point_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to add control point characteristic: %d", status); + } +} + +bool hid_svc_update_report_map(uint8_t* data, uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + + tBleStatus status = aci_gatt_update_char_value(hid_svc->svc_handle, + hid_svc->report_map_char_handle, + 0, + len, + data); + if(status) { + FURI_LOG_E(TAG, "Failed updating report map characteristic"); + return false; + } + return true; +} + +bool hid_svc_update_input_report(uint8_t* data, uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + + tBleStatus status = aci_gatt_update_char_value(hid_svc->svc_handle, + hid_svc->report_char_handle, + 0, + len, + data); + if(status) { + FURI_LOG_E(TAG, "Failed updating report characteristic"); + return false; + } + return true; +} + +bool hid_svc_update_info(uint8_t* data, uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + + tBleStatus status = aci_gatt_update_char_value(hid_svc->svc_handle, + hid_svc->info_char_handle, + 0, + len, + data); + if(status) { + FURI_LOG_E(TAG, "Failed updating info characteristic"); + return false; + } + return true; +} + +bool hid_svc_is_started() { + return hid_svc != NULL; +} + +void hid_svc_stop() { + tBleStatus status; + if(hid_svc) { + // Delete characteristics + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_map_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Report Map characteristic: %d", status); + } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->report_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Report characteristic: %d", status); + } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->protocol_mode_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Protocol Mode characteristic: %d", status); + } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->keyboard_boot_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Keyboard Boot characteristic: %d", status); + } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->info_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Information characteristic: %d", status); + } + status = aci_gatt_del_char(hid_svc->svc_handle, hid_svc->ctrl_point_char_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete Control Point characteristic: %d", status); + } + // Delete service + status = aci_gatt_del_service(hid_svc->svc_handle); + if(status) { + FURI_LOG_E(TAG, "Failed to delete HID service: %d", status); + } + // Delete buffer size mutex + free(hid_svc); + hid_svc = NULL; + } +} diff --git a/firmware/targets/f7/ble-glue/hid_service.h b/firmware/targets/f7/ble-glue/hid_service.h new file mode 100644 index 00000000..ab4a2bb9 --- /dev/null +++ b/firmware/targets/f7/ble-glue/hid_service.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#define HID_SVC_REPORT_MAP_MAX_LEN (80) +#define HID_SVC_REPORT_MAX_LEN (8) +#define HID_SVC_BOOT_KEYBOARD_INPUT_REPORT_MAX_LEN (8) +#define HID_SVC_REPORT_REF_LEN (2) +#define HID_SVC_INFO_LEN (4) +#define HID_SVC_CONTROL_POINT_LEN (1) + +void hid_svc_start(); + +void hid_svc_stop(); + +bool hid_svc_is_started(); + +bool hid_svc_update_report_map(uint8_t* data, uint16_t len); + +bool hid_svc_update_input_report(uint8_t* data, uint16_t len); + +bool hid_svc_update_info(uint8_t* data, uint16_t len); diff --git a/firmware/targets/f7/ble-glue/serial_service.c b/firmware/targets/f7/ble-glue/serial_service.c index c7ea6db2..4c70ccb0 100644 --- a/firmware/targets/f7/ble-glue/serial_service.c +++ b/firmware/targets/f7/ble-glue/serial_service.c @@ -14,8 +14,7 @@ typedef struct { osMutexId_t buff_size_mtx; uint32_t buff_size; uint16_t bytes_ready_to_receive; - SerialSvcDataReceivedCallback on_received_cb; - SerialSvcDataSentCallback on_sent_cb; + SerialServiceEventCallback callback; void* context; } SerialSvc; @@ -40,7 +39,7 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { FURI_LOG_D(TAG, "RX descriptor event"); } else if(attribute_modified->Attr_Handle == serial_svc->rx_char_handle + 1) { FURI_LOG_D(TAG, "Received %d bytes", attribute_modified->Attr_Data_Length); - if(serial_svc->on_received_cb) { + if(serial_svc->callback) { furi_check(osMutexAcquire(serial_svc->buff_size_mtx, osWaitForever) == osOK); if(attribute_modified->Attr_Data_Length > serial_svc->bytes_ready_to_receive) { FURI_LOG_W( @@ -48,8 +47,15 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { attribute_modified->Attr_Data_Length, serial_svc->bytes_ready_to_receive); } serial_svc->bytes_ready_to_receive -= MIN(serial_svc->bytes_ready_to_receive, attribute_modified->Attr_Data_Length); + SerialServiceEvent event = { + .event = SerialServiceEventTypeDataReceived, + .data = { + .buffer = attribute_modified->Attr_Data, + .size = attribute_modified->Attr_Data_Length, + } + }; uint32_t buff_free_size = - serial_svc->on_received_cb(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length, serial_svc->context); + serial_svc->callback(event, serial_svc->context); FURI_LOG_D(TAG, "Available buff size: %d", buff_free_size); furi_check(osMutexRelease(serial_svc->buff_size_mtx) == osOK); } @@ -57,8 +63,11 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) { } } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { FURI_LOG_D(TAG, "Ack received", blecore_evt->ecode); - if(serial_svc->on_sent_cb) { - serial_svc->on_sent_cb(serial_svc->context); + if(serial_svc->callback) { + SerialServiceEvent event = { + .event = SerialServiceEventTypeDataSent, + }; + serial_svc->callback(event, serial_svc->context); } ret = SVCCTL_EvtAckFlowEnable; } @@ -119,10 +128,9 @@ void serial_svc_start() { serial_svc->buff_size_mtx = osMutexNew(NULL); } -void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { +void serial_svc_set_callbacks(uint16_t buff_size, SerialServiceEventCallback callback, void* context) { furi_assert(serial_svc); - serial_svc->on_received_cb = on_received_cb; - serial_svc->on_sent_cb = on_sent_cb; + serial_svc->callback = callback; serial_svc->context = context; serial_svc->buff_size = buff_size; serial_svc->bytes_ready_to_receive = buff_size; @@ -172,6 +180,10 @@ void serial_svc_stop() { } } +bool serial_svc_is_started() { + return serial_svc != NULL; +} + bool serial_svc_update_tx(uint8_t* data, uint8_t data_len) { if(data_len > SERIAL_SVC_DATA_LEN_MAX) { return false; diff --git a/firmware/targets/f7/ble-glue/serial_service.h b/firmware/targets/f7/ble-glue/serial_service.h index 5be13f1b..3ea548af 100644 --- a/firmware/targets/f7/ble-glue/serial_service.h +++ b/firmware/targets/f7/ble-glue/serial_service.h @@ -9,17 +9,33 @@ extern "C" { #endif -typedef uint16_t(*SerialSvcDataReceivedCallback)(uint8_t* buff, uint16_t size, void* context); -typedef void(*SerialSvcDataSentCallback)(void* context); +typedef enum { + SerialServiceEventTypeDataReceived, + SerialServiceEventTypeDataSent, +} SerialServiceEventType; + +typedef struct { + uint8_t* buffer; + uint16_t size; +} SerialServiceData; + +typedef struct { + SerialServiceEventType event; + SerialServiceData data; +} SerialServiceEvent; + +typedef uint16_t(*SerialServiceEventCallback)(SerialServiceEvent event, void* context); void serial_svc_start(); -void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context); +void serial_svc_set_callbacks(uint16_t buff_size, SerialServiceEventCallback callback, void* context); void serial_svc_notify_buffer_is_empty(); void serial_svc_stop(); +bool serial_svc_is_started(); + bool serial_svc_update_tx(uint8_t* data, uint8_t data_len); #ifdef __cplusplus diff --git a/firmware/targets/f7/furi-hal/furi-hal-bt-hid.c b/firmware/targets/f7/furi-hal/furi-hal-bt-hid.c new file mode 100644 index 00000000..5d5e89ed --- /dev/null +++ b/firmware/targets/f7/furi-hal/furi-hal-bt-hid.c @@ -0,0 +1,141 @@ +#include "furi-hal-bt-hid.h" +#include "dev_info_service.h" +#include "battery_service.h" +#include "hid_service.h" + +#include + +#define FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION (0x0101) +#define FURI_HAL_BT_INFO_COUNTRY_CODE (0x00) +#define FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) +#define FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) + +#define FURI_HAL_BT_HID_KB_KEYS_MAX (6) + +typedef struct { + uint8_t mods; + uint8_t reserved; + uint8_t key[FURI_HAL_BT_HID_KB_KEYS_MAX]; +} FuriHalBtHidKbReport; + +// TODO rework with HID defines +static uint8_t furi_hal_bt_hid_report_map_data[] = { + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x06, // Usage (Keyboard) + 0xA1, 0x01, // Collection (Application) + 0x05, 0x07, // Usage Page (Key Codes) + 0x19, 0xe0, // Usage Minimum (224) + 0x29, 0xe7, // Usage Maximum (231) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x81, 0x02, // Input (Data, Variable, Absolute) + + 0x95, 0x01, // Report Count (1) + 0x75, 0x08, // Report Size (8) + 0x81, 0x01, // Input (Constant) reserved byte(1) + + 0x95, 0x05, // Report Count (5) + 0x75, 0x01, // Report Size (1) + 0x05, 0x08, // Usage Page (Page# for LEDs) + 0x19, 0x01, // Usage Minimum (1) + 0x29, 0x05, // Usage Maximum (5) + 0x91, 0x02, // Output (Data, Variable, Absolute), Led report + 0x95, 0x01, // Report Count (1) + 0x75, 0x03, // Report Size (3) + 0x91, 0x01, // Output (Data, Variable, Absolute), Led report padding + + 0x95, 0x06, // Report Count (6) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x65, // Logical Maximum (101) + 0x05, 0x07, // Usage Page (Key codes) + 0x19, 0x00, // Usage Minimum (0) + 0x29, 0x65, // Usage Maximum (101) + 0x81, 0x00, // Input (Data, Array) Key array(6 bytes) + + 0x09, 0x05, // Usage (Vendor Defined) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8 bit) + 0x95, 0x02, // Report Count (2) + 0xB1, 0x02, // Feature (Data, Variable, Absolute) + + 0xC0 // End Collection (Application) +}; + +FuriHalBtHidKbReport* kb_report = NULL; + +void furi_hal_bt_hid_start() { + // Start device info + if(!dev_info_svc_is_started()) { + dev_info_svc_start(); + } + // Start battery service + if(!battery_svc_is_started()) { + battery_svc_start(); + } + // Start HID service + if(!hid_svc_is_started()) { + hid_svc_start(); + } + // Configure HID Keyboard + kb_report = furi_alloc(sizeof(FuriHalBtHidKbReport)); + // Configure Report Map characteristic + hid_svc_update_report_map(furi_hal_bt_hid_report_map_data, sizeof(furi_hal_bt_hid_report_map_data)); + // Configure HID Information characteristic + uint8_t hid_info_val[4] = { + FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION & 0x00ff, + (FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8, + FURI_HAL_BT_INFO_COUNTRY_CODE, + FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK | FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK, + }; + hid_svc_update_info(hid_info_val, sizeof(hid_info_val)); +} + +void furi_hal_bt_hid_stop() { + furi_assert(kb_report); + // Stop all services + if(dev_info_svc_is_started()) { + dev_info_svc_stop(); + } + if(battery_svc_is_started()) { + battery_svc_stop(); + } + if(hid_svc_is_started()) { + hid_svc_stop(); + } + free(kb_report); + kb_report = NULL; +} + +bool furi_hal_bt_hid_kb_press(uint16_t button) { + furi_assert(kb_report); + for (uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { + if (kb_report->key[i] == 0) { + kb_report->key[i] = button & 0xFF; + break; + } + } + kb_report->mods |= (button >> 8); + return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); +} + +bool furi_hal_bt_hid_kb_release(uint16_t button) { + furi_assert(kb_report); + for (uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { + if (kb_report->key[i] == (button & 0xFF)) { + kb_report->key[i] = 0; + break; + } + } + kb_report->mods &= ~(button >> 8); + return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); +} + +bool furi_hal_bt_hid_kb_release_all() { + furi_assert(kb_report); + memset(kb_report, 0, sizeof(FuriHalBtHidKbReport)); + return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); +} diff --git a/firmware/targets/f7/furi-hal/furi-hal-bt-serial.c b/firmware/targets/f7/furi-hal/furi-hal-bt-serial.c new file mode 100644 index 00000000..d5ea869b --- /dev/null +++ b/firmware/targets/f7/furi-hal/furi-hal-bt-serial.c @@ -0,0 +1,51 @@ +#include "furi-hal-bt-serial.h" +#include "dev_info_service.h" +#include "battery_service.h" +#include "serial_service.h" + +#include + +void furi_hal_bt_serial_start() { + // Start device info + if(!dev_info_svc_is_started()) { + dev_info_svc_start(); + } + // Start battery service + if(!battery_svc_is_started()) { + battery_svc_start(); + } + // Start Serial service + if(!serial_svc_is_started()) { + serial_svc_start(); + } +} + +void furi_hal_bt_serial_set_event_callback(uint16_t buff_size, FuriHalBtSerialCallback callback, void* context) { + serial_svc_set_callbacks(buff_size, callback, context); +} + +void furi_hal_bt_serial_notify_buffer_is_empty() { + serial_svc_notify_buffer_is_empty(); +} + +bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size) { + if(size > FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX) { + return false; + } + return serial_svc_update_tx(data, size); +} + +void furi_hal_bt_serial_stop() { + // Stop all services + if(dev_info_svc_is_started()) { + dev_info_svc_stop(); + } + // Start battery service + if(battery_svc_is_started()) { + battery_svc_stop(); + } + // Start Serial service + if(serial_svc_is_started()) { + serial_svc_stop(); + } +} diff --git a/firmware/targets/f7/furi-hal/furi-hal-bt.c b/firmware/targets/f7/furi-hal/furi-hal-bt.c index b74a9e29..714d894f 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-bt.c +++ b/firmware/targets/f7/furi-hal/furi-hal-bt.c @@ -4,18 +4,66 @@ #include #include +#include +#include +#include +#include "battery_service.h" + #include #define TAG "FuriHalBt" +#define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72} + osMutexId_t furi_hal_bt_core2_mtx = NULL; +typedef void (*FuriHalBtProfileStart)(void); +typedef void (*FuriHalBtProfileStop)(void); + +typedef struct { + FuriHalBtProfileStart start; + FuriHalBtProfileStart stop; + GapConfig config; + uint16_t appearance_char; + uint16_t advertise_service_uuid; +} FuriHalBtProfileConfig; + +FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { + [FuriHalBtProfileSerial] = { + .start = furi_hal_bt_serial_start, + .stop = furi_hal_bt_serial_stop, + .config = { + .adv_service_uuid = 0x3080, + .appearance_char = 0x8600, + .bonding_mode = true, + .pairing_method = GapPairingPinCodeShow, + .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, + }, + }, + [FuriHalBtProfileHidKeyboard] = { + .start = furi_hal_bt_hid_start, + .stop = furi_hal_bt_hid_stop, + .config = { + .adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, + .appearance_char = GAP_APPEARANCE_KEYBOARD, + .bonding_mode = true, + .pairing_method = GapPairingPinCodeVerifyYesNo, + .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, + }, + } +}; +FuriHalBtProfileConfig* current_profile = NULL; + void furi_hal_bt_init() { - furi_hal_bt_core2_mtx = osMutexNew(NULL); - furi_assert(furi_hal_bt_core2_mtx); + if(!furi_hal_bt_core2_mtx) { + furi_hal_bt_core2_mtx = osMutexNew(NULL); + furi_assert(furi_hal_bt_core2_mtx); + } // Explicitly tell that we are in charge of CLK48 domain - HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); + if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { + HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); + } // Start Core2 ble_glue_init(); @@ -31,12 +79,14 @@ void furi_hal_bt_unlock_core2() { furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); } -bool furi_hal_bt_start_core2() { +static bool furi_hal_bt_start_core2() { furi_assert(furi_hal_bt_core2_mtx); osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); // Explicitly tell that we are in charge of CLK48 domain - HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); + if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { + HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); + } // Start Core2 bool ret = ble_glue_start(); osMutexRelease(furi_hal_bt_core2_mtx); @@ -44,9 +94,80 @@ bool furi_hal_bt_start_core2() { return ret; } -bool furi_hal_bt_init_app(BleEventCallback event_cb, void* context) { +bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) { furi_assert(event_cb); - return gap_init(event_cb, context); + furi_assert(profile < FuriHalBtProfileNumber); + bool ret = true; + + do { + // Start 2nd core + ret = furi_hal_bt_start_core2(); + if(!ret) { + ble_app_thread_stop(); + FURI_LOG_E(TAG, "Failed to start 2nd core"); + break; + } + // Set mac address + memcpy( + profile_config[profile].config.mac_address, + furi_hal_version_get_ble_mac(), + sizeof(profile_config[profile].config.mac_address) + ); + // Set advertise name + strlcpy( + profile_config[profile].config.adv_name, + furi_hal_version_get_ble_local_device_name_ptr(), + FURI_HAL_VERSION_DEVICE_NAME_LENGTH + ); + // Configure GAP + GapConfig* config = &profile_config[profile].config; + if(profile == FuriHalBtProfileSerial) { + config->adv_service_uuid |= furi_hal_version_get_hw_color(); + } else if(profile == FuriHalBtProfileHidKeyboard) { + // Change MAC address for HID profile + config->mac_address[2]++; + // Change name Flipper -> Clicker + const char* clicker_str = "Clicker"; + memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str) - 1); + } + ret = gap_init(config, event_cb, context); + if(!ret) { + gap_thread_stop(); + FURI_LOG_E(TAG, "Failed to init GAP"); + break; + } + // Start selected profile services + profile_config[profile].start(); + } while(false); + current_profile = &profile_config[profile]; + + return ret; +} + +bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) { + furi_assert(event_cb); + furi_assert(profile < FuriHalBtProfileNumber); + bool ret = true; + + FURI_LOG_I(TAG, "Stop current profile services"); + current_profile->stop(); + FURI_LOG_I(TAG, "Disconnect and stop advertising"); + furi_hal_bt_stop_advertising(); + FURI_LOG_I(TAG, "Shutdow 2nd core"); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + FURI_LOG_I(TAG, "Stop BLE related RTOS threads"); + ble_app_thread_stop(); + gap_thread_stop(); + FURI_LOG_I(TAG, "Reset SHCI"); + SHCI_C2_Reinit(); + ble_glue_thread_stop(); + FURI_LOG_I(TAG, "Start BT initialization"); + furi_hal_bt_init(); + ret = furi_hal_bt_start_app(profile, event_cb, context); + if(ret) { + current_profile = &profile_config[profile]; + } + return ret; } void furi_hal_bt_start_advertising() { @@ -64,19 +185,10 @@ void furi_hal_bt_stop_advertising() { } } -void furi_hal_bt_set_data_event_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { - serial_svc_set_callbacks(buff_size, on_received_cb, on_sent_cb, context); -} - -void furi_hal_bt_notify_buffer_is_empty() { - serial_svc_notify_buffer_is_empty(); -} - -bool furi_hal_bt_tx(uint8_t* data, uint16_t size) { - if(size > FURI_HAL_BT_PACKET_SIZE_MAX) { - return false; +void furi_hal_bt_update_battery_level(uint8_t battery_level) { + if(battery_svc_is_started()) { + battery_svc_update_level(battery_level); } - return serial_svc_update_tx(data, size); } void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size) { diff --git a/firmware/targets/f7/furi-hal/furi-hal-flash.c b/firmware/targets/f7/furi-hal/furi-hal-flash.c index 156a26a9..8a1d2d06 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-flash.c +++ b/firmware/targets/f7/furi-hal/furi-hal-flash.c @@ -113,9 +113,11 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) { } // Take sempahopre and prevent core2 from anyting funky - if (HAL_HSEM_FastTake(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != HAL_OK) { - taskEXIT_CRITICAL(); - continue; + if(!HAL_HSEM_IsSemTaken(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID)) { + if (HAL_HSEM_FastTake(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != HAL_OK) { + taskEXIT_CRITICAL(); + continue; + } } break; diff --git a/firmware/targets/furi-hal-include/furi-hal-bt-hid.h b/firmware/targets/furi-hal-include/furi-hal-bt-hid.h new file mode 100644 index 00000000..03b2c7f7 --- /dev/null +++ b/firmware/targets/furi-hal-include/furi-hal-bt-hid.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +/** Start Hid Keyboard Profile + */ +void furi_hal_bt_hid_start(); + +/** Stop Hid Keyboard Profile + */ +void furi_hal_bt_hid_stop(); + +/** Press key button + * + * @param button button code from HID specification + * + * @return true on success + */ +bool furi_hal_bt_hid_kb_press(uint16_t button); + +/** Release key button + * + * @param button button code from HID specification + * + * @return true on success + */ +bool furi_hal_bt_hid_kb_release(uint16_t button); + +/** Release all key buttons + * + * @return true on success + */ +bool furi_hal_bt_hid_kb_release_all(); diff --git a/firmware/targets/furi-hal-include/furi-hal-bt-serial.h b/firmware/targets/furi-hal-include/furi-hal-bt-serial.h new file mode 100644 index 00000000..a1c71041 --- /dev/null +++ b/firmware/targets/furi-hal-include/furi-hal-bt-serial.h @@ -0,0 +1,37 @@ +#pragma once + +#include "serial_service.h" + +#define FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX SERIAL_SVC_DATA_LEN_MAX + +/** Serial service callback type */ +typedef SerialServiceEventCallback FuriHalBtSerialCallback; + +/** Start Serial Profile + */ +void furi_hal_bt_serial_start(); + +/** Stop Serial Profile + */ +void furi_hal_bt_serial_stop(); + +/** Set Serial service events callback + * + * @param buffer_size Applicaition buffer size + * @param calback FuriHalBtSerialCallback instance + * @param context pointer to context + */ +void furi_hal_bt_serial_set_event_callback(uint16_t buff_size, FuriHalBtSerialCallback callback, void* context); + +/** Notify that application buffer is empty + */ +void furi_hal_bt_serial_notify_buffer_is_empty(); + +/** Send data through BLE + * + * @param data data buffer + * @param size data buffer size + * + * @return true on success + */ +bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size); diff --git a/firmware/targets/furi-hal-include/furi-hal-bt.h b/firmware/targets/furi-hal-include/furi-hal-bt.h index c7d86165..8de7296f 100644 --- a/firmware/targets/furi-hal-include/furi-hal-bt.h +++ b/firmware/targets/furi-hal-include/furi-hal-bt.h @@ -12,13 +12,20 @@ #include #include - -#define FURI_HAL_BT_PACKET_SIZE_MAX SERIAL_SVC_DATA_LEN_MAX +#include "furi-hal-bt-serial.h" #ifdef __cplusplus extern "C" { #endif +typedef enum { + FuriHalBtProfileSerial, + FuriHalBtProfileHidKeyboard, + + // Keep last for Profiles number calculation + FuriHalBtProfileNumber, +} FuriHalBtProfile; + /** Initialize */ void furi_hal_bt_init(); @@ -29,17 +36,32 @@ void furi_hal_bt_lock_core2(); /** Lock core2 state transition */ void furi_hal_bt_unlock_core2(); -/** Start 2nd core and BLE stack - * - * @return true on success - */ -bool furi_hal_bt_start_core2(); - /** Start BLE app - * @param event_cb - BleEventCallback instance - * @param context - pointer to context + * + * @param profile FuriHalBtProfile instance + * @param event_cb BleEventCallback instance + * @param context pointer to context + * + * @return true on success */ -bool furi_hal_bt_init_app(BleEventCallback event_cb, void* context); +bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context); + +/** Change BLE app + * Restarts 2nd core + * + * @param profile FuriHalBtProfile instance + * @param event_cb BleEventCallback instance + * @param context pointer to context + * + * @return true on success +*/ +bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context); + +/** Update battery level + * + * @param battery_level battery level + */ +void furi_hal_bt_update_battery_level(uint8_t battery_level); /** Start advertising */ @@ -91,22 +113,6 @@ void furi_hal_bt_nvm_sram_sem_release(); */ void furi_hal_bt_set_key_storage_change_callback(BleGlueKeyStorageChangedCallback callback, void* context); -/** Set data event callbacks - * @param on_received_cb - SerialSvcDataReceivedCallback instance - * @param on_sent_cb - SerialSvcDataSentCallback instance - * @param context - pointer to context - */ -void furi_hal_bt_set_data_event_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context); - -/** Notify that buffer is empty */ -void furi_hal_bt_notify_buffer_is_empty(); - -/** Send data through BLE - * @param data - data buffer - * @param size - data buffer size - */ -bool furi_hal_bt_tx(uint8_t* data, uint16_t size); - /** Start ble tone tx at given channel and power * * @param[in] channel The channel