[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.h> | ||||||
| #include <furi-hal.h> | #include <furi-hal.h> | ||||||
|  | #include <applications/cli/cli.h> | ||||||
|  | #include <lib/toolbox/args.h> | ||||||
|  | 
 | ||||||
| #include "bt_settings.h" | #include "bt_settings.h" | ||||||
| 
 | 
 | ||||||
| void bt_on_system_start() { | static const char* bt_cli_address_types[] = { | ||||||
| #ifdef SRV_CLI |     "Public Device Address", | ||||||
|     Cli* cli = furi_record_open("cli"); |     "Random Device Address", | ||||||
|  |     "Public Identity Address", | ||||||
|  |     "Random (Static) Identity Address", | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
|     cli_add_command(cli, "bt_info", CliCommandFlagDefault, bt_cli_command_info, NULL); | static void bt_cli_command_hci_info(Cli* cli, string_t args, void* context) { | ||||||
|     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) { |  | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
|     furi_hal_bt_dump_state(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); |     string_clear(buffer); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { | static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { | ||||||
|     uint16_t channel; |     int channel = 0; | ||||||
|     uint16_t power; |     int power = 0; | ||||||
|     BtSettings bt_settings; |  | ||||||
|     bt_settings_load(&bt_settings); |  | ||||||
| 
 | 
 | ||||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &power); |     do { | ||||||
|     if(ret != 2) { |         if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) { | ||||||
|         printf("sscanf returned %d, channel: %hu, power: %hu\r\n", ret, channel, power); |             printf("Incorrect or missing channel, expected int 0-39"); | ||||||
|         cli_print_usage("bt_tx_carrier", "<Channel number> <Power>", string_get_cstr(args)); |             break; | ||||||
|         return; |         } | ||||||
|     } |         if(!args_read_int_and_trim(args, &power) && (power < 0 || power > 6)) { | ||||||
|     if(channel > 39) { |             printf("Incorrect or missing power, expected int 0-6"); | ||||||
|         printf("Channel number must be in 0...39 range, not %hu\r\n", channel); |             break; | ||||||
|         return; |         } | ||||||
|     } |  | ||||||
|     if(power > 6) { |  | ||||||
|         printf("Power must be in 0...6 dB range, not %hu\r\n", power); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     furi_hal_bt_stop_advertising(); |         furi_hal_bt_stop_advertising(); | ||||||
|     printf("Transmitting carrier at %hu channel at %hu dB power\r\n", channel, power); |         printf("Transmitting carrier at %d channel at %d dB power\r\n", channel, power); | ||||||
|     printf("Press CTRL+C to stop\r\n"); |         printf("Press CTRL+C to stop\r\n"); | ||||||
|     furi_hal_bt_start_tone_tx(channel, 0x19 + power); |         furi_hal_bt_start_tone_tx(channel, 0x19 + power); | ||||||
| 
 | 
 | ||||||
|     while(!cli_cmd_interrupt_received(cli)) { |         while(!cli_cmd_interrupt_received(cli)) { | ||||||
|         osDelay(250); |             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(); |     furi_hal_bt_stop_scan(); | ||||||
|     if(bt_settings.enabled) { |     osMessageQueueDelete(queue); | ||||||
|         furi_hal_bt_start_advertising(); | } | ||||||
|  | 
 | ||||||
|  | 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) { | static void bt_cli(Cli* cli, string_t args, void* context) { | ||||||
|     uint16_t channel; |     string_t cmd; | ||||||
|  |     string_init(cmd); | ||||||
|     BtSettings bt_settings; |     BtSettings bt_settings; | ||||||
|     bt_settings_load(&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(); |     do { | ||||||
|     printf("Receiving carrier at %hu channel\r\n", channel); |         if(!args_read_string_and_trim(args, cmd)) { | ||||||
|     printf("Press CTRL+C to stop\r\n"); |             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) { |     if(bt_settings.enabled) { | ||||||
|         furi_hal_bt_start_advertising(); |         furi_hal_bt_start_advertising(); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(cmd); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { | void bt_on_system_start() { | ||||||
|     uint16_t channel; | #ifdef SRV_CLI | ||||||
|     uint16_t pattern; |     Cli* cli = furi_record_open("cli"); | ||||||
|     uint16_t datarate; |     furi_record_open("bt"); | ||||||
|     BtSettings bt_settings; |     cli_add_command(cli, "bt", CliCommandFlagDefault, bt_cli, NULL); | ||||||
|     bt_settings_load(&bt_settings); |     furi_record_close("bt"); | ||||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu %hu", &channel, &pattern, &datarate); |     furi_record_close("cli"); | ||||||
|     if(ret != 3) { | #endif | ||||||
|         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(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -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 "bt_debug_app.h" | ||||||
| #include <furi-hal-bt.h> | #include <furi-hal-bt.h> | ||||||
| 
 | 
 | ||||||
|  | #define TAG "BtDebugApp" | ||||||
|  | 
 | ||||||
| enum BtDebugSubmenuIndex { | enum BtDebugSubmenuIndex { | ||||||
|     BtDebugSubmenuIndexCarrierTest, |     BtDebugSubmenuIndexCarrierTest, | ||||||
|     BtDebugSubmenuIndexPacketTest, |     BtDebugSubmenuIndexPacketTest, | ||||||
| @ -92,6 +94,13 @@ void bt_debug_app_free(BtDebugApp* app) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int32_t bt_debug_app(void* p) { | 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(); |     BtDebugApp* app = bt_debug_app_alloc(); | ||||||
|     // Stop advertising
 |     // Stop advertising
 | ||||||
|     furi_hal_bt_stop_advertising(); |     furi_hal_bt_stop_advertising(); | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ | |||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
|  | #include <dialogs/dialogs.h> | ||||||
| 
 | 
 | ||||||
| #include <gui/modules/submenu.h> | #include <gui/modules/submenu.h> | ||||||
| #include "views/bt_carrier_test.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
 | // 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); |     furi_assert(context); | ||||||
|     Bt* bt = context; |     Bt* bt = context; | ||||||
|     bool ret = false; |     bool ret = false; | ||||||
| 
 | 
 | ||||||
|     if(event.type == BleEventTypeConnected) { |     if(event.type == GapEventTypeConnected) { | ||||||
|         // Update status bar
 |         // Update status bar
 | ||||||
|         bt->status = BtStatusConnected; |         bt->status = BtStatusConnected; | ||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; |         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; |         message.data.battery_level = info.charge; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == BleEventTypeDisconnected) { |     } else if(event.type == GapEventTypeDisconnected) { | ||||||
|         if(bt->profile == BtProfileSerial && bt->rpc_session) { |         if(bt->profile == BtProfileSerial && bt->rpc_session) { | ||||||
|             FURI_LOG_I(TAG, "Close RPC connection"); |             FURI_LOG_I(TAG, "Close RPC connection"); | ||||||
|             osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); |             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; |             bt->rpc_session = NULL; | ||||||
|         } |         } | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == BleEventTypeStartAdvertising) { |     } else if(event.type == GapEventTypeStartAdvertising) { | ||||||
|         bt->status = BtStatusAdvertising; |         bt->status = BtStatusAdvertising; | ||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; |         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == BleEventTypeStopAdvertising) { |     } else if(event.type == GapEventTypeStopAdvertising) { | ||||||
|         bt->status = BtStatusOff; |         bt->status = BtStatusOff; | ||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; |         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == BleEventTypePinCodeShow) { |     } else if(event.type == GapEventTypePinCodeShow) { | ||||||
|         BtMessage message = { |         BtMessage message = { | ||||||
|             .type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code}; |             .type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == BleEventTypePinCodeVerify) { |     } else if(event.type == GapEventTypePinCodeVerify) { | ||||||
|         ret = bt_pin_code_verify_event_handler(bt, event.data.pin_code); |         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; |         bt->max_packet_size = event.data.max_packet_size; | ||||||
|         ret = true; |         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) { | static void bt_change_profile(Bt* bt, BtMessage* message) { | ||||||
|     bt_settings_load(&bt->bt_settings); |     FuriHalBtStack stack = furi_hal_bt_get_radio_stack(); | ||||||
|     if(bt->profile == BtProfileSerial && bt->rpc_session) { |     if(stack == FuriHalBtStackLight) { | ||||||
|         FURI_LOG_I(TAG, "Close RPC connection"); |         bt_settings_load(&bt->bt_settings); | ||||||
|         osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); |         if(bt->profile == BtProfileSerial && bt->rpc_session) { | ||||||
|         rpc_session_close(bt->rpc_session); |             FURI_LOG_I(TAG, "Close RPC connection"); | ||||||
|         furi_hal_bt_serial_set_event_callback(0, NULL, NULL); |             osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); | ||||||
|         bt->rpc_session = NULL; |             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; |         FuriHalBtProfile furi_profile; | ||||||
|     } else { |         if(message->data.profile == BtProfileHidKeyboard) { | ||||||
|         furi_profile = FuriHalBtProfileSerial; |             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) { |         if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) { | ||||||
|             furi_hal_bt_start_advertising(); |             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 { |     } else { | ||||||
|         FURI_LOG_E(TAG, "Failed to start Bt App"); |         bt_show_warning(bt, "Radio stack doesn't support this app"); | ||||||
|         *message->result = false; |         *message->result = false; | ||||||
|     } |     } | ||||||
|     osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT); |     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() { | int32_t bt_srv() { | ||||||
|     Bt* bt = bt_alloc(); |     Bt* bt = bt_alloc(); | ||||||
|     furi_record_create("bt", bt); |  | ||||||
| 
 | 
 | ||||||
|     // Read keys
 |     // Read keys
 | ||||||
|     if(!bt_load_key_storage(bt)) { |     if(!bt_load_key_storage(bt)) { | ||||||
|         FURI_LOG_W(TAG, "Failed to load bonding keys"); |         FURI_LOG_W(TAG, "Failed to load bonding keys"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Start BLE stack
 |     // Start radio stack
 | ||||||
|     if(furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) { |     if(!furi_hal_bt_start_radio_stack()) { | ||||||
|         FURI_LOG_I(TAG, "BLE stack started"); |         FURI_LOG_E(TAG, "Radio stack start failed"); | ||||||
|         if(bt->bt_settings.enabled) { |     } | ||||||
|             furi_hal_bt_start_advertising(); |     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
 |     furi_record_create("bt", bt); | ||||||
|     bt_statusbar_update(bt); |  | ||||||
| 
 | 
 | ||||||
|     BtMessage message; |     BtMessage message; | ||||||
|     while(1) { |     while(1) { | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ extern "C" { | |||||||
| typedef struct Bt Bt; | typedef struct Bt Bt; | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|  |     BtStatusUnavailable, | ||||||
|     BtStatusOff, |     BtStatusOff, | ||||||
|     BtStatusAdvertising, |     BtStatusAdvertising, | ||||||
|     BtStatusConnected, |     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) { | void bt_settings_scene_start_on_enter(void* context) { | ||||||
|     BtSettingsApp* app = context; |     BtSettingsApp* app = context; | ||||||
|     VariableItemList* var_item_list = app->var_item_list; |     VariableItemList* var_item_list = app->var_item_list; | ||||||
| 
 |  | ||||||
|     VariableItem* item; |     VariableItem* item; | ||||||
|     item = variable_item_list_add( | 
 | ||||||
|         var_item_list, |     FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack(); | ||||||
|         "Bluetooth", |     if(stack_type == FuriHalBtStackLight) { | ||||||
|         BtSettingNum, |         item = variable_item_list_add( | ||||||
|         bt_settings_scene_start_var_list_change_callback, |             var_item_list, | ||||||
|         app); |             "Bluetooth", | ||||||
|     if(app->settings.enabled) { |             BtSettingNum, | ||||||
|         variable_item_set_current_value_index(item, BtSettingOn); |             bt_settings_scene_start_var_list_change_callback, | ||||||
|         variable_item_set_current_value_text(item, bt_settings_text[BtSettingOn]); |             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 { |     } else { | ||||||
|         variable_item_set_current_value_index(item, BtSettingOff); |         item = variable_item_list_add(var_item_list, "Bluetooth", 1, NULL, NULL); | ||||||
|         variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); |         variable_item_set_current_value_text(item, "Broken"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, BtSettingsAppViewVarItemList); |     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_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]; | 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 { | typedef struct { | ||||||
|     osMutexId_t hci_mtx; |     osMutexId_t hci_mtx; | ||||||
|     osSemaphoreId_t hci_sem; |     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; |     bool ret = false; | ||||||
|     size_t countdown = 1000; |     size_t countdown = 1000; | ||||||
|     while (countdown > 0) { |     while (countdown > 0) { | ||||||
|         if (ble_glue->status == status) { |         if (ble_glue->status == BleGlueStatusFusStarted) { | ||||||
|             ret = true; |             ret = true; | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         countdown--; |         countdown--; | ||||||
|         osDelay(1); |         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; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ble_glue_start() { | bool ble_glue_start() { | ||||||
|     furi_assert(ble_glue); |     furi_assert(ble_glue); | ||||||
| 
 | 
 | ||||||
|     if (!ble_glue_wait_status(BleGlueStatusFusStarted)) { |     if (ble_glue->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(); |  | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -146,6 +148,7 @@ bool ble_glue_start() { | |||||||
|     } else { |     } else { | ||||||
|         FURI_LOG_E(TAG, "Radio stack startup failed"); |         FURI_LOG_E(TAG, "Radio stack startup failed"); | ||||||
|         ble_glue->status = BleGlueStatusRadioStackMissing; |         ble_glue->status = BleGlueStatusRadioStackMissing; | ||||||
|  |         ble_app_thread_stop(); | ||||||
|     } |     } | ||||||
|     furi_hal_power_insomnia_exit(); |     furi_hal_power_insomnia_exit(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
|  | #include <shci/shci.h> | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -25,6 +26,8 @@ bool ble_glue_start(); | |||||||
|  */ |  */ | ||||||
| bool ble_glue_is_alive(); | bool ble_glue_is_alive(); | ||||||
| 
 | 
 | ||||||
|  | bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info); | ||||||
|  | 
 | ||||||
| /** Is core2 radio stack present and ready
 | /** Is core2 radio stack present and ready
 | ||||||
|  * |  * | ||||||
|  * @return     true if present and ready |  * @return     true if present and ready | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ typedef struct { | |||||||
|     GapConfig* config; |     GapConfig* config; | ||||||
|     GapState state; |     GapState state; | ||||||
|     osMutexId_t state_mutex; |     osMutexId_t state_mutex; | ||||||
|     BleEventCallback on_event_cb; |     GapEventCallback on_event_cb; | ||||||
|     void* context; |     void* context; | ||||||
|     osTimerId_t advertise_timer; |     osTimerId_t advertise_timer; | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
| @ -40,12 +40,18 @@ typedef enum { | |||||||
|     GapCommandKillThread, |     GapCommandKillThread, | ||||||
| } GapCommand; | } GapCommand; | ||||||
| 
 | 
 | ||||||
|  | typedef struct { | ||||||
|  |     GapScanCallback callback; | ||||||
|  |     void* context; | ||||||
|  | } GapScan; | ||||||
|  | 
 | ||||||
| // Identity root key
 | // 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}; | 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
 | // 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 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 Gap* gap = NULL; | ||||||
|  | static GapScan* gap_scan = NULL; | ||||||
| 
 | 
 | ||||||
| static void gap_advertise_start(GapState new_state); | static void gap_advertise_start(GapState new_state); | ||||||
| static int32_t gap_app(void* context); | 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; |     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) { |     switch (event_pckt->evt) { | ||||||
|         case EVT_DISCONN_COMPLETE: |         case EVT_DISCONN_COMPLETE: | ||||||
|         { |         { | ||||||
| @ -77,7 +85,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | |||||||
|                 gap_advertise_start(GapStateAdvFast); |                 gap_advertise_start(GapStateAdvFast); | ||||||
|                 furi_hal_power_insomnia_exit(); |                 furi_hal_power_insomnia_exit(); | ||||||
|             } |             } | ||||||
|             BleEvent event = {.type = BleEventTypeDisconnected}; |             GapEvent event = {.type = GapEventTypeDisconnected}; | ||||||
|             gap->on_event_cb(event, gap->context); |             gap->on_event_cb(event, gap->context); | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
| @ -120,6 +128,23 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | |||||||
|                 aci_gap_slave_security_req(connection_complete_event->Connection_Handle); |                 aci_gap_slave_security_req(connection_complete_event->Connection_Handle); | ||||||
|                 break; |                 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: |                 default: | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| @ -140,7 +165,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | |||||||
|                 uint32_t pin = rand() % 999999; |                 uint32_t pin = rand() % 999999; | ||||||
|                 aci_gap_pass_key_resp(gap->service.connection_handle, pin); |                 aci_gap_pass_key_resp(gap->service.connection_handle, pin); | ||||||
|                 FURI_LOG_I(TAG, "Pass key request event. Pin: %06d", 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); |                 gap->on_event_cb(event, gap->context); | ||||||
|             } |             } | ||||||
|                 break; |                 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; |                 aci_att_exchange_mtu_resp_event_rp0 *pr = (void*)blue_evt->data; | ||||||
|                 FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU); |                 FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU); | ||||||
|                 // Set maximum packet size given header size is 3 bytes
 |                 // 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); |                 gap->on_event_cb(event, gap->context); | ||||||
|             } |             } | ||||||
|                 break; |                 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; |                 uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value; | ||||||
|                 FURI_LOG_I(TAG, "Verify numeric comparison: %06d", pin); |                 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); |                 bool result = gap->on_event_cb(event, gap->context); | ||||||
|                 aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); |                 aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); | ||||||
|                 break; |                 break; | ||||||
| @ -197,7 +222,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | |||||||
|                     aci_gap_terminate(gap->service.connection_handle, 5); |                     aci_gap_terminate(gap->service.connection_handle, 5); | ||||||
|                 } else { |                 } else { | ||||||
|                     FURI_LOG_I(TAG, "Pairing complete"); |                     FURI_LOG_I(TAG, "Pairing complete"); | ||||||
|                     BleEvent event = {.type = BleEventTypeConnected}; |                     GapEvent event = {.type = GapEventTypeConnected}; | ||||||
|                     gap->on_event_cb(event, gap->context); |                     gap->on_event_cb(event, gap->context); | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
| @ -209,7 +234,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | |||||||
|             default: |             default: | ||||||
|                 break; |                 break; | ||||||
|     } |     } | ||||||
|     osMutexRelease(gap->state_mutex); |     if(gap) { | ||||||
|  |         osMutexRelease(gap->state_mutex); | ||||||
|  |     } | ||||||
|     return SVCCTL_UserEvtFlowEnable; |     return SVCCTL_UserEvtFlowEnable; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -322,7 +349,7 @@ static void gap_advertise_start(GapState new_state) | |||||||
|         FURI_LOG_E(TAG, "Set discoverable err: %d", status); |         FURI_LOG_E(TAG, "Set discoverable err: %d", status); | ||||||
|     } |     } | ||||||
|     gap->state = new_state; |     gap->state = new_state; | ||||||
|     BleEvent event = {.type = BleEventTypeStartAdvertising}; |     GapEvent event = {.type = GapEventTypeStartAdvertising}; | ||||||
|     gap->on_event_cb(event, gap->context); |     gap->on_event_cb(event, gap->context); | ||||||
|     osTimerStart(gap->advertise_timer, INITIAL_ADV_TIMEOUT); |     osTimerStart(gap->advertise_timer, INITIAL_ADV_TIMEOUT); | ||||||
| } | } | ||||||
| @ -338,7 +365,7 @@ static void gap_advertise_stop() { | |||||||
|         aci_gap_set_non_discoverable(); |         aci_gap_set_non_discoverable(); | ||||||
|         gap->state = GapStateIdle; |         gap->state = GapStateIdle; | ||||||
|     } |     } | ||||||
|     BleEvent event = {.type = BleEventTypeStopAdvertising}; |     GapEvent event = {.type = GapEventTypeStopAdvertising}; | ||||||
|     gap->on_event_cb(event, gap->context); |     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); |     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()) { |     if (!ble_glue_is_radio_stack_ready()) { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @ -416,12 +443,33 @@ bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) { | |||||||
| 
 | 
 | ||||||
| GapState gap_get_state() { | GapState gap_get_state() { | ||||||
|     GapState state; |     GapState state; | ||||||
|     osMutexAcquire(gap->state_mutex, osWaitForever); |     if(gap) { | ||||||
|     state = gap->state; |         osMutexAcquire(gap->state_mutex, osWaitForever); | ||||||
|     osMutexRelease(gap->state_mutex ); |         state = gap->state; | ||||||
|  |         osMutexRelease(gap->state_mutex ); | ||||||
|  |     } else { | ||||||
|  |         state = GapStateUninitialized; | ||||||
|  |     } | ||||||
|     return state; |     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() { | void gap_thread_stop() { | ||||||
|     if(gap) { |     if(gap) { | ||||||
|         osMutexAcquire(gap->state_mutex, osWaitForever); |         osMutexAcquire(gap->state_mutex, osWaitForever); | ||||||
|  | |||||||
| @ -12,28 +12,36 @@ extern "C" { | |||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     BleEventTypeConnected, |     GapEventTypeConnected, | ||||||
|     BleEventTypeDisconnected, |     GapEventTypeDisconnected, | ||||||
|     BleEventTypeStartAdvertising, |     GapEventTypeStartAdvertising, | ||||||
|     BleEventTypeStopAdvertising, |     GapEventTypeStopAdvertising, | ||||||
|     BleEventTypePinCodeShow, |     GapEventTypePinCodeShow, | ||||||
|     BleEventTypePinCodeVerify, |     GapEventTypePinCodeVerify, | ||||||
|     BleEventTypeUpdateMTU, |     GapEventTypeUpdateMTU, | ||||||
| } BleEventType; | } GapEventType; | ||||||
| 
 | 
 | ||||||
| typedef union { | typedef union { | ||||||
|     uint32_t pin_code; |     uint32_t pin_code; | ||||||
|     uint16_t max_packet_size; |     uint16_t max_packet_size; | ||||||
| } BleEventData; | } GapEventData; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     BleEventType type; |     GapEventType type; | ||||||
|     BleEventData data; |     GapEventData data; | ||||||
| } BleEvent; | } 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 { | typedef enum { | ||||||
|  |     GapStateUninitialized, | ||||||
|     GapStateIdle, |     GapStateIdle, | ||||||
|     GapStateStartingAdv, |     GapStateStartingAdv, | ||||||
|     GapStateAdvFast, |     GapStateAdvFast, | ||||||
| @ -42,6 +50,7 @@ typedef enum { | |||||||
| } GapState; | } GapState; | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|  |     GapPairingNone, | ||||||
|     GapPairingPinCodeShow, |     GapPairingPinCodeShow, | ||||||
|     GapPairingPinCodeVerifyYesNo, |     GapPairingPinCodeVerifyYesNo, | ||||||
| } GapPairing; | } GapPairing; | ||||||
| @ -55,7 +64,7 @@ typedef struct { | |||||||
|     char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; |     char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; | ||||||
| } GapConfig; | } 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(); | void gap_start_advertising(); | ||||||
| 
 | 
 | ||||||
| @ -65,6 +74,10 @@ GapState gap_get_state(); | |||||||
| 
 | 
 | ||||||
| void gap_thread_stop(); | void gap_thread_stop(); | ||||||
| 
 | 
 | ||||||
|  | void gap_start_scan(GapScanCallback callback, void* context); | ||||||
|  | 
 | ||||||
|  | void gap_stop_scan(); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ | |||||||
| #define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72} | #define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72} | ||||||
| 
 | 
 | ||||||
| osMutexId_t furi_hal_bt_core2_mtx = NULL; | osMutexId_t furi_hal_bt_core2_mtx = NULL; | ||||||
|  | static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown; | ||||||
| 
 | 
 | ||||||
| typedef void (*FuriHalBtProfileStart)(void); | typedef void (*FuriHalBtProfileStart)(void); | ||||||
| typedef void (*FuriHalBtProfileStop)(void); | typedef void (*FuriHalBtProfileStop)(void); | ||||||
| @ -50,7 +51,7 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { | |||||||
|             .pairing_method = GapPairingPinCodeVerifyYesNo, |             .pairing_method = GapPairingPinCodeVerifyYesNo, | ||||||
|             .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, |             .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, | ||||||
|         }, |         }, | ||||||
|     } |     }, | ||||||
| }; | }; | ||||||
| FuriHalBtProfileConfig* current_profile = NULL; | FuriHalBtProfileConfig* current_profile = NULL; | ||||||
| 
 | 
 | ||||||
| @ -79,32 +80,81 @@ void furi_hal_bt_unlock_core2() { | |||||||
|     furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); |     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); |     furi_assert(furi_hal_bt_core2_mtx); | ||||||
| 
 | 
 | ||||||
|     osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); |     osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); | ||||||
|  | 
 | ||||||
|     // Explicitly tell that we are in charge of CLK48 domain
 |     // Explicitly tell that we are in charge of CLK48 domain
 | ||||||
|     if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { |     if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { | ||||||
|         HAL_HSEM_FastTake(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 { |     do { | ||||||
|         // Start 2nd core
 |         // Wait until FUS is started or timeout
 | ||||||
|         ret = furi_hal_bt_start_core2(); |         WirelessFwInfo_t info = {}; | ||||||
|         if(!ret) { |         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(); |             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; |             break; | ||||||
|         } |         } | ||||||
|         // Set mac address
 |         // Set mac address
 | ||||||
| @ -130,21 +180,23 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, | |||||||
|             const char* clicker_str = "Keynote"; |             const char* clicker_str = "Keynote"; | ||||||
|             memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); |             memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); | ||||||
|         } |         } | ||||||
|         ret = gap_init(config, event_cb, context); |         if(!gap_init(config, event_cb, context)) { | ||||||
|         if(!ret) { |  | ||||||
|             gap_thread_stop(); |             gap_thread_stop(); | ||||||
|             FURI_LOG_E(TAG, "Failed to init GAP"); |             FURI_LOG_E(TAG, "Failed to init GAP"); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         // Start selected profile services
 |         // Start selected profile services
 | ||||||
|         profile_config[profile].start(); |         if(furi_hal_bt_stack == FuriHalBtStackLight) { | ||||||
|  |             profile_config[profile].start(); | ||||||
|  |         } | ||||||
|  |         ret = true; | ||||||
|     } while(false); |     } while(false); | ||||||
|     current_profile = &profile_config[profile]; |     current_profile = &profile_config[profile]; | ||||||
| 
 | 
 | ||||||
|     return ret; |     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(event_cb); | ||||||
|     furi_assert(profile < FuriHalBtProfileNumber); |     furi_assert(profile < FuriHalBtProfileNumber); | ||||||
|     bool ret = true; |     bool ret = true; | ||||||
| @ -164,6 +216,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, | |||||||
|     ble_glue_thread_stop(); |     ble_glue_thread_stop(); | ||||||
|     FURI_LOG_I(TAG, "Start BT initialization"); |     FURI_LOG_I(TAG, "Start BT initialization"); | ||||||
|     furi_hal_bt_init(); |     furi_hal_bt_init(); | ||||||
|  |     furi_hal_bt_start_radio_stack(); | ||||||
|     ret = furi_hal_bt_start_app(profile, event_cb, context); |     ret = furi_hal_bt_start_app(profile, event_cb, context); | ||||||
|     if(ret) { |     if(ret) { | ||||||
|         current_profile = &profile_config[profile]; |         current_profile = &profile_config[profile]; | ||||||
| @ -171,6 +224,10 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, | |||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static bool furi_hal_bt_is_active() { | ||||||
|  |     return gap_get_state() > GapStateIdle; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void furi_hal_bt_start_advertising() { | void furi_hal_bt_start_advertising() { | ||||||
|     if(gap_get_state() == GapStateIdle) { |     if(gap_get_state() == GapStateIdle) { | ||||||
|         gap_start_advertising(); |         gap_start_advertising(); | ||||||
| @ -236,10 +293,6 @@ bool furi_hal_bt_is_alive() { | |||||||
|     return ble_glue_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) { | void furi_hal_bt_start_tone_tx(uint8_t channel, uint8_t power) { | ||||||
|     aci_hal_set_tx_power_level(0, power); |     aci_hal_set_tx_power_level(0, power); | ||||||
|     aci_hal_tone_start(channel, 0); |     aci_hal_tone_start(channel, 0); | ||||||
| @ -300,3 +353,17 @@ uint32_t furi_hal_bt_get_transmitted_packets() { | |||||||
| void furi_hal_bt_stop_rx() { | void furi_hal_bt_stop_rx() { | ||||||
|     aci_hal_rx_stop(); |     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_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]; | 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 { | typedef struct { | ||||||
|     osMutexId_t hci_mtx; |     osMutexId_t hci_mtx; | ||||||
|     osSemaphoreId_t hci_sem; |     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; |     bool ret = false; | ||||||
|     size_t countdown = 1000; |     size_t countdown = 1000; | ||||||
|     while (countdown > 0) { |     while (countdown > 0) { | ||||||
|         if (ble_glue->status == status) { |         if (ble_glue->status == BleGlueStatusFusStarted) { | ||||||
|             ret = true; |             ret = true; | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         countdown--; |         countdown--; | ||||||
|         osDelay(1); |         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; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool ble_glue_start() { | bool ble_glue_start() { | ||||||
|     furi_assert(ble_glue); |     furi_assert(ble_glue); | ||||||
| 
 | 
 | ||||||
|     if (!ble_glue_wait_status(BleGlueStatusFusStarted)) { |     if (ble_glue->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(); |  | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -146,6 +148,7 @@ bool ble_glue_start() { | |||||||
|     } else { |     } else { | ||||||
|         FURI_LOG_E(TAG, "Radio stack startup failed"); |         FURI_LOG_E(TAG, "Radio stack startup failed"); | ||||||
|         ble_glue->status = BleGlueStatusRadioStackMissing; |         ble_glue->status = BleGlueStatusRadioStackMissing; | ||||||
|  |         ble_app_thread_stop(); | ||||||
|     } |     } | ||||||
|     furi_hal_power_insomnia_exit(); |     furi_hal_power_insomnia_exit(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
|  | #include <shci/shci.h> | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -25,6 +26,8 @@ bool ble_glue_start(); | |||||||
|  */ |  */ | ||||||
| bool ble_glue_is_alive(); | bool ble_glue_is_alive(); | ||||||
| 
 | 
 | ||||||
|  | bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info); | ||||||
|  | 
 | ||||||
| /** Is core2 radio stack present and ready
 | /** Is core2 radio stack present and ready
 | ||||||
|  * |  * | ||||||
|  * @return     true if present and ready |  * @return     true if present and ready | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ typedef struct { | |||||||
|     GapConfig* config; |     GapConfig* config; | ||||||
|     GapState state; |     GapState state; | ||||||
|     osMutexId_t state_mutex; |     osMutexId_t state_mutex; | ||||||
|     BleEventCallback on_event_cb; |     GapEventCallback on_event_cb; | ||||||
|     void* context; |     void* context; | ||||||
|     osTimerId_t advertise_timer; |     osTimerId_t advertise_timer; | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
| @ -40,12 +40,18 @@ typedef enum { | |||||||
|     GapCommandKillThread, |     GapCommandKillThread, | ||||||
| } GapCommand; | } GapCommand; | ||||||
| 
 | 
 | ||||||
|  | typedef struct { | ||||||
|  |     GapScanCallback callback; | ||||||
|  |     void* context; | ||||||
|  | } GapScan; | ||||||
|  | 
 | ||||||
| // Identity root key
 | // 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}; | 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
 | // 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 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 Gap* gap = NULL; | ||||||
|  | static GapScan* gap_scan = NULL; | ||||||
| 
 | 
 | ||||||
| static void gap_advertise_start(GapState new_state); | static void gap_advertise_start(GapState new_state); | ||||||
| static int32_t gap_app(void* context); | 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; |     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) { |     switch (event_pckt->evt) { | ||||||
|         case EVT_DISCONN_COMPLETE: |         case EVT_DISCONN_COMPLETE: | ||||||
|         { |         { | ||||||
| @ -77,7 +85,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | |||||||
|                 gap_advertise_start(GapStateAdvFast); |                 gap_advertise_start(GapStateAdvFast); | ||||||
|                 furi_hal_power_insomnia_exit(); |                 furi_hal_power_insomnia_exit(); | ||||||
|             } |             } | ||||||
|             BleEvent event = {.type = BleEventTypeDisconnected}; |             GapEvent event = {.type = GapEventTypeDisconnected}; | ||||||
|             gap->on_event_cb(event, gap->context); |             gap->on_event_cb(event, gap->context); | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
| @ -120,6 +128,23 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | |||||||
|                 aci_gap_slave_security_req(connection_complete_event->Connection_Handle); |                 aci_gap_slave_security_req(connection_complete_event->Connection_Handle); | ||||||
|                 break; |                 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: |                 default: | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| @ -140,7 +165,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | |||||||
|                 uint32_t pin = rand() % 999999; |                 uint32_t pin = rand() % 999999; | ||||||
|                 aci_gap_pass_key_resp(gap->service.connection_handle, pin); |                 aci_gap_pass_key_resp(gap->service.connection_handle, pin); | ||||||
|                 FURI_LOG_I(TAG, "Pass key request event. Pin: %06d", 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); |                 gap->on_event_cb(event, gap->context); | ||||||
|             } |             } | ||||||
|                 break; |                 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; |                 aci_att_exchange_mtu_resp_event_rp0 *pr = (void*)blue_evt->data; | ||||||
|                 FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU); |                 FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU); | ||||||
|                 // Set maximum packet size given header size is 3 bytes
 |                 // 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); |                 gap->on_event_cb(event, gap->context); | ||||||
|             } |             } | ||||||
|                 break; |                 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; |                 uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value; | ||||||
|                 FURI_LOG_I(TAG, "Verify numeric comparison: %06d", pin); |                 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); |                 bool result = gap->on_event_cb(event, gap->context); | ||||||
|                 aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); |                 aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result); | ||||||
|                 break; |                 break; | ||||||
| @ -197,7 +222,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | |||||||
|                     aci_gap_terminate(gap->service.connection_handle, 5); |                     aci_gap_terminate(gap->service.connection_handle, 5); | ||||||
|                 } else { |                 } else { | ||||||
|                     FURI_LOG_I(TAG, "Pairing complete"); |                     FURI_LOG_I(TAG, "Pairing complete"); | ||||||
|                     BleEvent event = {.type = BleEventTypeConnected}; |                     GapEvent event = {.type = GapEventTypeConnected}; | ||||||
|                     gap->on_event_cb(event, gap->context); |                     gap->on_event_cb(event, gap->context); | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
| @ -209,7 +234,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt ) | |||||||
|             default: |             default: | ||||||
|                 break; |                 break; | ||||||
|     } |     } | ||||||
|     osMutexRelease(gap->state_mutex); |     if(gap) { | ||||||
|  |         osMutexRelease(gap->state_mutex); | ||||||
|  |     } | ||||||
|     return SVCCTL_UserEvtFlowEnable; |     return SVCCTL_UserEvtFlowEnable; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -322,7 +349,7 @@ static void gap_advertise_start(GapState new_state) | |||||||
|         FURI_LOG_E(TAG, "Set discoverable err: %d", status); |         FURI_LOG_E(TAG, "Set discoverable err: %d", status); | ||||||
|     } |     } | ||||||
|     gap->state = new_state; |     gap->state = new_state; | ||||||
|     BleEvent event = {.type = BleEventTypeStartAdvertising}; |     GapEvent event = {.type = GapEventTypeStartAdvertising}; | ||||||
|     gap->on_event_cb(event, gap->context); |     gap->on_event_cb(event, gap->context); | ||||||
|     osTimerStart(gap->advertise_timer, INITIAL_ADV_TIMEOUT); |     osTimerStart(gap->advertise_timer, INITIAL_ADV_TIMEOUT); | ||||||
| } | } | ||||||
| @ -338,7 +365,7 @@ static void gap_advertise_stop() { | |||||||
|         aci_gap_set_non_discoverable(); |         aci_gap_set_non_discoverable(); | ||||||
|         gap->state = GapStateIdle; |         gap->state = GapStateIdle; | ||||||
|     } |     } | ||||||
|     BleEvent event = {.type = BleEventTypeStopAdvertising}; |     GapEvent event = {.type = GapEventTypeStopAdvertising}; | ||||||
|     gap->on_event_cb(event, gap->context); |     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); |     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()) { |     if (!ble_glue_is_radio_stack_ready()) { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @ -416,12 +443,33 @@ bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) { | |||||||
| 
 | 
 | ||||||
| GapState gap_get_state() { | GapState gap_get_state() { | ||||||
|     GapState state; |     GapState state; | ||||||
|     osMutexAcquire(gap->state_mutex, osWaitForever); |     if(gap) { | ||||||
|     state = gap->state; |         osMutexAcquire(gap->state_mutex, osWaitForever); | ||||||
|     osMutexRelease(gap->state_mutex ); |         state = gap->state; | ||||||
|  |         osMutexRelease(gap->state_mutex ); | ||||||
|  |     } else { | ||||||
|  |         state = GapStateUninitialized; | ||||||
|  |     } | ||||||
|     return state; |     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() { | void gap_thread_stop() { | ||||||
|     if(gap) { |     if(gap) { | ||||||
|         osMutexAcquire(gap->state_mutex, osWaitForever); |         osMutexAcquire(gap->state_mutex, osWaitForever); | ||||||
|  | |||||||
| @ -12,28 +12,36 @@ extern "C" { | |||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     BleEventTypeConnected, |     GapEventTypeConnected, | ||||||
|     BleEventTypeDisconnected, |     GapEventTypeDisconnected, | ||||||
|     BleEventTypeStartAdvertising, |     GapEventTypeStartAdvertising, | ||||||
|     BleEventTypeStopAdvertising, |     GapEventTypeStopAdvertising, | ||||||
|     BleEventTypePinCodeShow, |     GapEventTypePinCodeShow, | ||||||
|     BleEventTypePinCodeVerify, |     GapEventTypePinCodeVerify, | ||||||
|     BleEventTypeUpdateMTU, |     GapEventTypeUpdateMTU, | ||||||
| } BleEventType; | } GapEventType; | ||||||
| 
 | 
 | ||||||
| typedef union { | typedef union { | ||||||
|     uint32_t pin_code; |     uint32_t pin_code; | ||||||
|     uint16_t max_packet_size; |     uint16_t max_packet_size; | ||||||
| } BleEventData; | } GapEventData; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     BleEventType type; |     GapEventType type; | ||||||
|     BleEventData data; |     GapEventData data; | ||||||
| } BleEvent; | } 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 { | typedef enum { | ||||||
|  |     GapStateUninitialized, | ||||||
|     GapStateIdle, |     GapStateIdle, | ||||||
|     GapStateStartingAdv, |     GapStateStartingAdv, | ||||||
|     GapStateAdvFast, |     GapStateAdvFast, | ||||||
| @ -42,6 +50,7 @@ typedef enum { | |||||||
| } GapState; | } GapState; | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|  |     GapPairingNone, | ||||||
|     GapPairingPinCodeShow, |     GapPairingPinCodeShow, | ||||||
|     GapPairingPinCodeVerifyYesNo, |     GapPairingPinCodeVerifyYesNo, | ||||||
| } GapPairing; | } GapPairing; | ||||||
| @ -55,7 +64,7 @@ typedef struct { | |||||||
|     char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; |     char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH]; | ||||||
| } GapConfig; | } 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(); | void gap_start_advertising(); | ||||||
| 
 | 
 | ||||||
| @ -65,6 +74,10 @@ GapState gap_get_state(); | |||||||
| 
 | 
 | ||||||
| void gap_thread_stop(); | void gap_thread_stop(); | ||||||
| 
 | 
 | ||||||
|  | void gap_start_scan(GapScanCallback callback, void* context); | ||||||
|  | 
 | ||||||
|  | void gap_stop_scan(); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ | |||||||
| #define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72} | #define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72} | ||||||
| 
 | 
 | ||||||
| osMutexId_t furi_hal_bt_core2_mtx = NULL; | osMutexId_t furi_hal_bt_core2_mtx = NULL; | ||||||
|  | static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown; | ||||||
| 
 | 
 | ||||||
| typedef void (*FuriHalBtProfileStart)(void); | typedef void (*FuriHalBtProfileStart)(void); | ||||||
| typedef void (*FuriHalBtProfileStop)(void); | typedef void (*FuriHalBtProfileStop)(void); | ||||||
| @ -50,7 +51,7 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { | |||||||
|             .pairing_method = GapPairingPinCodeVerifyYesNo, |             .pairing_method = GapPairingPinCodeVerifyYesNo, | ||||||
|             .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, |             .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, | ||||||
|         }, |         }, | ||||||
|     } |     }, | ||||||
| }; | }; | ||||||
| FuriHalBtProfileConfig* current_profile = NULL; | FuriHalBtProfileConfig* current_profile = NULL; | ||||||
| 
 | 
 | ||||||
| @ -79,32 +80,81 @@ void furi_hal_bt_unlock_core2() { | |||||||
|     furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); |     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); |     furi_assert(furi_hal_bt_core2_mtx); | ||||||
| 
 | 
 | ||||||
|     osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); |     osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); | ||||||
|  | 
 | ||||||
|     // Explicitly tell that we are in charge of CLK48 domain
 |     // Explicitly tell that we are in charge of CLK48 domain
 | ||||||
|     if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { |     if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { | ||||||
|         HAL_HSEM_FastTake(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 { |     do { | ||||||
|         // Start 2nd core
 |         // Wait until FUS is started or timeout
 | ||||||
|         ret = furi_hal_bt_start_core2(); |         WirelessFwInfo_t info = {}; | ||||||
|         if(!ret) { |         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(); |             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; |             break; | ||||||
|         } |         } | ||||||
|         // Set mac address
 |         // Set mac address
 | ||||||
| @ -130,21 +180,23 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, | |||||||
|             const char* clicker_str = "Keynote"; |             const char* clicker_str = "Keynote"; | ||||||
|             memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); |             memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); | ||||||
|         } |         } | ||||||
|         ret = gap_init(config, event_cb, context); |         if(!gap_init(config, event_cb, context)) { | ||||||
|         if(!ret) { |  | ||||||
|             gap_thread_stop(); |             gap_thread_stop(); | ||||||
|             FURI_LOG_E(TAG, "Failed to init GAP"); |             FURI_LOG_E(TAG, "Failed to init GAP"); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         // Start selected profile services
 |         // Start selected profile services
 | ||||||
|         profile_config[profile].start(); |         if(furi_hal_bt_stack == FuriHalBtStackLight) { | ||||||
|  |             profile_config[profile].start(); | ||||||
|  |         } | ||||||
|  |         ret = true; | ||||||
|     } while(false); |     } while(false); | ||||||
|     current_profile = &profile_config[profile]; |     current_profile = &profile_config[profile]; | ||||||
| 
 | 
 | ||||||
|     return ret; |     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(event_cb); | ||||||
|     furi_assert(profile < FuriHalBtProfileNumber); |     furi_assert(profile < FuriHalBtProfileNumber); | ||||||
|     bool ret = true; |     bool ret = true; | ||||||
| @ -164,6 +216,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, | |||||||
|     ble_glue_thread_stop(); |     ble_glue_thread_stop(); | ||||||
|     FURI_LOG_I(TAG, "Start BT initialization"); |     FURI_LOG_I(TAG, "Start BT initialization"); | ||||||
|     furi_hal_bt_init(); |     furi_hal_bt_init(); | ||||||
|  |     furi_hal_bt_start_radio_stack(); | ||||||
|     ret = furi_hal_bt_start_app(profile, event_cb, context); |     ret = furi_hal_bt_start_app(profile, event_cb, context); | ||||||
|     if(ret) { |     if(ret) { | ||||||
|         current_profile = &profile_config[profile]; |         current_profile = &profile_config[profile]; | ||||||
| @ -171,6 +224,10 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, | |||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static bool furi_hal_bt_is_active() { | ||||||
|  |     return gap_get_state() > GapStateIdle; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void furi_hal_bt_start_advertising() { | void furi_hal_bt_start_advertising() { | ||||||
|     if(gap_get_state() == GapStateIdle) { |     if(gap_get_state() == GapStateIdle) { | ||||||
|         gap_start_advertising(); |         gap_start_advertising(); | ||||||
| @ -236,10 +293,6 @@ bool furi_hal_bt_is_alive() { | |||||||
|     return ble_glue_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) { | void furi_hal_bt_start_tone_tx(uint8_t channel, uint8_t power) { | ||||||
|     aci_hal_set_tx_power_level(0, power); |     aci_hal_set_tx_power_level(0, power); | ||||||
|     aci_hal_tone_start(channel, 0); |     aci_hal_tone_start(channel, 0); | ||||||
| @ -300,3 +353,17 @@ uint32_t furi_hal_bt_get_transmitted_packets() { | |||||||
| void furi_hal_bt_stop_rx() { | void furi_hal_bt_stop_rx() { | ||||||
|     aci_hal_rx_stop(); |     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" | #include "furi-hal-bt-serial.h" | ||||||
| 
 | 
 | ||||||
|  | #define FURI_HAL_BT_STACK_VERSION_MAJOR (1) | ||||||
|  | #define FURI_HAL_BT_STACK_VERSION_MINOR (13) | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | typedef enum { | ||||||
|  |     FuriHalBtStackUnknown, | ||||||
|  |     FuriHalBtStackHciLayer, | ||||||
|  |     FuriHalBtStackLight, | ||||||
|  | } FuriHalBtStack; | ||||||
|  | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     FuriHalBtProfileSerial, |     FuriHalBtProfileSerial, | ||||||
|     FuriHalBtProfileHidKeyboard, |     FuriHalBtProfileHidKeyboard, | ||||||
| @ -36,26 +45,38 @@ void furi_hal_bt_lock_core2(); | |||||||
| /** Lock core2 state transition */ | /** Lock core2 state transition */ | ||||||
| void furi_hal_bt_unlock_core2(); | 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
 | /** Start BLE app
 | ||||||
|  * |  * | ||||||
|  * @param profile   FuriHalBtProfile instance |  * @param profile   FuriHalBtProfile instance | ||||||
|  * @param event_cb  BleEventCallback instance |  * @param event_cb  GapEventCallback instance | ||||||
|  * @param context   pointer to context |  * @param context   pointer to context | ||||||
|  * |  * | ||||||
|  * @return          true on success |  * @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
 | /** Change BLE app
 | ||||||
|  * Restarts 2nd core |  * Restarts 2nd core | ||||||
|  * |  * | ||||||
|  * @param profile   FuriHalBtProfile instance |  * @param profile   FuriHalBtProfile instance | ||||||
|  * @param event_cb  BleEventCallback instance |  * @param event_cb  GapEventCallback instance | ||||||
|  * @param context   pointer to context |  * @param context   pointer to context | ||||||
|  * |  * | ||||||
|  * @return          true on success |  * @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
 | /** Update battery level
 | ||||||
|  * |  * | ||||||
| @ -71,12 +92,6 @@ void furi_hal_bt_start_advertising(); | |||||||
|  */ |  */ | ||||||
| void furi_hal_bt_stop_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
 | /** Get BT/BLE system component state
 | ||||||
|  * |  * | ||||||
|  * @param[in]  buffer  string_t buffer to write to |  * @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(); | 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 | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 gornekich
						gornekich