[FL-1976] BLE HID (#852)
* ble: prototype ble hid * ble: add HID service and characteristics * debug tools: add ble keyboard app * ble: change appearance * ble: working keyboard * bt: introduce furi-hal-bt-hid * bt: restart hid service on each keyboard app enter * bt: introduce switch profile * bt: add profile to ble glue * bt: working profile switch * bt: introduce bt serial profile, rework API * bt: rewotk HID profile * bt: rework gap with profile configuration * bt: move change profile routine to furi hal bt * bt: change switch profile API to blocking * bt: move battery update to furi hal bt * bt: cleanup * bt: add support for f6 target * bt: update documentation * bt: clean up code * bt: remove NO OUTPUT setting * bt: set numeric comparison pairing in BLE HID * bt: support f6 target * bt: set mac address in profile configuration * bt: set advertise name in profile config * bt: rework with furi thread * bt: support f6 target * bt: clear hci command buffer on core2 restart * bt: correct thread kill sequence * bt: fix freertos functions calls * bt: add some enterprise delays fo correct memory free * bt: code cleanup * bt: change terminate -> stop * bt: fix memory leakage Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							
								
								
									
										141
									
								
								firmware/targets/f6/furi-hal/furi-hal-bt-hid.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								firmware/targets/f6/furi-hal/furi-hal-bt-hid.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| #include "furi-hal-bt-hid.h" | ||||
| #include "dev_info_service.h" | ||||
| #include "battery_service.h" | ||||
| #include "hid_service.h" | ||||
|  | ||||
| #include <furi.h> | ||||
|  | ||||
| #define FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION (0x0101) | ||||
| #define FURI_HAL_BT_INFO_COUNTRY_CODE (0x00) | ||||
| #define FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) | ||||
| #define FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) | ||||
|  | ||||
| #define FURI_HAL_BT_HID_KB_KEYS_MAX (6) | ||||
|  | ||||
| typedef struct { | ||||
|     uint8_t mods; | ||||
|     uint8_t reserved; | ||||
|     uint8_t key[FURI_HAL_BT_HID_KB_KEYS_MAX]; | ||||
| } FuriHalBtHidKbReport; | ||||
|  | ||||
| // TODO rework with HID defines | ||||
| static uint8_t furi_hal_bt_hid_report_map_data[] = { | ||||
|     0x05, 0x01,       // Usage Page (Generic Desktop) | ||||
|     0x09, 0x06,       // Usage (Keyboard) | ||||
|     0xA1, 0x01,       // Collection (Application) | ||||
|     0x05, 0x07,       // Usage Page (Key Codes) | ||||
|     0x19, 0xe0,       // Usage Minimum (224) | ||||
|     0x29, 0xe7,       // Usage Maximum (231) | ||||
|     0x15, 0x00,       // Logical Minimum (0) | ||||
|     0x25, 0x01,       // Logical Maximum (1) | ||||
|     0x75, 0x01,       // Report Size (1) | ||||
|     0x95, 0x08,       // Report Count (8) | ||||
|     0x81, 0x02,       // Input (Data, Variable, Absolute) | ||||
|  | ||||
|     0x95, 0x01,       // Report Count (1) | ||||
|     0x75, 0x08,       // Report Size (8) | ||||
|     0x81, 0x01,       // Input (Constant) reserved byte(1) | ||||
|  | ||||
|     0x95, 0x05,       // Report Count (5) | ||||
|     0x75, 0x01,       // Report Size (1) | ||||
|     0x05, 0x08,       // Usage Page (Page# for LEDs) | ||||
|     0x19, 0x01,       // Usage Minimum (1) | ||||
|     0x29, 0x05,       // Usage Maximum (5) | ||||
|     0x91, 0x02,       // Output (Data, Variable, Absolute), Led report | ||||
|     0x95, 0x01,       // Report Count (1) | ||||
|     0x75, 0x03,       // Report Size (3) | ||||
|     0x91, 0x01,       // Output (Data, Variable, Absolute), Led report padding | ||||
|  | ||||
|     0x95, 0x06,       // Report Count (6) | ||||
|     0x75, 0x08,       // Report Size (8) | ||||
|     0x15, 0x00,       // Logical Minimum (0) | ||||
|     0x25, 0x65,       // Logical Maximum (101) | ||||
|     0x05, 0x07,       // Usage Page (Key codes) | ||||
|     0x19, 0x00,       // Usage Minimum (0) | ||||
|     0x29, 0x65,       // Usage Maximum (101) | ||||
|     0x81, 0x00,       // Input (Data, Array) Key array(6 bytes) | ||||
|  | ||||
|     0x09, 0x05,       // Usage (Vendor Defined) | ||||
|     0x15, 0x00,       // Logical Minimum (0) | ||||
|     0x26, 0xFF, 0x00, // Logical Maximum (255) | ||||
|     0x75, 0x08,       // Report Size (8 bit) | ||||
|     0x95, 0x02,       // Report Count (2) | ||||
|     0xB1, 0x02,       // Feature (Data, Variable, Absolute) | ||||
|  | ||||
|     0xC0              // End Collection (Application) | ||||
| }; | ||||
|  | ||||
| FuriHalBtHidKbReport* kb_report = NULL; | ||||
|  | ||||
| void furi_hal_bt_hid_start() { | ||||
|     // Start device info | ||||
|     if(!dev_info_svc_is_started()) { | ||||
|         dev_info_svc_start(); | ||||
|     } | ||||
|     // Start battery service | ||||
|     if(!battery_svc_is_started()) { | ||||
|         battery_svc_start(); | ||||
|     } | ||||
|     // Start HID service | ||||
|     if(!hid_svc_is_started()) { | ||||
|         hid_svc_start(); | ||||
|     } | ||||
|     // Configure HID Keyboard | ||||
|     kb_report = furi_alloc(sizeof(FuriHalBtHidKbReport)); | ||||
|     // Configure Report Map characteristic | ||||
|     hid_svc_update_report_map(furi_hal_bt_hid_report_map_data, sizeof(furi_hal_bt_hid_report_map_data)); | ||||
|     // Configure HID Information characteristic | ||||
|     uint8_t hid_info_val[4] = { | ||||
|         FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION & 0x00ff, | ||||
|         (FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8, | ||||
|         FURI_HAL_BT_INFO_COUNTRY_CODE, | ||||
|         FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK | FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK, | ||||
|     }; | ||||
|     hid_svc_update_info(hid_info_val, sizeof(hid_info_val)); | ||||
| } | ||||
|  | ||||
| void furi_hal_bt_hid_stop() { | ||||
|     furi_assert(kb_report); | ||||
|     // Stop all services | ||||
|     if(dev_info_svc_is_started()) { | ||||
|         dev_info_svc_stop(); | ||||
|     } | ||||
|     if(battery_svc_is_started()) { | ||||
|         battery_svc_stop(); | ||||
|     } | ||||
|     if(hid_svc_is_started()) { | ||||
|         hid_svc_stop(); | ||||
|     } | ||||
|     free(kb_report); | ||||
|     kb_report = NULL; | ||||
| } | ||||
|  | ||||
| bool furi_hal_bt_hid_kb_press(uint16_t button) { | ||||
|     furi_assert(kb_report); | ||||
|     for (uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { | ||||
|         if (kb_report->key[i] == 0) { | ||||
|             kb_report->key[i] = button & 0xFF; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     kb_report->mods |= (button >> 8); | ||||
|     return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); | ||||
| } | ||||
|  | ||||
| bool furi_hal_bt_hid_kb_release(uint16_t button) { | ||||
|     furi_assert(kb_report); | ||||
|     for (uint8_t i = 0; i < FURI_HAL_BT_HID_KB_KEYS_MAX; i++) { | ||||
|         if (kb_report->key[i] == (button & 0xFF)) { | ||||
|             kb_report->key[i] = 0; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     kb_report->mods &= ~(button >> 8); | ||||
|     return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); | ||||
| } | ||||
|  | ||||
| bool furi_hal_bt_hid_kb_release_all() { | ||||
|     furi_assert(kb_report); | ||||
|     memset(kb_report, 0, sizeof(FuriHalBtHidKbReport)); | ||||
|     return hid_svc_update_input_report((uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); | ||||
| } | ||||
							
								
								
									
										51
									
								
								firmware/targets/f6/furi-hal/furi-hal-bt-serial.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								firmware/targets/f6/furi-hal/furi-hal-bt-serial.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| #include "furi-hal-bt-serial.h" | ||||
| #include "dev_info_service.h" | ||||
| #include "battery_service.h" | ||||
| #include "serial_service.h" | ||||
|  | ||||
| #include <furi.h> | ||||
|  | ||||
| void furi_hal_bt_serial_start() { | ||||
|     // Start device info | ||||
|     if(!dev_info_svc_is_started()) { | ||||
|         dev_info_svc_start(); | ||||
|     } | ||||
|     // Start battery service | ||||
|     if(!battery_svc_is_started()) { | ||||
|         battery_svc_start(); | ||||
|     } | ||||
|     // Start Serial service | ||||
|     if(!serial_svc_is_started()) { | ||||
|         serial_svc_start(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void furi_hal_bt_serial_set_event_callback(uint16_t buff_size, FuriHalBtSerialCallback callback, void* context) { | ||||
|     serial_svc_set_callbacks(buff_size, callback, context); | ||||
| } | ||||
|  | ||||
| void furi_hal_bt_serial_notify_buffer_is_empty() { | ||||
|     serial_svc_notify_buffer_is_empty(); | ||||
| } | ||||
|  | ||||
| bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size) { | ||||
|     if(size > FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX) { | ||||
|         return false; | ||||
|     } | ||||
|     return serial_svc_update_tx(data, size); | ||||
| } | ||||
|  | ||||
| void furi_hal_bt_serial_stop() { | ||||
|     // Stop all services | ||||
|     if(dev_info_svc_is_started()) { | ||||
|         dev_info_svc_stop(); | ||||
|     } | ||||
|     // Start battery service | ||||
|     if(battery_svc_is_started()) { | ||||
|         battery_svc_stop(); | ||||
|     } | ||||
|     // Start Serial service | ||||
|     if(serial_svc_is_started()) { | ||||
|         serial_svc_stop(); | ||||
|     } | ||||
| } | ||||
| @@ -4,18 +4,66 @@ | ||||
| #include <shci.h> | ||||
| #include <cmsis_os2.h> | ||||
|  | ||||
| #include <furi-hal-version.h> | ||||
| #include <furi-hal-bt-hid.h> | ||||
| #include <furi-hal-bt-serial.h> | ||||
| #include "battery_service.h" | ||||
|  | ||||
| #include <furi.h> | ||||
|  | ||||
| #define TAG "FuriHalBt" | ||||
|  | ||||
| #define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72} | ||||
|  | ||||
| osMutexId_t furi_hal_bt_core2_mtx = NULL; | ||||
|  | ||||
| typedef void (*FuriHalBtProfileStart)(void); | ||||
| typedef void (*FuriHalBtProfileStop)(void); | ||||
|  | ||||
| typedef struct { | ||||
|     FuriHalBtProfileStart start; | ||||
|     FuriHalBtProfileStart stop; | ||||
|     GapConfig config; | ||||
|     uint16_t appearance_char; | ||||
|     uint16_t advertise_service_uuid; | ||||
| } FuriHalBtProfileConfig; | ||||
|  | ||||
| FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { | ||||
|     [FuriHalBtProfileSerial] = { | ||||
|         .start = furi_hal_bt_serial_start, | ||||
|         .stop = furi_hal_bt_serial_stop, | ||||
|         .config = { | ||||
|             .adv_service_uuid = 0x3080, | ||||
|             .appearance_char = 0x8600, | ||||
|             .bonding_mode = true, | ||||
|             .pairing_method = GapPairingPinCodeShow, | ||||
|             .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, | ||||
|         }, | ||||
|     }, | ||||
|     [FuriHalBtProfileHidKeyboard] = { | ||||
|         .start = furi_hal_bt_hid_start, | ||||
|         .stop = furi_hal_bt_hid_stop, | ||||
|         .config = { | ||||
|             .adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, | ||||
|             .appearance_char = GAP_APPEARANCE_KEYBOARD, | ||||
|             .bonding_mode = true, | ||||
|             .pairing_method = GapPairingPinCodeVerifyYesNo, | ||||
|             .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, | ||||
|         }, | ||||
|     } | ||||
| }; | ||||
| FuriHalBtProfileConfig* current_profile = NULL; | ||||
|  | ||||
| void furi_hal_bt_init() { | ||||
|     furi_hal_bt_core2_mtx = osMutexNew(NULL); | ||||
|     furi_assert(furi_hal_bt_core2_mtx); | ||||
|     if(!furi_hal_bt_core2_mtx) { | ||||
|         furi_hal_bt_core2_mtx = osMutexNew(NULL); | ||||
|         furi_assert(furi_hal_bt_core2_mtx); | ||||
|     } | ||||
|  | ||||
|     // Explicitly tell that we are in charge of CLK48 domain | ||||
|     HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); | ||||
|     if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { | ||||
|         HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); | ||||
|     } | ||||
|  | ||||
|     // Start Core2 | ||||
|     ble_glue_init(); | ||||
| @@ -31,12 +79,14 @@ void furi_hal_bt_unlock_core2() { | ||||
|     furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK); | ||||
| } | ||||
|  | ||||
| bool furi_hal_bt_start_core2() { | ||||
| static bool furi_hal_bt_start_core2() { | ||||
|     furi_assert(furi_hal_bt_core2_mtx); | ||||
|  | ||||
|     osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever); | ||||
|     // Explicitly tell that we are in charge of CLK48 domain | ||||
|     HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); | ||||
|     if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) { | ||||
|         HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID); | ||||
|     } | ||||
|     // Start Core2 | ||||
|     bool ret = ble_glue_start(); | ||||
|     osMutexRelease(furi_hal_bt_core2_mtx); | ||||
| @@ -44,9 +94,80 @@ bool furi_hal_bt_start_core2() { | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| bool furi_hal_bt_init_app(BleEventCallback event_cb, void* context) { | ||||
| bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) { | ||||
|     furi_assert(event_cb); | ||||
|     return gap_init(event_cb, context); | ||||
|     furi_assert(profile < FuriHalBtProfileNumber); | ||||
|     bool ret = true; | ||||
|  | ||||
|     do { | ||||
|         // Start 2nd core | ||||
|         ret = furi_hal_bt_start_core2(); | ||||
|         if(!ret) { | ||||
|             ble_app_thread_stop(); | ||||
|             FURI_LOG_E(TAG, "Failed to start 2nd core"); | ||||
|             break; | ||||
|         } | ||||
|         // Set mac address | ||||
|         memcpy( | ||||
|             profile_config[profile].config.mac_address, | ||||
|             furi_hal_version_get_ble_mac(), | ||||
|             sizeof(profile_config[profile].config.mac_address) | ||||
|         ); | ||||
|         // Set advertise name | ||||
|         strlcpy( | ||||
|             profile_config[profile].config.adv_name, | ||||
|             furi_hal_version_get_ble_local_device_name_ptr(), | ||||
|             FURI_HAL_VERSION_DEVICE_NAME_LENGTH | ||||
|         ); | ||||
|         // Configure GAP | ||||
|         GapConfig* config = &profile_config[profile].config; | ||||
|         if(profile == FuriHalBtProfileSerial) { | ||||
|             config->adv_service_uuid |= furi_hal_version_get_hw_color(); | ||||
|         } else if(profile == FuriHalBtProfileHidKeyboard) { | ||||
|             // Change MAC address for HID profile | ||||
|             config->mac_address[2]++; | ||||
|             // Change name Flipper -> Clicker | ||||
|             const char* clicker_str = "Clicker"; | ||||
|             memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str) - 1); | ||||
|         } | ||||
|         ret = gap_init(config, event_cb, context); | ||||
|         if(!ret) { | ||||
|             gap_thread_stop(); | ||||
|             FURI_LOG_E(TAG, "Failed to init GAP"); | ||||
|             break; | ||||
|         } | ||||
|         // Start selected profile services | ||||
|         profile_config[profile].start(); | ||||
|     } while(false); | ||||
|     current_profile = &profile_config[profile]; | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) { | ||||
|     furi_assert(event_cb); | ||||
|     furi_assert(profile < FuriHalBtProfileNumber); | ||||
|     bool ret = true; | ||||
|  | ||||
|     FURI_LOG_I(TAG, "Stop current profile services"); | ||||
|     current_profile->stop(); | ||||
|     FURI_LOG_I(TAG, "Disconnect and stop advertising"); | ||||
|     furi_hal_bt_stop_advertising(); | ||||
|     FURI_LOG_I(TAG, "Shutdow 2nd core"); | ||||
|     LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN); | ||||
|     FURI_LOG_I(TAG, "Stop BLE related RTOS threads"); | ||||
|     ble_app_thread_stop(); | ||||
|     gap_thread_stop(); | ||||
|     FURI_LOG_I(TAG, "Reset SHCI"); | ||||
|     SHCI_C2_Reinit(); | ||||
|     ble_glue_thread_stop(); | ||||
|     FURI_LOG_I(TAG, "Start BT initialization"); | ||||
|     furi_hal_bt_init(); | ||||
|     ret = furi_hal_bt_start_app(profile, event_cb, context); | ||||
|     if(ret) { | ||||
|         current_profile = &profile_config[profile]; | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| void furi_hal_bt_start_advertising() { | ||||
| @@ -64,19 +185,10 @@ void furi_hal_bt_stop_advertising() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| void furi_hal_bt_set_data_event_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) { | ||||
|     serial_svc_set_callbacks(buff_size, on_received_cb, on_sent_cb, context); | ||||
| } | ||||
|  | ||||
| void furi_hal_bt_notify_buffer_is_empty() { | ||||
|     serial_svc_notify_buffer_is_empty(); | ||||
| } | ||||
|  | ||||
| bool furi_hal_bt_tx(uint8_t* data, uint16_t size) { | ||||
|     if(size > FURI_HAL_BT_PACKET_SIZE_MAX) { | ||||
|         return false; | ||||
| void furi_hal_bt_update_battery_level(uint8_t battery_level) { | ||||
|     if(battery_svc_is_started()) { | ||||
|         battery_svc_update_level(battery_level); | ||||
|     } | ||||
|     return serial_svc_update_tx(data, size); | ||||
| } | ||||
|  | ||||
| void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size) { | ||||
|   | ||||
| @@ -113,9 +113,11 @@ static void furi_hal_flash_begin_with_core2(bool erase_flag) { | ||||
|         } | ||||
|  | ||||
|         // Take sempahopre and prevent core2 from anyting funky | ||||
|         if (HAL_HSEM_FastTake(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != HAL_OK) { | ||||
|             taskEXIT_CRITICAL(); | ||||
|             continue; | ||||
|         if(!HAL_HSEM_IsSemTaken(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID)) { | ||||
|             if (HAL_HSEM_FastTake(CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != HAL_OK) { | ||||
|                 taskEXIT_CRITICAL(); | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         break; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user