[FL-1220] BLE scan MAC addresses test (#939)
* bt: refactore cli commands * bt: add radio stack control, add scan mac addresses * bt: refactore with new furi-hal-bt API * bt: f6 targer sync * bt: code cleanup, update documentation * Bt: new command names, proper radio stack handling Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									a39002ce22
								
							
						
					
					
						commit
						7522b111c2
					
				| @ -1,23 +1,18 @@ | ||||
| #include "bt_cli.h" | ||||
| #include <furi.h> | ||||
| #include <furi-hal.h> | ||||
| #include <applications/cli/cli.h> | ||||
| #include <lib/toolbox/args.h> | ||||
| 
 | ||||
| #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", "<Channel number> <Power>", 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 <cmd> <args>\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 <channel:0-39> <power:0-6>\t - start tx carrier test\r\n"); | ||||
|         printf("\trx_carrier <channel:0-39>\t - start rx carrier test\r\n"); | ||||
|         printf("\ttx_pt <channel:0-39> <pattern:0-5> <datarate:1-2>\t - start tx packet test\r\n"); | ||||
|         printf("\trx_pt <channel:0-39> <datarate:1-2>\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", "<Channel number>", 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", "<Channel number> <Pattern> <Datarate>", 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", "<Channel number> <Datarate>", 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 | ||||
| } | ||||
|  | ||||
| @ -1,15 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <cli/cli.h> | ||||
| 
 | ||||
| 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); | ||||
							
								
								
									
										9
									
								
								applications/bt/bt_debug_app/bt_debug_app.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										9
									
								
								applications/bt/bt_debug_app/bt_debug_app.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -1,6 +1,8 @@ | ||||
| #include "bt_debug_app.h" | ||||
| #include <furi-hal-bt.h> | ||||
| 
 | ||||
| #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(); | ||||
|  | ||||
| @ -4,6 +4,7 @@ | ||||
| #include <gui/gui.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <dialogs/dialogs.h> | ||||
| 
 | ||||
| #include <gui/modules/submenu.h> | ||||
| #include "views/bt_carrier_test.h" | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -10,6 +10,7 @@ extern "C" { | ||||
| typedef struct Bt Bt; | ||||
| 
 | ||||
| typedef enum { | ||||
|     BtStatusUnavailable, | ||||
|     BtStatusOff, | ||||
|     BtStatusAdvertising, | ||||
|     BtStatusConnected, | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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(); | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <shci/shci.h> | ||||
| 
 | ||||
| #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 | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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(); | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <shci/shci.h> | ||||
| 
 | ||||
| #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 | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 gornekich
						gornekich