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 000000000..7060e893d Binary files /dev/null and b/applications/examples/example_ble_beacon/example_ble_beacon_10px.png differ diff --git a/applications/examples/example_ble_beacon/images/lighthouse_35x44.png b/applications/examples/example_ble_beacon/images/lighthouse_35x44.png new file mode 100644 index 000000000..4cf4d19c5 Binary files /dev/null and b/applications/examples/example_ble_beacon/images/lighthouse_35x44.png differ diff --git a/applications/examples/example_ble_beacon/scenes/scene_config.h b/applications/examples/example_ble_beacon/scenes/scene_config.h new file mode 100644 index 000000000..28aa376ab --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scene_config.h @@ -0,0 +1,4 @@ +ADD_SCENE(ble_beacon_app, menu, Menu) +ADD_SCENE(ble_beacon_app, input_mac_addr, InputMacAddress) +ADD_SCENE(ble_beacon_app, input_beacon_data, InputBeaconData) +ADD_SCENE(ble_beacon_app, run_beacon, RunBeacon) diff --git a/applications/examples/example_ble_beacon/scenes/scene_input_beacon_data.c b/applications/examples/example_ble_beacon/scenes/scene_input_beacon_data.c new file mode 100644 index 000000000..376d667a5 --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scene_input_beacon_data.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_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 f6a4d22a2..464545dec 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/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; } } 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 a4bcc4975..0315a4eaf 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,15 +71,21 @@ 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->suppress_pin_screen) return; + 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); + 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); } 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,9 +93,13 @@ 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(); + } 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( @@ -97,29 +117,34 @@ 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); - } + bt_settings_load(&bt->bt_settings); // Keys storage bt->keys_storage = bt_keys_storage_alloc(BT_KEYS_STORAGE_PATH); // Alloc queue @@ -127,18 +152,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); @@ -175,7 +196,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); } @@ -196,10 +221,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 @@ -214,66 +239,55 @@ 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; 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); 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); - // 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) { - // 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); - 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); - } else { - FURI_LOG_W(TAG, "RPC is busy, failed to open new session"); - } - } + do_update_status = true; + bt_open_rpc_connection(bt); // 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) { bt->pin = event.data.pin_code; @@ -288,6 +302,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; } @@ -304,11 +332,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); @@ -316,56 +351,84 @@ 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) { +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"); 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()) { - bt_settings_load(&bt->bt_settings); + if(furi_hal_bt_is_gatt_gap_supported()) { 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); } @@ -376,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); @@ -435,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(); @@ -477,8 +473,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) { @@ -508,7 +506,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 3d96ce603..d49b0b3ba 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -2,8 +2,8 @@ #include #include - -#include +#include +#include #ifdef __cplusplus extern "C" { @@ -20,11 +20,6 @@ typedef enum { BtStatusConnected, } BtStatus; -typedef enum { - BtProfileSerial, - BtProfileHidKeyboard, -} BtProfile; - typedef struct { uint8_t rssi; uint32_t since; @@ -35,12 +30,25 @@ 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 * @@ -76,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/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 4c6151bfd..eb9428388 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 4874e6b46..a01ce173a 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 @@ -398,27 +399,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 de29bd47d..11261a86c 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/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/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index e03e4f45a..ec49f9843 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 c27ad9c81..f14d6b063 100644 --- a/applications/system/hid_app/application.fam +++ b/applications/system/hid_app/application.fam @@ -4,6 +4,10 @@ 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", fap_icon="hid_usb_10px.png", fap_icon_assets="assets", @@ -17,6 +21,11 @@ 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", fap_icon="hid_ble_10px.png", fap_icon_assets="assets", 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 000000000..a299d3630 Binary files /dev/null and b/applications/system/hid_app/assets/DolphinNice_96x59.png differ 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 f5bf276f3..000000000 Binary files a/applications/system/hid_app/assets/Like_pressed_17x17.png and /dev/null differ diff --git a/applications/system/hid_app/assets/Pin_back_arrow_rotated_8x10.png b/applications/system/hid_app/assets/Pin_back_arrow_rotated_8x10.png deleted file mode 100644 index 929992022..000000000 Binary files a/applications/system/hid_app/assets/Pin_back_arrow_rotated_8x10.png and /dev/null differ diff --git a/applications/system/hid_app/assets/RoundButtonPressed_16x16.png b/applications/system/hid_app/assets/RoundButtonPressed_16x16.png deleted file mode 100644 index c4892e642..000000000 Binary files a/applications/system/hid_app/assets/RoundButtonPressed_16x16.png and /dev/null differ diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index eea9e60b6..cd4f75d2e 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -1,7 +1,10 @@ #include "hid.h" +#include +#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); 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); diff --git a/assets/icons/StatusBar/BLE_beacon_7x8.png b/assets/icons/StatusBar/BLE_beacon_7x8.png new file mode 100644 index 000000000..e8480287c Binary files /dev/null and b/assets/icons/StatusBar/BLE_beacon_7x8.png differ 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/lib/ble_profile/extra_services/hid_service.c b/lib/ble_profile/extra_services/hid_service.c new file mode 100644 index 000000000..d9ea09c14 --- /dev/null +++ b/lib/ble_profile/extra_services/hid_service.c @@ -0,0 +1,320 @@ +#include "hid_service.h" +#include "app_common.h" +#include +#include +#include + +#include +#include + +#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, + HidSvcGattCharacteristicReportMap, + HidSvcGattCharacteristicInfo, + HidSvcGattCharacteristicCtrlPoint, + HidSvcGattCharacteristicCount, +} HidSvcGattCharacteristicId; + +typedef struct { + uint8_t report_idx; + uint8_t report_type; +} HidSvcReportId; + +static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes"); + +static const Service_UUID_t ble_svc_hid_uuid = { + .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, +}; + +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) { + *data = (const uint8_t*)report_id; + } + return false; +} + +typedef struct { + const void* data_ptr; + uint16_t data_len; +} HidSvcDataWrapper; + +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 = BLE_SVC_HID_REPORT_MAP_MAX_LEN; + } + return false; +} + +static const BleGattCharacteristicParams ble_svc_hid_chars[HidSvcGattCharacteristicCount] = { + [HidSvcGattCharacteristicProtocolMode] = + {.name = "Protocol Mode", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = 1, + .uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [HidSvcGattCharacteristicReportMap] = + {.name = "Report Map", + .data_prop_type = FlipperGattCharacteristicDataCallback, + .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, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_VARIABLE}, + [HidSvcGattCharacteristicInfo] = + {.name = "HID Information", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .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, + .char_properties = CHAR_PROP_READ, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [HidSvcGattCharacteristicCtrlPoint] = + {.name = "HID Control Point", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .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, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, + .is_variable = CHAR_VALUE_LEN_CONSTANT}, +}; + +static const BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr_template = { + .uuid_type = UUID_TYPE_16, + .uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID, + .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 BleGattCharacteristicParams ble_svc_hid_report_template = { + .name = "Report", + .data_prop_type = FlipperGattCharacteristicDataCallback, + .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, + .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, + .security_permissions = ATTR_PERMISSION_NONE, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_VARIABLE, +}; + +struct BleServiceHid { + uint16_t svc_handle; + 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 BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) { + UNUSED(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; + if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { + if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { + // Process modification events + ret = BleEventAckFlowEnable; + } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { + // Process notification confirmation + ret = BleEventAckFlowEnable; + } + } + return ret; +} + +BleServiceHid* ble_svc_hid_start() { + BleServiceHid* hid_svc = malloc(sizeof(BleServiceHid)); + + // Register event handler + hid_svc->event_handler = + ble_event_dispatcher_register_svc_handler(ble_svc_hid_event_handler, hid_svc); + /** + * Add Human Interface Device Service + */ + 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 + ble_gatt_characteristic_init( + hid_svc->svc_handle, + &ble_svc_hid_chars[HidSvcGattCharacteristicProtocolMode], + &hid_svc->chars[HidSvcGattCharacteristicProtocolMode]); + + uint8_t protocol_mode = 1; + ble_gatt_characteristic_update( + hid_svc->svc_handle, + &hid_svc->chars[HidSvcGattCharacteristicProtocolMode], + &protocol_mode); + + // reports + BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr; + BleGattCharacteristicParams report_char; + HidSvcReportId report_id; + + 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)); + + 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; + BleGattCharacteristicInstance* chars; + } HidSvcReportCharProps; + + HidSvcReportCharProps hid_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); + report_type_idx++) { + report_id.report_type = hid_report_chars[report_type_idx].report_type; + 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; + ble_gatt_characteristic_init( + hid_svc->svc_handle, + &report_char, + &hid_report_chars[report_type_idx].chars[report_idx]); + } + } + + // Setup remaining characteristics + for(size_t i = HidSvcGattCharacteristicReportMap; i < HidSvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_init( + hid_svc->svc_handle, &ble_svc_hid_chars[i], &hid_svc->chars[i]); + } + + return hid_svc; +} + +bool ble_svc_hid_update_report_map(BleServiceHid* hid_svc, const uint8_t* data, uint16_t len) { + furi_assert(data); + furi_assert(hid_svc); + + HidSvcDataWrapper report_data = { + .data_ptr = data, + .data_len = len, + }; + return ble_gatt_characteristic_update( + hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data); +} + +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 < BLE_SVC_HID_INPUT_REPORT_COUNT); + + HidSvcDataWrapper report_data = { + .data_ptr = data, + .data_len = len, + }; + return ble_gatt_characteristic_update( + hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data); +} + +bool ble_svc_hid_update_info(BleServiceHid* hid_svc, uint8_t* data) { + furi_assert(data); + furi_assert(hid_svc); + + return ble_gatt_characteristic_update( + hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data); +} + +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 8e55b7d96..b680cb891 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 acbe526df..f0e24e7fd 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/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", + ) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index ef7549c87..51cdfcb9c 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -208,7 +208,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; 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 018827135..03c12a66b 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,, @@ -233,6 +235,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,, @@ -261,8 +270,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,, @@ -382,6 +389,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 @@ -674,9 +684,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, @@ -684,29 +705,58 @@ 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_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_profile,_Bool,"Bt*, BtProfile" -Function,+,bt_set_profile_adv_name,void,"Bt*, const char*, ..." -Function,+,bt_set_profile_mac_address,void,"Bt*, const uint8_t[6]" -Function,+,bt_set_profile_pairing_method,void,"Bt*, GapPairing" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -1135,59 +1185,37 @@ 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_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*, +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_conn_rssi,uint32_t,uint8_t* -Function,-,furi_hal_bt_get_hardfault_info,const FuriHalBtHardfaultInfo*, 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_hid_consumer_key_release,_Bool,uint16_t -Function,+,furi_hal_bt_hid_consumer_key_release_all,_Bool, -Function,+,furi_hal_bt_hid_get_led_state,uint8_t, -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_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, 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_reverse_mac_addr,void,uint8_t[( 6 )] -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_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,_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, @@ -1199,7 +1227,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 @@ -1765,6 +1793,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* @@ -1803,6 +1832,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_remote_conn_rssi,uint32_t,int8_t* Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" @@ -1827,6 +1865,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*" @@ -2901,13 +2940,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" @@ -3554,6 +3586,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 c06403f55..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); @@ -62,7 +63,7 @@ void flipper_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) { @@ -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 aa962fd8b..faac3be45 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 @@ -98,7 +101,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; @@ -293,7 +296,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) { @@ -369,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 @@ -409,6 +410,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 @@ -451,7 +454,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) { @@ -477,7 +481,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; @@ -489,7 +493,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"); @@ -518,8 +522,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; @@ -545,6 +548,7 @@ 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; } @@ -559,7 +563,7 @@ uint32_t gap_get_remote_conn_rssi(int8_t* rssi) { return 0; } -GapState gap_get_state() { +GapState gap_get_state(void) { GapState state; if(gap) { furi_mutex_acquire(gap->state_mutex, FuriWaitForever); @@ -571,7 +575,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; @@ -584,6 +588,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; } @@ -614,3 +620,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 396d64e67..8ee4b3d91 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 { @@ -67,19 +73,21 @@ 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; 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); uint32_t gap_get_remote_conn_rssi(int8_t* rssi); 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 3178c0f65..5f0603922 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_version(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.c b/targets/f7/ble_glue/services/hid_service.c deleted file mode 100644 index d3fad0108..000000000 --- a/targets/f7/ble_glue/services/hid_service.c +++ /dev/null @@ -1,366 +0,0 @@ -#include "hid_service.h" -#include "app_common.h" -#include -#include "gatt_char.h" - -#include - -#define TAG "BtHid" - -typedef enum { - HidSvcGattCharacteristicProtocolMode = 0, - HidSvcGattCharacteristicReportMap, - HidSvcGattCharacteristicInfo, - HidSvcGattCharacteristicCtrlPoint, - HidSvcGattCharacteristicLed, - HidSvcGattCharacteristicCount, -} HidSvcGattCharacteristicId; - -typedef struct { - uint8_t report_idx; - uint8_t report_type; -} HidSvcReportId; - -static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes"); - -static const Service_UUID_t hid_svc_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) { - const HidSvcReportId* report_id = context; - *data_len = sizeof(HidSvcReportId); - if(data) { - *data = (const uint8_t*)report_id; - } - return false; -} - -typedef struct { - const void* data_ptr; - uint16_t data_len; -} HidSvcDataWrapper; - -static bool - hid_svc_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; - } - return false; -} - -// LED Descriptor params for BadBT - -static uint8_t led_desc_context_buf[2] = {HID_SVC_REPORT_COUNT + 1, 2}; - -static FlipperGattCharacteristicDescriptorParams hid_svc_char_descr_led = { - .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, - .data_callback.context = led_desc_context_buf, - .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_chars[HidSvcGattCharacteristicCount] = { - [HidSvcGattCharacteristicProtocolMode] = - {.name = "Protocol Mode", - .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = 1, - .uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, - .is_variable = CHAR_VALUE_LEN_CONSTANT}, - [HidSvcGattCharacteristicReportMap] = - {.name = "Report Map", - .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.fn = hid_svc_report_data_callback, - .data.callback.context = NULL, - .uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_VARIABLE}, - [HidSvcGattCharacteristicInfo] = - {.name = "HID Information", - .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = HID_SVC_INFO_LEN, - .data.fixed.ptr = NULL, - .uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_CONSTANT}, - [HidSvcGattCharacteristicCtrlPoint] = - {.name = "HID Control Point", - .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = HID_SVC_CONTROL_POINT_LEN, - .uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_WRITE_WITHOUT_RESP, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, - .is_variable = CHAR_VALUE_LEN_CONSTANT}, - [HidSvcGattCharacteristicLed] = - { - .name = - "HID LED State", // LED Characteristic and descriptor for BadBT to get numlock state for altchars - .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = 1, - .uuid.Char_UUID_16 = REPORT_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE | - GATT_NOTIFY_WRITE_REQ_AND_WAIT_FOR_APPL_RESP, - .is_variable = CHAR_VALUE_LEN_CONSTANT, - .descriptor_params = &hid_svc_char_descr_led, - }, -}; - -static const FlipperGattCharacteristicDescriptorParams hid_svc_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, - .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 = { - .name = "Report", - .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.fn = hid_svc_report_data_callback, - .data.callback.context = NULL, - .uuid.Char_UUID_16 = REPORT_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_VARIABLE, -}; - -typedef struct { - 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]; - // led state - HidLedStateEventCallback led_state_event_callback; - void* led_state_ctx; -} HIDSvc; - -static HIDSvc* hid_svc = NULL; - -static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void* event) { - SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; - hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); - evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; - // aci_gatt_attribute_modified_event_rp0* attribute_modified; - if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { - if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { - // Process modification events - ret = SVCCTL_EvtAckFlowEnable; - } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { - // Process notification confirmation - ret = SVCCTL_EvtAckFlowEnable; - } else if(blecore_evt->ecode == ACI_GATT_WRITE_PERMIT_REQ_VSEVT_CODE) { - // LED Characteristic and descriptor for BadBT to get numlock state for altchars - // - // Process write request - aci_gatt_write_permit_req_event_rp0* req = - (aci_gatt_write_permit_req_event_rp0*)blecore_evt->data; - - furi_check(hid_svc->led_state_event_callback && hid_svc->led_state_ctx); - - // this check is likely to be incorrect, it will actually work in our case - // but we need to investigate gatt api to see what is the rules - // that specify attibute handle value from char handle (or the reverse) - if(req->Attribute_Handle == (hid_svc->chars[HidSvcGattCharacteristicLed].handle + 1)) { - hid_svc->led_state_event_callback(req->Data[0], hid_svc->led_state_ctx); - aci_gatt_write_resp( - req->Connection_Handle, - req->Attribute_Handle, - 0x00, /* write_status = 0 (no error))*/ - 0x00, /* err_code */ - req->Data_Length, - req->Data); - aci_gatt_write_char_value( - req->Connection_Handle, - hid_svc->chars[HidSvcGattCharacteristicLed].handle, - req->Data_Length, - req->Data); - ret = SVCCTL_EvtAckFlowEnable; - } - } - } - return ret; -} - -void hid_svc_start() { - tBleStatus status; - hid_svc = malloc(sizeof(HIDSvc)); - - // Register event handler - SVCCTL_RegisterSvcHandler(hid_svc_event_handler); - /** - * 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 + - 4, /* Service + Report Map + HID Information + HID Control Point + LED state */ - &hid_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add HID service: %d", status); - } - - for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_init( - hid_svc->svc_handle, &hid_svc_chars[i], &hid_svc->chars[i]); - } - uint8_t protocol_mode = 1; - flipper_gatt_characteristic_update( - hid_svc->svc_handle, - &hid_svc->chars[HidSvcGattCharacteristicProtocolMode], - &protocol_mode); - - // reports - FlipperGattCharacteristicDescriptorParams hid_svc_char_descr; - FlipperGattCharacteristicParams 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)); - - hid_svc_char_descr.data_callback.context = &report_id; - report_char.descriptor_params = &hid_svc_char_descr; - - typedef struct { - uint8_t report_type; - uint8_t report_count; - FlipperGattCharacteristicInstance* 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}, - }; - - for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); - report_type_idx++) { - report_id.report_type = hid_report_chars[report_type_idx].report_type; - 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( - hid_svc->svc_handle, - &report_char, - &hid_report_chars[report_type_idx].chars[report_idx]); - } - } -} - -bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { - furi_assert(data); - furi_assert(hid_svc); - - HidSvcDataWrapper report_data = { - .data_ptr = data, - .data_len = len, - }; - return flipper_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) { - furi_assert(data); - furi_assert(hid_svc); - furi_assert(input_report_num < HID_SVC_INPUT_REPORT_COUNT); - - HidSvcDataWrapper report_data = { - .data_ptr = data, - .data_len = len, - }; - return flipper_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) { - furi_assert(data); - furi_assert(hid_svc); - - return flipper_gatt_characteristic_update( - hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data); -} - -void hid_svc_register_led_state_callback(HidLedStateEventCallback callback, void* context) { - furi_assert(hid_svc); - furi_assert(callback); - furi_assert(context); - - hid_svc->led_state_event_callback = callback; - hid_svc->led_state_ctx = context; -} - -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; - } -} 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 4d0ed4c4f..000000000 --- a/targets/f7/ble_glue/services/hid_service.h +++ /dev/null @@ -1,33 +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) - -typedef uint16_t (*HidLedStateEventCallback)(uint8_t state, void* ctx); - -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); - -void hid_svc_register_led_state_callback(HidLedStateEventCallback callback, void* context); 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 f87839fef..2b1fbf04c 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,94 +15,30 @@ #include #include -#include -#include #include #include #include #define TAG "FuriHalBt" +#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); @@ -109,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); @@ -165,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; } @@ -184,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; } @@ -199,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 { @@ -215,69 +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; } - GapConfig* config = &profile_config[profile].config; - // Configure GAP - if(profile == FuriHalBtProfileSerial) { - // 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_BT_ADV_NAME_LENGTH); - config->adv_service_uuid |= furi_hal_version_get_hw_color(); - } else if(profile == FuriHalBtProfileHidKeyboard) { - // Change MAC address for HID profile - const uint8_t* normal_mac = furi_hal_version_get_ble_mac(); - uint8_t empty_mac[sizeof(config->mac_address)] = FURI_HAL_BT_EMPTY_MAC_ADDR; - uint8_t default_mac[sizeof(config->mac_address)] = FURI_HAL_BT_DEFAULT_MAC_ADDR; - if(memcmp(config->mac_address, empty_mac, sizeof(config->mac_address)) == 0 || - memcmp(config->mac_address, normal_mac, sizeof(config->mac_address)) == 0 || - memcmp(config->mac_address, default_mac, sizeof(config->mac_address)) == 0) { - memcpy(config->mac_address, normal_mac, sizeof(config->mac_address)); - config->mac_address[2]++; - } - // Change name Flipper -> Control - if(strnlen(config->adv_name, FURI_HAL_BT_ADV_NAME_LENGTH) < 2 || - strnlen(config->adv_name + 1, FURI_HAL_BT_ADV_NAME_LENGTH - 1) < 1) { - snprintf( - config->adv_name, - FURI_HAL_BT_ADV_NAME_LENGTH, - "%cControl %s", - AD_TYPE_COMPLETE_LOCAL_NAME, - furi_hal_version_get_name_ptr()); - } - } - 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() { @@ -285,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); @@ -307,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() { @@ -352,15 +269,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) { @@ -489,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++) { @@ -543,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); @@ -614,11 +436,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 43b278578..000000000 --- a/targets/f7/furi_hal/furi_hal_bt_hid.c +++ /dev/null @@ -1,362 +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, - ReportIdLEDState = 4, -}; -// 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_REPORT_ID(ReportIdLEDState), - 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_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; - -typedef struct { -// shortcuts -#define s_undefined data.bits.b_undefined -#define s_num_lock data.bits.b_num_lock -#define s_caps_lock data.bits.b_caps_lock -#define s_scroll_lock data.bits.b_scroll_lock -#define s_compose data.bits.b_compose -#define s_kana data.bits.b_kana -#define s_power data.bits.b_power -#define s_shift data.bits.b_shift -#define s_value data.value - union { - struct { - uint8_t b_undefined : 1; - uint8_t b_num_lock : 1; - uint8_t b_caps_lock : 1; - uint8_t b_scroll_lock : 1; - uint8_t b_compose : 1; - uint8_t b_kana : 1; - uint8_t b_power : 1; - uint8_t b_shift : 1; - } bits; - uint8_t value; - } data; -} __attribute__((__packed__)) FuriHalBtHidLedState; - -FuriHalBtHidLedState hid_host_led_state = {.s_value = 0}; - -uint16_t furi_hal_bt_hid_led_state_cb(uint8_t state, void* ctx) { - FuriHalBtHidLedState* led_state = (FuriHalBtHidLedState*)ctx; - - //FURI_LOG_D("HalBtHid", "LED state updated !"); - - led_state->s_value = state; - - return 0; -} - -uint8_t furi_hal_bt_hid_get_led_state(void) { - /*FURI_LOG_D( - "HalBtHid", - "LED state: RFU=%d NUMLOCK=%d CAPSLOCK=%d SCROLLLOCK=%d COMPOSE=%d KANA=%d POWER=%d SHIFT=%d", - hid_host_led_state.s_undefined, - hid_host_led_state.s_num_lock, - hid_host_led_state.s_caps_lock, - hid_host_led_state.s_scroll_lock, - hid_host_led_state.s_compose, - hid_host_led_state.s_kana, - hid_host_led_state.s_power, - hid_host_led_state.s_shift); - */ - - return (hid_host_led_state.s_value >> 1); // bit 0 is undefined (after shift bit location - // match with HID led state bits defines) - // see bad_bt_script.c (ducky_numlock_on function) -} - -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 - hid_svc_register_led_state_callback(furi_hal_bt_hid_led_state_cb, &hid_host_led_state); - - 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); - - hid_svc_register_led_state_callback(NULL, NULL); - // 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); - uint8_t i; - for(i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { - if(kb_report->key[i] == 0) { - kb_report->key[i] = button & 0xFF; - break; - } - } - if(i == FURI_HAL_BT_HID_KB_MAX_KEYS) { - return false; - } - 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/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/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 7ff0a4257..e498b1586 100644 --- a/targets/furi_hal_include/furi_hal_bt.h +++ b/targets/furi_hal_include/furi_hal_bt.h @@ -8,21 +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_EMPTY_MAC_ADDR \ - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } - -#define FURI_HAL_BT_DEFAULT_MAC_ADDR \ - { 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 } +#define FURI_HAL_BT_C2_START_TIMEOUT (1000) #ifdef __cplusplus extern "C" { @@ -34,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(); @@ -68,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 * @@ -76,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 * @@ -95,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 * @@ -110,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,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 @@ -295,18 +245,60 @@ bool furi_hal_bt_is_connected(void); */ 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 56a8b4e48..000000000 --- a/targets/furi_hal_include/furi_hal_bt_hid.h +++ /dev/null @@ -1,98 +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(); - -/** 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/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 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 {