diff --git a/applications/bt/bt_cli.c b/applications/bt/bt_cli.c index 4b6d8ee9..38d95df4 100644 --- a/applications/bt/bt_cli.c +++ b/applications/bt/bt_cli.c @@ -1,23 +1,18 @@ -#include "bt_cli.h" #include #include +#include +#include + #include "bt_settings.h" -void bt_on_system_start() { -#ifdef SRV_CLI - Cli* cli = furi_record_open("cli"); +static const char* bt_cli_address_types[] = { + "Public Device Address", + "Random Device Address", + "Public Identity Address", + "Random (Static) Identity Address", +}; - cli_add_command(cli, "bt_info", CliCommandFlagDefault, bt_cli_command_info, NULL); - cli_add_command(cli, "bt_tx_carrier", CliCommandFlagDefault, bt_cli_command_carrier_tx, NULL); - cli_add_command(cli, "bt_rx_carrier", CliCommandFlagDefault, bt_cli_command_carrier_rx, NULL); - cli_add_command(cli, "bt_tx_pt", CliCommandFlagDefault, bt_cli_command_packet_tx, NULL); - cli_add_command(cli, "bt_rx_pt", CliCommandFlagDefault, bt_cli_command_packet_rx, NULL); - - furi_record_close("cli"); -#endif -} - -void bt_cli_command_info(Cli* cli, string_t args, void* context) { +static void bt_cli_command_hci_info(Cli* cli, string_t args, void* context) { string_t buffer; string_init(buffer); furi_hal_bt_dump_state(buffer); @@ -25,160 +20,229 @@ void bt_cli_command_info(Cli* cli, string_t args, void* context) { string_clear(buffer); } -void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { - uint16_t channel; - uint16_t power; - BtSettings bt_settings; - bt_settings_load(&bt_settings); +static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { + int channel = 0; + int power = 0; - int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &power); - if(ret != 2) { - printf("sscanf returned %d, channel: %hu, power: %hu\r\n", ret, channel, power); - cli_print_usage("bt_tx_carrier", " ", string_get_cstr(args)); - return; - } - if(channel > 39) { - printf("Channel number must be in 0...39 range, not %hu\r\n", channel); - return; - } - if(power > 6) { - printf("Power must be in 0...6 dB range, not %hu\r\n", power); - return; - } + do { + if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) { + printf("Incorrect or missing channel, expected int 0-39"); + break; + } + if(!args_read_int_and_trim(args, &power) && (power < 0 || power > 6)) { + printf("Incorrect or missing power, expected int 0-6"); + break; + } - furi_hal_bt_stop_advertising(); - printf("Transmitting carrier at %hu channel at %hu dB power\r\n", channel, power); - printf("Press CTRL+C to stop\r\n"); - furi_hal_bt_start_tone_tx(channel, 0x19 + power); + furi_hal_bt_stop_advertising(); + printf("Transmitting carrier at %d channel at %d dB power\r\n", channel, power); + printf("Press CTRL+C to stop\r\n"); + furi_hal_bt_start_tone_tx(channel, 0x19 + power); - while(!cli_cmd_interrupt_received(cli)) { - osDelay(250); + while(!cli_cmd_interrupt_received(cli)) { + osDelay(250); + } + furi_hal_bt_stop_tone_tx(); + } while(false); +} + +static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { + int channel = 0; + + do { + if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) { + printf("Incorrect or missing channel, expected int 0-39"); + break; + } + + furi_hal_bt_stop_advertising(); + printf("Receiving carrier at %d channel\r\n", channel); + printf("Press CTRL+C to stop\r\n"); + + furi_hal_bt_start_packet_rx(channel, 1); + + while(!cli_cmd_interrupt_received(cli)) { + osDelay(250); + printf("RSSI: %6.1f dB\r", furi_hal_bt_get_rssi()); + fflush(stdout); + } + + furi_hal_bt_stop_packet_test(); + } while(false); +} + +static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { + int channel = 0; + int pattern = 0; + int datarate = 1; + + do { + if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) { + printf("Incorrect or missing channel, expected int 0-39"); + break; + } + if(!args_read_int_and_trim(args, &pattern) && (pattern < 0 || pattern > 5)) { + printf("Incorrect or missing pattern, expected int 0-5 \r\n"); + printf("0 - Pseudo-Random bit sequence 9\r\n"); + printf("1 - Pattern of alternating bits '11110000'\r\n"); + printf("2 - Pattern of alternating bits '10101010'\r\n"); + printf("3 - Pseudo-Random bit sequence 15\r\n"); + printf("4 - Pattern of All '1' bits\r\n"); + printf("5 - Pattern of All '0' bits\r\n"); + break; + } + if(!args_read_int_and_trim(args, &datarate) && (datarate < 1 || datarate > 2)) { + printf("Incorrect or missing datarate, expected int 1-2"); + break; + } + + furi_hal_bt_stop_advertising(); + printf( + "Transmitting %d pattern packet at %d channel at %d M datarate\r\n", + pattern, + channel, + datarate); + printf("Press CTRL+C to stop\r\n"); + furi_hal_bt_start_packet_tx(channel, pattern, datarate); + + while(!cli_cmd_interrupt_received(cli)) { + osDelay(250); + } + furi_hal_bt_stop_packet_test(); + printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets()); + + } while(false); +} + +static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { + int channel = 0; + int datarate = 1; + + do { + if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) { + printf("Incorrect or missing channel, expected int 0-39"); + break; + } + if(!args_read_int_and_trim(args, &datarate) && (datarate < 1 || datarate > 2)) { + printf("Incorrect or missing datarate, expected int 1-2"); + break; + } + + furi_hal_bt_stop_advertising(); + printf("Receiving packets at %d channel at %d M datarate\r\n", channel, datarate); + printf("Press CTRL+C to stop\r\n"); + furi_hal_bt_start_packet_rx(channel, datarate); + + float rssi_raw = 0; + while(!cli_cmd_interrupt_received(cli)) { + osDelay(250); + rssi_raw = furi_hal_bt_get_rssi(); + printf("RSSI: %03.1f dB\r", rssi_raw); + fflush(stdout); + } + uint16_t packets_received = furi_hal_bt_stop_packet_test(); + printf("Received %hu packets", packets_received); + } while(false); +} + +static void bt_cli_scan_callback(GapAddress address, void* context) { + furi_assert(context); + osMessageQueueId_t queue = context; + osMessageQueuePut(queue, &address, NULL, 250); +} + +static void bt_cli_command_scan(Cli* cli, string_t args, void* context) { + osMessageQueueId_t queue = osMessageQueueNew(20, sizeof(GapAddress), NULL); + furi_hal_bt_start_scan(bt_cli_scan_callback, queue); + + GapAddress address = {}; + bool exit = false; + while(!exit) { + if(osMessageQueueGet(queue, &address, NULL, 250) == osOK) { + if(address.type < sizeof(bt_cli_address_types)) { + printf("Found new device. Type: %s, MAC: ", bt_cli_address_types[address.type]); + for(uint8_t i = 0; i < sizeof(address.mac) - 1; i++) { + printf("%02X:", address.mac[i]); + } + printf("%02X\r\n", address.mac[sizeof(address.mac) - 1]); + } + } + exit = cli_cmd_interrupt_received(cli); } - furi_hal_bt_stop_tone_tx(); - if(bt_settings.enabled) { - furi_hal_bt_start_advertising(); + furi_hal_bt_stop_scan(); + osMessageQueueDelete(queue); +} + +static void bt_cli_print_usage() { + printf("Usage:\r\n"); + printf("bt \r\n"); + printf("Cmd list:\r\n"); + printf("\thci_info\t - HCI info\r\n"); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && + furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) { + printf("\ttx_carrier \t - start tx carrier test\r\n"); + printf("\trx_carrier \t - start rx carrier test\r\n"); + printf("\ttx_pt \t - start tx packet test\r\n"); + printf("\trx_pt \t - start rx packer test\r\n"); + printf("\tscan\t - start scanner\r\n"); } } -void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { - uint16_t channel; +static void bt_cli(Cli* cli, string_t args, void* context) { + string_t cmd; + string_init(cmd); BtSettings bt_settings; bt_settings_load(&bt_settings); - int ret = sscanf(string_get_cstr(args), "%hu", &channel); - if(ret != 1) { - printf("sscanf returned %d, channel: %hu\r\n", ret, channel); - cli_print_usage("bt_rx_carrier", "", string_get_cstr(args)); - return; - } - if(channel > 39) { - printf("Channel number must be in 0...39 range, not %hu\r\n", channel); - return; - } - furi_hal_bt_stop_advertising(); - printf("Receiving carrier at %hu channel\r\n", channel); - printf("Press CTRL+C to stop\r\n"); + do { + if(!args_read_string_and_trim(args, cmd)) { + bt_cli_print_usage(); + break; + } + if(string_cmp_str(cmd, "hci_info") == 0) { + bt_cli_command_hci_info(cli, args, NULL); + break; + } + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && + furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) { + if(string_cmp_str(cmd, "carrier_tx") == 0) { + bt_cli_command_carrier_tx(cli, args, NULL); + break; + } + if(string_cmp_str(cmd, "carrier_rx") == 0) { + bt_cli_command_carrier_rx(cli, args, NULL); + break; + } + if(string_cmp_str(cmd, "packet_tx") == 0) { + bt_cli_command_packet_tx(cli, args, NULL); + break; + } + if(string_cmp_str(cmd, "packet_rx") == 0) { + bt_cli_command_packet_rx(cli, args, NULL); + break; + } + if(string_cmp_str(cmd, "scan") == 0) { + bt_cli_command_scan(cli, args, NULL); + break; + } + } - furi_hal_bt_start_packet_rx(channel, 1); + bt_cli_print_usage(); + } while(false); - while(!cli_cmd_interrupt_received(cli)) { - osDelay(1024 / 4); - printf("RSSI: %6.1f dB\r", furi_hal_bt_get_rssi()); - fflush(stdout); - } - - furi_hal_bt_stop_packet_test(); if(bt_settings.enabled) { furi_hal_bt_start_advertising(); } + + string_clear(cmd); } -void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { - uint16_t channel; - uint16_t pattern; - uint16_t datarate; - BtSettings bt_settings; - bt_settings_load(&bt_settings); - int ret = sscanf(string_get_cstr(args), "%hu %hu %hu", &channel, &pattern, &datarate); - if(ret != 3) { - printf("sscanf returned %d, channel: %hu %hu %hu\r\n", ret, channel, pattern, datarate); - cli_print_usage( - "bt_tx_pt", " ", string_get_cstr(args)); - return; - } - if(channel > 39) { - printf("Channel number must be in 0...39 range, not %hu\r\n", channel); - return; - } - if(pattern > 5) { - printf("Pattern must be in 0...5 range, not %hu\r\n", pattern); - printf("0 - Pseudo-Random bit sequence 9\r\n"); - printf("1 - Pattern of alternating bits '11110000'\r\n"); - printf("2 - Pattern of alternating bits '10101010'\r\n"); - printf("3 - Pseudo-Random bit sequence 15\r\n"); - printf("4 - Pattern of All '1' bits\r\n"); - printf("5 - Pattern of All '0' bits\r\n"); - return; - } - if(datarate < 1 || datarate > 2) { - printf("Datarate must be in 1 or 2 Mb, not %hu\r\n", datarate); - return; - } - - furi_hal_bt_stop_advertising(); - printf( - "Transmitting %hu pattern packet at %hu channel at %hu M datarate\r\n", - pattern, - channel, - datarate); - printf("Press CTRL+C to stop\r\n"); - furi_hal_bt_start_packet_tx(channel, pattern, datarate); - - while(!cli_cmd_interrupt_received(cli)) { - osDelay(250); - } - furi_hal_bt_stop_packet_test(); - printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets()); - if(bt_settings.enabled) { - furi_hal_bt_start_advertising(); - } -} - -void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { - uint16_t channel; - uint16_t datarate; - BtSettings bt_settings; - bt_settings_load(&bt_settings); - int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &datarate); - if(ret != 2) { - printf("sscanf returned %d, channel: %hu datarate: %hu\r\n", ret, channel, datarate); - cli_print_usage("bt_rx_pt", " ", string_get_cstr(args)); - return; - } - if(channel > 39) { - printf("Channel number must be in 0...39 range, not %hu\r\n", channel); - return; - } - if(datarate < 1 || datarate > 2) { - printf("Datarate must be in 1 or 2 Mb, not %hu\r\n", datarate); - return; - } - - furi_hal_bt_stop_advertising(); - printf("Receiving packets at %hu channel at %hu M datarate\r\n", channel, datarate); - printf("Press CTRL+C to stop\r\n"); - furi_hal_bt_start_packet_rx(channel, datarate); - - float rssi_raw = 0; - while(!cli_cmd_interrupt_received(cli)) { - osDelay(250); - rssi_raw = furi_hal_bt_get_rssi(); - printf("RSSI: %03.1f dB\r", rssi_raw); - fflush(stdout); - } - uint16_t packets_received = furi_hal_bt_stop_packet_test(); - printf("Received %hu packets", packets_received); - if(bt_settings.enabled) { - furi_hal_bt_start_advertising(); - } +void bt_on_system_start() { +#ifdef SRV_CLI + Cli* cli = furi_record_open("cli"); + furi_record_open("bt"); + cli_add_command(cli, "bt", CliCommandFlagDefault, bt_cli, NULL); + furi_record_close("bt"); + furi_record_close("cli"); +#endif } diff --git a/applications/bt/bt_cli.h b/applications/bt/bt_cli.h deleted file mode 100644 index 0e30145f..00000000 --- a/applications/bt/bt_cli.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -void bt_on_system_start(); - -void bt_cli_command_info(Cli* cli, string_t args, void* context); - -void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context); - -void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context); - -void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context); - -void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context); diff --git a/applications/bt/bt_debug_app/bt_debug_app.c b/applications/bt/bt_debug_app/bt_debug_app.c old mode 100755 new mode 100644 index 90333107..a69174e0 --- a/applications/bt/bt_debug_app/bt_debug_app.c +++ b/applications/bt/bt_debug_app/bt_debug_app.c @@ -1,6 +1,8 @@ #include "bt_debug_app.h" #include +#define TAG "BtDebugApp" + enum BtDebugSubmenuIndex { BtDebugSubmenuIndexCarrierTest, BtDebugSubmenuIndexPacketTest, @@ -92,6 +94,13 @@ void bt_debug_app_free(BtDebugApp* app) { } int32_t bt_debug_app(void* p) { + if(furi_hal_bt_get_radio_stack() != FuriHalBtStackHciLayer) { + FURI_LOG_E(TAG, "Incorrect radio stack, replace with HciLayer for tests."); + DialogsApp* dialogs = furi_record_open("dialogs"); + dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack"); + return 255; + } + BtDebugApp* app = bt_debug_app_alloc(); // Stop advertising furi_hal_bt_stop_advertising(); diff --git a/applications/bt/bt_debug_app/bt_debug_app.h b/applications/bt/bt_debug_app/bt_debug_app.h index ceb0e934..c3657626 100644 --- a/applications/bt/bt_debug_app/bt_debug_app.h +++ b/applications/bt/bt_debug_app/bt_debug_app.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "views/bt_carrier_test.h" diff --git a/applications/bt/bt_service/bt.c b/applications/bt/bt_service/bt.c index fdd43ec4..204061f5 100755 --- a/applications/bt/bt_service/bt.c +++ b/applications/bt/bt_service/bt.c @@ -154,12 +154,12 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt } // Called from GAP thread -static bool bt_on_gap_event_callback(BleEvent event, void* context) { +static bool bt_on_gap_event_callback(GapEvent event, void* context) { furi_assert(context); Bt* bt = context; bool ret = false; - if(event.type == BleEventTypeConnected) { + if(event.type == GapEventTypeConnected) { // Update status bar bt->status = BtStatusConnected; BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; @@ -181,7 +181,7 @@ static bool bt_on_gap_event_callback(BleEvent event, void* context) { message.data.battery_level = info.charge; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); ret = true; - } else if(event.type == BleEventTypeDisconnected) { + } else if(event.type == GapEventTypeDisconnected) { if(bt->profile == BtProfileSerial && bt->rpc_session) { FURI_LOG_I(TAG, "Close RPC connection"); osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); @@ -190,24 +190,24 @@ static bool bt_on_gap_event_callback(BleEvent event, void* context) { bt->rpc_session = NULL; } ret = true; - } else if(event.type == BleEventTypeStartAdvertising) { + } else if(event.type == GapEventTypeStartAdvertising) { bt->status = BtStatusAdvertising; BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); ret = true; - } else if(event.type == BleEventTypeStopAdvertising) { + } else if(event.type == GapEventTypeStopAdvertising) { bt->status = BtStatusOff; BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); ret = true; - } else if(event.type == BleEventTypePinCodeShow) { + } else if(event.type == GapEventTypePinCodeShow) { BtMessage message = { .type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code}; furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); ret = true; - } else if(event.type == BleEventTypePinCodeVerify) { + } else if(event.type == GapEventTypePinCodeVerify) { ret = bt_pin_code_verify_event_handler(bt, event.data.pin_code); - } else if(event.type == BleEventTypeUpdateMTU) { + } else if(event.type == GapEventTypeUpdateMTU) { bt->max_packet_size = event.data.max_packet_size; ret = true; } @@ -234,33 +234,45 @@ static void bt_statusbar_update(Bt* bt) { } } +static void bt_show_warning(Bt* bt, const char* text) { + 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_change_profile(Bt* bt, BtMessage* message) { - bt_settings_load(&bt->bt_settings); - if(bt->profile == BtProfileSerial && bt->rpc_session) { - FURI_LOG_I(TAG, "Close RPC connection"); - osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); - rpc_session_close(bt->rpc_session); - furi_hal_bt_serial_set_event_callback(0, NULL, NULL); - bt->rpc_session = NULL; - } - - FuriHalBtProfile furi_profile; - if(message->data.profile == BtProfileHidKeyboard) { - furi_profile = FuriHalBtProfileHidKeyboard; - } else { - furi_profile = FuriHalBtProfileSerial; - } - - if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) { - FURI_LOG_I(TAG, "Bt App started"); - if(bt->bt_settings.enabled) { - furi_hal_bt_start_advertising(); + FuriHalBtStack stack = furi_hal_bt_get_radio_stack(); + if(stack == FuriHalBtStackLight) { + bt_settings_load(&bt->bt_settings); + if(bt->profile == BtProfileSerial && bt->rpc_session) { + FURI_LOG_I(TAG, "Close RPC connection"); + osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); + rpc_session_close(bt->rpc_session); + furi_hal_bt_serial_set_event_callback(0, NULL, NULL); + bt->rpc_session = NULL; + } + + FuriHalBtProfile furi_profile; + if(message->data.profile == BtProfileHidKeyboard) { + furi_profile = FuriHalBtProfileHidKeyboard; + } else { + furi_profile = FuriHalBtProfileSerial; + } + + if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) { + FURI_LOG_I(TAG, "Bt App started"); + if(bt->bt_settings.enabled) { + furi_hal_bt_start_advertising(); + } + furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); + bt->profile = message->data.profile; + *message->result = true; + } else { + FURI_LOG_E(TAG, "Failed to start Bt App"); + *message->result = false; } - furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); - bt->profile = message->data.profile; - *message->result = true; } else { - FURI_LOG_E(TAG, "Failed to start Bt App"); + bt_show_warning(bt, "Radio stack doesn't support this app"); *message->result = false; } osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT); @@ -268,26 +280,35 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { int32_t bt_srv() { Bt* bt = bt_alloc(); - furi_record_create("bt", bt); // Read keys if(!bt_load_key_storage(bt)) { FURI_LOG_W(TAG, "Failed to load bonding keys"); } - // Start BLE stack - if(furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) { - FURI_LOG_I(TAG, "BLE stack started"); - if(bt->bt_settings.enabled) { - furi_hal_bt_start_advertising(); + // Start radio stack + if(!furi_hal_bt_start_radio_stack()) { + FURI_LOG_E(TAG, "Radio stack start failed"); + } + FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack(); + + if(stack_type == FuriHalBtStackUnknown) { + bt_show_warning(bt, "Unsupported radio stack"); + bt->status = BtStatusUnavailable; + } else if(stack_type == FuriHalBtStackHciLayer) { + bt->status = BtStatusUnavailable; + } else if(stack_type == FuriHalBtStackLight) { + if(!furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) { + FURI_LOG_E(TAG, "BLE App start failed"); + } else { + 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); } - furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); - } else { - FURI_LOG_E(TAG, "BT App start failed"); } - // Update statusbar - bt_statusbar_update(bt); + furi_record_create("bt", bt); BtMessage message; while(1) { diff --git a/applications/bt/bt_service/bt.h b/applications/bt/bt_service/bt.h index 2adf0176..d928f8b1 100644 --- a/applications/bt/bt_service/bt.h +++ b/applications/bt/bt_service/bt.h @@ -10,6 +10,7 @@ extern "C" { typedef struct Bt Bt; typedef enum { + BtStatusUnavailable, BtStatusOff, BtStatusAdvertising, BtStatusConnected, diff --git a/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c b/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c index 6e021148..598780a4 100755 --- a/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c +++ b/applications/bt/bt_settings_app/scenes/bt_settings_scene_start.c @@ -23,20 +23,26 @@ static void bt_settings_scene_start_var_list_change_callback(VariableItem* item) void bt_settings_scene_start_on_enter(void* context) { BtSettingsApp* app = context; VariableItemList* var_item_list = app->var_item_list; - VariableItem* item; - item = variable_item_list_add( - var_item_list, - "Bluetooth", - BtSettingNum, - bt_settings_scene_start_var_list_change_callback, - app); - if(app->settings.enabled) { - variable_item_set_current_value_index(item, BtSettingOn); - variable_item_set_current_value_text(item, bt_settings_text[BtSettingOn]); + + FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack(); + if(stack_type == FuriHalBtStackLight) { + item = variable_item_list_add( + var_item_list, + "Bluetooth", + BtSettingNum, + bt_settings_scene_start_var_list_change_callback, + app); + if(app->settings.enabled) { + variable_item_set_current_value_index(item, BtSettingOn); + variable_item_set_current_value_text(item, bt_settings_text[BtSettingOn]); + } else { + variable_item_set_current_value_index(item, BtSettingOff); + variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); + } } else { - variable_item_set_current_value_index(item, BtSettingOff); - variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); + item = variable_item_list_add(var_item_list, "Bluetooth", 1, NULL, NULL); + variable_item_set_current_value_text(item, "Broken"); } view_dispatcher_switch_to_view(app->view_dispatcher, BtSettingsAppViewVarItemList); diff --git a/firmware/targets/f6/ble-glue/ble_app.c b/firmware/targets/f6/ble-glue/ble_app.c index 989cea32..ebb94892 100644 --- a/firmware/targets/f6/ble-glue/ble_app.c +++ b/firmware/targets/f6/ble-glue/ble_app.c @@ -16,6 +16,8 @@ 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]; +_Static_assert(sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 49, "Ble stack config structure size mismatch"); + typedef struct { osMutexId_t hci_mtx; osSemaphoreId_t hci_sem; diff --git a/firmware/targets/f6/ble-glue/ble_glue.c b/firmware/targets/f6/ble-glue/ble_glue.c index 096bc614..25162331 100644 --- a/firmware/targets/f6/ble-glue/ble_glue.c +++ b/firmware/targets/f6/ble-glue/ble_glue.c @@ -106,29 +106,31 @@ void ble_glue_init() { */ } -static bool ble_glue_wait_status(BleGlueStatus status) { +bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info) { bool ret = false; size_t countdown = 1000; while (countdown > 0) { - if (ble_glue->status == status) { + if (ble_glue->status == BleGlueStatusFusStarted) { ret = true; break; } countdown--; osDelay(1); } + if(ble_glue->status == BleGlueStatusFusStarted) { + SHCI_GetWirelessFwInfo(info); + } else { + FURI_LOG_E(TAG, "Failed to start FUS"); + ble_glue->status = BleGlueStatusBroken; + } + furi_hal_power_insomnia_exit(); return ret; } bool ble_glue_start() { furi_assert(ble_glue); - if (!ble_glue_wait_status(BleGlueStatusFusStarted)) { - // shutdown core2 power - FURI_LOG_E(TAG, "Core2 catastrophic failure, cutting its power"); - LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); - ble_glue->status = BleGlueStatusBroken; - furi_hal_power_insomnia_exit(); + if (ble_glue->status != BleGlueStatusFusStarted) { return false; } @@ -146,6 +148,7 @@ bool ble_glue_start() { } else { FURI_LOG_E(TAG, "Radio stack startup failed"); ble_glue->status = BleGlueStatusRadioStackMissing; + ble_app_thread_stop(); } furi_hal_power_insomnia_exit(); diff --git a/firmware/targets/f6/ble-glue/ble_glue.h b/firmware/targets/f6/ble-glue/ble_glue.h index a95d633c..598ab072 100644 --- a/firmware/targets/f6/ble-glue/ble_glue.h +++ b/firmware/targets/f6/ble-glue/ble_glue.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -25,6 +26,8 @@ bool ble_glue_start(); */ bool ble_glue_is_alive(); +bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info); + /** Is core2 radio stack present and ready * * @return true if present and ready diff --git a/firmware/targets/f6/ble-glue/gap.c b/firmware/targets/f6/ble-glue/gap.c index 70d3bd4d..5c454422 100644 --- a/firmware/targets/f6/ble-glue/gap.c +++ b/firmware/targets/f6/ble-glue/gap.c @@ -25,7 +25,7 @@ typedef struct { GapConfig* config; GapState state; osMutexId_t state_mutex; - BleEventCallback on_event_cb; + GapEventCallback on_event_cb; void* context; osTimerId_t advertise_timer; FuriThread* thread; @@ -40,12 +40,18 @@ typedef enum { GapCommandKillThread, } GapCommand; +typedef struct { + GapScanCallback callback; + void* context; +} GapScan; + // Identity root key static const uint8_t gap_irk[16] = {0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0,0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0}; // Encryption root key static const uint8_t gap_erk[16] = {0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21,0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21}; static Gap* gap = NULL; +static GapScan* gap_scan = NULL; static void gap_advertise_start(GapState new_state); static int32_t gap_app(void* context); @@ -62,7 +68,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data; - osMutexAcquire(gap->state_mutex, osWaitForever); + if(gap) { + osMutexAcquire(gap->state_mutex, osWaitForever); + } switch (event_pckt->evt) { case EVT_DISCONN_COMPLETE: { @@ -77,7 +85,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) gap_advertise_start(GapStateAdvFast); furi_hal_power_insomnia_exit(); } - BleEvent event = {.type = BleEventTypeDisconnected}; + GapEvent event = {.type = GapEventTypeDisconnected}; gap->on_event_cb(event, gap->context); } break; @@ -120,6 +128,23 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) aci_gap_slave_security_req(connection_complete_event->Connection_Handle); break; + case EVT_LE_ADVERTISING_REPORT: { + if(gap_scan) { + GapAddress address; + hci_le_advertising_report_event_rp0* evt = (hci_le_advertising_report_event_rp0*) meta_evt->data; + for(uint8_t i = 0; i < evt->Num_Reports; i++) { + Advertising_Report_t* rep = &evt->Advertising_Report[i]; + address.type = rep->Address_Type; + // Original MAC addres is in inverted order + for(uint8_t j = 0; j < sizeof(address.mac); j++) { + address.mac[j] = rep->Address[sizeof(address.mac) - j - 1]; + } + gap_scan->callback(address, gap_scan->context); + } + } + } + break; + default: break; } @@ -140,7 +165,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) uint32_t pin = rand() % 999999; aci_gap_pass_key_resp(gap->service.connection_handle, pin); FURI_LOG_I(TAG, "Pass key request event. Pin: %06d", pin); - BleEvent event = {.type = BleEventTypePinCodeShow, .data.pin_code = pin}; + GapEvent event = {.type = GapEventTypePinCodeShow, .data.pin_code = pin}; gap->on_event_cb(event, gap->context); } break; @@ -150,7 +175,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) aci_att_exchange_mtu_resp_event_rp0 *pr = (void*)blue_evt->data; FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU); // Set maximum packet size given header size is 3 bytes - BleEvent event = {.type = BleEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3}; + GapEvent event = {.type = GapEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3}; gap->on_event_cb(event, gap->context); } break; @@ -184,7 +209,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) { uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value; FURI_LOG_I(TAG, "Verify numeric comparison: %06d", pin); - BleEvent event = {.type = BleEventTypePinCodeVerify, .data.pin_code = pin}; + GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin}; bool result = gap->on_event_cb(event, gap->context); aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); break; @@ -197,7 +222,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) aci_gap_terminate(gap->service.connection_handle, 5); } else { FURI_LOG_I(TAG, "Pairing complete"); - BleEvent event = {.type = BleEventTypeConnected}; + GapEvent event = {.type = GapEventTypeConnected}; gap->on_event_cb(event, gap->context); } break; @@ -209,7 +234,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) default: break; } - osMutexRelease(gap->state_mutex); + if(gap) { + osMutexRelease(gap->state_mutex); + } return SVCCTL_UserEvtFlowEnable; } @@ -322,7 +349,7 @@ static void gap_advertise_start(GapState new_state) FURI_LOG_E(TAG, "Set discoverable err: %d", status); } gap->state = new_state; - BleEvent event = {.type = BleEventTypeStartAdvertising}; + GapEvent event = {.type = GapEventTypeStartAdvertising}; gap->on_event_cb(event, gap->context); osTimerStart(gap->advertise_timer, INITIAL_ADV_TIMEOUT); } @@ -338,7 +365,7 @@ static void gap_advertise_stop() { aci_gap_set_non_discoverable(); gap->state = GapStateIdle; } - BleEvent event = {.type = BleEventTypeStopAdvertising}; + GapEvent event = {.type = GapEventTypeStopAdvertising}; gap->on_event_cb(event, gap->context); } @@ -370,7 +397,7 @@ static void gap_advetise_timer_callback(void* context) { furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); } -bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) { +bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { if (!ble_glue_is_radio_stack_ready()) { return false; } @@ -416,12 +443,33 @@ bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) { GapState gap_get_state() { GapState state; - osMutexAcquire(gap->state_mutex, osWaitForever); - state = gap->state; - osMutexRelease(gap->state_mutex ); + if(gap) { + osMutexAcquire(gap->state_mutex, osWaitForever); + state = gap->state; + osMutexRelease(gap->state_mutex ); + } else { + state = GapStateUninitialized; + } return state; } +void gap_start_scan(GapScanCallback callback, void* context) { + furi_assert(callback); + gap_scan = furi_alloc(sizeof(GapScan)); + gap_scan->callback = callback; + gap_scan->context = context; + // Scan interval 250 ms + hci_le_set_scan_parameters(1, 4000, 200, 0, 0); + hci_le_set_scan_enable(1, 1); +} + +void gap_stop_scan() { + furi_assert(gap_scan); + hci_le_set_scan_enable(0, 1); + free(gap_scan); + gap_scan = NULL; +} + void gap_thread_stop() { if(gap) { osMutexAcquire(gap->state_mutex, osWaitForever); diff --git a/firmware/targets/f6/ble-glue/gap.h b/firmware/targets/f6/ble-glue/gap.h index 03d9bbfc..2ea3cdd2 100644 --- a/firmware/targets/f6/ble-glue/gap.h +++ b/firmware/targets/f6/ble-glue/gap.h @@ -12,28 +12,36 @@ extern "C" { #endif typedef enum { - BleEventTypeConnected, - BleEventTypeDisconnected, - BleEventTypeStartAdvertising, - BleEventTypeStopAdvertising, - BleEventTypePinCodeShow, - BleEventTypePinCodeVerify, - BleEventTypeUpdateMTU, -} BleEventType; + GapEventTypeConnected, + GapEventTypeDisconnected, + GapEventTypeStartAdvertising, + GapEventTypeStopAdvertising, + GapEventTypePinCodeShow, + GapEventTypePinCodeVerify, + GapEventTypeUpdateMTU, +} GapEventType; typedef union { uint32_t pin_code; uint16_t max_packet_size; -} BleEventData; +} GapEventData; typedef struct { - BleEventType type; - BleEventData data; -} BleEvent; + GapEventType type; + GapEventData data; +} GapEvent; -typedef bool(*BleEventCallback) (BleEvent event, void* context); +typedef bool(*GapEventCallback) (GapEvent event, void* context); + +typedef struct { + uint8_t type; + uint8_t mac[6]; +} GapAddress; + +typedef void(*GapScanCallback) (GapAddress address, void* context); typedef enum { + GapStateUninitialized, GapStateIdle, GapStateStartingAdv, GapStateAdvFast, @@ -42,6 +50,7 @@ typedef enum { } GapState; typedef enum { + GapPairingNone, GapPairingPinCodeShow, GapPairingPinCodeVerifyYesNo, } GapPairing; @@ -55,7 +64,7 @@ typedef struct { char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; } GapConfig; -bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context); +bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context); void gap_start_advertising(); @@ -65,6 +74,10 @@ GapState gap_get_state(); void gap_thread_stop(); +void gap_start_scan(GapScanCallback callback, void* context); + +void gap_stop_scan(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f6/furi-hal/furi-hal-bt.c b/firmware/targets/f6/furi-hal/furi-hal-bt.c index 5a457279..1435a165 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-bt.c +++ b/firmware/targets/f6/furi-hal/furi-hal-bt.c @@ -16,6 +16,7 @@ #define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72} osMutexId_t furi_hal_bt_core2_mtx = NULL; +static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown; typedef void (*FuriHalBtProfileStart)(void); typedef void (*FuriHalBtProfileStop)(void); @@ -50,7 +51,7 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { .pairing_method = GapPairingPinCodeVerifyYesNo, .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, }, - } + }, }; FuriHalBtProfileConfig* current_profile = NULL; @@ -79,32 +80,81 @@ void furi_hal_bt_unlock_core2() { furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); } -static bool furi_hal_bt_start_core2() { +static bool furi_hal_bt_radio_stack_is_supported(WirelessFwInfo_t* info) { + bool supported = false; + if(info->StackType == INFO_STACK_TYPE_BLE_HCI) { + furi_hal_bt_stack = FuriHalBtStackHciLayer; + supported = true; + } else if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) { + if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR && + info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) { + furi_hal_bt_stack = FuriHalBtStackLight; + supported = true; + } + } else { + furi_hal_bt_stack = FuriHalBtStackUnknown; + } + return supported; +} + +bool furi_hal_bt_start_radio_stack() { + bool res = false; furi_assert(furi_hal_bt_core2_mtx); osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); + // Explicitly tell that we are in charge of CLK48 domain if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); } - // Start Core2 - bool ret = ble_glue_start(); - osMutexRelease(furi_hal_bt_core2_mtx); - - return ret; -} - -bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) { - furi_assert(event_cb); - furi_assert(profile < FuriHalBtProfileNumber); - bool ret = true; do { - // Start 2nd core - ret = furi_hal_bt_start_core2(); - if(!ret) { + // Wait until FUS is started or timeout + WirelessFwInfo_t info = {}; + if(!ble_glue_wait_for_fus_start(&info)) { + FURI_LOG_E(TAG, "FUS start failed"); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + ble_glue_thread_stop(); + break; + } + // Check weather we support radio stack + if(!furi_hal_bt_radio_stack_is_supported(&info)) { + FURI_LOG_E(TAG, "Unsupported radio stack"); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + ble_glue_thread_stop(); + break; + } + // Starting radio stack + if(!ble_glue_start()) { + FURI_LOG_E(TAG, "Failed to start radio stack"); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + ble_glue_thread_stop(); ble_app_thread_stop(); - FURI_LOG_E(TAG, "Failed to start 2nd core"); + break; + } + res = true; + } while(false); + osMutexRelease(furi_hal_bt_core2_mtx); + + return res; +} + +FuriHalBtStack furi_hal_bt_get_radio_stack() { + return furi_hal_bt_stack; +} + +bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { + furi_assert(event_cb); + furi_assert(profile < FuriHalBtProfileNumber); + bool ret = false; + + 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_stack != FuriHalBtStackLight) { + FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack"); break; } // Set mac address @@ -130,21 +180,23 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, const char* clicker_str = "Keynote"; memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); } - ret = gap_init(config, event_cb, context); - if(!ret) { + if(!gap_init(config, event_cb, context)) { gap_thread_stop(); FURI_LOG_E(TAG, "Failed to init GAP"); break; } // Start selected profile services - profile_config[profile].start(); + if(furi_hal_bt_stack == FuriHalBtStackLight) { + profile_config[profile].start(); + } + ret = true; } while(false); current_profile = &profile_config[profile]; return ret; } -bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) { +bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { furi_assert(event_cb); furi_assert(profile < FuriHalBtProfileNumber); bool ret = true; @@ -164,6 +216,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, ble_glue_thread_stop(); FURI_LOG_I(TAG, "Start BT initialization"); furi_hal_bt_init(); + furi_hal_bt_start_radio_stack(); ret = furi_hal_bt_start_app(profile, event_cb, context); if(ret) { current_profile = &profile_config[profile]; @@ -171,6 +224,10 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, return ret; } +static bool furi_hal_bt_is_active() { + return gap_get_state() > GapStateIdle; +} + void furi_hal_bt_start_advertising() { if(gap_get_state() == GapStateIdle) { gap_start_advertising(); @@ -236,10 +293,6 @@ bool furi_hal_bt_is_alive() { return ble_glue_is_alive(); } -bool furi_hal_bt_is_active() { - return gap_get_state() > GapStateIdle; -} - void furi_hal_bt_start_tone_tx(uint8_t channel, uint8_t power) { aci_hal_set_tx_power_level(0, power); aci_hal_tone_start(channel, 0); @@ -300,3 +353,17 @@ uint32_t furi_hal_bt_get_transmitted_packets() { void furi_hal_bt_stop_rx() { aci_hal_rx_stop(); } + +bool furi_hal_bt_start_scan(GapScanCallback callback, void* context) { + if(furi_hal_bt_stack != FuriHalBtStackHciLayer) { + return false; + } + gap_start_scan(callback, context); + return true; +} + +void furi_hal_bt_stop_scan() { + if(furi_hal_bt_stack == FuriHalBtStackHciLayer) { + gap_stop_scan(); + } +} diff --git a/firmware/targets/f7/ble-glue/ble_app.c b/firmware/targets/f7/ble-glue/ble_app.c index 989cea32..ebb94892 100644 --- a/firmware/targets/f7/ble-glue/ble_app.c +++ b/firmware/targets/f7/ble-glue/ble_app.c @@ -16,6 +16,8 @@ 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]; +_Static_assert(sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 49, "Ble stack config structure size mismatch"); + typedef struct { osMutexId_t hci_mtx; osSemaphoreId_t hci_sem; diff --git a/firmware/targets/f7/ble-glue/ble_glue.c b/firmware/targets/f7/ble-glue/ble_glue.c index 096bc614..25162331 100644 --- a/firmware/targets/f7/ble-glue/ble_glue.c +++ b/firmware/targets/f7/ble-glue/ble_glue.c @@ -106,29 +106,31 @@ void ble_glue_init() { */ } -static bool ble_glue_wait_status(BleGlueStatus status) { +bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info) { bool ret = false; size_t countdown = 1000; while (countdown > 0) { - if (ble_glue->status == status) { + if (ble_glue->status == BleGlueStatusFusStarted) { ret = true; break; } countdown--; osDelay(1); } + if(ble_glue->status == BleGlueStatusFusStarted) { + SHCI_GetWirelessFwInfo(info); + } else { + FURI_LOG_E(TAG, "Failed to start FUS"); + ble_glue->status = BleGlueStatusBroken; + } + furi_hal_power_insomnia_exit(); return ret; } bool ble_glue_start() { furi_assert(ble_glue); - if (!ble_glue_wait_status(BleGlueStatusFusStarted)) { - // shutdown core2 power - FURI_LOG_E(TAG, "Core2 catastrophic failure, cutting its power"); - LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); - ble_glue->status = BleGlueStatusBroken; - furi_hal_power_insomnia_exit(); + if (ble_glue->status != BleGlueStatusFusStarted) { return false; } @@ -146,6 +148,7 @@ bool ble_glue_start() { } else { FURI_LOG_E(TAG, "Radio stack startup failed"); ble_glue->status = BleGlueStatusRadioStackMissing; + ble_app_thread_stop(); } furi_hal_power_insomnia_exit(); diff --git a/firmware/targets/f7/ble-glue/ble_glue.h b/firmware/targets/f7/ble-glue/ble_glue.h index a95d633c..598ab072 100644 --- a/firmware/targets/f7/ble-glue/ble_glue.h +++ b/firmware/targets/f7/ble-glue/ble_glue.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -25,6 +26,8 @@ bool ble_glue_start(); */ bool ble_glue_is_alive(); +bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info); + /** Is core2 radio stack present and ready * * @return true if present and ready diff --git a/firmware/targets/f7/ble-glue/gap.c b/firmware/targets/f7/ble-glue/gap.c index 70d3bd4d..5c454422 100644 --- a/firmware/targets/f7/ble-glue/gap.c +++ b/firmware/targets/f7/ble-glue/gap.c @@ -25,7 +25,7 @@ typedef struct { GapConfig* config; GapState state; osMutexId_t state_mutex; - BleEventCallback on_event_cb; + GapEventCallback on_event_cb; void* context; osTimerId_t advertise_timer; FuriThread* thread; @@ -40,12 +40,18 @@ typedef enum { GapCommandKillThread, } GapCommand; +typedef struct { + GapScanCallback callback; + void* context; +} GapScan; + // Identity root key static const uint8_t gap_irk[16] = {0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0,0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0}; // Encryption root key static const uint8_t gap_erk[16] = {0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21,0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21}; static Gap* gap = NULL; +static GapScan* gap_scan = NULL; static void gap_advertise_start(GapState new_state); static int32_t gap_app(void* context); @@ -62,7 +68,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data; - osMutexAcquire(gap->state_mutex, osWaitForever); + if(gap) { + osMutexAcquire(gap->state_mutex, osWaitForever); + } switch (event_pckt->evt) { case EVT_DISCONN_COMPLETE: { @@ -77,7 +85,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) gap_advertise_start(GapStateAdvFast); furi_hal_power_insomnia_exit(); } - BleEvent event = {.type = BleEventTypeDisconnected}; + GapEvent event = {.type = GapEventTypeDisconnected}; gap->on_event_cb(event, gap->context); } break; @@ -120,6 +128,23 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) aci_gap_slave_security_req(connection_complete_event->Connection_Handle); break; + case EVT_LE_ADVERTISING_REPORT: { + if(gap_scan) { + GapAddress address; + hci_le_advertising_report_event_rp0* evt = (hci_le_advertising_report_event_rp0*) meta_evt->data; + for(uint8_t i = 0; i < evt->Num_Reports; i++) { + Advertising_Report_t* rep = &evt->Advertising_Report[i]; + address.type = rep->Address_Type; + // Original MAC addres is in inverted order + for(uint8_t j = 0; j < sizeof(address.mac); j++) { + address.mac[j] = rep->Address[sizeof(address.mac) - j - 1]; + } + gap_scan->callback(address, gap_scan->context); + } + } + } + break; + default: break; } @@ -140,7 +165,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) uint32_t pin = rand() % 999999; aci_gap_pass_key_resp(gap->service.connection_handle, pin); FURI_LOG_I(TAG, "Pass key request event. Pin: %06d", pin); - BleEvent event = {.type = BleEventTypePinCodeShow, .data.pin_code = pin}; + GapEvent event = {.type = GapEventTypePinCodeShow, .data.pin_code = pin}; gap->on_event_cb(event, gap->context); } break; @@ -150,7 +175,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) aci_att_exchange_mtu_resp_event_rp0 *pr = (void*)blue_evt->data; FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU); // Set maximum packet size given header size is 3 bytes - BleEvent event = {.type = BleEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3}; + GapEvent event = {.type = GapEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3}; gap->on_event_cb(event, gap->context); } break; @@ -184,7 +209,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) { uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value; FURI_LOG_I(TAG, "Verify numeric comparison: %06d", pin); - BleEvent event = {.type = BleEventTypePinCodeVerify, .data.pin_code = pin}; + GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin}; bool result = gap->on_event_cb(event, gap->context); aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); break; @@ -197,7 +222,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) aci_gap_terminate(gap->service.connection_handle, 5); } else { FURI_LOG_I(TAG, "Pairing complete"); - BleEvent event = {.type = BleEventTypeConnected}; + GapEvent event = {.type = GapEventTypeConnected}; gap->on_event_cb(event, gap->context); } break; @@ -209,7 +234,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) default: break; } - osMutexRelease(gap->state_mutex); + if(gap) { + osMutexRelease(gap->state_mutex); + } return SVCCTL_UserEvtFlowEnable; } @@ -322,7 +349,7 @@ static void gap_advertise_start(GapState new_state) FURI_LOG_E(TAG, "Set discoverable err: %d", status); } gap->state = new_state; - BleEvent event = {.type = BleEventTypeStartAdvertising}; + GapEvent event = {.type = GapEventTypeStartAdvertising}; gap->on_event_cb(event, gap->context); osTimerStart(gap->advertise_timer, INITIAL_ADV_TIMEOUT); } @@ -338,7 +365,7 @@ static void gap_advertise_stop() { aci_gap_set_non_discoverable(); gap->state = GapStateIdle; } - BleEvent event = {.type = BleEventTypeStopAdvertising}; + GapEvent event = {.type = GapEventTypeStopAdvertising}; gap->on_event_cb(event, gap->context); } @@ -370,7 +397,7 @@ static void gap_advetise_timer_callback(void* context) { furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK); } -bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) { +bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { if (!ble_glue_is_radio_stack_ready()) { return false; } @@ -416,12 +443,33 @@ bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) { GapState gap_get_state() { GapState state; - osMutexAcquire(gap->state_mutex, osWaitForever); - state = gap->state; - osMutexRelease(gap->state_mutex ); + if(gap) { + osMutexAcquire(gap->state_mutex, osWaitForever); + state = gap->state; + osMutexRelease(gap->state_mutex ); + } else { + state = GapStateUninitialized; + } return state; } +void gap_start_scan(GapScanCallback callback, void* context) { + furi_assert(callback); + gap_scan = furi_alloc(sizeof(GapScan)); + gap_scan->callback = callback; + gap_scan->context = context; + // Scan interval 250 ms + hci_le_set_scan_parameters(1, 4000, 200, 0, 0); + hci_le_set_scan_enable(1, 1); +} + +void gap_stop_scan() { + furi_assert(gap_scan); + hci_le_set_scan_enable(0, 1); + free(gap_scan); + gap_scan = NULL; +} + void gap_thread_stop() { if(gap) { osMutexAcquire(gap->state_mutex, osWaitForever); diff --git a/firmware/targets/f7/ble-glue/gap.h b/firmware/targets/f7/ble-glue/gap.h index 03d9bbfc..2ea3cdd2 100644 --- a/firmware/targets/f7/ble-glue/gap.h +++ b/firmware/targets/f7/ble-glue/gap.h @@ -12,28 +12,36 @@ extern "C" { #endif typedef enum { - BleEventTypeConnected, - BleEventTypeDisconnected, - BleEventTypeStartAdvertising, - BleEventTypeStopAdvertising, - BleEventTypePinCodeShow, - BleEventTypePinCodeVerify, - BleEventTypeUpdateMTU, -} BleEventType; + GapEventTypeConnected, + GapEventTypeDisconnected, + GapEventTypeStartAdvertising, + GapEventTypeStopAdvertising, + GapEventTypePinCodeShow, + GapEventTypePinCodeVerify, + GapEventTypeUpdateMTU, +} GapEventType; typedef union { uint32_t pin_code; uint16_t max_packet_size; -} BleEventData; +} GapEventData; typedef struct { - BleEventType type; - BleEventData data; -} BleEvent; + GapEventType type; + GapEventData data; +} GapEvent; -typedef bool(*BleEventCallback) (BleEvent event, void* context); +typedef bool(*GapEventCallback) (GapEvent event, void* context); + +typedef struct { + uint8_t type; + uint8_t mac[6]; +} GapAddress; + +typedef void(*GapScanCallback) (GapAddress address, void* context); typedef enum { + GapStateUninitialized, GapStateIdle, GapStateStartingAdv, GapStateAdvFast, @@ -42,6 +50,7 @@ typedef enum { } GapState; typedef enum { + GapPairingNone, GapPairingPinCodeShow, GapPairingPinCodeVerifyYesNo, } GapPairing; @@ -55,7 +64,7 @@ typedef struct { char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; } GapConfig; -bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context); +bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context); void gap_start_advertising(); @@ -65,6 +74,10 @@ GapState gap_get_state(); void gap_thread_stop(); +void gap_start_scan(GapScanCallback callback, void* context); + +void gap_stop_scan(); + #ifdef __cplusplus } #endif diff --git a/firmware/targets/f7/furi-hal/furi-hal-bt.c b/firmware/targets/f7/furi-hal/furi-hal-bt.c index 5a457279..1435a165 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-bt.c +++ b/firmware/targets/f7/furi-hal/furi-hal-bt.c @@ -16,6 +16,7 @@ #define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72} osMutexId_t furi_hal_bt_core2_mtx = NULL; +static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown; typedef void (*FuriHalBtProfileStart)(void); typedef void (*FuriHalBtProfileStop)(void); @@ -50,7 +51,7 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { .pairing_method = GapPairingPinCodeVerifyYesNo, .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, }, - } + }, }; FuriHalBtProfileConfig* current_profile = NULL; @@ -79,32 +80,81 @@ void furi_hal_bt_unlock_core2() { furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); } -static bool furi_hal_bt_start_core2() { +static bool furi_hal_bt_radio_stack_is_supported(WirelessFwInfo_t* info) { + bool supported = false; + if(info->StackType == INFO_STACK_TYPE_BLE_HCI) { + furi_hal_bt_stack = FuriHalBtStackHciLayer; + supported = true; + } else if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) { + if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR && + info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) { + furi_hal_bt_stack = FuriHalBtStackLight; + supported = true; + } + } else { + furi_hal_bt_stack = FuriHalBtStackUnknown; + } + return supported; +} + +bool furi_hal_bt_start_radio_stack() { + bool res = false; furi_assert(furi_hal_bt_core2_mtx); osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); + // Explicitly tell that we are in charge of CLK48 domain if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); } - // Start Core2 - bool ret = ble_glue_start(); - osMutexRelease(furi_hal_bt_core2_mtx); - - return ret; -} - -bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) { - furi_assert(event_cb); - furi_assert(profile < FuriHalBtProfileNumber); - bool ret = true; do { - // Start 2nd core - ret = furi_hal_bt_start_core2(); - if(!ret) { + // Wait until FUS is started or timeout + WirelessFwInfo_t info = {}; + if(!ble_glue_wait_for_fus_start(&info)) { + FURI_LOG_E(TAG, "FUS start failed"); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + ble_glue_thread_stop(); + break; + } + // Check weather we support radio stack + if(!furi_hal_bt_radio_stack_is_supported(&info)) { + FURI_LOG_E(TAG, "Unsupported radio stack"); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + ble_glue_thread_stop(); + break; + } + // Starting radio stack + if(!ble_glue_start()) { + FURI_LOG_E(TAG, "Failed to start radio stack"); + LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); + ble_glue_thread_stop(); ble_app_thread_stop(); - FURI_LOG_E(TAG, "Failed to start 2nd core"); + break; + } + res = true; + } while(false); + osMutexRelease(furi_hal_bt_core2_mtx); + + return res; +} + +FuriHalBtStack furi_hal_bt_get_radio_stack() { + return furi_hal_bt_stack; +} + +bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { + furi_assert(event_cb); + furi_assert(profile < FuriHalBtProfileNumber); + bool ret = false; + + 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_stack != FuriHalBtStackLight) { + FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack"); break; } // Set mac address @@ -130,21 +180,23 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, const char* clicker_str = "Keynote"; memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); } - ret = gap_init(config, event_cb, context); - if(!ret) { + if(!gap_init(config, event_cb, context)) { gap_thread_stop(); FURI_LOG_E(TAG, "Failed to init GAP"); break; } // Start selected profile services - profile_config[profile].start(); + if(furi_hal_bt_stack == FuriHalBtStackLight) { + profile_config[profile].start(); + } + ret = true; } while(false); current_profile = &profile_config[profile]; return ret; } -bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) { +bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { furi_assert(event_cb); furi_assert(profile < FuriHalBtProfileNumber); bool ret = true; @@ -164,6 +216,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, ble_glue_thread_stop(); FURI_LOG_I(TAG, "Start BT initialization"); furi_hal_bt_init(); + furi_hal_bt_start_radio_stack(); ret = furi_hal_bt_start_app(profile, event_cb, context); if(ret) { current_profile = &profile_config[profile]; @@ -171,6 +224,10 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, return ret; } +static bool furi_hal_bt_is_active() { + return gap_get_state() > GapStateIdle; +} + void furi_hal_bt_start_advertising() { if(gap_get_state() == GapStateIdle) { gap_start_advertising(); @@ -236,10 +293,6 @@ bool furi_hal_bt_is_alive() { return ble_glue_is_alive(); } -bool furi_hal_bt_is_active() { - return gap_get_state() > GapStateIdle; -} - void furi_hal_bt_start_tone_tx(uint8_t channel, uint8_t power) { aci_hal_set_tx_power_level(0, power); aci_hal_tone_start(channel, 0); @@ -300,3 +353,17 @@ uint32_t furi_hal_bt_get_transmitted_packets() { void furi_hal_bt_stop_rx() { aci_hal_rx_stop(); } + +bool furi_hal_bt_start_scan(GapScanCallback callback, void* context) { + if(furi_hal_bt_stack != FuriHalBtStackHciLayer) { + return false; + } + gap_start_scan(callback, context); + return true; +} + +void furi_hal_bt_stop_scan() { + if(furi_hal_bt_stack == FuriHalBtStackHciLayer) { + gap_stop_scan(); + } +} diff --git a/firmware/targets/furi-hal-include/furi-hal-bt.h b/firmware/targets/furi-hal-include/furi-hal-bt.h index 8de7296f..b13d7260 100644 --- a/firmware/targets/furi-hal-include/furi-hal-bt.h +++ b/firmware/targets/furi-hal-include/furi-hal-bt.h @@ -14,10 +14,19 @@ #include "furi-hal-bt-serial.h" +#define FURI_HAL_BT_STACK_VERSION_MAJOR (1) +#define FURI_HAL_BT_STACK_VERSION_MINOR (13) + #ifdef __cplusplus extern "C" { #endif +typedef enum { + FuriHalBtStackUnknown, + FuriHalBtStackHciLayer, + FuriHalBtStackLight, +} FuriHalBtStack; + typedef enum { FuriHalBtProfileSerial, FuriHalBtProfileHidKeyboard, @@ -36,26 +45,38 @@ void furi_hal_bt_lock_core2(); /** Lock core2 state transition */ void furi_hal_bt_unlock_core2(); +/** Start radio stack + * + * @return true on successfull radio stack start + */ +bool furi_hal_bt_start_radio_stack(); + +/** Get radio stack type + * + * @return FuriHalBtStack instance + */ +FuriHalBtStack furi_hal_bt_get_radio_stack(); + /** Start BLE app * * @param profile FuriHalBtProfile instance - * @param event_cb BleEventCallback instance + * @param event_cb GapEventCallback instance * @param context pointer to context * * @return true on success */ -bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context); +bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context); /** Change BLE app * Restarts 2nd core * * @param profile FuriHalBtProfile instance - * @param event_cb BleEventCallback instance + * @param event_cb GapEventCallback instance * @param context pointer to context * * @return true on success */ -bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context); +bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context); /** Update battery level * @@ -71,12 +92,6 @@ void furi_hal_bt_start_advertising(); */ void furi_hal_bt_stop_advertising(); -/** Returns true if BLE is advertising - * - * @return true if BLE advertising - */ -bool furi_hal_bt_is_active(); - /** Get BT/BLE system component state * * @param[in] buffer string_t buffer to write to @@ -167,6 +182,17 @@ float furi_hal_bt_get_rssi(); */ uint32_t furi_hal_bt_get_transmitted_packets(); +/** Start MAC addresses scan + * @note Works only with HciLayer 2nd core firmware + * + * @param callback GapScanCallback instance + * @param context pointer to context + */ +bool furi_hal_bt_start_scan(GapScanCallback callback, void* context); + +/** Stop MAC addresses scan */ +void furi_hal_bt_stop_scan(); + #ifdef __cplusplus } #endif