[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:
		@@ -16,6 +16,8 @@
 | 
			
		||||
PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer;
 | 
			
		||||
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE];
 | 
			
		||||
 | 
			
		||||
_Static_assert(sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 49, "Ble stack config structure size mismatch");
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    osMutexId_t hci_mtx;
 | 
			
		||||
    osSemaphoreId_t hci_sem;
 | 
			
		||||
 
 | 
			
		||||
@@ -106,29 +106,31 @@ void ble_glue_init() {
 | 
			
		||||
     */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool ble_glue_wait_status(BleGlueStatus status) {
 | 
			
		||||
bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info) {
 | 
			
		||||
    bool ret = false;
 | 
			
		||||
    size_t countdown = 1000;
 | 
			
		||||
    while (countdown > 0) {
 | 
			
		||||
        if (ble_glue->status == status) {
 | 
			
		||||
        if (ble_glue->status == BleGlueStatusFusStarted) {
 | 
			
		||||
            ret = true;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        countdown--;
 | 
			
		||||
        osDelay(1);
 | 
			
		||||
    }
 | 
			
		||||
    if(ble_glue->status == BleGlueStatusFusStarted) {
 | 
			
		||||
        SHCI_GetWirelessFwInfo(info);
 | 
			
		||||
    } else {
 | 
			
		||||
        FURI_LOG_E(TAG, "Failed to start FUS");
 | 
			
		||||
        ble_glue->status = BleGlueStatusBroken;
 | 
			
		||||
    }
 | 
			
		||||
    furi_hal_power_insomnia_exit();
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ble_glue_start() {
 | 
			
		||||
    furi_assert(ble_glue);
 | 
			
		||||
 | 
			
		||||
    if (!ble_glue_wait_status(BleGlueStatusFusStarted)) {
 | 
			
		||||
        // shutdown core2 power
 | 
			
		||||
        FURI_LOG_E(TAG, "Core2 catastrophic failure, cutting its power");
 | 
			
		||||
        LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
 | 
			
		||||
        ble_glue->status = BleGlueStatusBroken;
 | 
			
		||||
        furi_hal_power_insomnia_exit();
 | 
			
		||||
    if (ble_glue->status != BleGlueStatusFusStarted) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -146,6 +148,7 @@ bool ble_glue_start() {
 | 
			
		||||
    } else {
 | 
			
		||||
        FURI_LOG_E(TAG, "Radio stack startup failed");
 | 
			
		||||
        ble_glue->status = BleGlueStatusRadioStackMissing;
 | 
			
		||||
        ble_app_thread_stop();
 | 
			
		||||
    }
 | 
			
		||||
    furi_hal_power_insomnia_exit();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <shci/shci.h>
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
@@ -25,6 +26,8 @@ bool ble_glue_start();
 | 
			
		||||
 */
 | 
			
		||||
bool ble_glue_is_alive();
 | 
			
		||||
 | 
			
		||||
bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info);
 | 
			
		||||
 | 
			
		||||
/** Is core2 radio stack present and ready
 | 
			
		||||
 *
 | 
			
		||||
 * @return     true if present and ready
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ typedef struct {
 | 
			
		||||
    GapConfig* config;
 | 
			
		||||
    GapState state;
 | 
			
		||||
    osMutexId_t state_mutex;
 | 
			
		||||
    BleEventCallback on_event_cb;
 | 
			
		||||
    GapEventCallback on_event_cb;
 | 
			
		||||
    void* context;
 | 
			
		||||
    osTimerId_t advertise_timer;
 | 
			
		||||
    FuriThread* thread;
 | 
			
		||||
@@ -40,12 +40,18 @@ typedef enum {
 | 
			
		||||
    GapCommandKillThread,
 | 
			
		||||
} GapCommand;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    GapScanCallback callback;
 | 
			
		||||
    void* context;
 | 
			
		||||
} GapScan;
 | 
			
		||||
 | 
			
		||||
// Identity root key
 | 
			
		||||
static const uint8_t gap_irk[16] = {0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0,0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0};
 | 
			
		||||
// Encryption root key
 | 
			
		||||
static const uint8_t gap_erk[16] = {0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21,0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21};
 | 
			
		||||
 | 
			
		||||
static Gap* gap = NULL;
 | 
			
		||||
static GapScan* gap_scan = NULL;
 | 
			
		||||
 | 
			
		||||
static void gap_advertise_start(GapState new_state);
 | 
			
		||||
static int32_t gap_app(void* context);
 | 
			
		||||
@@ -62,7 +68,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
 | 
			
		||||
    event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data;
 | 
			
		||||
 | 
			
		||||
    osMutexAcquire(gap->state_mutex, osWaitForever);
 | 
			
		||||
    if(gap) {
 | 
			
		||||
        osMutexAcquire(gap->state_mutex, osWaitForever);
 | 
			
		||||
    }
 | 
			
		||||
    switch (event_pckt->evt) {
 | 
			
		||||
        case EVT_DISCONN_COMPLETE:
 | 
			
		||||
        {
 | 
			
		||||
@@ -77,7 +85,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
                gap_advertise_start(GapStateAdvFast);
 | 
			
		||||
                furi_hal_power_insomnia_exit();
 | 
			
		||||
            }
 | 
			
		||||
            BleEvent event = {.type = BleEventTypeDisconnected};
 | 
			
		||||
            GapEvent event = {.type = GapEventTypeDisconnected};
 | 
			
		||||
            gap->on_event_cb(event, gap->context);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
@@ -120,6 +128,23 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
                aci_gap_slave_security_req(connection_complete_event->Connection_Handle);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                case EVT_LE_ADVERTISING_REPORT: {
 | 
			
		||||
                    if(gap_scan) {
 | 
			
		||||
                        GapAddress address;
 | 
			
		||||
                        hci_le_advertising_report_event_rp0* evt = (hci_le_advertising_report_event_rp0*) meta_evt->data;
 | 
			
		||||
                        for(uint8_t i = 0; i < evt->Num_Reports; i++) {
 | 
			
		||||
                            Advertising_Report_t* rep = &evt->Advertising_Report[i];
 | 
			
		||||
                            address.type = rep->Address_Type;
 | 
			
		||||
                            // Original MAC addres is in inverted order
 | 
			
		||||
                            for(uint8_t j = 0; j < sizeof(address.mac); j++) {
 | 
			
		||||
                                address.mac[j] = rep->Address[sizeof(address.mac) - j - 1];
 | 
			
		||||
                            }
 | 
			
		||||
                            gap_scan->callback(address, gap_scan->context);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
@@ -140,7 +165,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
                uint32_t pin = rand() % 999999;
 | 
			
		||||
                aci_gap_pass_key_resp(gap->service.connection_handle, pin);
 | 
			
		||||
                FURI_LOG_I(TAG, "Pass key request event. Pin: %06d", pin);
 | 
			
		||||
                BleEvent event = {.type = BleEventTypePinCodeShow, .data.pin_code = pin};
 | 
			
		||||
                GapEvent event = {.type = GapEventTypePinCodeShow, .data.pin_code = pin};
 | 
			
		||||
                gap->on_event_cb(event, gap->context);
 | 
			
		||||
            }
 | 
			
		||||
                break;
 | 
			
		||||
@@ -150,7 +175,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
                aci_att_exchange_mtu_resp_event_rp0 *pr = (void*)blue_evt->data;
 | 
			
		||||
                FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU);
 | 
			
		||||
                // Set maximum packet size given header size is 3 bytes
 | 
			
		||||
                BleEvent event = {.type = BleEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3};
 | 
			
		||||
                GapEvent event = {.type = GapEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3};
 | 
			
		||||
                gap->on_event_cb(event, gap->context);
 | 
			
		||||
            }
 | 
			
		||||
                break;
 | 
			
		||||
@@ -184,7 +209,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
            {
 | 
			
		||||
                uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value;
 | 
			
		||||
                FURI_LOG_I(TAG, "Verify numeric comparison: %06d", pin);
 | 
			
		||||
                BleEvent event = {.type = BleEventTypePinCodeVerify, .data.pin_code = pin};
 | 
			
		||||
                GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin};
 | 
			
		||||
                bool result = gap->on_event_cb(event, gap->context);
 | 
			
		||||
                aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result);
 | 
			
		||||
                break;
 | 
			
		||||
@@ -197,7 +222,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
                    aci_gap_terminate(gap->service.connection_handle, 5);
 | 
			
		||||
                } else {
 | 
			
		||||
                    FURI_LOG_I(TAG, "Pairing complete");
 | 
			
		||||
                    BleEvent event = {.type = BleEventTypeConnected};
 | 
			
		||||
                    GapEvent event = {.type = GapEventTypeConnected};
 | 
			
		||||
                    gap->on_event_cb(event, gap->context);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
@@ -209,7 +234,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
    }
 | 
			
		||||
    osMutexRelease(gap->state_mutex);
 | 
			
		||||
    if(gap) {
 | 
			
		||||
        osMutexRelease(gap->state_mutex);
 | 
			
		||||
    }
 | 
			
		||||
    return SVCCTL_UserEvtFlowEnable;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -322,7 +349,7 @@ static void gap_advertise_start(GapState new_state)
 | 
			
		||||
        FURI_LOG_E(TAG, "Set discoverable err: %d", status);
 | 
			
		||||
    }
 | 
			
		||||
    gap->state = new_state;
 | 
			
		||||
    BleEvent event = {.type = BleEventTypeStartAdvertising};
 | 
			
		||||
    GapEvent event = {.type = GapEventTypeStartAdvertising};
 | 
			
		||||
    gap->on_event_cb(event, gap->context);
 | 
			
		||||
    osTimerStart(gap->advertise_timer, INITIAL_ADV_TIMEOUT);
 | 
			
		||||
}
 | 
			
		||||
@@ -338,7 +365,7 @@ static void gap_advertise_stop() {
 | 
			
		||||
        aci_gap_set_non_discoverable();
 | 
			
		||||
        gap->state = GapStateIdle;
 | 
			
		||||
    }
 | 
			
		||||
    BleEvent event = {.type = BleEventTypeStopAdvertising};
 | 
			
		||||
    GapEvent event = {.type = GapEventTypeStopAdvertising};
 | 
			
		||||
    gap->on_event_cb(event, gap->context);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -370,7 +397,7 @@ static void gap_advetise_timer_callback(void* context) {
 | 
			
		||||
    furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) {
 | 
			
		||||
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
 | 
			
		||||
    if (!ble_glue_is_radio_stack_ready()) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
@@ -416,12 +443,33 @@ bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) {
 | 
			
		||||
 | 
			
		||||
GapState gap_get_state() {
 | 
			
		||||
    GapState state;
 | 
			
		||||
    osMutexAcquire(gap->state_mutex, osWaitForever);
 | 
			
		||||
    state = gap->state;
 | 
			
		||||
    osMutexRelease(gap->state_mutex );
 | 
			
		||||
    if(gap) {
 | 
			
		||||
        osMutexAcquire(gap->state_mutex, osWaitForever);
 | 
			
		||||
        state = gap->state;
 | 
			
		||||
        osMutexRelease(gap->state_mutex );
 | 
			
		||||
    } else {
 | 
			
		||||
        state = GapStateUninitialized;
 | 
			
		||||
    }
 | 
			
		||||
    return state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void gap_start_scan(GapScanCallback callback, void* context) {
 | 
			
		||||
    furi_assert(callback);
 | 
			
		||||
    gap_scan = furi_alloc(sizeof(GapScan));
 | 
			
		||||
    gap_scan->callback = callback;
 | 
			
		||||
    gap_scan->context = context;
 | 
			
		||||
    // Scan interval 250 ms
 | 
			
		||||
    hci_le_set_scan_parameters(1, 4000, 200, 0, 0);
 | 
			
		||||
    hci_le_set_scan_enable(1, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void gap_stop_scan() {
 | 
			
		||||
    furi_assert(gap_scan);
 | 
			
		||||
    hci_le_set_scan_enable(0, 1);
 | 
			
		||||
    free(gap_scan);
 | 
			
		||||
    gap_scan = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void gap_thread_stop() {
 | 
			
		||||
    if(gap) {
 | 
			
		||||
        osMutexAcquire(gap->state_mutex, osWaitForever);
 | 
			
		||||
 
 | 
			
		||||
@@ -12,28 +12,36 @@ extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    BleEventTypeConnected,
 | 
			
		||||
    BleEventTypeDisconnected,
 | 
			
		||||
    BleEventTypeStartAdvertising,
 | 
			
		||||
    BleEventTypeStopAdvertising,
 | 
			
		||||
    BleEventTypePinCodeShow,
 | 
			
		||||
    BleEventTypePinCodeVerify,
 | 
			
		||||
    BleEventTypeUpdateMTU,
 | 
			
		||||
} BleEventType;
 | 
			
		||||
    GapEventTypeConnected,
 | 
			
		||||
    GapEventTypeDisconnected,
 | 
			
		||||
    GapEventTypeStartAdvertising,
 | 
			
		||||
    GapEventTypeStopAdvertising,
 | 
			
		||||
    GapEventTypePinCodeShow,
 | 
			
		||||
    GapEventTypePinCodeVerify,
 | 
			
		||||
    GapEventTypeUpdateMTU,
 | 
			
		||||
} GapEventType;
 | 
			
		||||
 | 
			
		||||
typedef union {
 | 
			
		||||
    uint32_t pin_code;
 | 
			
		||||
    uint16_t max_packet_size;
 | 
			
		||||
} BleEventData;
 | 
			
		||||
} GapEventData;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    BleEventType type;
 | 
			
		||||
    BleEventData data;
 | 
			
		||||
} BleEvent;
 | 
			
		||||
    GapEventType type;
 | 
			
		||||
    GapEventData data;
 | 
			
		||||
} GapEvent;
 | 
			
		||||
 | 
			
		||||
typedef bool(*BleEventCallback) (BleEvent event, void* context);
 | 
			
		||||
typedef bool(*GapEventCallback) (GapEvent event, void* context);
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint8_t type;
 | 
			
		||||
    uint8_t mac[6];
 | 
			
		||||
} GapAddress;
 | 
			
		||||
 | 
			
		||||
typedef void(*GapScanCallback) (GapAddress address, void* context);
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    GapStateUninitialized,
 | 
			
		||||
    GapStateIdle,
 | 
			
		||||
    GapStateStartingAdv,
 | 
			
		||||
    GapStateAdvFast,
 | 
			
		||||
@@ -42,6 +50,7 @@ typedef enum {
 | 
			
		||||
} GapState;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    GapPairingNone,
 | 
			
		||||
    GapPairingPinCodeShow,
 | 
			
		||||
    GapPairingPinCodeVerifyYesNo,
 | 
			
		||||
} GapPairing;
 | 
			
		||||
@@ -55,7 +64,7 @@ typedef struct {
 | 
			
		||||
    char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH];
 | 
			
		||||
} GapConfig;
 | 
			
		||||
 | 
			
		||||
bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context);
 | 
			
		||||
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context);
 | 
			
		||||
 | 
			
		||||
void gap_start_advertising();
 | 
			
		||||
 | 
			
		||||
@@ -65,6 +74,10 @@ GapState gap_get_state();
 | 
			
		||||
 | 
			
		||||
void gap_thread_stop();
 | 
			
		||||
 | 
			
		||||
void gap_start_scan(GapScanCallback callback, void* context);
 | 
			
		||||
 | 
			
		||||
void gap_stop_scan();
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@
 | 
			
		||||
#define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72}
 | 
			
		||||
 | 
			
		||||
osMutexId_t furi_hal_bt_core2_mtx = NULL;
 | 
			
		||||
static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown;
 | 
			
		||||
 | 
			
		||||
typedef void (*FuriHalBtProfileStart)(void);
 | 
			
		||||
typedef void (*FuriHalBtProfileStop)(void);
 | 
			
		||||
@@ -50,7 +51,7 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = {
 | 
			
		||||
            .pairing_method = GapPairingPinCodeVerifyYesNo,
 | 
			
		||||
            .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR,
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
FuriHalBtProfileConfig* current_profile = NULL;
 | 
			
		||||
 | 
			
		||||
@@ -79,32 +80,81 @@ void furi_hal_bt_unlock_core2() {
 | 
			
		||||
    furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool furi_hal_bt_start_core2() {
 | 
			
		||||
static bool furi_hal_bt_radio_stack_is_supported(WirelessFwInfo_t* info) {
 | 
			
		||||
    bool supported = false;
 | 
			
		||||
    if(info->StackType == INFO_STACK_TYPE_BLE_HCI) {
 | 
			
		||||
        furi_hal_bt_stack = FuriHalBtStackHciLayer;
 | 
			
		||||
        supported = true;
 | 
			
		||||
    } else if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) {
 | 
			
		||||
        if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR &&
 | 
			
		||||
           info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) {
 | 
			
		||||
            furi_hal_bt_stack = FuriHalBtStackLight;
 | 
			
		||||
            supported = true;
 | 
			
		||||
           } 
 | 
			
		||||
    } else {
 | 
			
		||||
        furi_hal_bt_stack = FuriHalBtStackUnknown;
 | 
			
		||||
    }
 | 
			
		||||
    return supported;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_start_radio_stack() {
 | 
			
		||||
    bool res = false;
 | 
			
		||||
    furi_assert(furi_hal_bt_core2_mtx);
 | 
			
		||||
 | 
			
		||||
    osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever);
 | 
			
		||||
 | 
			
		||||
    // Explicitly tell that we are in charge of CLK48 domain
 | 
			
		||||
    if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) {
 | 
			
		||||
        HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID);
 | 
			
		||||
    }
 | 
			
		||||
    // Start Core2
 | 
			
		||||
    bool ret = ble_glue_start();
 | 
			
		||||
    osMutexRelease(furi_hal_bt_core2_mtx);
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) {
 | 
			
		||||
    furi_assert(event_cb);
 | 
			
		||||
    furi_assert(profile < FuriHalBtProfileNumber);
 | 
			
		||||
    bool ret = true;
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
        // Start 2nd core
 | 
			
		||||
        ret = furi_hal_bt_start_core2();
 | 
			
		||||
        if(!ret) {
 | 
			
		||||
        // Wait until FUS is started or timeout
 | 
			
		||||
        WirelessFwInfo_t info = {};
 | 
			
		||||
        if(!ble_glue_wait_for_fus_start(&info)) {
 | 
			
		||||
            FURI_LOG_E(TAG, "FUS start failed");
 | 
			
		||||
            LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
 | 
			
		||||
            ble_glue_thread_stop();
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        // Check weather we support radio stack
 | 
			
		||||
        if(!furi_hal_bt_radio_stack_is_supported(&info)) {
 | 
			
		||||
            FURI_LOG_E(TAG, "Unsupported radio stack");
 | 
			
		||||
            LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
 | 
			
		||||
            ble_glue_thread_stop();
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        // Starting radio stack
 | 
			
		||||
        if(!ble_glue_start()) {
 | 
			
		||||
            FURI_LOG_E(TAG, "Failed to start radio stack");
 | 
			
		||||
            LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
 | 
			
		||||
            ble_glue_thread_stop();
 | 
			
		||||
            ble_app_thread_stop();
 | 
			
		||||
            FURI_LOG_E(TAG, "Failed to start 2nd core");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        res = true;
 | 
			
		||||
    } while(false);
 | 
			
		||||
    osMutexRelease(furi_hal_bt_core2_mtx);
 | 
			
		||||
 | 
			
		||||
    return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FuriHalBtStack furi_hal_bt_get_radio_stack() {
 | 
			
		||||
    return furi_hal_bt_stack;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) {
 | 
			
		||||
    furi_assert(event_cb);
 | 
			
		||||
    furi_assert(profile < FuriHalBtProfileNumber);
 | 
			
		||||
    bool ret = false;
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
        if(!ble_glue_is_radio_stack_ready()) {
 | 
			
		||||
            FURI_LOG_E(TAG, "Can't start BLE App - radio stack did not start");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if(furi_hal_bt_stack != FuriHalBtStackLight) {
 | 
			
		||||
            FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        // Set mac address
 | 
			
		||||
@@ -130,21 +180,23 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb,
 | 
			
		||||
            const char* clicker_str = "Keynote";
 | 
			
		||||
            memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str));
 | 
			
		||||
        }
 | 
			
		||||
        ret = gap_init(config, event_cb, context);
 | 
			
		||||
        if(!ret) {
 | 
			
		||||
        if(!gap_init(config, event_cb, context)) {
 | 
			
		||||
            gap_thread_stop();
 | 
			
		||||
            FURI_LOG_E(TAG, "Failed to init GAP");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        // Start selected profile services
 | 
			
		||||
        profile_config[profile].start();
 | 
			
		||||
        if(furi_hal_bt_stack == FuriHalBtStackLight) {
 | 
			
		||||
            profile_config[profile].start();
 | 
			
		||||
        }
 | 
			
		||||
        ret = true;
 | 
			
		||||
    } while(false);
 | 
			
		||||
    current_profile = &profile_config[profile];
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) {
 | 
			
		||||
bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) {
 | 
			
		||||
    furi_assert(event_cb);
 | 
			
		||||
    furi_assert(profile < FuriHalBtProfileNumber);
 | 
			
		||||
    bool ret = true;
 | 
			
		||||
@@ -164,6 +216,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb,
 | 
			
		||||
    ble_glue_thread_stop();
 | 
			
		||||
    FURI_LOG_I(TAG, "Start BT initialization");
 | 
			
		||||
    furi_hal_bt_init();
 | 
			
		||||
    furi_hal_bt_start_radio_stack();
 | 
			
		||||
    ret = furi_hal_bt_start_app(profile, event_cb, context);
 | 
			
		||||
    if(ret) {
 | 
			
		||||
        current_profile = &profile_config[profile];
 | 
			
		||||
@@ -171,6 +224,10 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb,
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool furi_hal_bt_is_active() {
 | 
			
		||||
    return gap_get_state() > GapStateIdle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void furi_hal_bt_start_advertising() {
 | 
			
		||||
    if(gap_get_state() == GapStateIdle) {
 | 
			
		||||
        gap_start_advertising();
 | 
			
		||||
@@ -236,10 +293,6 @@ bool furi_hal_bt_is_alive() {
 | 
			
		||||
    return ble_glue_is_alive();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_is_active() {
 | 
			
		||||
    return gap_get_state() > GapStateIdle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void furi_hal_bt_start_tone_tx(uint8_t channel, uint8_t power) {
 | 
			
		||||
    aci_hal_set_tx_power_level(0, power);
 | 
			
		||||
    aci_hal_tone_start(channel, 0);
 | 
			
		||||
@@ -300,3 +353,17 @@ uint32_t furi_hal_bt_get_transmitted_packets() {
 | 
			
		||||
void furi_hal_bt_stop_rx() {
 | 
			
		||||
    aci_hal_rx_stop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_start_scan(GapScanCallback callback, void* context) {
 | 
			
		||||
    if(furi_hal_bt_stack != FuriHalBtStackHciLayer) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    gap_start_scan(callback, context);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void furi_hal_bt_stop_scan() {
 | 
			
		||||
    if(furi_hal_bt_stack == FuriHalBtStackHciLayer) {
 | 
			
		||||
        gap_stop_scan();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,8 @@
 | 
			
		||||
PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer;
 | 
			
		||||
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE];
 | 
			
		||||
 | 
			
		||||
_Static_assert(sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 49, "Ble stack config structure size mismatch");
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    osMutexId_t hci_mtx;
 | 
			
		||||
    osSemaphoreId_t hci_sem;
 | 
			
		||||
 
 | 
			
		||||
@@ -106,29 +106,31 @@ void ble_glue_init() {
 | 
			
		||||
     */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool ble_glue_wait_status(BleGlueStatus status) {
 | 
			
		||||
bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info) {
 | 
			
		||||
    bool ret = false;
 | 
			
		||||
    size_t countdown = 1000;
 | 
			
		||||
    while (countdown > 0) {
 | 
			
		||||
        if (ble_glue->status == status) {
 | 
			
		||||
        if (ble_glue->status == BleGlueStatusFusStarted) {
 | 
			
		||||
            ret = true;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        countdown--;
 | 
			
		||||
        osDelay(1);
 | 
			
		||||
    }
 | 
			
		||||
    if(ble_glue->status == BleGlueStatusFusStarted) {
 | 
			
		||||
        SHCI_GetWirelessFwInfo(info);
 | 
			
		||||
    } else {
 | 
			
		||||
        FURI_LOG_E(TAG, "Failed to start FUS");
 | 
			
		||||
        ble_glue->status = BleGlueStatusBroken;
 | 
			
		||||
    }
 | 
			
		||||
    furi_hal_power_insomnia_exit();
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ble_glue_start() {
 | 
			
		||||
    furi_assert(ble_glue);
 | 
			
		||||
 | 
			
		||||
    if (!ble_glue_wait_status(BleGlueStatusFusStarted)) {
 | 
			
		||||
        // shutdown core2 power
 | 
			
		||||
        FURI_LOG_E(TAG, "Core2 catastrophic failure, cutting its power");
 | 
			
		||||
        LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
 | 
			
		||||
        ble_glue->status = BleGlueStatusBroken;
 | 
			
		||||
        furi_hal_power_insomnia_exit();
 | 
			
		||||
    if (ble_glue->status != BleGlueStatusFusStarted) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -146,6 +148,7 @@ bool ble_glue_start() {
 | 
			
		||||
    } else {
 | 
			
		||||
        FURI_LOG_E(TAG, "Radio stack startup failed");
 | 
			
		||||
        ble_glue->status = BleGlueStatusRadioStackMissing;
 | 
			
		||||
        ble_app_thread_stop();
 | 
			
		||||
    }
 | 
			
		||||
    furi_hal_power_insomnia_exit();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <shci/shci.h>
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
@@ -25,6 +26,8 @@ bool ble_glue_start();
 | 
			
		||||
 */
 | 
			
		||||
bool ble_glue_is_alive();
 | 
			
		||||
 | 
			
		||||
bool ble_glue_wait_for_fus_start(WirelessFwInfo_t* info);
 | 
			
		||||
 | 
			
		||||
/** Is core2 radio stack present and ready
 | 
			
		||||
 *
 | 
			
		||||
 * @return     true if present and ready
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ typedef struct {
 | 
			
		||||
    GapConfig* config;
 | 
			
		||||
    GapState state;
 | 
			
		||||
    osMutexId_t state_mutex;
 | 
			
		||||
    BleEventCallback on_event_cb;
 | 
			
		||||
    GapEventCallback on_event_cb;
 | 
			
		||||
    void* context;
 | 
			
		||||
    osTimerId_t advertise_timer;
 | 
			
		||||
    FuriThread* thread;
 | 
			
		||||
@@ -40,12 +40,18 @@ typedef enum {
 | 
			
		||||
    GapCommandKillThread,
 | 
			
		||||
} GapCommand;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    GapScanCallback callback;
 | 
			
		||||
    void* context;
 | 
			
		||||
} GapScan;
 | 
			
		||||
 | 
			
		||||
// Identity root key
 | 
			
		||||
static const uint8_t gap_irk[16] = {0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0,0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0};
 | 
			
		||||
// Encryption root key
 | 
			
		||||
static const uint8_t gap_erk[16] = {0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21,0xfe,0xdc,0xba,0x09,0x87,0x65,0x43,0x21};
 | 
			
		||||
 | 
			
		||||
static Gap* gap = NULL;
 | 
			
		||||
static GapScan* gap_scan = NULL;
 | 
			
		||||
 | 
			
		||||
static void gap_advertise_start(GapState new_state);
 | 
			
		||||
static int32_t gap_app(void* context);
 | 
			
		||||
@@ -62,7 +68,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
 | 
			
		||||
    event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data;
 | 
			
		||||
 | 
			
		||||
    osMutexAcquire(gap->state_mutex, osWaitForever);
 | 
			
		||||
    if(gap) {
 | 
			
		||||
        osMutexAcquire(gap->state_mutex, osWaitForever);
 | 
			
		||||
    }
 | 
			
		||||
    switch (event_pckt->evt) {
 | 
			
		||||
        case EVT_DISCONN_COMPLETE:
 | 
			
		||||
        {
 | 
			
		||||
@@ -77,7 +85,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
                gap_advertise_start(GapStateAdvFast);
 | 
			
		||||
                furi_hal_power_insomnia_exit();
 | 
			
		||||
            }
 | 
			
		||||
            BleEvent event = {.type = BleEventTypeDisconnected};
 | 
			
		||||
            GapEvent event = {.type = GapEventTypeDisconnected};
 | 
			
		||||
            gap->on_event_cb(event, gap->context);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
@@ -120,6 +128,23 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
                aci_gap_slave_security_req(connection_complete_event->Connection_Handle);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                case EVT_LE_ADVERTISING_REPORT: {
 | 
			
		||||
                    if(gap_scan) {
 | 
			
		||||
                        GapAddress address;
 | 
			
		||||
                        hci_le_advertising_report_event_rp0* evt = (hci_le_advertising_report_event_rp0*) meta_evt->data;
 | 
			
		||||
                        for(uint8_t i = 0; i < evt->Num_Reports; i++) {
 | 
			
		||||
                            Advertising_Report_t* rep = &evt->Advertising_Report[i];
 | 
			
		||||
                            address.type = rep->Address_Type;
 | 
			
		||||
                            // Original MAC addres is in inverted order
 | 
			
		||||
                            for(uint8_t j = 0; j < sizeof(address.mac); j++) {
 | 
			
		||||
                                address.mac[j] = rep->Address[sizeof(address.mac) - j - 1];
 | 
			
		||||
                            }
 | 
			
		||||
                            gap_scan->callback(address, gap_scan->context);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
@@ -140,7 +165,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
                uint32_t pin = rand() % 999999;
 | 
			
		||||
                aci_gap_pass_key_resp(gap->service.connection_handle, pin);
 | 
			
		||||
                FURI_LOG_I(TAG, "Pass key request event. Pin: %06d", pin);
 | 
			
		||||
                BleEvent event = {.type = BleEventTypePinCodeShow, .data.pin_code = pin};
 | 
			
		||||
                GapEvent event = {.type = GapEventTypePinCodeShow, .data.pin_code = pin};
 | 
			
		||||
                gap->on_event_cb(event, gap->context);
 | 
			
		||||
            }
 | 
			
		||||
                break;
 | 
			
		||||
@@ -150,7 +175,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
                aci_att_exchange_mtu_resp_event_rp0 *pr = (void*)blue_evt->data;
 | 
			
		||||
                FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU);
 | 
			
		||||
                // Set maximum packet size given header size is 3 bytes
 | 
			
		||||
                BleEvent event = {.type = BleEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3};
 | 
			
		||||
                GapEvent event = {.type = GapEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3};
 | 
			
		||||
                gap->on_event_cb(event, gap->context);
 | 
			
		||||
            }
 | 
			
		||||
                break;
 | 
			
		||||
@@ -184,7 +209,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
            {
 | 
			
		||||
                uint32_t pin = ((aci_gap_numeric_comparison_value_event_rp0 *)(blue_evt->data))->Numeric_Value;
 | 
			
		||||
                FURI_LOG_I(TAG, "Verify numeric comparison: %06d", pin);
 | 
			
		||||
                BleEvent event = {.type = BleEventTypePinCodeVerify, .data.pin_code = pin};
 | 
			
		||||
                GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin};
 | 
			
		||||
                bool result = gap->on_event_cb(event, gap->context);
 | 
			
		||||
                aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result);
 | 
			
		||||
                break;
 | 
			
		||||
@@ -197,7 +222,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
                    aci_gap_terminate(gap->service.connection_handle, 5);
 | 
			
		||||
                } else {
 | 
			
		||||
                    FURI_LOG_I(TAG, "Pairing complete");
 | 
			
		||||
                    BleEvent event = {.type = BleEventTypeConnected};
 | 
			
		||||
                    GapEvent event = {.type = GapEventTypeConnected};
 | 
			
		||||
                    gap->on_event_cb(event, gap->context);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
@@ -209,7 +234,9 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification( void *pckt )
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
    }
 | 
			
		||||
    osMutexRelease(gap->state_mutex);
 | 
			
		||||
    if(gap) {
 | 
			
		||||
        osMutexRelease(gap->state_mutex);
 | 
			
		||||
    }
 | 
			
		||||
    return SVCCTL_UserEvtFlowEnable;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -322,7 +349,7 @@ static void gap_advertise_start(GapState new_state)
 | 
			
		||||
        FURI_LOG_E(TAG, "Set discoverable err: %d", status);
 | 
			
		||||
    }
 | 
			
		||||
    gap->state = new_state;
 | 
			
		||||
    BleEvent event = {.type = BleEventTypeStartAdvertising};
 | 
			
		||||
    GapEvent event = {.type = GapEventTypeStartAdvertising};
 | 
			
		||||
    gap->on_event_cb(event, gap->context);
 | 
			
		||||
    osTimerStart(gap->advertise_timer, INITIAL_ADV_TIMEOUT);
 | 
			
		||||
}
 | 
			
		||||
@@ -338,7 +365,7 @@ static void gap_advertise_stop() {
 | 
			
		||||
        aci_gap_set_non_discoverable();
 | 
			
		||||
        gap->state = GapStateIdle;
 | 
			
		||||
    }
 | 
			
		||||
    BleEvent event = {.type = BleEventTypeStopAdvertising};
 | 
			
		||||
    GapEvent event = {.type = GapEventTypeStopAdvertising};
 | 
			
		||||
    gap->on_event_cb(event, gap->context);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -370,7 +397,7 @@ static void gap_advetise_timer_callback(void* context) {
 | 
			
		||||
    furi_check(osMessageQueuePut(gap->command_queue, &command, 0, 0) == osOK);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) {
 | 
			
		||||
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
 | 
			
		||||
    if (!ble_glue_is_radio_stack_ready()) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
@@ -416,12 +443,33 @@ bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context) {
 | 
			
		||||
 | 
			
		||||
GapState gap_get_state() {
 | 
			
		||||
    GapState state;
 | 
			
		||||
    osMutexAcquire(gap->state_mutex, osWaitForever);
 | 
			
		||||
    state = gap->state;
 | 
			
		||||
    osMutexRelease(gap->state_mutex );
 | 
			
		||||
    if(gap) {
 | 
			
		||||
        osMutexAcquire(gap->state_mutex, osWaitForever);
 | 
			
		||||
        state = gap->state;
 | 
			
		||||
        osMutexRelease(gap->state_mutex );
 | 
			
		||||
    } else {
 | 
			
		||||
        state = GapStateUninitialized;
 | 
			
		||||
    }
 | 
			
		||||
    return state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void gap_start_scan(GapScanCallback callback, void* context) {
 | 
			
		||||
    furi_assert(callback);
 | 
			
		||||
    gap_scan = furi_alloc(sizeof(GapScan));
 | 
			
		||||
    gap_scan->callback = callback;
 | 
			
		||||
    gap_scan->context = context;
 | 
			
		||||
    // Scan interval 250 ms
 | 
			
		||||
    hci_le_set_scan_parameters(1, 4000, 200, 0, 0);
 | 
			
		||||
    hci_le_set_scan_enable(1, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void gap_stop_scan() {
 | 
			
		||||
    furi_assert(gap_scan);
 | 
			
		||||
    hci_le_set_scan_enable(0, 1);
 | 
			
		||||
    free(gap_scan);
 | 
			
		||||
    gap_scan = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void gap_thread_stop() {
 | 
			
		||||
    if(gap) {
 | 
			
		||||
        osMutexAcquire(gap->state_mutex, osWaitForever);
 | 
			
		||||
 
 | 
			
		||||
@@ -12,28 +12,36 @@ extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    BleEventTypeConnected,
 | 
			
		||||
    BleEventTypeDisconnected,
 | 
			
		||||
    BleEventTypeStartAdvertising,
 | 
			
		||||
    BleEventTypeStopAdvertising,
 | 
			
		||||
    BleEventTypePinCodeShow,
 | 
			
		||||
    BleEventTypePinCodeVerify,
 | 
			
		||||
    BleEventTypeUpdateMTU,
 | 
			
		||||
} BleEventType;
 | 
			
		||||
    GapEventTypeConnected,
 | 
			
		||||
    GapEventTypeDisconnected,
 | 
			
		||||
    GapEventTypeStartAdvertising,
 | 
			
		||||
    GapEventTypeStopAdvertising,
 | 
			
		||||
    GapEventTypePinCodeShow,
 | 
			
		||||
    GapEventTypePinCodeVerify,
 | 
			
		||||
    GapEventTypeUpdateMTU,
 | 
			
		||||
} GapEventType;
 | 
			
		||||
 | 
			
		||||
typedef union {
 | 
			
		||||
    uint32_t pin_code;
 | 
			
		||||
    uint16_t max_packet_size;
 | 
			
		||||
} BleEventData;
 | 
			
		||||
} GapEventData;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    BleEventType type;
 | 
			
		||||
    BleEventData data;
 | 
			
		||||
} BleEvent;
 | 
			
		||||
    GapEventType type;
 | 
			
		||||
    GapEventData data;
 | 
			
		||||
} GapEvent;
 | 
			
		||||
 | 
			
		||||
typedef bool(*BleEventCallback) (BleEvent event, void* context);
 | 
			
		||||
typedef bool(*GapEventCallback) (GapEvent event, void* context);
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint8_t type;
 | 
			
		||||
    uint8_t mac[6];
 | 
			
		||||
} GapAddress;
 | 
			
		||||
 | 
			
		||||
typedef void(*GapScanCallback) (GapAddress address, void* context);
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    GapStateUninitialized,
 | 
			
		||||
    GapStateIdle,
 | 
			
		||||
    GapStateStartingAdv,
 | 
			
		||||
    GapStateAdvFast,
 | 
			
		||||
@@ -42,6 +50,7 @@ typedef enum {
 | 
			
		||||
} GapState;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    GapPairingNone,
 | 
			
		||||
    GapPairingPinCodeShow,
 | 
			
		||||
    GapPairingPinCodeVerifyYesNo,
 | 
			
		||||
} GapPairing;
 | 
			
		||||
@@ -55,7 +64,7 @@ typedef struct {
 | 
			
		||||
    char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH];
 | 
			
		||||
} GapConfig;
 | 
			
		||||
 | 
			
		||||
bool gap_init(GapConfig* config, BleEventCallback on_event_cb, void* context);
 | 
			
		||||
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context);
 | 
			
		||||
 | 
			
		||||
void gap_start_advertising();
 | 
			
		||||
 | 
			
		||||
@@ -65,6 +74,10 @@ GapState gap_get_state();
 | 
			
		||||
 | 
			
		||||
void gap_thread_stop();
 | 
			
		||||
 | 
			
		||||
void gap_start_scan(GapScanCallback callback, void* context);
 | 
			
		||||
 | 
			
		||||
void gap_stop_scan();
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@
 | 
			
		||||
#define FURI_HAL_BT_DEFAULT_MAC_ADDR {0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72}
 | 
			
		||||
 | 
			
		||||
osMutexId_t furi_hal_bt_core2_mtx = NULL;
 | 
			
		||||
static FuriHalBtStack furi_hal_bt_stack = FuriHalBtStackUnknown;
 | 
			
		||||
 | 
			
		||||
typedef void (*FuriHalBtProfileStart)(void);
 | 
			
		||||
typedef void (*FuriHalBtProfileStop)(void);
 | 
			
		||||
@@ -50,7 +51,7 @@ FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = {
 | 
			
		||||
            .pairing_method = GapPairingPinCodeVerifyYesNo,
 | 
			
		||||
            .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR,
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
FuriHalBtProfileConfig* current_profile = NULL;
 | 
			
		||||
 | 
			
		||||
@@ -79,32 +80,81 @@ void furi_hal_bt_unlock_core2() {
 | 
			
		||||
    furi_check(osMutexRelease(furi_hal_bt_core2_mtx) == osOK);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool furi_hal_bt_start_core2() {
 | 
			
		||||
static bool furi_hal_bt_radio_stack_is_supported(WirelessFwInfo_t* info) {
 | 
			
		||||
    bool supported = false;
 | 
			
		||||
    if(info->StackType == INFO_STACK_TYPE_BLE_HCI) {
 | 
			
		||||
        furi_hal_bt_stack = FuriHalBtStackHciLayer;
 | 
			
		||||
        supported = true;
 | 
			
		||||
    } else if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) {
 | 
			
		||||
        if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR &&
 | 
			
		||||
           info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) {
 | 
			
		||||
            furi_hal_bt_stack = FuriHalBtStackLight;
 | 
			
		||||
            supported = true;
 | 
			
		||||
           } 
 | 
			
		||||
    } else {
 | 
			
		||||
        furi_hal_bt_stack = FuriHalBtStackUnknown;
 | 
			
		||||
    }
 | 
			
		||||
    return supported;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_start_radio_stack() {
 | 
			
		||||
    bool res = false;
 | 
			
		||||
    furi_assert(furi_hal_bt_core2_mtx);
 | 
			
		||||
 | 
			
		||||
    osMutexAcquire(furi_hal_bt_core2_mtx, osWaitForever);
 | 
			
		||||
 | 
			
		||||
    // Explicitly tell that we are in charge of CLK48 domain
 | 
			
		||||
    if(!HAL_HSEM_IsSemTaken(CFG_HW_CLK48_CONFIG_SEMID)) {
 | 
			
		||||
        HAL_HSEM_FastTake(CFG_HW_CLK48_CONFIG_SEMID);
 | 
			
		||||
    }
 | 
			
		||||
    // Start Core2
 | 
			
		||||
    bool ret = ble_glue_start();
 | 
			
		||||
    osMutexRelease(furi_hal_bt_core2_mtx);
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) {
 | 
			
		||||
    furi_assert(event_cb);
 | 
			
		||||
    furi_assert(profile < FuriHalBtProfileNumber);
 | 
			
		||||
    bool ret = true;
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
        // Start 2nd core
 | 
			
		||||
        ret = furi_hal_bt_start_core2();
 | 
			
		||||
        if(!ret) {
 | 
			
		||||
        // Wait until FUS is started or timeout
 | 
			
		||||
        WirelessFwInfo_t info = {};
 | 
			
		||||
        if(!ble_glue_wait_for_fus_start(&info)) {
 | 
			
		||||
            FURI_LOG_E(TAG, "FUS start failed");
 | 
			
		||||
            LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
 | 
			
		||||
            ble_glue_thread_stop();
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        // Check weather we support radio stack
 | 
			
		||||
        if(!furi_hal_bt_radio_stack_is_supported(&info)) {
 | 
			
		||||
            FURI_LOG_E(TAG, "Unsupported radio stack");
 | 
			
		||||
            LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
 | 
			
		||||
            ble_glue_thread_stop();
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        // Starting radio stack
 | 
			
		||||
        if(!ble_glue_start()) {
 | 
			
		||||
            FURI_LOG_E(TAG, "Failed to start radio stack");
 | 
			
		||||
            LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
 | 
			
		||||
            ble_glue_thread_stop();
 | 
			
		||||
            ble_app_thread_stop();
 | 
			
		||||
            FURI_LOG_E(TAG, "Failed to start 2nd core");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        res = true;
 | 
			
		||||
    } while(false);
 | 
			
		||||
    osMutexRelease(furi_hal_bt_core2_mtx);
 | 
			
		||||
 | 
			
		||||
    return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FuriHalBtStack furi_hal_bt_get_radio_stack() {
 | 
			
		||||
    return furi_hal_bt_stack;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) {
 | 
			
		||||
    furi_assert(event_cb);
 | 
			
		||||
    furi_assert(profile < FuriHalBtProfileNumber);
 | 
			
		||||
    bool ret = false;
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
        if(!ble_glue_is_radio_stack_ready()) {
 | 
			
		||||
            FURI_LOG_E(TAG, "Can't start BLE App - radio stack did not start");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if(furi_hal_bt_stack != FuriHalBtStackLight) {
 | 
			
		||||
            FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        // Set mac address
 | 
			
		||||
@@ -130,21 +180,23 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb,
 | 
			
		||||
            const char* clicker_str = "Keynote";
 | 
			
		||||
            memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str));
 | 
			
		||||
        }
 | 
			
		||||
        ret = gap_init(config, event_cb, context);
 | 
			
		||||
        if(!ret) {
 | 
			
		||||
        if(!gap_init(config, event_cb, context)) {
 | 
			
		||||
            gap_thread_stop();
 | 
			
		||||
            FURI_LOG_E(TAG, "Failed to init GAP");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        // Start selected profile services
 | 
			
		||||
        profile_config[profile].start();
 | 
			
		||||
        if(furi_hal_bt_stack == FuriHalBtStackLight) {
 | 
			
		||||
            profile_config[profile].start();
 | 
			
		||||
        }
 | 
			
		||||
        ret = true;
 | 
			
		||||
    } while(false);
 | 
			
		||||
    current_profile = &profile_config[profile];
 | 
			
		||||
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context) {
 | 
			
		||||
bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) {
 | 
			
		||||
    furi_assert(event_cb);
 | 
			
		||||
    furi_assert(profile < FuriHalBtProfileNumber);
 | 
			
		||||
    bool ret = true;
 | 
			
		||||
@@ -164,6 +216,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb,
 | 
			
		||||
    ble_glue_thread_stop();
 | 
			
		||||
    FURI_LOG_I(TAG, "Start BT initialization");
 | 
			
		||||
    furi_hal_bt_init();
 | 
			
		||||
    furi_hal_bt_start_radio_stack();
 | 
			
		||||
    ret = furi_hal_bt_start_app(profile, event_cb, context);
 | 
			
		||||
    if(ret) {
 | 
			
		||||
        current_profile = &profile_config[profile];
 | 
			
		||||
@@ -171,6 +224,10 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb,
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool furi_hal_bt_is_active() {
 | 
			
		||||
    return gap_get_state() > GapStateIdle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void furi_hal_bt_start_advertising() {
 | 
			
		||||
    if(gap_get_state() == GapStateIdle) {
 | 
			
		||||
        gap_start_advertising();
 | 
			
		||||
@@ -236,10 +293,6 @@ bool furi_hal_bt_is_alive() {
 | 
			
		||||
    return ble_glue_is_alive();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_is_active() {
 | 
			
		||||
    return gap_get_state() > GapStateIdle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void furi_hal_bt_start_tone_tx(uint8_t channel, uint8_t power) {
 | 
			
		||||
    aci_hal_set_tx_power_level(0, power);
 | 
			
		||||
    aci_hal_tone_start(channel, 0);
 | 
			
		||||
@@ -300,3 +353,17 @@ uint32_t furi_hal_bt_get_transmitted_packets() {
 | 
			
		||||
void furi_hal_bt_stop_rx() {
 | 
			
		||||
    aci_hal_rx_stop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool furi_hal_bt_start_scan(GapScanCallback callback, void* context) {
 | 
			
		||||
    if(furi_hal_bt_stack != FuriHalBtStackHciLayer) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    gap_start_scan(callback, context);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void furi_hal_bt_stop_scan() {
 | 
			
		||||
    if(furi_hal_bt_stack == FuriHalBtStackHciLayer) {
 | 
			
		||||
        gap_stop_scan();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,19 @@
 | 
			
		||||
 | 
			
		||||
#include "furi-hal-bt-serial.h"
 | 
			
		||||
 | 
			
		||||
#define FURI_HAL_BT_STACK_VERSION_MAJOR (1)
 | 
			
		||||
#define FURI_HAL_BT_STACK_VERSION_MINOR (13)
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    FuriHalBtStackUnknown,
 | 
			
		||||
    FuriHalBtStackHciLayer,
 | 
			
		||||
    FuriHalBtStackLight,
 | 
			
		||||
} FuriHalBtStack;
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    FuriHalBtProfileSerial,
 | 
			
		||||
    FuriHalBtProfileHidKeyboard,
 | 
			
		||||
@@ -36,26 +45,38 @@ void furi_hal_bt_lock_core2();
 | 
			
		||||
/** Lock core2 state transition */
 | 
			
		||||
void furi_hal_bt_unlock_core2();
 | 
			
		||||
 | 
			
		||||
/** Start radio stack
 | 
			
		||||
 *
 | 
			
		||||
 * @return  true on successfull radio stack start
 | 
			
		||||
 */
 | 
			
		||||
bool furi_hal_bt_start_radio_stack();
 | 
			
		||||
 | 
			
		||||
/** Get radio stack type
 | 
			
		||||
 *
 | 
			
		||||
 * @return  FuriHalBtStack instance
 | 
			
		||||
 */
 | 
			
		||||
FuriHalBtStack furi_hal_bt_get_radio_stack();
 | 
			
		||||
 | 
			
		||||
/** Start BLE app
 | 
			
		||||
 *
 | 
			
		||||
 * @param profile   FuriHalBtProfile instance
 | 
			
		||||
 * @param event_cb  BleEventCallback instance
 | 
			
		||||
 * @param event_cb  GapEventCallback instance
 | 
			
		||||
 * @param context   pointer to context
 | 
			
		||||
 *
 | 
			
		||||
 * @return          true on success
 | 
			
		||||
*/
 | 
			
		||||
bool furi_hal_bt_start_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context);
 | 
			
		||||
bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context);
 | 
			
		||||
 | 
			
		||||
/** Change BLE app
 | 
			
		||||
 * Restarts 2nd core
 | 
			
		||||
 *
 | 
			
		||||
 * @param profile   FuriHalBtProfile instance
 | 
			
		||||
 * @param event_cb  BleEventCallback instance
 | 
			
		||||
 * @param event_cb  GapEventCallback instance
 | 
			
		||||
 * @param context   pointer to context
 | 
			
		||||
 *
 | 
			
		||||
 * @return          true on success
 | 
			
		||||
*/
 | 
			
		||||
bool furi_hal_bt_change_app(FuriHalBtProfile profile, BleEventCallback event_cb, void* context);
 | 
			
		||||
bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context);
 | 
			
		||||
 | 
			
		||||
/** Update battery level
 | 
			
		||||
 *
 | 
			
		||||
@@ -71,12 +92,6 @@ void furi_hal_bt_start_advertising();
 | 
			
		||||
 */
 | 
			
		||||
void furi_hal_bt_stop_advertising();
 | 
			
		||||
 | 
			
		||||
/** Returns true if BLE is advertising
 | 
			
		||||
 *
 | 
			
		||||
 * @return     true if BLE advertising
 | 
			
		||||
 */
 | 
			
		||||
bool furi_hal_bt_is_active();
 | 
			
		||||
 | 
			
		||||
/** Get BT/BLE system component state
 | 
			
		||||
 *
 | 
			
		||||
 * @param[in]  buffer  string_t buffer to write to
 | 
			
		||||
@@ -167,6 +182,17 @@ float furi_hal_bt_get_rssi();
 | 
			
		||||
 */
 | 
			
		||||
uint32_t furi_hal_bt_get_transmitted_packets();
 | 
			
		||||
 | 
			
		||||
/** Start MAC addresses scan
 | 
			
		||||
 * @note Works only with HciLayer 2nd core firmware
 | 
			
		||||
 *
 | 
			
		||||
 * @param callback  GapScanCallback instance
 | 
			
		||||
 * @param context   pointer to context
 | 
			
		||||
 */
 | 
			
		||||
bool furi_hal_bt_start_scan(GapScanCallback callback, void* context);
 | 
			
		||||
 | 
			
		||||
/** Stop MAC addresses scan */
 | 
			
		||||
void furi_hal_bt_stop_scan();
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user