From 60a9d7e6cf37f1cc9a57d5665bb1a8844a9b0baa Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 16 Feb 2024 11:20:45 +0400 Subject: [PATCH 1/9] 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 | 26 +- 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 | 228 +++++----- 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 | 103 ++--- 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 | 233 ++++------ 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 | 173 ++++--- .../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 | 120 +++-- targets/furi_hal_include/furi_hal_bt_hid.h | 91 ---- targets/furi_hal_include/furi_hal_bt_serial.h | 59 --- 91 files changed, 3361 insertions(+), 1739 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 000000000..fc5a911ab --- /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 000000000..20e3e307a --- /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 000000000..563bd5bed --- /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 000000000..003934bba --- /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 000000000..83223e93c --- /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 000000000..121001e0e --- /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 000000000..13e7ac832 --- /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 000000000..64d15350d --- /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 06c0a7f61..45cc08d44 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 8bc904587..d9d1dec34 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 64c279ffe..f9c386326 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 044820ef3..ba8e481be 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 02bf6cee4..e8ba215bf 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 36409fe5c..4ef3a1595 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 ca47936db..270922543 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 BtProfile + * @param bt Bt instance + * @param profile_template Profile template to change to + * @param params Profile parameters. Can be NULL * * @return true on success */ -bool bt_set_profile(Bt* bt, BtProfile profile); +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 + * + * @return true on success + */ +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 e31031783..ab5d20128 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 55bae76f3..04c1734b7 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 d02462734..67511a194 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 df54bf3f4..dd60e820e 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 67dea4b1f..456a83172 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 158b95de6..1520c1f3d 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 d33ea178c..53b139dd4 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 f7cda64f7..d34efb4f8 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 5db98e9de..c148f0943 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 a1fb314b8..cc218c31a 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 88a68f09d..c6d88124c 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 49d8b4e04..e6e974f30 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 000000000..92a260add --- /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 000000000..882a715a5 --- /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 468529d56..6ef11729e 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 901a76214..bf3f46a9b 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 233b574b0..802596169 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 abc85bb90..3c1a17258 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 44d66fb21..83c051cc2 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 cf96a1b84..812573932 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 000000000..3b20d38f5 --- /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 000000000..aaa66d960 --- /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 000000000..eb4884e45 --- /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 cf2aca24e..d9ea09c14 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 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; - 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]); - } - } - - // Delete service - status = aci_gatt_del_service(hid_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete HID service: %d", status); - } - free(hid_svc); - 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]); } + + typedef struct { + uint8_t report_count; + BleGattCharacteristicInstance* chars; + } HidSvcReportCharProps; + + 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}, + }; + + 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]); + } + } + + // 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 000000000..8e9cc2975 --- /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 785cf831d..2b3eebf9e 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 8a8ad9644..9f25744e9 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 67975ed0f..a3b0d4a78 100644 --- a/scripts/fbt_tools/fbt_hwtarget.py +++ b/scripts/fbt_tools/fbt_hwtarget.py @@ -30,8 +30,11 @@ class HardwareTargetLoader: 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 2cf43dce0..7ce0e8842 100755 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -63,7 +63,13 @@ class Main(App): 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 @@ class Main(App): "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 @@ class Main(App): ), ) + 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 6d994653b..bdfa8c7a4 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 12b8fe685..d856dc694 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 e969636d2..8097d23db 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 25fa688c7..fbf6d0291 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 05dd46e94..1f392529d 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,48 +114,19 @@ 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(); - } - } - - return 0; -} - -// Called by WPAN lib -void hci_notify_asynch_evt(void* pdata) { - UNUSED(pdata); +void ble_app_deinit(void) { 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); + + 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)); } +/////////////////////////////////////////////////////////////////////////////// +// AN5289, 4.9 + void hci_cmd_resp_release(uint32_t flag) { UNUSED(flag); furi_check(ble_app); @@ -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; - tHCI_UserEvtRxParam* pParam = (tHCI_UserEvtRxParam*)pPayload; +/////////////////////////////////////////////////////////////////////////////// +static void ble_app_hci_event_handler(void* pPayload) { furi_check(ble_app); - svctl_return_status = SVCCTL_UserEvtRx((void*)&(pParam->pckt->evtserial)); - if(svctl_return_status != SVCCTL_UserEvtFlowDisable) { + + tHCI_UserEvtRxParam* pParam = (tHCI_UserEvtRxParam*)pPayload; + BleEventFlowStatus event_flow_status = + ble_event_dispatcher_process_event((void*)&(pParam->pckt->evtserial)); + + 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 2e6babab7..22edccd1b 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 2b9c22dfe..4c523a707 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 000000000..6f9a1cdcd --- /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 000000000..bce858d6b --- /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 5f129ba8c..91cb020d7 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()) { +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_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()) { - 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)); + } while(!started && !furi_hal_cortex_timer_is_expired(timer)); - 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 { + 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)); +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)); } -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; - } +bool ble_glue_reinit_c2(void) { + return (SHCI_C2_Reinit() == SHCI_Success); } -// 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); - } -} - -bool ble_glue_reinit_c2() { - 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 bd2588a02..05c34148c 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(); +BleGlueCommandResult ble_glue_fus_wait_operation(void); + +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 000000000..092afd742 --- /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 000000000..446b31380 --- /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 000000000..675ea538c --- /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 000000000..ce3661d6d --- /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 000000000..90fc0762f --- /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 f6e27f53e..dcea5f987 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 000000000..5a33e9e54 --- /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 000000000..f1b42837b --- /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 8e3ec58b7..86623b02f 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 1e207299f..a90d07304 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 gap_stop_advertising(void); -GapState gap_get_state(); +GapState gap_get_state(void); -void gap_thread_stop(); +void gap_thread_stop(void); + +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 000000000..a3949abfc --- /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 000000000..e07eaef03 --- /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 63f736b3b..f4bc1ff7b 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}}; + .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}}; -typedef struct { +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(); -} - -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]); + battery_svc->auto_update = auto_update; + if(auto_update) { + if(!instances_initialized) { + BatterySvcInstanceList_init(instances); + instances_initialized = true; } - // 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); + + BatterySvcInstanceList_push_back(instances, battery_svc); + } + + return battery_svc; +} + +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; + } } - free(battery_svc); - 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_is_started() { - return battery_svc != NULL; -} - -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 f38bfc00d..dccc44047 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 59af23e5c..37caa8c90 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); +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*)&dev_info_svc->hardware_revision; + *data = (const uint8_t*)context; } 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); - if(data) { - *data = (const uint8_t*)furi_string_get_cstr(dev_info_svc->version_string); - } - 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 8cce20a6c..42471e56d 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 959ab67a4..000000000 --- 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 211adcd6c..000000000 --- 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 0db25b3d3..a8f10e6d7 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); + + if(!ble_gatt_service_add( + UUID_TYPE_128, &service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle)) { + free(serial_svc); + return NULL; } - - // Add characteristics 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); + + 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_is_started() { - return serial_svc != NULL; -} - -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 7d38066f4..91ad886e0 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; -void serial_svc_set_callbacks( +BleServiceSerial* ble_svc_serial_start(void); + +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 ble_svc_serial_set_rpc_active(BleServiceSerial* service, bool active); -void serial_svc_notify_buffer_is_empty(); +void ble_svc_serial_notify_buffer_is_empty(BleServiceSerial* service); -void serial_svc_stop(); - -bool serial_svc_is_started(); - -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 a297d9ad6..577e8f2ed 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 48bce998e..c276b5cf4 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 7ec712af4..000000000 --- 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 2927d946f..000000000 --- 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 6b5efc376..9865e6ef8 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 3bc57f8f3..6a68bbae7 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 0f0161fe4..25872198b 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 4d538265d..939c9a30d 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; - -/** Get hardfault info - * - * @return hardfault info. NULL if no hardfault +/** + * Extra BLE beacon API */ -const FuriHalBtHardfaultInfo* furi_hal_bt_get_hardfault_info(); + +/** 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 last configured extra beacon config + * + * @return extra beacon config. NULL if beacon had never been configured. + */ +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 4e74bbda7..000000000 --- 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 0472d31d1..000000000 --- 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 From 88a3b4593d5eb6d79ccc6058f2efeee2534a90e5 Mon Sep 17 00:00:00 2001 From: Maxwell Pray Date: Fri, 16 Feb 2024 23:26:58 -0800 Subject: [PATCH 2/9] Add IR codes for PLUS U5/V3-200R projector remote (controls PLUS U5-632h and PLUS U5-732h) (#3453) * Add IR codes for PLUS U5/V3-200R projector to projector.ir. * Add U5/V3-200R "freeze" and "cancel" IR codes. * Remove pause and play IR codes for U5/V3-200R remote. --- .../resources/infrared/assets/projector.ir | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/applications/main/infrared/resources/infrared/assets/projector.ir b/applications/main/infrared/resources/infrared/assets/projector.ir index e9861de21..dcfcb06ff 100644 --- a/applications/main/infrared/resources/infrared/assets/projector.ir +++ b/applications/main/infrared/resources/infrared/assets/projector.ir @@ -826,4 +826,28 @@ name: Mute type: raw frequency: 38000 duty_cycle: 0.33 -data: 9011 4388 557 1617 532 1617 532 489 533 489 558 464 558 440 582 440 582 1593 556 466 556 466 556 1594 556 467 555 1595 555 1595 529 1620 554 1596 554 467 554 468 555 1595 579 443 581 1569 581 441 581 441 580 442 581 1569 581 1569 581 441 581 1569 580 441 581 1569 581 1569 581 1570 579 42152 8957 2159 556 \ No newline at end of file +data: 9011 4388 557 1617 532 1617 532 489 533 489 558 464 558 440 582 440 582 1593 556 466 556 466 556 1594 556 467 555 1595 555 1595 529 1620 554 1596 554 467 554 468 555 1595 579 443 581 1569 581 441 581 441 580 442 581 1569 581 1569 581 441 581 1569 580 441 581 1569 581 1569 581 1570 579 42152 8957 2159 556 +# PLUS U5/V3-200R Standby +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9033 4255 562 543 564 1701 562 1674 563 567 565 541 564 567 565 567 537 1700 564 1700 537 568 564 1699 564 541 565 1699 565 540 565 567 565 570 535 567 565 567 565 1673 564 567 565 1673 564 567 565 540 618 514 565 1700 563 1674 564 567 564 1674 563 569 563 1672 565 1700 589 1648 564 +# PLUS U5/V3-200R Mute +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9034 4255 564 567 564 1671 566 1699 564 540 565 567 565 540 564 566 566 1699 563 1672 567 565 566 1673 565 567 565 1672 565 566 566 539 565 567 565 1698 539 566 566 1698 564 541 565 565 567 1672 565 566 566 1672 565 567 565 1699 538 566 566 1698 564 1673 566 566 565 1672 566 566 566 +# PLUS U5/V3-200R 1/Volume up +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9035 4254 565 566 565 1672 566 1699 564 539 567 566 566 540 564 567 565 1698 539 1698 566 566 564 1673 565 566 565 1672 565 566 566 539 566 566 566 567 563 541 565 566 566 538 566 566 566 1671 566 566 566 1697 565 1675 563 1699 563 1674 565 1699 538 1700 564 565 565 1674 564 567 565 +# PLUS U5/V3-200R 3/Volume down +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9010 4253 564 566 566 1671 566 1699 565 565 539 568 564 566 565 539 566 1699 565 1672 565 566 566 1672 564 567 565 1672 565 567 565 567 564 541 564 1698 566 539 565 567 565 567 562 542 565 1699 564 539 567 1699 565 540 564 1698 566 1672 565 1698 566 1672 565 567 565 1671 565 566 566 From fcf3b50f690f1f32a5a0b7f93c14837aca03ad7c Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Sat, 17 Feb 2024 10:32:32 +0300 Subject: [PATCH 3/9] Add notification sending (#3449) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .github/workflows/reindex.yml | 27 +++++++++++++--- scripts/send_firebase_notification.py | 44 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 scripts/send_firebase_notification.py diff --git a/.github/workflows/reindex.yml b/.github/workflows/reindex.yml index 82cb04680..4489c7093 100644 --- a/.github/workflows/reindex.yml +++ b/.github/workflows/reindex.yml @@ -1,15 +1,32 @@ -name: 'Reindex' +name: 'Post-release hooks' on: release: - types: [prereleased,released] + types: [prereleased, released] jobs: reindex: - name: 'Reindex updates' + name: 'Post-release hooks' runs-on: [self-hosted, FlipperZeroShell] steps: - - name: Trigger reindex + - name: 'Checkout code' + uses: actions/checkout@v4 + + - name: 'Trigger reindex' run: | curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ - "${{ secrets.INDEXER_URL }}"/firmware/reindex + "${{ secrets.INDEXER_URL }}"/firmware/reindex; + + - name: 'Send release notification' + if: ${{ github.event.action == 'released' }} + run: | + echo '${{ secrets.FIREBASE_TOKEN }}' > firebase-token.json; + python3 -m pip install firebase-admin==6.4.0; + python3 scripts/send_firebase_notification.py \ + "--version=${{ github.event.release.name }}" \ + "--token=firebase-token.json"; + + - name: 'Remove firebase token' + if: always() + run: | + rm -rf firebase-token.json; diff --git a/scripts/send_firebase_notification.py b/scripts/send_firebase_notification.py new file mode 100644 index 000000000..102cf0066 --- /dev/null +++ b/scripts/send_firebase_notification.py @@ -0,0 +1,44 @@ +import argparse +import logging +from firebase_admin import messaging, credentials, initialize_app + + +class FirebaseNotifications: + def __init__(self, service_account_file): + try: + cred = credentials.Certificate(service_account_file) + self.firebase_app = initialize_app(cred) + except Exception as e: + logging.exception(e) + raise e + + def send(self, title, body, condition): + try: + message = messaging.Message( + notification=messaging.Notification(title=title, body=body), + condition=condition, + ) + messaging.send(message, app=self.firebase_app) + except Exception as e: + logging.exception(e) + raise e + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--token_file", help="Firebase token file", required=True) + parser.add_argument( + "--version", help="Firmware version to notify with", required=True + ) + args = parser.parse_args() + return args + + +if __name__ == "__main__": + args = parse_args() + notification = FirebaseNotifications(args.token_file) + notification.send( + title="Firmware Update Available", + body=f"New firmware version is ready to install: {args.version}", + condition="'flipper_update_firmware_release' in topics", + ) From 2c650b5bc7e2586248e32f0df726831fbea52502 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:15:44 +0400 Subject: [PATCH 4/9] [FL-3699] HID: Add confirmation dialogue to the remove pairing option (#3263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * HID: Add confirmation dialogue to the un-pair option * Initial refactor to use SceneManager * Make PVS happy * Fix the exit dialog Co-authored-by: あく --- .../hid_app/assets/DolphinNice_96x59.png | Bin 0 -> 2459 bytes applications/system/hid_app/hid.c | 80 +++++++++--------- applications/system/hid_app/hid.h | 6 ++ .../system/hid_app/scenes/hid_scene.c | 30 +++++++ .../system/hid_app/scenes/hid_scene.h | 29 +++++++ .../system/hid_app/scenes/hid_scene_config.h | 3 + .../hid_app/scenes/hid_scene_exit_confirm.c | 45 ++++++++++ .../system/hid_app/scenes/hid_scene_main.c | 22 +++++ .../system/hid_app/scenes/hid_scene_unpair.c | 63 ++++++++++++++ applications/system/hid_app/views.h | 3 +- 10 files changed, 242 insertions(+), 39 deletions(-) create mode 100644 applications/system/hid_app/assets/DolphinNice_96x59.png create mode 100644 applications/system/hid_app/scenes/hid_scene.c create mode 100644 applications/system/hid_app/scenes/hid_scene.h create mode 100644 applications/system/hid_app/scenes/hid_scene_config.h create mode 100644 applications/system/hid_app/scenes/hid_scene_exit_confirm.c create mode 100644 applications/system/hid_app/scenes/hid_scene_main.c create mode 100644 applications/system/hid_app/scenes/hid_scene_unpair.c diff --git a/applications/system/hid_app/assets/DolphinNice_96x59.png b/applications/system/hid_app/assets/DolphinNice_96x59.png new file mode 100644 index 0000000000000000000000000000000000000000..a299d3630239b4486e249cc501872bed5996df3b GIT binary patch literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!Q #include +#include "hid_icons.h" #define TAG "HidApp" @@ -19,7 +20,22 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexRemovePairing, }; -static void bt_hid_remove_pairing(Bt* bt) { +bool hid_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + Hid* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +bool hid_back_event_callback(void* context) { + furi_assert(context); + Hid* app = context; + FURI_LOG_D("HID", "Back event"); + scene_manager_next_scene(app->scene_manager, HidSceneExitConfirm); + return true; +} + +void bt_hid_remove_pairing(Hid* app) { + Bt* bt = app->bt; bt_disconnect(bt); // Wait 2nd core to update nvm storage @@ -62,7 +78,7 @@ static void hid_submenu_callback(void* context, uint32_t index) { app->view_id = HidViewMouseJiggler; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); } else if(index == HidSubmenuIndexRemovePairing) { - bt_hid_remove_pairing(app->bt); + scene_manager_next_scene(app->scene_manager, HidSceneUnpair); } } @@ -86,23 +102,6 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con hid_tiktok_set_connected_status(hid->hid_tiktok, connected); } -static void hid_dialog_callback(DialogExResult result, void* context) { - furi_assert(context); - Hid* app = context; - if(result == DialogExResultLeft) { - view_dispatcher_stop(app->view_dispatcher); - } else if(result == DialogExResultRight) { - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view - } else if(result == DialogExResultCenter) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); - } -} - -static uint32_t hid_exit_confirm_view(void* context) { - UNUSED(context); - return HidViewExitConfirm; -} - static uint32_t hid_exit(void* context) { UNUSED(context); return VIEW_NONE; @@ -124,6 +123,12 @@ Hid* hid_alloc() { app->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_enable_queue(app->view_dispatcher); view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_set_navigation_event_callback(app->view_dispatcher, hid_back_event_callback); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + // Scene Manager + app->scene_manager = scene_manager_alloc(&hid_scene_handlers, app); + // Device Type Submenu view app->device_type_submenu = submenu_alloc(); submenu_add_item( @@ -172,58 +177,48 @@ Hid* hid_alloc() { view_dispatcher_add_view( app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); app->view_id = HidViewSubmenu; - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); return app; } Hid* hid_app_alloc_view(void* context) { furi_assert(context); Hid* app = context; + // Dialog view app->dialog = dialog_ex_alloc(); - dialog_ex_set_result_callback(app->dialog, hid_dialog_callback); - dialog_ex_set_context(app->dialog, app); - dialog_ex_set_left_button_text(app->dialog, "Exit"); - dialog_ex_set_right_button_text(app->dialog, "Stay"); - dialog_ex_set_center_button_text(app->dialog, "Menu"); - dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); - view_dispatcher_add_view( - app->view_dispatcher, HidViewExitConfirm, dialog_ex_get_view(app->dialog)); + view_dispatcher_add_view(app->view_dispatcher, HidViewDialog, dialog_ex_get_view(app->dialog)); + + // Popup view + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, HidViewPopup, popup_get_view(app->popup)); // Keynote view app->hid_keynote = hid_keynote_alloc(app); - view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote)); // Keyboard view app->hid_keyboard = hid_keyboard_alloc(app); - view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard)); // Media view app->hid_media = hid_media_alloc(app); - view_set_previous_callback(hid_media_get_view(app->hid_media), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media)); // TikTok view app->hid_tiktok = hid_tiktok_alloc(app); - view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok)); // Mouse view app->hid_mouse = hid_mouse_alloc(app); - view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); // Mouse clicker view app->hid_mouse_clicker = hid_mouse_clicker_alloc(app); - view_set_previous_callback( - hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouseClicker, @@ -231,8 +226,6 @@ Hid* hid_app_alloc_view(void* context) { // Mouse jiggler view app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); - view_set_previous_callback( - hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouseJiggler, @@ -251,8 +244,10 @@ void hid_free(Hid* app) { // Free views view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); submenu_free(app->device_type_submenu); - view_dispatcher_remove_view(app->view_dispatcher, HidViewExitConfirm); + view_dispatcher_remove_view(app->view_dispatcher, HidViewDialog); dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, HidViewPopup); + popup_free(app->popup); view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); hid_keynote_free(app->hid_keynote); view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard); @@ -267,6 +262,7 @@ void hid_free(Hid* app) { hid_mouse_jiggler_free(app->hid_mouse_jiggler); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); hid_tiktok_free(app->hid_tiktok); + scene_manager_free(app->scene_manager); view_dispatcher_free(app->view_dispatcher); // Close records @@ -285,6 +281,8 @@ int32_t hid_usb_app(void* p) { UNUSED(p); Hid* app = hid_alloc(); app = hid_app_alloc_view(app); + FURI_LOG_D("HID", "Starting as USB app"); + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); @@ -293,6 +291,8 @@ int32_t hid_usb_app(void* p) { dolphin_deed(DolphinDeedPluginStart); + scene_manager_next_scene(app->scene_manager, HidSceneMain); + view_dispatcher_run(app->view_dispatcher); furi_hal_usb_set_config(usb_mode_prev, NULL); @@ -307,6 +307,8 @@ int32_t hid_ble_app(void* p) { Hid* app = hid_alloc(); app = hid_app_alloc_view(app); + FURI_LOG_D("HID", "Starting as BLE app"); + bt_disconnect(app->bt); // Wait 2nd core to update nvm storage @@ -333,6 +335,8 @@ int32_t hid_ble_app(void* p) { dolphin_deed(DolphinDeedPluginStart); + scene_manager_next_scene(app->scene_manager, HidSceneMain); + view_dispatcher_run(app->view_dispatcher); bt_set_status_changed_callback(app->bt, NULL, NULL); diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index e6e974f30..a79b2bcd3 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,8 @@ #include "views/hid_mouse_jiggler.h" #include "views/hid_tiktok.h" +#include "scenes/hid_scene.h" + #define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" typedef enum { @@ -40,8 +43,10 @@ struct Hid { Gui* gui; NotificationApp* notifications; ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; Submenu* device_type_submenu; DialogEx* dialog; + Popup* popup; HidKeynote* hid_keynote; HidKeyboard* hid_keyboard; HidMedia* hid_media; @@ -53,6 +58,7 @@ struct Hid { HidTransport transport; uint32_t view_id; }; +void bt_hid_remove_pairing(Hid* app); void hid_hal_keyboard_press(Hid* instance, uint16_t event); void hid_hal_keyboard_release(Hid* instance, uint16_t event); diff --git a/applications/system/hid_app/scenes/hid_scene.c b/applications/system/hid_app/scenes/hid_scene.c new file mode 100644 index 000000000..89399a809 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene.c @@ -0,0 +1,30 @@ +#include "hid_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const hid_on_enter_handlers[])(void*) = { +#include "hid_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 hid_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "hid_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 hid_on_exit_handlers[])(void* context) = { +#include "hid_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers hid_scene_handlers = { + .on_enter_handlers = hid_on_enter_handlers, + .on_event_handlers = hid_on_event_handlers, + .on_exit_handlers = hid_on_exit_handlers, + .scene_num = HidSceneNum, +}; diff --git a/applications/system/hid_app/scenes/hid_scene.h b/applications/system/hid_app/scenes/hid_scene.h new file mode 100644 index 000000000..9a2e6bb32 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) HidScene##id, +typedef enum { +#include "hid_scene_config.h" + HidSceneNum, +} HidScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers hid_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "hid_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 "hid_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 "hid_scene_config.h" +#undef ADD_SCENE diff --git a/applications/system/hid_app/scenes/hid_scene_config.h b/applications/system/hid_app/scenes/hid_scene_config.h new file mode 100644 index 000000000..8f3a788d1 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(hid, main, Main) +ADD_SCENE(hid, unpair, Unpair) +ADD_SCENE(hid, exit_confirm, ExitConfirm) \ No newline at end of file diff --git a/applications/system/hid_app/scenes/hid_scene_exit_confirm.c b/applications/system/hid_app/scenes/hid_scene_exit_confirm.c new file mode 100644 index 000000000..94e783e93 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_exit_confirm.c @@ -0,0 +1,45 @@ +#include "../hid.h" +#include "../views.h" + +static void hid_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + Hid* app = context; + if(result == DialogExResultLeft) { + view_dispatcher_stop(app->view_dispatcher); + } else if(result == DialogExResultRight) { + scene_manager_previous_scene(app->scene_manager); + } else if(result == DialogExResultCenter) { + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, HidSceneMain); + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); + } +} + +void hid_scene_exit_confirm_on_enter(void* context) { + Hid* app = context; + + // Exit dialog view + dialog_ex_reset(app->dialog); + dialog_ex_set_result_callback(app->dialog, hid_scene_exit_confirm_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_left_button_text(app->dialog, "Exit"); + dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); + dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); + + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewDialog); +} + +bool hid_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void hid_scene_exit_confirm_on_exit(void* context) { + Hid* app = context; + + dialog_ex_reset(app->dialog); +} diff --git a/applications/system/hid_app/scenes/hid_scene_main.c b/applications/system/hid_app/scenes/hid_scene_main.c new file mode 100644 index 000000000..6c4a11682 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_main.c @@ -0,0 +1,22 @@ +#include "../hid.h" +#include "../views.h" + +void hid_scene_main_on_enter(void* context) { + Hid* app = context; + + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); +} + +bool hid_scene_main_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void hid_scene_main_on_exit(void* context) { + Hid* app = context; + UNUSED(app); +} diff --git a/applications/system/hid_app/scenes/hid_scene_unpair.c b/applications/system/hid_app/scenes/hid_scene_unpair.c new file mode 100644 index 000000000..7b0bbd9e6 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_unpair.c @@ -0,0 +1,63 @@ +#include "../hid.h" +#include "../views.h" +#include "hid_icons.h" + +static void hid_scene_unpair_dialog_callback(DialogExResult result, void* context) { + Hid* app = context; + + if(result == DialogExResultRight) { + // Unpair all devices + bt_hid_remove_pairing(app); + + // Show popup + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPopup); + } else if(result == DialogExResultLeft) { + scene_manager_previous_scene(app->scene_manager); + } +} + +void hid_scene_unpair_popup_callback(void* context) { + Hid* app = context; + + scene_manager_previous_scene(app->scene_manager); +} + +void hid_scene_unpair_on_enter(void* context) { + Hid* app = context; + + // Un-pair dialog view + dialog_ex_reset(app->dialog); + dialog_ex_set_result_callback(app->dialog, hid_scene_unpair_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_header(app->dialog, "Unpair All Devices?", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_text( + app->dialog, "All previous pairings\nwill be lost!", 64, 22, AlignCenter, AlignTop); + dialog_ex_set_left_button_text(app->dialog, "Back"); + dialog_ex_set_right_button_text(app->dialog, "Unpair"); + + // Un-pair success popup view + popup_set_icon(app->popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(app->popup, "Done", 14, 15, AlignLeft, AlignTop); + popup_set_timeout(app->popup, 1500); + popup_set_context(app->popup, app); + popup_set_callback(app->popup, hid_scene_unpair_popup_callback); + popup_enable_timeout(app->popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewDialog); +} + +bool hid_scene_unpair_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void hid_scene_unpair_on_exit(void* context) { + Hid* app = context; + + dialog_ex_reset(app->dialog); + popup_reset(app->popup); +} diff --git a/applications/system/hid_app/views.h b/applications/system/hid_app/views.h index 1bea3355e..f94a55cc6 100644 --- a/applications/system/hid_app/views.h +++ b/applications/system/hid_app/views.h @@ -7,5 +7,6 @@ typedef enum { HidViewMouseClicker, HidViewMouseJiggler, BtHidViewTikTok, - HidViewExitConfirm, + HidViewDialog, + HidViewPopup, } HidView; \ No newline at end of file From 4e1089ec49e2a300ab36ea68d824b4cb2fee025d Mon Sep 17 00:00:00 2001 From: Violet Shreve Date: Sun, 18 Feb 2024 04:20:27 -0500 Subject: [PATCH 5/9] Quote $FBT_TOOLCHAIN_PATH to avoid splitting (#3459) --- scripts/toolchain/fbtenv.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 27fb8f975..ef7768e82 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -203,7 +203,7 @@ fbtenv_show_unpack_percentage() fbtenv_unpack_toolchain() { echo "Unpacking toolchain to '$FBT_TOOLCHAIN_PATH/toolchain':"; - rm $FBT_TOOLCHAIN_PATH/toolchain/current || true; + rm "$FBT_TOOLCHAIN_PATH/toolchain/current" || true; tar -xvf "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" -C "$FBT_TOOLCHAIN_PATH/toolchain" 2>&1 | fbtenv_show_unpack_percentage; mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1; From 66c60e65aa24e42a77286aa13b0931e8e9249d4c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 19 Feb 2024 03:52:35 +0300 Subject: [PATCH 6/9] Merge fixes + thanks @Willy-JL ! --- applications/services/bt/bt_service/bt.c | 125 +++++------------- applications/services/bt/bt_service/bt.h | 32 ++--- .../services/namechanger/namechanger.c | 2 +- lib/ble_profile/extra_profiles/hid_profile.h | 7 - targets/f7/api_symbols.csv | 21 +-- targets/f7/ble_glue/furi_ble/gatt.c | 2 +- targets/f7/ble_glue/gap.c | 35 +++-- targets/f7/ble_glue/gap.h | 2 +- targets/f7/furi_hal/furi_hal_bt.c | 91 ------------- targets/f7/furi_hal/furi_hal_version.c | 32 ++--- targets/furi_hal_include/furi_hal_bt.h | 62 --------- targets/furi_hal_include/furi_hal_region.h | 2 - targets/furi_hal_include/furi_hal_version.h | 7 +- 13 files changed, 89 insertions(+), 331 deletions(-) diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index f27cb061f..0315a4eaf 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -77,6 +77,8 @@ static void bt_pin_code_show(Bt* bt, uint32_t pin_code) { gui_add_view_port(bt->gui, bt->pin_code_view_port, GuiLayerFullscreen); } notification_message(bt->notification, &sequence_display_backlight_on); + if(bt->suppress_pin_screen) return; + gui_view_port_send_to_front(bt->gui, bt->pin_code_view_port); view_port_enabled_set(bt->pin_code_view_port, true); } @@ -91,8 +93,9 @@ static void bt_pin_code_hide(Bt* bt) { static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { furi_assert(bt); bt->pin_code = pin; - if(bt->suppress_pin_screen) return true; notification_message(bt->notification, &sequence_display_backlight_on); + if(bt->suppress_pin_screen) return true; + FuriString* pin_str; if(!bt->dialog_message) { bt->dialog_message = dialog_message_alloc(); @@ -141,9 +144,7 @@ Bt* bt_alloc() { 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); - } + bt_settings_load(&bt->bt_settings); // Keys storage bt->keys_storage = bt_keys_storage_alloc(BT_KEYS_STORAGE_PATH); // Alloc queue @@ -250,6 +251,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { furi_assert(context); Bt* bt = context; bool ret = false; + bt->pin = 0; bool do_update_status = false; bool current_profile_is_serial = furi_hal_bt_check_profile_type(bt->current_profile, ble_profile_serial); @@ -258,26 +260,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { // Update status bar bt->status = BtStatusConnected; 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(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, bt_serial_buffer_is_empty_callback); - rpc_session_set_context(bt->rpc_session, bt); - 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"); - } - } + bt_open_rpc_connection(bt); // Update battery level PowerInfo info; power_get_info(bt->power, &info); @@ -376,7 +359,31 @@ static void bt_show_warning(Bt* bt, const char* text) { dialog_message_show(bt->dialogs, bt->dialog_message); } -static void bt_close_rpc_connection(Bt* bt) { +void bt_open_rpc_connection(Bt* bt) { + if(!bt->rpc_session && bt->status == BtStatusConnected) { + // 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(furi_hal_bt_check_profile_type(bt->current_profile, ble_profile_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, bt_serial_buffer_is_empty_callback); + rpc_session_set_context(bt->rpc_session, bt); + 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"); + } + } + } +} + +void bt_close_rpc_connection(Bt* bt) { if(furi_hal_bt_check_profile_type(bt->current_profile, ble_profile_serial) && bt->rpc_session) { FURI_LOG_I(TAG, "Close RPC connection"); @@ -389,7 +396,6 @@ static void bt_close_rpc_connection(Bt* bt) { static void bt_change_profile(Bt* bt, BtMessage* message) { if(furi_hal_bt_is_gatt_gap_supported()) { - bt_settings_load(&bt->bt_settings); bt_close_rpc_connection(bt); bt_keys_storage_load(bt->keys_storage); @@ -433,52 +439,6 @@ static void bt_close_connection(Bt* bt, BtMessage* message) { if(message->lock) api_lock_unlock(message->lock); } -static inline FuriHalBtProfile get_hal_bt_profile(BtProfile profile) { - if(profile == BtProfileHidKeyboard) { - return FuriHalBtProfileHidKeyboard; - } else { - return FuriHalBtProfileSerial; - } -} - -void bt_restart(Bt* bt) { - furi_hal_bt_change_app(get_hal_bt_profile(bt->profile), bt_on_gap_event_callback, bt); - furi_hal_bt_start_advertising(); -} - -void bt_set_profile_adv_name(Bt* bt, const char* fmt, ...) { - furi_assert(bt); - furi_assert(fmt); - - char name[FURI_HAL_BT_ADV_NAME_LENGTH]; - va_list args; - va_start(args, fmt); - vsnprintf(name, sizeof(name), fmt, args); - va_end(args); - furi_hal_bt_set_profile_adv_name(get_hal_bt_profile(bt->profile), name); - - bt_restart(bt); -} - -const char* bt_get_profile_adv_name(Bt* bt) { - furi_assert(bt); - return furi_hal_bt_get_profile_adv_name(get_hal_bt_profile(bt->profile)); -} - -void bt_set_profile_mac_address(Bt* bt, const uint8_t mac[6]) { - furi_assert(bt); - furi_assert(mac); - - furi_hal_bt_set_profile_mac_addr(get_hal_bt_profile(bt->profile), mac); - - bt_restart(bt); -} - -const uint8_t* bt_get_profile_mac_address(Bt* bt) { - furi_assert(bt); - return furi_hal_bt_get_profile_mac_addr(get_hal_bt_profile(bt->profile)); -} - bool bt_remote_rssi(Bt* bt, uint8_t* rssi) { furi_assert(bt); @@ -492,27 +452,6 @@ bool bt_remote_rssi(Bt* bt, uint8_t* rssi) { return true; } -void bt_set_profile_pairing_method(Bt* bt, GapPairing pairing_method) { - furi_assert(bt); - furi_hal_bt_set_profile_pairing_method(get_hal_bt_profile(bt->profile), pairing_method); - bt_restart(bt); -} - -GapPairing bt_get_profile_pairing_method(Bt* bt) { - furi_assert(bt); - return furi_hal_bt_get_profile_pairing_method(get_hal_bt_profile(bt->profile)); -} - -void bt_disable_peer_key_update(Bt* bt) { - UNUSED(bt); - furi_hal_bt_set_key_storage_change_callback(NULL, NULL); -} - -void bt_enable_peer_key_update(Bt* bt) { - furi_assert(bt); - furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); -} - int32_t bt_srv(void* p) { UNUSED(p); Bt* bt = bt_alloc(); diff --git a/applications/services/bt/bt_service/bt.h b/applications/services/bt/bt_service/bt.h index e04d07ab1..d49b0b3ba 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -5,8 +5,6 @@ #include #include -#include - #ifdef __cplusplus extern "C" { #endif @@ -22,6 +20,11 @@ typedef enum { BtStatusConnected, } BtStatus; +typedef struct { + uint8_t rssi; + uint32_t since; +} BtRssi; + typedef void (*BtStatusChangedCallback)(BtStatus status, void* context); /** Change BLE Profile @@ -81,31 +84,20 @@ void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path); */ void bt_keys_storage_set_default_path(Bt* bt); -// New methods - -void bt_set_profile_adv_name(Bt* bt, const char* fmt, ...); - -const char* bt_get_profile_adv_name(Bt* bt); - -void bt_set_profile_mac_address(Bt* bt, const uint8_t mac[6]); - -const uint8_t* bt_get_profile_mac_address(Bt* bt); - bool bt_remote_rssi(Bt* bt, uint8_t* rssi); -void bt_set_profile_pairing_method(Bt* bt, GapPairing pairing_method); -GapPairing bt_get_profile_pairing_method(Bt* bt); - -/** Stop saving new peer key to flash (in .bt.keys file) +/** * + * (Probably bad) way of opening the RPC connection, everywhereTM */ -void bt_disable_peer_key_update(Bt* bt); -/** Enable saving peer key to internal flash (enable by default) +void bt_open_rpc_connection(Bt* bt); + +/** * - * @note This function should be called if bt_disable_peer_key_update was called before + * Closing the RPC connection, everywhereTM */ -void bt_enable_peer_key_update(Bt* bt); +void bt_close_rpc_connection(Bt* bt); #ifdef __cplusplus } diff --git a/applications/services/namechanger/namechanger.c b/applications/services/namechanger/namechanger.c index 796dd2d3b..5bdaa2a1b 100644 --- a/applications/services/namechanger/namechanger.c +++ b/applications/services/namechanger/namechanger.c @@ -99,7 +99,7 @@ int32_t namechanger_on_system_start(void* p) { furi_delay_ms(3); Bt* bt = furi_record_open(RECORD_BT); - if(!bt_set_profile(bt, BtProfileSerial)) { + if(!bt_profile_restore_default(bt)) { //FURI_LOG_D(TAG, "Failed to touch bluetooth to name change"); } furi_record_close(RECORD_BT); diff --git a/lib/ble_profile/extra_profiles/hid_profile.h b/lib/ble_profile/extra_profiles/hid_profile.h index 44e430dbc..eb4884e45 100644 --- a/lib/ble_profile/extra_profiles/hid_profile.h +++ b/lib/ble_profile/extra_profiles/hid_profile.h @@ -100,13 +100,6 @@ bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile); */ bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta); -/** Retrieves LED state from remote BT HID host - * - * @return (look at HID usage page to know what each bit of the returned byte means) - * NB: RFU bit has been shifted out in the returned octet so USB defines should work -*/ -uint8_t furi_hal_bt_hid_get_led_state(void); - #ifdef __cplusplus } #endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 3a0f6d52b..03c12a66b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -748,17 +748,15 @@ 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_disable_peer_key_update,void,Bt* +Function,-,bt_close_rpc_connection,void,Bt* Function,+,bt_disconnect,void,Bt* -Function,+,bt_enable_peer_key_update,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* -Function,+,bt_get_profile_adv_name,const char*,Bt* -Function,+,bt_get_profile_mac_address,const uint8_t*,Bt* -Function,+,bt_get_profile_pairing_method,GapPairing,Bt* Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,-,bt_open_rpc_connection,void,Bt* Function,+,bt_profile_restore_default,_Bool,Bt* Function,+,bt_profile_start,FuriHalBleProfileBase*,"Bt*, const FuriHalBleProfileTemplate*, FuriHalBleProfileParams" +Function,+,bt_remote_rssi,_Bool,"Bt*, uint8_t*" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -1190,9 +1188,6 @@ Function,+,furi_get_tick,uint32_t, 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_custom_adv_set,_Bool,"const uint8_t*, size_t" -Function,+,furi_hal_bt_custom_adv_start,_Bool,"uint16_t, uint16_t, uint8_t, const uint8_t[( 6 )], uint8_t" -Function,+,furi_hal_bt_custom_adv_stop,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode Function,+,furi_hal_bt_extra_beacon_get_config,const GapExtraBeaconConfig*, @@ -1202,17 +1197,15 @@ 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_conn_rssi,uint32_t,uint8_t* Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" -Function,+,furi_hal_bt_get_profile_adv_name,const char*,FuriHalBtProfile -Function,+,furi_hal_bt_get_profile_mac_addr,const uint8_t*,FuriHalBtProfile -Function,-,furi_hal_bt_get_profile_pairing_method,GapPairing,FuriHalBtProfile 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_init,void, Function,+,furi_hal_bt_is_active,_Bool, Function,+,furi_hal_bt_is_alive,_Bool, +Function,+,furi_hal_bt_is_connected,_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, @@ -1221,9 +1214,6 @@ Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, Function,+,furi_hal_bt_reverse_mac_addr,void,uint8_t[( 6 )] Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" -Function,+,furi_hal_bt_set_profile_adv_name,void,"FuriHalBtProfile, const char[( ( 1 + 8 + ( 8 + 1 ) ) + 1 )]" -Function,+,furi_hal_bt_set_profile_mac_addr,void,"FuriHalBtProfile, const uint8_t[( 6 )]" -Function,+,furi_hal_bt_set_profile_pairing_method,void,"FuriHalBtProfile, GapPairing" Function,+,furi_hal_bt_start_advertising,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" @@ -1851,6 +1841,7 @@ 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_remote_conn_rssi,uint32_t,int8_t* Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, diff --git a/targets/f7/ble_glue/furi_ble/gatt.c b/targets/f7/ble_glue/furi_ble/gatt.c index 75af8849a..dcea5f987 100644 --- a/targets/f7/ble_glue/furi_ble/gatt.c +++ b/targets/f7/ble_glue/furi_ble/gatt.c @@ -63,7 +63,7 @@ void ble_gatt_characteristic_init( char_data_descriptor->security_permissions, char_data_descriptor->access_permissions, char_data_descriptor->gatt_evt_mask, - MIN_ENCRY_KEY_SIZE, + GATT_MIN_READ_KEY_SIZE, char_data_descriptor->is_variable, &char_instance->descriptor_handle); if(status) { diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index e752a1090..faac3be45 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -372,35 +372,33 @@ static void gap_init_svc(Gap* gap) { // Set default PHY hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); // Set I/O capability + bool bonding_mode = gap->config->bonding_mode; + uint8_t cfg_mitm_protection = CFG_MITM_PROTECTION; + uint8_t cfg_used_fixed_pin = CFG_USED_FIXED_PIN; bool keypress_supported = false; - // New things below - uint8_t conf_mitm = CFG_MITM_PROTECTION; - uint8_t conf_used_fixed_pin = CFG_USED_FIXED_PIN; - bool conf_bonding = gap->config->bonding_mode; - if(gap->config->pairing_method == GapPairingPinCodeShow) { aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); } else if(gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) { aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); keypress_supported = true; } else if(gap->config->pairing_method == GapPairingNone) { - // Just works pairing method (IOS accept it, it seems android and linux doesn't) - conf_mitm = 0; - conf_used_fixed_pin = 0; - conf_bonding = false; - // if just works isn't supported, we want the numeric comparaison method + // "Just works" pairing method (iOS accepts it, it seems Android and Linux don't) + bonding_mode = false; + cfg_mitm_protection = MITM_PROTECTION_NOT_REQUIRED; + cfg_used_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_ALLOWED; + // If "just works" isn't supported, we want the numeric comparaison method aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); keypress_supported = true; } // Setup authentication aci_gap_set_authentication_requirement( - conf_bonding, - conf_mitm, + bonding_mode, + cfg_mitm_protection, CFG_SC_SUPPORT, keypress_supported, CFG_ENCRYPTION_KEY_SIZE_MIN, CFG_ENCRYPTION_KEY_SIZE_MAX, - conf_used_fixed_pin, // 0x0 for no pin + cfg_used_fixed_pin, 0, CFG_IDENTITY_ADDRESS); // Configure whitelist @@ -554,6 +552,17 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { return true; } +// Get RSSI +uint32_t gap_get_remote_conn_rssi(int8_t* rssi) { + if(gap && gap->state == GapStateConnected) { + fetch_rssi(); + *rssi = gap->conn_rssi; + + if(gap->time_rssi_sample) return furi_get_tick() - gap->time_rssi_sample; + } + return 0; +} + GapState gap_get_state(void) { GapState state; if(gap) { diff --git a/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h index 9c51c275b..8ee4b3d91 100644 --- a/targets/f7/ble_glue/gap.h +++ b/targets/f7/ble_glue/gap.h @@ -73,7 +73,7 @@ typedef struct { bool bonding_mode; GapPairing pairing_method; uint8_t mac_address[GAP_MAC_ADDR_SIZE]; - char adv_name[FURI_HAL_BT_ADV_NAME_LENGTH]; + char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; GapConnectionParamsRequest conn_param; } GapConfig; diff --git a/targets/f7/furi_hal/furi_hal_bt.c b/targets/f7/furi_hal/furi_hal_bt.c index ac6d6bb80..2b1fbf04c 100644 --- a/targets/f7/furi_hal/furi_hal_bt.c +++ b/targets/f7/furi_hal/furi_hal_bt.c @@ -402,51 +402,6 @@ uint32_t furi_hal_bt_get_conn_rssi(uint8_t* rssi) { return since; } -// API for BLE beacon plugin -bool furi_hal_bt_custom_adv_set(const uint8_t* adv_data, size_t adv_len) { - tBleStatus status = aci_gap_additional_beacon_set_data(adv_len, adv_data); - if(status) { - FURI_LOG_E(TAG, "custom_adv_set failed %d", status); - return false; - } else { - FURI_LOG_D(TAG, "custom_adv_set success"); - return true; - } -} - -bool furi_hal_bt_custom_adv_start( - uint16_t min_interval, - uint16_t max_interval, - uint8_t mac_type, - const uint8_t mac_addr[GAP_MAC_ADDR_SIZE], - uint8_t power_amp_level) { - tBleStatus status = aci_gap_additional_beacon_start( - min_interval / 0.625, // Millis to gap time - max_interval / 0.625, // Millis to gap time - 0b00000111, // All 3 channels - mac_type, - mac_addr, - power_amp_level); - if(status) { - FURI_LOG_E(TAG, "custom_adv_start failed %d", status); - return false; - } else { - FURI_LOG_D(TAG, "custom_adv_start success"); - return true; - } -} - -bool furi_hal_bt_custom_adv_stop() { - tBleStatus status = aci_gap_additional_beacon_stop(); - if(status) { - FURI_LOG_E(TAG, "custom_adv_stop failed %d", status); - return false; - } else { - FURI_LOG_D(TAG, "custom_adv_stop success"); - return true; - } -} - void furi_hal_bt_reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]) { uint8_t tmp; for(size_t i = 0; i < GAP_MAC_ADDR_SIZE / 2; i++) { @@ -456,52 +411,6 @@ void furi_hal_bt_reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]) { } } -void furi_hal_bt_set_profile_adv_name( - FuriHalBtProfile profile, - const char name[FURI_HAL_BT_ADV_NAME_LENGTH]) { - furi_assert(profile < FuriHalBtProfileNumber); - furi_assert(name); - - if(strlen(name) == 0) { - memset(&(profile_config[profile].config.adv_name[1]), 0, FURI_HAL_BT_ADV_NAME_LENGTH - 1); - } else { - profile_config[profile].config.adv_name[0] = AD_TYPE_COMPLETE_LOCAL_NAME; - strlcpy( - &(profile_config[profile].config.adv_name[1]), - name, - FURI_HAL_BT_ADV_NAME_LENGTH - 1 /* BLE symbol */); - } -} - -const char* furi_hal_bt_get_profile_adv_name(FuriHalBtProfile profile) { - furi_assert(profile < FuriHalBtProfileNumber); - return &(profile_config[profile].config.adv_name[1]); -} - -void furi_hal_bt_set_profile_mac_addr( - FuriHalBtProfile profile, - const uint8_t mac_addr[GAP_MAC_ADDR_SIZE]) { - furi_assert(profile < FuriHalBtProfileNumber); - furi_assert(mac_addr); - - memcpy(profile_config[profile].config.mac_address, mac_addr, GAP_MAC_ADDR_SIZE); -} - -const uint8_t* furi_hal_bt_get_profile_mac_addr(FuriHalBtProfile profile) { - furi_assert(profile < FuriHalBtProfileNumber); - return profile_config[profile].config.mac_address; -} - -void furi_hal_bt_set_profile_pairing_method(FuriHalBtProfile profile, GapPairing pairing_method) { - furi_assert(profile < FuriHalBtProfileNumber); - profile_config[profile].config.pairing_method = pairing_method; -} - -GapPairing furi_hal_bt_get_profile_pairing_method(FuriHalBtProfile profile) { - furi_assert(profile < FuriHalBtProfileNumber); - return profile_config[profile].config.pairing_method; -} - uint32_t furi_hal_bt_get_transmitted_packets() { uint32_t packets = 0; aci_hal_le_tx_test_packet_number(&packets); diff --git a/targets/f7/furi_hal/furi_hal_version.c b/targets/f7/furi_hal/furi_hal_version.c index 46a15bde8..375cff108 100644 --- a/targets/f7/furi_hal/furi_hal_version.c +++ b/targets/f7/furi_hal/furi_hal_version.c @@ -91,7 +91,14 @@ typedef struct { static FuriHalVersion furi_hal_version = {0}; void furi_hal_version_set_name(const char* name) { - if(name != NULL) { + uint32_t udn = LL_FLASH_GetUDN(); + if(name == NULL) { + name = version_get_custom_name(NULL); + if(name != NULL) { + udn = *((uint32_t*)name); + } + } + if(name != NULL && strlen(name)) { strlcpy(furi_hal_version.name, name, FURI_HAL_VERSION_ARRAY_NAME_LENGTH); snprintf( furi_hal_version.device_name, @@ -105,11 +112,6 @@ void furi_hal_version_set_name(const char* name) { furi_hal_version.device_name[0] = AD_TYPE_COMPLETE_LOCAL_NAME; // BLE Mac address - uint32_t udn = LL_FLASH_GetUDN(); - if(version_get_custom_name(NULL) != NULL) { - udn = *((uint32_t*)version_get_custom_name(NULL)); - } - uint32_t company_id = LL_FLASH_GetSTCompanyID(); // uint32_t device_id = LL_FLASH_GetDeviceID(); // Some flippers return 0x27 (flippers with chip revision 2003 6495) instead of 0x26 (flippers with chip revision 2001 6495) @@ -137,11 +139,7 @@ static void furi_hal_version_load_otp_v0() { furi_hal_version.board_body = otp->board_body; furi_hal_version.board_connect = otp->board_connect; - if(version_get_custom_name(NULL) != NULL) { - furi_hal_version_set_name(version_get_custom_name(NULL)); - } else { - furi_hal_version_set_name(otp->name); - } + furi_hal_version_set_name(otp->name); } static void furi_hal_version_load_otp_v1() { @@ -155,11 +153,7 @@ static void furi_hal_version_load_otp_v1() { furi_hal_version.board_color = otp->board_color; furi_hal_version.board_region = otp->board_region; - if(version_get_custom_name(NULL) != NULL) { - furi_hal_version_set_name(version_get_custom_name(NULL)); - } else { - furi_hal_version_set_name(otp->name); - } + furi_hal_version_set_name(otp->name); } static void furi_hal_version_load_otp_v2() { @@ -179,11 +173,7 @@ static void furi_hal_version_load_otp_v2() { if(otp->board_color != 0xFF) { furi_hal_version.board_color = otp->board_color; furi_hal_version.board_region = otp->board_region; - if(version_get_custom_name(NULL) != NULL) { - furi_hal_version_set_name(version_get_custom_name(NULL)); - } else { - furi_hal_version_set_name(otp->name); - } + furi_hal_version_set_name(otp->name); } else { furi_hal_version.board_color = 0; furi_hal_version.board_region = 0; diff --git a/targets/furi_hal_include/furi_hal_bt.h b/targets/furi_hal_include/furi_hal_bt.h index e5878160d..e498b1586 100644 --- a/targets/furi_hal_include/furi_hal_bt.h +++ b/targets/furi_hal_include/furi_hal_bt.h @@ -18,12 +18,6 @@ #define FURI_HAL_BT_STACK_VERSION_MINOR (12) #define FURI_HAL_BT_C2_START_TIMEOUT (1000) -#define FURI_HAL_BT_EMPTY_MAC_ADDR \ - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } - -#define FURI_HAL_BT_DEFAULT_MAC_ADDR \ - { 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 } - #ifdef __cplusplus extern "C" { #endif @@ -236,69 +230,13 @@ float furi_hal_bt_get_rssi(); */ uint32_t furi_hal_bt_get_transmitted_packets(); -// BadBT Stuff /** Reverse a MAC address byte order in-place * @param[in] mac mac address to reverse */ void furi_hal_bt_reverse_mac_addr(uint8_t mac_addr[GAP_MAC_ADDR_SIZE]); -/** Modify profile advertisement name and restart bluetooth - * @param[in] profile profile type - * @param[in] name new adv name -*/ -void furi_hal_bt_set_profile_adv_name( - FuriHalBtProfile profile, - const char name[FURI_HAL_BT_ADV_NAME_LENGTH]); - -const char* furi_hal_bt_get_profile_adv_name(FuriHalBtProfile profile); - -/** Modify profile mac address and restart bluetooth - * @param[in] profile profile type - * @param[in] mac new mac address -*/ -void furi_hal_bt_set_profile_mac_addr( - FuriHalBtProfile profile, - const uint8_t mac_addr[GAP_MAC_ADDR_SIZE]); - -const uint8_t* furi_hal_bt_get_profile_mac_addr(FuriHalBtProfile profile); - uint32_t furi_hal_bt_get_conn_rssi(uint8_t* rssi); -// API for BLE Beacon plugin -/** Set custom advertisement packet data - * @param[in] adv_data pointer to advertisement data - * @param[in] adv_len length of advertisement data - * - * @return true on success -*/ -bool furi_hal_bt_custom_adv_set(const uint8_t* adv_data, size_t adv_len); - -/** Start custom advertisement beacon - * @param[in] min_interval minimum advertisement interval (20 - 10240 ms) - * @param[in] max_interval maximum advertisement interval (20 - 10240 ms) - * @param[in] mac_type type of mac address (0x00 public, 0x01 static random) - * @param[in] mac_addr pointer to mac address - * @param[in] power_amp_level amplifier level (output dBm) (0x00 - 0x1F) - * - * @return true on success -*/ -bool furi_hal_bt_custom_adv_start( - uint16_t min_interval, - uint16_t max_interval, - uint8_t mac_type, - const uint8_t mac_addr[GAP_MAC_ADDR_SIZE], - uint8_t power_amp_level); - -/** Stop custom advertisement beacon - * - * @return true on success -*/ -bool furi_hal_bt_custom_adv_stop(); - -void furi_hal_bt_set_profile_pairing_method(FuriHalBtProfile profile, GapPairing pairing_method); - -GapPairing furi_hal_bt_get_profile_pairing_method(FuriHalBtProfile profile); - bool furi_hal_bt_is_connected(void); /** Check & switch C2 to given mode diff --git a/targets/furi_hal_include/furi_hal_region.h b/targets/furi_hal_include/furi_hal_region.h index a48d9961d..9586d51ed 100644 --- a/targets/furi_hal_include/furi_hal_region.h +++ b/targets/furi_hal_include/furi_hal_region.h @@ -46,8 +46,6 @@ bool furi_hal_region_is_provisioned(); * * 2 letter Region code according to iso 3166 standard * There are 2 extra values that we use in special cases: - * RM, whats the reason you not doing a release? - * Waiting for my commits? * - "00" - developer edition, unlocked * - "WW" - world wide, region provisioned by default * - "--" - no provisioned region diff --git a/targets/furi_hal_include/furi_hal_version.h b/targets/furi_hal_include/furi_hal_version.h index 701113a86..49f4f82cb 100644 --- a/targets/furi_hal_include/furi_hal_version.h +++ b/targets/furi_hal_include/furi_hal_version.h @@ -16,10 +16,9 @@ extern "C" { #define FURI_HAL_VERSION_NAME_LENGTH 8 #define FURI_HAL_VERSION_ARRAY_NAME_LENGTH (FURI_HAL_VERSION_NAME_LENGTH + 1) -/** BLE symbol + "Flipper " + name */ -#define FURI_HAL_VERSION_DEVICE_NAME_LENGTH (1 + 8 + FURI_HAL_VERSION_ARRAY_NAME_LENGTH) -// 18 characters + null terminator -#define FURI_HAL_BT_ADV_NAME_LENGTH (FURI_HAL_VERSION_DEVICE_NAME_LENGTH + 1) +#define FURI_HAL_BT_ADV_NAME_LENGTH (18 + 1) // 18 characters + null terminator +#define FURI_HAL_VERSION_DEVICE_NAME_LENGTH \ + (1 + FURI_HAL_BT_ADV_NAME_LENGTH) // Used for custom BT name, BLE symbol + name /** OTP Versions enum */ typedef enum { From 2d32ea68fe749b19854b528dcc94d7c13809c7d0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 19 Feb 2024 04:33:36 +0300 Subject: [PATCH 7/9] hid app merge fixes + changes by Willy-JL --- .../hid_app/assets/Like_pressed_17x17.png | Bin 3643 -> 0 bytes .../assets/Pin_back_arrow_rotated_8x10.png | Bin 959 -> 0 bytes .../assets/RoundButtonPressed_16x16.png | Bin 320 -> 0 bytes applications/system/hid_app/hid.c | 275 +++++++----------- applications/system/hid_app/hid.h | 16 +- .../system/hid_app/scenes/hid_scene.c | 30 ++ .../system/hid_app/scenes/hid_scene.h | 29 ++ .../system/hid_app/scenes/hid_scene_config.h | 3 + .../hid_app/scenes/hid_scene_exit_confirm.c | 45 +++ .../system/hid_app/scenes/hid_scene_main.c | 22 ++ .../system/hid_app/scenes/hid_scene_unpair.c | 63 ++++ applications/system/hid_app/transport_ble.c | 60 ++++ applications/system/hid_app/transport_usb.c | 61 ++++ applications/system/hid_app/views.h | 6 +- .../system/hid_app/views/hid_keynote.c | 4 +- applications/system/hid_app/views/hid_media.c | 2 +- applications/system/hid_app/views/hid_movie.c | 2 +- .../system/hid_app/views/hid_music_macos.c | 2 +- .../system/hid_app/views/hid_tikshorts.h | 14 - .../views/{hid_tikshorts.c => hid_tiktok.c} | 137 +++++---- .../system/hid_app/views/hid_tiktok.h | 14 + 21 files changed, 515 insertions(+), 270 deletions(-) delete mode 100644 applications/system/hid_app/assets/Like_pressed_17x17.png delete mode 100644 applications/system/hid_app/assets/Pin_back_arrow_rotated_8x10.png delete mode 100644 applications/system/hid_app/assets/RoundButtonPressed_16x16.png create mode 100644 applications/system/hid_app/scenes/hid_scene.c create mode 100644 applications/system/hid_app/scenes/hid_scene.h create mode 100644 applications/system/hid_app/scenes/hid_scene_config.h create mode 100644 applications/system/hid_app/scenes/hid_scene_exit_confirm.c create mode 100644 applications/system/hid_app/scenes/hid_scene_main.c create mode 100644 applications/system/hid_app/scenes/hid_scene_unpair.c create mode 100644 applications/system/hid_app/transport_ble.c create mode 100644 applications/system/hid_app/transport_usb.c delete mode 100644 applications/system/hid_app/views/hid_tikshorts.h rename applications/system/hid_app/views/{hid_tikshorts.c => hid_tiktok.c} (61%) create mode 100644 applications/system/hid_app/views/hid_tiktok.h diff --git a/applications/system/hid_app/assets/Like_pressed_17x17.png b/applications/system/hid_app/assets/Like_pressed_17x17.png deleted file mode 100644 index f5bf276f31a716a4b419995bd0631b2778f99ee9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3643 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 +#include #include "views.h" #include #include +#include "hid_icons.h" #define TAG "HidApp" @@ -13,13 +16,43 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexMedia, HidSubmenuIndexMusicMacOs, HidSubmenuIndexMovie, - HidSubmenuIndexTikShorts, + HidSubmenuIndexTikTok, HidSubmenuIndexMouse, HidSubmenuIndexMouseClicker, HidSubmenuIndexMouseJiggler, HidSubmenuIndexPushToTalk, + HidSubmenuIndexRemovePairing, }; +bool hid_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + Hid* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +bool hid_back_event_callback(void* context) { + furi_assert(context); + Hid* app = context; + FURI_LOG_D("HID", "Back event"); + app->view_id = HidViewSubmenu; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); + return true; +} + +void bt_hid_remove_pairing(Hid* app) { + Bt* bt = app->bt; + bt_disconnect(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + furi_hal_bt_stop_advertising(); + + bt_forget_bonded_devices(bt); + + furi_hal_bt_start_advertising(); +} + static void hid_submenu_callback(void* context, uint32_t index) { furi_assert(context); Hid* app = context; @@ -49,9 +82,9 @@ static void hid_submenu_callback(void* context, uint32_t index) { } else if(index == HidSubmenuIndexMouse) { app->view_id = HidViewMouse; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); - } else if(index == HidSubmenuIndexTikShorts) { - app->view_id = BtHidViewTikShorts; - view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikShorts); + } else if(index == HidSubmenuIndexTikTok) { + app->view_id = BtHidViewTikTok; + view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); } else if(index == HidSubmenuIndexMouseClicker) { app->view_id = HidViewMouseClicker; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker); @@ -61,6 +94,8 @@ static void hid_submenu_callback(void* context, uint32_t index) { } else if(index == HidSubmenuIndexPushToTalk) { app->view_id = HidViewPushToTalkMenu; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPushToTalkMenu); + } else if(index == HidSubmenuIndexRemovePairing) { + scene_manager_next_scene(app->scene_manager, HidSceneUnpair); } } @@ -68,13 +103,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_numpad_set_connected_status(hid->hid_numpad, connected); @@ -85,12 +120,7 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected); hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected); hid_ptt_set_connected_status(hid->hid_ptt, connected); - hid_tikshorts_set_connected_status(hid->hid_tikshorts, connected); -} - -static uint32_t hid_menu_view(void* context) { - UNUSED(context); - return HidViewSubmenu; + hid_tiktok_set_connected_status(hid->hid_tiktok, connected); } static uint32_t hid_exit(void* context) { @@ -103,9 +133,8 @@ static uint32_t hid_ptt_menu_view(void* context) { return HidViewPushToTalkMenu; } -Hid* hid_alloc(HidTransport transport) { +Hid* hid_alloc() { Hid* app = malloc(sizeof(Hid)); - app->transport = transport; // Gui app->gui = furi_record_open(RECORD_GUI); @@ -120,6 +149,12 @@ Hid* hid_alloc(HidTransport transport) { app->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_enable_queue(app->view_dispatcher); view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_set_navigation_event_callback(app->view_dispatcher, hid_back_event_callback); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + // Scene Manager + app->scene_manager = scene_manager_alloc(&hid_scene_handlers, app); + // Device Type Submenu view app->device_type_submenu = submenu_alloc(); submenu_add_item( @@ -146,14 +181,12 @@ Hid* hid_alloc(HidTransport transport) { app->device_type_submenu, "Movie", HidSubmenuIndexMovie, 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 / YT Shorts", - HidSubmenuIndexTikShorts, - hid_submenu_callback, - app); - } + submenu_add_item( + app->device_type_submenu, + "TikTok / YT Shorts", + HidSubmenuIndexTikTok, + hid_submenu_callback, + app); submenu_add_item( app->device_type_submenu, "Mouse Clicker", @@ -172,11 +205,18 @@ Hid* hid_alloc(HidTransport transport) { HidSubmenuIndexPushToTalk, 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)); app->view_id = HidViewSubmenu; - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); return app; } @@ -184,57 +224,56 @@ Hid* hid_app_alloc_view(void* context) { furi_assert(context); Hid* app = context; + // Dialog view + app->dialog = dialog_ex_alloc(); + view_dispatcher_add_view(app->view_dispatcher, HidViewDialog, dialog_ex_get_view(app->dialog)); + + // Popup view + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, HidViewPopup, popup_get_view(app->popup)); + // Keynote view app->hid_keynote = hid_keynote_alloc(app); - view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote)); // Keyboard view app->hid_keyboard = hid_keyboard_alloc(app); - view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard)); //Numpad keyboard view app->hid_numpad = hid_numpad_alloc(app); - view_set_previous_callback(hid_numpad_get_view(app->hid_numpad), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewNumpad, hid_numpad_get_view(app->hid_numpad)); // Media view app->hid_media = hid_media_alloc(app); - view_set_previous_callback(hid_media_get_view(app->hid_media), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media)); // Music MacOs view app->hid_music_macos = hid_music_macos_alloc(app); - view_set_previous_callback(hid_music_macos_get_view(app->hid_music_macos), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMusicMacOs, hid_music_macos_get_view(app->hid_music_macos)); // Movie view app->hid_movie = hid_movie_alloc(app); - view_set_previous_callback(hid_movie_get_view(app->hid_movie), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMovie, hid_movie_get_view(app->hid_movie)); - // TikTok / YT Shorts view - app->hid_tikshorts = hid_tikshorts_alloc(app); - view_set_previous_callback(hid_tikshorts_get_view(app->hid_tikshorts), hid_menu_view); + // TikTok view + app->hid_tiktok = hid_tiktok_alloc(app); view_dispatcher_add_view( - app->view_dispatcher, BtHidViewTikShorts, hid_tikshorts_get_view(app->hid_tikshorts)); + app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok)); // Mouse view app->hid_mouse = hid_mouse_alloc(app); - view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); // Mouse clicker view app->hid_mouse_clicker = hid_mouse_clicker_alloc(app); - view_set_previous_callback(hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouseClicker, @@ -242,7 +281,6 @@ Hid* hid_app_alloc_view(void* context) { // Mouse jiggler view app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); - view_set_previous_callback(hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouseJiggler, @@ -250,7 +288,6 @@ Hid* hid_app_alloc_view(void* context) { // PushToTalk view app->hid_ptt_menu = hid_ptt_menu_alloc(app); - view_set_previous_callback(hid_ptt_menu_get_view(app->hid_ptt_menu), hid_menu_view); view_dispatcher_add_view( app->view_dispatcher, HidViewPushToTalkMenu, hid_ptt_menu_get_view(app->hid_ptt_menu)); app->hid_ptt = hid_ptt_alloc(app); @@ -265,13 +302,16 @@ 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); + view_dispatcher_remove_view(app->view_dispatcher, HidViewDialog); + dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, HidViewPopup); + popup_free(app->popup); view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); hid_keynote_free(app->hid_keynote); view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard); @@ -294,8 +334,9 @@ void hid_free(Hid* app) { hid_ptt_menu_free(app->hid_ptt_menu); view_dispatcher_remove_view(app->view_dispatcher, HidViewPushToTalk); hid_ptt_free(app->hid_ptt); - view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikShorts); - hid_tikshorts_free(app->hid_tikshorts); + view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); + hid_tiktok_free(app->hid_tiktok); + scene_manager_free(app->scene_manager); view_dispatcher_free(app->view_dispatcher); // Close records @@ -310,132 +351,12 @@ 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); + FURI_LOG_D("HID", "Starting as USB app"); + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); @@ -444,6 +365,8 @@ int32_t hid_usb_app(void* p) { dolphin_deed(DolphinDeedPluginStart); + scene_manager_next_scene(app->scene_manager, HidSceneMain); + view_dispatcher_run(app->view_dispatcher); furi_hal_usb_set_config(usb_mode_prev, NULL); @@ -455,9 +378,11 @@ 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); + FURI_LOG_D("HID", "Starting as BLE app"); + bt_disconnect(app->bt); // Wait 2nd core to update nvm storage @@ -475,13 +400,17 @@ 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); dolphin_deed(DolphinDeedPluginStart); + scene_manager_next_scene(app->scene_manager, HidSceneMain); + view_dispatcher_run(app->view_dispatcher); bt_set_status_changed_callback(app->bt, NULL, NULL); @@ -493,7 +422,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 3c0f0ae79..70a73e2ec 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -2,14 +2,16 @@ #include #include -#include #include #include +#include + #include #include #include #include +#include #include #include @@ -25,10 +27,12 @@ #include "views/hid_mouse.h" #include "views/hid_mouse_clicker.h" #include "views/hid_mouse_jiggler.h" -#include "views/hid_tikshorts.h" +#include "views/hid_tiktok.h" #include "views/hid_ptt.h" #include "views/hid_ptt_menu.h" +#include "scenes/hid_scene.h" + #define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" typedef enum { @@ -39,12 +43,15 @@ typedef enum { typedef struct Hid Hid; struct Hid { + FuriHalBleProfileBase* ble_hid_profile; Bt* bt; Gui* gui; NotificationApp* notifications; ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; Submenu* device_type_submenu; DialogEx* dialog; + Popup* popup; HidKeynote* hid_keynote; HidKeyboard* hid_keyboard; HidNumpad* hid_numpad; @@ -54,13 +61,14 @@ struct Hid { HidMouse* hid_mouse; HidMouseClicker* hid_mouse_clicker; HidMouseJiggler* hid_mouse_jiggler; - HidTikShorts* hid_tikshorts; + HidTikTok* hid_tiktok; HidPushToTalk* hid_ptt; HidPushToTalkMenu* hid_ptt_menu; HidTransport transport; uint32_t view_id; }; +void bt_hid_remove_pairing(Hid* app); void hid_hal_keyboard_press(Hid* instance, uint16_t event); void hid_hal_keyboard_release(Hid* instance, uint16_t event); @@ -74,4 +82,4 @@ void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy); void hid_hal_mouse_scroll(Hid* instance, int8_t delta); void hid_hal_mouse_press(Hid* instance, uint16_t event); void hid_hal_mouse_release(Hid* instance, uint16_t event); -void hid_hal_mouse_release_all(Hid* instance); +void hid_hal_mouse_release_all(Hid* instance); \ No newline at end of file diff --git a/applications/system/hid_app/scenes/hid_scene.c b/applications/system/hid_app/scenes/hid_scene.c new file mode 100644 index 000000000..89399a809 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene.c @@ -0,0 +1,30 @@ +#include "hid_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const hid_on_enter_handlers[])(void*) = { +#include "hid_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 hid_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "hid_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 hid_on_exit_handlers[])(void* context) = { +#include "hid_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers hid_scene_handlers = { + .on_enter_handlers = hid_on_enter_handlers, + .on_event_handlers = hid_on_event_handlers, + .on_exit_handlers = hid_on_exit_handlers, + .scene_num = HidSceneNum, +}; diff --git a/applications/system/hid_app/scenes/hid_scene.h b/applications/system/hid_app/scenes/hid_scene.h new file mode 100644 index 000000000..9a2e6bb32 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) HidScene##id, +typedef enum { +#include "hid_scene_config.h" + HidSceneNum, +} HidScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers hid_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "hid_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 "hid_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 "hid_scene_config.h" +#undef ADD_SCENE diff --git a/applications/system/hid_app/scenes/hid_scene_config.h b/applications/system/hid_app/scenes/hid_scene_config.h new file mode 100644 index 000000000..8f3a788d1 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(hid, main, Main) +ADD_SCENE(hid, unpair, Unpair) +ADD_SCENE(hid, exit_confirm, ExitConfirm) \ No newline at end of file diff --git a/applications/system/hid_app/scenes/hid_scene_exit_confirm.c b/applications/system/hid_app/scenes/hid_scene_exit_confirm.c new file mode 100644 index 000000000..94e783e93 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_exit_confirm.c @@ -0,0 +1,45 @@ +#include "../hid.h" +#include "../views.h" + +static void hid_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + Hid* app = context; + if(result == DialogExResultLeft) { + view_dispatcher_stop(app->view_dispatcher); + } else if(result == DialogExResultRight) { + scene_manager_previous_scene(app->scene_manager); + } else if(result == DialogExResultCenter) { + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, HidSceneMain); + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); + } +} + +void hid_scene_exit_confirm_on_enter(void* context) { + Hid* app = context; + + // Exit dialog view + dialog_ex_reset(app->dialog); + dialog_ex_set_result_callback(app->dialog, hid_scene_exit_confirm_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_left_button_text(app->dialog, "Exit"); + dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); + dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); + + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewDialog); +} + +bool hid_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void hid_scene_exit_confirm_on_exit(void* context) { + Hid* app = context; + + dialog_ex_reset(app->dialog); +} diff --git a/applications/system/hid_app/scenes/hid_scene_main.c b/applications/system/hid_app/scenes/hid_scene_main.c new file mode 100644 index 000000000..6c4a11682 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_main.c @@ -0,0 +1,22 @@ +#include "../hid.h" +#include "../views.h" + +void hid_scene_main_on_enter(void* context) { + Hid* app = context; + + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); +} + +bool hid_scene_main_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void hid_scene_main_on_exit(void* context) { + Hid* app = context; + UNUSED(app); +} diff --git a/applications/system/hid_app/scenes/hid_scene_unpair.c b/applications/system/hid_app/scenes/hid_scene_unpair.c new file mode 100644 index 000000000..7b0bbd9e6 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_unpair.c @@ -0,0 +1,63 @@ +#include "../hid.h" +#include "../views.h" +#include "hid_icons.h" + +static void hid_scene_unpair_dialog_callback(DialogExResult result, void* context) { + Hid* app = context; + + if(result == DialogExResultRight) { + // Unpair all devices + bt_hid_remove_pairing(app); + + // Show popup + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPopup); + } else if(result == DialogExResultLeft) { + scene_manager_previous_scene(app->scene_manager); + } +} + +void hid_scene_unpair_popup_callback(void* context) { + Hid* app = context; + + scene_manager_previous_scene(app->scene_manager); +} + +void hid_scene_unpair_on_enter(void* context) { + Hid* app = context; + + // Un-pair dialog view + dialog_ex_reset(app->dialog); + dialog_ex_set_result_callback(app->dialog, hid_scene_unpair_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_header(app->dialog, "Unpair All Devices?", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_text( + app->dialog, "All previous pairings\nwill be lost!", 64, 22, AlignCenter, AlignTop); + dialog_ex_set_left_button_text(app->dialog, "Back"); + dialog_ex_set_right_button_text(app->dialog, "Unpair"); + + // Un-pair success popup view + popup_set_icon(app->popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(app->popup, "Done", 14, 15, AlignLeft, AlignTop); + popup_set_timeout(app->popup, 1500); + popup_set_context(app->popup, app); + popup_set_callback(app->popup, hid_scene_unpair_popup_callback); + popup_enable_timeout(app->popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewDialog); +} + +bool hid_scene_unpair_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void hid_scene_unpair_on_exit(void* context) { + Hid* app = context; + + dialog_ex_reset(app->dialog); + popup_reset(app->popup); +} diff --git a/applications/system/hid_app/transport_ble.c b/applications/system/hid_app/transport_ble.c new file mode 100644 index 000000000..92a260add --- /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 000000000..882a715a5 --- /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.h b/applications/system/hid_app/views.h index 583ddb4b9..6fc8531ae 100644 --- a/applications/system/hid_app/views.h +++ b/applications/system/hid_app/views.h @@ -9,8 +9,10 @@ typedef enum { HidViewMouse, HidViewMouseClicker, HidViewMouseJiggler, - BtHidViewTikShorts, + BtHidViewTikTok, HidViewPushToTalk, HidViewPushToTalkMenu, HidViewPushToTalkHelp, -} HidView; + HidViewDialog, + HidViewPopup, +} HidView; \ No newline at end of file diff --git a/applications/system/hid_app/views/hid_keynote.c b/applications/system/hid_app/views/hid_keynote.c index 7d0e125d7..543363bf6 100644 --- a/applications/system/hid_app/views/hid_keynote.c +++ b/applications/system/hid_app/views/hid_keynote.c @@ -116,16 +116,16 @@ static void hid_keynote_draw_vertical_callback(Canvas* canvas, void* context) { HidKeynoteModel* model = context; // Header - canvas_set_font(canvas, FontPrimary); if(model->transport == HidTransportBle) { if(model->connected) { canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); } else { canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); } - + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 20, 3, AlignLeft, AlignTop, "Keynote"); } else { + canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 12, 3, AlignLeft, AlignTop, "Keynote"); } diff --git a/applications/system/hid_app/views/hid_media.c b/applications/system/hid_app/views/hid_media.c index 849c511d9..af213eb03 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/applications/system/hid_app/views/hid_movie.c b/applications/system/hid_app/views/hid_movie.c index 229f7299a..0a91b7f3b 100644 --- a/applications/system/hid_app/views/hid_movie.c +++ b/applications/system/hid_app/views/hid_movie.c @@ -1,7 +1,7 @@ #include "hid_movie.h" #include -#include #include +#include #include #include "../hid.h" diff --git a/applications/system/hid_app/views/hid_music_macos.c b/applications/system/hid_app/views/hid_music_macos.c index d5dd4dab3..68d738fd7 100644 --- a/applications/system/hid_app/views/hid_music_macos.c +++ b/applications/system/hid_app/views/hid_music_macos.c @@ -1,7 +1,7 @@ #include "hid_music_macos.h" #include -#include #include +#include #include #include "../hid.h" diff --git a/applications/system/hid_app/views/hid_tikshorts.h b/applications/system/hid_app/views/hid_tikshorts.h deleted file mode 100644 index 5604962ee..000000000 --- a/applications/system/hid_app/views/hid_tikshorts.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -typedef struct Hid Hid; -typedef struct HidTikShorts HidTikShorts; - -HidTikShorts* hid_tikshorts_alloc(Hid* bt_hid); - -void hid_tikshorts_free(HidTikShorts* hid_tikshorts); - -View* hid_tikshorts_get_view(HidTikShorts* hid_tikshorts); - -void hid_tikshorts_set_connected_status(HidTikShorts* hid_tikshorts, bool connected); diff --git a/applications/system/hid_app/views/hid_tikshorts.c b/applications/system/hid_app/views/hid_tiktok.c similarity index 61% rename from applications/system/hid_app/views/hid_tikshorts.c rename to applications/system/hid_app/views/hid_tiktok.c index 6965c1331..e9198791d 100644 --- a/applications/system/hid_app/views/hid_tikshorts.c +++ b/applications/system/hid_app/views/hid_tiktok.c @@ -1,12 +1,12 @@ -#include "hid_tikshorts.h" +#include "hid_tiktok.h" #include "../hid.h" #include #include "hid_icons.h" -#define TAG "HidTikShorts" +#define TAG "HidTikTok" -struct HidTikShorts { +struct HidTikTok { View* view; Hid* hid; }; @@ -21,11 +21,11 @@ typedef struct { bool is_cursor_set; bool back_mouse_pressed; HidTransport transport; -} HidTikShortsModel; +} HidTikTokModel; -static void hid_tikshorts_draw_callback(Canvas* canvas, void* context) { +static void hid_tiktok_draw_callback(Canvas* canvas, void* context) { furi_assert(context); - HidTikShortsModel* model = context; + HidTikTokModel* model = context; // Header if(model->transport == HidTransportBle) { @@ -110,32 +110,30 @@ static void hid_tikshorts_draw_callback(Canvas* canvas, void* context) { elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); } -static void hid_tikshorts_reset_cursor(HidTikShorts* hid_tikshorts) { +static void hid_tiktok_reset_cursor(HidTikTok* hid_tiktok) { // Set cursor to the phone's left up corner // Delays to guarantee one packet per connection interval for(size_t i = 0; i < 8; i++) { - hid_hal_mouse_move(hid_tikshorts->hid, -127, -127); + hid_hal_mouse_move(hid_tiktok->hid, -127, -127); furi_delay_ms(50); } // Move cursor from the corner - hid_hal_mouse_move(hid_tikshorts->hid, 20, 120); + hid_hal_mouse_move(hid_tiktok->hid, 20, 120); furi_delay_ms(50); } -static void hid_tikshorts_process_press( - HidTikShorts* hid_tikshorts, - HidTikShortsModel* model, - InputEvent* event) { +static void + hid_tiktok_process_press(HidTikTok* hid_tiktok, HidTikTokModel* model, InputEvent* event) { if(event->key == InputKeyUp) { model->up_pressed = true; } else if(event->key == InputKeyDown) { model->down_pressed = true; } else if(event->key == InputKeyLeft) { model->left_pressed = true; - hid_hal_consumer_key_press(hid_tikshorts->hid, HID_CONSUMER_VOLUME_DECREMENT); + hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyRight) { model->right_pressed = true; - hid_hal_consumer_key_press(hid_tikshorts->hid, HID_CONSUMER_VOLUME_INCREMENT); + hid_hal_consumer_key_press(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT); } else if(event->key == InputKeyOk) { model->ok_pressed = true; } else if(event->key == InputKeyBack) { @@ -143,20 +141,18 @@ static void hid_tikshorts_process_press( } } -static void hid_tikshorts_process_release( - HidTikShorts* hid_tikshorts, - HidTikShortsModel* model, - InputEvent* event) { +static void + hid_tiktok_process_release(HidTikTok* hid_tiktok, HidTikTokModel* model, InputEvent* event) { if(event->key == InputKeyUp) { model->up_pressed = false; } else if(event->key == InputKeyDown) { model->down_pressed = false; } else if(event->key == InputKeyLeft) { model->left_pressed = false; - hid_hal_consumer_key_release(hid_tikshorts->hid, HID_CONSUMER_VOLUME_DECREMENT); + hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_DECREMENT); } else if(event->key == InputKeyRight) { model->right_pressed = false; - hid_hal_consumer_key_release(hid_tikshorts->hid, HID_CONSUMER_VOLUME_INCREMENT); + hid_hal_consumer_key_release(hid_tiktok->hid, HID_CONSUMER_VOLUME_INCREMENT); } else if(event->key == InputKeyOk) { model->ok_pressed = false; } else if(event->key == InputKeyBack) { @@ -164,61 +160,61 @@ static void hid_tikshorts_process_release( } } -static bool hid_tikshorts_input_callback(InputEvent* event, void* context) { +static bool hid_tiktok_input_callback(InputEvent* event, void* context) { furi_assert(context); - HidTikShorts* hid_tikshorts = context; + HidTikTok* hid_tiktok = context; bool consumed = false; with_view_model( - hid_tikshorts->view, - HidTikShortsModel * model, + hid_tiktok->view, + HidTikTokModel * model, { if(event->type == InputTypePress) { - hid_tikshorts_process_press(hid_tikshorts, model, event); + hid_tiktok_process_press(hid_tiktok, model, event); if(model->connected && !model->is_cursor_set) { - hid_tikshorts_reset_cursor(hid_tikshorts); + hid_tiktok_reset_cursor(hid_tiktok); model->is_cursor_set = true; } consumed = true; } else if(event->type == InputTypeRelease) { - hid_tikshorts_process_release(hid_tikshorts, model, event); + hid_tiktok_process_release(hid_tiktok, model, event); consumed = true; } else if(event->type == InputTypeShort) { if(event->key == InputKeyOk) { - hid_hal_mouse_press(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); furi_delay_ms(25); - hid_hal_mouse_release(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); furi_delay_ms(100); - hid_hal_mouse_press(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); furi_delay_ms(25); - hid_hal_mouse_release(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT); - consumed = true; - } else if(event->key == InputKeyDown) { - // Swipe to next video - hid_hal_mouse_scroll(hid_tikshorts->hid, 6); - hid_hal_mouse_scroll(hid_tikshorts->hid, 8); - hid_hal_mouse_scroll(hid_tikshorts->hid, 10); - hid_hal_mouse_scroll(hid_tikshorts->hid, 8); - hid_hal_mouse_scroll(hid_tikshorts->hid, 6); + hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); consumed = true; } else if(event->key == InputKeyUp) { // Swipe to previous video - hid_hal_mouse_scroll(hid_tikshorts->hid, -6); - hid_hal_mouse_scroll(hid_tikshorts->hid, -8); - hid_hal_mouse_scroll(hid_tikshorts->hid, -10); - hid_hal_mouse_scroll(hid_tikshorts->hid, -8); - hid_hal_mouse_scroll(hid_tikshorts->hid, -6); + hid_hal_mouse_scroll(hid_tiktok->hid, -6); + hid_hal_mouse_scroll(hid_tiktok->hid, -8); + hid_hal_mouse_scroll(hid_tiktok->hid, -10); + hid_hal_mouse_scroll(hid_tiktok->hid, -8); + hid_hal_mouse_scroll(hid_tiktok->hid, -6); + consumed = true; + } else if(event->key == InputKeyDown) { + // Swipe to next video + hid_hal_mouse_scroll(hid_tiktok->hid, 6); + hid_hal_mouse_scroll(hid_tiktok->hid, 8); + hid_hal_mouse_scroll(hid_tiktok->hid, 10); + hid_hal_mouse_scroll(hid_tiktok->hid, 8); + hid_hal_mouse_scroll(hid_tiktok->hid, 6); consumed = true; } else if(event->key == InputKeyBack) { // Pause - hid_hal_mouse_press(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); furi_delay_ms(50); - hid_hal_mouse_release(hid_tikshorts->hid, HID_MOUSE_BTN_LEFT); + hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); consumed = true; } } else if(event->type == InputTypeLong) { if(event->key == InputKeyBack) { - hid_hal_consumer_key_release_all(hid_tikshorts->hid); + hid_hal_consumer_key_release_all(hid_tiktok->hid); model->is_cursor_set = false; consumed = false; } @@ -229,40 +225,37 @@ static bool hid_tikshorts_input_callback(InputEvent* event, void* context) { return consumed; } -HidTikShorts* hid_tikshorts_alloc(Hid* bt_hid) { - HidTikShorts* hid_tikshorts = malloc(sizeof(HidTikShorts)); - hid_tikshorts->hid = bt_hid; - hid_tikshorts->view = view_alloc(); - view_set_context(hid_tikshorts->view, hid_tikshorts); - view_allocate_model(hid_tikshorts->view, ViewModelTypeLocking, sizeof(HidTikShortsModel)); - view_set_draw_callback(hid_tikshorts->view, hid_tikshorts_draw_callback); - view_set_input_callback(hid_tikshorts->view, hid_tikshorts_input_callback); +HidTikTok* hid_tiktok_alloc(Hid* bt_hid) { + HidTikTok* hid_tiktok = malloc(sizeof(HidTikTok)); + hid_tiktok->hid = bt_hid; + hid_tiktok->view = view_alloc(); + view_set_context(hid_tiktok->view, hid_tiktok); + view_allocate_model(hid_tiktok->view, ViewModelTypeLocking, sizeof(HidTikTokModel)); + view_set_draw_callback(hid_tiktok->view, hid_tiktok_draw_callback); + view_set_input_callback(hid_tiktok->view, hid_tiktok_input_callback); with_view_model( - hid_tikshorts->view, - HidTikShortsModel * model, - { model->transport = bt_hid->transport; }, - true); + hid_tiktok->view, HidTikTokModel * model, { model->transport = bt_hid->transport; }, true); - return hid_tikshorts; + return hid_tiktok; } -void hid_tikshorts_free(HidTikShorts* hid_tikshorts) { - furi_assert(hid_tikshorts); - view_free(hid_tikshorts->view); - free(hid_tikshorts); +void hid_tiktok_free(HidTikTok* hid_tiktok) { + furi_assert(hid_tiktok); + view_free(hid_tiktok->view); + free(hid_tiktok); } -View* hid_tikshorts_get_view(HidTikShorts* hid_tikshorts) { - furi_assert(hid_tikshorts); - return hid_tikshorts->view; +View* hid_tiktok_get_view(HidTikTok* hid_tiktok) { + furi_assert(hid_tiktok); + return hid_tiktok->view; } -void hid_tikshorts_set_connected_status(HidTikShorts* hid_tikshorts, bool connected) { - furi_assert(hid_tikshorts); +void hid_tiktok_set_connected_status(HidTikTok* hid_tiktok, bool connected) { + furi_assert(hid_tiktok); with_view_model( - hid_tikshorts->view, - HidTikShortsModel * model, + hid_tiktok->view, + HidTikTokModel * model, { model->connected = connected; model->is_cursor_set = false; diff --git a/applications/system/hid_app/views/hid_tiktok.h b/applications/system/hid_app/views/hid_tiktok.h new file mode 100644 index 000000000..b2efc3692 --- /dev/null +++ b/applications/system/hid_app/views/hid_tiktok.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct Hid Hid; +typedef struct HidTikTok HidTikTok; + +HidTikTok* hid_tiktok_alloc(Hid* bt_hid); + +void hid_tiktok_free(HidTikTok* hid_tiktok); + +View* hid_tiktok_get_view(HidTikTok* hid_tiktok); + +void hid_tiktok_set_connected_status(HidTikTok* hid_tiktok, bool connected); From 21962da7151f92eea2c4a12431294e60a91375d7 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 21 Feb 2024 00:29:50 +0300 Subject: [PATCH 8/9] Subghz Fix led blink on decode raw > signal info by Willy-JL --- applications/main/subghz/scenes/subghz_scene_receiver_info.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 5cf79eabc..0b0c5d376 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -111,7 +111,8 @@ void subghz_scene_receiver_info_on_enter(void* context) { subghz_scene_receiver_info_draw_widget(subghz); - if(!subghz_history_get_text_space_left(subghz->history, NULL)) { + if(!subghz_history_get_text_space_left(subghz->history, NULL) && + !scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneDecodeRAW)) { subghz->state_notifications = SubGhzNotificationStateRx; } } From 113afc0d0f5e459fab1c5fe85ba79b940694c555 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 21 Feb 2024 00:40:29 +0300 Subject: [PATCH 9/9] usbdisk support in js by Willy-JL --- applications/system/js_app/application.fam | 8 + .../examples/apps/Scripts/badusb_demo.js | 3 + .../js_app/examples/apps/Scripts/usbdisk.js | 10 + applications/system/js_app/js_app.c | 2 +- .../system/js_app/modules/js_badusb.c | 35 +- .../js_app/modules/js_usbdisk/js_usbdisk.c | 197 +++++++ .../modules/js_usbdisk/mass_storage_scsi.c | 266 ++++++++++ .../modules/js_usbdisk/mass_storage_scsi.h | 56 ++ .../modules/js_usbdisk/mass_storage_usb.c | 482 ++++++++++++++++++ .../modules/js_usbdisk/mass_storage_usb.h | 9 + 10 files changed, 1060 insertions(+), 8 deletions(-) create mode 100644 applications/system/js_app/examples/apps/Scripts/usbdisk.js create mode 100644 applications/system/js_app/modules/js_usbdisk/js_usbdisk.c create mode 100644 applications/system/js_app/modules/js_usbdisk/mass_storage_scsi.c create mode 100644 applications/system/js_app/modules/js_usbdisk/mass_storage_scsi.h create mode 100644 applications/system/js_app/modules/js_usbdisk/mass_storage_usb.c create mode 100644 applications/system/js_app/modules/js_usbdisk/mass_storage_usb.h diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 5716234dc..b586f1623 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -39,3 +39,11 @@ App( requires=["js_app"], sources=["modules/js_serial.c"], ) + +App( + appid="js_usbdisk", + apptype=FlipperAppType.PLUGIN, + entry_point="js_usbdisk_ep", + requires=["js_app"], + sources=["modules/js_usbdisk/*.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js index 21090f603..bbeedd445 100644 --- a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js +++ b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js @@ -31,3 +31,6 @@ if (badusb.isConnected()) { print("USB not connected"); notify.error(); } + +// Optional, but allows to interchange with usbdisk +badusb.quit(); \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/usbdisk.js b/applications/system/js_app/examples/apps/Scripts/usbdisk.js new file mode 100644 index 000000000..7d148ab4c --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/usbdisk.js @@ -0,0 +1,10 @@ +let usbdisk = require("usbdisk"); +print("Starting UsbDisk..."); +usbdisk.start("/ext/apps_data/mass_storage/128MB.img"); +print("Started, waiting until ejected..."); +while (!usbdisk.wasEjected()) { + delay(1000); +} +print("Ejected, stopping UsbDisk..."); +usbdisk.stop(); +print("Done"); \ No newline at end of file diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index e99cc3249..1c1a2ae3c 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -97,7 +97,7 @@ static void js_app_free(JsApp* app) { int32_t js_app(void* arg) { JsApp* app = js_app_alloc(); - FuriString* script_path = furi_string_alloc_set(APP_ASSETS_PATH()); + FuriString* script_path = furi_string_alloc_set(EXT_PATH("apps/Scripts")); do { if(arg != NULL && strlen(arg) > 0) { furi_string_set(script_path, (const char*)arg); diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 6b19faea2..648632f51 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -54,6 +54,16 @@ static const struct { {"F12", HID_KEYBOARD_F12}, }; +static void js_badusb_quit_free(JsBadusbInst* badusb) { + if(badusb->usb_if_prev) { + furi_hal_hid_kb_release_all(); + furi_check(furi_hal_usb_set_config(badusb->usb_if_prev, NULL)); + } + if(badusb->hid_cfg) { + free(badusb->hid_cfg); + } +} + static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConfig* hid_cfg) { if(!mjs_is_object(arg)) { return false; @@ -130,6 +140,22 @@ static void js_badusb_setup(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } +static void js_badusb_quit(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + js_badusb_quit_free(badusb); + + mjs_return(mjs, MJS_UNDEFINED); +} + static void js_badusb_is_connected(struct mjs* mjs) { mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); @@ -371,6 +397,7 @@ static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t badusb_obj = mjs_mk_object(mjs); mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb)); mjs_set(mjs, badusb_obj, "setup", ~0, MJS_MK_FN(js_badusb_setup)); + mjs_set(mjs, badusb_obj, "quit", ~0, MJS_MK_FN(js_badusb_quit)); mjs_set(mjs, badusb_obj, "isConnected", ~0, MJS_MK_FN(js_badusb_is_connected)); mjs_set(mjs, badusb_obj, "press", ~0, MJS_MK_FN(js_badusb_press)); mjs_set(mjs, badusb_obj, "hold", ~0, MJS_MK_FN(js_badusb_hold)); @@ -383,13 +410,7 @@ static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { static void js_badusb_destroy(void* inst) { JsBadusbInst* badusb = inst; - if(badusb->usb_if_prev) { - furi_hal_hid_kb_release_all(); - furi_check(furi_hal_usb_set_config(badusb->usb_if_prev, NULL)); - } - if(badusb->hid_cfg) { - free(badusb->hid_cfg); - } + js_badusb_quit_free(badusb); free(badusb); } diff --git a/applications/system/js_app/modules/js_usbdisk/js_usbdisk.c b/applications/system/js_app/modules/js_usbdisk/js_usbdisk.c new file mode 100644 index 000000000..8ff744b84 --- /dev/null +++ b/applications/system/js_app/modules/js_usbdisk/js_usbdisk.c @@ -0,0 +1,197 @@ +#include "../../js_modules.h" +#include +#include "mass_storage_usb.h" + +#define TAG "JsUsbdisk" + +typedef struct { + File* file; + char* path; + MassStorageUsb* usb; + bool was_ejected; +} JsUsbdiskInst; + +static bool file_read( + void* ctx, + uint32_t lba, + uint16_t count, + uint8_t* out, + uint32_t* out_len, + uint32_t out_cap) { + JsUsbdiskInst* usbdisk = ctx; + FURI_LOG_T(TAG, "file_read lba=%08lX count=%04X out_cap=%08lX", lba, count, out_cap); + if(!storage_file_seek(usbdisk->file, lba * SCSI_BLOCK_SIZE, true)) { + FURI_LOG_W(TAG, "seek failed"); + return false; + } + uint16_t clamp = MIN(out_cap, count * SCSI_BLOCK_SIZE); + *out_len = storage_file_read(usbdisk->file, out, clamp); + FURI_LOG_T(TAG, "%lu/%lu", *out_len, count * SCSI_BLOCK_SIZE); + return *out_len == clamp; +} + +static bool file_write(void* ctx, uint32_t lba, uint16_t count, uint8_t* buf, uint32_t len) { + JsUsbdiskInst* usbdisk = ctx; + FURI_LOG_T(TAG, "file_write lba=%08lX count=%04X len=%08lX", lba, count, len); + if(len != count * SCSI_BLOCK_SIZE) { + FURI_LOG_W(TAG, "bad write params count=%u len=%lu", count, len); + return false; + } + if(!storage_file_seek(usbdisk->file, lba * SCSI_BLOCK_SIZE, true)) { + FURI_LOG_W(TAG, "seek failed"); + return false; + } + return storage_file_write(usbdisk->file, buf, len) == len; +} + +static uint32_t file_num_blocks(void* ctx) { + JsUsbdiskInst* usbdisk = ctx; + return storage_file_size(usbdisk->file) / SCSI_BLOCK_SIZE; +} + +static void file_eject(void* ctx) { + JsUsbdiskInst* usbdisk = ctx; + FURI_LOG_D(TAG, "EJECT"); + usbdisk->was_ejected = true; +} + +static void js_usbdisk_internal_stop_free(JsUsbdiskInst* usbdisk) { + if(usbdisk->usb) { + mass_storage_usb_stop(usbdisk->usb); + usbdisk->usb = NULL; + } + if(usbdisk->file) { + storage_file_free(usbdisk->file); + furi_record_close(RECORD_STORAGE); + usbdisk->file = NULL; + } + if(usbdisk->path) { + free(usbdisk->path); + usbdisk->path = NULL; + } +} + +static void js_usbdisk_start(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUsbdiskInst* usbdisk = mjs_get_ptr(mjs, obj_inst); + furi_assert(usbdisk); + + if(usbdisk->usb) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "SCSI is already started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + const char* error = NULL; + do { + if(mjs_nargs(mjs) != 1) { + error = "Wrong argument count"; + break; + } + + mjs_val_t path_arg = mjs_arg(mjs, 0); + if(!mjs_is_string(path_arg)) { + error = "Path must be a string"; + break; + } + + size_t path_len = 0; + const char* path = mjs_get_string(mjs, &path_arg, &path_len); + if((path_len == 0) || (path == NULL)) { + error = "Bad path argument"; + break; + } + usbdisk->path = strdup(path); + + usbdisk->file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(!storage_file_open( + usbdisk->file, usbdisk->path, FSAM_READ | FSAM_WRITE, FSOM_OPEN_EXISTING)) { + error = storage_file_get_error_desc(usbdisk->file); + break; + } + } while(0); + + if(error) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); + js_usbdisk_internal_stop_free(usbdisk); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + SCSIDeviceFunc fn = { + .ctx = usbdisk, + .read = file_read, + .write = file_write, + .num_blocks = file_num_blocks, + .eject = file_eject, + }; + + furi_hal_usb_unlock(); + usbdisk->was_ejected = false; + usbdisk->usb = mass_storage_usb_start(usbdisk->path, fn); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_usbdisk_was_ejected(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUsbdiskInst* usbdisk = mjs_get_ptr(mjs, obj_inst); + furi_assert(usbdisk); + + if(!usbdisk->usb) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "SCSI is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + mjs_return(mjs, mjs_mk_boolean(mjs, usbdisk->was_ejected)); +} + +static void js_usbdisk_stop(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUsbdiskInst* usbdisk = mjs_get_ptr(mjs, obj_inst); + furi_assert(usbdisk); + + if(!usbdisk->usb) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "SCSI is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + js_usbdisk_internal_stop_free(usbdisk); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void* js_usbdisk_create(struct mjs* mjs, mjs_val_t* object) { + JsUsbdiskInst* usbdisk = malloc(sizeof(JsUsbdiskInst)); + mjs_val_t usbdisk_obj = mjs_mk_object(mjs); + mjs_set(mjs, usbdisk_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, usbdisk)); + mjs_set(mjs, usbdisk_obj, "start", ~0, MJS_MK_FN(js_usbdisk_start)); + mjs_set(mjs, usbdisk_obj, "stop", ~0, MJS_MK_FN(js_usbdisk_stop)); + mjs_set(mjs, usbdisk_obj, "wasEjected", ~0, MJS_MK_FN(js_usbdisk_was_ejected)); + *object = usbdisk_obj; + return usbdisk; +} + +static void js_usbdisk_destroy(void* inst) { + JsUsbdiskInst* usbdisk = inst; + js_usbdisk_internal_stop_free(usbdisk); + free(usbdisk); +} + +static const JsModuleDescriptor js_usbdisk_desc = { + "usbdisk", + js_usbdisk_create, + js_usbdisk_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_usbdisk_desc, +}; + +const FlipperAppPluginDescriptor* js_usbdisk_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_usbdisk/mass_storage_scsi.c b/applications/system/js_app/modules/js_usbdisk/mass_storage_scsi.c new file mode 100644 index 000000000..c1efacf8e --- /dev/null +++ b/applications/system/js_app/modules/js_usbdisk/mass_storage_scsi.c @@ -0,0 +1,266 @@ +#include "mass_storage_scsi.h" + +#include + +#define TAG "MassStorageSCSI" + +#define SCSI_TEST_UNIT_READY (0x00) +#define SCSI_REQUEST_SENSE (0x03) +#define SCSI_INQUIRY (0x12) +#define SCSI_READ_FORMAT_CAPACITIES (0x23) +#define SCSI_READ_CAPACITY_10 (0x25) +#define SCSI_MODE_SENSE_6 (0x1A) +#define SCSI_READ_10 (0x28) +#define SCSI_PREVENT_MEDIUM_REMOVAL (0x1E) +#define SCSI_START_STOP_UNIT (0x1B) +#define SCSI_WRITE_10 (0x2A) + +bool scsi_cmd_start(SCSISession* scsi, uint8_t* cmd, uint8_t len) { + if(!len) { + scsi->sk = SCSI_SK_ILLEGAL_REQUEST; + scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE; + return false; + } + FURI_LOG_T(TAG, "START %02X", cmd[0]); + scsi->cmd = cmd; + scsi->cmd_len = len; + scsi->rx_done = false; + scsi->tx_done = false; + switch(cmd[0]) { + case SCSI_WRITE_10: { + if(len < 10) return false; + scsi->write_10.lba = cmd[2] << 24 | cmd[3] << 16 | cmd[4] << 8 | cmd[5]; + scsi->write_10.count = cmd[7] << 8 | cmd[8]; + FURI_LOG_D(TAG, "SCSI_WRITE_10 %08lX %04X", scsi->write_10.lba, scsi->write_10.count); + return true; + }; break; + case SCSI_READ_10: { + if(len < 10) return false; + scsi->read_10.lba = cmd[2] << 24 | cmd[3] << 16 | cmd[4] << 8 | cmd[5]; + scsi->read_10.count = cmd[7] << 8 | cmd[8]; + FURI_LOG_D(TAG, "SCSI_READ_10 %08lX %04X", scsi->read_10.lba, scsi->read_10.count); + return true; + }; break; + } + return true; +} + +bool scsi_cmd_rx_data(SCSISession* scsi, uint8_t* data, uint32_t len) { + FURI_LOG_T(TAG, "RX %02X len %lu", scsi->cmd[0], len); + if(scsi->rx_done) return false; + switch(scsi->cmd[0]) { + case SCSI_WRITE_10: { + uint32_t block_size = SCSI_BLOCK_SIZE; + uint16_t blocks = len / block_size; + bool result = + scsi->fn.write(scsi->fn.ctx, scsi->write_10.lba, blocks, data, blocks * block_size); + scsi->write_10.lba += blocks; + scsi->write_10.count -= blocks; + if(!scsi->write_10.count) { + scsi->rx_done = true; + } + return result; + }; break; + default: { + FURI_LOG_W(TAG, "unexpected scsi rx data cmd=%02X", scsi->cmd[0]); + scsi->sk = SCSI_SK_ILLEGAL_REQUEST; + scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE; + return false; + }; break; + } +} + +bool scsi_cmd_tx_data(SCSISession* scsi, uint8_t* data, uint32_t* len, uint32_t cap) { + FURI_LOG_T(TAG, "TX %02X cap %lu", scsi->cmd[0], cap); + if(scsi->tx_done) return false; + switch(scsi->cmd[0]) { + case SCSI_REQUEST_SENSE: { + FURI_LOG_D(TAG, "SCSI_REQUEST_SENSE"); + if(cap < 18) return false; + memset(data, 0, cap); + data[0] = 0x70; // fixed format sense data + data[1] = 0; // obsolete + data[2] = scsi->sk; // sense key + data[3] = 0; // information + data[4] = 0; // information + data[5] = 0; // information + data[6] = 0; // information + data[7] = 10; // additional sense length (len-8) + data[8] = 0; // command specific information + data[9] = 0; // command specific information + data[10] = 0; // command specific information + data[11] = 0; // command specific information + data[12] = scsi->asc; // additional sense code + data[13] = 0; // additional sense code qualifier + data[14] = 0; // field replaceable unit code + data[15] = 0; // sense key specific information + data[16] = 0; // sense key specific information + data[17] = 0; // sense key specific information + *len = 18; + scsi->sk = 0; + scsi->asc = 0; + scsi->tx_done = true; + return true; + }; break; + case SCSI_INQUIRY: { + FURI_LOG_D(TAG, "SCSI_INQUIRY"); + if(scsi->cmd_len < 5) return false; + + if(cap < 36) return false; + + bool evpd = scsi->cmd[1] & 1; + uint8_t page_code = scsi->cmd[2]; + if(evpd == 0) { + if(page_code != 0) return false; + + data[0] = 0x00; // device type: direct access block device + data[1] = 0x80; // removable: true + data[2] = 0x04; // version + data[3] = 0x02; // response data format + data[4] = 31; // additional length (len - 5) + data[5] = 0; // flags + data[6] = 0; // flags + data[7] = 0; // flags + memcpy(data + 8, "Flipper ", 8); // vendor id + memcpy(data + 16, "Mass Storage ", 16); // product id + memcpy(data + 32, "0001", 4); // product revision level + *len = 36; + scsi->tx_done = true; + return true; + } else { + if(page_code != 0x80) { + FURI_LOG_W(TAG, "Unsupported VPD code %02X", page_code); + return false; + } + data[0] = 0x00; + data[1] = 0x80; + data[2] = 0x00; + data[3] = 0x01; // Serial len + data[4] = '0'; + *len = 5; + scsi->tx_done = true; + return true; + } + }; break; + case SCSI_READ_FORMAT_CAPACITIES: { + FURI_LOG_D(TAG, "SCSI_READ_FORMAT_CAPACITIES"); + if(cap < 12) { + return false; + } + uint32_t n_blocks = scsi->fn.num_blocks(scsi->fn.ctx); + uint32_t block_size = SCSI_BLOCK_SIZE; + // Capacity List Header + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 8; + + // Capacity Descriptor + data[4] = (n_blocks - 1) >> 24; + data[5] = (n_blocks - 1) >> 16; + data[6] = (n_blocks - 1) >> 8; + data[7] = (n_blocks - 1) & 0xFF; + data[8] = 0x02; // Formatted media + data[9] = block_size >> 16; + data[10] = block_size >> 8; + data[11] = block_size & 0xFF; + *len = 12; + scsi->tx_done = true; + return true; + }; break; + case SCSI_READ_CAPACITY_10: { + FURI_LOG_D(TAG, "SCSI_READ_CAPACITY_10"); + if(cap < 8) return false; + uint32_t n_blocks = scsi->fn.num_blocks(scsi->fn.ctx); + uint32_t block_size = SCSI_BLOCK_SIZE; + data[0] = (n_blocks - 1) >> 24; + data[1] = (n_blocks - 1) >> 16; + data[2] = (n_blocks - 1) >> 8; + data[3] = (n_blocks - 1) & 0xFF; + data[4] = block_size >> 24; + data[5] = block_size >> 16; + data[6] = block_size >> 8; + data[7] = block_size & 0xFF; + *len = 8; + scsi->tx_done = true; + return true; + }; break; + case SCSI_MODE_SENSE_6: { + FURI_LOG_D(TAG, "SCSI_MODE_SENSE_6 %lu", cap); + if(cap < 4) return false; + data[0] = 3; // mode data length (len - 1) + data[1] = 0; // medium type + data[2] = 0; // device-specific parameter + data[3] = 0; // block descriptor length + *len = 4; + scsi->tx_done = true; + return true; + }; break; + case SCSI_READ_10: { + uint32_t block_size = SCSI_BLOCK_SIZE; + bool result = + scsi->fn.read(scsi->fn.ctx, scsi->read_10.lba, scsi->read_10.count, data, len, cap); + *len -= *len % block_size; + uint16_t blocks = *len / block_size; + scsi->read_10.lba += blocks; + scsi->read_10.count -= blocks; + if(!scsi->read_10.count) { + scsi->tx_done = true; + } + return result; + }; break; + default: { + FURI_LOG_W(TAG, "unexpected scsi tx data cmd=%02X", scsi->cmd[0]); + scsi->sk = SCSI_SK_ILLEGAL_REQUEST; + scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE; + return false; + }; break; + } +} + +bool scsi_cmd_end(SCSISession* scsi) { + FURI_LOG_T(TAG, "END %02X", scsi->cmd[0]); + uint8_t* cmd = scsi->cmd; + uint8_t len = scsi->cmd_len; + scsi->cmd = NULL; + scsi->cmd_len = 0; + switch(cmd[0]) { + case SCSI_WRITE_10: + return scsi->rx_done; + + case SCSI_REQUEST_SENSE: + case SCSI_INQUIRY: + case SCSI_READ_FORMAT_CAPACITIES: + case SCSI_READ_CAPACITY_10: + case SCSI_MODE_SENSE_6: + case SCSI_READ_10: + return scsi->tx_done; + + case SCSI_TEST_UNIT_READY: { + FURI_LOG_D(TAG, "SCSI_TEST_UNIT_READY"); + return true; + }; break; + case SCSI_PREVENT_MEDIUM_REMOVAL: { + if(len < 6) return false; + bool prevent = cmd[5]; + FURI_LOG_D(TAG, "SCSI_PREVENT_MEDIUM_REMOVAL prevent=%d", prevent); + return !prevent; + }; break; + case SCSI_START_STOP_UNIT: { + if(len < 6) return false; + bool eject = (cmd[4] & 2) != 0; + bool start = (cmd[4] & 1) != 0; + FURI_LOG_D(TAG, "SCSI_START_STOP_UNIT eject=%d start=%d", eject, start); + if(eject) { + scsi->fn.eject(scsi->fn.ctx); + } + return true; + }; break; + default: { + FURI_LOG_W(TAG, "unexpected scsi cmd=%02X", cmd[0]); + scsi->sk = SCSI_SK_ILLEGAL_REQUEST; + scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE; + return false; + }; break; + } +} diff --git a/applications/system/js_app/modules/js_usbdisk/mass_storage_scsi.h b/applications/system/js_app/modules/js_usbdisk/mass_storage_scsi.h new file mode 100644 index 000000000..a35d6aff3 --- /dev/null +++ b/applications/system/js_app/modules/js_usbdisk/mass_storage_scsi.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#define SCSI_BLOCK_SIZE (0x200UL) + +#define SCSI_SK_ILLEGAL_REQUEST (5) + +#define SCSI_ASC_INVALID_COMMAND_OPERATION_CODE (0x20) +#define SCSI_ASC_LBA_OOB (0x21) +#define SCSI_ASC_INVALID_FIELD_IN_CDB (0x24) + +typedef struct { + void* ctx; + bool (*read)( + void* ctx, + uint32_t lba, + uint16_t count, + uint8_t* out, + uint32_t* out_len, + uint32_t out_cap); + bool (*write)(void* ctx, uint32_t lba, uint16_t count, uint8_t* buf, uint32_t len); + uint32_t (*num_blocks)(void* ctx); + void (*eject)(void* ctx); +} SCSIDeviceFunc; + +typedef struct { + SCSIDeviceFunc fn; + + uint8_t* cmd; + uint8_t cmd_len; + bool rx_done; + bool tx_done; + + uint8_t sk; // sense key + uint8_t asc; // additional sense code + + // command-specific data + // valid from cmd_start to cmd_end + union { + struct { + uint16_t count; + uint32_t lba; + } read_10; // SCSI_READ_10 + + struct { + uint16_t count; + uint32_t lba; + } write_10; // SCSI_WRITE_10 + }; +} SCSISession; + +bool scsi_cmd_start(SCSISession* scsi, uint8_t* cmd, uint8_t len); +bool scsi_cmd_rx_data(SCSISession* scsi, uint8_t* data, uint32_t len); +bool scsi_cmd_tx_data(SCSISession* scsi, uint8_t* data, uint32_t* len, uint32_t cap); +bool scsi_cmd_end(SCSISession* scsi); \ No newline at end of file diff --git a/applications/system/js_app/modules/js_usbdisk/mass_storage_usb.c b/applications/system/js_app/modules/js_usbdisk/mass_storage_usb.c new file mode 100644 index 000000000..b96b4fecc --- /dev/null +++ b/applications/system/js_app/modules/js_usbdisk/mass_storage_usb.c @@ -0,0 +1,482 @@ +#include "mass_storage_usb.h" +#include + +#define TAG "MassStorageUsb" + +#define USB_MSC_RX_EP (0x01) +#define USB_MSC_TX_EP (0x82) + +#define USB_MSC_RX_EP_SIZE (64UL) +#define USB_MSC_TX_EP_SIZE (64UL) + +#define USB_MSC_BOT_GET_MAX_LUN (0xFE) +#define USB_MSC_BOT_RESET (0xFF) + +#define CBW_SIG (0x43425355) +#define CBW_FLAGS_DEVICE_TO_HOST (0x80) + +#define CSW_SIG (0x53425355) +#define CSW_STATUS_OK (0) +#define CSW_STATUS_NOK (1) +#define CSW_STATUS_PHASE_ERROR (2) + +// must be SCSI_BLOCK_SIZE aligned +// larger than 0x10000 exceeds size_t, storage_file_* ops fail +#define USB_MSC_BUF_MAX (0x10000UL - SCSI_BLOCK_SIZE) + +static usbd_respond usb_ep_config(usbd_device* dev, uint8_t cfg); +static usbd_respond usb_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback); + +typedef enum { + EventExit = 1 << 0, + EventReset = 1 << 1, + EventRxTx = 1 << 2, + + EventAll = EventExit | EventReset | EventRxTx, +} MassStorageEvent; + +typedef struct { + uint32_t sig; + uint32_t tag; + uint32_t len; + uint8_t flags; + uint8_t lun; + uint8_t cmd_len; + uint8_t cmd[16]; +} __attribute__((packed)) CBW; + +typedef struct { + uint32_t sig; + uint32_t tag; + uint32_t residue; + uint8_t status; +} __attribute__((packed)) CSW; + +struct MassStorageUsb { + FuriHalUsbInterface usb; + FuriHalUsbInterface* usb_prev; + + FuriThread* thread; + usbd_device* dev; + SCSIDeviceFunc fn; +}; + +static int32_t mass_thread_worker(void* context) { + MassStorageUsb* mass = context; + usbd_device* dev = mass->dev; + SCSISession scsi = { + .fn = mass->fn, + }; + CBW cbw = {0}; + CSW csw = {0}; + uint8_t* buf = NULL; + uint32_t buf_len = 0, buf_cap = 0, buf_sent = 0; + enum { + StateReadCBW, + StateReadData, + StateWriteData, + StateBuildCSW, + StateWriteCSW, + } state = StateReadCBW; + while(true) { + uint32_t flags = furi_thread_flags_wait(EventAll, FuriFlagWaitAny, FuriWaitForever); + if(flags & EventExit) { + FURI_LOG_D(TAG, "exit"); + break; + } + if(flags & EventReset) { + FURI_LOG_D(TAG, "reset"); + scsi.sk = 0; + scsi.asc = 0; + memset(&cbw, 0, sizeof(cbw)); + memset(&csw, 0, sizeof(csw)); + if(buf) { + free(buf); + buf = NULL; + } + buf_len = buf_cap = buf_sent = 0; + state = StateReadCBW; + mass->fn.eject(mass->fn.ctx); + } + if(flags & EventRxTx) do { + switch(state) { + case StateReadCBW: { + FURI_LOG_T(TAG, "StateReadCBW"); + int32_t len = usbd_ep_read(dev, USB_MSC_RX_EP, &cbw, sizeof(cbw)); + if(len <= 0) { + FURI_LOG_T(TAG, "cbw not ready"); + break; + } + if(len != sizeof(cbw) || cbw.sig != CBW_SIG) { + FURI_LOG_W(TAG, "bad cbw sig=%08lx", cbw.sig); + usbd_ep_stall(dev, USB_MSC_TX_EP); + usbd_ep_stall(dev, USB_MSC_RX_EP); + continue; + } + if(!scsi_cmd_start(&scsi, cbw.cmd, cbw.cmd_len)) { + FURI_LOG_W(TAG, "bad cmd"); + usbd_ep_stall(dev, USB_MSC_RX_EP); + csw.sig = CSW_SIG; + csw.tag = cbw.tag; + csw.status = CSW_STATUS_NOK; + state = StateWriteCSW; + continue; + } + if(cbw.flags & CBW_FLAGS_DEVICE_TO_HOST) { + buf_len = 0; + buf_sent = 0; + state = StateWriteData; + } else { + buf_len = 0; + state = StateReadData; + } + continue; + }; break; + case StateReadData: { + FURI_LOG_T(TAG, "StateReadData %lu/%lu", buf_len, cbw.len); + if(!cbw.len) { + state = StateBuildCSW; + continue; + } + uint32_t buf_clamp = MIN(cbw.len, USB_MSC_BUF_MAX); + if(buf_clamp > buf_cap) { + FURI_LOG_T(TAG, "growing buf %lu -> %lu", buf_cap, buf_clamp); + if(buf) { + free(buf); + } + buf_cap = buf_clamp; + buf = malloc(buf_cap); + } + if(buf_len < buf_clamp) { + int32_t len = + usbd_ep_read(dev, USB_MSC_RX_EP, buf + buf_len, buf_clamp - buf_len); + if(len < 0) { + FURI_LOG_T(TAG, "rx not ready %ld", len); + break; + } + FURI_LOG_T(TAG, "clamp %lu len %ld", buf_clamp, len); + buf_len += len; + } + if(buf_len == buf_clamp) { + if(!scsi_cmd_rx_data(&scsi, buf, buf_len)) { + FURI_LOG_W(TAG, "short rx"); + usbd_ep_stall(dev, USB_MSC_RX_EP); + csw.sig = CSW_SIG; + csw.tag = cbw.tag; + csw.status = CSW_STATUS_NOK; + csw.residue = cbw.len; + state = StateWriteCSW; + continue; + } + cbw.len -= buf_len; + buf_len = 0; + } + continue; + }; break; + case StateWriteData: { + FURI_LOG_T(TAG, "StateWriteData %lu", cbw.len); + if(!cbw.len) { + state = StateBuildCSW; + continue; + } + uint32_t buf_clamp = MIN(cbw.len, USB_MSC_BUF_MAX); + if(buf_clamp > buf_cap) { + FURI_LOG_T(TAG, "growing buf %lu -> %lu", buf_cap, buf_clamp); + if(buf) { + free(buf); + } + buf_cap = buf_clamp; + buf = malloc(buf_cap); + } + if(!buf_len && !scsi_cmd_tx_data(&scsi, buf, &buf_len, buf_clamp)) { + FURI_LOG_W(TAG, "short tx"); + // usbd_ep_stall(dev, USB_MSC_TX_EP); + state = StateBuildCSW; + continue; + } + int32_t len = usbd_ep_write( + dev, + USB_MSC_TX_EP, + buf + buf_sent, + MIN(USB_MSC_TX_EP_SIZE, buf_len - buf_sent)); + if(len < 0) { + FURI_LOG_T(TAG, "tx not ready %ld", len); + break; + } + buf_sent += len; + if(buf_sent == buf_len) { + cbw.len -= buf_len; + buf_len = 0; + buf_sent = 0; + } + continue; + }; break; + case StateBuildCSW: { + FURI_LOG_T(TAG, "StateBuildCSW"); + csw.sig = CSW_SIG; + csw.tag = cbw.tag; + if(scsi_cmd_end(&scsi)) { + csw.status = CSW_STATUS_OK; + } else { + csw.status = CSW_STATUS_NOK; + } + csw.residue = cbw.len; + state = StateWriteCSW; + continue; + }; break; + case StateWriteCSW: { + FURI_LOG_T(TAG, "StateWriteCSW"); + if(csw.status) { + FURI_LOG_W( + TAG, + "csw sig=%08lx tag=%08lx residue=%08lx status=%02x", + csw.sig, + csw.tag, + csw.residue, + csw.status); + } + int32_t len = usbd_ep_write(dev, USB_MSC_TX_EP, &csw, sizeof(csw)); + if(len < 0) { + FURI_LOG_T(TAG, "csw not ready"); + break; + } + if(len != sizeof(csw)) { + FURI_LOG_W(TAG, "bad csw write %ld", len); + usbd_ep_stall(dev, USB_MSC_TX_EP); + break; + } + memset(&cbw, 0, sizeof(cbw)); + memset(&csw, 0, sizeof(csw)); + state = StateReadCBW; + continue; + }; break; + } + break; + } while(true); + } + if(buf) { + free(buf); + } + return 0; +} + +// needed in usb_deinit, usb_suspend, usb_rxtx_ep_callback, usb_control, +// where if_ctx isn't passed +static MassStorageUsb* mass_cur = NULL; + +static void usb_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { + UNUSED(intf); + MassStorageUsb* mass = ctx; + mass_cur = mass; + mass->dev = dev; + + usbd_reg_config(dev, usb_ep_config); + usbd_reg_control(dev, usb_control); + usbd_connect(dev, true); + + mass->thread = furi_thread_alloc(); + furi_thread_set_name(mass->thread, "MassStorageUsb"); + furi_thread_set_stack_size(mass->thread, 1024); + furi_thread_set_context(mass->thread, ctx); + furi_thread_set_callback(mass->thread, mass_thread_worker); + furi_thread_start(mass->thread); +} + +static void usb_deinit(usbd_device* dev) { + usbd_reg_config(dev, NULL); + usbd_reg_control(dev, NULL); + + MassStorageUsb* mass = mass_cur; + if(!mass || mass->dev != dev) { + FURI_LOG_E(TAG, "deinit mass_cur leak"); + return; + } + mass_cur = NULL; + + furi_assert(mass->thread); + furi_thread_flags_set(furi_thread_get_id(mass->thread), EventExit); + furi_thread_join(mass->thread); + furi_thread_free(mass->thread); + mass->thread = NULL; + + free(mass->usb.str_prod_descr); + mass->usb.str_prod_descr = NULL; + free(mass->usb.str_serial_descr); + mass->usb.str_serial_descr = NULL; + free(mass); +} + +static void usb_wakeup(usbd_device* dev) { + UNUSED(dev); +} + +static void usb_suspend(usbd_device* dev) { + MassStorageUsb* mass = mass_cur; + if(!mass || mass->dev != dev) return; + furi_thread_flags_set(furi_thread_get_id(mass->thread), EventReset); +} + +static void usb_rxtx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { + UNUSED(ep); + UNUSED(event); + MassStorageUsb* mass = mass_cur; + if(!mass || mass->dev != dev) return; + furi_thread_flags_set(furi_thread_get_id(mass->thread), EventRxTx); +} + +static usbd_respond usb_ep_config(usbd_device* dev, uint8_t cfg) { + switch(cfg) { + case 0: // deconfig + usbd_ep_deconfig(dev, USB_MSC_RX_EP); + usbd_ep_deconfig(dev, USB_MSC_TX_EP); + usbd_reg_endpoint(dev, USB_MSC_RX_EP, NULL); + usbd_reg_endpoint(dev, USB_MSC_TX_EP, NULL); + return usbd_ack; + case 1: // config + usbd_ep_config( + dev, USB_MSC_RX_EP, USB_EPTYPE_BULK /* | USB_EPTYPE_DBLBUF*/, USB_MSC_RX_EP_SIZE); + usbd_ep_config( + dev, USB_MSC_TX_EP, USB_EPTYPE_BULK /* | USB_EPTYPE_DBLBUF*/, USB_MSC_TX_EP_SIZE); + usbd_reg_endpoint(dev, USB_MSC_RX_EP, usb_rxtx_ep_callback); + usbd_reg_endpoint(dev, USB_MSC_TX_EP, usb_rxtx_ep_callback); + return usbd_ack; + } + return usbd_fail; +} + +static usbd_respond usb_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) { + UNUSED(callback); + if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) != + (USB_REQ_INTERFACE | USB_REQ_CLASS)) { + return usbd_fail; + } + switch(req->bRequest) { + case USB_MSC_BOT_GET_MAX_LUN: { + static uint8_t max_lun = 0; + dev->status.data_ptr = &max_lun; + dev->status.data_count = 1; + return usbd_ack; + }; break; + case USB_MSC_BOT_RESET: { + MassStorageUsb* mass = mass_cur; + if(!mass || mass->dev != dev) return usbd_fail; + furi_thread_flags_set(furi_thread_get_id(mass->thread), EventReset); + return usbd_ack; + }; break; + } + return usbd_fail; +} + +static const struct usb_string_descriptor dev_manuf_desc = USB_STRING_DESC("Flipper Devices Inc."); + +struct MassStorageDescriptor { + struct usb_config_descriptor config; + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor ep_rx; + struct usb_endpoint_descriptor ep_tx; +} __attribute__((packed)); + +static const struct usb_device_descriptor usb_mass_dev_descr = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = USB_DTYPE_DEVICE, + .bcdUSB = VERSION_BCD(2, 0, 0), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = USB_SUBCLASS_NONE, + .bDeviceProtocol = USB_PROTO_NONE, + .bMaxPacketSize0 = 8, // USB_EP0_SIZE + .idVendor = 0x0483, + .idProduct = 0x5720, + .bcdDevice = VERSION_BCD(1, 0, 0), + .iManufacturer = 1, // UsbDevManuf + .iProduct = 2, // UsbDevProduct + .iSerialNumber = 3, // UsbDevSerial + .bNumConfigurations = 1, +}; + +static const struct MassStorageDescriptor usb_mass_cfg_descr = { + .config = + { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = USB_DTYPE_CONFIGURATION, + .wTotalLength = sizeof(struct MassStorageDescriptor), + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = NO_DESCRIPTOR, + .bmAttributes = USB_CFG_ATTR_RESERVED | USB_CFG_ATTR_SELFPOWERED, + .bMaxPower = USB_CFG_POWER_MA(100), + }, + .intf = + { + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DTYPE_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = 0x06, // scsi transparent + .bInterfaceProtocol = 0x50, // bulk only + .iInterface = NO_DESCRIPTOR, + }, + .ep_rx = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = USB_MSC_RX_EP, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = USB_MSC_RX_EP_SIZE, + .bInterval = 0, + }, + .ep_tx = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = USB_MSC_TX_EP, + .bmAttributes = USB_EPTYPE_BULK, + .wMaxPacketSize = USB_MSC_TX_EP_SIZE, + .bInterval = 0, + }, +}; + +MassStorageUsb* mass_storage_usb_start(const char* filename, SCSIDeviceFunc fn) { + MassStorageUsb* mass = malloc(sizeof(MassStorageUsb)); + mass->usb_prev = furi_hal_usb_get_config(); + mass->usb.init = usb_init; + mass->usb.deinit = usb_deinit; + mass->usb.wakeup = usb_wakeup; + mass->usb.suspend = usb_suspend; + mass->usb.dev_descr = (struct usb_device_descriptor*)&usb_mass_dev_descr; + mass->usb.str_manuf_descr = (void*)&dev_manuf_desc; + mass->usb.str_prod_descr = NULL; + mass->usb.str_serial_descr = NULL; + mass->usb.cfg_descr = (void*)&usb_mass_cfg_descr; + + const char* name = furi_hal_version_get_device_name_ptr(); + if(!name) name = "Flipper Zero"; + size_t len = strlen(name); + struct usb_string_descriptor* str_prod_descr = malloc(len * 2 + 2); + str_prod_descr->bLength = len * 2 + 2; + str_prod_descr->bDescriptorType = USB_DTYPE_STRING; + for(uint8_t i = 0; i < len; i++) str_prod_descr->wString[i] = name[i]; + mass->usb.str_prod_descr = str_prod_descr; + + len = strlen(filename); + struct usb_string_descriptor* str_serial_descr = malloc(len * 2 + 2); + str_serial_descr->bLength = len * 2 + 2; + str_serial_descr->bDescriptorType = USB_DTYPE_STRING; + for(uint8_t i = 0; i < len; i++) str_serial_descr->wString[i] = filename[i]; + mass->usb.str_serial_descr = str_serial_descr; + + mass->fn = fn; + if(!furi_hal_usb_set_config(&mass->usb, mass)) { + FURI_LOG_E(TAG, "USB locked, cannot start Mass Storage"); + free(mass->usb.str_prod_descr); + free(mass->usb.str_serial_descr); + free(mass); + return NULL; + } + return mass; +} + +void mass_storage_usb_stop(MassStorageUsb* mass) { + furi_hal_usb_set_config(mass->usb_prev, NULL); +} diff --git a/applications/system/js_app/modules/js_usbdisk/mass_storage_usb.h b/applications/system/js_app/modules/js_usbdisk/mass_storage_usb.h new file mode 100644 index 000000000..0f370f98e --- /dev/null +++ b/applications/system/js_app/modules/js_usbdisk/mass_storage_usb.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include "mass_storage_scsi.h" + +typedef struct MassStorageUsb MassStorageUsb; + +MassStorageUsb* mass_storage_usb_start(const char* filename, SCSIDeviceFunc fn); +void mass_storage_usb_stop(MassStorageUsb* mass);