[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:
@@ -40,6 +40,7 @@ extern int32_t subghz_app(void* p);
|
||||
extern int32_t usb_mouse_app(void* p);
|
||||
extern int32_t usb_test_app(void* p);
|
||||
extern int32_t vibro_test_app(void* p);
|
||||
extern int32_t ble_keyboard_app(void* p);
|
||||
|
||||
// Plugins
|
||||
extern int32_t music_player_app(void* p);
|
||||
@@ -218,6 +219,10 @@ const size_t FLIPPER_PLUGINS_COUNT = sizeof(FLIPPER_PLUGINS) / sizeof(FlipperApp
|
||||
|
||||
// Plugin menu
|
||||
const FlipperApplication FLIPPER_DEBUG_APPS[] = {
|
||||
#ifdef APP_BLE_KEYBOARD
|
||||
{.app = ble_keyboard_app, .name = "BLE keyboard demo", .stack_size = 1024, .icon = NULL},
|
||||
#endif
|
||||
|
||||
#ifdef APP_BLINK
|
||||
{.app = blink_test_app, .name = "Blink Test", .stack_size = 1024, .icon = NULL},
|
||||
#endif
|
||||
|
@@ -47,7 +47,7 @@ APP_SD_TEST = 1
|
||||
APP_VIBRO_TEST = 1
|
||||
APP_USB_TEST = 1
|
||||
APP_DISPLAY_TEST = 1
|
||||
|
||||
APP_BLE_KEYBOARD = 1
|
||||
APP_USB_MOUSE = 1
|
||||
APP_BAD_USB = 1
|
||||
APP_UART_ECHO = 1
|
||||
@@ -167,6 +167,12 @@ CFLAGS += -DAPP_BAD_USB
|
||||
SRV_GUI = 1
|
||||
endif
|
||||
|
||||
APP_BLE_KEYBOARD ?=0
|
||||
ifeq ($(APP_BLE_KEYBOARD), 1)
|
||||
CFLAGS += -DAPP_BLE_KEYBOARD
|
||||
SRV_GUI = 1
|
||||
endif
|
||||
|
||||
APP_KEYPAD_TEST ?= 0
|
||||
ifeq ($(APP_KEYPAD_TEST), 1)
|
||||
CFLAGS += -DAPP_KEYPAD_TEST
|
||||
|
@@ -39,6 +39,19 @@ static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) {
|
||||
string_clear(pin_str);
|
||||
}
|
||||
|
||||
static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) {
|
||||
furi_assert(bt);
|
||||
string_t pin_str;
|
||||
dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0);
|
||||
string_init_printf(pin_str, "Verify code\n%06d", pin);
|
||||
dialog_message_set_text(
|
||||
bt->dialog_message, string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop);
|
||||
dialog_message_set_buttons(bt->dialog_message, "Cancel", "Ok", NULL);
|
||||
DialogMessageButton button = dialog_message_show(bt->dialogs, bt->dialog_message);
|
||||
string_clear(pin_str);
|
||||
return button == DialogMessageButtonCenter;
|
||||
}
|
||||
|
||||
static void bt_battery_level_changed_callback(const void* _event, void* context) {
|
||||
furi_assert(_event);
|
||||
furi_assert(context);
|
||||
@@ -56,7 +69,8 @@ static void bt_battery_level_changed_callback(const void* _event, void* context)
|
||||
Bt* bt_alloc() {
|
||||
Bt* bt = furi_alloc(sizeof(Bt));
|
||||
// Init default maximum packet size
|
||||
bt->max_packet_size = FURI_HAL_BT_PACKET_SIZE_MAX;
|
||||
bt->max_packet_size = FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX;
|
||||
bt->profile = BtProfileSerial;
|
||||
// Load settings
|
||||
if(!bt_settings_load(&bt->bt_settings)) {
|
||||
bt_settings_save(&bt->bt_settings);
|
||||
@@ -83,27 +97,30 @@ Bt* bt_alloc() {
|
||||
bt->rpc = furi_record_open("rpc");
|
||||
bt->rpc_event = osEventFlagsNew(NULL);
|
||||
|
||||
// API evnent
|
||||
bt->api_event = osEventFlagsNew(NULL);
|
||||
|
||||
return bt;
|
||||
}
|
||||
|
||||
// Called from GAP thread from Serial service
|
||||
static uint16_t bt_on_data_received_callback(uint8_t* data, uint16_t size, void* context) {
|
||||
static uint16_t bt_serial_event_callback(SerialServiceEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
uint16_t ret = 0;
|
||||
|
||||
size_t bytes_processed = rpc_session_feed(bt->rpc_session, data, size, 1000);
|
||||
if(bytes_processed != size) {
|
||||
FURI_LOG_E(TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size);
|
||||
if(event.event == SerialServiceEventTypeDataReceived) {
|
||||
size_t bytes_processed =
|
||||
rpc_session_feed(bt->rpc_session, event.data.buffer, event.data.size, 1000);
|
||||
if(bytes_processed != event.data.size) {
|
||||
FURI_LOG_E(
|
||||
TAG, "Only %d of %d bytes processed by RPC", bytes_processed, event.data.size);
|
||||
}
|
||||
ret = rpc_session_get_available_size(bt->rpc_session);
|
||||
} else if(event.event == SerialServiceEventTypeDataSent) {
|
||||
osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_BUFF_SENT);
|
||||
}
|
||||
return rpc_session_get_available_size(bt->rpc_session);
|
||||
}
|
||||
|
||||
// Called from GAP thread from Serial service
|
||||
static void bt_on_data_sent_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
|
||||
osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_BUFF_SENT);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Called from RPC thread
|
||||
@@ -115,11 +132,11 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt
|
||||
size_t bytes_sent = 0;
|
||||
while(bytes_sent < bytes_len) {
|
||||
size_t bytes_remain = bytes_len - bytes_sent;
|
||||
if(bytes_remain > bt->max_packet_size) {
|
||||
furi_hal_bt_tx(&bytes[bytes_sent], bt->max_packet_size);
|
||||
bytes_sent += bt->max_packet_size;
|
||||
if(bytes_remain > FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX) {
|
||||
furi_hal_bt_serial_tx(&bytes[bytes_sent], FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX);
|
||||
bytes_sent += FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX;
|
||||
} else {
|
||||
furi_hal_bt_tx(&bytes[bytes_sent], bytes_remain);
|
||||
furi_hal_bt_serial_tx(&bytes[bytes_sent], bytes_remain);
|
||||
bytes_sent += bytes_remain;
|
||||
}
|
||||
uint32_t event_flag =
|
||||
@@ -130,58 +147,65 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_rpc_buffer_is_empty_callback(void* context) {
|
||||
furi_assert(context);
|
||||
furi_hal_bt_notify_buffer_is_empty();
|
||||
}
|
||||
|
||||
// Called from GAP thread
|
||||
static void bt_on_gap_event_callback(BleEvent event, void* context) {
|
||||
static bool bt_on_gap_event_callback(BleEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
bool ret = false;
|
||||
|
||||
if(event.type == BleEventTypeConnected) {
|
||||
// Update status bar
|
||||
bt->status = BtStatusConnected;
|
||||
BtMessage message = {.type = BtMessageTypeUpdateStatusbar};
|
||||
furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
|
||||
// Open RPC session
|
||||
FURI_LOG_I(TAG, "Open RPC connection");
|
||||
bt->rpc_session = rpc_session_open(bt->rpc);
|
||||
rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback);
|
||||
rpc_session_set_buffer_is_empty_callback(bt->rpc_session, bt_rpc_buffer_is_empty_callback);
|
||||
rpc_session_set_context(bt->rpc_session, bt);
|
||||
furi_hal_bt_set_data_event_callbacks(
|
||||
RPC_BUFFER_SIZE, bt_on_data_received_callback, bt_on_data_sent_callback, bt);
|
||||
if(bt->profile == BtProfileSerial) {
|
||||
// Open RPC session
|
||||
FURI_LOG_I(TAG, "Open RPC connection");
|
||||
bt->rpc_session = rpc_session_open(bt->rpc);
|
||||
rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback);
|
||||
rpc_session_set_buffer_is_empty_callback(
|
||||
bt->rpc_session, furi_hal_bt_serial_notify_buffer_is_empty);
|
||||
rpc_session_set_context(bt->rpc_session, bt);
|
||||
furi_hal_bt_serial_set_event_callback(RPC_BUFFER_SIZE, bt_serial_event_callback, bt);
|
||||
}
|
||||
// Update battery level
|
||||
PowerInfo info;
|
||||
power_get_info(bt->power, &info);
|
||||
message.type = BtMessageTypeUpdateBatteryLevel;
|
||||
message.data.battery_level = info.charge;
|
||||
furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
|
||||
ret = true;
|
||||
} else if(event.type == BleEventTypeDisconnected) {
|
||||
if(bt->rpc_session) {
|
||||
if(bt->profile == BtProfileSerial && bt->rpc_session) {
|
||||
FURI_LOG_I(TAG, "Close RPC connection");
|
||||
osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
|
||||
rpc_session_close(bt->rpc_session);
|
||||
furi_hal_bt_set_data_event_callbacks(0, NULL, NULL, NULL);
|
||||
furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
|
||||
bt->rpc_session = NULL;
|
||||
}
|
||||
ret = true;
|
||||
} else if(event.type == BleEventTypeStartAdvertising) {
|
||||
bt->status = BtStatusAdvertising;
|
||||
BtMessage message = {.type = BtMessageTypeUpdateStatusbar};
|
||||
furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
|
||||
ret = true;
|
||||
} else if(event.type == BleEventTypeStopAdvertising) {
|
||||
bt->status = BtStatusOff;
|
||||
BtMessage message = {.type = BtMessageTypeUpdateStatusbar};
|
||||
furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
|
||||
ret = true;
|
||||
} else if(event.type == BleEventTypePinCodeShow) {
|
||||
BtMessage message = {
|
||||
.type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code};
|
||||
furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
|
||||
ret = true;
|
||||
} else if(event.type == BleEventTypePinCodeVerify) {
|
||||
ret = bt_pin_code_verify_event_handler(bt, event.data.pin_code);
|
||||
} else if(event.type == BleEventTypeUpdateMTU) {
|
||||
bt->max_packet_size = event.data.max_packet_size;
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) {
|
||||
@@ -204,29 +228,56 @@ static void bt_statusbar_update(Bt* bt) {
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_change_profile(Bt* bt, BtMessage* message) {
|
||||
if(bt->profile == BtProfileSerial && bt->rpc_session) {
|
||||
FURI_LOG_I(TAG, "Close RPC connection");
|
||||
osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
|
||||
rpc_session_close(bt->rpc_session);
|
||||
furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
|
||||
bt->rpc_session = NULL;
|
||||
}
|
||||
|
||||
FuriHalBtProfile furi_profile;
|
||||
if(message->data.profile == BtProfileHidKeyboard) {
|
||||
furi_profile = FuriHalBtProfileHidKeyboard;
|
||||
} else {
|
||||
furi_profile = FuriHalBtProfileSerial;
|
||||
}
|
||||
|
||||
if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) {
|
||||
FURI_LOG_I(TAG, "Bt App started");
|
||||
if(bt->bt_settings.enabled) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
|
||||
bt->profile = message->data.profile;
|
||||
*message->result = true;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to start Bt App");
|
||||
*message->result = false;
|
||||
}
|
||||
osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT);
|
||||
}
|
||||
|
||||
int32_t bt_srv() {
|
||||
Bt* bt = bt_alloc();
|
||||
furi_record_create("bt", bt);
|
||||
|
||||
// Read keys
|
||||
if(!bt_load_key_storage(bt)) {
|
||||
FURI_LOG_W(TAG, "Failed to load saved bonding keys");
|
||||
FURI_LOG_W(TAG, "Failed to load bonding keys");
|
||||
}
|
||||
// Start 2nd core
|
||||
if(!furi_hal_bt_start_core2()) {
|
||||
FURI_LOG_E(TAG, "Core2 startup failed");
|
||||
} else {
|
||||
view_port_enabled_set(bt->statusbar_view_port, true);
|
||||
if(furi_hal_bt_init_app(bt_on_gap_event_callback, bt)) {
|
||||
FURI_LOG_I(TAG, "BLE stack started");
|
||||
if(bt->bt_settings.enabled) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "BT App start failed");
|
||||
|
||||
// Start BLE stack
|
||||
if(furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) {
|
||||
FURI_LOG_I(TAG, "BLE stack started");
|
||||
if(bt->bt_settings.enabled) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "BT App start failed");
|
||||
}
|
||||
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
|
||||
|
||||
// Update statusbar
|
||||
bt_statusbar_update(bt);
|
||||
@@ -239,14 +290,14 @@ int32_t bt_srv() {
|
||||
bt_statusbar_update(bt);
|
||||
} else if(message.type == BtMessageTypeUpdateBatteryLevel) {
|
||||
// Update battery level
|
||||
if(furi_hal_bt_is_active()) {
|
||||
battery_svc_update_level(message.data.battery_level);
|
||||
}
|
||||
furi_hal_bt_update_battery_level(message.data.battery_level);
|
||||
} else if(message.type == BtMessageTypePinCodeShow) {
|
||||
// Display PIN code
|
||||
bt_pin_code_show_event_handler(bt, message.data.pin_code);
|
||||
} else if(message.type == BtMessageTypeKeysStorageUpdated) {
|
||||
bt_save_key_storage(bt);
|
||||
} else if(message.type == BtMessageTypeSetProfile) {
|
||||
bt_change_profile(bt, &message);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
@@ -9,6 +9,22 @@ extern "C" {
|
||||
|
||||
typedef struct Bt Bt;
|
||||
|
||||
typedef enum {
|
||||
BtProfileSerial,
|
||||
BtProfileHidKeyboard,
|
||||
} BtProfile;
|
||||
|
||||
/**
|
||||
* Change BLE Profile
|
||||
* @note Call of this function leads to 2nd core restart
|
||||
*
|
||||
* @param bt Bt instance
|
||||
* @param profile BtProfile
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool bt_set_profile(Bt* bt, BtProfile profile);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
15
applications/bt/bt_service/bt_api.c
Executable file
15
applications/bt/bt_service/bt_api.c
Executable file
@@ -0,0 +1,15 @@
|
||||
#include "bt_i.h"
|
||||
|
||||
bool bt_set_profile(Bt* bt, BtProfile profile) {
|
||||
furi_assert(bt);
|
||||
|
||||
// Send message
|
||||
bool result = false;
|
||||
BtMessage message = {
|
||||
.type = BtMessageTypeSetProfile, .data.profile = profile, .result = &result};
|
||||
furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
|
||||
// Wait for unlock
|
||||
osEventFlagsWait(bt->api_event, BT_API_UNLOCK_EVENT, osFlagsWaitAny, osWaitForever);
|
||||
|
||||
return result;
|
||||
}
|
@@ -15,6 +15,8 @@
|
||||
|
||||
#include "../bt_settings.h"
|
||||
|
||||
#define BT_API_UNLOCK_EVENT (1UL << 0)
|
||||
|
||||
typedef enum {
|
||||
BtStatusOff,
|
||||
BtStatusAdvertising,
|
||||
@@ -26,16 +28,19 @@ typedef enum {
|
||||
BtMessageTypeUpdateBatteryLevel,
|
||||
BtMessageTypePinCodeShow,
|
||||
BtMessageTypeKeysStorageUpdated,
|
||||
BtMessageTypeSetProfile,
|
||||
} BtMessageType;
|
||||
|
||||
typedef union {
|
||||
uint32_t pin_code;
|
||||
uint8_t battery_level;
|
||||
BtProfile profile;
|
||||
} BtMessageData;
|
||||
|
||||
typedef struct {
|
||||
BtMessageType type;
|
||||
BtMessageData data;
|
||||
bool* result;
|
||||
} BtMessage;
|
||||
|
||||
struct Bt {
|
||||
@@ -44,6 +49,7 @@ struct Bt {
|
||||
uint16_t max_packet_size;
|
||||
BtSettings bt_settings;
|
||||
BtStatus status;
|
||||
BtProfile profile;
|
||||
osMessageQueueId_t message_queue;
|
||||
Gui* gui;
|
||||
ViewPort* statusbar_view_port;
|
||||
@@ -53,4 +59,5 @@ struct Bt {
|
||||
Rpc* rpc;
|
||||
RpcSession* rpc_session;
|
||||
osEventFlagsId_t rpc_event;
|
||||
osEventFlagsId_t api_event;
|
||||
};
|
||||
|
138
applications/debug_tools/ble_keyboard/ble_keyboard.c
Executable file
138
applications/debug_tools/ble_keyboard/ble_keyboard.c
Executable file
@@ -0,0 +1,138 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <bt/bt_service/bt.h>
|
||||
#include <furi-hal-bt.h>
|
||||
#include <furi-hal-bt-hid.h>
|
||||
#include <furi-hal-usb-hid.h>
|
||||
|
||||
#define TAG "BleKeyboardApp"
|
||||
|
||||
typedef enum {
|
||||
EventTypeInput,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
InputEvent input;
|
||||
};
|
||||
EventType type;
|
||||
} BleKeyboardEvent;
|
||||
|
||||
static void ble_keyboard_render_callback(Canvas* canvas, void* ctx) {
|
||||
canvas_clear(canvas);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 0, 10, "BLE keypad demo");
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 0, 63, "Hold [back] to exit");
|
||||
}
|
||||
|
||||
static void ble_keyboard_input_callback(InputEvent* input_event, void* ctx) {
|
||||
osMessageQueueId_t event_queue = ctx;
|
||||
|
||||
BleKeyboardEvent event;
|
||||
event.type = EventTypeInput;
|
||||
event.input = *input_event;
|
||||
osMessageQueuePut(event_queue, &event, 0, osWaitForever);
|
||||
}
|
||||
|
||||
int32_t ble_keyboard_app(void* p) {
|
||||
Bt* bt = furi_record_open("bt");
|
||||
if(!bt_set_profile(bt, BtProfileHidKeyboard)) {
|
||||
FURI_LOG_E(TAG, "Failed to switch profile");
|
||||
furi_record_close("bt");
|
||||
return -1;
|
||||
}
|
||||
bool bt_turned_on = furi_hal_bt_is_active();
|
||||
if(!bt_turned_on) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
|
||||
osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(BleKeyboardEvent), NULL);
|
||||
furi_check(event_queue);
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
|
||||
view_port_draw_callback_set(view_port, ble_keyboard_render_callback, NULL);
|
||||
view_port_input_callback_set(view_port, ble_keyboard_input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
BleKeyboardEvent event;
|
||||
while(1) {
|
||||
osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, osWaitForever);
|
||||
|
||||
if(event_status == osOK) {
|
||||
if(event.type == EventTypeInput) {
|
||||
if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) {
|
||||
furi_hal_bt_hid_kb_release_all();
|
||||
break;
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyBack) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_ESC);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_ESC);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyOk) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_ENTER);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_ENTER);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyRight) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_RIGHT_ARROW);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_RIGHT_ARROW);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyLeft) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_LEFT_ARROW);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_LEFT_ARROW);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyDown) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_DOWN_ARROW);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_DOWN_ARROW);
|
||||
}
|
||||
}
|
||||
|
||||
if(event.input.key == InputKeyUp) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
furi_hal_bt_hid_kb_press(KEY_UP_ARROW);
|
||||
} else if(event.input.type == InputTypeRelease) {
|
||||
furi_hal_bt_hid_kb_release(KEY_UP_ARROW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
view_port_update(view_port);
|
||||
}
|
||||
|
||||
if(bt_turned_on) {
|
||||
furi_hal_bt_stop_advertising();
|
||||
}
|
||||
// remove & free all stuff created by app
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
osMessageQueueDelete(event_queue);
|
||||
furi_record_close("gui");
|
||||
bt_set_profile(bt, BtProfileSerial);
|
||||
furi_record_close("bt");
|
||||
return 0;
|
||||
}
|
Reference in New Issue
Block a user