From 60a9d7e6cf37f1cc9a57d5665bb1a8844a9b0baa Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 16 Feb 2024 11:20:45 +0400 Subject: [PATCH] ble: profile rework (#3272) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ble: profile rework, initial * apps: hid: fix for pairing cleanup * app: hid: select transport based on #define * fixing PVS warnings * ble: serial service: fixed uid naming * bt service: on-demand dialog init; ble profiles: docs; battery svc: proper update * Added shci_cmd_resp_wait/shci_cmd_resp_release impl with semaphore * app: hid: separated transport code * ble: fixed service init order for serial svc; moved hardfault check to ble_glue * cli: ps: added thread prio to output, fixed heap display * ble_glue: naming changes; separate thread for event processing; * furi: added runtime stats; cli: added cpu% to `ps` * cli: fixed thread time calculation * furi: added getter for thread priority * fixing pvs warnings * hid profile: fixed naming * more naming fixes * hal: ble init small cleanup * cleanup & draft beacon api * f18: api sync * apps: moved example_custom_font from debug to examples * BLE extra beacon demo app * naming fix * UI fixes for demo app (wip) * desktop, ble svc: added statusbar icon for beacon * minor cleanup * Minor cleanup & naming fixes * api sync * Removed stale header * hal: added FURI_BLE_EXTRA_LOG for extra logging; comments & code cleanup * naming & macro fixes * quick fixes from review * Eliminated stock svc_ctl * cli: ps: removed runtime stats * minor include fixes * (void) * naming fixes * More naming fixes * fbt: always build all libs * fbt: explicitly globbing libs; dist: logging SDK path * scripts: fixed lib path precedence * hal: bt: profiles: naming changes, support for passing params to a profile; include cleanup * ble: hid: added parameter processing for profile template * api sync * BLE HID: long name trim * Removed unused check * desktop: updated beacon status icon; ble: hid: cleaner device name management * desktop: updated status icon Co-authored-by: あく Co-authored-by: nminaylov --- .../example_ble_beacon/application.fam | 11 + .../example_ble_beacon/ble_beacon_app.c | 149 ++++++ .../example_ble_beacon/ble_beacon_app.h | 50 ++ .../example_ble_beacon_10px.png | Bin 0 -> 8727 bytes .../images/lighthouse_35x44.png | Bin 0 -> 8910 bytes .../example_ble_beacon/scenes/scene_config.h | 4 + .../scenes/scene_input_beacon_data.c | 44 ++ .../scenes/scene_input_mac_addr.c | 44 ++ .../example_ble_beacon/scenes/scene_menu.c | 56 +++ .../scenes/scene_run_beacon.c | 79 ++++ .../example_ble_beacon/scenes/scenes.c | 30 ++ .../example_ble_beacon/scenes/scenes.h | 29 ++ .../example_custom_font/application.fam | 4 +- .../example_custom_font/example_custom_font.c | 0 .../main/archive/helpers/archive_apps.h | 2 + .../main/nfc/plugins/supported_cards/opal.c | 2 +- .../main/nfc/plugins/supported_cards/umarsh.c | 1 - applications/services/bt/bt_cli.c | 9 +- applications/services/bt/bt_service/bt.c | 175 ++++--- applications/services/bt/bt_service/bt.h | 24 +- applications/services/bt/bt_service/bt_api.c | 23 +- applications/services/bt/bt_service/bt_i.h | 11 +- applications/services/cli/cli_command_gpio.c | 1 - applications/services/cli/cli_commands.c | 14 +- .../widget_elements/widget_element_i.h | 4 +- applications/services/loader/loader.c | 1 - applications/services/rpc/rpc.c | 3 +- applications/services/rpc/rpc.h | 1 + .../scenes/bt_settings_scene_start.c | 2 +- applications/system/hid_app/application.fam | 5 + applications/system/hid_app/hid.c | 188 ++------ applications/system/hid_app/hid.h | 4 +- applications/system/hid_app/transport_ble.c | 60 +++ applications/system/hid_app/transport_usb.c | 61 +++ applications/system/hid_app/views/hid_media.c | 2 +- assets/icons/StatusBar/BLE_beacon_7x8.png | Bin 0 -> 117 bytes firmware.scons | 4 + furi/core/check.c | 2 +- furi/core/thread.c | 16 +- furi/core/thread.h | 9 +- lib/SConscript | 1 + lib/ble_profile/SConscript | 27 ++ lib/ble_profile/extra_profiles/hid_profile.c | 427 ++++++++++++++++++ lib/ble_profile/extra_profiles/hid_profile.h | 105 +++++ .../ble_profile/extra_services}/hid_service.c | 220 ++++----- lib/ble_profile/extra_services/hid_service.h | 29 ++ lib/nfc/protocols/st25tb/st25tb.c | 1 - lib/stm32wb.scons | 1 - scripts/fbt_tools/fbt_hwtarget.py | 7 +- scripts/sconsdist.py | 15 +- targets/f18/api_symbols.csv | 128 ++++-- targets/f7/api_symbols.csv | 130 ++++-- targets/f7/ble_glue/app_common.h | 2 +- targets/f7/ble_glue/app_conf.h | 2 +- targets/f7/ble_glue/ble_app.c | 99 ++-- targets/f7/ble_glue/ble_app.h | 10 +- targets/f7/ble_glue/ble_conf.h | 9 +- targets/f7/ble_glue/ble_event_thread.c | 96 ++++ targets/f7/ble_glue/ble_event_thread.h | 15 + targets/f7/ble_glue/ble_glue.c | 235 ++++------ targets/f7/ble_glue/ble_glue.h | 46 +- targets/f7/ble_glue/ble_tl_hooks.c | 40 ++ targets/f7/ble_glue/extra_beacon.c | 161 +++++++ targets/f7/ble_glue/extra_beacon.h | 98 ++++ .../f7/ble_glue/furi_ble/event_dispatcher.c | 97 ++++ .../f7/ble_glue/furi_ble/event_dispatcher.h | 50 ++ .../{services/gatt_char.c => furi_ble/gatt.c} | 51 ++- targets/f7/ble_glue/furi_ble/gatt.h | 110 +++++ .../f7/ble_glue/furi_ble/profile_interface.h | 39 ++ targets/f7/ble_glue/gap.c | 34 +- targets/f7/ble_glue/gap.h | 16 +- targets/f7/ble_glue/profiles/serial_profile.c | 114 +++++ targets/f7/ble_glue/profiles/serial_profile.h | 61 +++ .../f7/ble_glue/services/battery_service.c | 171 ++++--- .../f7/ble_glue/services/battery_service.h | 22 +- .../f7/ble_glue/services/dev_info_service.c | 121 ++--- .../f7/ble_glue/services/dev_info_service.h | 11 +- targets/f7/ble_glue/services/gatt_char.h | 96 ---- targets/f7/ble_glue/services/hid_service.h | 29 -- targets/f7/ble_glue/services/serial_service.c | 135 +++--- targets/f7/ble_glue/services/serial_service.h | 32 +- .../ble_glue/services/serial_service_uuid.inc | 8 +- targets/f7/furi_hal/furi_hal_bt.c | 221 ++++----- targets/f7/furi_hal/furi_hal_bt_hid.c | 289 ------------ targets/f7/furi_hal/furi_hal_bt_serial.c | 64 --- targets/f7/furi_hal/furi_hal_cortex.c | 2 +- targets/f7/inc/FreeRTOSConfig.h | 10 +- targets/f7/target.json | 5 +- targets/furi_hal_include/furi_hal_bt.h | 116 +++-- targets/furi_hal_include/furi_hal_bt_hid.h | 91 ---- targets/furi_hal_include/furi_hal_bt_serial.h | 59 --- 91 files changed, 3352 insertions(+), 1730 deletions(-) create mode 100644 applications/examples/example_ble_beacon/application.fam create mode 100644 applications/examples/example_ble_beacon/ble_beacon_app.c create mode 100644 applications/examples/example_ble_beacon/ble_beacon_app.h create mode 100644 applications/examples/example_ble_beacon/example_ble_beacon_10px.png create mode 100644 applications/examples/example_ble_beacon/images/lighthouse_35x44.png create mode 100644 applications/examples/example_ble_beacon/scenes/scene_config.h create mode 100644 applications/examples/example_ble_beacon/scenes/scene_input_beacon_data.c create mode 100644 applications/examples/example_ble_beacon/scenes/scene_input_mac_addr.c create mode 100644 applications/examples/example_ble_beacon/scenes/scene_menu.c create mode 100644 applications/examples/example_ble_beacon/scenes/scene_run_beacon.c create mode 100644 applications/examples/example_ble_beacon/scenes/scenes.c create mode 100644 applications/examples/example_ble_beacon/scenes/scenes.h rename applications/{debug => examples}/example_custom_font/application.fam (71%) rename applications/{debug => examples}/example_custom_font/example_custom_font.c (100%) create mode 100644 applications/system/hid_app/transport_ble.c create mode 100644 applications/system/hid_app/transport_usb.c create mode 100644 assets/icons/StatusBar/BLE_beacon_7x8.png create mode 100644 lib/ble_profile/SConscript create mode 100644 lib/ble_profile/extra_profiles/hid_profile.c create mode 100644 lib/ble_profile/extra_profiles/hid_profile.h rename {targets/f7/ble_glue/services => lib/ble_profile/extra_services}/hid_service.c (53%) create mode 100644 lib/ble_profile/extra_services/hid_service.h create mode 100644 targets/f7/ble_glue/ble_event_thread.c create mode 100644 targets/f7/ble_glue/ble_event_thread.h create mode 100644 targets/f7/ble_glue/ble_tl_hooks.c create mode 100644 targets/f7/ble_glue/extra_beacon.c create mode 100644 targets/f7/ble_glue/extra_beacon.h create mode 100644 targets/f7/ble_glue/furi_ble/event_dispatcher.c create mode 100644 targets/f7/ble_glue/furi_ble/event_dispatcher.h rename targets/f7/ble_glue/{services/gatt_char.c => furi_ble/gatt.c} (73%) create mode 100644 targets/f7/ble_glue/furi_ble/gatt.h create mode 100644 targets/f7/ble_glue/furi_ble/profile_interface.h create mode 100644 targets/f7/ble_glue/profiles/serial_profile.c create mode 100644 targets/f7/ble_glue/profiles/serial_profile.h delete mode 100644 targets/f7/ble_glue/services/gatt_char.h delete mode 100644 targets/f7/ble_glue/services/hid_service.h delete mode 100644 targets/f7/furi_hal/furi_hal_bt_hid.c delete mode 100644 targets/f7/furi_hal/furi_hal_bt_serial.c delete mode 100644 targets/furi_hal_include/furi_hal_bt_hid.h delete mode 100644 targets/furi_hal_include/furi_hal_bt_serial.h diff --git a/applications/examples/example_ble_beacon/application.fam b/applications/examples/example_ble_beacon/application.fam new file mode 100644 index 0000000000..fc5a911ab1 --- /dev/null +++ b/applications/examples/example_ble_beacon/application.fam @@ -0,0 +1,11 @@ +App( + appid="example_ble_beacon", + name="Example: BLE Beacon", + apptype=FlipperAppType.EXTERNAL, + entry_point="ble_beacon_app", + requires=["gui"], + stack_size=1 * 1024, + fap_icon="example_ble_beacon_10px.png", + fap_category="Examples", + fap_icon_assets="images", +) diff --git a/applications/examples/example_ble_beacon/ble_beacon_app.c b/applications/examples/example_ble_beacon/ble_beacon_app.c new file mode 100644 index 0000000000..20e3e307ab --- /dev/null +++ b/applications/examples/example_ble_beacon/ble_beacon_app.c @@ -0,0 +1,149 @@ +#include "ble_beacon_app.h" + +#include +#include + +#include + +#define TAG "ble_beacon_app" + +static bool ble_beacon_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BleBeaconApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool ble_beacon_app_back_event_callback(void* context) { + furi_assert(context); + BleBeaconApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void ble_beacon_app_tick_event_callback(void* context) { + furi_assert(context); + BleBeaconApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void ble_beacon_app_restore_beacon_state(BleBeaconApp* app) { + // Restore beacon data from service + GapExtraBeaconConfig* local_config = &app->beacon_config; + const GapExtraBeaconConfig* config = furi_hal_bt_extra_beacon_get_config(); + if(config) { + // We have a config, copy it + memcpy(local_config, config, sizeof(app->beacon_config)); + } else { + // No config, set up default values - they will stay until overriden or device is reset + local_config->min_adv_interval_ms = 50; + local_config->max_adv_interval_ms = 150; + + local_config->adv_channel_map = GapAdvChannelMapAll; + local_config->adv_power_level = GapAdvPowerLevel_0dBm; + + local_config->address_type = GapAddressTypePublic; + memcpy( + local_config->address, furi_hal_version_get_ble_mac(), sizeof(local_config->address)); + // Modify MAC address to make it different from the one used by the main app + local_config->address[0] ^= 0xFF; + local_config->address[3] ^= 0xFF; + + furi_check(furi_hal_bt_extra_beacon_set_config(local_config)); + } + + // Get beacon state + app->is_beacon_active = furi_hal_bt_extra_beacon_is_active(); + + // Restore last beacon data + app->beacon_data_len = furi_hal_bt_extra_beacon_get_data(app->beacon_data); +} + +static BleBeaconApp* ble_beacon_app_alloc() { + BleBeaconApp* app = malloc(sizeof(BleBeaconApp)); + + app->gui = furi_record_open(RECORD_GUI); + + app->scene_manager = scene_manager_alloc(&ble_beacon_app_scene_handlers, app); + app->view_dispatcher = view_dispatcher_alloc(); + + app->status_string = furi_string_alloc(); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, ble_beacon_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, ble_beacon_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, ble_beacon_app_tick_event_callback, 100); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BleBeaconAppViewSubmenu, submenu_get_view(app->submenu)); + + app->dialog_ex = dialog_ex_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BleBeaconAppViewDialog, dialog_ex_get_view(app->dialog_ex)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BleBeaconAppViewByteInput, byte_input_get_view(app->byte_input)); + + ble_beacon_app_restore_beacon_state(app); + + return app; +} + +static void ble_beacon_app_free(BleBeaconApp* app) { + view_dispatcher_remove_view(app->view_dispatcher, BleBeaconAppViewByteInput); + view_dispatcher_remove_view(app->view_dispatcher, BleBeaconAppViewSubmenu); + view_dispatcher_remove_view(app->view_dispatcher, BleBeaconAppViewDialog); + + free(app->byte_input); + free(app->submenu); + free(app->dialog_ex); + + free(app->scene_manager); + free(app->view_dispatcher); + + free(app->status_string); + + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_GUI); + app->gui = NULL; + + free(app); +} + +int32_t ble_beacon_app(void* args) { + UNUSED(args); + + BleBeaconApp* app = ble_beacon_app_alloc(); + + scene_manager_next_scene(app->scene_manager, BleBeaconAppSceneRunBeacon); + + view_dispatcher_run(app->view_dispatcher); + + ble_beacon_app_free(app); + return 0; +} + +void ble_beacon_app_update_state(BleBeaconApp* app) { + furi_hal_bt_extra_beacon_stop(); + + furi_check(furi_hal_bt_extra_beacon_set_config(&app->beacon_config)); + + app->beacon_data_len = 0; + while((app->beacon_data[app->beacon_data_len] != 0) && + (app->beacon_data_len < sizeof(app->beacon_data))) { + app->beacon_data_len++; + } + + FURI_LOG_I(TAG, "beacon_data_len: %d", app->beacon_data_len); + + furi_check(furi_hal_bt_extra_beacon_set_data(app->beacon_data, app->beacon_data_len)); + + if(app->is_beacon_active) { + furi_check(furi_hal_bt_extra_beacon_start()); + } +} diff --git a/applications/examples/example_ble_beacon/ble_beacon_app.h b/applications/examples/example_ble_beacon/ble_beacon_app.h new file mode 100644 index 0000000000..563bd5beda --- /dev/null +++ b/applications/examples/example_ble_beacon/ble_beacon_app.h @@ -0,0 +1,50 @@ +#pragma once + +#include "extra_beacon.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include "scenes/scenes.h" +#include + +typedef struct { + Gui* gui; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + + Submenu* submenu; + ByteInput* byte_input; + DialogEx* dialog_ex; + + FuriString* status_string; + + GapExtraBeaconConfig beacon_config; + uint8_t beacon_data[EXTRA_BEACON_MAX_DATA_SIZE]; + uint8_t beacon_data_len; + bool is_beacon_active; +} BleBeaconApp; + +typedef enum { + BleBeaconAppViewSubmenu, + BleBeaconAppViewByteInput, + BleBeaconAppViewDialog, +} BleBeaconAppView; + +typedef enum { + BleBeaconAppCustomEventDataEditResult = 100, +} BleBeaconAppCustomEvent; + +void ble_beacon_app_update_state(BleBeaconApp* app); diff --git a/applications/examples/example_ble_beacon/example_ble_beacon_10px.png b/applications/examples/example_ble_beacon/example_ble_beacon_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..7060e893db14944726f2d5141ca6fb29f5333bc8 GIT binary patch literal 8727 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 zAview_dispatcher, BleBeaconAppCustomEventDataEditResult); +} + +void ble_beacon_app_scene_input_beacon_data_on_enter(void* context) { + BleBeaconApp* ble_beacon = context; + byte_input_set_header_text(ble_beacon->byte_input, "Enter beacon data"); + + byte_input_set_result_callback( + ble_beacon->byte_input, + ble_beacon_app_scene_add_type_byte_input_callback, + NULL, + context, + ble_beacon->beacon_data, + sizeof(ble_beacon->beacon_data)); + + view_dispatcher_switch_to_view(ble_beacon->view_dispatcher, BleBeaconAppViewByteInput); +} + +bool ble_beacon_app_scene_input_beacon_data_on_event(void* context, SceneManagerEvent event) { + BleBeaconApp* ble_beacon = context; + SceneManager* scene_manager = ble_beacon->scene_manager; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BleBeaconAppCustomEventDataEditResult) { + ble_beacon_app_update_state(ble_beacon); + scene_manager_previous_scene(scene_manager); + return true; + } + } + + return false; +} + +void ble_beacon_app_scene_input_beacon_data_on_exit(void* context) { + BleBeaconApp* ble_beacon = context; + + byte_input_set_result_callback(ble_beacon->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(ble_beacon->byte_input, NULL); +} diff --git a/applications/examples/example_ble_beacon/scenes/scene_input_mac_addr.c b/applications/examples/example_ble_beacon/scenes/scene_input_mac_addr.c new file mode 100644 index 0000000000..003934bbad --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scene_input_mac_addr.c @@ -0,0 +1,44 @@ +#include "../ble_beacon_app.h" + +static void ble_beacon_app_scene_add_type_byte_input_callback(void* context) { + BleBeaconApp* ble_beacon = context; + view_dispatcher_send_custom_event( + ble_beacon->view_dispatcher, BleBeaconAppCustomEventDataEditResult); +} + +void ble_beacon_app_scene_input_mac_addr_on_enter(void* context) { + BleBeaconApp* ble_beacon = context; + byte_input_set_header_text(ble_beacon->byte_input, "Enter MAC (reversed)"); + + byte_input_set_result_callback( + ble_beacon->byte_input, + ble_beacon_app_scene_add_type_byte_input_callback, + NULL, + context, + ble_beacon->beacon_config.address, + sizeof(ble_beacon->beacon_config.address)); + + view_dispatcher_switch_to_view(ble_beacon->view_dispatcher, BleBeaconAppViewByteInput); +} + +bool ble_beacon_app_scene_input_mac_addr_on_event(void* context, SceneManagerEvent event) { + BleBeaconApp* ble_beacon = context; + SceneManager* scene_manager = ble_beacon->scene_manager; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BleBeaconAppCustomEventDataEditResult) { + ble_beacon_app_update_state(ble_beacon); + scene_manager_previous_scene(scene_manager); + return true; + } + } + + return false; +} + +void ble_beacon_app_scene_input_mac_addr_on_exit(void* context) { + BleBeaconApp* ble_beacon = context; + + byte_input_set_result_callback(ble_beacon->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(ble_beacon->byte_input, NULL); +} diff --git a/applications/examples/example_ble_beacon/scenes/scene_menu.c b/applications/examples/example_ble_beacon/scenes/scene_menu.c new file mode 100644 index 0000000000..83223e93ca --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scene_menu.c @@ -0,0 +1,56 @@ +#include "../ble_beacon_app.h" + +enum SubmenuIndex { + SubmenuIndexSetMac, + SubmenuIndexSetData, +}; + +static void ble_beacon_app_scene_menu_submenu_callback(void* context, uint32_t index) { + BleBeaconApp* ble_beacon = context; + view_dispatcher_send_custom_event(ble_beacon->view_dispatcher, index); +} + +void ble_beacon_app_scene_menu_on_enter(void* context) { + BleBeaconApp* ble_beacon = context; + Submenu* submenu = ble_beacon->submenu; + + submenu_add_item( + submenu, + "Set MAC", + SubmenuIndexSetMac, + ble_beacon_app_scene_menu_submenu_callback, + ble_beacon); + submenu_add_item( + submenu, + "Set Data", + SubmenuIndexSetData, + ble_beacon_app_scene_menu_submenu_callback, + ble_beacon); + + view_dispatcher_switch_to_view(ble_beacon->view_dispatcher, BleBeaconAppViewSubmenu); +} + +bool ble_beacon_app_scene_menu_on_event(void* context, SceneManagerEvent event) { + BleBeaconApp* ble_beacon = context; + SceneManager* scene_manager = ble_beacon->scene_manager; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + const uint32_t submenu_index = event.event; + if(submenu_index == SubmenuIndexSetMac) { + scene_manager_next_scene(scene_manager, BleBeaconAppSceneInputMacAddress); + consumed = true; + } else if(submenu_index == SubmenuIndexSetData) { + scene_manager_next_scene(scene_manager, BleBeaconAppSceneInputBeaconData); + consumed = true; + } + } + + return consumed; +} + +void ble_beacon_app_scene_menu_on_exit(void* context) { + BleBeaconApp* ble_beacon = context; + submenu_reset(ble_beacon->submenu); +} diff --git a/applications/examples/example_ble_beacon/scenes/scene_run_beacon.c b/applications/examples/example_ble_beacon/scenes/scene_run_beacon.c new file mode 100644 index 0000000000..121001e0ee --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scene_run_beacon.c @@ -0,0 +1,79 @@ +#include "../ble_beacon_app.h" +#include + +static void + ble_beacon_app_scene_run_beacon_confirm_dialog_callback(DialogExResult result, void* context) { + BleBeaconApp* ble_beacon = context; + + view_dispatcher_send_custom_event(ble_beacon->view_dispatcher, result); +} + +static void update_status_text(BleBeaconApp* ble_beacon) { + DialogEx* dialog_ex = ble_beacon->dialog_ex; + + dialog_ex_set_header(dialog_ex, "BLE Beacon Demo", 64, 0, AlignCenter, AlignTop); + + FuriString* status = ble_beacon->status_string; + + furi_string_reset(status); + + furi_string_cat_str(status, "Status: "); + if(ble_beacon->is_beacon_active) { + furi_string_cat_str(status, "Running\n"); + } else { + furi_string_cat_str(status, "Stopped\n"); + } + + // Output MAC in reverse order + for(int i = sizeof(ble_beacon->beacon_config.address) - 1; i >= 0; i--) { + furi_string_cat_printf(status, "%02X", ble_beacon->beacon_config.address[i]); + if(i > 0) { + furi_string_cat_str(status, ":"); + } + } + + furi_string_cat_printf(status, "\nData length: %d", ble_beacon->beacon_data_len); + + dialog_ex_set_text(dialog_ex, furi_string_get_cstr(status), 0, 29, AlignLeft, AlignCenter); + + dialog_ex_set_icon(dialog_ex, 93, 20, &I_lighthouse_35x44); + + dialog_ex_set_left_button_text(dialog_ex, "Config"); + + dialog_ex_set_center_button_text(dialog_ex, ble_beacon->is_beacon_active ? "Stop" : "Start"); + + dialog_ex_set_result_callback( + dialog_ex, ble_beacon_app_scene_run_beacon_confirm_dialog_callback); + dialog_ex_set_context(dialog_ex, ble_beacon); +} + +void ble_beacon_app_scene_run_beacon_on_enter(void* context) { + BleBeaconApp* ble_beacon = context; + + update_status_text(ble_beacon); + + view_dispatcher_switch_to_view(ble_beacon->view_dispatcher, BleBeaconAppViewDialog); +} + +bool ble_beacon_app_scene_run_beacon_on_event(void* context, SceneManagerEvent event) { + BleBeaconApp* ble_beacon = context; + SceneManager* scene_manager = ble_beacon->scene_manager; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultLeft) { + scene_manager_next_scene(scene_manager, BleBeaconAppSceneMenu); + return true; + } else if(event.event == DialogExResultCenter) { + ble_beacon->is_beacon_active = !ble_beacon->is_beacon_active; + ble_beacon_app_update_state(ble_beacon); + update_status_text(ble_beacon); + return true; + } + } + return false; +} + +void ble_beacon_app_scene_run_beacon_on_exit(void* context) { + BleBeaconApp* ble_beacon = context; + UNUSED(ble_beacon); +} diff --git a/applications/examples/example_ble_beacon/scenes/scenes.c b/applications/examples/example_ble_beacon/scenes/scenes.c new file mode 100644 index 0000000000..13e7ac8323 --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scenes.c @@ -0,0 +1,30 @@ +#include "scenes.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const ble_beacon_app_on_enter_handlers[])(void*) = { +#include "scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const ble_beacon_app_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const ble_beacon_app_on_exit_handlers[])(void* context) = { +#include "scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers ble_beacon_app_scene_handlers = { + .on_enter_handlers = ble_beacon_app_on_enter_handlers, + .on_event_handlers = ble_beacon_app_on_event_handlers, + .on_exit_handlers = ble_beacon_app_on_exit_handlers, + .scene_num = BleBeaconAppSceneNum, +}; diff --git a/applications/examples/example_ble_beacon/scenes/scenes.h b/applications/examples/example_ble_beacon/scenes/scenes.h new file mode 100644 index 0000000000..64d15350d7 --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scenes.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BleBeaconAppScene##id, +typedef enum { +#include "scene_config.h" + BleBeaconAppSceneNum, +} BleBeaconAppScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers ble_beacon_app_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "scene_config.h" +#undef ADD_SCENE diff --git a/applications/debug/example_custom_font/application.fam b/applications/examples/example_custom_font/application.fam similarity index 71% rename from applications/debug/example_custom_font/application.fam rename to applications/examples/example_custom_font/application.fam index 06c0a7f618..45cc08d442 100644 --- a/applications/debug/example_custom_font/application.fam +++ b/applications/examples/example_custom_font/application.fam @@ -1,9 +1,9 @@ App( appid="example_custom_font", name="Example: custom font", - apptype=FlipperAppType.DEBUG, + apptype=FlipperAppType.EXTERNAL, entry_point="example_custom_font_main", requires=["gui"], stack_size=1 * 1024, - fap_category="Debug", + fap_category="Examples", ) diff --git a/applications/debug/example_custom_font/example_custom_font.c b/applications/examples/example_custom_font/example_custom_font.c similarity index 100% rename from applications/debug/example_custom_font/example_custom_font.c rename to applications/examples/example_custom_font/example_custom_font.c diff --git a/applications/main/archive/helpers/archive_apps.h b/applications/main/archive/helpers/archive_apps.h index 8bc904587b..d9d1dec345 100644 --- a/applications/main/archive/helpers/archive_apps.h +++ b/applications/main/archive/helpers/archive_apps.h @@ -1,5 +1,7 @@ #pragma once +#include "archive_files.h" + typedef enum { ArchiveAppTypeU2f, ArchiveAppTypeUnknown, diff --git a/applications/main/nfc/plugins/supported_cards/opal.c b/applications/main/nfc/plugins/supported_cards/opal.c index 64c279ffe3..f9c386326d 100644 --- a/applications/main/nfc/plugins/supported_cards/opal.c +++ b/applications/main/nfc/plugins/supported_cards/opal.c @@ -61,7 +61,7 @@ static const char* opal_usages[14] = { }; // Opal file 0x7 structure. Assumes a little-endian CPU. -typedef struct __attribute__((__packed__)) { +typedef struct FURI_PACKED { uint32_t serial : 32; uint8_t check_digit : 4; bool blocked : 1; diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c index 044820ef39..ba8e481be1 100644 --- a/applications/main/nfc/plugins/supported_cards/umarsh.c +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -23,7 +23,6 @@ * along with this program. If not, see . */ -#include "core/core_defines.h" #include "nfc_supported_card_plugin.h" #include "protocols/mf_classic/mf_classic.h" diff --git a/applications/services/bt/bt_cli.c b/applications/services/bt/bt_cli.c index 02bf6cee4a..e8ba215bfe 100644 --- a/applications/services/bt/bt_cli.c +++ b/applications/services/bt/bt_cli.c @@ -6,6 +6,7 @@ #include #include "bt_settings.h" #include "bt_service/bt.h" +#include static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) { UNUSED(cli); @@ -45,7 +46,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) } furi_hal_bt_stop_tone_tx(); - bt_set_profile(bt, BtProfileSerial); + bt_profile_restore_default(bt); furi_record_close(RECORD_BT); } while(false); } @@ -76,7 +77,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) furi_hal_bt_stop_packet_test(); - bt_set_profile(bt, BtProfileSerial); + bt_profile_restore_default(bt); furi_record_close(RECORD_BT); } while(false); } @@ -124,7 +125,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) furi_hal_bt_stop_packet_test(); printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets()); - bt_set_profile(bt, BtProfileSerial); + bt_profile_restore_default(bt); furi_record_close(RECORD_BT); } while(false); } @@ -159,7 +160,7 @@ static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) uint16_t packets_received = furi_hal_bt_stop_packet_test(); printf("Received %hu packets", packets_received); - bt_set_profile(bt, BtProfileSerial); + bt_profile_restore_default(bt); furi_record_close(RECORD_BT); } while(false); } diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 36409fe5cd..4ef3a15953 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -1,10 +1,13 @@ #include "bt_i.h" #include "bt_keys_storage.h" +#include +#include #include #include #include #include +#include #define TAG "BtSrv" @@ -12,14 +15,21 @@ #define BT_RPC_EVENT_DISCONNECTED (1UL << 1) #define BT_RPC_EVENT_ALL (BT_RPC_EVENT_BUFF_SENT | BT_RPC_EVENT_DISCONNECTED) +#define ICON_SPACER 2 + static void bt_draw_statusbar_callback(Canvas* canvas, void* context) { furi_assert(context); Bt* bt = context; + uint8_t draw_offset = 0; + if(bt->beacon_active) { + canvas_draw_icon(canvas, 0, 0, &I_BLE_beacon_7x8); + draw_offset += icon_get_width(&I_BLE_beacon_7x8) + ICON_SPACER; + } if(bt->status == BtStatusAdvertising) { - canvas_draw_icon(canvas, 0, 0, &I_Bluetooth_Idle_5x8); + canvas_draw_icon(canvas, draw_offset, 0, &I_Bluetooth_Idle_5x8); } else if(bt->status == BtStatusConnected) { - canvas_draw_icon(canvas, 0, 0, &I_Bluetooth_Connected_16x8); + canvas_draw_icon(canvas, draw_offset, 0, &I_Bluetooth_Connected_16x8); } } @@ -61,6 +71,11 @@ static ViewPort* bt_pin_code_view_port_alloc(Bt* bt) { static void bt_pin_code_show(Bt* bt, uint32_t pin_code) { bt->pin_code = pin_code; + if(!bt->pin_code_view_port) { + // Pin code view port + bt->pin_code_view_port = bt_pin_code_view_port_alloc(bt); + gui_add_view_port(bt->gui, bt->pin_code_view_port, GuiLayerFullscreen); + } notification_message(bt->notification, &sequence_display_backlight_on); gui_view_port_send_to_front(bt->gui, bt->pin_code_view_port); view_port_enabled_set(bt->pin_code_view_port, true); @@ -68,7 +83,7 @@ static void bt_pin_code_show(Bt* bt, uint32_t pin_code) { static void bt_pin_code_hide(Bt* bt) { bt->pin_code = 0; - if(view_port_is_enabled(bt->pin_code_view_port)) { + if(bt->pin_code_view_port && view_port_is_enabled(bt->pin_code_view_port)) { view_port_enabled_set(bt->pin_code_view_port, false); } } @@ -77,6 +92,9 @@ static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { furi_assert(bt); notification_message(bt->notification, &sequence_display_backlight_on); FuriString* pin_str; + if(!bt->dialog_message) { + bt->dialog_message = dialog_message_alloc(); + } dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); pin_str = furi_string_alloc_printf("Verify code\n%06lu", pin); dialog_message_set_text( @@ -94,25 +112,32 @@ static void bt_battery_level_changed_callback(const void* _event, void* context) Bt* bt = context; BtMessage message = {}; const PowerEvent* event = _event; - if(event->type == PowerEventTypeBatteryLevelChanged) { + bool is_charging = false; + switch(event->type) { + case PowerEventTypeBatteryLevelChanged: message.type = BtMessageTypeUpdateBatteryLevel; message.data.battery_level = event->data.battery_level; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); - } else if( - event->type == PowerEventTypeStartCharging || event->type == PowerEventTypeFullyCharged || - event->type == PowerEventTypeStopCharging) { + break; + case PowerEventTypeStartCharging: + is_charging = true; + /* fallthrough */ + case PowerEventTypeFullyCharged: + case PowerEventTypeStopCharging: message.type = BtMessageTypeUpdatePowerState; + message.data.power_state_charging = is_charging; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); + break; } } Bt* bt_alloc() { Bt* bt = malloc(sizeof(Bt)); // Init default maximum packet size - bt->max_packet_size = FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX; - bt->profile = BtProfileSerial; + bt->max_packet_size = BLE_PROFILE_SERIAL_PACKET_SIZE_MAX; + bt->current_profile = NULL; // Load settings if(!bt_settings_load(&bt->bt_settings)) { bt_settings_save(&bt->bt_settings); @@ -124,18 +149,14 @@ Bt* bt_alloc() { // Setup statusbar view port bt->statusbar_view_port = bt_statusbar_view_port_alloc(bt); - // Pin code view port - bt->pin_code_view_port = bt_pin_code_view_port_alloc(bt); // Notification bt->notification = furi_record_open(RECORD_NOTIFICATION); // Gui bt->gui = furi_record_open(RECORD_GUI); gui_add_view_port(bt->gui, bt->statusbar_view_port, GuiLayerStatusBarLeft); - gui_add_view_port(bt->gui, bt->pin_code_view_port, GuiLayerFullscreen); // Dialogs bt->dialogs = furi_record_open(RECORD_DIALOGS); - bt->dialog_message = dialog_message_alloc(); // Power bt->power = furi_record_open(RECORD_POWER); @@ -170,7 +191,11 @@ static uint16_t bt_serial_event_callback(SerialServiceEvent event, void* context furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_BUFF_SENT); } else if(event.event == SerialServiceEventTypesBleResetRequest) { FURI_LOG_I(TAG, "BLE restart request received"); - BtMessage message = {.type = BtMessageTypeSetProfile, .data.profile = BtProfileSerial}; + BtMessage message = { + .type = BtMessageTypeSetProfile, + .data.profile.params = NULL, + .data.profile.template = ble_profile_serial, + }; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } @@ -191,10 +216,10 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt while(bytes_sent < bytes_len) { size_t bytes_remain = bytes_len - bytes_sent; if(bytes_remain > bt->max_packet_size) { - furi_hal_bt_serial_tx(&bytes[bytes_sent], bt->max_packet_size); + ble_profile_serial_tx(bt->current_profile, &bytes[bytes_sent], bt->max_packet_size); bytes_sent += bt->max_packet_size; } else { - furi_hal_bt_serial_tx(&bytes[bytes_sent], bytes_remain); + ble_profile_serial_tx(bt->current_profile, &bytes[bytes_sent], bytes_remain); bytes_sent += bytes_remain; } // We want BT_RPC_EVENT_DISCONNECTED to stick, so don't clear @@ -209,32 +234,42 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt } } +static void bt_serial_buffer_is_empty_callback(void* context) { + furi_assert(context); + Bt* bt = context; + furi_check(furi_hal_bt_check_profile_type(bt->current_profile, ble_profile_serial)); + ble_profile_serial_notify_buffer_is_empty(bt->current_profile); +} + // Called from GAP thread static bool bt_on_gap_event_callback(GapEvent event, void* context) { furi_assert(context); Bt* bt = context; bool ret = false; + bool do_update_status = false; + bool current_profile_is_serial = + furi_hal_bt_check_profile_type(bt->current_profile, ble_profile_serial); if(event.type == GapEventTypeConnected) { // Update status bar bt->status = BtStatusConnected; - BtMessage message = {.type = BtMessageTypeUpdateStatus}; - furi_check( - furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); + do_update_status = true; // Clear BT_RPC_EVENT_DISCONNECTED because it might be set from previous session furi_event_flag_clear(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); - if(bt->profile == BtProfileSerial) { + + if(current_profile_is_serial) { // Open RPC session bt->rpc_session = rpc_session_open(bt->rpc, RpcOwnerBle); if(bt->rpc_session) { FURI_LOG_I(TAG, "Open RPC connection"); 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); + bt->rpc_session, bt_serial_buffer_is_empty_callback); rpc_session_set_context(bt->rpc_session, bt); - furi_hal_bt_serial_set_event_callback( - RPC_BUFFER_SIZE, bt_serial_event_callback, bt); - furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatusActive); + ble_profile_serial_set_event_callback( + bt->current_profile, RPC_BUFFER_SIZE, bt_serial_event_callback, bt); + ble_profile_serial_set_rpc_active( + bt->current_profile, FuriHalBtSerialRpcStatusActive); } else { FURI_LOG_W(TAG, "RPC is busy, failed to open new session"); } @@ -242,32 +277,30 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { // Update battery level PowerInfo info; power_get_info(bt->power, &info); + BtMessage message = {.type = BtMessageTypeUpdateStatus}; message.type = BtMessageTypeUpdateBatteryLevel; message.data.battery_level = info.charge; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); ret = true; } else if(event.type == GapEventTypeDisconnected) { - if(bt->profile == BtProfileSerial && bt->rpc_session) { + if(current_profile_is_serial && bt->rpc_session) { FURI_LOG_I(TAG, "Close RPC connection"); - furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatusNotActive); + ble_profile_serial_set_rpc_active( + bt->current_profile, FuriHalBtSerialRpcStatusNotActive); furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); rpc_session_close(bt->rpc_session); - furi_hal_bt_serial_set_event_callback(0, NULL, NULL); + ble_profile_serial_set_event_callback(bt->current_profile, 0, NULL, NULL); bt->rpc_session = NULL; } ret = true; } else if(event.type == GapEventTypeStartAdvertising) { bt->status = BtStatusAdvertising; - BtMessage message = {.type = BtMessageTypeUpdateStatus}; - furi_check( - furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); + do_update_status = true; ret = true; } else if(event.type == GapEventTypeStopAdvertising) { bt->status = BtStatusOff; - BtMessage message = {.type = BtMessageTypeUpdateStatus}; - furi_check( - furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); + do_update_status = true; ret = true; } else if(event.type == GapEventTypePinCodeShow) { BtMessage message = { @@ -280,6 +313,20 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { } else if(event.type == GapEventTypeUpdateMTU) { bt->max_packet_size = event.data.max_packet_size; ret = true; + } else if(event.type == GapEventTypeBeaconStart) { + bt->beacon_active = true; + do_update_status = true; + ret = true; + } else if(event.type == GapEventTypeBeaconStop) { + bt->beacon_active = false; + do_update_status = true; + ret = true; + } + + if(do_update_status) { + BtMessage message = {.type = BtMessageTypeUpdateStatus}; + furi_check( + furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } return ret; } @@ -296,11 +343,18 @@ static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void } static void bt_statusbar_update(Bt* bt) { + uint8_t active_icon_width = 0; + if(bt->beacon_active) { + active_icon_width = icon_get_width(&I_BLE_beacon_7x8) + ICON_SPACER; + } if(bt->status == BtStatusAdvertising) { - view_port_set_width(bt->statusbar_view_port, icon_get_width(&I_Bluetooth_Idle_5x8)); - view_port_enabled_set(bt->statusbar_view_port, true); + active_icon_width += icon_get_width(&I_Bluetooth_Idle_5x8); } else if(bt->status == BtStatusConnected) { - view_port_set_width(bt->statusbar_view_port, icon_get_width(&I_Bluetooth_Connected_16x8)); + active_icon_width += icon_get_width(&I_Bluetooth_Connected_16x8); + } + + if(active_icon_width > 0) { + view_port_set_width(bt->statusbar_view_port, active_icon_width); view_port_enabled_set(bt->statusbar_view_port, true); } else { view_port_enabled_set(bt->statusbar_view_port, false); @@ -308,56 +362,61 @@ static void bt_statusbar_update(Bt* bt) { } static void bt_show_warning(Bt* bt, const char* text) { + if(!bt->dialog_message) { + bt->dialog_message = dialog_message_alloc(); + } dialog_message_set_text(bt->dialog_message, text, 64, 28, AlignCenter, AlignCenter); dialog_message_set_buttons(bt->dialog_message, "Quit", NULL, NULL); dialog_message_show(bt->dialogs, bt->dialog_message); } static void bt_close_rpc_connection(Bt* bt) { - if(bt->profile == BtProfileSerial && bt->rpc_session) { + if(furi_hal_bt_check_profile_type(bt->current_profile, ble_profile_serial) && + bt->rpc_session) { FURI_LOG_I(TAG, "Close RPC connection"); furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); rpc_session_close(bt->rpc_session); - furi_hal_bt_serial_set_event_callback(0, NULL, NULL); + ble_profile_serial_set_event_callback(bt->current_profile, 0, NULL, NULL); bt->rpc_session = NULL; } } static void bt_change_profile(Bt* bt, BtMessage* message) { - if(furi_hal_bt_is_ble_gatt_gap_supported()) { + if(furi_hal_bt_is_gatt_gap_supported()) { bt_settings_load(&bt->bt_settings); bt_close_rpc_connection(bt); - FuriHalBtProfile furi_profile; - if(message->data.profile == BtProfileHidKeyboard) { - furi_profile = FuriHalBtProfileHidKeyboard; - } else { - furi_profile = FuriHalBtProfileSerial; - } - bt_keys_storage_load(bt->keys_storage); - if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) { + bt->current_profile = furi_hal_bt_change_app( + message->data.profile.template, + message->data.profile.params, + bt_on_gap_event_callback, + bt); + if(bt->current_profile) { 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; - if(message->result) { - *message->result = true; - } } else { FURI_LOG_E(TAG, "Failed to start Bt App"); - if(message->result) { - *message->result = false; - } } + if(message->profile_instance) { + *message->profile_instance = bt->current_profile; + } + if(message->result) { + *message->result = bt->current_profile != NULL; + } + } else { bt_show_warning(bt, "Radio stack doesn't support this app"); if(message->result) { *message->result = false; } + if(message->profile_instance) { + *message->profile_instance = NULL; + } } if(message->lock) api_lock_unlock(message->lock); } @@ -389,8 +448,10 @@ int32_t bt_srv(void* p) { FURI_LOG_E(TAG, "Radio stack start failed"); } - if(furi_hal_bt_is_ble_gatt_gap_supported()) { - if(!furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) { + if(furi_hal_bt_is_gatt_gap_supported()) { + bt->current_profile = + furi_hal_bt_start_app(ble_profile_serial, NULL, bt_on_gap_event_callback, bt); + if(!bt->current_profile) { FURI_LOG_E(TAG, "BLE App start failed"); } else { if(bt->bt_settings.enabled) { @@ -420,7 +481,7 @@ int32_t bt_srv(void* p) { // Update battery level furi_hal_bt_update_battery_level(message.data.battery_level); } else if(message.type == BtMessageTypeUpdatePowerState) { - furi_hal_bt_update_power_state(); + furi_hal_bt_update_power_state(message.data.power_state_charging); } else if(message.type == BtMessageTypePinCodeShow) { // Display PIN code bt_pin_code_show(bt, message.data.pin_code); diff --git a/applications/services/bt/bt_service/bt.h b/applications/services/bt/bt_service/bt.h index ca47936dbb..270922543e 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -2,6 +2,8 @@ #include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -18,22 +20,30 @@ typedef enum { BtStatusConnected, } BtStatus; -typedef enum { - BtProfileSerial, - BtProfileHidKeyboard, -} BtProfile; - typedef void (*BtStatusChangedCallback)(BtStatus status, void* context); /** Change BLE Profile + * @note Call of this function leads to 2nd core restart + * + * @param bt Bt instance + * @param profile_template Profile template to change to + * @param params Profile parameters. Can be NULL + * + * @return true on success + */ +FURI_WARN_UNUSED FuriHalBleProfileBase* bt_profile_start( + Bt* bt, + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams params); + +/** Stop current BLE Profile and restore default 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); +bool bt_profile_restore_default(Bt* bt); /** Disconnect from Central * diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c index e31031783b..ab5d201281 100644 --- a/applications/services/bt/bt_service/bt_api.c +++ b/applications/services/bt/bt_service/bt_api.c @@ -1,21 +1,34 @@ #include "bt_i.h" +#include -bool bt_set_profile(Bt* bt, BtProfile profile) { +FuriHalBleProfileBase* bt_profile_start( + Bt* bt, + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams params) { furi_assert(bt); // Send message - bool result = false; + FuriHalBleProfileBase* profile_instance = NULL; + BtMessage message = { .lock = api_lock_alloc_locked(), .type = BtMessageTypeSetProfile, - .data.profile = profile, - .result = &result}; + .profile_instance = &profile_instance, + .data.profile.params = params, + .data.profile.template = profile_template, + }; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); // Wait for unlock api_lock_wait_unlock_and_free(message.lock); - return result; + bt->current_profile = profile_instance; + return profile_instance; +} + +bool bt_profile_restore_default(Bt* bt) { + bt->current_profile = bt_profile_start(bt, ble_profile_serial, NULL); + return bt->current_profile != NULL; } void bt_disconnect(Bt* bt) { diff --git a/applications/services/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h index 55bae76f3b..04c1734b75 100644 --- a/applications/services/bt/bt_service/bt_i.h +++ b/applications/services/bt/bt_service/bt_i.h @@ -42,7 +42,12 @@ typedef struct { typedef union { uint32_t pin_code; uint8_t battery_level; - BtProfile profile; + bool power_state_charging; + struct { + const FuriHalBleProfileTemplate* template; + FuriHalBleProfileParams params; + } profile; + FuriHalBleProfileParams profile_params; BtKeyStorageUpdateData key_storage_data; } BtMessageData; @@ -51,6 +56,7 @@ typedef struct { BtMessageType type; BtMessageData data; bool* result; + FuriHalBleProfileBase** profile_instance; } BtMessage; struct Bt { @@ -60,7 +66,8 @@ struct Bt { BtSettings bt_settings; BtKeysStorage* keys_storage; BtStatus status; - BtProfile profile; + bool beacon_active; + FuriHalBleProfileBase* current_profile; FuriMessageQueue* message_queue; NotificationApp* notification; Gui* gui; diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index d024627349..67511a194f 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -1,6 +1,5 @@ #include "cli_command_gpio.h" -#include "core/string.h" #include #include #include diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index df54bf3f4d..dd60e820ed 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -1,6 +1,7 @@ #include "cli_commands.h" #include "cli_command_gpio.h" +#include #include #include #include @@ -388,27 +389,30 @@ void cli_command_ps(Cli* cli, FuriString* args, void* context) { const uint8_t threads_num_max = 32; FuriThreadId threads_ids[threads_num_max]; - uint8_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max); + uint32_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max); printf( - "%-20s %-20s %-14s %-8s %-8s %s\r\n", + "%-17s %-20s %-5s %-13s %-6s %-8s %s\r\n", "AppID", "Name", + "Prio", "Stack start", "Heap", "Stack", "Stack min free"); for(uint8_t i = 0; i < thread_num; i++) { TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i]; + size_t thread_heap = memmgr_heap_get_thread_memory(threads_ids[i]); printf( - "%-20s %-20s 0x%-12lx %-8zu %-8lu %-8lu\r\n", + "%-17s %-20s %-5d 0x%-11lx %-6zu %-8lu %-8lu\r\n", furi_thread_get_appid(threads_ids[i]), furi_thread_get_name(threads_ids[i]), + furi_thread_get_priority(threads_ids[i]), (uint32_t)tcb->pxStack, - memmgr_heap_get_thread_memory(threads_ids[i]), + thread_heap == MEMMGR_HEAP_UNKNOWN ? 0u : thread_heap, (uint32_t)(tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t), furi_thread_get_stack_space(threads_ids[i])); } - printf("\r\nTotal: %d", thread_num); + printf("\r\nTotal: %lu", thread_num); } void cli_command_free(Cli* cli, FuriString* args, void* context) { diff --git a/applications/services/gui/modules/widget_elements/widget_element_i.h b/applications/services/gui/modules/widget_elements/widget_element_i.h index 67dea4b1f6..456a831729 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_i.h +++ b/applications/services/gui/modules/widget_elements/widget_element_i.h @@ -4,10 +4,12 @@ */ #pragma once + +#include "../widget.h" +#include "widget_element.h" #include #include #include -#include "widget_element.h" #ifdef __cplusplus extern "C" { diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 158b95de64..1520c1f3da 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -1,5 +1,4 @@ #include "loader.h" -#include "core/core_defines.h" #include "loader_i.h" #include #include diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index d33ea178c8..53b139dd49 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -1,3 +1,4 @@ +#include "profiles/serial_profile.h" #include "rpc_i.h" #include @@ -331,7 +332,7 @@ static int32_t rpc_session_worker(void* context) { // Disconnect BLE session FURI_LOG_E("RPC", "BLE session closed due to a decode error"); Bt* bt = furi_record_open(RECORD_BT); - bt_set_profile(bt, BtProfileSerial); + bt_profile_restore_default(bt); furi_record_close(RECORD_BT); FURI_LOG_E("RPC", "Finished disconnecting the BLE session"); } diff --git a/applications/services/rpc/rpc.h b/applications/services/rpc/rpc.h index f7cda64f73..d34efb4f8a 100644 --- a/applications/services/rpc/rpc.h +++ b/applications/services/rpc/rpc.h @@ -94,6 +94,7 @@ void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallba * * @param session pointer to RpcSession descriptor * @param callback callback to notify client that buffer is empty (can be NULL) + * @param context context to pass to callback */ void rpc_session_set_buffer_is_empty_callback( RpcSession* session, diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c index 5db98e9dec..c148f09435 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c @@ -39,7 +39,7 @@ void bt_settings_scene_start_on_enter(void* context) { VariableItemList* var_item_list = app->var_item_list; VariableItem* item; - if(furi_hal_bt_is_ble_gatt_gap_supported()) { + if(furi_hal_bt_is_gatt_gap_supported()) { item = variable_item_list_add( var_item_list, "Bluetooth", diff --git a/applications/system/hid_app/application.fam b/applications/system/hid_app/application.fam index a1fb314b82..cc218c31a3 100644 --- a/applications/system/hid_app/application.fam +++ b/applications/system/hid_app/application.fam @@ -4,6 +4,8 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="hid_usb_app", stack_size=1 * 1024, + sources=["*.c", "!transport_ble.c"], + cdefines=["HID_TRANSPORT_USB"], fap_description="Use Flipper as a HID remote control over USB", fap_version="1.0", fap_category="USB", @@ -19,6 +21,9 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="hid_ble_app", stack_size=1 * 1024, + sources=["*.c", "!transport_usb.c"], + cdefines=["HID_TRANSPORT_BLE"], + fap_libs=["ble_profile"], fap_description="Use Flipper as a HID remote control over Bluetooth", fap_version="1.0", fap_category="Bluetooth", diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index 88a68f09d0..c6d88124cf 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -1,4 +1,6 @@ #include "hid.h" +#include +#include #include "views.h" #include #include @@ -68,13 +70,13 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con furi_assert(context); Hid* hid = context; bool connected = (status == BtStatusConnected); - if(hid->transport == HidTransportBle) { - if(connected) { - notification_internal_message(hid->notifications, &sequence_set_blue_255); - } else { - notification_internal_message(hid->notifications, &sequence_reset_blue); - } +#ifdef HID_TRANSPORT_BLE + if(connected) { + notification_internal_message(hid->notifications, &sequence_set_blue_255); + } else { + notification_internal_message(hid->notifications, &sequence_reset_blue); } +#endif hid_keynote_set_connected_status(hid->hid_keynote, connected); hid_keyboard_set_connected_status(hid->hid_keyboard, connected); hid_media_set_connected_status(hid->hid_media, connected); @@ -106,9 +108,8 @@ static uint32_t hid_exit(void* context) { return VIEW_NONE; } -Hid* hid_alloc(HidTransport transport) { +Hid* hid_alloc() { Hid* app = malloc(sizeof(Hid)); - app->transport = transport; // Gui app->gui = furi_record_open(RECORD_GUI); @@ -139,14 +140,14 @@ Hid* hid_alloc(HidTransport transport) { app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); submenu_add_item( app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); - if(app->transport == HidTransportBle) { - submenu_add_item( - app->device_type_submenu, - "TikTok Controller", - HidSubmenuIndexTikTok, - hid_submenu_callback, - app); - } +#ifdef HID_TRANSPORT_BLE + submenu_add_item( + app->device_type_submenu, + "TikTok Controller", + HidSubmenuIndexTikTok, + hid_submenu_callback, + app); +#endif submenu_add_item( app->device_type_submenu, "Mouse Clicker", @@ -159,14 +160,14 @@ Hid* hid_alloc(HidTransport transport) { HidSubmenuIndexMouseJiggler, hid_submenu_callback, app); - if(transport == HidTransportBle) { - submenu_add_item( - app->device_type_submenu, - "Remove Pairing", - HidSubmenuIndexRemovePairing, - hid_submenu_callback, - app); - } +#ifdef HID_TRANSPORT_BLE + submenu_add_item( + app->device_type_submenu, + "Remove Pairing", + HidSubmenuIndexRemovePairing, + hid_submenu_callback, + app); +#endif view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); view_dispatcher_add_view( app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); @@ -244,10 +245,9 @@ void hid_free(Hid* app) { furi_assert(app); // Reset notification - if(app->transport == HidTransportBle) { - notification_internal_message(app->notifications, &sequence_reset_blue); - } - +#ifdef HID_TRANSPORT_BLE + notification_internal_message(app->notifications, &sequence_reset_blue); +#endif // Free views view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); submenu_free(app->device_type_submenu); @@ -281,131 +281,9 @@ void hid_free(Hid* app) { free(app); } -void hid_hal_keyboard_press(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_kb_press(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_press(event); - } else { - furi_crash(); - } -} - -void hid_hal_keyboard_release(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_kb_release(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_release(event); - } else { - furi_crash(); - } -} - -void hid_hal_keyboard_release_all(Hid* instance) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_kb_release_all(); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_release_all(); - } else { - furi_crash(); - } -} - -void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_consumer_key_press(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_consumer_key_press(event); - } else { - furi_crash(); - } -} - -void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_consumer_key_release(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_consumer_key_release(event); - } else { - furi_crash(); - } -} - -void hid_hal_consumer_key_release_all(Hid* instance) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_consumer_key_release_all(); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_release_all(); - } else { - furi_crash(); - } -} - -void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_move(dx, dy); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_move(dx, dy); - } else { - furi_crash(); - } -} - -void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_scroll(delta); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_scroll(delta); - } else { - furi_crash(); - } -} - -void hid_hal_mouse_press(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_press(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_press(event); - } else { - furi_crash(); - } -} - -void hid_hal_mouse_release(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_release(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_release(event); - } else { - furi_crash(); - } -} - -void hid_hal_mouse_release_all(Hid* instance) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_release_all(); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); - furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); - } else { - furi_crash(); - } -} - int32_t hid_usb_app(void* p) { UNUSED(p); - Hid* app = hid_alloc(HidTransportUsb); + Hid* app = hid_alloc(); app = hid_app_alloc_view(app); FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); furi_hal_usb_unlock(); @@ -426,7 +304,7 @@ int32_t hid_usb_app(void* p) { int32_t hid_ble_app(void* p) { UNUSED(p); - Hid* app = hid_alloc(HidTransportBle); + Hid* app = hid_alloc(); app = hid_app_alloc_view(app); bt_disconnect(app->bt); @@ -446,7 +324,9 @@ int32_t hid_ble_app(void* p) { furi_record_close(RECORD_STORAGE); - furi_check(bt_set_profile(app->bt, BtProfileHidKeyboard)); + app->ble_hid_profile = bt_profile_start(app->bt, ble_profile_hid, NULL); + + furi_check(app->ble_hid_profile); furi_hal_bt_start_advertising(); bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); @@ -464,7 +344,7 @@ int32_t hid_ble_app(void* p) { bt_keys_storage_set_default_path(app->bt); - furi_check(bt_set_profile(app->bt, BtProfileSerial)); + furi_check(bt_profile_restore_default(app->bt)); hid_free(app); diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index 49d8b4e045..e6e974f308 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -2,10 +2,11 @@ #include #include -#include #include #include +#include + #include #include #include @@ -34,6 +35,7 @@ typedef enum { typedef struct Hid Hid; struct Hid { + FuriHalBleProfileBase* ble_hid_profile; Bt* bt; Gui* gui; NotificationApp* notifications; diff --git a/applications/system/hid_app/transport_ble.c b/applications/system/hid_app/transport_ble.c new file mode 100644 index 0000000000..92a260adda --- /dev/null +++ b/applications/system/hid_app/transport_ble.c @@ -0,0 +1,60 @@ +#include "hid.h" + +#ifndef HID_TRANSPORT_BLE +#error "HID_TRANSPORT_BLE must be defined" +#endif + +void hid_hal_keyboard_press(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_kb_press(instance->ble_hid_profile, event); +} + +void hid_hal_keyboard_release(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_kb_release(instance->ble_hid_profile, event); +} + +void hid_hal_keyboard_release_all(Hid* instance) { + furi_assert(instance); + ble_profile_hid_kb_release_all(instance->ble_hid_profile); +} + +void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_consumer_key_press(instance->ble_hid_profile, event); +} + +void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_consumer_key_release(instance->ble_hid_profile, event); +} + +void hid_hal_consumer_key_release_all(Hid* instance) { + furi_assert(instance); + ble_profile_hid_consumer_key_release_all(instance->ble_hid_profile); +} + +void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { + furi_assert(instance); + ble_profile_hid_mouse_move(instance->ble_hid_profile, dx, dy); +} + +void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { + furi_assert(instance); + ble_profile_hid_mouse_scroll(instance->ble_hid_profile, delta); +} + +void hid_hal_mouse_press(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_mouse_press(instance->ble_hid_profile, event); +} + +void hid_hal_mouse_release(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_mouse_release(instance->ble_hid_profile, event); +} + +void hid_hal_mouse_release_all(Hid* instance) { + furi_assert(instance); + ble_profile_hid_mouse_release_all(instance->ble_hid_profile); +} diff --git a/applications/system/hid_app/transport_usb.c b/applications/system/hid_app/transport_usb.c new file mode 100644 index 0000000000..882a715a5f --- /dev/null +++ b/applications/system/hid_app/transport_usb.c @@ -0,0 +1,61 @@ +#include "hid.h" + +#ifndef HID_TRANSPORT_USB +#error "HID_TRANSPORT_USB must be defined" +#endif + +void hid_hal_keyboard_press(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_kb_press(event); +} + +void hid_hal_keyboard_release(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_kb_release(event); +} + +void hid_hal_keyboard_release_all(Hid* instance) { + furi_assert(instance); + furi_hal_hid_kb_release_all(); +} + +void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_consumer_key_press(event); +} + +void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_consumer_key_release(event); +} + +void hid_hal_consumer_key_release_all(Hid* instance) { + furi_assert(instance); + furi_hal_hid_kb_release_all(); +} + +void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { + furi_assert(instance); + furi_hal_hid_mouse_move(dx, dy); +} + +void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { + furi_assert(instance); + furi_hal_hid_mouse_scroll(delta); +} + +void hid_hal_mouse_press(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_mouse_press(event); +} + +void hid_hal_mouse_release(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_mouse_release(event); +} + +void hid_hal_mouse_release_all(Hid* instance) { + furi_assert(instance); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); +} diff --git a/applications/system/hid_app/views/hid_media.c b/applications/system/hid_app/views/hid_media.c index 468529d56a..6ef11729e9 100644 --- a/applications/system/hid_app/views/hid_media.c +++ b/applications/system/hid_app/views/hid_media.c @@ -1,7 +1,7 @@ #include "hid_media.h" #include -#include #include +#include #include #include "../hid.h" diff --git a/assets/icons/StatusBar/BLE_beacon_7x8.png b/assets/icons/StatusBar/BLE_beacon_7x8.png new file mode 100644 index 0000000000000000000000000000000000000000..e8480287ce43f472d31f0d15478179eb6772b847 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)H$P6U;PnEj@DYgKg5ZC|z{{xw!hc4FvDb50q z$YKTtZXpn6ymYtj4^U9l)5S4_<9c#I;*293xH_CASQ<94F-Yq%Gxx4z3I{4+@O1Ta JS?83{1OUl%9BBXm literal 0 HcmV?d00001 diff --git a/firmware.scons b/firmware.scons index 901a762145..bf3f46a9bf 100644 --- a/firmware.scons +++ b/firmware.scons @@ -139,8 +139,12 @@ for app_dir, _ in fwenv["APPDIRS"]: fwenv.PrepareApplicationsBuild() + # Build external apps + configure SDK if env["IS_BASE_FIRMWARE"]: + # Ensure all libs are built - even if they are not used in firmware + fw_artifacts.append(fwenv["LIB_DIST_DIR"].glob("*.a")) + fwenv.SetDefault(FBT_FAP_DEBUG_ELF_ROOT=fwenv["BUILD_DIR"].Dir(".extapps")) fw_extapps = fwenv["FW_EXTAPPS"] = SConscript( "site_scons/extapps.scons", diff --git a/furi/core/check.c b/furi/core/check.c index 233b574b04..8025961699 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -86,7 +86,7 @@ static void __furi_print_stack_info() { } static void __furi_print_bt_stack_info() { - const FuriHalBtHardfaultInfo* fault_info = furi_hal_bt_get_hardfault_info(); + const BleGlueHardfaultInfo* fault_info = ble_glue_get_hardfault_info(); if(fault_info == NULL) { furi_log_puts("\r\n\tcore2: not faulted"); } else { diff --git a/furi/core/thread.c b/furi/core/thread.c index abc85bb90d..3c1a17258d 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -11,6 +11,7 @@ #include #include +#include #include #define TAG "FuriThread" @@ -223,6 +224,12 @@ void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) { thread->priority = priority; } +FuriThreadPriority furi_thread_get_priority(FuriThread* thread) { + furi_assert(thread); + TaskHandle_t hTask = furi_thread_get_id(thread); + return (FuriThreadPriority)uxTaskPriorityGet(hTask); +} + void furi_thread_set_current_priority(FuriThreadPriority priority) { UBaseType_t new_priority = priority ? priority : FuriThreadPriorityNormal; vTaskPrioritySet(NULL, new_priority); @@ -497,22 +504,23 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo return (rflags); } -uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_items) { +uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count) { uint32_t i, count; TaskStatus_t* task; - if(FURI_IS_IRQ_MODE() || (thread_array == NULL) || (array_items == 0U)) { + if(FURI_IS_IRQ_MODE() || (thread_array == NULL) || (array_item_count == 0U)) { count = 0U; } else { vTaskSuspendAll(); count = uxTaskGetNumberOfTasks(); task = pvPortMalloc(count * sizeof(TaskStatus_t)); + configRUN_TIME_COUNTER_TYPE total_run_time; if(task != NULL) { - count = uxTaskGetSystemState(task, count, NULL); + count = uxTaskGetSystemState(task, count, &total_run_time); - for(i = 0U; (i < count) && (i < array_items); i++) { + for(i = 0U; (i < count) && (i < array_item_count); i++) { thread_array[i] = (FuriThreadId)task[i].xHandle; } count = i; diff --git a/furi/core/thread.h b/furi/core/thread.h index 44d66fb21a..83c051cc22 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -138,6 +138,13 @@ void furi_thread_set_context(FuriThread* thread, void* context); */ void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority); +/** Get FuriThread priority + * + * @param thread FuriThread instance + * @return FuriThreadPriority value + */ +FuriThreadPriority furi_thread_get_priority(FuriThread* thread); + /** Set current thread priority * * @param priority FuriThreadPriority value @@ -259,7 +266,7 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo * @param array_items array size * @return uint32_t threads count */ -uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_items); +uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count); /** * @brief Get thread name diff --git a/lib/SConscript b/lib/SConscript index cf96a1b84b..8125739325 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -42,6 +42,7 @@ libs = env.BuildModules( "nanopb", "update_util", "heatshrink", + "ble_profile", "bit_lib", "datetime", ], diff --git a/lib/ble_profile/SConscript b/lib/ble_profile/SConscript new file mode 100644 index 0000000000..3b20d38f59 --- /dev/null +++ b/lib/ble_profile/SConscript @@ -0,0 +1,27 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/ble_profile", + ], + SDK_HEADERS=[ + File("extra_profiles/hid_profile.h"), + File("extra_services/hid_service.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="ble_profile") +libenv.AppendUnique( + CCFLAGS=[ + # Required for lib to be linkable with .faps + "-mword-relocations", + "-mlong-calls", + ], +) +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c new file mode 100644 index 0000000000..aaa66d9607 --- /dev/null +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -0,0 +1,427 @@ +#include "hid_profile.h" + +#include +#include +#include +#include + +#include +#include +#include + +#define HID_INFO_BASE_USB_SPECIFICATION (0x0101) +#define HID_INFO_COUNTRY_CODE (0x00) +#define BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) +#define BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) + +#define BLE_PROFILE_HID_KB_MAX_KEYS (6) +#define BLE_PROFILE_CONSUMER_MAX_KEYS (1) + +// Report ids cant be 0 +enum HidReportId { + ReportIdKeyboard = 1, + ReportIdMouse = 2, + ReportIdConsumer = 3, +}; +// Report numbers corresponded to the report id with an offset of 1 +enum HidInputNumber { + ReportNumberKeyboard = 0, + ReportNumberMouse = 1, + ReportNumberConsumer = 2, +}; + +typedef struct { + uint8_t mods; + uint8_t reserved; + uint8_t key[BLE_PROFILE_HID_KB_MAX_KEYS]; +} FURI_PACKED FuriHalBtHidKbReport; + +typedef struct { + uint8_t btn; + int8_t x; + int8_t y; + int8_t wheel; +} FURI_PACKED FuriHalBtHidMouseReport; + +typedef struct { + uint16_t key[BLE_PROFILE_CONSUMER_MAX_KEYS]; +} FURI_PACKED FuriHalBtHidConsumerReport; + +// keyboard+mouse+consumer hid report +static const uint8_t ble_profile_hid_report_map_data[] = { + // Keyboard Report + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_KEYBOARD), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdKeyboard), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), + HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(8), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(1), + HID_REPORT_SIZE(8), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_LED), + HID_REPORT_COUNT(8), + HID_REPORT_SIZE(1), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(8), + HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(BLE_PROFILE_HID_KB_MAX_KEYS), + HID_REPORT_SIZE(8), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(101), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(0), + HID_USAGE_MAXIMUM(101), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, + // Mouse Report + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_MOUSE), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_USAGE(HID_DESKTOP_POINTER), + HID_COLLECTION(HID_PHYSICAL_COLLECTION), + HID_REPORT_ID(ReportIdMouse), + HID_USAGE_PAGE(HID_PAGE_BUTTON), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(3), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_COUNT(3), + HID_REPORT_SIZE(1), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(5), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_X), + HID_USAGE(HID_DESKTOP_Y), + HID_USAGE(HID_DESKTOP_WHEEL), + HID_LOGICAL_MINIMUM(-127), + HID_LOGICAL_MAXIMUM(127), + HID_REPORT_SIZE(8), + HID_REPORT_COUNT(3), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + HID_END_COLLECTION, + HID_END_COLLECTION, + // Consumer Report + HID_USAGE_PAGE(HID_PAGE_CONSUMER), + HID_USAGE(HID_CONSUMER_CONTROL), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdConsumer), + HID_LOGICAL_MINIMUM(0), + HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), + HID_USAGE_MINIMUM(0), + HID_RI_USAGE_MAXIMUM(16, 0x3FF), + HID_REPORT_COUNT(BLE_PROFILE_CONSUMER_MAX_KEYS), + HID_REPORT_SIZE(16), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, +}; + +typedef struct { + FuriHalBleProfileBase base; + + FuriHalBtHidKbReport* kb_report; + FuriHalBtHidMouseReport* mouse_report; + FuriHalBtHidConsumerReport* consumer_report; + + BleServiceBattery* battery_svc; + BleServiceDevInfo* dev_info_svc; + BleServiceHid* hid_svc; +} BleProfileHid; +_Static_assert(offsetof(BleProfileHid, base) == 0, "Wrong layout"); + +static FuriHalBleProfileBase* ble_profile_hid_start(FuriHalBleProfileParams profile_params) { + UNUSED(profile_params); + + BleProfileHid* profile = malloc(sizeof(BleProfileHid)); + + profile->base.config = ble_profile_hid; + + profile->battery_svc = ble_svc_battery_start(true); + profile->dev_info_svc = ble_svc_dev_info_start(); + profile->hid_svc = ble_svc_hid_start(); + + // Configure HID Keyboard + profile->kb_report = malloc(sizeof(FuriHalBtHidKbReport)); + profile->mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); + profile->consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); + + // Configure Report Map characteristic + ble_svc_hid_update_report_map( + profile->hid_svc, + ble_profile_hid_report_map_data, + sizeof(ble_profile_hid_report_map_data)); + // Configure HID Information characteristic + uint8_t hid_info_val[4] = { + HID_INFO_BASE_USB_SPECIFICATION & 0x00ff, + (HID_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8, + HID_INFO_COUNTRY_CODE, + BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK | + BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK, + }; + ble_svc_hid_update_info(profile->hid_svc, hid_info_val); + + return &profile->base; +} + +static void ble_profile_hid_stop(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + ble_svc_battery_stop(hid_profile->battery_svc); + ble_svc_dev_info_stop(hid_profile->dev_info_svc); + ble_svc_hid_stop(hid_profile->hid_svc); + + free(hid_profile->kb_report); + free(hid_profile->mouse_report); + free(hid_profile->consumer_report); +} + +bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; + for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { + if(kb_report->key[i] == 0) { + kb_report->key[i] = button & 0xFF; + break; + } + } + kb_report->mods |= (button >> 8); + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberKeyboard, + (uint8_t*)kb_report, + sizeof(FuriHalBtHidKbReport)); +} + +bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + + FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; + for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { + if(kb_report->key[i] == (button & 0xFF)) { + kb_report->key[i] = 0; + break; + } + } + kb_report->mods &= ~(button >> 8); + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberKeyboard, + (uint8_t*)kb_report, + sizeof(FuriHalBtHidKbReport)); +} + +bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; + for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { + kb_report->key[i] = 0; + } + kb_report->mods = 0; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberKeyboard, + (uint8_t*)kb_report, + sizeof(FuriHalBtHidKbReport)); +} + +bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; + for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 + if(consumer_report->key[i] == 0) { + consumer_report->key[i] = button; + break; + } + } + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberConsumer, + (uint8_t*)consumer_report, + sizeof(FuriHalBtHidConsumerReport)); +} + +bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; + for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 + if(consumer_report->key[i] == button) { + consumer_report->key[i] = 0; + break; + } + } + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberConsumer, + (uint8_t*)consumer_report, + sizeof(FuriHalBtHidConsumerReport)); +} + +bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; + for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 + consumer_report->key[i] = 0; + } + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberConsumer, + (uint8_t*)consumer_report, + sizeof(FuriHalBtHidConsumerReport)); +} + +bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->x = dx; + mouse_report->y = dy; + bool state = ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); + mouse_report->x = 0; + mouse_report->y = 0; + return state; +} + +bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->btn |= button; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); +} + +bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->btn &= ~button; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); +} + +bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->btn = 0; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); +} + +bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->wheel = delta; + bool state = ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); + mouse_report->wheel = 0; + return state; +} + +static GapConfig template_config = { + .adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, + .appearance_char = GAP_APPEARANCE_KEYBOARD, + .bonding_mode = true, + .pairing_method = GapPairingPinCodeVerifyYesNo, + .conn_param = + { + .conn_int_min = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms + .slave_latency = 0, + .supervisor_timeout = 0, + }, +}; + +static void ble_profile_hid_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) { + BleProfileHidParams* hid_profile_params = profile_params; + + furi_check(config); + memcpy(config, &template_config, sizeof(GapConfig)); + // Set mac address + memcpy(config->mac_address, furi_hal_version_get_ble_mac(), sizeof(config->mac_address)); + + // Change MAC address for HID profile + config->mac_address[2]++; + if(hid_profile_params) { + config->mac_address[0] ^= hid_profile_params->mac_xor; + config->mac_address[1] ^= hid_profile_params->mac_xor >> 8; + } + + // Set advertise name + memset(config->adv_name, 0, sizeof(config->adv_name)); + FuriString* name = furi_string_alloc_set(furi_hal_version_get_ble_local_device_name_ptr()); + + const char* clicker_str = "Control"; + if(hid_profile_params && hid_profile_params->device_name_prefix) { + clicker_str = hid_profile_params->device_name_prefix; + } + furi_string_replace_str(name, "Flipper", clicker_str); + if(furi_string_size(name) >= sizeof(config->adv_name)) { + furi_string_left(name, sizeof(config->adv_name) - 1); + } + memcpy(config->adv_name, furi_string_get_cstr(name), furi_string_size(name)); + furi_string_free(name); +} + +static const FuriHalBleProfileTemplate profile_callbacks = { + .start = ble_profile_hid_start, + .stop = ble_profile_hid_stop, + .get_gap_config = ble_profile_hid_get_config, +}; + +const FuriHalBleProfileTemplate* ble_profile_hid = &profile_callbacks; diff --git a/lib/ble_profile/extra_profiles/hid_profile.h b/lib/ble_profile/extra_profiles/hid_profile.h new file mode 100644 index 0000000000..eb4884e452 --- /dev/null +++ b/lib/ble_profile/extra_profiles/hid_profile.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Optional arguments to pass along with profile template as + * FuriHalBleProfileParams for tuning profile behavior + **/ +typedef struct { + const char* device_name_prefix; /**< Prefix for device name. Length must be less than 8 */ + uint16_t mac_xor; /**< XOR mask for device address, for uniqueness */ +} BleProfileHidParams; + +/** Hid Keyboard Profile descriptor */ +extern const FuriHalBleProfileTemplate* ble_profile_hid; + +/** Press keyboard button + * + * @param profile profile instance + * @param button button code from HID specification + * + * @return true on success + */ +bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button); + +/** Release keyboard button + * + * @param profile profile instance + * @param button button code from HID specification + * + * @return true on success + */ +bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button); + +/** Release all keyboard buttons + * + * @param profile profile instance + * @return true on success + */ +bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile); + +/** Set the following consumer key to pressed state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button); + +/** Set the following consumer key to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button); + +/** Set consumer key to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile); + +/** Set mouse movement and send HID report + * + * @param profile profile instance + * @param dx x coordinate delta + * @param dy y coordinate delta + */ +bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy); + +/** Set mouse button to pressed state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button); + +/** Set mouse button to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button); + +/** Set mouse button to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile); + +/** Set mouse wheel position and send HID report + * + * @param profile profile instance + * @param delta number of scroll steps + */ +bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/services/hid_service.c b/lib/ble_profile/extra_services/hid_service.c similarity index 53% rename from targets/f7/ble_glue/services/hid_service.c rename to lib/ble_profile/extra_services/hid_service.c index cf2aca24e3..d9ea09c140 100644 --- a/targets/f7/ble_glue/services/hid_service.c +++ b/lib/ble_profile/extra_services/hid_service.c @@ -1,11 +1,26 @@ #include "hid_service.h" #include "app_common.h" #include -#include "gatt_char.h" +#include +#include #include +#include -#define TAG "BtHid" +#define TAG "BleHid" + +#define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255) +#define BLE_SVC_HID_REPORT_MAX_LEN (255) +#define BLE_SVC_HID_REPORT_REF_LEN (2) +#define BLE_SVC_HID_INFO_LEN (4) +#define BLE_SVC_HID_CONTROL_POINT_LEN (1) + +#define BLE_SVC_HID_INPUT_REPORT_COUNT (3) +#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0) +#define BLE_SVC_HID_FEATURE_REPORT_COUNT (0) +#define BLE_SVC_HID_REPORT_COUNT \ + (BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \ + BLE_SVC_HID_FEATURE_REPORT_COUNT) typedef enum { HidSvcGattCharacteristicProtocolMode = 0, @@ -22,12 +37,14 @@ typedef struct { static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes"); -static const Service_UUID_t hid_svc_uuid = { +static const Service_UUID_t ble_svc_hid_uuid = { .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, }; -static bool - hid_svc_char_desc_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) { +static bool ble_svc_hid_char_desc_data_callback( + const void* context, + const uint8_t** data, + uint16_t* data_len) { const HidSvcReportId* report_id = context; *data_len = sizeof(HidSvcReportId); if(data) { @@ -41,19 +58,21 @@ typedef struct { uint16_t data_len; } HidSvcDataWrapper; -static bool - hid_svc_report_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) { +static bool ble_svc_hid_report_data_callback( + const void* context, + const uint8_t** data, + uint16_t* data_len) { const HidSvcDataWrapper* report_data = context; if(data) { *data = report_data->data_ptr; *data_len = report_data->data_len; } else { - *data_len = HID_SVC_REPORT_MAP_MAX_LEN; + *data_len = BLE_SVC_HID_REPORT_MAP_MAX_LEN; } return false; } -static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteristicCount] = { +static const BleGattCharacteristicParams ble_svc_hid_chars[HidSvcGattCharacteristicCount] = { [HidSvcGattCharacteristicProtocolMode] = {.name = "Protocol Mode", .data_prop_type = FlipperGattCharacteristicDataFixed, @@ -67,7 +86,7 @@ static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteris [HidSvcGattCharacteristicReportMap] = {.name = "Report Map", .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.fn = hid_svc_report_data_callback, + .data.callback.fn = ble_svc_hid_report_data_callback, .data.callback.context = NULL, .uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID, .uuid_type = UUID_TYPE_16, @@ -78,7 +97,7 @@ static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteris [HidSvcGattCharacteristicInfo] = {.name = "HID Information", .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = HID_SVC_INFO_LEN, + .data.fixed.length = BLE_SVC_HID_INFO_LEN, .data.fixed.ptr = NULL, .uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID, .uuid_type = UUID_TYPE_16, @@ -89,7 +108,7 @@ static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteris [HidSvcGattCharacteristicCtrlPoint] = {.name = "HID Control Point", .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = HID_SVC_CONTROL_POINT_LEN, + .data.fixed.length = BLE_SVC_HID_CONTROL_POINT_LEN, .uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_WRITE_WITHOUT_RESP, @@ -98,21 +117,21 @@ static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteris .is_variable = CHAR_VALUE_LEN_CONSTANT}, }; -static const FlipperGattCharacteristicDescriptorParams hid_svc_char_descr_template = { +static const BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr_template = { .uuid_type = UUID_TYPE_16, .uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID, - .max_length = HID_SVC_REPORT_REF_LEN, - .data_callback.fn = hid_svc_char_desc_data_callback, + .max_length = BLE_SVC_HID_REPORT_REF_LEN, + .data_callback.fn = ble_svc_hid_char_desc_data_callback, .security_permissions = ATTR_PERMISSION_NONE, .access_permissions = ATTR_ACCESS_READ_WRITE, .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, .is_variable = CHAR_VALUE_LEN_CONSTANT, }; -static const FlipperGattCharacteristicParams hid_svc_report_template = { +static const BleGattCharacteristicParams ble_svc_hid_report_template = { .name = "Report", .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.fn = hid_svc_report_data_callback, + .data.callback.fn = ble_svc_hid_report_data_callback, .data.callback.context = NULL, .uuid.Char_UUID_16 = REPORT_CHAR_UUID, .uuid_type = UUID_TYPE_16, @@ -122,88 +141,90 @@ static const FlipperGattCharacteristicParams hid_svc_report_template = { .is_variable = CHAR_VALUE_LEN_VARIABLE, }; -typedef struct { +struct BleServiceHid { uint16_t svc_handle; - FlipperGattCharacteristicInstance chars[HidSvcGattCharacteristicCount]; - FlipperGattCharacteristicInstance input_report_chars[HID_SVC_INPUT_REPORT_COUNT]; - FlipperGattCharacteristicInstance output_report_chars[HID_SVC_OUTPUT_REPORT_COUNT]; - FlipperGattCharacteristicInstance feature_report_chars[HID_SVC_FEATURE_REPORT_COUNT]; -} HIDSvc; + BleGattCharacteristicInstance chars[HidSvcGattCharacteristicCount]; + BleGattCharacteristicInstance input_report_chars[BLE_SVC_HID_INPUT_REPORT_COUNT]; + BleGattCharacteristicInstance output_report_chars[BLE_SVC_HID_OUTPUT_REPORT_COUNT]; + BleGattCharacteristicInstance feature_report_chars[BLE_SVC_HID_FEATURE_REPORT_COUNT]; + GapSvcEventHandler* event_handler; +}; -static HIDSvc* hid_svc = NULL; +static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) { + UNUSED(context); -static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void* event) { - SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; + BleEventAckStatus ret = BleEventNotAck; 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; + ret = BleEventAckFlowEnable; } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { // Process notification confirmation - ret = SVCCTL_EvtAckFlowEnable; + ret = BleEventAckFlowEnable; } } return ret; } -void hid_svc_start() { - tBleStatus status; - hid_svc = malloc(sizeof(HIDSvc)); +BleServiceHid* ble_svc_hid_start() { + BleServiceHid* hid_svc = malloc(sizeof(BleServiceHid)); // Register event handler - SVCCTL_RegisterSvcHandler(hid_svc_event_handler); + hid_svc->event_handler = + ble_event_dispatcher_register_svc_handler(ble_svc_hid_event_handler, hid_svc); /** * Add Human Interface Device Service */ - status = aci_gatt_add_service( - UUID_TYPE_16, - &hid_svc_uuid, - PRIMARY_SERVICE, - 2 + /* protocol mode */ - (4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) + - (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + - 2, /* Service + Report Map + HID Information + HID Control Point */ - &hid_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add HID service: %d", status); + if(!ble_gatt_service_add( + UUID_TYPE_16, + &ble_svc_hid_uuid, + PRIMARY_SERVICE, + 2 + /* protocol mode */ + (4 * BLE_SVC_HID_INPUT_REPORT_COUNT) + (3 * BLE_SVC_HID_OUTPUT_REPORT_COUNT) + + (3 * BLE_SVC_HID_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + + 2, /* Service + Report Map + HID Information + HID Control Point */ + &hid_svc->svc_handle)) { + free(hid_svc); + return NULL; } // Maintain previously defined characteristic order - flipper_gatt_characteristic_init( + ble_gatt_characteristic_init( hid_svc->svc_handle, - &hid_svc_chars[HidSvcGattCharacteristicProtocolMode], + &ble_svc_hid_chars[HidSvcGattCharacteristicProtocolMode], &hid_svc->chars[HidSvcGattCharacteristicProtocolMode]); uint8_t protocol_mode = 1; - flipper_gatt_characteristic_update( + ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicProtocolMode], &protocol_mode); // reports - FlipperGattCharacteristicDescriptorParams hid_svc_char_descr; - FlipperGattCharacteristicParams report_char; + BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr; + BleGattCharacteristicParams report_char; HidSvcReportId report_id; - memcpy(&hid_svc_char_descr, &hid_svc_char_descr_template, sizeof(hid_svc_char_descr)); - memcpy(&report_char, &hid_svc_report_template, sizeof(report_char)); + memcpy( + &ble_svc_hid_char_descr, &ble_svc_hid_char_descr_template, sizeof(ble_svc_hid_char_descr)); + memcpy(&report_char, &ble_svc_hid_report_template, sizeof(report_char)); - hid_svc_char_descr.data_callback.context = &report_id; - report_char.descriptor_params = &hid_svc_char_descr; + ble_svc_hid_char_descr.data_callback.context = &report_id; + report_char.descriptor_params = &ble_svc_hid_char_descr; typedef struct { uint8_t report_type; uint8_t report_count; - FlipperGattCharacteristicInstance* chars; + BleGattCharacteristicInstance* chars; } HidSvcReportCharProps; HidSvcReportCharProps hid_report_chars[] = { - {0x01, HID_SVC_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, - {0x02, HID_SVC_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, - {0x03, HID_SVC_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, + {0x01, BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, + {0x02, BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, + {0x03, BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, }; for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); @@ -212,7 +233,7 @@ void hid_svc_start() { for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; report_idx++) { report_id.report_idx = report_idx + 1; - flipper_gatt_characteristic_init( + ble_gatt_characteristic_init( hid_svc->svc_handle, &report_char, &hid_report_chars[report_type_idx].chars[report_idx]); @@ -221,12 +242,14 @@ void hid_svc_start() { // Setup remaining characteristics for(size_t i = HidSvcGattCharacteristicReportMap; i < HidSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_init( - hid_svc->svc_handle, &hid_svc_chars[i], &hid_svc->chars[i]); + ble_gatt_characteristic_init( + hid_svc->svc_handle, &ble_svc_hid_chars[i], &hid_svc->chars[i]); } + + return hid_svc; } -bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { +bool ble_svc_hid_update_report_map(BleServiceHid* hid_svc, const uint8_t* data, uint16_t len) { furi_assert(data); furi_assert(hid_svc); @@ -234,69 +257,64 @@ bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { .data_ptr = data, .data_len = len, }; - return flipper_gatt_characteristic_update( + return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data); } -bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len) { +bool ble_svc_hid_update_input_report( + BleServiceHid* hid_svc, + uint8_t input_report_num, + uint8_t* data, + uint16_t len) { furi_assert(data); furi_assert(hid_svc); - furi_assert(input_report_num < HID_SVC_INPUT_REPORT_COUNT); + furi_assert(input_report_num < BLE_SVC_HID_INPUT_REPORT_COUNT); HidSvcDataWrapper report_data = { .data_ptr = data, .data_len = len, }; - return flipper_gatt_characteristic_update( + return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data); } -bool hid_svc_update_info(uint8_t* data) { +bool ble_svc_hid_update_info(BleServiceHid* hid_svc, uint8_t* data) { furi_assert(data); furi_assert(hid_svc); - return flipper_gatt_characteristic_update( + return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data); } -bool hid_svc_is_started() { - return hid_svc != NULL; -} +void ble_svc_hid_stop(BleServiceHid* hid_svc) { + furi_assert(hid_svc); + ble_event_dispatcher_unregister_svc_handler(hid_svc->event_handler); + // Delete characteristics + for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]); + } -void hid_svc_stop() { - tBleStatus status; - if(hid_svc) { - // Delete characteristics - for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]); - } + typedef struct { + uint8_t report_count; + BleGattCharacteristicInstance* chars; + } HidSvcReportCharProps; - typedef struct { - uint8_t report_count; - FlipperGattCharacteristicInstance* chars; - } HidSvcReportCharProps; - - HidSvcReportCharProps hid_report_chars[] = { - {HID_SVC_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, - {HID_SVC_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, - {HID_SVC_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, - }; - - for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); - report_type_idx++) { - for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; - report_idx++) { - flipper_gatt_characteristic_delete( - hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]); - } - } + HidSvcReportCharProps hid_report_chars[] = { + {BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, + {BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, + {BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, + }; - // Delete service - status = aci_gatt_del_service(hid_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete HID service: %d", status); + for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); + report_type_idx++) { + for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; + report_idx++) { + ble_gatt_characteristic_delete( + hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]); } - free(hid_svc); - hid_svc = NULL; } + + // Delete service + ble_gatt_service_delete(hid_svc->svc_handle); + free(hid_svc); } diff --git a/lib/ble_profile/extra_services/hid_service.h b/lib/ble_profile/extra_services/hid_service.h new file mode 100644 index 0000000000..8e9cc29750 --- /dev/null +++ b/lib/ble_profile/extra_services/hid_service.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BleServiceHid BleServiceHid; + +BleServiceHid* ble_svc_hid_start(); + +void ble_svc_hid_stop(BleServiceHid* service); + +bool ble_svc_hid_update_report_map(BleServiceHid* service, const uint8_t* data, uint16_t len); + +bool ble_svc_hid_update_input_report( + BleServiceHid* service, + uint8_t input_report_num, + uint8_t* data, + uint16_t len); + +// Expects data to be of length BLE_SVC_HID_INFO_LEN (4 bytes) +bool ble_svc_hid_update_info(BleServiceHid* service, uint8_t* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/st25tb/st25tb.c b/lib/nfc/protocols/st25tb/st25tb.c index 785cf831d9..2b3eebf9e1 100644 --- a/lib/nfc/protocols/st25tb/st25tb.c +++ b/lib/nfc/protocols/st25tb/st25tb.c @@ -1,6 +1,5 @@ #include "st25tb.h" -#include "core/string.h" #include "flipper_format.h" #include diff --git a/lib/stm32wb.scons b/lib/stm32wb.scons index 8a8ad96449..9f25744e97 100644 --- a/lib/stm32wb.scons +++ b/lib/stm32wb.scons @@ -56,7 +56,6 @@ sources += Glob( ) sources += [ "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/tl_mbox.c", - "stm32wb_copro/wpan/ble/svc/Src/svc_ctl.c", "stm32wb_copro/wpan/ble/core/auto/ble_gap_aci.c", "stm32wb_copro/wpan/ble/core/auto/ble_gatt_aci.c", "stm32wb_copro/wpan/ble/core/auto/ble_hal_aci.c", diff --git a/scripts/fbt_tools/fbt_hwtarget.py b/scripts/fbt_tools/fbt_hwtarget.py index 67975ed0f8..a3b0d4a788 100644 --- a/scripts/fbt_tools/fbt_hwtarget.py +++ b/scripts/fbt_tools/fbt_hwtarget.py @@ -30,8 +30,11 @@ def _loadDescription(self, target_id): if not target_json_file.exists(): raise Exception(f"Target file {target_json_file} does not exist") with open(target_json_file.get_abspath(), "r") as f: - vals = json.load(f) - return vals + try: + vals = json.load(f) + return vals + except json.JSONDecodeError as e: + raise Exception(f"Failed to parse target file {target_json_file}: {e}") def _processTargetDefinitions(self, target_id): target_dir = self._getTargetDir(target_id) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 2cf43dce02..7ce0e88421 100755 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -63,7 +63,13 @@ def get_project_file_name(self, project: ProjectDir, filetype: str) -> str: return dist_target_path def note_dist_component(self, component: str, extension: str, srcpath: str) -> None: - self._dist_components[f"{component}.{extension}"] = srcpath + component_key = f"{component}.{extension}" + if component_key in self._dist_components: + self.logger.debug( + f"Skipping duplicate component {component_key} in {srcpath}" + ) + return + self._dist_components[component_key] = srcpath def get_dist_file_name(self, dist_artifact_type: str, filetype: str) -> str: return f"{self.DIST_FILE_PREFIX}{self.target}-{dist_artifact_type}-{self.args.suffix}.{filetype}" @@ -162,8 +168,9 @@ def bundle_sdk(self): "scripts.dir", ) + sdk_bundle_path = self.get_dist_path(self.get_dist_file_name("sdk", "zip")) with zipfile.ZipFile( - self.get_dist_path(self.get_dist_file_name("sdk", "zip")), + sdk_bundle_path, "w", zipfile.ZIP_DEFLATED, ) as zf: @@ -205,6 +212,10 @@ def bundle_sdk(self): ), ) + self.logger.info( + fg.boldgreen(f"SDK bundle can be found at:\n\t{sdk_bundle_path}") + ) + def bundle_update_package(self): self.logger.debug( f"Generating update bundle with version {self.args.version} for {self.target}" diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 6d994653bd..bdfa8c7a4e 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,57.0,, +Version,+,58.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -38,6 +38,8 @@ Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, Header,+,lib/bit_lib/bit_lib.h,, +Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, +Header,+,lib/ble_profile/extra_services/hid_service.h,, Header,+,lib/datetime/datetime.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, @@ -169,6 +171,13 @@ Header,+,lib/toolbox/version.h,, Header,+,targets/f18/furi_hal/furi_hal_resources.h,, Header,+,targets/f18/furi_hal/furi_hal_spi_config.h,, Header,+,targets/f18/furi_hal/furi_hal_target_hw.h,, +Header,+,targets/f7/ble_glue/furi_ble/event_dispatcher.h,, +Header,+,targets/f7/ble_glue/furi_ble/gatt.h,, +Header,+,targets/f7/ble_glue/furi_ble/profile_interface.h,, +Header,+,targets/f7/ble_glue/profiles/serial_profile.h,, +Header,+,targets/f7/ble_glue/services/battery_service.h,, +Header,+,targets/f7/ble_glue/services/dev_info_service.h,, +Header,+,targets/f7/ble_glue/services/serial_service.h,, Header,+,targets/f7/furi_hal/furi_hal_bus.h,, Header,+,targets/f7/furi_hal/furi_hal_clock.h,, Header,+,targets/f7/furi_hal/furi_hal_dma.h,, @@ -191,8 +200,6 @@ Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,, -Header,+,targets/furi_hal_include/furi_hal_bt_hid.h,, -Header,+,targets/furi_hal_include/furi_hal_bt_serial.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,, Header,+,targets/furi_hal_include/furi_hal_debug.h,, @@ -310,6 +317,9 @@ Function,-,LL_USART_DeInit,ErrorStatus,const USART_TypeDef* Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, const LL_USART_InitTypeDef*" Function,-,LL_USART_StructInit,void,LL_USART_InitTypeDef* Function,-,LL_mDelay,void,uint32_t +Function,-,Osal_MemCmp,int,"const void*, const void*, unsigned int" +Function,-,Osal_MemCpy,void*,"void*, const void*, unsigned int" +Function,-,Osal_MemSet,void*,"void*, int, unsigned int" Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int @@ -602,9 +612,20 @@ Function,+,bit_lib_set_bit,void,"uint8_t*, size_t, _Bool" Function,+,bit_lib_set_bits,void,"uint8_t*, size_t, uint8_t, uint8_t" Function,+,bit_lib_test_parity,_Bool,"const uint8_t*, size_t, uint8_t, BitLibParity, uint8_t" Function,+,bit_lib_test_parity_32,_Bool,"uint32_t, BitLibParity" -Function,+,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" -Function,+,ble_app_init,_Bool, -Function,+,ble_app_thread_stop,void, +Function,-,ble_app_deinit,void, +Function,-,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,-,ble_app_init,_Bool, +Function,-,ble_event_app_notification,BleEventFlowStatus,void* +Function,-,ble_event_dispatcher_init,void, +Function,-,ble_event_dispatcher_process_event,BleEventFlowStatus,void* +Function,+,ble_event_dispatcher_register_svc_handler,GapSvcEventHandler*,"BleSvcEventHandlerCb, void*" +Function,-,ble_event_dispatcher_reset,void, +Function,+,ble_event_dispatcher_unregister_svc_handler,void,GapSvcEventHandler* +Function,+,ble_gatt_characteristic_delete,void,"uint16_t, BleGattCharacteristicInstance*" +Function,+,ble_gatt_characteristic_init,void,"uint16_t, const BleGattCharacteristicParams*, BleGattCharacteristicInstance*" +Function,+,ble_gatt_characteristic_update,_Bool,"uint16_t, BleGattCharacteristicInstance*, const void*" +Function,+,ble_gatt_service_add,_Bool,"uint8_t, const Service_UUID_t*, uint8_t, uint8_t, uint16_t*" +Function,+,ble_gatt_service_delete,_Bool,uint16_t Function,+,ble_glue_force_c2_mode,BleGlueCommandResult,BleGlueC2Mode Function,-,ble_glue_fus_get_status,BleGlueCommandResult, Function,-,ble_glue_fus_stack_delete,BleGlueCommandResult, @@ -612,20 +633,55 @@ Function,-,ble_glue_fus_stack_install,BleGlueCommandResult,"uint32_t, uint32_t" Function,-,ble_glue_fus_wait_operation,BleGlueCommandResult, Function,+,ble_glue_get_c2_info,const BleGlueC2Info*, Function,-,ble_glue_get_c2_status,BleGlueStatus, +Function,-,ble_glue_get_hardfault_info,const BleGlueHardfaultInfo*, Function,+,ble_glue_init,void, Function,+,ble_glue_is_alive,_Bool, Function,+,ble_glue_is_radio_stack_ready,_Bool, Function,+,ble_glue_reinit_c2,_Bool, Function,+,ble_glue_set_key_storage_changed_callback,void,"BleGlueKeyStorageChangedCallback, void*" Function,+,ble_glue_start,_Bool, -Function,+,ble_glue_thread_stop,void, +Function,+,ble_glue_stop,void, Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t +Function,-,ble_profile_hid_consumer_key_press,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_consumer_key_release,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_consumer_key_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_kb_press,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_kb_release,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_kb_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_mouse_move,_Bool,"FuriHalBleProfileBase*, int8_t, int8_t" +Function,-,ble_profile_hid_mouse_press,_Bool,"FuriHalBleProfileBase*, uint8_t" +Function,-,ble_profile_hid_mouse_release,_Bool,"FuriHalBleProfileBase*, uint8_t" +Function,-,ble_profile_hid_mouse_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_mouse_scroll,_Bool,"FuriHalBleProfileBase*, int8_t" +Function,+,ble_profile_serial_notify_buffer_is_empty,void,FuriHalBleProfileBase* +Function,+,ble_profile_serial_set_event_callback,void,"FuriHalBleProfileBase*, uint16_t, FuriHalBtSerialCallback, void*" +Function,+,ble_profile_serial_set_rpc_active,void,"FuriHalBleProfileBase*, _Bool" +Function,+,ble_profile_serial_tx,_Bool,"FuriHalBleProfileBase*, uint8_t*, uint16_t" +Function,+,ble_svc_battery_start,BleServiceBattery*,_Bool +Function,+,ble_svc_battery_state_update,void,"uint8_t*, _Bool*" +Function,+,ble_svc_battery_stop,void,BleServiceBattery* +Function,+,ble_svc_battery_update_level,_Bool,"BleServiceBattery*, uint8_t" +Function,+,ble_svc_battery_update_power_state,_Bool,"BleServiceBattery*, _Bool" +Function,+,ble_svc_dev_info_start,BleServiceDevInfo*, +Function,+,ble_svc_dev_info_stop,void,BleServiceDevInfo* +Function,-,ble_svc_hid_start,BleServiceHid*, +Function,-,ble_svc_hid_stop,void,BleServiceHid* +Function,-,ble_svc_hid_update_info,_Bool,"BleServiceHid*, uint8_t*" +Function,-,ble_svc_hid_update_input_report,_Bool,"BleServiceHid*, uint8_t, uint8_t*, uint16_t" +Function,-,ble_svc_hid_update_report_map,_Bool,"BleServiceHid*, const uint8_t*, uint16_t" +Function,+,ble_svc_serial_notify_buffer_is_empty,void,BleServiceSerial* +Function,+,ble_svc_serial_set_callbacks,void,"BleServiceSerial*, uint16_t, SerialServiceEventCallback, void*" +Function,+,ble_svc_serial_set_rpc_active,void,"BleServiceSerial*, _Bool" +Function,+,ble_svc_serial_start,BleServiceSerial*, +Function,+,ble_svc_serial_stop,void,BleServiceSerial* +Function,+,ble_svc_serial_update_tx,_Bool,"BleServiceSerial*, uint8_t*, uint16_t" Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" -Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" +Function,+,bt_profile_restore_default,_Bool,Bt* +Function,+,bt_profile_start,FuriHalBleProfileBase*,"Bt*, const FuriHalBleProfileTemplate*, FuriHalBleProfileParams" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -1030,46 +1086,34 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_get_tick,uint32_t, -Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" +Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode -Function,-,furi_hal_bt_get_hardfault_info,const FuriHalBtHardfaultInfo*, +Function,+,furi_hal_bt_extra_beacon_get_config,const GapExtraBeaconConfig*, +Function,+,furi_hal_bt_extra_beacon_get_data,uint8_t,uint8_t* +Function,+,furi_hal_bt_extra_beacon_is_active,_Bool, +Function,+,furi_hal_bt_extra_beacon_set_config,_Bool,const GapExtraBeaconConfig* +Function,+,furi_hal_bt_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" +Function,+,furi_hal_bt_extra_beacon_start,_Bool, +Function,+,furi_hal_bt_extra_beacon_stop,_Bool, Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, Function,+,furi_hal_bt_get_rssi,float, Function,+,furi_hal_bt_get_transmitted_packets,uint32_t, -Function,+,furi_hal_bt_hid_consumer_key_press,_Bool,uint16_t -Function,+,furi_hal_bt_hid_consumer_key_release,_Bool,uint16_t -Function,+,furi_hal_bt_hid_consumer_key_release_all,_Bool, -Function,+,furi_hal_bt_hid_kb_press,_Bool,uint16_t -Function,+,furi_hal_bt_hid_kb_release,_Bool,uint16_t -Function,+,furi_hal_bt_hid_kb_release_all,_Bool, -Function,+,furi_hal_bt_hid_mouse_move,_Bool,"int8_t, int8_t" -Function,+,furi_hal_bt_hid_mouse_press,_Bool,uint8_t -Function,+,furi_hal_bt_hid_mouse_release,_Bool,uint8_t -Function,+,furi_hal_bt_hid_mouse_release_all,_Bool, -Function,+,furi_hal_bt_hid_mouse_scroll,_Bool,int8_t -Function,+,furi_hal_bt_hid_start,void, -Function,+,furi_hal_bt_hid_stop,void, Function,-,furi_hal_bt_init,void, Function,+,furi_hal_bt_is_active,_Bool, Function,+,furi_hal_bt_is_alive,_Bool, -Function,+,furi_hal_bt_is_ble_gatt_gap_supported,_Bool, +Function,+,furi_hal_bt_is_gatt_gap_supported,_Bool, Function,+,furi_hal_bt_is_testing_supported,_Bool, Function,+,furi_hal_bt_lock_core2,void, Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, -Function,+,furi_hal_bt_serial_notify_buffer_is_empty,void, -Function,+,furi_hal_bt_serial_set_event_callback,void,"uint16_t, FuriHalBtSerialCallback, void*" -Function,+,furi_hal_bt_serial_set_rpc_status,void,FuriHalBtSerialRpcStatus -Function,+,furi_hal_bt_serial_start,void, -Function,+,furi_hal_bt_serial_stop,void, -Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" Function,+,furi_hal_bt_start_advertising,void, -Function,+,furi_hal_bt_start_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t" Function,+,furi_hal_bt_start_radio_stack,_Bool, @@ -1081,7 +1125,7 @@ Function,+,furi_hal_bt_stop_rx,void, Function,+,furi_hal_bt_stop_tone_tx,void, Function,+,furi_hal_bt_unlock_core2,void, Function,+,furi_hal_bt_update_battery_level,void,uint8_t -Function,+,furi_hal_bt_update_power_state,void, +Function,+,furi_hal_bt_update_power_state,void,_Bool Function,+,furi_hal_bus_deinit_early,void, Function,+,furi_hal_bus_disable,void,FuriHalBus Function,+,furi_hal_bus_enable,void,FuriHalBus @@ -1530,6 +1574,7 @@ Function,+,furi_thread_get_current_priority,FuriThreadPriority, Function,+,furi_thread_get_heap_size,size_t,FuriThread* Function,+,furi_thread_get_id,FuriThreadId,FuriThread* Function,+,furi_thread_get_name,const char*,FuriThreadId +Function,+,furi_thread_get_priority,FuriThreadPriority,FuriThread* Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* @@ -1568,6 +1613,15 @@ Function,-,gamma,double,double Function,-,gamma_r,double,"double, int*" Function,-,gammaf,float,float Function,-,gammaf_r,float,"float, int*" +Function,-,gap_emit_ble_beacon_status_event,void,_Bool +Function,-,gap_extra_beacon_get_config,const GapExtraBeaconConfig*, +Function,-,gap_extra_beacon_get_data,uint8_t,uint8_t* +Function,-,gap_extra_beacon_get_state,GapExtraBeaconState, +Function,-,gap_extra_beacon_init,void, +Function,-,gap_extra_beacon_set_config,_Bool,const GapExtraBeaconConfig* +Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" +Function,-,gap_extra_beacon_start,_Bool, +Function,-,gap_extra_beacon_stop,_Bool, Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, @@ -1591,6 +1645,7 @@ Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" +Function,-,hci_send_req,int,"hci_request*, uint8_t" Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" @@ -2274,13 +2329,6 @@ Function,+,scene_manager_stop,void,SceneManager* Function,+,sd_api_get_fs_type_text,const char*,SDFsType Function,-,secure_getenv,char*,const char* Function,-,seed48,unsigned short*,unsigned short[3] -Function,-,serial_svc_is_started,_Bool, -Function,-,serial_svc_notify_buffer_is_empty,void, -Function,-,serial_svc_set_callbacks,void,"uint16_t, SerialServiceEventCallback, void*" -Function,-,serial_svc_set_rpc_status,void,SerialServiceRpcStatus -Function,-,serial_svc_start,void, -Function,-,serial_svc_stop,void, -Function,-,serial_svc_update_tx,_Bool,"uint8_t*, uint16_t" Function,-,setbuf,void,"FILE*, char*" Function,-,setbuffer,void,"FILE*, char*, int" Function,-,setenv,int,"const char*, const char*, int" @@ -2693,6 +2741,8 @@ Variable,+,_impure_data,_reent, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, +Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, +Variable,-,ble_profile_serial,const FuriHalBleProfileTemplate*, Variable,+,cli_vcp,CliSession, Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 12b8fe685f..d856dc6948 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,57.0,, +Version,+,58.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -39,6 +39,8 @@ Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, Header,+,lib/bit_lib/bit_lib.h,, +Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, +Header,+,lib/ble_profile/extra_services/hid_service.h,, Header,+,lib/datetime/datetime.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, @@ -229,6 +231,13 @@ Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/version.h,, +Header,+,targets/f7/ble_glue/furi_ble/event_dispatcher.h,, +Header,+,targets/f7/ble_glue/furi_ble/gatt.h,, +Header,+,targets/f7/ble_glue/furi_ble/profile_interface.h,, +Header,+,targets/f7/ble_glue/profiles/serial_profile.h,, +Header,+,targets/f7/ble_glue/services/battery_service.h,, +Header,+,targets/f7/ble_glue/services/dev_info_service.h,, +Header,+,targets/f7/ble_glue/services/serial_service.h,, Header,+,targets/f7/furi_hal/furi_hal_bus.h,, Header,+,targets/f7/furi_hal/furi_hal_clock.h,, Header,+,targets/f7/furi_hal/furi_hal_dma.h,, @@ -257,8 +266,6 @@ Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,, -Header,+,targets/furi_hal_include/furi_hal_bt_hid.h,, -Header,+,targets/furi_hal_include/furi_hal_bt_serial.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,, Header,+,targets/furi_hal_include/furi_hal_debug.h,, @@ -378,6 +385,9 @@ Function,-,LL_USART_DeInit,ErrorStatus,const USART_TypeDef* Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, const LL_USART_InitTypeDef*" Function,-,LL_USART_StructInit,void,LL_USART_InitTypeDef* Function,-,LL_mDelay,void,uint32_t +Function,-,Osal_MemCmp,int,"const void*, const void*, unsigned int" +Function,-,Osal_MemCpy,void*,"void*, const void*, unsigned int" +Function,-,Osal_MemSet,void*,"void*, int, unsigned int" Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int @@ -670,9 +680,20 @@ Function,+,bit_lib_set_bit,void,"uint8_t*, size_t, _Bool" Function,+,bit_lib_set_bits,void,"uint8_t*, size_t, uint8_t, uint8_t" Function,+,bit_lib_test_parity,_Bool,"const uint8_t*, size_t, uint8_t, BitLibParity, uint8_t" Function,+,bit_lib_test_parity_32,_Bool,"uint32_t, BitLibParity" -Function,+,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" -Function,+,ble_app_init,_Bool, -Function,+,ble_app_thread_stop,void, +Function,-,ble_app_deinit,void, +Function,-,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,-,ble_app_init,_Bool, +Function,-,ble_event_app_notification,BleEventFlowStatus,void* +Function,-,ble_event_dispatcher_init,void, +Function,+,ble_event_dispatcher_process_event,BleEventFlowStatus,void* +Function,+,ble_event_dispatcher_register_svc_handler,GapSvcEventHandler*,"BleSvcEventHandlerCb, void*" +Function,-,ble_event_dispatcher_reset,void, +Function,+,ble_event_dispatcher_unregister_svc_handler,void,GapSvcEventHandler* +Function,+,ble_gatt_characteristic_delete,void,"uint16_t, BleGattCharacteristicInstance*" +Function,+,ble_gatt_characteristic_init,void,"uint16_t, const BleGattCharacteristicParams*, BleGattCharacteristicInstance*" +Function,+,ble_gatt_characteristic_update,_Bool,"uint16_t, BleGattCharacteristicInstance*, const void*" +Function,+,ble_gatt_service_add,_Bool,"uint8_t, const Service_UUID_t*, uint8_t, uint8_t, uint16_t*" +Function,+,ble_gatt_service_delete,_Bool,uint16_t Function,+,ble_glue_force_c2_mode,BleGlueCommandResult,BleGlueC2Mode Function,-,ble_glue_fus_get_status,BleGlueCommandResult, Function,-,ble_glue_fus_stack_delete,BleGlueCommandResult, @@ -680,20 +701,55 @@ Function,-,ble_glue_fus_stack_install,BleGlueCommandResult,"uint32_t, uint32_t" Function,-,ble_glue_fus_wait_operation,BleGlueCommandResult, Function,+,ble_glue_get_c2_info,const BleGlueC2Info*, Function,-,ble_glue_get_c2_status,BleGlueStatus, +Function,-,ble_glue_get_hardfault_info,const BleGlueHardfaultInfo*, Function,+,ble_glue_init,void, Function,+,ble_glue_is_alive,_Bool, Function,+,ble_glue_is_radio_stack_ready,_Bool, Function,+,ble_glue_reinit_c2,_Bool, Function,+,ble_glue_set_key_storage_changed_callback,void,"BleGlueKeyStorageChangedCallback, void*" -Function,+,ble_glue_start,_Bool, -Function,+,ble_glue_thread_stop,void, +Function,-,ble_glue_start,_Bool, +Function,-,ble_glue_stop,void, Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t +Function,-,ble_profile_hid_consumer_key_press,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_consumer_key_release,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_consumer_key_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_kb_press,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_kb_release,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_kb_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_mouse_move,_Bool,"FuriHalBleProfileBase*, int8_t, int8_t" +Function,-,ble_profile_hid_mouse_press,_Bool,"FuriHalBleProfileBase*, uint8_t" +Function,-,ble_profile_hid_mouse_release,_Bool,"FuriHalBleProfileBase*, uint8_t" +Function,-,ble_profile_hid_mouse_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_mouse_scroll,_Bool,"FuriHalBleProfileBase*, int8_t" +Function,+,ble_profile_serial_notify_buffer_is_empty,void,FuriHalBleProfileBase* +Function,+,ble_profile_serial_set_event_callback,void,"FuriHalBleProfileBase*, uint16_t, FuriHalBtSerialCallback, void*" +Function,+,ble_profile_serial_set_rpc_active,void,"FuriHalBleProfileBase*, _Bool" +Function,+,ble_profile_serial_tx,_Bool,"FuriHalBleProfileBase*, uint8_t*, uint16_t" +Function,+,ble_svc_battery_start,BleServiceBattery*,_Bool +Function,+,ble_svc_battery_state_update,void,"uint8_t*, _Bool*" +Function,+,ble_svc_battery_stop,void,BleServiceBattery* +Function,+,ble_svc_battery_update_level,_Bool,"BleServiceBattery*, uint8_t" +Function,+,ble_svc_battery_update_power_state,_Bool,"BleServiceBattery*, _Bool" +Function,+,ble_svc_dev_info_start,BleServiceDevInfo*, +Function,+,ble_svc_dev_info_stop,void,BleServiceDevInfo* +Function,-,ble_svc_hid_start,BleServiceHid*, +Function,-,ble_svc_hid_stop,void,BleServiceHid* +Function,-,ble_svc_hid_update_info,_Bool,"BleServiceHid*, uint8_t*" +Function,-,ble_svc_hid_update_input_report,_Bool,"BleServiceHid*, uint8_t, uint8_t*, uint16_t" +Function,-,ble_svc_hid_update_report_map,_Bool,"BleServiceHid*, const uint8_t*, uint16_t" +Function,+,ble_svc_serial_notify_buffer_is_empty,void,BleServiceSerial* +Function,+,ble_svc_serial_set_callbacks,void,"BleServiceSerial*, uint16_t, SerialServiceEventCallback, void*" +Function,+,ble_svc_serial_set_rpc_active,void,"BleServiceSerial*, _Bool" +Function,+,ble_svc_serial_start,BleServiceSerial*, +Function,+,ble_svc_serial_stop,void,BleServiceSerial* +Function,+,ble_svc_serial_update_tx,_Bool,"BleServiceSerial*, uint8_t*, uint16_t" Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" -Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" +Function,+,bt_profile_restore_default,_Bool,Bt* +Function,+,bt_profile_start,FuriHalBleProfileBase*,"Bt*, const FuriHalBleProfileTemplate*, FuriHalBleProfileParams" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -1098,46 +1154,34 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_get_tick,uint32_t, -Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" +Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode -Function,-,furi_hal_bt_get_hardfault_info,const FuriHalBtHardfaultInfo*, +Function,+,furi_hal_bt_extra_beacon_get_config,const GapExtraBeaconConfig*, +Function,+,furi_hal_bt_extra_beacon_get_data,uint8_t,uint8_t* +Function,+,furi_hal_bt_extra_beacon_is_active,_Bool, +Function,+,furi_hal_bt_extra_beacon_set_config,_Bool,const GapExtraBeaconConfig* +Function,+,furi_hal_bt_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" +Function,+,furi_hal_bt_extra_beacon_start,_Bool, +Function,+,furi_hal_bt_extra_beacon_stop,_Bool, Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, Function,+,furi_hal_bt_get_rssi,float, Function,+,furi_hal_bt_get_transmitted_packets,uint32_t, -Function,+,furi_hal_bt_hid_consumer_key_press,_Bool,uint16_t -Function,+,furi_hal_bt_hid_consumer_key_release,_Bool,uint16_t -Function,+,furi_hal_bt_hid_consumer_key_release_all,_Bool, -Function,+,furi_hal_bt_hid_kb_press,_Bool,uint16_t -Function,+,furi_hal_bt_hid_kb_release,_Bool,uint16_t -Function,+,furi_hal_bt_hid_kb_release_all,_Bool, -Function,+,furi_hal_bt_hid_mouse_move,_Bool,"int8_t, int8_t" -Function,+,furi_hal_bt_hid_mouse_press,_Bool,uint8_t -Function,+,furi_hal_bt_hid_mouse_release,_Bool,uint8_t -Function,+,furi_hal_bt_hid_mouse_release_all,_Bool, -Function,+,furi_hal_bt_hid_mouse_scroll,_Bool,int8_t -Function,+,furi_hal_bt_hid_start,void, -Function,+,furi_hal_bt_hid_stop,void, Function,-,furi_hal_bt_init,void, Function,+,furi_hal_bt_is_active,_Bool, Function,+,furi_hal_bt_is_alive,_Bool, -Function,+,furi_hal_bt_is_ble_gatt_gap_supported,_Bool, +Function,+,furi_hal_bt_is_gatt_gap_supported,_Bool, Function,+,furi_hal_bt_is_testing_supported,_Bool, Function,+,furi_hal_bt_lock_core2,void, Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, -Function,+,furi_hal_bt_serial_notify_buffer_is_empty,void, -Function,+,furi_hal_bt_serial_set_event_callback,void,"uint16_t, FuriHalBtSerialCallback, void*" -Function,+,furi_hal_bt_serial_set_rpc_status,void,FuriHalBtSerialRpcStatus -Function,+,furi_hal_bt_serial_start,void, -Function,+,furi_hal_bt_serial_stop,void, -Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" Function,+,furi_hal_bt_start_advertising,void, -Function,+,furi_hal_bt_start_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t" Function,+,furi_hal_bt_start_radio_stack,_Bool, @@ -1149,7 +1193,7 @@ Function,+,furi_hal_bt_stop_rx,void, Function,+,furi_hal_bt_stop_tone_tx,void, Function,+,furi_hal_bt_unlock_core2,void, Function,+,furi_hal_bt_update_battery_level,void,uint8_t -Function,+,furi_hal_bt_update_power_state,void, +Function,+,furi_hal_bt_update_power_state,void,_Bool Function,+,furi_hal_bus_deinit_early,void, Function,+,furi_hal_bus_disable,void,FuriHalBus Function,+,furi_hal_bus_enable,void,FuriHalBus @@ -1705,6 +1749,7 @@ Function,+,furi_thread_get_current_priority,FuriThreadPriority, Function,+,furi_thread_get_heap_size,size_t,FuriThread* Function,+,furi_thread_get_id,FuriThreadId,FuriThread* Function,+,furi_thread_get_name,const char*,FuriThreadId +Function,+,furi_thread_get_priority,FuriThreadPriority,FuriThread* Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* @@ -1743,6 +1788,15 @@ Function,-,gamma,double,double Function,-,gamma_r,double,"double, int*" Function,-,gammaf,float,float Function,-,gammaf_r,float,"float, int*" +Function,-,gap_emit_ble_beacon_status_event,void,_Bool +Function,-,gap_extra_beacon_get_config,const GapExtraBeaconConfig*, +Function,-,gap_extra_beacon_get_data,uint8_t,uint8_t* +Function,-,gap_extra_beacon_get_state,GapExtraBeaconState, +Function,-,gap_extra_beacon_init,void, +Function,-,gap_extra_beacon_set_config,_Bool,const GapExtraBeaconConfig* +Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" +Function,-,gap_extra_beacon_start,_Bool, +Function,-,gap_extra_beacon_stop,_Bool, Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, @@ -1766,6 +1820,7 @@ Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" +Function,-,hci_send_req,int,"hci_request*, uint8_t" Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" @@ -2834,13 +2889,6 @@ Function,+,scene_manager_stop,void,SceneManager* Function,+,sd_api_get_fs_type_text,const char*,SDFsType Function,-,secure_getenv,char*,const char* Function,-,seed48,unsigned short*,unsigned short[3] -Function,-,serial_svc_is_started,_Bool, -Function,-,serial_svc_notify_buffer_is_empty,void, -Function,-,serial_svc_set_callbacks,void,"uint16_t, SerialServiceEventCallback, void*" -Function,-,serial_svc_set_rpc_status,void,SerialServiceRpcStatus -Function,-,serial_svc_start,void, -Function,-,serial_svc_stop,void, -Function,-,serial_svc_update_tx,_Bool,"uint8_t*, uint16_t" Function,-,setbuf,void,"FILE*, char*" Function,-,setbuffer,void,"FILE*, char*, int" Function,-,setenv,int,"const char*, const char*, int" @@ -3455,6 +3503,8 @@ Variable,+,_impure_data,_reent, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, +Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, +Variable,-,ble_profile_serial,const FuriHalBleProfileTemplate*, Variable,+,cli_vcp,CliSession, Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f7/ble_glue/app_common.h b/targets/f7/ble_glue/app_common.h index e969636d27..8097d23dbe 100644 --- a/targets/f7/ble_glue/app_common.h +++ b/targets/f7/ble_glue/app_common.h @@ -7,6 +7,6 @@ #include #include -#include +#include #include "app_conf.h" diff --git a/targets/f7/ble_glue/app_conf.h b/targets/f7/ble_glue/app_conf.h index 25fa688c70..fbf6d0291d 100644 --- a/targets/f7/ble_glue/app_conf.h +++ b/targets/f7/ble_glue/app_conf.h @@ -46,7 +46,7 @@ * Maximum number of simultaneous connections that the device will support. * Valid values are from 1 to 8 */ -#define CFG_BLE_NUM_LINK 1 +#define CFG_BLE_NUM_LINK 2 /** * Maximum number of Services that can be stored in the GATT database. diff --git a/targets/f7/ble_glue/ble_app.c b/targets/f7/ble_glue/ble_app.c index 05dd46e943..1f392529de 100644 --- a/targets/f7/ble_glue/ble_app.c +++ b/targets/f7/ble_glue/ble_app.c @@ -1,19 +1,17 @@ #include "ble_app.h" +#include #include #include #include #include "gap.h" +#include "furi_ble/event_dispatcher.h" #include #include #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]; @@ -24,12 +22,10 @@ _Static_assert( typedef struct { FuriMutex* hci_mtx; FuriSemaphore* hci_sem; - FuriThread* thread; } BleApp; static BleApp* ble_app = NULL; -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); @@ -81,30 +77,35 @@ static const SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = { SHCI_C2_BLE_INIT_OPTIONS_APPEARANCE_READONLY, }}; -bool ble_app_init() { +bool ble_app_init(void) { SHCI_CmdStatus_t status; ble_app = malloc(sizeof(BleApp)); // Allocate semafore and mutex for ble command buffer access ble_app->hci_mtx = furi_mutex_alloc(FuriMutexTypeNormal); ble_app->hci_sem = furi_semaphore_alloc(1, 0); - // HCI transport layer thread to handle user asynch events - ble_app->thread = furi_thread_alloc_ex("BleHciDriver", 1024, ble_app_hci_thread, ble_app); - furi_thread_start(ble_app->thread); // Initialize Ble Transport Layer hci_init(ble_app_hci_event_handler, (void*)&hci_tl_config); - // Configure NVM store for pairing data - status = SHCI_C2_Config((SHCI_C2_CONFIG_Cmd_Param_t*)&config_param); - if(status) { - FURI_LOG_E(TAG, "Failed to configure 2nd core: %d", status); - } + do { + // Configure NVM store for pairing data + if((status = SHCI_C2_Config((SHCI_C2_CONFIG_Cmd_Param_t*)&config_param))) { + FURI_LOG_E(TAG, "Failed to configure 2nd core: %d", status); + break; + } + + // Start ble stack on 2nd core + if((status = SHCI_C2_BLE_Init((SHCI_C2_Ble_Init_Cmd_Packet_t*)&ble_init_cmd_packet))) { + FURI_LOG_E(TAG, "Failed to start ble stack: %d", status); + break; + } + + if((status = SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7))) { + FURI_LOG_E(TAG, "Failed to set flash activity control: %d", status); + break; + } + } while(false); - // Start ble stack on 2nd core - status = SHCI_C2_BLE_Init((SHCI_C2_Ble_Init_Cmd_Packet_t*)&ble_init_cmd_packet); - if(status) { - FURI_LOG_E(TAG, "Failed to start ble stack: %d", status); - } return status == SHCI_Success; } @@ -113,47 +114,18 @@ void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size) { *size = sizeof(ble_app_nvm); } -void ble_app_thread_stop() { - if(ble_app) { - FuriThreadId thread_id = furi_thread_get_id(ble_app->thread); - furi_assert(thread_id); - furi_thread_flags_set(thread_id, BLE_APP_FLAG_KILL_THREAD); - furi_thread_join(ble_app->thread); - furi_thread_free(ble_app->thread); - // Free resources - furi_mutex_free(ble_app->hci_mtx); - furi_semaphore_free(ble_app->hci_sem); - 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) { - UNUSED(arg); - uint32_t flags = 0; - - while(1) { - flags = furi_thread_flags_wait(BLE_APP_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); - if(flags & BLE_APP_FLAG_KILL_THREAD) { - break; - } - if(flags & BLE_APP_FLAG_HCI_EVENT) { - hci_user_evt_proc(); - } - } +void ble_app_deinit(void) { + furi_check(ble_app); - return 0; + furi_mutex_free(ble_app->hci_mtx); + furi_semaphore_free(ble_app->hci_sem); + free(ble_app); + ble_app = NULL; + memset(&ble_app_cmd_buffer, 0, sizeof(ble_app_cmd_buffer)); } -// Called by WPAN lib -void hci_notify_asynch_evt(void* pdata) { - UNUSED(pdata); - furi_check(ble_app); - FuriThreadId thread_id = furi_thread_get_id(ble_app->thread); - furi_assert(thread_id); - furi_thread_flags_set(thread_id, BLE_APP_FLAG_HCI_EVENT); -} +/////////////////////////////////////////////////////////////////////////////// +// AN5289, 4.9 void hci_cmd_resp_release(uint32_t flag) { UNUSED(flag); @@ -166,13 +138,16 @@ void hci_cmd_resp_wait(uint32_t timeout) { furi_check(furi_semaphore_acquire(ble_app->hci_sem, timeout) == FuriStatusOk); } +/////////////////////////////////////////////////////////////////////////////// + static void ble_app_hci_event_handler(void* pPayload) { - SVCCTL_UserEvtFlowStatus_t svctl_return_status; + furi_check(ble_app); + tHCI_UserEvtRxParam* pParam = (tHCI_UserEvtRxParam*)pPayload; + BleEventFlowStatus event_flow_status = + ble_event_dispatcher_process_event((void*)&(pParam->pckt->evtserial)); - furi_check(ble_app); - svctl_return_status = SVCCTL_UserEvtRx((void*)&(pParam->pckt->evtserial)); - if(svctl_return_status != SVCCTL_UserEvtFlowDisable) { + if(event_flow_status != BleEventFlowDisable) { pParam->status = HCI_TL_UserEventFlow_Enable; } else { pParam->status = HCI_TL_UserEventFlow_Disable; diff --git a/targets/f7/ble_glue/ble_app.h b/targets/f7/ble_glue/ble_app.h index 2e6babab79..22edccd1b1 100644 --- a/targets/f7/ble_glue/ble_app.h +++ b/targets/f7/ble_glue/ble_app.h @@ -3,13 +3,19 @@ #include #include +/* + * BLE stack init and cleanup + */ + #ifdef __cplusplus extern "C" { #endif -bool ble_app_init(); +bool ble_app_init(void); + void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size); -void ble_app_thread_stop(); + +void ble_app_deinit(void); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/ble_conf.h b/targets/f7/ble_glue/ble_conf.h index 2b9c22dfe3..4c523a707e 100644 --- a/targets/f7/ble_glue/ble_conf.h +++ b/targets/f7/ble_glue/ble_conf.h @@ -3,12 +3,9 @@ #include "app_conf.h" /** - * There is one handler per service enabled - * Note: There is no handler for the Device Information Service - * - * This shall take into account all registered handlers - * (from either the provided services or the custom services) + * We're not using WPAN's event dispatchers + * so both client & service max callback count is set to 0. */ -#define BLE_CFG_SVC_MAX_NBR_CB 7 +#define BLE_CFG_SVC_MAX_NBR_CB 0 #define BLE_CFG_CLT_MAX_NBR_CB 0 diff --git a/targets/f7/ble_glue/ble_event_thread.c b/targets/f7/ble_glue/ble_event_thread.c new file mode 100644 index 0000000000..6f9a1cdcd3 --- /dev/null +++ b/targets/f7/ble_glue/ble_event_thread.c @@ -0,0 +1,96 @@ +#include "app_common.h" + +#include +#include +#include + +#include +#include + +#define TAG "BleEvt" + +#define BLE_EVENT_THREAD_FLAG_SHCI_EVENT (1UL << 0) +#define BLE_EVENT_THREAD_FLAG_HCI_EVENT (1UL << 1) +#define BLE_EVENT_THREAD_FLAG_KILL_THREAD (1UL << 2) + +#define BLE_EVENT_THREAD_FLAG_ALL \ + (BLE_EVENT_THREAD_FLAG_SHCI_EVENT | BLE_EVENT_THREAD_FLAG_HCI_EVENT | \ + BLE_EVENT_THREAD_FLAG_KILL_THREAD) + +static FuriThread* event_thread = NULL; + +static int32_t ble_event_thread(void* context) { + UNUSED(context); + uint32_t flags = 0; + + while((flags & BLE_EVENT_THREAD_FLAG_KILL_THREAD) == 0) { + flags = + furi_thread_flags_wait(BLE_EVENT_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); + if(flags & BLE_EVENT_THREAD_FLAG_SHCI_EVENT) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_W(TAG, "shci_user_evt_proc"); +#endif + shci_user_evt_proc(); + } + if(flags & BLE_EVENT_THREAD_FLAG_HCI_EVENT) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_W(TAG, "hci_user_evt_proc"); +#endif + hci_user_evt_proc(); + } + } + + return 0; +} + +void shci_notify_asynch_evt(void* pdata) { + UNUSED(pdata); + if(!event_thread) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_E(TAG, "shci: event_thread is NULL"); +#endif + return; + } + + FuriThreadId thread_id = furi_thread_get_id(event_thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BLE_EVENT_THREAD_FLAG_SHCI_EVENT); +} + +void hci_notify_asynch_evt(void* pdata) { + UNUSED(pdata); + if(!event_thread) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_E(TAG, "hci: event_thread is NULL"); +#endif + return; + } + + FuriThreadId thread_id = furi_thread_get_id(event_thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BLE_EVENT_THREAD_FLAG_HCI_EVENT); +} + +void ble_event_thread_stop(void) { + if(!event_thread) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_E(TAG, "thread_stop: event_thread is NULL"); +#endif + return; + } + + FuriThreadId thread_id = furi_thread_get_id(event_thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BLE_EVENT_THREAD_FLAG_KILL_THREAD); + furi_thread_join(event_thread); + furi_thread_free(event_thread); + event_thread = NULL; +} + +void ble_event_thread_start(void) { + furi_check(event_thread == NULL); + + event_thread = furi_thread_alloc_ex("BleEventWorker", 1024, ble_event_thread, NULL); + furi_thread_set_priority(event_thread, FuriThreadPriorityHigh); + furi_thread_start(event_thread); +} diff --git a/targets/f7/ble_glue/ble_event_thread.h b/targets/f7/ble_glue/ble_event_thread.h new file mode 100644 index 0000000000..bce858d6b7 --- /dev/null +++ b/targets/f7/ble_glue/ble_event_thread.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Controls for thread handling SHCI & HCI event queues. Used internally. */ + +void ble_event_thread_start(void); + +void ble_event_thread_stop(void); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/ble_glue.c b/targets/f7/ble_glue/ble_glue.c index 5f129ba8ce..91cb020d72 100644 --- a/targets/f7/ble_glue/ble_glue.c +++ b/targets/f7/ble_glue/ble_glue.c @@ -1,6 +1,11 @@ #include "ble_glue.h" #include "app_common.h" #include "ble_app.h" +#include "ble_event_thread.h" + +#include +#include +#include #include #include @@ -13,26 +18,26 @@ #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 BLE_GLUE_HARDFAULT_CHECK_PERIOD_MS (5000) + +#define BLE_GLUE_HARDFAULT_INFO_MAGIC (0x1170FD0F) #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]; -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t ble_glue_system_cmd_buff; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_event_pool[POOL_SIZE]; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t ble_glue_cmd_buff; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) -static uint8_t ble_glue_system_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U]; +static uint8_t ble_glue_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U]; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) -static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; +static uint8_t ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; typedef struct { FuriMutex* shci_mtx; - FuriThread* thread; + FuriTimer* hardfault_check_timer; BleGlueStatus status; BleGlueKeyStorageChangedCallback callback; BleGlueC2Info c2_info; @@ -41,9 +46,10 @@ typedef struct { static BleGlue* ble_glue = NULL; -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); +// static int32_t ble_glue_shci_thread(void* argument); +static void ble_sys_status_not_callback(SHCI_TL_CmdStatus_t status); +static void ble_sys_user_event_callback(void* pPayload); +static void ble_glue_clear_shared_memory(); void ble_glue_set_key_storage_changed_callback( BleGlueKeyStorageChangedCallback callback, @@ -54,43 +60,21 @@ void ble_glue_set_key_storage_changed_callback( ble_glue->context = context; } -/////////////////////////////////////////////////////////////////////////////// - -/* TL hook to catch hardfaults */ - -int32_t ble_glue_TL_SYS_SendCmd(uint8_t* buffer, uint16_t size) { - if(furi_hal_bt_get_hardfault_info()) { - furi_crash("ST(R) Copro(R) HardFault"); - } - - return TL_SYS_SendCmd(buffer, size); -} - -void shci_register_io_bus(tSHciIO* fops) { - /* Register IO bus services */ - fops->Init = TL_SYS_Init; - fops->Send = ble_glue_TL_SYS_SendCmd; -} - -static int32_t ble_glue_TL_BLE_SendCmd(uint8_t* buffer, uint16_t size) { - if(furi_hal_bt_get_hardfault_info()) { +static void furi_hal_bt_hardfault_check(void* context) { + UNUSED(context); + if(ble_glue_get_hardfault_info()) { furi_crash("ST(R) Copro(R) HardFault"); } - - return TL_BLE_SendCmd(buffer, size); -} - -void hci_register_io_bus(tHciIO* fops) { - /* Register IO bus services */ - fops->Init = TL_BLE_Init; - fops->Send = ble_glue_TL_BLE_SendCmd; } /////////////////////////////////////////////////////////////////////////////// -void ble_glue_init() { +void ble_glue_init(void) { ble_glue = malloc(sizeof(BleGlue)); ble_glue->status = BleGlueStatusStartup; + ble_glue->hardfault_check_timer = + furi_timer_alloc(furi_hal_bt_hardfault_check, FuriTimerTypePeriodic, NULL); + furi_timer_start(ble_glue->hardfault_check_timer, BLE_GLUE_HARDFAULT_CHECK_PERIOD_MS); #ifdef BLE_GLUE_DEBUG APPD_Init(); @@ -105,18 +89,17 @@ void ble_glue_init() { ble_glue->shci_mtx = furi_mutex_alloc(FuriMutexTypeNormal); // FreeRTOS system task creation - ble_glue->thread = furi_thread_alloc_ex("BleShciDriver", 1024, ble_glue_shci_thread, ble_glue); - furi_thread_start(ble_glue->thread); + ble_event_thread_start(); // System channel initialization - SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&ble_glue_system_cmd_buff; - SHci_Tl_Init_Conf.StatusNotCallBack = ble_glue_sys_status_not_callback; - shci_init(ble_glue_sys_user_event_callback, (void*)&SHci_Tl_Init_Conf); + SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&ble_glue_cmd_buff; + SHci_Tl_Init_Conf.StatusNotCallBack = ble_sys_status_not_callback; + shci_init(ble_sys_user_event_callback, (void*)&SHci_Tl_Init_Conf); /**< Memory Manager channel initialization */ - tl_mm_config.p_BleSpareEvtBuffer = ble_glue_ble_spare_event_buff; - tl_mm_config.p_SystemSpareEvtBuffer = ble_glue_system_spare_event_buff; - tl_mm_config.p_AsynchEvtPool = ble_glue_event_pool; + tl_mm_config.p_BleSpareEvtBuffer = ble_spare_event_buff; + tl_mm_config.p_SystemSpareEvtBuffer = ble_glue_spare_event_buff; + tl_mm_config.p_AsynchEvtPool = ble_event_pool; tl_mm_config.AsynchEvtPoolSize = POOL_SIZE; TL_MM_Init(&tl_mm_config); TL_Enable(); @@ -124,15 +107,15 @@ void ble_glue_init() { /* * From now, the application is waiting for the ready event ( VS_HCI_C2_Ready ) * received on the system channel before starting the Stack - * This system event is received with ble_glue_sys_user_event_callback() + * This system event is received with ble_sys_user_event_callback() */ } -const BleGlueC2Info* ble_glue_get_c2_info() { +const BleGlueC2Info* ble_glue_get_c2_info(void) { return &ble_glue->c2_info; } -BleGlueStatus ble_glue_get_c2_status() { +BleGlueStatus ble_glue_get_c2_status(void) { return ble_glue->status; } @@ -159,7 +142,7 @@ static const char* ble_glue_get_reltype_str(const uint8_t reltype) { } } -static void ble_glue_update_c2_fw_info() { +static void ble_glue_update_c2_fw_info(void) { WirelessFwInfo_t wireless_info; SHCI_GetWirelessFwInfo(&wireless_info); BleGlueC2Info* local_info = &ble_glue->c2_info; @@ -178,7 +161,7 @@ static void ble_glue_update_c2_fw_info() { local_info->StackType = wireless_info.StackType; snprintf( local_info->StackTypeString, - BLE_GLUE_MAX_VERSION_STRING_LEN, + BLE_MAX_VERSION_STRING_LEN, "%d.%d.%d:%s", local_info->VersionMajor, local_info->VersionMinor, @@ -193,7 +176,7 @@ static void ble_glue_update_c2_fw_info() { local_info->FusMemorySizeFlash = wireless_info.FusMemorySizeFlash; } -static void ble_glue_dump_stack_info() { +static void ble_glue_dump_stack_info(void) { const BleGlueC2Info* c2_info = &ble_glue->c2_info; FURI_LOG_I( TAG, @@ -216,59 +199,63 @@ static void ble_glue_dump_stack_info() { c2_info->MemorySizeFlash); } -bool ble_glue_wait_for_c2_start(int32_t timeout) { +bool ble_glue_wait_for_c2_start(int32_t timeout_ms) { bool started = false; + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout_ms * 1000); do { + furi_delay_tick(1); started = ble_glue->status == BleGlueStatusC2Started; - if(!started) { - timeout--; - furi_delay_tick(1); - } - } while(!started && (timeout > 0)); - - if(started) { - FURI_LOG_I( - TAG, - "C2 boot completed, mode: %s", - ble_glue->c2_info.mode == BleGlueC2ModeFUS ? "FUS" : "Stack"); - ble_glue_update_c2_fw_info(); - ble_glue_dump_stack_info(); - } else { + } while(!started && !furi_hal_cortex_timer_is_expired(timer)); + + if(!started) { FURI_LOG_E(TAG, "C2 startup failed"); ble_glue->status = BleGlueStatusBroken; + return false; } - return started; + FURI_LOG_I( + TAG, + "C2 boot completed, mode: %s", + ble_glue->c2_info.mode == BleGlueC2ModeFUS ? "FUS" : "Stack"); + ble_glue_update_c2_fw_info(); + ble_glue_dump_stack_info(); + return true; } -bool ble_glue_start() { +bool ble_glue_start(void) { furi_assert(ble_glue); if(ble_glue->status != BleGlueStatusC2Started) { return false; } - bool ret = false; - if(ble_app_init()) { - FURI_LOG_I(TAG, "Radio stack started"); - ble_glue->status = BleGlueStatusRadioStackRunning; - ret = true; - if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) { - FURI_LOG_I(TAG, "Flash activity control switched to SEM7"); - } else { - FURI_LOG_E(TAG, "Failed to switch flash activity control to SEM7"); - } - } else { + if(!ble_app_init()) { FURI_LOG_E(TAG, "Radio stack startup failed"); ble_glue->status = BleGlueStatusRadioStackMissing; - ble_app_thread_stop(); + ble_app_deinit(); + return false; } - return ret; + FURI_LOG_I(TAG, "Radio stack started"); + ble_glue->status = BleGlueStatusRadioStackRunning; + return true; } -bool ble_glue_is_alive() { +void ble_glue_stop(void) { + furi_assert(ble_glue); + + ble_event_thread_stop(); + // Free resources + furi_mutex_free(ble_glue->shci_mtx); + furi_timer_free(ble_glue->hardfault_check_timer); + + ble_glue_clear_shared_memory(); + free(ble_glue); + ble_glue = NULL; +} + +bool ble_glue_is_alive(void) { if(!ble_glue) { return false; } @@ -276,7 +263,7 @@ bool ble_glue_is_alive() { return ble_glue->status >= BleGlueStatusC2Started; } -bool ble_glue_is_radio_stack_ready() { +bool ble_glue_is_radio_stack_ready(void) { if(!ble_glue) { return false; } @@ -319,7 +306,7 @@ BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode desired_mode) { return BleGlueCommandResultError; } -static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { +static void ble_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { switch(status) { case SHCI_TL_CmdBusy: furi_mutex_acquire(ble_glue->shci_mtx, FuriWaitForever); @@ -341,7 +328,7 @@ static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { * ( eg ((tSHCI_UserEvtRxParam*)pPayload)->status shall be set to SHCI_TL_UserEventFlow_Disable ) * When the status is not filled, the buffer is released by default */ -static void ble_glue_sys_user_event_callback(void* pPayload) { +static void ble_sys_user_event_callback(void* pPayload) { UNUSED(pPayload); #ifdef BLE_GLUE_DEBUG @@ -375,60 +362,18 @@ static void ble_glue_sys_user_event_callback(void* pPayload) { } } -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) { - FuriThreadId thread_id = furi_thread_get_id(ble_glue->thread); - furi_assert(thread_id); - furi_thread_flags_set(thread_id, BLE_GLUE_FLAG_KILL_THREAD); - furi_thread_join(ble_glue->thread); - furi_thread_free(ble_glue->thread); - // Free resources - furi_mutex_free(ble_glue->shci_mtx); - ble_glue_clear_shared_memory(); - free(ble_glue); - ble_glue = NULL; - } -} - -// Wrap functions -static int32_t ble_glue_shci_thread(void* context) { - UNUSED(context); - uint32_t flags = 0; - - while(true) { - flags = furi_thread_flags_wait(BLE_GLUE_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); - 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); - if(ble_glue) { - FuriThreadId thread_id = furi_thread_get_id(ble_glue->thread); - furi_assert(thread_id); - furi_thread_flags_set(thread_id, BLE_GLUE_FLAG_SHCI_EVENT); - } +static void ble_glue_clear_shared_memory(void) { + memset(ble_event_pool, 0, sizeof(ble_event_pool)); + memset(&ble_glue_cmd_buff, 0, sizeof(ble_glue_cmd_buff)); + memset(ble_glue_spare_event_buff, 0, sizeof(ble_glue_spare_event_buff)); + memset(ble_spare_event_buff, 0, sizeof(ble_spare_event_buff)); } -bool ble_glue_reinit_c2() { - return SHCI_C2_Reinit() == SHCI_Success; +bool ble_glue_reinit_c2(void) { + return (SHCI_C2_Reinit() == SHCI_Success); } -BleGlueCommandResult ble_glue_fus_stack_delete() { +BleGlueCommandResult ble_glue_fus_stack_delete(void) { FURI_LOG_I(TAG, "Erasing stack"); SHCI_CmdStatus_t erase_stat = SHCI_C2_FUS_FwDelete(); FURI_LOG_I(TAG, "Cmd res = %x", erase_stat); @@ -450,8 +395,9 @@ BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_ return BleGlueCommandResultError; } -BleGlueCommandResult ble_glue_fus_get_status() { +BleGlueCommandResult ble_glue_fus_get_status(void) { furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); + SHCI_FUS_GetState_ErrorCode_t error_code = 0; uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code); FURI_LOG_I(TAG, "FUS state: %x, error: %x", fus_state, error_code); @@ -465,7 +411,7 @@ BleGlueCommandResult ble_glue_fus_get_status() { return BleGlueCommandResultOK; } -BleGlueCommandResult ble_glue_fus_wait_operation() { +BleGlueCommandResult ble_glue_fus_wait_operation(void) { furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); while(true) { @@ -479,3 +425,12 @@ BleGlueCommandResult ble_glue_fus_wait_operation() { } } } + +const BleGlueHardfaultInfo* ble_glue_get_hardfault_info(void) { + /* AN5289, 4.8.2 */ + const BleGlueHardfaultInfo* info = (BleGlueHardfaultInfo*)(SRAM2A_BASE); + if(info->magic != BLE_GLUE_HARDFAULT_INFO_MAGIC) { + return NULL; + } + return info; +} diff --git a/targets/f7/ble_glue/ble_glue.h b/targets/f7/ble_glue/ble_glue.h index bd2588a026..05c34148cf 100644 --- a/targets/f7/ble_glue/ble_glue.h +++ b/targets/f7/ble_glue/ble_glue.h @@ -7,13 +7,17 @@ extern "C" { #endif +/* + * Low-level interface to Core2 - startup, shutdown, mode switching, FUS commands. + */ + typedef enum { BleGlueC2ModeUnknown = 0, BleGlueC2ModeFUS, BleGlueC2ModeStack, } BleGlueC2Mode; -#define BLE_GLUE_MAX_VERSION_STRING_LEN 20 +#define BLE_MAX_VERSION_STRING_LEN (20) typedef struct { BleGlueC2Mode mode; /** @@ -29,7 +33,7 @@ typedef struct { uint8_t MemorySizeSram1; /*< Multiple of 1K */ uint8_t MemorySizeFlash; /*< Multiple of 4K */ uint8_t StackType; - char StackTypeString[BLE_GLUE_MAX_VERSION_STRING_LEN]; + char StackTypeString[BLE_MAX_VERSION_STRING_LEN]; /** * Fus Info */ @@ -55,35 +59,37 @@ typedef void ( *BleGlueKeyStorageChangedCallback)(uint8_t* change_addr_start, uint16_t size, void* context); /** Initialize start core2 and initialize transport */ -void ble_glue_init(); +void ble_glue_init(void); /** Start Core2 Radio stack * * @return true on success */ -bool ble_glue_start(); +bool ble_glue_start(void); + +void ble_glue_stop(void); /** Is core2 alive and at least FUS is running * * @return true if core2 is alive */ -bool ble_glue_is_alive(); +bool ble_glue_is_alive(void); /** Waits for C2 to reports its mode to callback * * @return true if it reported before reaching timeout */ -bool ble_glue_wait_for_c2_start(int32_t timeout); +bool ble_glue_wait_for_c2_start(int32_t timeout_ms); -BleGlueStatus ble_glue_get_c2_status(); +BleGlueStatus ble_glue_get_c2_status(void); -const BleGlueC2Info* ble_glue_get_c2_info(); +const BleGlueC2Info* ble_glue_get_c2_info(void); /** Is core2 radio stack present and ready * * @return true if present and ready */ -bool ble_glue_is_radio_stack_ready(); +bool ble_glue_is_radio_stack_ready(void); /** Set callback for NVM in RAM changes * @@ -94,9 +100,6 @@ void ble_glue_set_key_storage_changed_callback( BleGlueKeyStorageChangedCallback callback, void* context); -/** Stop SHCI thread */ -void ble_glue_thread_stop(); - bool ble_glue_reinit_c2(); typedef enum { @@ -113,13 +116,26 @@ typedef enum { */ BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode mode); -BleGlueCommandResult ble_glue_fus_stack_delete(); +BleGlueCommandResult ble_glue_fus_stack_delete(void); BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr); -BleGlueCommandResult ble_glue_fus_get_status(); +BleGlueCommandResult ble_glue_fus_get_status(void); + +BleGlueCommandResult ble_glue_fus_wait_operation(void); -BleGlueCommandResult ble_glue_fus_wait_operation(); +typedef struct { + uint32_t magic; + uint32_t source_pc; + uint32_t source_lr; + uint32_t source_sp; +} BleGlueHardfaultInfo; + +/** Get hardfault info + * + * @return hardfault info. NULL if no hardfault + */ +const BleGlueHardfaultInfo* ble_glue_get_hardfault_info(void); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/ble_tl_hooks.c b/targets/f7/ble_glue/ble_tl_hooks.c new file mode 100644 index 0000000000..092afd7424 --- /dev/null +++ b/targets/f7/ble_glue/ble_tl_hooks.c @@ -0,0 +1,40 @@ +#include "ble_glue.h" + +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////// + +/* + * TL hooks to catch hardfaults + */ + +int32_t ble_glue_TL_SYS_SendCmd(uint8_t* buffer, uint16_t size) { + if(ble_glue_get_hardfault_info()) { + furi_crash("ST(R) Copro(R) HardFault"); + } + + return TL_SYS_SendCmd(buffer, size); +} + +void shci_register_io_bus(tSHciIO* fops) { + /* Register IO bus services */ + fops->Init = TL_SYS_Init; + fops->Send = ble_glue_TL_SYS_SendCmd; +} + +static int32_t ble_glue_TL_BLE_SendCmd(uint8_t* buffer, uint16_t size) { + if(ble_glue_get_hardfault_info()) { + furi_crash("ST(R) Copro(R) HardFault"); + } + + return TL_BLE_SendCmd(buffer, size); +} + +void hci_register_io_bus(tHciIO* fops) { + /* Register IO bus services */ + fops->Init = TL_BLE_Init; + fops->Send = ble_glue_TL_BLE_SendCmd; +} diff --git a/targets/f7/ble_glue/extra_beacon.c b/targets/f7/ble_glue/extra_beacon.c new file mode 100644 index 0000000000..446b31380f --- /dev/null +++ b/targets/f7/ble_glue/extra_beacon.c @@ -0,0 +1,161 @@ +#include "extra_beacon.h" +#include "gap.h" + +#include +#include + +#define TAG "BleExtraBeacon" + +#define GAP_MS_TO_SCAN_INTERVAL(x) ((uint16_t)((x) / 0.625)) + +// Also used as an indicator of whether the beacon had ever been configured +#define GAP_MIN_ADV_INTERVAL_MS (20) + +typedef struct { + GapExtraBeaconConfig last_config; + GapExtraBeaconState extra_beacon_state; + uint8_t extra_beacon_data[EXTRA_BEACON_MAX_DATA_SIZE]; + uint8_t extra_beacon_data_len; + FuriMutex* state_mutex; +} ExtraBeacon; + +static ExtraBeacon extra_beacon = {0}; + +void gap_extra_beacon_init() { + if(extra_beacon.state_mutex) { + // Already initialized - restore state if needed + FURI_LOG_I(TAG, "Restoring state"); + gap_extra_beacon_set_data( + extra_beacon.extra_beacon_data, extra_beacon.extra_beacon_data_len); + if(extra_beacon.extra_beacon_state == GapExtraBeaconStateStarted) { + extra_beacon.extra_beacon_state = GapExtraBeaconStateStopped; + gap_extra_beacon_set_config(&extra_beacon.last_config); + } + + } else { + // First time init + FURI_LOG_I(TAG, "Init"); + extra_beacon.extra_beacon_state = GapExtraBeaconStateStopped; + extra_beacon.extra_beacon_data_len = 0; + memset(extra_beacon.extra_beacon_data, 0, EXTRA_BEACON_MAX_DATA_SIZE); + extra_beacon.state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + } +} + +bool gap_extra_beacon_set_config(const GapExtraBeaconConfig* config) { + furi_check(extra_beacon.state_mutex); + furi_check(config); + + furi_check(config->min_adv_interval_ms <= config->max_adv_interval_ms); + furi_check(config->min_adv_interval_ms >= GAP_MIN_ADV_INTERVAL_MS); + + if(extra_beacon.extra_beacon_state != GapExtraBeaconStateStopped) { + return false; + } + + furi_mutex_acquire(extra_beacon.state_mutex, FuriWaitForever); + if(config != &extra_beacon.last_config) { + memcpy(&extra_beacon.last_config, config, sizeof(GapExtraBeaconConfig)); + } + furi_mutex_release(extra_beacon.state_mutex); + + return true; +} + +bool gap_extra_beacon_start() { + furi_check(extra_beacon.state_mutex); + furi_check(extra_beacon.last_config.min_adv_interval_ms >= GAP_MIN_ADV_INTERVAL_MS); + + if(extra_beacon.extra_beacon_state != GapExtraBeaconStateStopped) { + return false; + } + + FURI_LOG_I(TAG, "Starting"); + furi_mutex_acquire(extra_beacon.state_mutex, FuriWaitForever); + const GapExtraBeaconConfig* config = &extra_beacon.last_config; + tBleStatus status = aci_gap_additional_beacon_start( + GAP_MS_TO_SCAN_INTERVAL(config->min_adv_interval_ms), + GAP_MS_TO_SCAN_INTERVAL(config->max_adv_interval_ms), + (uint8_t)config->adv_channel_map, + config->address_type, + config->address, + (uint8_t)config->adv_power_level); + if(status) { + FURI_LOG_E(TAG, "Failed to start: 0x%x", status); + return false; + } + extra_beacon.extra_beacon_state = GapExtraBeaconStateStarted; + gap_emit_ble_beacon_status_event(true); + furi_mutex_release(extra_beacon.state_mutex); + + return true; +} + +bool gap_extra_beacon_stop() { + furi_check(extra_beacon.state_mutex); + + if(extra_beacon.extra_beacon_state != GapExtraBeaconStateStarted) { + return false; + } + + FURI_LOG_I(TAG, "Stopping"); + furi_mutex_acquire(extra_beacon.state_mutex, FuriWaitForever); + tBleStatus status = aci_gap_additional_beacon_stop(); + if(status) { + FURI_LOG_E(TAG, "Failed to stop: 0x%x", status); + return false; + } + extra_beacon.extra_beacon_state = GapExtraBeaconStateStopped; + gap_emit_ble_beacon_status_event(false); + furi_mutex_release(extra_beacon.state_mutex); + + return true; +} + +bool gap_extra_beacon_set_data(const uint8_t* data, uint8_t length) { + furi_check(extra_beacon.state_mutex); + furi_check(data); + furi_check(length <= EXTRA_BEACON_MAX_DATA_SIZE); + + furi_mutex_acquire(extra_beacon.state_mutex, FuriWaitForever); + if(data != extra_beacon.extra_beacon_data) { + memcpy(extra_beacon.extra_beacon_data, data, length); + } + extra_beacon.extra_beacon_data_len = length; + + tBleStatus status = aci_gap_additional_beacon_set_data(length, data); + if(status) { + FURI_LOG_E(TAG, "Failed updating adv data: %d", status); + return false; + } + furi_mutex_release(extra_beacon.state_mutex); + + return true; +} + +uint8_t gap_extra_beacon_get_data(uint8_t* data) { + furi_check(extra_beacon.state_mutex); + furi_check(data); + + furi_mutex_acquire(extra_beacon.state_mutex, FuriWaitForever); + memcpy(data, extra_beacon.extra_beacon_data, extra_beacon.extra_beacon_data_len); + furi_mutex_release(extra_beacon.state_mutex); + + return extra_beacon.extra_beacon_data_len; +} + +GapExtraBeaconState gap_extra_beacon_get_state() { + furi_check(extra_beacon.state_mutex); + + return extra_beacon.extra_beacon_state; +} + +const GapExtraBeaconConfig* gap_extra_beacon_get_config() { + furi_check(extra_beacon.state_mutex); + + if(extra_beacon.last_config.min_adv_interval_ms < GAP_MIN_ADV_INTERVAL_MS) { + return NULL; + } + + return &extra_beacon.last_config; +} \ No newline at end of file diff --git a/targets/f7/ble_glue/extra_beacon.h b/targets/f7/ble_glue/extra_beacon.h new file mode 100644 index 0000000000..675ea538c9 --- /dev/null +++ b/targets/f7/ble_glue/extra_beacon.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Additinal non-connetable beacon API. + * Not to be used directly, but through furi_hal_bt_extra_beacon_* APIs. + */ + +#define EXTRA_BEACON_MAX_DATA_SIZE (31) +#define EXTRA_BEACON_MAC_ADDR_SIZE (6) + +typedef enum { + GapAdvChannelMap37 = 0b001, + GapAdvChannelMap38 = 0b010, + GapAdvChannelMap39 = 0b100, + GapAdvChannelMapAll = 0b111, +} GapAdvChannelMap; + +typedef enum { + GapAdvPowerLevel_Neg40dBm = 0x00, + GapAdvPowerLevel_Neg20_85dBm = 0x01, + GapAdvPowerLevel_Neg19_75dBm = 0x02, + GapAdvPowerLevel_Neg18_85dBm = 0x03, + GapAdvPowerLevel_Neg17_6dBm = 0x04, + GapAdvPowerLevel_Neg16_5dBm = 0x05, + GapAdvPowerLevel_Neg15_25dBm = 0x06, + GapAdvPowerLevel_Neg14_1dBm = 0x07, + GapAdvPowerLevel_Neg13_15dBm = 0x08, + GapAdvPowerLevel_Neg12_05dBm = 0x09, + GapAdvPowerLevel_Neg10_9dBm = 0x0A, + GapAdvPowerLevel_Neg9_9dBm = 0x0B, + GapAdvPowerLevel_Neg8_85dBm = 0x0C, + GapAdvPowerLevel_Neg7_8dBm = 0x0D, + GapAdvPowerLevel_Neg6_9dBm = 0x0E, + GapAdvPowerLevel_Neg5_9dBm = 0x0F, + GapAdvPowerLevel_Neg4_95dBm = 0x10, + GapAdvPowerLevel_Neg4dBm = 0x11, + GapAdvPowerLevel_Neg3_15dBm = 0x12, + GapAdvPowerLevel_Neg2_45dBm = 0x13, + GapAdvPowerLevel_Neg1_8dBm = 0x14, + GapAdvPowerLevel_Neg1_3dBm = 0x15, + GapAdvPowerLevel_Neg0_85dBm = 0x16, + GapAdvPowerLevel_Neg0_5dBm = 0x17, + GapAdvPowerLevel_Neg0_15dBm = 0x18, + GapAdvPowerLevel_0dBm = 0x19, + GapAdvPowerLevel_1dBm = 0x1A, + GapAdvPowerLevel_2dBm = 0x1B, + GapAdvPowerLevel_3dBm = 0x1C, + GapAdvPowerLevel_4dBm = 0x1D, + GapAdvPowerLevel_5dBm = 0x1E, + GapAdvPowerLevel_6dBm = 0x1F, +} GapAdvPowerLevelInd; + +typedef enum { + GapAddressTypePublic = 0, + GapAddressTypeRandom = 1, +} GapAddressType; + +typedef struct { + uint16_t min_adv_interval_ms, max_adv_interval_ms; + GapAdvChannelMap adv_channel_map; + GapAdvPowerLevelInd adv_power_level; + GapAddressType address_type; + uint8_t address[EXTRA_BEACON_MAC_ADDR_SIZE]; +} GapExtraBeaconConfig; + +typedef enum { + GapExtraBeaconStateUndefined = 0, + GapExtraBeaconStateStopped, + GapExtraBeaconStateStarted, +} GapExtraBeaconState; + +void gap_extra_beacon_init(); + +GapExtraBeaconState gap_extra_beacon_get_state(); + +bool gap_extra_beacon_start(); + +bool gap_extra_beacon_stop(); + +bool gap_extra_beacon_set_config(const GapExtraBeaconConfig* config); + +const GapExtraBeaconConfig* gap_extra_beacon_get_config(); + +bool gap_extra_beacon_set_data(const uint8_t* data, uint8_t length); + +// Fill "data" with last configured extra beacon data and return its length +uint8_t gap_extra_beacon_get_data(uint8_t* data); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/furi_ble/event_dispatcher.c b/targets/f7/ble_glue/furi_ble/event_dispatcher.c new file mode 100644 index 0000000000..ce3661d6d9 --- /dev/null +++ b/targets/f7/ble_glue/furi_ble/event_dispatcher.c @@ -0,0 +1,97 @@ +#include "event_dispatcher.h" +#include +#include +#include + +#include + +struct GapEventHandler { + void* context; + BleSvcEventHandlerCb callback; +}; + +LIST_DEF(GapSvcEventHandlerList, GapSvcEventHandler, M_POD_OPLIST); + +static GapSvcEventHandlerList_t handlers; +static bool initialized = false; + +BleEventFlowStatus ble_event_dispatcher_process_event(void* payload) { + furi_check(initialized); + + GapSvcEventHandlerList_it_t it; + BleEventAckStatus ack_status = BleEventNotAck; + + for(GapSvcEventHandlerList_it(it, handlers); !GapSvcEventHandlerList_end_p(it); + GapSvcEventHandlerList_next(it)) { + const GapSvcEventHandler* item = GapSvcEventHandlerList_cref(it); + ack_status = item->callback(payload, item->context); + if(ack_status == BleEventNotAck) { + /* Keep going */ + continue; + } else if((ack_status == BleEventAckFlowEnable) || (ack_status == BleEventAckFlowDisable)) { + break; + } + } + + /* Handlers for client-mode events are also to be implemented here. But not today. */ + + /* Now, decide on a flow control action based on results of all handlers */ + switch(ack_status) { + case BleEventNotAck: + /* The event has NOT been managed yet. Pass to app for processing */ + return ble_event_app_notification(payload); + case BleEventAckFlowEnable: + return BleEventFlowEnable; + case BleEventAckFlowDisable: + return BleEventFlowDisable; + default: + return BleEventFlowEnable; + } +} + +void ble_event_dispatcher_init(void) { + furi_assert(!initialized); + + GapSvcEventHandlerList_init(handlers); + initialized = true; +} + +void ble_event_dispatcher_reset(void) { + furi_assert(initialized); + furi_check(GapSvcEventHandlerList_size(handlers) == 0); + + GapSvcEventHandlerList_clear(handlers); +} + +GapSvcEventHandler* + ble_event_dispatcher_register_svc_handler(BleSvcEventHandlerCb handler, void* context) { + furi_check(handler); + furi_check(context); + furi_check(initialized); + + GapSvcEventHandler* item = GapSvcEventHandlerList_push_raw(handlers); + item->context = context; + item->callback = handler; + + return item; +} + +void ble_event_dispatcher_unregister_svc_handler(GapSvcEventHandler* handler) { + furi_check(handler); + + bool found = false; + GapSvcEventHandlerList_it_t it; + + for(GapSvcEventHandlerList_it(it, handlers); !GapSvcEventHandlerList_end_p(it); + GapSvcEventHandlerList_next(it)) { + const GapSvcEventHandler* item = GapSvcEventHandlerList_cref(it); + + if(item == handler) { + GapSvcEventHandlerList_remove(handlers, it); + found = true; + break; + } + } + + furi_check(found); +} diff --git a/targets/f7/ble_glue/furi_ble/event_dispatcher.h b/targets/f7/ble_glue/furi_ble/event_dispatcher.h new file mode 100644 index 0000000000..90fc0762f8 --- /dev/null +++ b/targets/f7/ble_glue/furi_ble/event_dispatcher.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + BleEventNotAck, + BleEventAckFlowEnable, + BleEventAckFlowDisable, +} BleEventAckStatus; + +typedef enum { + BleEventFlowDisable, + BleEventFlowEnable, +} BleEventFlowStatus; + +/* Using other types so not to leak all the BLE stack headers + (we don't have a wrapper for them yet) + * Event data is hci_uart_pckt* + * Context is user-defined + */ +typedef BleEventAckStatus (*BleSvcEventHandlerCb)(void* event, void* context); + +typedef struct GapEventHandler GapSvcEventHandler; + +/* To be called once at BLE system startup */ +void ble_event_dispatcher_init(void); + +/* To be called at stack reset - ensures that all handlers are unregistered */ +void ble_event_dispatcher_reset(void); + +BleEventFlowStatus ble_event_dispatcher_process_event(void* payload); + +/* Final handler for event not ack'd by services - to be implemented by app */ +BleEventFlowStatus ble_event_app_notification(void* pckt); + +/* Add a handler to the list of handlers */ +FURI_WARN_UNUSED GapSvcEventHandler* + ble_event_dispatcher_register_svc_handler(BleSvcEventHandlerCb handler, void* context); + +/* Remove a handler from the list of handlers */ +void ble_event_dispatcher_unregister_svc_handler(GapSvcEventHandler* handler); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/services/gatt_char.c b/targets/f7/ble_glue/furi_ble/gatt.c similarity index 73% rename from targets/f7/ble_glue/services/gatt_char.c rename to targets/f7/ble_glue/furi_ble/gatt.c index f6e27f53e6..dcea5f9873 100644 --- a/targets/f7/ble_glue/services/gatt_char.c +++ b/targets/f7/ble_glue/furi_ble/gatt.c @@ -1,4 +1,5 @@ -#include "gatt_char.h" +#include "gatt.h" +#include #include @@ -6,19 +7,19 @@ #define GATT_MIN_READ_KEY_SIZE (10) -void flipper_gatt_characteristic_init( +void ble_gatt_characteristic_init( uint16_t svc_handle, - const FlipperGattCharacteristicParams* char_descriptor, - FlipperGattCharacteristicInstance* char_instance) { + const BleGattCharacteristicParams* char_descriptor, + BleGattCharacteristicInstance* char_instance) { furi_assert(char_descriptor); furi_assert(char_instance); // Copy the descriptor to the instance, since it may point to stack memory - char_instance->characteristic = malloc(sizeof(FlipperGattCharacteristicParams)); + char_instance->characteristic = malloc(sizeof(BleGattCharacteristicParams)); memcpy( (void*)char_instance->characteristic, char_descriptor, - sizeof(FlipperGattCharacteristicParams)); + sizeof(BleGattCharacteristicParams)); uint16_t char_data_size = 0; if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataFixed) { @@ -46,7 +47,7 @@ void flipper_gatt_characteristic_init( char_instance->descriptor_handle = 0; if((status == 0) && char_descriptor->descriptor_params) { uint8_t const* char_data = NULL; - const FlipperGattCharacteristicDescriptorParams* char_data_descriptor = + const BleGattCharacteristicDescriptorParams* char_data_descriptor = char_descriptor->descriptor_params; bool release_data = char_data_descriptor->data_callback.fn( char_data_descriptor->data_callback.context, &char_data, &char_data_size); @@ -74,9 +75,9 @@ void flipper_gatt_characteristic_init( } } -void flipper_gatt_characteristic_delete( +void ble_gatt_characteristic_delete( uint16_t svc_handle, - FlipperGattCharacteristicInstance* char_instance) { + BleGattCharacteristicInstance* char_instance) { tBleStatus status = aci_gatt_del_char(svc_handle, char_instance->handle); if(status) { FURI_LOG_E( @@ -85,12 +86,12 @@ void flipper_gatt_characteristic_delete( free((void*)char_instance->characteristic); } -bool flipper_gatt_characteristic_update( +bool ble_gatt_characteristic_update( uint16_t svc_handle, - FlipperGattCharacteristicInstance* char_instance, + BleGattCharacteristicInstance* char_instance, const void* source) { furi_assert(char_instance); - const FlipperGattCharacteristicParams* char_descriptor = char_instance->characteristic; + const BleGattCharacteristicParams* char_descriptor = char_instance->characteristic; FURI_LOG_D(TAG, "Updating %s char", char_descriptor->name); const uint8_t* char_data = NULL; @@ -119,4 +120,28 @@ bool flipper_gatt_characteristic_update( free((void*)char_data); } return result != BLE_STATUS_SUCCESS; -} \ No newline at end of file +} + +bool ble_gatt_service_add( + uint8_t Service_UUID_Type, + const Service_UUID_t* Service_UUID, + uint8_t Service_Type, + uint8_t Max_Attribute_Records, + uint16_t* Service_Handle) { + tBleStatus result = aci_gatt_add_service( + Service_UUID_Type, Service_UUID, Service_Type, Max_Attribute_Records, Service_Handle); + if(result) { + FURI_LOG_E(TAG, "Failed to add service: %x", result); + } + + return result == BLE_STATUS_SUCCESS; +} + +bool ble_gatt_service_delete(uint16_t svc_handle) { + tBleStatus result = aci_gatt_del_service(svc_handle); + if(result) { + FURI_LOG_E(TAG, "Failed to delete service: %x", result); + } + + return result == BLE_STATUS_SUCCESS; +} diff --git a/targets/f7/ble_glue/furi_ble/gatt.h b/targets/f7/ble_glue/furi_ble/gatt.h new file mode 100644 index 0000000000..5a33e9e542 --- /dev/null +++ b/targets/f7/ble_glue/furi_ble/gatt.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Callback signature for getting characteristic data + * Is called when characteristic is created to get max data length. Data ptr is NULL in this case + * The result is passed to aci_gatt_add_char as "Char_Value_Length" + * For updates, called with a context - see flipper_gatt_characteristic_update + * Returns true if *data ownership is transferred to the caller and will be freed */ +typedef bool ( + *cbBleGattCharacteristicData)(const void* context, const uint8_t** data, uint16_t* data_len); + +/* Used to specify the type of data for a characteristic - constant or callback-based */ +typedef enum { + FlipperGattCharacteristicDataFixed, + FlipperGattCharacteristicDataCallback, +} BleGattCharacteristicDataType; + +typedef struct { + Char_Desc_Uuid_t uuid; + struct { + cbBleGattCharacteristicData fn; + const void* context; + } data_callback; + uint8_t uuid_type; + uint8_t max_length; + uint8_t security_permissions; + uint8_t access_permissions; + uint8_t gatt_evt_mask; + uint8_t is_variable; +} BleGattCharacteristicDescriptorParams; + +/* Describes a single characteristic, providing data or callbacks to get data */ +typedef struct { + const char* name; + BleGattCharacteristicDescriptorParams* descriptor_params; + union { + struct { + const uint8_t* ptr; + uint16_t length; + } fixed; + struct { + cbBleGattCharacteristicData fn; + const void* context; + } callback; + } data; + Char_UUID_t uuid; + // Some packed bitfields to save space + BleGattCharacteristicDataType data_prop_type : 2; + uint8_t is_variable : 2; + uint8_t uuid_type : 2; + uint8_t char_properties; + uint8_t security_permissions; + uint8_t gatt_evt_mask; +} BleGattCharacteristicParams; + +_Static_assert( + sizeof(BleGattCharacteristicParams) == 36, + "BleGattCharacteristicParams size must be 36 bytes"); + +typedef struct { + const BleGattCharacteristicParams* characteristic; + uint16_t handle; + uint16_t descriptor_handle; +} BleGattCharacteristicInstance; + +/* Initialize a characteristic instance; copies the characteristic descriptor + * into the instance */ +void ble_gatt_characteristic_init( + uint16_t svc_handle, + const BleGattCharacteristicParams* char_descriptor, + BleGattCharacteristicInstance* char_instance); + +/* Delete a characteristic instance; frees the copied characteristic + * descriptor from the instance */ +void ble_gatt_characteristic_delete( + uint16_t svc_handle, + BleGattCharacteristicInstance* char_instance); + +/* Update a characteristic instance; if source==NULL, uses the data from + * the characteristic: + * - For fixed data, fixed.ptr is used as the source if source==NULL + * - For callback-based data, collback.context is passed as the context + * if source==NULL + */ +bool ble_gatt_characteristic_update( + uint16_t svc_handle, + BleGattCharacteristicInstance* char_instance, + const void* source); + +bool ble_gatt_service_add( + uint8_t Service_UUID_Type, + const Service_UUID_t* Service_UUID, + uint8_t Service_Type, + uint8_t Max_Attribute_Records, + uint16_t* Service_Handle); + +bool ble_gatt_service_delete(uint16_t svc_handle); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/furi_ble/profile_interface.h b/targets/f7/ble_glue/furi_ble/profile_interface.h new file mode 100644 index 0000000000..f1b42837bd --- /dev/null +++ b/targets/f7/ble_glue/furi_ble/profile_interface.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FuriHalBleProfileTemplate FuriHalBleProfileTemplate; + +/* Actual profiles must inherit (include this structure) as their first field */ +typedef struct { + /* Pointer to the config for this profile. Must be used to check if the + * instance belongs to the profile */ + const FuriHalBleProfileTemplate* config; +} FuriHalBleProfileBase; + +typedef void* FuriHalBleProfileParams; + +typedef FuriHalBleProfileBase* (*FuriHalBleProfileStart)(FuriHalBleProfileParams profile_params); +typedef void (*FuriHalBleProfileStop)(FuriHalBleProfileBase* profile); +typedef void (*FuriHalBleProfileGetGapConfig)( + GapConfig* target_config, + FuriHalBleProfileParams profile_params); + +struct FuriHalBleProfileTemplate { + /* Returns an instance of the profile */ + FuriHalBleProfileStart start; + /* Destroys the instance of the profile. Must check if instance belongs to the profile */ + FuriHalBleProfileStop stop; + /* Called before starting the profile to get the GAP configuration */ + FuriHalBleProfileGetGapConfig get_gap_config; +}; + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 8e3ec58b73..86623b02fb 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -1,12 +1,15 @@ #include "gap.h" #include "app_common.h" +#include +#include "furi_ble/event_dispatcher.h" #include #include #include +#include -#define TAG "BtGap" +#define TAG "BleGap" #define FAST_ADV_TIMEOUT 30000 #define INITIAL_ADV_TIMEOUT 60000 @@ -83,7 +86,7 @@ static void gap_verify_connection_parameters(Gap* gap) { } } -SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { +BleEventFlowStatus ble_event_app_notification(void* pckt) { hci_event_pckt* event_pckt; evt_le_meta_event* meta_evt; evt_blecore_aci* blue_evt; @@ -269,7 +272,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { if(gap) { furi_mutex_release(gap->state_mutex); } - return SVCCTL_UserEvtFlowEnable; + return BleEventFlowEnable; } static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) { @@ -372,6 +375,8 @@ static void gap_advertise_start(GapState new_state) { uint16_t min_interval; uint16_t max_interval; + FURI_LOG_I(TAG, "Start: %d", new_state); + if(new_state == GapStateAdvFast) { min_interval = 0x80; // 80 ms max_interval = 0xa0; // 100 ms @@ -414,7 +419,8 @@ static void gap_advertise_start(GapState new_state) { furi_timer_start(gap->advertise_timer, INITIAL_ADV_TIMEOUT); } -static void gap_advertise_stop() { +static void gap_advertise_stop(void) { + FURI_LOG_I(TAG, "Stop"); tBleStatus ret; if(gap->state > GapStateIdle) { if(gap->state == GapStateConnected) { @@ -440,7 +446,7 @@ static void gap_advertise_stop() { gap->on_event_cb(event, gap->context); } -void gap_start_advertising() { +void gap_start_advertising(void) { furi_mutex_acquire(gap->state_mutex, FuriWaitForever); if(gap->state == GapStateIdle) { gap->state = GapStateStartingAdv; @@ -452,7 +458,7 @@ void gap_start_advertising() { furi_mutex_release(gap->state_mutex); } -void gap_stop_advertising() { +void gap_stop_advertising(void) { furi_mutex_acquire(gap->state_mutex, FuriWaitForever); if(gap->state > GapStateIdle) { FURI_LOG_I(TAG, "Stop advertising"); @@ -481,8 +487,7 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { // Initialization of GATT & GAP layer gap->service.adv_name = config->adv_name; gap_init_svc(gap); - // Initialization of the BLE Services - SVCCTL_Init(); + ble_event_dispatcher_init(); // Initialization of the GAP state gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); gap->state = GapStateIdle; @@ -505,10 +510,11 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { // Set callback gap->on_event_cb = on_event_cb; gap->context = context; + return true; } -GapState gap_get_state() { +GapState gap_get_state(void) { GapState state; if(gap) { furi_mutex_acquire(gap->state_mutex, FuriWaitForever); @@ -520,7 +526,7 @@ GapState gap_get_state() { return state; } -void gap_thread_stop() { +void gap_thread_stop(void) { if(gap) { furi_mutex_acquire(gap->state_mutex, FuriWaitForever); gap->enable_adv = false; @@ -533,6 +539,8 @@ void gap_thread_stop() { furi_mutex_free(gap->state_mutex); furi_message_queue_free(gap->command_queue); furi_timer_free(gap->advertise_timer); + + ble_event_dispatcher_reset(); free(gap); gap = NULL; } @@ -563,3 +571,9 @@ static int32_t gap_app(void* context) { return 0; } + +void gap_emit_ble_beacon_status_event(bool active) { + GapEvent event = {.type = active ? GapEventTypeBeaconStart : GapEventTypeBeaconStop}; + gap->on_event_cb(event, gap->context); + FURI_LOG_I(TAG, "Beacon status event: %d", active); +} diff --git a/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h index 1e207299f2..a90d073047 100644 --- a/targets/f7/ble_glue/gap.h +++ b/targets/f7/ble_glue/gap.h @@ -7,6 +7,10 @@ #define GAP_MAC_ADDR_SIZE (6) +/* + * GAP helpers - background thread that handles BLE GAP events and advertising. + */ + #ifdef __cplusplus extern "C" { #endif @@ -19,6 +23,8 @@ typedef enum { GapEventTypePinCodeShow, GapEventTypePinCodeVerify, GapEventTypeUpdateMTU, + GapEventTypeBeaconStart, + GapEventTypeBeaconStop, } GapEventType; typedef union { @@ -73,13 +79,15 @@ typedef struct { bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context); -void gap_start_advertising(); +void gap_start_advertising(void); + +void gap_stop_advertising(void); -void gap_stop_advertising(); +GapState gap_get_state(void); -GapState gap_get_state(); +void gap_thread_stop(void); -void gap_thread_stop(); +void gap_emit_ble_beacon_status_event(bool active); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c new file mode 100644 index 0000000000..a3949abfc8 --- /dev/null +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -0,0 +1,114 @@ +#include "serial_profile.h" + +#include +#include +#include +#include +#include +#include + +typedef struct { + FuriHalBleProfileBase base; + + BleServiceDevInfo* dev_info_svc; + BleServiceBattery* battery_svc; + BleServiceSerial* serial_svc; +} BleProfileSerial; +_Static_assert(offsetof(BleProfileSerial, base) == 0, "Wrong layout"); + +static FuriHalBleProfileBase* ble_profile_serial_start(FuriHalBleProfileParams profile_params) { + UNUSED(profile_params); + + BleProfileSerial* profile = malloc(sizeof(BleProfileSerial)); + + profile->base.config = ble_profile_serial; + + profile->dev_info_svc = ble_svc_dev_info_start(); + profile->battery_svc = ble_svc_battery_start(true); + profile->serial_svc = ble_svc_serial_start(); + + return &profile->base; +} + +static void ble_profile_serial_stop(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_serial); + + BleProfileSerial* serial_profile = (BleProfileSerial*)profile; + ble_svc_battery_stop(serial_profile->battery_svc); + ble_svc_dev_info_stop(serial_profile->dev_info_svc); + ble_svc_serial_stop(serial_profile->serial_svc); +} + +static GapConfig serial_template_config = { + .adv_service_uuid = 0x3080, + .appearance_char = 0x8600, + .bonding_mode = true, + .pairing_method = GapPairingPinCodeShow, + .conn_param = { + .conn_int_min = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms + .slave_latency = 0, + .supervisor_timeout = 0, + }}; + +static void + ble_profile_serial_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) { + UNUSED(profile_params); + + furi_check(config); + memcpy(config, &serial_template_config, sizeof(GapConfig)); + // Set mac address + memcpy(config->mac_address, furi_hal_version_get_ble_mac(), sizeof(config->mac_address)); + // Set advertise name + strlcpy( + config->adv_name, + furi_hal_version_get_ble_local_device_name_ptr(), + FURI_HAL_VERSION_DEVICE_NAME_LENGTH); + config->adv_service_uuid |= furi_hal_version_get_hw_color(); +} + +static const FuriHalBleProfileTemplate profile_callbacks = { + .start = ble_profile_serial_start, + .stop = ble_profile_serial_stop, + .get_gap_config = ble_profile_serial_get_config, +}; + +const FuriHalBleProfileTemplate* ble_profile_serial = &profile_callbacks; + +void ble_profile_serial_set_event_callback( + FuriHalBleProfileBase* profile, + uint16_t buff_size, + FuriHalBtSerialCallback callback, + void* context) { + furi_check(profile && (profile->config == ble_profile_serial)); + + BleProfileSerial* serial_profile = (BleProfileSerial*)profile; + ble_svc_serial_set_callbacks(serial_profile->serial_svc, buff_size, callback, context); +} + +void ble_profile_serial_notify_buffer_is_empty(FuriHalBleProfileBase* profile) { + furi_check(profile && (profile->config == ble_profile_serial)); + + BleProfileSerial* serial_profile = (BleProfileSerial*)profile; + ble_svc_serial_notify_buffer_is_empty(serial_profile->serial_svc); +} + +void ble_profile_serial_set_rpc_active(FuriHalBleProfileBase* profile, bool active) { + furi_check(profile && (profile->config == ble_profile_serial)); + + BleProfileSerial* serial_profile = (BleProfileSerial*)profile; + ble_svc_serial_set_rpc_active(serial_profile->serial_svc, active); +} + +bool ble_profile_serial_tx(FuriHalBleProfileBase* profile, uint8_t* data, uint16_t size) { + furi_check(profile && (profile->config == ble_profile_serial)); + + BleProfileSerial* serial_profile = (BleProfileSerial*)profile; + + if(size > BLE_PROFILE_SERIAL_PACKET_SIZE_MAX) { + return false; + } + + return ble_svc_serial_update_tx(serial_profile->serial_svc, data, size); +} diff --git a/targets/f7/ble_glue/profiles/serial_profile.h b/targets/f7/ble_glue/profiles/serial_profile.h new file mode 100644 index 0000000000..e07eaef031 --- /dev/null +++ b/targets/f7/ble_glue/profiles/serial_profile.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define BLE_PROFILE_SERIAL_PACKET_SIZE_MAX BLE_SVC_SERIAL_DATA_LEN_MAX + +typedef enum { + FuriHalBtSerialRpcStatusNotActive, + FuriHalBtSerialRpcStatusActive, +} FuriHalBtSerialRpcStatus; + +/** Serial service callback type */ +typedef SerialServiceEventCallback FuriHalBtSerialCallback; + +/** Serial profile descriptor */ +extern const FuriHalBleProfileTemplate* ble_profile_serial; + +/** Send data through BLE + * + * @param profile Profile instance + * @param data data buffer + * @param size data buffer size + * + * @return true on success + */ +bool ble_profile_serial_tx(FuriHalBleProfileBase* profile, uint8_t* data, uint16_t size); + +/** Set BLE RPC status + * + * @param profile Profile instance + * @param active true if RPC is active + */ +void ble_profile_serial_set_rpc_active(FuriHalBleProfileBase* profile, bool active); + +/** Notify that application buffer is empty + * @param profile Profile instance + */ +void ble_profile_serial_notify_buffer_is_empty(FuriHalBleProfileBase* profile); + +/** Set Serial service events callback + * + * @param profile Profile instance + * @param buffer_size Applicaition buffer size + * @param calback FuriHalBtSerialCallback instance + * @param context pointer to context + */ +void ble_profile_serial_set_event_callback( + FuriHalBleProfileBase* profile, + uint16_t buff_size, + FuriHalBtSerialCallback callback, + void* context); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/services/battery_service.c b/targets/f7/ble_glue/services/battery_service.c index 63f736b3b7..f4bc1ff7bb 100644 --- a/targets/f7/ble_glue/services/battery_service.c +++ b/targets/f7/ble_glue/services/battery_service.c @@ -1,28 +1,30 @@ #include "battery_service.h" #include "app_common.h" -#include "gatt_char.h" +#include +#include #include #include -#include + +#include #define TAG "BtBatterySvc" enum { - // Common states + /* Common states */ BatterySvcPowerStateUnknown = 0b00, BatterySvcPowerStateUnsupported = 0b01, - // Level states + /* Level states */ BatterySvcPowerStateGoodLevel = 0b10, BatterySvcPowerStateCriticallyLowLevel = 0b11, - // Charging states + /* Charging states */ BatterySvcPowerStateNotCharging = 0b10, BatterySvcPowerStateCharging = 0b11, - // Discharging states + /* Discharging states */ BatterySvcPowerStateNotDischarging = 0b10, BatterySvcPowerStateDischarging = 0b11, - // Battery states + /* Battery states */ BatterySvcPowerStateBatteryNotPresent = 0b10, BatterySvcPowerStateBatteryPresent = 0b11, }; @@ -46,96 +48,110 @@ typedef enum { BatterySvcGattCharacteristicCount, } BatterySvcGattCharacteristicId; -static const FlipperGattCharacteristicParams battery_svc_chars[BatterySvcGattCharacteristicCount] = - {[BatterySvcGattCharacteristicBatteryLevel] = - {.name = "Battery Level", - .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = 1, - .uuid.Char_UUID_16 = BATTERY_LEVEL_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, - .security_permissions = ATTR_PERMISSION_AUTHEN_READ, - .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_CONSTANT}, - [BatterySvcGattCharacteristicPowerState] = { - .name = "Power State", +static const BleGattCharacteristicParams battery_svc_chars[BatterySvcGattCharacteristicCount] = { + [BatterySvcGattCharacteristicBatteryLevel] = + {.name = "Battery Level", .data_prop_type = FlipperGattCharacteristicDataFixed, .data.fixed.length = 1, - .uuid.Char_UUID_16 = BATTERY_POWER_STATE, + .uuid.Char_UUID_16 = BATTERY_LEVEL_CHAR_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, .security_permissions = ATTR_PERMISSION_AUTHEN_READ, .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_CONSTANT}}; - -typedef struct { + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [BatterySvcGattCharacteristicPowerState] = { + .name = "Power State", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = 1, + .uuid.Char_UUID_16 = BATTERY_POWER_STATE, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}}; + +struct BleServiceBattery { uint16_t svc_handle; - FlipperGattCharacteristicInstance chars[BatterySvcGattCharacteristicCount]; -} BatterySvc; + BleGattCharacteristicInstance chars[BatterySvcGattCharacteristicCount]; + bool auto_update; +}; -static BatterySvc* battery_svc = NULL; +LIST_DEF(BatterySvcInstanceList, BleServiceBattery*, M_POD_OPLIST); -void battery_svc_start() { - battery_svc = malloc(sizeof(BatterySvc)); - tBleStatus status; +/* We need to keep track of all battery service instances so that we can update + * them when the battery state changes. */ +static BatterySvcInstanceList_t instances; +static bool instances_initialized = false; - // Add Battery service - status = aci_gatt_add_service( - UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 8, &battery_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add Battery service: %d", status); +BleServiceBattery* ble_svc_battery_start(bool auto_update) { + BleServiceBattery* battery_svc = malloc(sizeof(BleServiceBattery)); + + if(!ble_gatt_service_add( + UUID_TYPE_16, + (Service_UUID_t*)&service_uuid, + PRIMARY_SERVICE, + 8, + &battery_svc->svc_handle)) { + free(battery_svc); + return NULL; } for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_init( + ble_gatt_characteristic_init( battery_svc->svc_handle, &battery_svc_chars[i], &battery_svc->chars[i]); } - battery_svc_update_power_state(); + battery_svc->auto_update = auto_update; + if(auto_update) { + if(!instances_initialized) { + BatterySvcInstanceList_init(instances); + instances_initialized = true; + } + + BatterySvcInstanceList_push_back(instances, battery_svc); + } + + return battery_svc; } -void battery_svc_stop() { - tBleStatus status; - if(battery_svc) { - for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_delete(battery_svc->svc_handle, &battery_svc->chars[i]); +void ble_svc_battery_stop(BleServiceBattery* battery_svc) { + furi_assert(battery_svc); + if(battery_svc->auto_update) { + BatterySvcInstanceList_it_t it; + for(BatterySvcInstanceList_it(it, instances); !BatterySvcInstanceList_end_p(it); + BatterySvcInstanceList_next(it)) { + if(*BatterySvcInstanceList_ref(it) == battery_svc) { + BatterySvcInstanceList_remove(instances, it); + break; + } } - // Delete Battery service - status = aci_gatt_del_service(battery_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Battery service: %d", status); - } - free(battery_svc); - battery_svc = NULL; } -} -bool battery_svc_is_started() { - return battery_svc != NULL; + for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_delete(battery_svc->svc_handle, &battery_svc->chars[i]); + } + /* Delete Battery service */ + ble_gatt_service_delete(battery_svc->svc_handle); + free(battery_svc); } -bool battery_svc_update_level(uint8_t battery_charge) { - // Check if service was started - if(battery_svc == NULL) { - return false; - } - // Update battery level characteristic - return flipper_gatt_characteristic_update( +bool ble_svc_battery_update_level(BleServiceBattery* battery_svc, uint8_t battery_charge) { + furi_check(battery_svc); + /* Update battery level characteristic */ + return ble_gatt_characteristic_update( battery_svc->svc_handle, &battery_svc->chars[BatterySvcGattCharacteristicBatteryLevel], &battery_charge); } -bool battery_svc_update_power_state() { - // Check if service was started - if(battery_svc == NULL) { - return false; - } - // Update power state characteristic +bool ble_svc_battery_update_power_state(BleServiceBattery* battery_svc, bool charging) { + furi_check(battery_svc); + + /* Update power state characteristic */ BattrySvcPowerState power_state = { .level = BatterySvcPowerStateUnsupported, .present = BatterySvcPowerStateBatteryPresent, }; - if(furi_hal_power_is_charging()) { + if(charging) { power_state.charging = BatterySvcPowerStateCharging; power_state.discharging = BatterySvcPowerStateNotDischarging; } else { @@ -143,8 +159,29 @@ bool battery_svc_update_power_state() { power_state.discharging = BatterySvcPowerStateDischarging; } - return flipper_gatt_characteristic_update( + return ble_gatt_characteristic_update( battery_svc->svc_handle, &battery_svc->chars[BatterySvcGattCharacteristicPowerState], &power_state); } + +void ble_svc_battery_state_update(uint8_t* battery_level, bool* charging) { + if(!instances_initialized) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_W(TAG, "Battery service not initialized"); +#endif + return; + } + + BatterySvcInstanceList_it_t it; + for(BatterySvcInstanceList_it(it, instances); !BatterySvcInstanceList_end_p(it); + BatterySvcInstanceList_next(it)) { + BleServiceBattery* battery_svc = *BatterySvcInstanceList_ref(it); + if(battery_level) { + ble_svc_battery_update_level(battery_svc, *battery_level); + } + if(charging) { + ble_svc_battery_update_power_state(battery_svc, *charging); + } + } +} diff --git a/targets/f7/ble_glue/services/battery_service.h b/targets/f7/ble_glue/services/battery_service.h index f38bfc00d2..dccc440476 100644 --- a/targets/f7/ble_glue/services/battery_service.h +++ b/targets/f7/ble_glue/services/battery_service.h @@ -7,15 +7,27 @@ extern "C" { #endif -void battery_svc_start(); +/* + * Battery service. Can be used in most profiles. + * If auto_update is true, the service will automatically update the battery + * level and charging state from power state updates. + */ -void battery_svc_stop(); +typedef struct BleServiceBattery BleServiceBattery; -bool battery_svc_is_started(); +BleServiceBattery* ble_svc_battery_start(bool auto_update); -bool battery_svc_update_level(uint8_t battery_level); +void ble_svc_battery_stop(BleServiceBattery* service); -bool battery_svc_update_power_state(); +bool ble_svc_battery_update_level(BleServiceBattery* service, uint8_t battery_level); + +bool ble_svc_battery_update_power_state(BleServiceBattery* service, bool charging); + +/* Global function, callable without a service instance + * Will update all service instances created with auto_update==true + * Both parameters are optional, pass NULL if no value is available + */ +void ble_svc_battery_state_update(uint8_t* battery_level, bool* charging); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/services/dev_info_service.c b/targets/f7/ble_glue/services/dev_info_service.c index 59af23e5c2..37caa8c90b 100644 --- a/targets/f7/ble_glue/services/dev_info_service.c +++ b/targets/f7/ble_glue/services/dev_info_service.c @@ -1,6 +1,6 @@ #include "dev_info_service.h" #include "app_common.h" -#include "gatt_char.h" +#include #include #include @@ -20,45 +20,30 @@ typedef enum { DevInfoSvcGattCharacteristicCount, } DevInfoSvcGattCharacteristicId; -#define DEVICE_INFO_HARDWARE_REV_SIZE 4 -typedef struct { - uint16_t service_handle; - FlipperGattCharacteristicInstance characteristics[DevInfoSvcGattCharacteristicCount]; - FuriString* version_string; - char hardware_revision[DEVICE_INFO_HARDWARE_REV_SIZE]; -} DevInfoSvc; +#define DEVICE_INFO_HARDWARE_REV_SIZE (4) +#define DEVICE_INFO_SOFTWARE_REV_SIZE (40) -static DevInfoSvc* dev_info_svc = NULL; +struct BleServiceDevInfo { + uint16_t service_handle; + BleGattCharacteristicInstance characteristics[DevInfoSvcGattCharacteristicCount]; +}; static const char dev_info_man_name[] = "Flipper Devices Inc."; static const char dev_info_serial_num[] = "1.0"; static const char dev_info_rpc_version[] = TOSTRING(PROTOBUF_MAJOR_VERSION.PROTOBUF_MINOR_VERSION); +static char hardware_revision[DEVICE_INFO_HARDWARE_REV_SIZE] = {0}; +static char software_revision[DEVICE_INFO_SOFTWARE_REV_SIZE] = {0}; -static bool dev_info_char_firmware_rev_callback( - const void* context, - const uint8_t** data, - uint16_t* data_len) { - const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context; - *data_len = strlen(dev_info_svc->hardware_revision); - if(data) { - *data = (const uint8_t*)&dev_info_svc->hardware_revision; - } - return false; -} - -static bool dev_info_char_software_rev_callback( - const void* context, - const uint8_t** data, - uint16_t* data_len) { - const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context; - *data_len = furi_string_size(dev_info_svc->version_string); +static bool + dev_info_char_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) { + *data_len = (uint16_t)strlen(context); //-V1029 if(data) { - *data = (const uint8_t*)furi_string_get_cstr(dev_info_svc->version_string); + *data = (const uint8_t*)context; } return false; } -static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCharacteristicCount] = +static const BleGattCharacteristicParams ble_svc_dev_info_chars[DevInfoSvcGattCharacteristicCount] = {[DevInfoSvcGattCharacteristicMfgName] = {.name = "Manufacturer Name", .data_prop_type = FlipperGattCharacteristicDataFixed, @@ -84,8 +69,8 @@ static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCh [DevInfoSvcGattCharacteristicFirmwareRev] = {.name = "Firmware Revision", .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.context = &dev_info_svc, - .data.callback.fn = dev_info_char_firmware_rev_callback, + .data.callback.context = hardware_revision, + .data.callback.fn = dev_info_char_data_callback, .uuid.Char_UUID_16 = FIRMWARE_REVISION_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_READ, @@ -95,8 +80,8 @@ static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCh [DevInfoSvcGattCharacteristicSoftwareRev] = {.name = "Software Revision", .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.context = &dev_info_svc, - .data.callback.fn = dev_info_char_software_rev_callback, + .data.callback.context = software_revision, + .data.callback.fn = dev_info_char_data_callback, .uuid.Char_UUID_16 = SOFTWARE_REVISION_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_READ, @@ -115,64 +100,52 @@ static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCh .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, .is_variable = CHAR_VALUE_LEN_CONSTANT}}; -void dev_info_svc_start() { - dev_info_svc = malloc(sizeof(DevInfoSvc)); - dev_info_svc->version_string = furi_string_alloc_printf( +BleServiceDevInfo* ble_svc_dev_info_start(void) { + BleServiceDevInfo* dev_info_svc = malloc(sizeof(BleServiceDevInfo)); + snprintf( + software_revision, + sizeof(software_revision), "%s %s %s %s", version_get_githash(NULL), version_get_gitbranch(NULL), version_get_gitbranchnum(NULL), version_get_builddate(NULL)); - snprintf( - dev_info_svc->hardware_revision, - sizeof(dev_info_svc->hardware_revision), - "%d", - version_get_target(NULL)); - tBleStatus status; + snprintf(hardware_revision, sizeof(hardware_revision), "%d", version_get_target(NULL)); // Add Device Information Service uint16_t uuid = DEVICE_INFORMATION_SERVICE_UUID; - status = aci_gatt_add_service( - UUID_TYPE_16, - (Service_UUID_t*)&uuid, - PRIMARY_SERVICE, - 1 + 2 * DevInfoSvcGattCharacteristicCount, - &dev_info_svc->service_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add Device Information Service: %d", status); + if(!ble_gatt_service_add( + UUID_TYPE_16, + (Service_UUID_t*)&uuid, + PRIMARY_SERVICE, + 1 + 2 * DevInfoSvcGattCharacteristicCount, + &dev_info_svc->service_handle)) { + free(dev_info_svc); + return NULL; } for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_init( + ble_gatt_characteristic_init( dev_info_svc->service_handle, - &dev_info_svc_chars[i], + &ble_svc_dev_info_chars[i], &dev_info_svc->characteristics[i]); - flipper_gatt_characteristic_update( + ble_gatt_characteristic_update( dev_info_svc->service_handle, &dev_info_svc->characteristics[i], NULL); } + + return dev_info_svc; } -void dev_info_svc_stop() { - tBleStatus status; - if(dev_info_svc) { - // Delete service characteristics - for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_delete( - dev_info_svc->service_handle, &dev_info_svc->characteristics[i]); - } - - // Delete service - status = aci_gatt_del_service(dev_info_svc->service_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete device info service: %d", status); - } - - furi_string_free(dev_info_svc->version_string); - free(dev_info_svc); - dev_info_svc = NULL; +void ble_svc_dev_info_stop(BleServiceDevInfo* dev_info_svc) { + furi_assert(dev_info_svc); + /* Delete service characteristics */ + for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_delete( + dev_info_svc->service_handle, &dev_info_svc->characteristics[i]); } -} -bool dev_info_svc_is_started() { - return dev_info_svc != NULL; + /* Delete service */ + ble_gatt_service_delete(dev_info_svc->service_handle); + + free(dev_info_svc); } diff --git a/targets/f7/ble_glue/services/dev_info_service.h b/targets/f7/ble_glue/services/dev_info_service.h index 8cce20a6cc..42471e56d4 100644 --- a/targets/f7/ble_glue/services/dev_info_service.h +++ b/targets/f7/ble_glue/services/dev_info_service.h @@ -7,11 +7,16 @@ extern "C" { #endif -void dev_info_svc_start(); +/* + * Device information service. + * Holds Flipper name, version and other information. + */ -void dev_info_svc_stop(); +typedef struct BleServiceDevInfo BleServiceDevInfo; -bool dev_info_svc_is_started(); +BleServiceDevInfo* ble_svc_dev_info_start(void); + +void ble_svc_dev_info_stop(BleServiceDevInfo* service); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/services/gatt_char.h b/targets/f7/ble_glue/services/gatt_char.h deleted file mode 100644 index 959ab67a49..0000000000 --- a/targets/f7/ble_glue/services/gatt_char.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// Callback signature for getting characteristic data -// Is called when characteristic is created to get max data length. Data ptr is NULL in this case -// The result is passed to aci_gatt_add_char as "Char_Value_Length" -// For updates, called with a context - see flipper_gatt_characteristic_update -// Returns true if *data ownership is transferred to the caller and will be freed -typedef bool (*cbFlipperGattCharacteristicData)( - const void* context, - const uint8_t** data, - uint16_t* data_len); - -typedef enum { - FlipperGattCharacteristicDataFixed, - FlipperGattCharacteristicDataCallback, -} FlipperGattCharacteristicDataType; - -typedef struct { - Char_Desc_Uuid_t uuid; - struct { - cbFlipperGattCharacteristicData fn; - const void* context; - } data_callback; - uint8_t uuid_type; - uint8_t max_length; - uint8_t security_permissions; - uint8_t access_permissions; - uint8_t gatt_evt_mask; - uint8_t is_variable; -} FlipperGattCharacteristicDescriptorParams; - -typedef struct { - const char* name; - FlipperGattCharacteristicDescriptorParams* descriptor_params; - union { - struct { - const uint8_t* ptr; - uint16_t length; - } fixed; - struct { - cbFlipperGattCharacteristicData fn; - const void* context; - } callback; - } data; - Char_UUID_t uuid; - // Some packed bitfields to save space - FlipperGattCharacteristicDataType data_prop_type : 2; - uint8_t is_variable : 2; - uint8_t uuid_type : 2; - uint8_t char_properties; - uint8_t security_permissions; - uint8_t gatt_evt_mask; -} FlipperGattCharacteristicParams; - -_Static_assert( - sizeof(FlipperGattCharacteristicParams) == 36, - "FlipperGattCharacteristicParams size must be 36 bytes"); - -typedef struct { - const FlipperGattCharacteristicParams* characteristic; - uint16_t handle; - uint16_t descriptor_handle; -} FlipperGattCharacteristicInstance; - -// Initialize a characteristic instance; copies the characteristic descriptor into the instance -void flipper_gatt_characteristic_init( - uint16_t svc_handle, - const FlipperGattCharacteristicParams* char_descriptor, - FlipperGattCharacteristicInstance* char_instance); - -// Delete a characteristic instance; frees the copied characteristic descriptor from the instance -void flipper_gatt_characteristic_delete( - uint16_t svc_handle, - FlipperGattCharacteristicInstance* char_instance); - -// Update a characteristic instance; if source==NULL, uses the data from the characteristic -// - For fixed data, fixed.ptr is used as the source if source==NULL -// - For callback-based data, collback.context is passed as the context if source==NULL -bool flipper_gatt_characteristic_update( - uint16_t svc_handle, - FlipperGattCharacteristicInstance* char_instance, - const void* source); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/targets/f7/ble_glue/services/hid_service.h b/targets/f7/ble_glue/services/hid_service.h deleted file mode 100644 index 211adcd6c4..0000000000 --- a/targets/f7/ble_glue/services/hid_service.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include - -#define HID_SVC_REPORT_MAP_MAX_LEN (255) -#define HID_SVC_REPORT_MAX_LEN (255) -#define HID_SVC_REPORT_REF_LEN (2) -#define HID_SVC_INFO_LEN (4) -#define HID_SVC_CONTROL_POINT_LEN (1) - -#define HID_SVC_INPUT_REPORT_COUNT (3) -#define HID_SVC_OUTPUT_REPORT_COUNT (0) -#define HID_SVC_FEATURE_REPORT_COUNT (0) -#define HID_SVC_REPORT_COUNT \ - (HID_SVC_INPUT_REPORT_COUNT + HID_SVC_OUTPUT_REPORT_COUNT + HID_SVC_FEATURE_REPORT_COUNT) - -void hid_svc_start(); - -void hid_svc_stop(); - -bool hid_svc_is_started(); - -bool hid_svc_update_report_map(const uint8_t* data, uint16_t len); - -bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len); - -// Expects data to be of length HID_SVC_INFO_LEN (4 bytes) -bool hid_svc_update_info(uint8_t* data); diff --git a/targets/f7/ble_glue/services/serial_service.c b/targets/f7/ble_glue/services/serial_service.c index 0db25b3d3a..a8f10e6d7b 100644 --- a/targets/f7/ble_glue/services/serial_service.c +++ b/targets/f7/ble_glue/services/serial_service.c @@ -1,11 +1,13 @@ #include "serial_service.h" #include "app_common.h" #include -#include "gatt_char.h" +#include +#include #include #include "serial_service_uuid.inc" +#include #define TAG "BtSerialSvc" @@ -17,12 +19,12 @@ typedef enum { SerialSvcGattCharacteristicCount, } SerialSvcGattCharacteristicId; -static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattCharacteristicCount] = { +static const BleGattCharacteristicParams ble_svc_serial_chars[SerialSvcGattCharacteristicCount] = { [SerialSvcGattCharacteristicRx] = {.name = "RX", .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = SERIAL_SVC_DATA_LEN_MAX, - .uuid.Char_UUID_128 = SERIAL_SVC_RX_CHAR_UUID, + .data.fixed.length = BLE_SVC_SERIAL_DATA_LEN_MAX, + .uuid.Char_UUID_128 = BLE_SVC_SERIAL_RX_CHAR_UUID, .uuid_type = UUID_TYPE_128, .char_properties = CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE | CHAR_PROP_READ, .security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE, @@ -31,8 +33,8 @@ static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattChara [SerialSvcGattCharacteristicTx] = {.name = "TX", .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = SERIAL_SVC_DATA_LEN_MAX, - .uuid.Char_UUID_128 = SERIAL_SVC_TX_CHAR_UUID, + .data.fixed.length = BLE_SVC_SERIAL_DATA_LEN_MAX, + .uuid.Char_UUID_128 = BLE_SVC_SERIAL_TX_CHAR_UUID, .uuid_type = UUID_TYPE_128, .char_properties = CHAR_PROP_READ | CHAR_PROP_INDICATE, .security_permissions = ATTR_PERMISSION_AUTHEN_READ, @@ -42,7 +44,7 @@ static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattChara {.name = "Flow control", .data_prop_type = FlipperGattCharacteristicDataFixed, .data.fixed.length = sizeof(uint32_t), - .uuid.Char_UUID_128 = SERIAL_SVC_FLOW_CONTROL_UUID, + .uuid.Char_UUID_128 = BLE_SVC_SERIAL_FLOW_CONTROL_UUID, .uuid_type = UUID_TYPE_128, .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, .security_permissions = ATTR_PERMISSION_AUTHEN_READ, @@ -51,28 +53,28 @@ static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattChara [SerialSvcGattCharacteristicStatus] = { .name = "RPC status", .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = sizeof(SerialServiceRpcStatus), - .uuid.Char_UUID_128 = SERIAL_SVC_RPC_STATUS_UUID, + .data.fixed.length = sizeof(uint32_t), + .uuid.Char_UUID_128 = BLE_SVC_SERIAL_RPC_STATUS_UUID, .uuid_type = UUID_TYPE_128, .char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE | CHAR_PROP_NOTIFY, .security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE, .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, .is_variable = CHAR_VALUE_LEN_CONSTANT}}; -typedef struct { +struct BleServiceSerial { uint16_t svc_handle; - FlipperGattCharacteristicInstance chars[SerialSvcGattCharacteristicCount]; + BleGattCharacteristicInstance chars[SerialSvcGattCharacteristicCount]; FuriMutex* buff_size_mtx; uint32_t buff_size; uint16_t bytes_ready_to_receive; SerialServiceEventCallback callback; void* context; -} SerialSvc; + GapSvcEventHandler* event_handler; +}; -static SerialSvc* serial_svc = NULL; - -static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { - SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; +static BleEventAckStatus ble_svc_serial_event_handler(void* event, void* context) { + BleServiceSerial* serial_svc = (BleServiceSerial*)context; + BleEventAckStatus ret = BleEventNotAck; 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; @@ -82,7 +84,7 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { if(attribute_modified->Attr_Handle == serial_svc->chars[SerialSvcGattCharacteristicRx].handle + 2) { // Descriptor handle - ret = SVCCTL_EvtAckFlowEnable; + ret = BleEventAckFlowEnable; FURI_LOG_D(TAG, "RX descriptor event"); } else if( attribute_modified->Attr_Handle == @@ -111,13 +113,12 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { FURI_LOG_D(TAG, "Available buff size: %ld", buff_free_size); furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk); } - ret = SVCCTL_EvtAckFlowEnable; + ret = BleEventAckFlowEnable; } else if( attribute_modified->Attr_Handle == serial_svc->chars[SerialSvcGattCharacteristicStatus].handle + 1) { - SerialServiceRpcStatus* rpc_status = - (SerialServiceRpcStatus*)attribute_modified->Attr_Data; - if(*rpc_status == SerialServiceRpcStatusNotActive) { + bool* rpc_status = (bool*)attribute_modified->Attr_Data; + if(!*rpc_status) { if(serial_svc->callback) { SerialServiceEvent event = { .event = SerialServiceEventTypesBleResetRequest, @@ -134,43 +135,47 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { }; serial_svc->callback(event, serial_svc->context); } - ret = SVCCTL_EvtAckFlowEnable; + ret = BleEventAckFlowEnable; } } return ret; } -static void serial_svc_update_rpc_char(SerialServiceRpcStatus status) { - flipper_gatt_characteristic_update( +typedef enum { + SerialServiceRpcStatusNotActive = 0UL, + SerialServiceRpcStatusActive = 1UL, +} SerialServiceRpcStatus; + +static void + ble_svc_serial_update_rpc_char(BleServiceSerial* serial_svc, SerialServiceRpcStatus status) { + ble_gatt_characteristic_update( serial_svc->svc_handle, &serial_svc->chars[SerialSvcGattCharacteristicStatus], &status); } -void serial_svc_start() { - UNUSED(serial_svc_chars); - tBleStatus status; - serial_svc = malloc(sizeof(SerialSvc)); - // Register event handler - SVCCTL_RegisterSvcHandler(serial_svc_event_handler); +BleServiceSerial* ble_svc_serial_start(void) { + BleServiceSerial* serial_svc = malloc(sizeof(BleServiceSerial)); - // Add service - status = aci_gatt_add_service( - UUID_TYPE_128, &service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add Serial service: %d", status); - } + serial_svc->event_handler = + ble_event_dispatcher_register_svc_handler(ble_svc_serial_event_handler, serial_svc); - // Add characteristics + if(!ble_gatt_service_add( + UUID_TYPE_128, &service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle)) { + free(serial_svc); + return NULL; + } for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_init( - serial_svc->svc_handle, &serial_svc_chars[i], &serial_svc->chars[i]); + ble_gatt_characteristic_init( + serial_svc->svc_handle, &ble_svc_serial_chars[i], &serial_svc->chars[i]); } - serial_svc_update_rpc_char(SerialServiceRpcStatusNotActive); - // Allocate buffer size mutex + ble_svc_serial_update_rpc_char(serial_svc, SerialServiceRpcStatusNotActive); serial_svc->buff_size_mtx = furi_mutex_alloc(FuriMutexTypeNormal); + + return serial_svc; } -void serial_svc_set_callbacks( +void ble_svc_serial_set_callbacks( + BleServiceSerial* serial_svc, uint16_t buff_size, SerialServiceEventCallback callback, void* context) { @@ -181,13 +186,13 @@ void serial_svc_set_callbacks( serial_svc->bytes_ready_to_receive = buff_size; uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size); - flipper_gatt_characteristic_update( + ble_gatt_characteristic_update( serial_svc->svc_handle, &serial_svc->chars[SerialSvcGattCharacteristicFlowCtrl], &buff_size_reversed); } -void serial_svc_notify_buffer_is_empty() { +void ble_svc_serial_notify_buffer_is_empty(BleServiceSerial* serial_svc) { furi_assert(serial_svc); furi_assert(serial_svc->buff_size_mtx); @@ -197,7 +202,7 @@ void serial_svc_notify_buffer_is_empty() { serial_svc->bytes_ready_to_receive = serial_svc->buff_size; uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size); - flipper_gatt_characteristic_update( + ble_gatt_characteristic_update( serial_svc->svc_handle, &serial_svc->chars[SerialSvcGattCharacteristicFlowCtrl], &buff_size_reversed); @@ -205,35 +210,26 @@ void serial_svc_notify_buffer_is_empty() { furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk); } -void serial_svc_stop() { - tBleStatus status; - if(serial_svc) { - for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_delete(serial_svc->svc_handle, &serial_svc->chars[i]); - } - // Delete service - status = aci_gatt_del_service(serial_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Serial service: %d", status); - } - // Delete buffer size mutex - furi_mutex_free(serial_svc->buff_size_mtx); - free(serial_svc); - serial_svc = NULL; - } -} +void ble_svc_serial_stop(BleServiceSerial* serial_svc) { + furi_check(serial_svc); -bool serial_svc_is_started() { - return serial_svc != NULL; + ble_event_dispatcher_unregister_svc_handler(serial_svc->event_handler); + + for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_delete(serial_svc->svc_handle, &serial_svc->chars[i]); + } + ble_gatt_service_delete(serial_svc->svc_handle); + furi_mutex_free(serial_svc->buff_size_mtx); + free(serial_svc); } -bool serial_svc_update_tx(uint8_t* data, uint16_t data_len) { - if(data_len > SERIAL_SVC_DATA_LEN_MAX) { +bool ble_svc_serial_update_tx(BleServiceSerial* serial_svc, uint8_t* data, uint16_t data_len) { + if(data_len > BLE_SVC_SERIAL_DATA_LEN_MAX) { return false; } for(uint16_t remained = data_len; remained > 0;) { - uint8_t value_len = MIN(SERIAL_SVC_CHAR_VALUE_LEN_MAX, remained); + uint8_t value_len = MIN(BLE_SVC_SERIAL_CHAR_VALUE_LEN_MAX, remained); uint16_t value_offset = data_len - remained; remained -= value_len; @@ -256,7 +252,8 @@ bool serial_svc_update_tx(uint8_t* data, uint16_t data_len) { return true; } -void serial_svc_set_rpc_status(SerialServiceRpcStatus status) { +void ble_svc_serial_set_rpc_active(BleServiceSerial* serial_svc, bool active) { furi_assert(serial_svc); - serial_svc_update_rpc_char(status); + ble_svc_serial_update_rpc_char( + serial_svc, active ? SerialServiceRpcStatusActive : SerialServiceRpcStatusNotActive); } diff --git a/targets/f7/ble_glue/services/serial_service.h b/targets/f7/ble_glue/services/serial_service.h index 7d38066f47..91ad886e06 100644 --- a/targets/f7/ble_glue/services/serial_service.h +++ b/targets/f7/ble_glue/services/serial_service.h @@ -3,17 +3,16 @@ #include #include -#define SERIAL_SVC_DATA_LEN_MAX (486) -#define SERIAL_SVC_CHAR_VALUE_LEN_MAX (243) - #ifdef __cplusplus extern "C" { #endif -typedef enum { - SerialServiceRpcStatusNotActive = 0UL, - SerialServiceRpcStatusActive = 1UL, -} SerialServiceRpcStatus; +/* + * Serial service. Implements RPC over BLE, with flow control. + */ + +#define BLE_SVC_SERIAL_DATA_LEN_MAX (486) +#define BLE_SVC_SERIAL_CHAR_VALUE_LEN_MAX (243) typedef enum { SerialServiceEventTypeDataReceived, @@ -33,22 +32,23 @@ typedef struct { typedef uint16_t (*SerialServiceEventCallback)(SerialServiceEvent event, void* context); -void serial_svc_start(); +typedef struct BleServiceSerial BleServiceSerial; + +BleServiceSerial* ble_svc_serial_start(void); -void serial_svc_set_callbacks( +void ble_svc_serial_stop(BleServiceSerial* service); + +void ble_svc_serial_set_callbacks( + BleServiceSerial* service, uint16_t buff_size, SerialServiceEventCallback callback, void* context); -void serial_svc_set_rpc_status(SerialServiceRpcStatus status); - -void serial_svc_notify_buffer_is_empty(); - -void serial_svc_stop(); +void ble_svc_serial_set_rpc_active(BleServiceSerial* service, bool active); -bool serial_svc_is_started(); +void ble_svc_serial_notify_buffer_is_empty(BleServiceSerial* service); -bool serial_svc_update_tx(uint8_t* data, uint16_t data_len); +bool ble_svc_serial_update_tx(BleServiceSerial* service, uint8_t* data, uint16_t data_len); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/services/serial_service_uuid.inc b/targets/f7/ble_glue/services/serial_service_uuid.inc index a297d9ad60..577e8f2eda 100644 --- a/targets/f7/ble_glue/services/serial_service_uuid.inc +++ b/targets/f7/ble_glue/services/serial_service_uuid.inc @@ -2,11 +2,11 @@ static const Service_UUID_t service_uuid = { .Service_UUID_128 = \ { 0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f }}; -#define SERIAL_SVC_TX_CHAR_UUID \ +#define BLE_SVC_SERIAL_TX_CHAR_UUID \ { 0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } -#define SERIAL_SVC_RX_CHAR_UUID \ +#define BLE_SVC_SERIAL_RX_CHAR_UUID \ { 0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } -#define SERIAL_SVC_FLOW_CONTROL_UUID \ +#define BLE_SVC_SERIAL_FLOW_CONTROL_UUID \ { 0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } -#define SERIAL_SVC_RPC_STATUS_UUID \ +#define BLE_SVC_SERIAL_RPC_STATUS_UUID \ { 0x00, 0x00, 0xfe, 0x64, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } diff --git a/targets/f7/furi_hal/furi_hal_bt.c b/targets/f7/furi_hal/furi_hal_bt.c index 48bce998ee..c276b5cf4e 100644 --- a/targets/f7/furi_hal/furi_hal_bt.c +++ b/targets/f7/furi_hal/furi_hal_bt.c @@ -1,8 +1,13 @@ +#include "ble_glue.h" +#include +#include #include +#include #include #include +#include #include #include @@ -10,97 +15,30 @@ #include #include -#include -#include #include #include #include #define TAG "FuriHalBt" -#define FURI_HAL_BT_DEFAULT_MAC_ADDR \ +#define furi_hal_bt_DEFAULT_MAC_ADDR \ { 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 } /* Time, in ms, to wait for mode transition before crashing */ #define C2_MODE_SWITCH_TIMEOUT 10000 -#define FURI_HAL_BT_HARDFAULT_INFO_MAGIC 0x1170FD0F - typedef struct { FuriMutex* core2_mtx; - FuriTimer* hardfault_check_timer; FuriHalBtStack stack; } FuriHalBt; static FuriHalBt furi_hal_bt = { .core2_mtx = NULL, - .hardfault_check_timer = NULL, .stack = FuriHalBtStackUnknown, }; -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, - .conn_param = - { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms - .slave_latency = 0, - .supervisor_timeout = 0, - }, - }, - }, - [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, - .conn_param = - { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms - .slave_latency = 0, - .supervisor_timeout = 0, - }, - }, - }, -}; -FuriHalBtProfileConfig* current_profile = NULL; - -static void furi_hal_bt_hardfault_check(void* context) { - UNUSED(context); - if(furi_hal_bt_get_hardfault_info()) { - furi_crash("ST(R) Copro(R) HardFault"); - } -} - void furi_hal_bt_init() { + FURI_LOG_I(TAG, "Start BT initialization"); furi_hal_bus_enable(FuriHalBusHSEM); furi_hal_bus_enable(FuriHalBusIPCC); furi_hal_bus_enable(FuriHalBusAES2); @@ -112,12 +50,6 @@ void furi_hal_bt_init() { furi_assert(furi_hal_bt.core2_mtx); } - if(!furi_hal_bt.hardfault_check_timer) { - furi_hal_bt.hardfault_check_timer = - furi_timer_alloc(furi_hal_bt_hardfault_check, FuriTimerTypePeriodic, NULL); - furi_timer_start(furi_hal_bt.hardfault_check_timer, 5000); - } - // Explicitly tell that we are in charge of CLK48 domain furi_check(LL_HSEM_1StepLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID) == 0); @@ -168,7 +100,6 @@ bool furi_hal_bt_start_radio_stack() { // Wait until C2 is started or timeout if(!ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)) { FURI_LOG_E(TAG, "Core2 start failed"); - ble_glue_thread_stop(); break; } @@ -187,14 +118,15 @@ bool furi_hal_bt_start_radio_stack() { // Starting radio stack if(!ble_glue_start()) { FURI_LOG_E(TAG, "Failed to start radio stack"); - ble_glue_thread_stop(); - ble_app_thread_stop(); + ble_app_deinit(); + ble_glue_stop(); break; } res = true; } while(false); furi_mutex_release(furi_hal_bt.core2_mtx); + gap_extra_beacon_init(); return res; } @@ -202,7 +134,7 @@ FuriHalBtStack furi_hal_bt_get_radio_stack() { return furi_hal_bt.stack; } -bool furi_hal_bt_is_ble_gatt_gap_supported() { +bool furi_hal_bt_is_gatt_gap_supported() { if(furi_hal_bt.stack == FuriHalBtStackLight || furi_hal_bt.stack == FuriHalBtStackFull) { return true; } else { @@ -218,55 +150,52 @@ bool furi_hal_bt_is_testing_supported() { } } -bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { +static FuriHalBleProfileBase* current_profile = NULL; +static GapConfig current_config = {0}; + +bool furi_hal_bt_check_profile_type( + FuriHalBleProfileBase* profile, + const FuriHalBleProfileTemplate* profile_template) { + if(!profile || !profile_template) { + return false; + } + + return profile->config == profile_template; +} + +FuriHalBleProfileBase* furi_hal_bt_start_app( + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams params, + GapEventCallback event_cb, + void* context) { furi_assert(event_cb); - furi_assert(profile < FuriHalBtProfileNumber); - bool ret = false; + furi_check(profile_template); + furi_check(current_profile == NULL); do { if(!ble_glue_is_radio_stack_ready()) { FURI_LOG_E(TAG, "Can't start BLE App - radio stack did not start"); break; } - if(!furi_hal_bt_is_ble_gatt_gap_supported()) { + if(!furi_hal_bt_is_gatt_gap_supported()) { FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack"); 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 -> Control - const char* clicker_str = "Control"; - memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); - } - if(!gap_init(config, event_cb, context)) { + + profile_template->get_gap_config(¤t_config, params); + + if(!gap_init(¤t_config, event_cb, context)) { gap_thread_stop(); FURI_LOG_E(TAG, "Failed to init GAP"); break; } // Start selected profile services - if(furi_hal_bt_is_ble_gatt_gap_supported()) { - profile_config[profile].start(); + if(furi_hal_bt_is_gatt_gap_supported()) { + current_profile = profile_template->start(params); } - ret = true; } while(false); - current_profile = &profile_config[profile]; - return ret; + return current_profile; } void furi_hal_bt_reinit() { @@ -274,21 +203,25 @@ void furi_hal_bt_reinit() { FURI_LOG_I(TAG, "Disconnect and stop advertising"); furi_hal_bt_stop_advertising(); - FURI_LOG_I(TAG, "Stop current profile services"); - current_profile->stop(); + if(current_profile) { + FURI_LOG_I(TAG, "Stop current profile services"); + current_profile->config->stop(current_profile); + current_profile = NULL; + } // Magic happens here hci_reset(); FURI_LOG_I(TAG, "Stop BLE related RTOS threads"); - ble_app_thread_stop(); gap_thread_stop(); + ble_app_deinit(); FURI_LOG_I(TAG, "Reset SHCI"); furi_check(ble_glue_reinit_c2()); + ble_glue_stop(); + // enterprise delay furi_delay_ms(100); - ble_glue_thread_stop(); furi_hal_bus_disable(FuriHalBusHSEM); furi_hal_bus_disable(FuriHalBusIPCC); @@ -296,25 +229,20 @@ void furi_hal_bt_reinit() { furi_hal_bus_disable(FuriHalBusPKA); furi_hal_bus_disable(FuriHalBusCRC); - FURI_LOG_I(TAG, "Start BT initialization"); furi_hal_bt_init(); - furi_hal_bt_start_radio_stack(); furi_hal_power_insomnia_exit(); } -bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { +FuriHalBleProfileBase* furi_hal_bt_change_app( + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams profile_params, + GapEventCallback event_cb, + void* context) { furi_assert(event_cb); - furi_assert(profile < FuriHalBtProfileNumber); - bool ret = true; furi_hal_bt_reinit(); - - ret = furi_hal_bt_start_app(profile, event_cb, context); - if(ret) { - current_profile = &profile_config[profile]; - } - return ret; + return furi_hal_bt_start_app(profile_template, profile_params, event_cb, context); } bool furi_hal_bt_is_active() { @@ -337,15 +265,11 @@ void furi_hal_bt_stop_advertising() { } void furi_hal_bt_update_battery_level(uint8_t battery_level) { - if(battery_svc_is_started()) { - battery_svc_update_level(battery_level); - } + ble_svc_battery_state_update(&battery_level, NULL); } -void furi_hal_bt_update_power_state() { - if(battery_svc_is_started()) { - battery_svc_update_power_state(); - } +void furi_hal_bt_update_power_state(bool charging) { + ble_svc_battery_state_update(NULL, &charging); } void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size) { @@ -484,11 +408,30 @@ bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) { return false; } -const FuriHalBtHardfaultInfo* furi_hal_bt_get_hardfault_info() { - /* AN5289, 4.8.2 */ - const FuriHalBtHardfaultInfo* info = (FuriHalBtHardfaultInfo*)(SRAM2A_BASE); - if(info->magic != FURI_HAL_BT_HARDFAULT_INFO_MAGIC) { - return NULL; - } - return info; +bool furi_hal_bt_extra_beacon_set_data(const uint8_t* data, uint8_t len) { + return gap_extra_beacon_set_data(data, len); +} + +uint8_t furi_hal_bt_extra_beacon_get_data(uint8_t* data) { + return gap_extra_beacon_get_data(data); +} + +bool furi_hal_bt_extra_beacon_set_config(const GapExtraBeaconConfig* config) { + return gap_extra_beacon_set_config(config); +} + +const GapExtraBeaconConfig* furi_hal_bt_extra_beacon_get_config() { + return gap_extra_beacon_get_config(); +} + +bool furi_hal_bt_extra_beacon_start() { + return gap_extra_beacon_start(); +} + +bool furi_hal_bt_extra_beacon_stop() { + return gap_extra_beacon_stop(); +} + +bool furi_hal_bt_extra_beacon_is_active() { + return gap_extra_beacon_get_state() == GapExtraBeaconStateStarted; } diff --git a/targets/f7/furi_hal/furi_hal_bt_hid.c b/targets/f7/furi_hal/furi_hal_bt_hid.c deleted file mode 100644 index 7ec712af4d..0000000000 --- a/targets/f7/furi_hal/furi_hal_bt_hid.c +++ /dev/null @@ -1,289 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#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_MAX_KEYS 6 -#define FURI_HAL_BT_HID_CONSUMER_MAX_KEYS 1 - -// Report ids cant be 0 -enum HidReportId { - ReportIdKeyboard = 1, - ReportIdMouse = 2, - ReportIdConsumer = 3, -}; -// Report numbers corresponded to the report id with an offset of 1 -enum HidInputNumber { - ReportNumberKeyboard = 0, - ReportNumberMouse = 1, - ReportNumberConsumer = 2, -}; - -typedef struct { - uint8_t mods; - uint8_t reserved; - uint8_t key[FURI_HAL_BT_HID_KB_MAX_KEYS]; -} __attribute__((__packed__)) FuriHalBtHidKbReport; - -typedef struct { - uint8_t btn; - int8_t x; - int8_t y; - int8_t wheel; -} __attribute__((__packed__)) FuriHalBtHidMouseReport; - -typedef struct { - uint16_t key[FURI_HAL_BT_HID_CONSUMER_MAX_KEYS]; -} __attribute__((__packed__)) FuriHalBtHidConsumerReport; - -// keyboard+mouse+consumer hid report -static const uint8_t furi_hal_bt_hid_report_map_data[] = { - // Keyboard Report - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_KEYBOARD), - HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_REPORT_ID(ReportIdKeyboard), - HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), - HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), - HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(8), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_COUNT(1), - HID_REPORT_SIZE(8), - HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_USAGE_PAGE(HID_PAGE_LED), - HID_REPORT_COUNT(8), - HID_REPORT_SIZE(1), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(8), - HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_COUNT(FURI_HAL_BT_HID_KB_MAX_KEYS), - HID_REPORT_SIZE(8), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(101), - HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), - HID_USAGE_MINIMUM(0), - HID_USAGE_MAXIMUM(101), - HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), - HID_END_COLLECTION, - // Mouse Report - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_MOUSE), - HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_USAGE(HID_DESKTOP_POINTER), - HID_COLLECTION(HID_PHYSICAL_COLLECTION), - HID_REPORT_ID(ReportIdMouse), - HID_USAGE_PAGE(HID_PAGE_BUTTON), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(3), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_COUNT(3), - HID_REPORT_SIZE(1), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(5), - HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_X), - HID_USAGE(HID_DESKTOP_Y), - HID_USAGE(HID_DESKTOP_WHEEL), - HID_LOGICAL_MINIMUM(-127), - HID_LOGICAL_MAXIMUM(127), - HID_REPORT_SIZE(8), - HID_REPORT_COUNT(3), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), - HID_END_COLLECTION, - HID_END_COLLECTION, - // Consumer Report - HID_USAGE_PAGE(HID_PAGE_CONSUMER), - HID_USAGE(HID_CONSUMER_CONTROL), - HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_REPORT_ID(ReportIdConsumer), - HID_LOGICAL_MINIMUM(0), - HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), - HID_USAGE_MINIMUM(0), - HID_RI_USAGE_MAXIMUM(16, 0x3FF), - HID_REPORT_COUNT(FURI_HAL_BT_HID_CONSUMER_MAX_KEYS), - HID_REPORT_SIZE(16), - HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), - HID_END_COLLECTION, -}; -FuriHalBtHidKbReport* kb_report = NULL; -FuriHalBtHidMouseReport* mouse_report = NULL; -FuriHalBtHidConsumerReport* consumer_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 = malloc(sizeof(FuriHalBtHidKbReport)); - mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); - consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); - // 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); -} - -void furi_hal_bt_hid_stop() { - furi_assert(kb_report); - furi_assert(mouse_report); - furi_assert(consumer_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); - free(mouse_report); - free(consumer_report); - kb_report = NULL; - mouse_report = NULL; - consumer_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_MAX_KEYS; 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( - ReportNumberKeyboard, (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_MAX_KEYS; 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( - ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); -} - -bool furi_hal_bt_hid_kb_release_all() { - furi_assert(kb_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { - kb_report->key[i] = 0; - } - kb_report->mods = 0; - return hid_svc_update_input_report( - ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); -} - -bool furi_hal_bt_hid_consumer_key_press(uint16_t button) { - furi_assert(consumer_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008 - if(consumer_report->key[i] == 0) { - consumer_report->key[i] = button; - break; - } - } - return hid_svc_update_input_report( - ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); -} - -bool furi_hal_bt_hid_consumer_key_release(uint16_t button) { - furi_assert(consumer_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008 - if(consumer_report->key[i] == button) { - consumer_report->key[i] = 0; - break; - } - } - return hid_svc_update_input_report( - ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); -} - -bool furi_hal_bt_hid_consumer_key_release_all() { - furi_assert(consumer_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008 - consumer_report->key[i] = 0; - } - return hid_svc_update_input_report( - ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); -} - -bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy) { - furi_assert(mouse_report); - mouse_report->x = dx; - mouse_report->y = dy; - bool state = hid_svc_update_input_report( - ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); - mouse_report->x = 0; - mouse_report->y = 0; - return state; -} - -bool furi_hal_bt_hid_mouse_press(uint8_t button) { - furi_assert(mouse_report); - mouse_report->btn |= button; - return hid_svc_update_input_report( - ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); -} - -bool furi_hal_bt_hid_mouse_release(uint8_t button) { - furi_assert(mouse_report); - mouse_report->btn &= ~button; - return hid_svc_update_input_report( - ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); -} - -bool furi_hal_bt_hid_mouse_release_all() { - furi_assert(mouse_report); - mouse_report->btn = 0; - return hid_svc_update_input_report( - ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); -} - -bool furi_hal_bt_hid_mouse_scroll(int8_t delta) { - furi_assert(mouse_report); - mouse_report->wheel = delta; - bool state = hid_svc_update_input_report( - ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); - mouse_report->wheel = 0; - return state; -} diff --git a/targets/f7/furi_hal/furi_hal_bt_serial.c b/targets/f7/furi_hal/furi_hal_bt_serial.c deleted file mode 100644 index 2927d946f9..0000000000 --- a/targets/f7/furi_hal/furi_hal_bt_serial.c +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include -#include - -#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(); -} - -void furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatus status) { - SerialServiceRpcStatus st; - if(status == FuriHalBtSerialRpcStatusActive) { - st = SerialServiceRpcStatusActive; - } else { - st = SerialServiceRpcStatusNotActive; - } - serial_svc_set_rpc_status(st); -} - -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/targets/f7/furi_hal/furi_hal_cortex.c b/targets/f7/furi_hal/furi_hal_cortex.c index 6b5efc376c..9865e6ef89 100644 --- a/targets/f7/furi_hal/furi_hal_cortex.c +++ b/targets/f7/furi_hal/furi_hal_cortex.c @@ -28,7 +28,7 @@ uint32_t furi_hal_cortex_instructions_per_microsecond() { return FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND; } -FuriHalCortexTimer furi_hal_cortex_timer_get(uint32_t timeout_us) { +FURI_WARN_UNUSED FuriHalCortexTimer furi_hal_cortex_timer_get(uint32_t timeout_us) { furi_check(timeout_us < (UINT32_MAX / FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND)); FuriHalCortexTimer cortex_timer = {0}; diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 3bc57f8f3b..6a68bbae73 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -28,7 +28,15 @@ /* Heap size determined automatically by linker */ // #define configTOTAL_HEAP_SIZE ((size_t)0) #define configMAX_TASK_NAME_LEN (32) -#define configGENERATE_RUN_TIME_STATS 0 + +/* Run-time stats - broken ATM, to be fixed */ +/* +#define configGENERATE_RUN_TIME_STATS 1 +#define configRUN_TIME_COUNTER_TYPE uint64_t +#define portGET_RUN_TIME_COUNTER_VALUE() (DWT->CYCCNT) +#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() +*/ + #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 #define configUSE_MUTEXES 1 diff --git a/targets/f7/target.json b/targets/f7/target.json index 0f0161fe4e..25872198bf 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -8,7 +8,10 @@ "sdk_header_paths": [ "../furi_hal_include", "furi_hal", - "platform_specific" + "platform_specific", + "ble_glue/furi_ble", + "ble_glue/services", + "ble_glue/profiles" ], "startup_script": "startup_stm32wb55xx_cm4.s", "linker_script_flash": "stm32wb55xx_flash.ld", diff --git a/targets/furi_hal_include/furi_hal_bt.h b/targets/furi_hal_include/furi_hal_bt.h index 4d538265db..939c9a30dd 100644 --- a/targets/furi_hal_include/furi_hal_bt.h +++ b/targets/furi_hal_include/furi_hal_bt.h @@ -8,15 +8,15 @@ #include #include #include -#include +#include +#include #include #include - -#include +#include #define FURI_HAL_BT_STACK_VERSION_MAJOR (1) #define FURI_HAL_BT_STACK_VERSION_MINOR (12) -#define FURI_HAL_BT_C2_START_TIMEOUT 1000 +#define FURI_HAL_BT_C2_START_TIMEOUT (1000) #ifdef __cplusplus extern "C" { @@ -28,14 +28,6 @@ typedef enum { FuriHalBtStackFull, } FuriHalBtStack; -typedef enum { - FuriHalBtProfileSerial, - FuriHalBtProfileHidKeyboard, - - // Keep last for Profiles number calculation - FuriHalBtProfileNumber, -} FuriHalBtProfile; - /** Initialize */ void furi_hal_bt_init(); @@ -62,7 +54,7 @@ FuriHalBtStack furi_hal_bt_get_radio_stack(); * * @return true if supported */ -bool furi_hal_bt_is_ble_gatt_gap_supported(); +bool furi_hal_bt_is_gatt_gap_supported(); /** Check if radio stack supports testing * @@ -70,15 +62,31 @@ bool furi_hal_bt_is_ble_gatt_gap_supported(); */ bool furi_hal_bt_is_testing_supported(); -/** Start BLE app +/** Check if particular instance of profile belongs to given type * - * @param profile FuriHalBtProfile instance - * @param event_cb GapEventCallback instance - * @param context pointer to context + * @param profile FuriHalBtProfile instance. If NULL, uses current profile + * @param profile_template basic profile template to check against * * @return true on success */ -bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context); +bool furi_hal_bt_check_profile_type( + FuriHalBleProfileBase* profile, + const FuriHalBleProfileTemplate* profile_template); + +/** Start BLE app + * + * @param profile_template FuriHalBleProfileTemplate instance + * @param params Parameters to pass to the profile. Can be NULL + * @param event_cb GapEventCallback instance + * @param context pointer to context + * + * @return instance of profile, NULL on failure +*/ +FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_start_app( + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams params, + GapEventCallback event_cb, + void* context); /** Reinitialize core2 * @@ -89,13 +97,17 @@ void furi_hal_bt_reinit(); /** Change BLE app * Restarts 2nd core * - * @param profile FuriHalBtProfile instance + * @param profile FuriHalBleProfileTemplate instance * @param event_cb GapEventCallback instance * @param context pointer to context * - * @return true on success + * @return instance of profile, NULL on failure */ -bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context); +FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_change_app( + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams profile_params, + GapEventCallback event_cb, + void* context); /** Update battery level * @@ -104,7 +116,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void furi_hal_bt_update_battery_level(uint8_t battery_level); /** Update battery power state */ -void furi_hal_bt_update_power_state(); +void furi_hal_bt_update_power_state(bool charging); /** Checks if BLE state is active * @@ -224,18 +236,60 @@ uint32_t furi_hal_bt_get_transmitted_packets(); */ bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode); -typedef struct { - uint32_t magic; - uint32_t source_pc; - uint32_t source_lr; - uint32_t source_sp; -} FuriHalBtHardfaultInfo; +/** + * Extra BLE beacon API + */ + +/** Set extra beacon data. Can be called in any state + * + * @param[in] data data to set + * @param[in] len data length. Must be <= EXTRA_BEACON_MAX_DATA_SIZE + * + * @return true on success + */ +bool furi_hal_bt_extra_beacon_set_data(const uint8_t* data, uint8_t len); + +/** Get last configured extra beacon data + * + * @param data data buffer to write to. Must be at least EXTRA_BEACON_MAX_DATA_SIZE bytes long + * + * @return valid data length + */ +uint8_t furi_hal_bt_extra_beacon_get_data(uint8_t* data); + +/** Configure extra beacon. + * + * @param[in] config extra beacon config: interval, power, address, etc. + * + * @return true on success + */ +bool furi_hal_bt_extra_beacon_set_config(const GapExtraBeaconConfig* config); + +/** Start extra beacon. + * Beacon must configured with furi_hal_bt_extra_beacon_set_config() + * and in stopped state before calling this function. + * + * @return true on success + */ +bool furi_hal_bt_extra_beacon_start(); + +/** Stop extra beacon + * + * @return true on success + */ +bool furi_hal_bt_extra_beacon_stop(); + +/** Check if extra beacon is active. + * + * @return extra beacon state + */ +bool furi_hal_bt_extra_beacon_is_active(); -/** Get hardfault info +/** Get last configured extra beacon config * - * @return hardfault info. NULL if no hardfault + * @return extra beacon config. NULL if beacon had never been configured. */ -const FuriHalBtHardfaultInfo* furi_hal_bt_get_hardfault_info(); +const GapExtraBeaconConfig* furi_hal_bt_extra_beacon_get_config(); #ifdef __cplusplus } diff --git a/targets/furi_hal_include/furi_hal_bt_hid.h b/targets/furi_hal_include/furi_hal_bt_hid.h deleted file mode 100644 index 4e74bbda75..0000000000 --- a/targets/furi_hal_include/furi_hal_bt_hid.h +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** Start Hid Keyboard Profile - */ -void furi_hal_bt_hid_start(); - -/** Stop Hid Keyboard Profile - */ -void furi_hal_bt_hid_stop(); - -/** Press keyboard button - * - * @param button button code from HID specification - * - * @return true on success - */ -bool furi_hal_bt_hid_kb_press(uint16_t button); - -/** Release keyboard button - * - * @param button button code from HID specification - * - * @return true on success - */ -bool furi_hal_bt_hid_kb_release(uint16_t button); - -/** Release all keyboard buttons - * - * @return true on success - */ -bool furi_hal_bt_hid_kb_release_all(); - -/** Set mouse movement and send HID report - * - * @param dx x coordinate delta - * @param dy y coordinate delta - */ -bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy); - -/** Set mouse button to pressed state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_mouse_press(uint8_t button); - -/** Set mouse button to released state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_mouse_release(uint8_t button); - -/** Set mouse button to released state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_mouse_release_all(); - -/** Set mouse wheel position and send HID report - * - * @param delta number of scroll steps - */ -bool furi_hal_bt_hid_mouse_scroll(int8_t delta); - -/** Set the following consumer key to pressed state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_consumer_key_press(uint16_t button); - -/** Set the following consumer key to released state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_consumer_key_release(uint16_t button); - -/** Set consumer key to released state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_consumer_key_release_all(); - -#ifdef __cplusplus -} -#endif diff --git a/targets/furi_hal_include/furi_hal_bt_serial.h b/targets/furi_hal_include/furi_hal_bt_serial.h deleted file mode 100644 index 0472d31d18..0000000000 --- a/targets/furi_hal_include/furi_hal_bt_serial.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX SERIAL_SVC_DATA_LEN_MAX - -typedef enum { - FuriHalBtSerialRpcStatusNotActive, - FuriHalBtSerialRpcStatusActive, -} FuriHalBtSerialRpcStatus; - -/** 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); - -/** Set BLE RPC status - * - * @param status FuriHalBtSerialRpcStatus instance - */ -void furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatus status); - -/** 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); - -#ifdef __cplusplus -} -#endif