Merge branch 'reborned/some_api_adjustments' of github.com:RebornedBrain/flipperzero-firmware into reborned/some_api_adjustments

This commit is contained in:
RebornedBrain 2024-10-14 18:33:15 +03:00
commit ddd0fc80f8
138 changed files with 12558 additions and 1560 deletions

View file

@ -9,8 +9,8 @@
# Flipper Zero Firmware
- [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what Flipper Zero can do.
- [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices.
- [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and anything you want to ask.
- [Flipper Zero Firmware Update](https://flipperzero.one/update). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices.
- [User Documentation](https://docs.flipper.net). Learn more about your dolphin: specs, usage guides, and anything you want to ask.
- [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). Dive into the Flipper Zero Firmware source code: build system, firmware structure, and more.
# Contributing
@ -19,7 +19,7 @@ Our main goal is to build a healthy and sustainable community around Flipper, so
## I need help
The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). If you want to contribute to the firmware development, or modify it for your own needs, you can also check our [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen).
The best place to search for answers is our [User Documentation](https://docs.flipper.net). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). If you want to contribute to the firmware development or modify it for your own needs, you can also check our [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen).
## I want to report an issue
@ -120,3 +120,7 @@ Also, see `ReadMe.md` files inside those directories for further details.
- Website: [flipperzero.one](https://flipperzero.one)
- Forum: [forum.flipperzero.one](https://forum.flipperzero.one/)
- Kickstarter: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers)
## SAST Tools
- [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code.

View file

@ -1,6 +1,5 @@
#include <furi.h>
#include <gui/gui.h>
#include <gui/canvas_i.h>
#include <input/input.h>
#define BUFFER_SIZE (32U)
@ -42,10 +41,11 @@ static DirectDraw* direct_draw_alloc(void) {
static void direct_draw_free(DirectDraw* instance) {
furi_pubsub_unsubscribe(instance->input, instance->input_subscription);
instance->canvas = NULL;
gui_direct_draw_release(instance->gui);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_INPUT_EVENTS);
free(instance);
}
static void direct_draw_block(Canvas* canvas, uint32_t size, uint32_t counter) {

View file

@ -1,7 +1,13 @@
# Number Input
# Number Input {#example_number_input}
Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need of intense validations after a user input.
Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need for intense validations after a user input.
Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed withing min - max, an additional button is displayed to switch the sign between + and -.
## Source code
It is also possible to define a header text, shown in this example app with the 3 different input options.
Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_number_input).
## General principle
Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed within min - max, an additional button is displayed to switch the sign between + and -.
It is also possible to define a header text, as shown in this example app with the 3 different input options.

View file

@ -7,7 +7,7 @@ App(
icon="A_BadUsb_14",
order=70,
resources="resources",
fap_libs=["assets", "ble_profile"],
fap_libs=["assets"],
fap_icon="icon.png",
fap_category="USB",
)

View file

@ -35,7 +35,6 @@ static void bad_usb_load_settings(BadUsbApp* app) {
FuriString* temp_str = furi_string_alloc();
uint32_t version = 0;
uint32_t interface = 0;
if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) {
do {
@ -45,8 +44,6 @@ static void bad_usb_load_settings(BadUsbApp* app) {
break;
if(!flipper_format_read_string(fff, "layout", temp_str)) break;
if(!flipper_format_read_uint32(fff, "interface", &interface, 1)) break;
if(interface > BadUsbHidInterfaceBle) break;
state = true;
} while(0);
@ -56,7 +53,6 @@ static void bad_usb_load_settings(BadUsbApp* app) {
if(state) {
furi_string_set(app->keyboard_layout, temp_str);
app->interface = interface;
Storage* fs_api = furi_record_open(RECORD_STORAGE);
FileInfo layout_file_info;
@ -68,7 +64,6 @@ static void bad_usb_load_settings(BadUsbApp* app) {
}
} else {
furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT);
app->interface = BadUsbHidInterfaceUsb;
}
furi_string_free(temp_str);
@ -84,9 +79,6 @@ static void bad_usb_save_settings(BadUsbApp* app) {
fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION))
break;
if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break;
uint32_t interface_id = app->interface;
if(!flipper_format_write_uint32(fff, "interface", (const uint32_t*)&interface_id, 1))
break;
} while(0);
}

View file

@ -41,7 +41,6 @@ struct BadUsbApp {
BadUsb* bad_usb_view;
BadUsbScript* bad_usb_script;
BadUsbHidInterface interface;
FuriHalUsbInterface* usb_if_prev;
};

View file

@ -1,12 +1,9 @@
#include "bad_usb_hid.h"
#include <extra_profiles/hid_profile.h>
#include <bt/bt_service/bt.h>
#include <storage/storage.h>
#define TAG "BadUSB HID"
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) {
furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg));
return NULL;
@ -72,155 +69,6 @@ static const BadUsbHidApi hid_api_usb = {
.release_all = hid_usb_release_all,
.get_led_state = hid_usb_get_led_state,
};
typedef struct {
Bt* bt;
FuriHalBleProfileBase* profile;
HidStateCallback state_callback;
void* callback_context;
bool is_connected;
} BleHidInstance;
static const BleProfileHidParams ble_hid_params = {
.device_name_prefix = "BadUSB",
.mac_xor = 0x0002,
};
static void hid_ble_connection_status_callback(BtStatus status, void* context) {
furi_assert(context);
BleHidInstance* ble_hid = context;
ble_hid->is_connected = (status == BtStatusConnected);
if(ble_hid->state_callback) {
ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context);
}
}
void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) {
UNUSED(hid_cfg);
BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance));
ble_hid->bt = furi_record_open(RECORD_BT);
bt_disconnect(ble_hid->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params);
furi_check(ble_hid->profile);
furi_hal_bt_start_advertising();
bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid);
return ble_hid;
}
void hid_ble_deinit(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
bt_set_status_changed_callback(ble_hid->bt, NULL, NULL);
bt_disconnect(ble_hid->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_default_path(ble_hid->bt);
furi_check(bt_profile_restore_default(ble_hid->bt));
furi_record_close(RECORD_BT);
free(ble_hid);
}
void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
ble_hid->state_callback = cb;
ble_hid->callback_context = context;
}
bool hid_ble_is_connected(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_hid->is_connected;
}
bool hid_ble_kb_press(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_kb_press(ble_hid->profile, button);
}
bool hid_ble_kb_release(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_kb_release(ble_hid->profile, button);
}
bool hid_ble_consumer_press(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_consumer_key_press(ble_hid->profile, button);
}
bool hid_ble_consumer_release(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_consumer_key_release(ble_hid->profile, button);
}
bool hid_ble_release_all(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
bool state = ble_profile_hid_kb_release_all(ble_hid->profile);
state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile);
return state;
}
uint8_t hid_ble_get_led_state(void* inst) {
UNUSED(inst);
FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented");
return 0;
}
static const BadUsbHidApi hid_api_ble = {
.init = hid_ble_init,
.deinit = hid_ble_deinit,
.set_state_callback = hid_ble_set_state_callback,
.is_connected = hid_ble_is_connected,
.kb_press = hid_ble_kb_press,
.kb_release = hid_ble_kb_release,
.consumer_press = hid_ble_consumer_press,
.consumer_release = hid_ble_consumer_release,
.release_all = hid_ble_release_all,
.get_led_state = hid_ble_get_led_state,
};
const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface) {
if(interface == BadUsbHidInterfaceUsb) {
const BadUsbHidApi* bad_usb_hid_get_interface() {
return &hid_api_usb;
} else {
return &hid_api_ble;
}
}
void bad_usb_hid_ble_remove_pairing(void) {
Bt* bt = furi_record_open(RECORD_BT);
bt_disconnect(bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
furi_hal_bt_stop_advertising();
bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
bt_forget_bonded_devices(bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_default_path(bt);
furi_check(bt_profile_restore_default(bt));
furi_record_close(RECORD_BT);
}

View file

@ -7,11 +7,6 @@ extern "C" {
#include <furi.h>
#include <furi_hal.h>
typedef enum {
BadUsbHidInterfaceUsb,
BadUsbHidInterfaceBle,
} BadUsbHidInterface;
typedef struct {
void* (*init)(FuriHalUsbHidConfig* hid_cfg);
void (*deinit)(void* inst);
@ -26,7 +21,7 @@ typedef struct {
uint8_t (*get_led_state)(void* inst);
} BadUsbHidApi;
const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface);
const BadUsbHidApi* bad_usb_hid_get_interface();
void bad_usb_hid_ble_remove_pairing(void);

View file

@ -650,7 +650,7 @@ static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) {
memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout)));
}
BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) {
BadUsbScript* bad_usb_script_open(FuriString* file_path) {
furi_assert(file_path);
BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
@ -660,7 +660,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface inte
bad_usb->st.state = BadUsbStateInit;
bad_usb->st.error[0] = '\0';
bad_usb->hid = bad_usb_hid_get_interface(interface);
bad_usb->hid = bad_usb_hid_get_interface();
bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb);
furi_thread_start(bad_usb->thread);

View file

@ -34,7 +34,7 @@ typedef struct {
typedef struct BadUsbScript BadUsbScript;
BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface);
BadUsbScript* bad_usb_script_open(FuriString* file_path);
void bad_usb_script_close(BadUsbScript* bad_usb);

View file

@ -1,88 +0,0 @@
#include "../bad_usb_app_i.h"
enum SubmenuIndex {
ConfigIndexKeyboardLayout,
ConfigIndexInterface,
ConfigIndexBleUnpair,
};
const char* const interface_mode_text[2] = {
"USB",
"BLE",
};
void bad_usb_scene_config_select_callback(void* context, uint32_t index) {
BadUsbApp* bad_usb = context;
if(index != ConfigIndexInterface) {
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index);
}
}
void bad_usb_scene_config_interface_callback(VariableItem* item) {
BadUsbApp* bad_usb = variable_item_get_context(item);
furi_assert(bad_usb);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, interface_mode_text[index]);
bad_usb->interface = index;
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ConfigIndexInterface);
}
static void draw_menu(BadUsbApp* bad_usb) {
VariableItemList* var_item_list = bad_usb->var_item_list;
variable_item_list_reset(var_item_list);
variable_item_list_add(var_item_list, "Keyboard Layout (global)", 0, NULL, NULL);
VariableItem* item = variable_item_list_add(
var_item_list, "Interface", 2, bad_usb_scene_config_interface_callback, bad_usb);
if(bad_usb->interface == BadUsbHidInterfaceUsb) {
variable_item_set_current_value_index(item, 0);
variable_item_set_current_value_text(item, interface_mode_text[0]);
} else {
variable_item_set_current_value_index(item, 1);
variable_item_set_current_value_text(item, interface_mode_text[1]);
variable_item_list_add(var_item_list, "Remove Pairing", 0, NULL, NULL);
}
}
void bad_usb_scene_config_on_enter(void* context) {
BadUsbApp* bad_usb = context;
VariableItemList* var_item_list = bad_usb->var_item_list;
variable_item_list_set_enter_callback(
var_item_list, bad_usb_scene_config_select_callback, bad_usb);
draw_menu(bad_usb);
variable_item_list_set_selected_item(var_item_list, 0);
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig);
}
bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) {
BadUsbApp* bad_usb = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == ConfigIndexKeyboardLayout) {
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout);
} else if(event.event == ConfigIndexInterface) {
draw_menu(bad_usb);
} else if(event.event == ConfigIndexBleUnpair) {
bad_usb_hid_ble_remove_pairing();
} else {
furi_crash("Unknown key type");
}
}
return consumed;
}
void bad_usb_scene_config_on_exit(void* context) {
BadUsbApp* bad_usb = context;
VariableItemList* var_item_list = bad_usb->var_item_list;
variable_item_list_reset(var_item_list);
}

View file

@ -1,5 +1,4 @@
ADD_SCENE(bad_usb, file_select, FileSelect)
ADD_SCENE(bad_usb, work, Work)
ADD_SCENE(bad_usb, error, Error)
ADD_SCENE(bad_usb, config, Config)
ADD_SCENE(bad_usb, config_layout, ConfigLayout)

View file

@ -20,7 +20,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
bad_usb_script_close(app->bad_usb_script);
app->bad_usb_script = NULL;
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout);
}
consumed = true;
} else if(event.event == InputKeyOk) {
@ -39,7 +39,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
void bad_usb_scene_work_on_enter(void* context) {
BadUsbApp* app = context;
app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface);
app->bad_usb_script = bad_usb_script_open(app->file_path);
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
FuriString* file_name;

View file

@ -47,7 +47,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
if((state == BadUsbStateIdle) || (state == BadUsbStateDone) ||
(state == BadUsbStateNotConnected)) {
elements_button_center(canvas, "Run");
elements_button_left(canvas, "Config");
elements_button_left(canvas, "Layout");
} else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) {
elements_button_center(canvas, "Stop");
if(!model->pause_wait) {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -360,12 +360,6 @@ protocol: NECext
address: 33 00 00 00
command: 0B F4 00 00
#
name: Power
type: parsed
protocol: NECext
address: 83 55 00 00
command: 90 6F 00 00
#
name: Vol_dn
type: parsed
protocol: NECext
@ -738,12 +732,6 @@ protocol: NEC
address: 00 00 00 00
command: 8C 00 00 00
#
name: Power
type: parsed
protocol: NECext
address: 87 45 00 00
command: 17 E8 00 00
#
name: Vol_up
type: raw
frequency: 38000
@ -810,6 +798,8 @@ protocol: NECext
address: 83 F4 00 00
command: 17 E8 00 00
#
# Model: ViewSonic X1_Projector
#
name: Vol_up
type: raw
frequency: 38000
@ -851,3 +841,351 @@ type: raw
frequency: 38000
duty_cycle: 0.330000
data: 9010 4253 564 566 566 1671 566 1699 565 565 539 568 564 566 565 539 566 1699 565 1672 565 566 566 1672 564 567 565 1672 565 567 565 567 564 541 564 1698 566 539 565 567 565 567 562 542 565 1699 564 539 567 1699 565 540 564 1698 566 1672 565 1698 566 1672 565 567 565 1671 565 566 566
#
# Model: Apeman LC650_
#
name: Power
type: parsed
protocol: NECext
address: 00 BD 00 00
command: 01 FE 00 00
#
name: Mute
type: parsed
protocol: NECext
address: 00 BD 00 00
command: 6A 95 00 00
#
# Model: BenQ MH856UST
#
name: Vol_up
type: parsed
protocol: NECext
address: 00 30 00 00
command: 97 68 00 00
#
# Model: BenQ TRY01
#
name: Power
type: parsed
protocol: NECext
address: 04 B1 00 00
command: 58 A7 00 00
#
# Model: Generic Universal_Remote
#
name: Power
type: parsed
protocol: NECext
address: 48 50 00 00
command: 02 FD 00 00
#
name: Mute
type: parsed
protocol: NECext
address: 48 50 00 00
command: 27 D8 00 00
#
# Model: Coolux X3S
#
name: Power
type: parsed
protocol: NEC
address: 01 00 00 00
command: 00 00 00 00
#
name: Mute
type: parsed
protocol: NEC
address: 01 00 00 00
command: 1A 00 00 00
#
# Model: Dell projector
#
name: Power
type: parsed
protocol: NECext
address: 4F 50 00 00
command: 02 FD 00 00
#
# Model: Dell tsfm_ir01
#
name: Mute
type: parsed
protocol: NECext
address: 4F 50 00 00
command: 0F F0 00 00
#
# Model: Epson 4650
#
name: Power
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 2288 611 571 587 1153 587 572 587 572 1167 572 1168 571 587 573 587 572 587 572 589 570 587 572 587 572 587 572 588 571 77346 2287 611 571 588 1152 588 571 588 571 1168 571 1168 571 588 571 588 571 588 571 589 570 587 572 588 572 587 572 587 572
#
name: Vol_up
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 2288 610 572 588 1151 587 573 588 571 1169 570 586 573 587 572 587 1152 587 573 586 1153 588 572 587 573 586 573 587 572 76771 2289 610 572 588 1151 587 573 587 572 1166 573 587 572 587 572 587 1152 588 571 588 1151 587 573 587 572 587 573 588 571
#
# Model: Epson EHTW5650
#
name: Mute
type: parsed
protocol: NECext
address: 83 55 00 00
command: AD 52 00 00
#
# Model: Epson EMP822H
#
name: Vol_up
type: parsed
protocol: NECext
address: 83 55 00 00
command: B1 4E 00 00
#
# Model: Epson projector_Power_Only
#
name: Power
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 8811 4222 530 1580 531 1579 531 507 531 507 531 507 531 508 531 508 530 1582 528 1583 527 535 503 1608 502 536 501 1609 501 537 501 1610 500 538 500 1611 499 538 500 539 500 538 500 1611 500 539 499 538 500 1611 499 539 499 1611 499 1611 500 1611 499 539 499 1611 500 1611 500 539 499 35437 8784 4252 500 1611 500 1612 500 539 500 539 500 539 500 539 500 539 500 1611 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 539 500 539 499 1612 499 540 499 539 500 1612 499 539 500 1612 499 1613 499 1612 499 539 500 1612 500 1612 500 539 500
#
# Model: Gateway 210_projextor
#
name: Power
type: parsed
protocol: NEC
address: 30 00 00 00
command: 0B 00 00 00
#
# Model: Groview
#
name: Power
type: parsed
protocol: NECext
address: 86 6B 00 00
command: 0A F5 00 00
#
name: Mute
type: parsed
protocol: NECext
address: 86 6B 00 00
command: 4A B5 00 00
#
name: Vol_up
type: parsed
protocol: NECext
address: 86 6B 00 00
command: 0E F1 00 00
#
# Model: Infocus Navigator_3
#
name: Power
type: parsed
protocol: NECext
address: 87 4E 00 00
command: 17 E8 00 00
#
# Model: JVC LX-UH1B
#
name: Power
type: parsed
protocol: NECext
address: 00 6A 00 00
command: 40 BF 00 00
#
# Model: LG PH300-NA
#
name: Power
type: parsed
protocol: NECext
address: 04 0F 00 00
command: AD 52 00 00
#
name: Vol_up
type: parsed
protocol: NECext
address: 04 0F 00 00
command: 02 FD 00 00
#
name: Vol_dn
type: parsed
protocol: NECext
address: 04 0F 00 00
command: 03 FC 00 00
#
name: Mute
type: parsed
protocol: NECext
address: 04 0F 00 00
command: 09 F6 00 00
#
# Model: Maxell MC-EU5001
#
name: Mute
type: parsed
protocol: NECext
address: 87 45 00 00
command: 52 AD 00 00
#
# Model: NexiGo-PJ20
#
name: Mute
type: parsed
protocol: NEC
address: 03 00 00 00
command: 02 00 00 00
#
# Model: Optoma projector
#
name: Vol_up
type: parsed
protocol: NECext
address: 4F 50 00 00
command: 07 F8 00 00
#
# Model: Optoma Remote_HOF04K276D6
#
name: Vol_dn
type: parsed
protocol: NECext
address: 4F 50 00 00
command: 0A F5 00 00
#
# Model: Optoma UHZ45
#
name: Mute
type: parsed
protocol: NEC
address: 32 00 00 00
command: 03 00 00 00
#
name: Vol_up
type: parsed
protocol: NEC
address: 32 00 00 00
command: 09 00 00 00
#
name: Vol_dn
type: parsed
protocol: NEC
address: 32 00 00 00
command: 0C 00 00 00
#
# Model: Philips PicoPix_Max_PPX620_Projector
#
name: Power
type: parsed
protocol: NEC
address: 02 00 00 00
command: 12 00 00 00
#
# Model: PVO YG300Pro
#
name: Power
type: parsed
protocol: NEC
address: 01 00 00 00
command: 40 00 00 00
#
name: Mute
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 9107 4376 681 1573 681 472 655 472 654 474 652 475 652 476 651 476 652 476 651 476 651 1604 651 1604 651 1604 651 1603 652 1604 651 1604 651 1604 651 1604 650 476 651 477 650 477 650 476 651 477 651 1604 650 476 651 477 650 1604 651 1604 650 1604 651 1604 650 1604 651 477 650 1604 650 39498 9079 2178 651
#
# Model: RIF6-cube-projector-raw
#
name: Power
type: raw
frequency: 36045
duty_cycle: 0.330000
data: 9024 4506 570 582 542 582 518 606 518 606 518 606 518 606 518 606 518 610 518 1698 548 1698 546 1700 546 1700 546 1698 546 1700 546 1696 548 1702 570 1674 546 610 516 1698 546 606 542 582 542 582 544 1674 546 610 520 606 542 1674 570 582 542 1676 546 1698 570 1674 572 582 520 1698 546
#
name: Mute
type: raw
frequency: 36045
duty_cycle: 0.330000
data: 9044 4484 572 580 544 580 544 580 544 580 542 582 544 580 520 604 542 586 544 1674 570 1674 570 1676 570 1674 572 1672 570 1674 546 1700 568 1678 570 582 542 1672 572 580 542 580 542 1674 566 582 542 1672 570 584 538 1674 564 580 534 1674 556 1674 556 580 530 1672 558 582 524 1676 552
#
# Model: Samsung Freestyle_Gen2
#
name: Power
type: parsed
protocol: Samsung32
address: 07 00 00 00
command: 02 00 00 00
#
name: Mute
type: parsed
protocol: Samsung32
address: 07 00 00 00
command: D1 00 00 00
#
# Model: Samsung VG-TM2360E
#
name: Power
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 1221 1171 433 566 433 881 433 2381 433 1486 434 565 434 1486 433 1776 433 2380 433 565 434 2381 433 1170 434 87358 1220 1171 433 566 433 883 431 2381 433 1486 433 567 432 1487 432 1775 434 2381 432 566 433 2380 434 1171 433 86252 1221 1172 432 565 434 880 435 2381 433 1486 433 565 434 1487 432 1776 433 2380 434 566 433 2379 434 1170 434
#
name: Vol_up
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 1221 1173 431 566 433 882 432 2382 432 1487 432 566 433 565 434 566 433 2671 432 2670 433 2381 433 566 433 87411 324 937 325 358 325 647 326
#
name: Vol_dn
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 1221 1172 432 566 433 882 433 2381 432 1486 434 566 433 565 434 882 433 1486 434 2669 434 2065 433 566 433 87779 324 936 326 358 325 647 326
#
# Model: Sharp RRMCGA664WJSA_Notevision XR-32S-L
#
name: Power
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 293 1801 296 753 295 1801 296 1801 296 752 296 754 294 1801 296 1800 297 752 296 1802 295 752 296 1801 296 753 295 1800 297 752 296 42709 296 1800 297 753 295 1800 297 1800 297 753 295 1802 295 753 295 753 295 1801 296 753 295 1801 296 754 294 1802 295 753 295 1801 296 42694 295 1800 297 752 296 1803 294 1803 294 753 295 753 295 1801 296 1802 295 752 296 1802 295 752 296 1801 296 753 295 1802 295 753 295 42709 295 1802 295 753 295 1803 294 1801 296 753 295 1802 295 752 296 752 296 1801 296 752 296 1803 294 754 294 1803 294 754 294 1804 293 42694 294 1802 294 755 293 1803 294 1804 268 779 269 779 269 1828 269 1828 269 780 268 1829 268 778 270 1829 323 725 268 1829 268 781 324
#
# Model: SMART Projectors
#
name: Power
type: parsed
protocol: NECext
address: 8B CA 00 00
command: 12 ED 00 00
#
name: Mute
type: parsed
protocol: NECext
address: 8B CA 00 00
command: 11 EE 00 00
#
# Model: Sony RM_PJ27
#
name: Vol_up
type: parsed
protocol: SIRC15
address: 54 00 00 00
command: 12 00 00 00
#
name: Vol_dn
type: parsed
protocol: SIRC15
address: 54 00 00 00
command: 13 00 00 00
#
# Model: TopVision
#
name: Vol_up
type: parsed
protocol: NEC
address: 02 00 00 00
command: 11 00 00 00

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,20 @@
#define TAG "Mosgortrans"
void render_section_header(
FuriString* str,
const char* name,
uint8_t prefix_separator_cnt,
uint8_t suffix_separator_cnt) {
for(uint8_t i = 0; i < prefix_separator_cnt; i++) {
furi_string_cat_printf(str, ":");
}
furi_string_cat_printf(str, "[ %s ]", name);
for(uint8_t i = 0; i < suffix_separator_cnt; i++) {
furi_string_cat_printf(str, ":");
}
}
void from_days_to_datetime(uint32_t days, DateTime* datetime, uint16_t start_year) {
uint32_t timestamp = days * 24 * 60 * 60;
DateTime start_datetime = {0};

View file

@ -10,6 +10,11 @@
extern "C" {
#endif
void render_section_header(
FuriString* str,
const char* name,
uint8_t prefix_separator_cnt,
uint8_t suffix_separator_cnt);
bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result);
#ifdef __cplusplus

View file

@ -15,4 +15,11 @@ static constexpr auto nfc_app_api_table = sort(create_array_t<sym_entry>(
API_METHOD(
mosgortrans_parse_transport_block,
bool,
(const MfClassicBlock* block, FuriString* result))));
(const MfClassicBlock* block, FuriString* result)),
API_METHOD(
render_section_header,
void,
(FuriString * str,
const char* name,
uint8_t prefix_separator_cnt,
uint8_t suffix_separator_cnt))));

View file

@ -92,6 +92,15 @@ App(
sources=["plugins/supported_cards/troika.c"],
)
App(
appid="social_moscow_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="social_moscow_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/social_moscow.c"],
)
App(
appid="washcity_parser",
apptype=FlipperAppType.PLUGIN,

View file

@ -559,6 +559,7 @@ static void nfc_protocol_support_scene_save_name_on_exit(NfcApp* instance) {
*/
enum {
NfcSceneEmulateStateWidget, /**< Widget view is displayed. */
NfcSceneEmulateStateWidgetLog, /**< Widget view with Log button is displayed */
NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */
};
@ -633,12 +634,14 @@ static bool
"Log",
nfc_protocol_support_common_widget_callback,
instance);
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog);
}
// Update TextBox data
text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
consumed = true;
} else if(event.event == GuiButtonTypeCenter) {
if(state == NfcSceneEmulateStateWidget) {
if(state == NfcSceneEmulateStateWidgetLog) {
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox);
@ -649,7 +652,7 @@ static bool
if(state == NfcSceneEmulateStateTextBox) {
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget);
instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog);
consumed = true;
}
}

View file

@ -106,7 +106,7 @@ static const IdMapping bart_zones[] = {
{.id = 0x0023, .name = "South Hayward"},
{.id = 0x0024, .name = "Union City"},
{.id = 0x0025, .name = "Fremont"},
{.id = 0x0026, .name = "Daly City(2)?"},
{.id = 0x0026, .name = "Castro Valley"},
{.id = 0x0027, .name = "Dublin/Pleasanton"},
{.id = 0x0028, .name = "South San Francisco"},
{.id = 0x0029, .name = "San Bruno"},
@ -115,6 +115,8 @@ static const IdMapping bart_zones[] = {
{.id = 0x002c, .name = "West Dublin/Pleasanton"},
{.id = 0x002d, .name = "OAK Airport"},
{.id = 0x002e, .name = "Warm Springs/South Fremont"},
{.id = 0x002f, .name = "Milpitas"},
{.id = 0x0030, .name = "Berryessa/North San Jose"},
};
static const size_t kNumBARTZones = COUNT(bart_zones);

View file

@ -4,10 +4,21 @@
#include <nfc/nfc_device.h>
#include <bit_lib/bit_lib.h>
#include <datetime.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#define TAG "Plantain"
void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t start_year) {
uint32_t timestamp = minutes * 60;
DateTime start_datetime = {0};
start_datetime.year = start_year - 1;
start_datetime.month = 12;
start_datetime.day = 31;
timestamp += datetime_datetime_to_timestamp(&start_datetime);
datetime_timestamp_to_datetime(timestamp, datetime);
}
typedef struct {
uint64_t a;
uint64_t b;
@ -208,29 +219,92 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
if(key != cfg.keys[cfg.data_sector].a) break;
// Point to block 0 of sector 4, value 0
const uint8_t* temp_ptr = data->block[16].data;
// Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
// 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
uint32_t balance =
((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
// Read card number
// Point to block 0 of sector 0, value 0
temp_ptr = data->block[0].data;
// Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
// 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal
uint8_t card_number_arr[7];
for(size_t i = 0; i < 7; i++) {
card_number_arr[i] = temp_ptr[6 - i];
}
// Copy card number to uint64_t
furi_string_printf(parsed_data, "\e#Plantain card\n");
uint64_t card_number = 0;
for(size_t i = 0; i < 7; i++) {
card_number = (card_number << 8) | card_number_arr[i];
card_number = (card_number << 8) | data->block[0].data[6 - i];
}
furi_string_printf(
parsed_data, "\e#Plantain\nNo.: %lluX\nBalance: %lu\n", card_number, balance);
// Print card number with 4-digit groups
furi_string_cat_printf(parsed_data, "Number: ");
FuriString* card_number_s = furi_string_alloc();
furi_string_cat_printf(card_number_s, "%llu", card_number);
FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 ");
for(uint8_t i = 0; i < 24; i += 4) {
for(uint8_t j = 0; j < 4; j++) {
furi_string_push_back(tmp_s, furi_string_get_char(card_number_s, i + j));
}
furi_string_push_back(tmp_s, ' ');
}
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s));
if(data->type == MfClassicType1k) {
//balance
uint32_t balance = 0;
for(uint8_t i = 0; i < 4; i++) {
balance = (balance << 8) | data->block[16].data[3 - i];
}
furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100);
//trips
uint8_t trips_metro = data->block[21].data[0];
uint8_t trips_ground = data->block[21].data[1];
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
//trip time
uint32_t last_trip_timestamp = 0;
for(uint8_t i = 0; i < 3; i++) {
last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i];
}
DateTime last_trip = {0};
from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010);
furi_string_cat_printf(
parsed_data,
"Trip start: %02d.%02d.%04d %02d:%02d\n",
last_trip.day,
last_trip.month,
last_trip.year,
last_trip.hour,
last_trip.minute);
//validator
uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4];
furi_string_cat_printf(parsed_data, "Validator: %d\n", validator);
//tariff
uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6];
furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100);
//trips in metro
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
//trips on ground
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
//last payment
uint32_t last_payment_timestamp = 0;
for(uint8_t i = 0; i < 3; i++) {
last_payment_timestamp = (last_payment_timestamp << 8) |
data->block[18].data[4 - i];
}
DateTime last_payment_date = {0};
from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010);
furi_string_cat_printf(
parsed_data,
"Last pay: %02d.%02d.%04d %02d:%02d\n",
last_payment_date.day,
last_payment_date.month,
last_payment_date.year,
last_payment_date.hour,
last_payment_date.minute);
//payment summ
uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8];
furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100);
furi_string_free(card_number_s);
furi_string_free(tmp_s);
} else if(data->type == MfClassicType4k) {
//trips
uint8_t trips_metro = data->block[36].data[0];
uint8_t trips_ground = data->block[36].data[1];
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
//trips in metro
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
//trips on ground
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
}
parsed = true;
} while(false);

View file

@ -0,0 +1,301 @@
#include "nfc_supported_card_plugin.h"
#include <core/check.h>
#include <flipper_application/flipper_application.h>
#include <nfc/nfc_device.h>
#include <bit_lib/bit_lib.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include "../../api/mosgortrans/mosgortrans_util.h"
#include "furi_hal_rtc.h"
#define TAG "Social_Moscow"
typedef struct {
uint64_t a;
uint64_t b;
} MfClassicKeyPair;
typedef struct {
const MfClassicKeyPair* keys;
uint32_t data_sector;
} SocialMoscowCardConfig;
static const MfClassicKeyPair social_moscow_1k_keys[] = {
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025},
{.a = 0x2735fc181807, .b = 0xbf23a53c1f63},
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368},
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
{.a = 0x73068f118c13, .b = 0x2b7f3253fac5},
{.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057},
{.a = 0x3a4bba8adaf0, .b = 0x67362d90f973},
{.a = 0x8765b17968a2, .b = 0x6202a38f69e2},
{.a = 0x40ead80721ce, .b = 0x100533b89331},
{.a = 0x0db5e6523f7c, .b = 0x653a87594079},
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
{.a = 0x51119dae5216, .b = 0xd8a274b2e026},
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368},
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f},
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}};
static const MfClassicKeyPair social_moscow_4k_keys[] = {
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //1
{.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, //2
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //3
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //4
{.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, //5
{.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, //6
{.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, //7
{.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, //8
{.a = 0x40ead80721ce, .b = 0x100533b89331}, //9
{.a = 0x0db5e6523f7c, .b = 0x653a87594079}, //10
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //11
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //12
{.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //13
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //14
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //15
{.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //16
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //17
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //18
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //19
{.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //20
{.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //21
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //22
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //23
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //24
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //25
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //26
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //27
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //28
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //29
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //30
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //31
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //32
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //33
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //34
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //35
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //36
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //37
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //38
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //39
{.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //40
};
static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) {
bool success = true;
if(type == MfClassicType1k) {
config->data_sector = 15;
config->keys = social_moscow_1k_keys;
} else if(type == MfClassicType4k) {
config->data_sector = 15;
config->keys = social_moscow_4k_keys;
} else {
success = false;
}
return success;
}
static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) {
bool verified = false;
do {
SocialMoscowCardConfig cfg = {};
if(!social_moscow_get_card_config(&cfg, type)) break;
const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector);
MfClassicKey key = {0};
bit_lib_num_to_bytes_be(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data);
MfClassicAuthContext auth_context;
MfClassicError error =
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
if(error != MfClassicErrorNone) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
break;
}
FURI_LOG_D(TAG, "Verify success!");
verified = true;
} while(false);
return verified;
}
static bool social_moscow_verify(Nfc* nfc) {
return social_moscow_verify_type(nfc, MfClassicType1k) ||
social_moscow_verify_type(nfc, MfClassicType4k);
}
static bool social_moscow_read(Nfc* nfc, NfcDevice* device) {
furi_assert(nfc);
furi_assert(device);
bool is_read = false;
MfClassicData* data = mf_classic_alloc();
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
do {
MfClassicType type = MfClassicType4k;
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
if(error != MfClassicErrorNone) break;
data->type = type;
SocialMoscowCardConfig cfg = {};
if(!social_moscow_get_card_config(&cfg, data->type)) break;
MfClassicDeviceKeys keys = {};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
FURI_BIT_SET(keys.key_b_mask, i);
}
error = mf_classic_poller_sync_read(nfc, &keys, data);
if(error == MfClassicErrorNotPresent) {
FURI_LOG_W(TAG, "Failed to read data");
break;
}
nfc_device_set_data(device, NfcProtocolMfClassic, data);
is_read = (error == MfClassicErrorNone);
} while(false);
mf_classic_free(data);
return is_read;
}
static uint8_t calculate_luhn(uint64_t number) {
// https://en.wikipedia.org/wiki/Luhn_algorithm
// Drop existing check digit to form payload
uint64_t payload = number / 10;
int sum = 0;
int position = 0;
while(payload > 0) {
int digit = payload % 10;
if(position % 2 == 0) {
digit *= 2;
}
if(digit > 9) {
digit = (digit / 10) + (digit % 10);
}
sum += digit;
payload /= 10;
position++;
}
return (10 - (sum % 10)) % 10;
}
static uint64_t hex_num(uint64_t hex) {
uint64_t result = 0;
for(uint8_t i = 0; i < 8; ++i) {
uint8_t half_byte = hex & 0x0F;
uint64_t num = 0;
for(uint8_t j = 0; j < 4; ++j) {
num += (half_byte & 0x1) * (1 << j);
half_byte = half_byte >> 1;
}
result += num * pow(10, i);
hex = hex >> 4;
}
return result;
}
static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
bool parsed = false;
do {
// Verify card type
SocialMoscowCardConfig cfg = {};
if(!social_moscow_get_card_config(&cfg, data->type)) break;
// Verify key
const MfClassicSectorTrailer* sec_tr =
mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector);
const uint64_t key_a =
bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
const uint64_t key_b =
bit_lib_bytes_to_num_be(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data));
if((key_a != cfg.keys[cfg.data_sector].a) || (key_b != cfg.keys[cfg.data_sector].b)) break;
uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24);
uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8);
uint64_t card_number = bit_lib_get_bits_64(data->block[60].data, 40, 40);
uint8_t card_control = bit_lib_get_bits(data->block[60].data, 80, 4);
uint64_t omc_number = bit_lib_get_bits_64(data->block[21].data, 8, 64);
uint8_t year = data->block[60].data[11];
uint8_t month = data->block[60].data[12];
uint64_t number = hex_num(card_control) + hex_num(card_number) * 10 +
hex_num(card_region) * 10 * 10000000000 +
hex_num(card_code) * 10 * 10000000000 * 100;
uint8_t luhn = calculate_luhn(number);
if(luhn != card_control) break;
FuriString* metro_result = furi_string_alloc();
FuriString* ground_result = furi_string_alloc();
bool is_metro_data_present =
mosgortrans_parse_transport_block(&data->block[4], metro_result);
bool is_ground_data_present =
mosgortrans_parse_transport_block(&data->block[16], ground_result);
furi_string_cat_printf(
parsed_data,
"\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n",
card_code,
card_region,
card_number,
card_control,
omc_number,
month,
year,
data->block[60].data[13],
data->block[60].data[14]);
if(is_metro_data_present && !furi_string_empty(metro_result)) {
render_section_header(parsed_data, "Metro", 22, 21);
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result));
}
if(is_ground_data_present && !furi_string_empty(ground_result)) {
render_section_header(parsed_data, "Ground", 21, 20);
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result));
}
furi_string_free(ground_result);
furi_string_free(metro_result);
parsed = true;
} while(false);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin social_moscow_plugin = {
.protocol = NfcProtocolMfClassic,
.verify = social_moscow_verify,
.read = social_moscow_read,
.parse = social_moscow_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor social_moscow_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &social_moscow_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* social_moscow_plugin_ep() {
return &social_moscow_plugin_descriptor;
}

View file

@ -82,20 +82,6 @@ static const MfClassicKeyPair troika_4k_keys[] = {
{.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40
};
static void troika_render_section_header(
FuriString* str,
const char* name,
uint8_t prefix_separator_cnt,
uint8_t suffix_separator_cnt) {
for(uint8_t i = 0; i < prefix_separator_cnt; i++) {
furi_string_cat_printf(str, ":");
}
furi_string_cat_printf(str, "[ %s ]", name);
for(uint8_t i = 0; i < suffix_separator_cnt; i++) {
furi_string_cat_printf(str, ":");
}
}
static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) {
bool success = true;
@ -212,23 +198,25 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) {
FuriString* ground_result = furi_string_alloc();
FuriString* tat_result = furi_string_alloc();
bool result1 = mosgortrans_parse_transport_block(&data->block[32], metro_result);
bool result2 = mosgortrans_parse_transport_block(&data->block[28], ground_result);
bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result);
bool is_metro_data_present =
mosgortrans_parse_transport_block(&data->block[32], metro_result);
bool is_ground_data_present =
mosgortrans_parse_transport_block(&data->block[28], ground_result);
bool is_tat_data_present = mosgortrans_parse_transport_block(&data->block[16], tat_result);
furi_string_cat_printf(parsed_data, "\e#Troyka card\n");
if(result1 && !furi_string_empty(metro_result)) {
troika_render_section_header(parsed_data, "Metro", 22, 21);
if(is_metro_data_present && !furi_string_empty(metro_result)) {
render_section_header(parsed_data, "Metro", 22, 21);
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result));
}
if(result2 && !furi_string_empty(ground_result)) {
troika_render_section_header(parsed_data, "Ediny", 22, 22);
if(is_ground_data_present && !furi_string_empty(ground_result)) {
render_section_header(parsed_data, "Ediny", 22, 22);
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result));
}
if(result3 && !furi_string_empty(tat_result)) {
troika_render_section_header(parsed_data, "TAT", 24, 23);
if(is_tat_data_present && !furi_string_empty(tat_result)) {
render_section_header(parsed_data, "TAT", 24, 23);
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tat_result));
}
@ -236,7 +224,7 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_string_free(ground_result);
furi_string_free(metro_result);
parsed = result1 || result2 || result3;
parsed = is_metro_data_present || is_ground_data_present || is_tat_data_present;
} while(false);
return parsed;

View file

@ -999,13 +999,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
chat_event = subghz_chat_worker_get_event_chat(subghz_chat);
switch(chat_event.event) {
case SubGhzChatEventInputData:
if(chat_event.c == CliSymbolAsciiETX) {
if(chat_event.c == CliKeyETX) {
printf("\r\n");
chat_event.event = SubGhzChatEventUserExit;
subghz_chat_worker_put_event_chat(subghz_chat, &chat_event);
break;
} else if(
(chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) {
} else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) {
size_t len = furi_string_utf8_length(input);
if(len > furi_string_utf8_length(name)) {
printf("%s", "\e[D\e[1P");
@ -1027,7 +1026,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
}
furi_string_set(input, sysmsg);
}
} else if(chat_event.c == CliSymbolAsciiCR) {
} else if(chat_event.c == CliKeyCR) {
printf("\r\n");
furi_string_push_back(input, '\r');
furi_string_push_back(input, '\n');
@ -1041,7 +1040,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) {
furi_string_printf(input, "%s", furi_string_get_cstr(name));
printf("%s", furi_string_get_cstr(input));
fflush(stdout);
} else if(chat_event.c == CliSymbolAsciiLF) {
} else if(chat_event.c == CliKeyLF) {
//cut out the symbol \n
} else {
putc(chat_event.c, stdout);

View file

@ -1,5 +1,6 @@
#pragma once
#include <cli/cli.h>
#include <cli/cli_ansi.h>
void subghz_on_system_start(void);

View file

@ -430,13 +430,11 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
*message->profile_instance = NULL;
}
}
if(message->lock) api_lock_unlock(message->lock);
}
static void bt_close_connection(Bt* bt, BtMessage* message) {
static void bt_close_connection(Bt* bt) {
bt_close_rpc_connection(bt);
furi_hal_bt_stop_advertising();
if(message->lock) api_lock_unlock(message->lock);
}
static void bt_apply_settings(Bt* bt) {
@ -484,19 +482,13 @@ static void bt_load_settings(Bt* bt) {
}
static void bt_handle_get_settings(Bt* bt, BtMessage* message) {
furi_assert(message->lock);
*message->data.settings = bt->bt_settings;
api_lock_unlock(message->lock);
}
static void bt_handle_set_settings(Bt* bt, BtMessage* message) {
furi_assert(message->lock);
bt->bt_settings = *message->data.csettings;
bt_apply_settings(bt);
bt_settings_save(&bt->bt_settings);
api_lock_unlock(message->lock);
}
static void bt_handle_reload_keys_settings(Bt* bt) {
@ -548,6 +540,12 @@ int32_t bt_srv(void* p) {
while(1) {
furi_check(
furi_message_queue_get(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
FURI_LOG_D(
TAG,
"call %d, lock 0x%p, result 0x%p",
message.type,
(void*)message.lock,
(void*)message.result);
if(message.type == BtMessageTypeUpdateStatus) {
// Update view ports
bt_statusbar_update(bt);
@ -571,7 +569,7 @@ int32_t bt_srv(void* p) {
} else if(message.type == BtMessageTypeSetProfile) {
bt_change_profile(bt, &message);
} else if(message.type == BtMessageTypeDisconnect) {
bt_close_connection(bt, &message);
bt_close_connection(bt);
} else if(message.type == BtMessageTypeForgetBondedDevices) {
bt_keys_storage_delete(bt->keys_storage);
} else if(message.type == BtMessageTypeGetSettings) {
@ -581,6 +579,8 @@ int32_t bt_srv(void* p) {
} else if(message.type == BtMessageTypeReloadKeysSettings) {
bt_handle_reload_keys_settings(bt);
}
if(message.lock) api_lock_unlock(message.lock);
}
return 0;

View file

@ -1,12 +1,15 @@
#include "cli_i.h"
#include "cli_commands.h"
#include "cli_vcp.h"
#include "cli_ansi.h"
#include <furi_hal_version.h>
#include <loader/loader.h>
#define TAG "CliSrv"
#define CLI_INPUT_LEN_LIMIT 256
#define CLI_PROMPT ">: " // qFlipper does not recognize us if we use escape sequences :(
#define CLI_PROMPT_LENGTH 3 // printable characters
Cli* cli_alloc(void) {
Cli* cli = malloc(sizeof(Cli));
@ -85,7 +88,7 @@ bool cli_cmd_interrupt_received(Cli* cli) {
char c = '\0';
if(cli_is_connected(cli)) {
if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) {
return c == CliSymbolAsciiETX;
return c == CliKeyETX;
}
} else {
return true;
@ -102,7 +105,8 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) {
}
void cli_motd(void) {
printf("\r\n"
printf(ANSI_FLIPPER_BRAND_ORANGE
"\r\n"
" _.-------.._ -,\r\n"
" .-\"```\"--..,,_/ /`-, -, \\ \r\n"
" .:\" /:/ /'\\ \\ ,_..., `. | |\r\n"
@ -116,12 +120,11 @@ void cli_motd(void) {
" _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n"
"| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n"
"| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n"
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n"
"\r\n"
"Welcome to Flipper Zero Command Line Interface!\r\n"
"|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" ANSI_RESET
"\r\n" ANSI_FG_BR_WHITE "Welcome to " ANSI_FLIPPER_BRAND_ORANGE
"Flipper Zero" ANSI_FG_BR_WHITE " Command Line Interface!\r\n"
"Read the manual: https://docs.flipper.net/development/cli\r\n"
"Run `help` or `?` to list available commands\r\n"
"\r\n");
"Run `help` or `?` to list available commands\r\n" ANSI_RESET "\r\n");
const Version* firmware_version = furi_hal_version_get_firmware_version();
if(firmware_version) {
@ -142,7 +145,7 @@ void cli_nl(Cli* cli) {
void cli_prompt(Cli* cli) {
UNUSED(cli);
printf("\r\n>: %s", furi_string_get_cstr(cli->line));
printf("\r\n" CLI_PROMPT "%s", furi_string_get_cstr(cli->line));
fflush(stdout);
}
@ -165,7 +168,7 @@ static void cli_handle_backspace(Cli* cli) {
cli->cursor_position--;
} else {
cli_putc(cli, CliSymbolAsciiBell);
cli_putc(cli, CliKeyBell);
}
}
@ -241,7 +244,7 @@ static void cli_handle_enter(Cli* cli) {
printf(
"`%s` command not found, use `help` or `?` to list all available commands",
furi_string_get_cstr(command));
cli_putc(cli, CliSymbolAsciiBell);
cli_putc(cli, CliKeyBell);
}
cli_reset(cli);
@ -305,8 +308,85 @@ static void cli_handle_autocomplete(Cli* cli) {
cli_prompt(cli);
}
static void cli_handle_escape(Cli* cli, char c) {
if(c == 'A') {
typedef enum {
CliCharClassWord,
CliCharClassSpace,
CliCharClassOther,
} CliCharClass;
/**
* @brief Determines the class that a character belongs to
*
* The return value of this function should not be used on its own; it should
* only be used for comparing it with other values returned by this function.
* This function is used internally in `cli_skip_run`.
*/
static CliCharClass cli_char_class(char c) {
if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') {
return CliCharClassWord;
} else if(c == ' ') {
return CliCharClassSpace;
} else {
return CliCharClassOther;
}
}
typedef enum {
CliSkipDirectionLeft,
CliSkipDirectionRight,
} CliSkipDirection;
/**
* @brief Skips a run of a class of characters
*
* @param string Input string
* @param original_pos Position to start the search at
* @param direction Direction in which to perform the search
* @returns The position at which the run ends
*/
static size_t cli_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) {
if(furi_string_size(string) == 0) return original_pos;
if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos;
if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string))
return original_pos;
int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0;
int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1;
int32_t position = original_pos;
CliCharClass start_class =
cli_char_class(furi_string_get_char(string, position + look_offset));
while(true) {
position += increment;
if(position < 0) break;
if(position >= (int32_t)furi_string_size(string)) break;
if(cli_char_class(furi_string_get_char(string, position + look_offset)) != start_class)
break;
}
return MAX(0, position);
}
void cli_process_input(Cli* cli) {
CliKeyCombo combo = cli_read_ansi_key_combo(cli);
FURI_LOG_T(TAG, "code=0x%02x, mod=0x%x\r\n", combo.key, combo.modifiers);
if(combo.key == CliKeyTab) {
cli_handle_autocomplete(cli);
} else if(combo.key == CliKeySOH) {
furi_delay_ms(33); // We are too fast, Minicom is not ready yet
cli_motd();
cli_prompt(cli);
} else if(combo.key == CliKeyETX) {
cli_reset(cli);
cli_prompt(cli);
} else if(combo.key == CliKeyEOT) {
cli_reset(cli);
} else if(combo.key == CliKeyUp && combo.modifiers == CliModKeyNo) {
// Use previous command if line buffer is empty
if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) {
// Set line buffer and cursor position
@ -315,67 +395,85 @@ static void cli_handle_escape(Cli* cli, char c) {
// Show new line to user
printf("%s", furi_string_get_cstr(cli->line));
}
} else if(c == 'B') {
} else if(c == 'C') {
} else if(combo.key == CliKeyDown && combo.modifiers == CliModKeyNo) {
// Clear input buffer
furi_string_reset(cli->line);
cli->cursor_position = 0;
printf("\r" CLI_PROMPT "\e[0K");
} else if(combo.key == CliKeyRight && combo.modifiers == CliModKeyNo) {
// Move right
if(cli->cursor_position < furi_string_size(cli->line)) {
cli->cursor_position++;
printf("\e[C");
}
} else if(c == 'D') {
} else if(combo.key == CliKeyLeft && combo.modifiers == CliModKeyNo) {
// Move left
if(cli->cursor_position > 0) {
cli->cursor_position--;
printf("\e[D");
}
}
fflush(stdout);
}
void cli_process_input(Cli* cli) {
char in_chr = cli_getc(cli);
size_t rx_len;
} else if(combo.key == CliKeyHome && combo.modifiers == CliModKeyNo) {
// Move to beginning of line
cli->cursor_position = 0;
printf("\e[%uG", CLI_PROMPT_LENGTH + 1); // columns start at 1 \(-_-)/
} else if(combo.key == CliKeyEnd && combo.modifiers == CliModKeyNo) {
// Move to end of line
cli->cursor_position = furi_string_size(cli->line);
printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1);
if(in_chr == CliSymbolAsciiTab) {
cli_handle_autocomplete(cli);
} else if(in_chr == CliSymbolAsciiSOH) {
furi_delay_ms(33); // We are too fast, Minicom is not ready yet
cli_motd();
cli_prompt(cli);
} else if(in_chr == CliSymbolAsciiETX) {
cli_reset(cli);
cli_prompt(cli);
} else if(in_chr == CliSymbolAsciiEOT) {
cli_reset(cli);
} else if(in_chr == CliSymbolAsciiEsc) {
rx_len = cli_read(cli, (uint8_t*)&in_chr, 1);
if((rx_len > 0) && (in_chr == '[')) {
cli_read(cli, (uint8_t*)&in_chr, 1);
cli_handle_escape(cli, in_chr);
} else {
cli_putc(cli, CliSymbolAsciiBell);
}
} else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) {
cli_handle_backspace(cli);
} else if(in_chr == CliSymbolAsciiCR) {
cli_handle_enter(cli);
} else if(
(in_chr >= 0x20 && in_chr < 0x7F) && //-V560
combo.modifiers == CliModKeyCtrl &&
(combo.key == CliKeyLeft || combo.key == CliKeyRight)) {
// Skip run of similar chars to the left or right
CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft :
CliSkipDirectionRight;
cli->cursor_position = cli_skip_run(cli->line, cli->cursor_position, direction);
printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1);
} else if(combo.key == CliKeyBackspace || combo.key == CliKeyDEL) {
cli_handle_backspace(cli);
} else if(combo.key == CliKeyETB) { // Ctrl + Backspace
// Delete run of similar chars to the left
size_t run_start = cli_skip_run(cli->line, cli->cursor_position, CliSkipDirectionLeft);
furi_string_replace_at(cli->line, run_start, cli->cursor_position - run_start, "");
cli->cursor_position = run_start;
printf(
"\e[%zuG%s\e[0K\e[%zuG", // move cursor, print second half of line, erase remains, move cursor again
CLI_PROMPT_LENGTH + cli->cursor_position + 1,
furi_string_get_cstr(cli->line) + run_start,
CLI_PROMPT_LENGTH + run_start + 1);
} else if(combo.key == CliKeyCR) {
cli_handle_enter(cli);
} else if(
(combo.key >= 0x20 && combo.key < 0x7F) && //-V560
(furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) {
if(cli->cursor_position == furi_string_size(cli->line)) {
furi_string_push_back(cli->line, in_chr);
cli_putc(cli, in_chr);
furi_string_push_back(cli->line, combo.key);
cli_putc(cli, combo.key);
} else {
// Insert character to line buffer
const char in_str[2] = {in_chr, 0};
const char in_str[2] = {combo.key, 0};
furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str);
// Print character in replace mode
printf("\e[4h%c\e[4l", in_chr);
printf("\e[4h%c\e[4l", combo.key);
fflush(stdout);
}
cli->cursor_position++;
} else {
cli_putc(cli, CliSymbolAsciiBell);
cli_putc(cli, CliKeyBell);
}
fflush(stdout);
}
void cli_add_command(

View file

@ -10,26 +10,12 @@
extern "C" {
#endif
typedef enum {
CliSymbolAsciiSOH = 0x01,
CliSymbolAsciiETX = 0x03,
CliSymbolAsciiEOT = 0x04,
CliSymbolAsciiBell = 0x07,
CliSymbolAsciiBackspace = 0x08,
CliSymbolAsciiTab = 0x09,
CliSymbolAsciiLF = 0x0A,
CliSymbolAsciiCR = 0x0D,
CliSymbolAsciiEsc = 0x1B,
CliSymbolAsciiUS = 0x1F,
CliSymbolAsciiSpace = 0x20,
CliSymbolAsciiDel = 0x7F,
} CliSymbols;
typedef enum {
CliCommandFlagDefault = 0, /**< Default, loader lock is used */
CliCommandFlagParallelSafe =
(1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */
CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */
CliCommandFlagHidden = (1 << 2), /**< Not shown in `help` */
} CliCommandFlag;
#define RECORD_CLI "cli"

View file

@ -0,0 +1,76 @@
#include "cli_ansi.h"
/**
* @brief Converts a single character representing a special key into the enum
* representation
*/
static CliKey cli_ansi_key_from_mnemonic(char c) {
switch(c) {
case 'A':
return CliKeyUp;
case 'B':
return CliKeyDown;
case 'C':
return CliKeyRight;
case 'D':
return CliKeyLeft;
case 'F':
return CliKeyEnd;
case 'H':
return CliKeyHome;
default:
return CliKeyUnrecognized;
}
}
CliKeyCombo cli_read_ansi_key_combo(Cli* cli) {
char ch = cli_getc(cli);
if(ch != CliKeyEsc)
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = ch,
};
ch = cli_getc(cli);
// ESC ESC -> ESC
if(ch == '\e')
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = '\e',
};
// ESC <char> -> Alt + <char>
if(ch != '[')
return (CliKeyCombo){
.modifiers = CliModKeyAlt,
.key = cli_getc(cli),
};
ch = cli_getc(cli);
// ESC [ 1
if(ch == '1') {
// ESC [ 1 ; <modifier bitfield> <key mnemonic>
if(cli_getc(cli) == ';') {
CliModKey modifiers = (cli_getc(cli) - '0'); // convert following digit to a number
modifiers &= ~1;
return (CliKeyCombo){
.modifiers = modifiers,
.key = cli_ansi_key_from_mnemonic(cli_getc(cli)),
};
}
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = CliKeyUnrecognized,
};
}
// ESC [ <key mnemonic>
return (CliKeyCombo){
.modifiers = CliModKeyNo,
.key = cli_ansi_key_from_mnemonic(ch),
};
}

View file

@ -0,0 +1,94 @@
#pragma once
#include "cli.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ANSI_RESET "\e[0m"
#define ANSI_BOLD "\e[1m"
#define ANSI_FAINT "\e[2m"
#define ANSI_FG_BLACK "\e[30m"
#define ANSI_FG_RED "\e[31m"
#define ANSI_FG_GREEN "\e[32m"
#define ANSI_FG_YELLOW "\e[33m"
#define ANSI_FG_BLUE "\e[34m"
#define ANSI_FG_MAGENTA "\e[35m"
#define ANSI_FG_CYAN "\e[36m"
#define ANSI_FG_WHITE "\e[37m"
#define ANSI_FG_BR_BLACK "\e[90m"
#define ANSI_FG_BR_RED "\e[91m"
#define ANSI_FG_BR_GREEN "\e[92m"
#define ANSI_FG_BR_YELLOW "\e[93m"
#define ANSI_FG_BR_BLUE "\e[94m"
#define ANSI_FG_BR_MAGENTA "\e[95m"
#define ANSI_FG_BR_CYAN "\e[96m"
#define ANSI_FG_BR_WHITE "\e[97m"
#define ANSI_BG_BLACK "\e[40m"
#define ANSI_BG_RED "\e[41m"
#define ANSI_BG_GREEN "\e[42m"
#define ANSI_BG_YELLOW "\e[43m"
#define ANSI_BG_BLUE "\e[44m"
#define ANSI_BG_MAGENTA "\e[45m"
#define ANSI_BG_CYAN "\e[46m"
#define ANSI_BG_WHITE "\e[47m"
#define ANSI_BG_BR_BLACK "\e[100m"
#define ANSI_BG_BR_RED "\e[101m"
#define ANSI_BG_BR_GREEN "\e[102m"
#define ANSI_BG_BR_YELLOW "\e[103m"
#define ANSI_BG_BR_BLUE "\e[104m"
#define ANSI_BG_BR_MAGENTA "\e[105m"
#define ANSI_BG_BR_CYAN "\e[106m"
#define ANSI_BG_BR_WHITE "\e[107m"
#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m"
typedef enum {
CliKeyUnrecognized = 0,
CliKeySOH = 0x01,
CliKeyETX = 0x03,
CliKeyEOT = 0x04,
CliKeyBell = 0x07,
CliKeyBackspace = 0x08,
CliKeyTab = 0x09,
CliKeyLF = 0x0A,
CliKeyCR = 0x0D,
CliKeyETB = 0x17,
CliKeyEsc = 0x1B,
CliKeyUS = 0x1F,
CliKeySpace = 0x20,
CliKeyDEL = 0x7F,
CliKeySpecial = 0x80,
CliKeyLeft,
CliKeyRight,
CliKeyUp,
CliKeyDown,
CliKeyHome,
CliKeyEnd,
} CliKey;
typedef enum {
CliModKeyNo = 0,
CliModKeyAlt = 2,
CliModKeyCtrl = 4,
CliModKeyMeta = 8,
} CliModKey;
typedef struct {
CliModKey modifiers;
CliKey key;
} CliKeyCombo;
/**
* @brief Reads a key or key combination
*/
CliKeyCombo cli_read_ansi_key_combo(Cli* cli);
#ifdef __cplusplus
}
#endif

View file

@ -1,5 +1,6 @@
#include "cli_commands.h"
#include "cli_command_gpio.h"
#include "cli_ansi.h"
#include <core/thread.h>
#include <furi_hal.h>
@ -10,6 +11,7 @@
#include <loader/loader.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include <storage/storage.h>
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d"
@ -52,37 +54,196 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) {
}
}
void cli_command_help(Cli* cli, FuriString* args, void* context) {
// Lil Easter egg :>
void cli_command_neofetch(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
UNUSED(args);
UNUSED(context);
static const char* const neofetch_logo[] = {
" _.-------.._ -,",
" .-\"```\"--..,,_/ /`-, -, \\ ",
" .:\" /:/ /'\\ \\ ,_..., `. | |",
" / ,----/:/ /`\\ _\\~`_-\"` _;",
" ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ",
" | | | 0 | | .-' ,/` /",
" | ,..\\ \\ ,.-\"` ,/` /",
"; : `/`\"\"\\` ,/--==,/-----,",
"| `-...| -.___-Z:_______J...---;",
": ` _-'",
};
#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE
// Determine logo parameters
size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0;
for(size_t i = 0; i < logo_height; i++)
logo_width = MAX(logo_width, strlen(neofetch_logo[i]));
logo_width += 4; // space between logo and info
// Format hostname delimiter
const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr());
char delimiter[64];
memset(delimiter, '-', size_of_hostname);
delimiter[size_of_hostname] = '\0';
// Get heap info
size_t heap_total = memmgr_get_total_heap();
size_t heap_used = heap_total - memmgr_get_free_heap();
uint16_t heap_percent = (100 * heap_used) / heap_total;
// Get storage info
Storage* storage = furi_record_open(RECORD_STORAGE);
uint64_t ext_total, ext_free, ext_used, ext_percent;
storage_common_fs_info(storage, "/ext", &ext_total, &ext_free);
ext_used = ext_total - ext_free;
ext_percent = (100 * ext_used) / ext_total;
ext_used /= 1024 * 1024;
ext_total /= 1024 * 1024;
furi_record_close(RECORD_STORAGE);
// Get battery info
uint16_t charge_percent = furi_hal_power_get_pct();
const char* charge_state;
if(furi_hal_power_is_charging()) {
if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) {
charge_state = "charging";
} else {
charge_state = "charged";
}
} else {
charge_state = "discharging";
}
// Get misc info
uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
const Version* version = version_get();
uint16_t major, minor;
furi_hal_info_get_api_version(&major, &minor);
// Print ASCII art with info
const size_t info_height = 16;
for(size_t i = 0; i < MAX(logo_height, info_height); i++) {
printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : "");
switch(i) {
case 0: // you@<hostname>
printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr());
break;
case 1: // delimiter
printf(ANSI_RESET "%s", delimiter);
break;
case 2: // OS: FURI <edition> <branch> <version> <commit> (SDK <maj>.<min>)
printf(
"OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)",
version_get_version(version),
version_get_gitbranch(version),
version_get_version(version),
version_get_githash(version),
major,
minor);
break;
case 3: // Host: <model> <hostname>
printf(
"Host" ANSI_RESET ": %s %s",
furi_hal_version_get_model_code(),
furi_hal_version_get_device_name_ptr());
break;
case 4: // Kernel: FreeRTOS <maj>.<min>.<build>
printf(
"Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d",
tskKERNEL_VERSION_MAJOR,
tskKERNEL_VERSION_MINOR,
tskKERNEL_VERSION_BUILD);
break;
case 5: // Uptime: ?h?m?s
printf(
"Uptime" ANSI_RESET ": %luh%lum%lus",
uptime / 60 / 60,
uptime / 60 % 60,
uptime % 60);
break;
case 6: // ST7567 128x64 @ 1 bpp in 1.4"
printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\"");
break;
case 7: // DE: GuiSrv
printf("DE" ANSI_RESET ": GuiSrv");
break;
case 8: // Shell: CliSrv
printf("Shell" ANSI_RESET ": CliSrv");
break;
case 9: // CPU: STM32WB55RG @ 64 MHz
printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz");
break;
case 10: // Memory: <used> / <total> B (??%)
printf(
"Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent);
break;
case 11: // Disk (/ext): <used> / <total> MiB (??%)
printf(
"Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)",
ext_used,
ext_total,
ext_percent);
break;
case 12: // Battery: ??% (<state>)
printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state);
break;
case 13: // empty space
break;
case 14: // Colors (line 1)
for(size_t j = 30; j <= 37; j++)
printf("\e[%dm███", j);
break;
case 15: // Colors (line 2)
for(size_t j = 90; j <= 97; j++)
printf("\e[%dm███", j);
break;
default:
break;
}
printf("\r\n");
}
printf(ANSI_RESET);
#undef NEOFETCH_COLOR
}
void cli_command_help(Cli* cli, FuriString* args, void* context) {
UNUSED(context);
printf("Commands available:");
// Command count
const size_t commands_count = CliCommandTree_size(cli->commands);
const size_t commands_count_mid = commands_count / 2 + commands_count % 2;
// Count non-hidden commands
CliCommandTree_it_t it_count;
CliCommandTree_it(it_count, cli->commands);
size_t commands_count = 0;
while(!CliCommandTree_end_p(it_count)) {
if(!(CliCommandTree_cref(it_count)->value_ptr->flags & CliCommandFlagHidden))
commands_count++;
CliCommandTree_next(it_count);
}
// Use 2 iterators from start and middle to show 2 columns
CliCommandTree_it_t it_left;
CliCommandTree_it(it_left, cli->commands);
CliCommandTree_it_t it_right;
CliCommandTree_it(it_right, cli->commands);
for(size_t i = 0; i < commands_count_mid; i++)
CliCommandTree_next(it_right);
// Create iterators starting at different positions
const size_t columns = 3;
const size_t commands_per_column = (commands_count / columns) + (commands_count % columns);
CliCommandTree_it_t iterators[columns];
for(size_t c = 0; c < columns; c++) {
CliCommandTree_it(iterators[c], cli->commands);
for(size_t i = 0; i < c * commands_per_column; i++)
CliCommandTree_next(iterators[c]);
}
// Iterate throw tree
for(size_t i = 0; i < commands_count_mid; i++) {
// Print commands
for(size_t r = 0; r < commands_per_column; r++) {
printf("\r\n");
// Left Column
if(!CliCommandTree_end_p(it_left)) {
printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr));
CliCommandTree_next(it_left);
for(size_t c = 0; c < columns; c++) {
if(!CliCommandTree_end_p(iterators[c])) {
const CliCommandTree_itref_t* item = CliCommandTree_cref(iterators[c]);
if(!(item->value_ptr->flags & CliCommandFlagHidden)) {
printf("%-30s", furi_string_get_cstr(*item->key_ptr));
}
CliCommandTree_next(iterators[c]);
}
}
// Right Column
if(!CliCommandTree_end_p(it_right)) {
printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr));
CliCommandTree_next(it_right);
}
};
if(furi_string_size(args) > 0) {
cli_nl(cli);
@ -391,16 +552,18 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
int interval = 1000;
args_read_int_and_trim(args, &interval);
if(interval) printf("\e[2J\e[?25l"); // Clear display, hide cursor
FuriThreadList* thread_list = furi_thread_list_alloc();
while(!cli_cmd_interrupt_received(cli)) {
uint32_t tick = furi_get_tick();
furi_thread_enumerate(thread_list);
if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0
if(interval) printf("\e[0;0f"); // Return to 0,0
uint32_t uptime = tick / furi_kernel_get_tick_frequency();
printf(
"Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n",
"\rThreads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\e[0K\r\n",
furi_thread_list_size(thread_list),
(double)furi_thread_list_get_isr_time(thread_list),
uptime / 60 / 60,
@ -408,14 +571,14 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
uptime % 60);
printf(
"Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n",
"\rHeap: total %zu, free %zu, minimum %zu, max block %zu\e[0K\r\n\r\n",
memmgr_get_total_heap(),
memmgr_get_free_heap(),
memmgr_get_minimum_free_heap(),
memmgr_heap_get_max_free_block());
printf(
"%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n",
"\r%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\e[0K\r\n",
"AppID",
"Name",
"State",
@ -429,7 +592,7 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) {
const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i);
printf(
"%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n",
"\r%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\e[0K\r\n",
item->app_id,
item->name,
item->state,
@ -448,6 +611,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) {
}
}
furi_thread_list_free(thread_list);
if(interval) printf("\e[?25h"); // Show cursor
}
void cli_command_free(Cli* cli, FuriString* args, void* context) {
@ -499,6 +664,12 @@ void cli_commands_init(Cli* cli) {
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL);
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
cli_add_command(
cli,
"neofetch",
CliCommandFlagParallelSafe | CliCommandFlagHidden,
cli_command_neofetch,
NULL);
cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);

View file

@ -3,6 +3,7 @@
#include <lib/toolbox/args.h>
#include <cli/cli.h>
#include <cli/cli_ansi.h>
void crypto_cli_print_usage(void) {
printf("Usage:\r\n");
@ -45,14 +46,14 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) {
input = furi_string_alloc();
char c;
while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
if(c == CliSymbolAsciiETX) {
if(c == CliKeyETX) {
printf("\r\n");
break;
} else if(c >= 0x20 && c < 0x7F) {
putc(c, stdout);
fflush(stdout);
furi_string_push_back(input, c);
} else if(c == CliSymbolAsciiCR) {
} else if(c == CliKeyCR) {
printf("\r\n");
furi_string_cat(input, "\r\n");
}
@ -120,14 +121,14 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) {
hex_input = furi_string_alloc();
char c;
while(cli_read(cli, (uint8_t*)&c, 1) == 1) {
if(c == CliSymbolAsciiETX) {
if(c == CliKeyETX) {
printf("\r\n");
break;
} else if(c >= 0x20 && c < 0x7F) {
putc(c, stdout);
fflush(stdout);
furi_string_push_back(hex_input, c);
} else if(c == CliSymbolAsciiCR) {
} else if(c == CliKeyCR) {
printf("\r\n");
}
}

View file

@ -54,11 +54,14 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow
ret = file_browser_context->result;
view_holder_set_view(view_holder, NULL);
view_holder_free(view_holder);
file_browser_stop(file_browser);
file_browser_free(file_browser);
view_holder_free(view_holder);
api_lock_free(file_browser_context->lock);
free(file_browser_context);
furi_record_close(RECORD_GUI);
return ret;

View file

@ -308,12 +308,14 @@ static void loader_applications_closed_callback(void* context) {
furi_message_queue_put(loader->queue, &message, FuriWaitForever);
}
static void loader_thread_state_callback(FuriThreadState thread_state, void* context) {
static void
loader_thread_state_callback(FuriThread* thread, FuriThreadState thread_state, void* context) {
UNUSED(thread);
furi_assert(context);
if(thread_state == FuriThreadStateStopped) {
Loader* loader = context;
if(thread_state == FuriThreadStateStopped) {
LoaderMessage message;
message.type = LoaderMessageTypeAppClosed;
furi_message_queue_put(loader->queue, &message, FuriWaitForever);

View file

@ -104,19 +104,12 @@ static int32_t region_load_file(void* context) {
return 0;
}
static void region_loader_pending_callback(void* context, uint32_t arg) {
UNUSED(arg);
FuriThread* loader = context;
furi_thread_join(loader);
furi_thread_free(loader);
}
static void region_loader_state_callback(FuriThreadState state, void* context) {
static void
region_loader_release_callback(FuriThread* thread, FuriThreadState state, void* context) {
UNUSED(context);
if(state == FuriThreadStateStopped) {
furi_timer_pending_callback(region_loader_pending_callback, furi_thread_get_current(), 0);
furi_thread_free(thread);
}
}
@ -126,7 +119,7 @@ static void region_storage_callback(const void* message, void* context) {
if(event->type == StorageEventTypeCardMount) {
FuriThread* loader = furi_thread_alloc_ex(NULL, 2048, region_load_file, NULL);
furi_thread_set_state_callback(loader, region_loader_state_callback);
furi_thread_set_state_callback(loader, region_loader_release_callback);
furi_thread_start(loader);
}
}

View file

@ -373,8 +373,10 @@ static void rpc_session_thread_pending_callback(void* context, uint32_t arg) {
free(session);
}
static void rpc_session_thread_state_callback(FuriThreadState thread_state, void* context) {
if(thread_state == FuriThreadStateStopped) {
static void
rpc_session_thread_state_callback(FuriThread* thread, FuriThreadState state, void* context) {
UNUSED(thread);
if(state == FuriThreadStateStopped) {
furi_timer_pending_callback(rpc_session_thread_pending_callback, context, 0);
}
}

View file

@ -2,6 +2,7 @@
#include <furi_hal.h>
#include <cli/cli.h>
#include <cli/cli_ansi.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/dir_walk.h>
#include <lib/toolbox/md5_calc.h>
@ -224,7 +225,7 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) {
while(true) {
uint8_t symbol = cli_getc(cli);
if(symbol == CliSymbolAsciiETX) {
if(symbol == CliKeyETX) {
size_t write_size = read_index % buffer_size;
if(write_size > 0) {

View file

@ -0,0 +1,12 @@
App(
appid="bad_ble",
name="Bad BLE",
apptype=FlipperAppType.EXTERNAL,
entry_point="bad_ble_app",
stack_size=2 * 1024,
icon="A_BadUsb_14",
fap_libs=["assets", "ble_profile"],
fap_icon="icon.png",
fap_icon_assets="assets",
fap_category="Bluetooth",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

View file

@ -0,0 +1,196 @@
#include "bad_ble_app_i.h"
#include <furi.h>
#include <furi_hal.h>
#include <storage/storage.h>
#include <lib/toolbox/path.h>
#include <flipper_format/flipper_format.h>
#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/.badble.settings"
#define BAD_BLE_SETTINGS_FILE_TYPE "Flipper BadBLE Settings File"
#define BAD_BLE_SETTINGS_VERSION 1
#define BAD_BLE_SETTINGS_DEFAULT_LAYOUT BAD_BLE_APP_PATH_LAYOUT_FOLDER "/en-US.kl"
static bool bad_ble_app_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
BadBleApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool bad_ble_app_back_event_callback(void* context) {
furi_assert(context);
BadBleApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void bad_ble_app_tick_event_callback(void* context) {
furi_assert(context);
BadBleApp* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
static void bad_ble_load_settings(BadBleApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff = flipper_format_file_alloc(storage);
bool state = false;
FuriString* temp_str = furi_string_alloc();
uint32_t version = 0;
if(flipper_format_file_open_existing(fff, BAD_BLE_SETTINGS_PATH)) {
do {
if(!flipper_format_read_header(fff, temp_str, &version)) break;
if((strcmp(furi_string_get_cstr(temp_str), BAD_BLE_SETTINGS_FILE_TYPE) != 0) ||
(version != BAD_BLE_SETTINGS_VERSION))
break;
if(!flipper_format_read_string(fff, "layout", temp_str)) break;
state = true;
} while(0);
}
flipper_format_free(fff);
furi_record_close(RECORD_STORAGE);
if(state) {
furi_string_set(app->keyboard_layout, temp_str);
Storage* fs_api = furi_record_open(RECORD_STORAGE);
FileInfo layout_file_info;
FS_Error file_check_err = storage_common_stat(
fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info);
furi_record_close(RECORD_STORAGE);
if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) {
furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT);
}
} else {
furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT);
}
furi_string_free(temp_str);
}
static void bad_ble_save_settings(BadBleApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff = flipper_format_file_alloc(storage);
if(flipper_format_file_open_always(fff, BAD_BLE_SETTINGS_PATH)) {
do {
if(!flipper_format_write_header_cstr(
fff, BAD_BLE_SETTINGS_FILE_TYPE, BAD_BLE_SETTINGS_VERSION))
break;
if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break;
} while(0);
}
flipper_format_free(fff);
furi_record_close(RECORD_STORAGE);
}
BadBleApp* bad_ble_app_alloc(char* arg) {
BadBleApp* app = malloc(sizeof(BadBleApp));
app->bad_ble_script = NULL;
app->file_path = furi_string_alloc();
app->keyboard_layout = furi_string_alloc();
if(arg && strlen(arg)) {
furi_string_set(app->file_path, arg);
}
bad_ble_load_settings(app);
app->gui = furi_record_open(RECORD_GUI);
app->notifications = furi_record_open(RECORD_NOTIFICATION);
app->dialogs = furi_record_open(RECORD_DIALOGS);
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&bad_ble_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, bad_ble_app_tick_event_callback, 500);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, bad_ble_app_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, bad_ble_app_back_event_callback);
// Custom Widget
app->widget = widget_alloc();
view_dispatcher_add_view(
app->view_dispatcher, BadBleAppViewWidget, widget_get_view(app->widget));
// Popup
app->popup = popup_alloc();
view_dispatcher_add_view(app->view_dispatcher, BadBleAppViewPopup, popup_get_view(app->popup));
app->var_item_list = variable_item_list_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
BadBleAppViewConfig,
variable_item_list_get_view(app->var_item_list));
app->bad_ble_view = bad_ble_view_alloc();
view_dispatcher_add_view(
app->view_dispatcher, BadBleAppViewWork, bad_ble_view_get_view(app->bad_ble_view));
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
if(!furi_string_empty(app->file_path)) {
scene_manager_next_scene(app->scene_manager, BadBleSceneWork);
} else {
furi_string_set(app->file_path, BAD_BLE_APP_BASE_FOLDER);
scene_manager_next_scene(app->scene_manager, BadBleSceneFileSelect);
}
return app;
}
void bad_ble_app_free(BadBleApp* app) {
furi_assert(app);
if(app->bad_ble_script) {
bad_ble_script_close(app->bad_ble_script);
app->bad_ble_script = NULL;
}
// Views
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWork);
bad_ble_view_free(app->bad_ble_view);
// Custom Widget
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWidget);
widget_free(app->widget);
// Popup
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewPopup);
popup_free(app->popup);
// Config menu
view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig);
variable_item_list_free(app->var_item_list);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
// Close records
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_DIALOGS);
bad_ble_save_settings(app);
furi_string_free(app->file_path);
furi_string_free(app->keyboard_layout);
free(app);
}
int32_t bad_ble_app(void* p) {
BadBleApp* bad_ble_app = bad_ble_app_alloc((char*)p);
view_dispatcher_run(bad_ble_app->view_dispatcher);
bad_ble_app_free(bad_ble_app);
return 0;
}

View file

@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef struct BadBleApp BadBleApp;
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,53 @@
#pragma once
#include "bad_ble_app.h"
#include "scenes/bad_ble_scene.h"
#include "helpers/ducky_script.h"
#include "helpers/bad_ble_hid.h"
#include <gui/gui.h>
#include <assets_icons.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <dialogs/dialogs.h>
#include <notification/notification_messages.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include <gui/modules/popup.h>
#include "views/bad_ble_view.h"
#define BAD_BLE_APP_BASE_FOLDER EXT_PATH("badusb")
#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/assets/layouts"
#define BAD_BLE_APP_SCRIPT_EXTENSION ".txt"
#define BAD_BLE_APP_LAYOUT_EXTENSION ".kl"
typedef enum {
BadBleAppErrorNoFiles,
BadBleAppErrorCloseRpc,
} BadBleAppError;
struct BadBleApp {
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
NotificationApp* notifications;
DialogsApp* dialogs;
Widget* widget;
Popup* popup;
VariableItemList* var_item_list;
BadBleAppError error;
FuriString* file_path;
FuriString* keyboard_layout;
BadBle* bad_ble_view;
BadBleScript* bad_ble_script;
BadBleHidInterface interface;
};
typedef enum {
BadBleAppViewWidget,
BadBleAppViewPopup,
BadBleAppViewWork,
BadBleAppViewConfig,
} BadBleAppView;

View file

@ -0,0 +1,157 @@
#include "bad_ble_hid.h"
#include <extra_profiles/hid_profile.h>
#include <bt/bt_service/bt.h>
#include <storage/storage.h>
#define TAG "BadBLE HID"
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
typedef struct {
Bt* bt;
FuriHalBleProfileBase* profile;
HidStateCallback state_callback;
void* callback_context;
bool is_connected;
} BleHidInstance;
static const BleProfileHidParams ble_hid_params = {
.device_name_prefix = "BadBLE",
.mac_xor = 0x0002,
};
static void hid_ble_connection_status_callback(BtStatus status, void* context) {
furi_assert(context);
BleHidInstance* ble_hid = context;
ble_hid->is_connected = (status == BtStatusConnected);
if(ble_hid->state_callback) {
ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context);
}
}
void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) {
UNUSED(hid_cfg);
BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance));
ble_hid->bt = furi_record_open(RECORD_BT);
bt_disconnect(ble_hid->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params);
furi_check(ble_hid->profile);
furi_hal_bt_start_advertising();
bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid);
return ble_hid;
}
void hid_ble_deinit(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
bt_set_status_changed_callback(ble_hid->bt, NULL, NULL);
bt_disconnect(ble_hid->bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_default_path(ble_hid->bt);
furi_check(bt_profile_restore_default(ble_hid->bt));
furi_record_close(RECORD_BT);
free(ble_hid);
}
void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
ble_hid->state_callback = cb;
ble_hid->callback_context = context;
}
bool hid_ble_is_connected(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_hid->is_connected;
}
bool hid_ble_kb_press(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_kb_press(ble_hid->profile, button);
}
bool hid_ble_kb_release(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_kb_release(ble_hid->profile, button);
}
bool hid_ble_consumer_press(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_consumer_key_press(ble_hid->profile, button);
}
bool hid_ble_consumer_release(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_consumer_key_release(ble_hid->profile, button);
}
bool hid_ble_release_all(void* inst) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
bool state = ble_profile_hid_kb_release_all(ble_hid->profile);
state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile);
return state;
}
uint8_t hid_ble_get_led_state(void* inst) {
UNUSED(inst);
FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented");
return 0;
}
static const BadBleHidApi hid_api_ble = {
.init = hid_ble_init,
.deinit = hid_ble_deinit,
.set_state_callback = hid_ble_set_state_callback,
.is_connected = hid_ble_is_connected,
.kb_press = hid_ble_kb_press,
.kb_release = hid_ble_kb_release,
.consumer_press = hid_ble_consumer_press,
.consumer_release = hid_ble_consumer_release,
.release_all = hid_ble_release_all,
.get_led_state = hid_ble_get_led_state,
};
const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface) {
UNUSED(interface);
return &hid_api_ble;
}
void bad_ble_hid_ble_remove_pairing(void) {
Bt* bt = furi_record_open(RECORD_BT);
bt_disconnect(bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
furi_hal_bt_stop_advertising();
bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
bt_forget_bonded_devices(bt);
// Wait 2nd core to update nvm storage
furi_delay_ms(200);
bt_keys_storage_set_default_path(bt);
furi_check(bt_profile_restore_default(bt));
furi_record_close(RECORD_BT);
}

View file

@ -0,0 +1,34 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <furi.h>
#include <furi_hal.h>
typedef enum {
BadBleHidInterfaceBle,
} BadBleHidInterface;
typedef struct {
void* (*init)(FuriHalUsbHidConfig* hid_cfg);
void (*deinit)(void* inst);
void (*set_state_callback)(void* inst, HidStateCallback cb, void* context);
bool (*is_connected)(void* inst);
bool (*kb_press)(void* inst, uint16_t button);
bool (*kb_release)(void* inst, uint16_t button);
bool (*consumer_press)(void* inst, uint16_t button);
bool (*consumer_release)(void* inst, uint16_t button);
bool (*release_all)(void* inst);
uint8_t (*get_led_state)(void* inst);
} BadBleHidApi;
const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface);
void bad_ble_hid_ble_remove_pairing(void);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,716 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include <storage/storage.h>
#include "ducky_script.h"
#include "ducky_script_i.h"
#include <dolphin/dolphin.h>
#define TAG "BadBle"
#define WORKER_TAG TAG "Worker"
#define BADUSB_ASCII_TO_KEY(script, x) \
(((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
typedef enum {
WorkerEvtStartStop = (1 << 0),
WorkerEvtPauseResume = (1 << 1),
WorkerEvtEnd = (1 << 2),
WorkerEvtConnect = (1 << 3),
WorkerEvtDisconnect = (1 << 4),
} WorkerEvtFlags;
static const char ducky_cmd_id[] = {"ID"};
static const uint8_t numpad_keys[10] = {
HID_KEYPAD_0,
HID_KEYPAD_1,
HID_KEYPAD_2,
HID_KEYPAD_3,
HID_KEYPAD_4,
HID_KEYPAD_5,
HID_KEYPAD_6,
HID_KEYPAD_7,
HID_KEYPAD_8,
HID_KEYPAD_9,
};
uint32_t ducky_get_command_len(const char* line) {
uint32_t len = strlen(line);
for(uint32_t i = 0; i < len; i++) {
if(line[i] == ' ') return i;
}
return 0;
}
bool ducky_is_line_end(const char chr) {
return (chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n');
}
uint16_t ducky_get_keycode(BadBleScript* bad_ble, const char* param, bool accept_chars) {
uint16_t keycode = ducky_get_keycode_by_name(param);
if(keycode != HID_KEYBOARD_NONE) {
return keycode;
}
if((accept_chars) && (strlen(param) > 0)) {
return BADUSB_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF;
}
return 0;
}
bool ducky_get_number(const char* param, uint32_t* val) {
uint32_t value = 0;
if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) {
*val = value;
return true;
}
return false;
}
void ducky_numlock_on(BadBleScript* bad_ble) {
if((bad_ble->hid->get_led_state(bad_ble->hid_inst) & HID_KB_LED_NUM) == 0) {
bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK);
bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK);
}
}
bool ducky_numpad_press(BadBleScript* bad_ble, const char num) {
if((num < '0') || (num > '9')) return false;
uint16_t key = numpad_keys[num - '0'];
bad_ble->hid->kb_press(bad_ble->hid_inst, key);
bad_ble->hid->kb_release(bad_ble->hid_inst, key);
return true;
}
bool ducky_altchar(BadBleScript* bad_ble, const char* charcode) {
uint8_t i = 0;
bool state = false;
bad_ble->hid->kb_press(bad_ble->hid_inst, KEY_MOD_LEFT_ALT);
while(!ducky_is_line_end(charcode[i])) {
state = ducky_numpad_press(bad_ble, charcode[i]);
if(state == false) break;
i++;
}
bad_ble->hid->kb_release(bad_ble->hid_inst, KEY_MOD_LEFT_ALT);
return state;
}
bool ducky_altstring(BadBleScript* bad_ble, const char* param) {
uint32_t i = 0;
bool state = false;
while(param[i] != '\0') {
if((param[i] < ' ') || (param[i] > '~')) {
i++;
continue; // Skip non-printable chars
}
char temp_str[4];
snprintf(temp_str, 4, "%u", param[i]);
state = ducky_altchar(bad_ble, temp_str);
if(state == false) break;
i++;
}
return state;
}
int32_t ducky_error(BadBleScript* bad_ble, const char* text, ...) {
va_list args;
va_start(args, text);
vsnprintf(bad_ble->st.error, sizeof(bad_ble->st.error), text, args);
va_end(args);
return SCRIPT_STATE_ERROR;
}
bool ducky_string(BadBleScript* bad_ble, const char* param) {
uint32_t i = 0;
while(param[i] != '\0') {
if(param[i] != '\n') {
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, param[i]);
if(keycode != HID_KEYBOARD_NONE) {
bad_ble->hid->kb_press(bad_ble->hid_inst, keycode);
bad_ble->hid->kb_release(bad_ble->hid_inst, keycode);
}
} else {
bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
}
i++;
}
bad_ble->stringdelay = 0;
return true;
}
static bool ducky_string_next(BadBleScript* bad_ble) {
if(bad_ble->string_print_pos >= furi_string_size(bad_ble->string_print)) {
return true;
}
char print_char = furi_string_get_char(bad_ble->string_print, bad_ble->string_print_pos);
if(print_char != '\n') {
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, print_char);
if(keycode != HID_KEYBOARD_NONE) {
bad_ble->hid->kb_press(bad_ble->hid_inst, keycode);
bad_ble->hid->kb_release(bad_ble->hid_inst, keycode);
}
} else {
bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN);
}
bad_ble->string_print_pos++;
return false;
}
static int32_t ducky_parse_line(BadBleScript* bad_ble, FuriString* line) {
uint32_t line_len = furi_string_size(line);
const char* line_tmp = furi_string_get_cstr(line);
if(line_len == 0) {
return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
}
FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
// Ducky Lang Functions
int32_t cmd_result = ducky_execute_cmd(bad_ble, line_tmp);
if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) {
return cmd_result;
}
// Special keys + modifiers
uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_ble, "No keycode defined for %s", line_tmp);
}
if((key & 0xFF00) != 0) {
// It's a modifier key
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
key |= ducky_get_keycode(bad_ble, line_tmp, true);
}
bad_ble->hid->kb_press(bad_ble->hid_inst, key);
bad_ble->hid->kb_release(bad_ble->hid_inst, key);
return 0;
}
static bool ducky_set_usb_id(BadBleScript* bad_ble, const char* line) {
if(sscanf(line, "%lX:%lX", &bad_ble->hid_cfg.vid, &bad_ble->hid_cfg.pid) == 2) {
bad_ble->hid_cfg.manuf[0] = '\0';
bad_ble->hid_cfg.product[0] = '\0';
uint8_t id_len = ducky_get_command_len(line);
if(!ducky_is_line_end(line[id_len + 1])) {
sscanf(
&line[id_len + 1],
"%31[^\r\n:]:%31[^\r\n]",
bad_ble->hid_cfg.manuf,
bad_ble->hid_cfg.product);
}
FURI_LOG_D(
WORKER_TAG,
"set id: %04lX:%04lX mfr:%s product:%s",
bad_ble->hid_cfg.vid,
bad_ble->hid_cfg.pid,
bad_ble->hid_cfg.manuf,
bad_ble->hid_cfg.product);
return true;
}
return false;
}
static void bad_ble_hid_state_callback(bool state, void* context) {
furi_assert(context);
BadBleScript* bad_ble = context;
if(state == true) {
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtConnect);
} else {
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtDisconnect);
}
}
static bool ducky_script_preload(BadBleScript* bad_ble, File* script_file) {
uint8_t ret = 0;
uint32_t line_len = 0;
furi_string_reset(bad_ble->line);
do {
ret = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN);
for(uint16_t i = 0; i < ret; i++) {
if(bad_ble->file_buf[i] == '\n' && line_len > 0) {
bad_ble->st.line_nb++;
line_len = 0;
} else {
if(bad_ble->st.line_nb == 0) { // Save first line
furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]);
}
line_len++;
}
}
if(storage_file_eof(script_file)) {
if(line_len > 0) {
bad_ble->st.line_nb++;
break;
}
}
} while(ret > 0);
const char* line_tmp = furi_string_get_cstr(bad_ble->line);
bool id_set = false; // Looking for ID command at first line
if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
id_set = ducky_set_usb_id(bad_ble, &line_tmp[strlen(ducky_cmd_id) + 1]);
}
if(id_set) {
bad_ble->hid_inst = bad_ble->hid->init(&bad_ble->hid_cfg);
} else {
bad_ble->hid_inst = bad_ble->hid->init(NULL);
}
bad_ble->hid->set_state_callback(bad_ble->hid_inst, bad_ble_hid_state_callback, bad_ble);
storage_file_seek(script_file, 0, true);
furi_string_reset(bad_ble->line);
return true;
}
static int32_t ducky_script_execute_next(BadBleScript* bad_ble, File* script_file) {
int32_t delay_val = 0;
if(bad_ble->repeat_cnt > 0) {
bad_ble->repeat_cnt--;
delay_val = ducky_parse_line(bad_ble, bad_ble->line_prev);
if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
return 0;
} else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays
return delay_val;
} else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button
return delay_val;
} else if(delay_val < 0) { // Script error
bad_ble->st.error_line = bad_ble->st.line_cur - 1;
FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur - 1U);
return SCRIPT_STATE_ERROR;
} else {
return delay_val + bad_ble->defdelay;
}
}
furi_string_set(bad_ble->line_prev, bad_ble->line);
furi_string_reset(bad_ble->line);
while(1) {
if(bad_ble->buf_len == 0) {
bad_ble->buf_len = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN);
if(storage_file_eof(script_file)) {
if((bad_ble->buf_len < FILE_BUFFER_LEN) && (bad_ble->file_end == false)) {
bad_ble->file_buf[bad_ble->buf_len] = '\n';
bad_ble->buf_len++;
bad_ble->file_end = true;
}
}
bad_ble->buf_start = 0;
if(bad_ble->buf_len == 0) return SCRIPT_STATE_END;
}
for(uint8_t i = bad_ble->buf_start; i < (bad_ble->buf_start + bad_ble->buf_len); i++) {
if(bad_ble->file_buf[i] == '\n' && furi_string_size(bad_ble->line) > 0) {
bad_ble->st.line_cur++;
bad_ble->buf_len = bad_ble->buf_len + bad_ble->buf_start - (i + 1);
bad_ble->buf_start = i + 1;
furi_string_trim(bad_ble->line);
delay_val = ducky_parse_line(bad_ble, bad_ble->line);
if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
return 0;
} else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays
return delay_val;
} else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button
return delay_val;
} else if(delay_val < 0) {
bad_ble->st.error_line = bad_ble->st.line_cur;
FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur);
return SCRIPT_STATE_ERROR;
} else {
return delay_val + bad_ble->defdelay;
}
} else {
furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]);
}
}
bad_ble->buf_len = 0;
if(bad_ble->file_end) return SCRIPT_STATE_END;
}
return 0;
}
static uint32_t bad_ble_flags_get(uint32_t flags_mask, uint32_t timeout) {
uint32_t flags = furi_thread_flags_get();
furi_check((flags & FuriFlagError) == 0);
if(flags == 0) {
flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout);
furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout));
} else {
uint32_t state = furi_thread_flags_clear(flags);
furi_check((state & FuriFlagError) == 0);
}
return flags;
}
static int32_t bad_ble_worker(void* context) {
BadBleScript* bad_ble = context;
BadBleWorkerState worker_state = BadBleStateInit;
BadBleWorkerState pause_state = BadBleStateRunning;
int32_t delay_val = 0;
FURI_LOG_I(WORKER_TAG, "Init");
File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
bad_ble->line = furi_string_alloc();
bad_ble->line_prev = furi_string_alloc();
bad_ble->string_print = furi_string_alloc();
while(1) {
if(worker_state == BadBleStateInit) { // State: initialization
if(storage_file_open(
script_file,
furi_string_get_cstr(bad_ble->file_path),
FSAM_READ,
FSOM_OPEN_EXISTING)) {
if((ducky_script_preload(bad_ble, script_file)) && (bad_ble->st.line_nb > 0)) {
if(bad_ble->hid->is_connected(bad_ble->hid_inst)) {
worker_state = BadBleStateIdle; // Ready to run
} else {
worker_state = BadBleStateNotConnected; // USB not connected
}
} else {
worker_state = BadBleStateScriptError; // Script preload error
}
} else {
FURI_LOG_E(WORKER_TAG, "File open error");
worker_state = BadBleStateFileError; // File open error
}
bad_ble->st.state = worker_state;
} else if(worker_state == BadBleStateNotConnected) { // State: USB not connected
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop,
FuriWaitForever);
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtConnect) {
worker_state = BadBleStateIdle; // Ready to run
} else if(flags & WorkerEvtStartStop) {
worker_state = BadBleStateWillRun; // Will run when USB is connected
}
bad_ble->st.state = worker_state;
} else if(worker_state == BadBleStateIdle) { // State: ready to start
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever);
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtStartStop) { // Start executing script
dolphin_deed(DolphinDeedBadUsbPlayScript);
delay_val = 0;
bad_ble->buf_len = 0;
bad_ble->st.line_cur = 0;
bad_ble->defdelay = 0;
bad_ble->stringdelay = 0;
bad_ble->defstringdelay = 0;
bad_ble->repeat_cnt = 0;
bad_ble->key_hold_nb = 0;
bad_ble->file_end = false;
storage_file_seek(script_file, 0, true);
worker_state = BadBleStateRunning;
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadBleStateNotConnected; // USB disconnected
}
bad_ble->st.state = worker_state;
} else if(worker_state == BadBleStateWillRun) { // State: start on connection
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever);
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtConnect) { // Start executing script
dolphin_deed(DolphinDeedBadUsbPlayScript);
delay_val = 0;
bad_ble->buf_len = 0;
bad_ble->st.line_cur = 0;
bad_ble->defdelay = 0;
bad_ble->stringdelay = 0;
bad_ble->defstringdelay = 0;
bad_ble->repeat_cnt = 0;
bad_ble->file_end = false;
storage_file_seek(script_file, 0, true);
// extra time for PC to recognize Flipper as keyboard
flags = furi_thread_flags_wait(
WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop,
FuriFlagWaitAny | FuriFlagNoClear,
1500);
if(flags == (unsigned)FuriFlagErrorTimeout) {
// If nothing happened - start script execution
worker_state = BadBleStateRunning;
} else if(flags & WorkerEvtStartStop) {
worker_state = BadBleStateIdle;
furi_thread_flags_clear(WorkerEvtStartStop);
}
} else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution
worker_state = BadBleStateNotConnected;
}
bad_ble->st.state = worker_state;
} else if(worker_state == BadBleStateRunning) { // State: running
uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
uint32_t flags = furi_thread_flags_wait(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
FuriFlagWaitAny,
delay_cur);
delay_val -= delay_cur;
if(!(flags & FuriFlagError)) {
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtStartStop) {
worker_state = BadBleStateIdle; // Stop executing script
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadBleStateNotConnected; // USB disconnected
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtPauseResume) {
pause_state = BadBleStateRunning;
worker_state = BadBleStatePaused; // Pause
}
bad_ble->st.state = worker_state;
continue;
} else if(
(flags == (unsigned)FuriFlagErrorTimeout) ||
(flags == (unsigned)FuriFlagErrorResource)) {
if(delay_val > 0) {
bad_ble->st.delay_remain--;
continue;
}
bad_ble->st.state = BadBleStateRunning;
delay_val = ducky_script_execute_next(bad_ble, script_file);
if(delay_val == SCRIPT_STATE_ERROR) { // Script error
delay_val = 0;
worker_state = BadBleStateScriptError;
bad_ble->st.state = worker_state;
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(delay_val == SCRIPT_STATE_END) { // End of script
delay_val = 0;
worker_state = BadBleStateIdle;
bad_ble->st.state = BadBleStateDone;
bad_ble->hid->release_all(bad_ble->hid_inst);
continue;
} else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays
delay_val = bad_ble->defdelay;
bad_ble->string_print_pos = 0;
worker_state = BadBleStateStringDelay;
} else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input
worker_state = BadBleStateWaitForBtn;
bad_ble->st.state = BadBleStateWaitForBtn; // Show long delays
} else if(delay_val > 1000) {
bad_ble->st.state = BadBleStateDelay; // Show long delays
bad_ble->st.delay_remain = delay_val / 1000;
}
} else {
furi_check((flags & FuriFlagError) == 0);
}
} else if(worker_state == BadBleStateWaitForBtn) { // State: Wait for button Press
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
FuriWaitForever);
if(!(flags & FuriFlagError)) {
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtStartStop) {
delay_val = 0;
worker_state = BadBleStateRunning;
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadBleStateNotConnected; // USB disconnected
bad_ble->hid->release_all(bad_ble->hid_inst);
}
bad_ble->st.state = worker_state;
continue;
}
} else if(worker_state == BadBleStatePaused) { // State: Paused
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
FuriWaitForever);
if(!(flags & FuriFlagError)) {
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtStartStop) {
worker_state = BadBleStateIdle; // Stop executing script
bad_ble->st.state = worker_state;
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadBleStateNotConnected; // USB disconnected
bad_ble->st.state = worker_state;
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtPauseResume) {
if(pause_state == BadBleStateRunning) {
if(delay_val > 0) {
bad_ble->st.state = BadBleStateDelay;
bad_ble->st.delay_remain = delay_val / 1000;
} else {
bad_ble->st.state = BadBleStateRunning;
delay_val = 0;
}
worker_state = BadBleStateRunning; // Resume
} else if(pause_state == BadBleStateStringDelay) {
bad_ble->st.state = BadBleStateRunning;
worker_state = BadBleStateStringDelay; // Resume
}
}
continue;
}
} else if(worker_state == BadBleStateStringDelay) { // State: print string with delays
uint32_t delay = (bad_ble->stringdelay == 0) ? bad_ble->defstringdelay :
bad_ble->stringdelay;
uint32_t flags = bad_ble_flags_get(
WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect,
delay);
if(!(flags & FuriFlagError)) {
if(flags & WorkerEvtEnd) {
break;
} else if(flags & WorkerEvtStartStop) {
worker_state = BadBleStateIdle; // Stop executing script
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtDisconnect) {
worker_state = BadBleStateNotConnected; // USB disconnected
bad_ble->hid->release_all(bad_ble->hid_inst);
} else if(flags & WorkerEvtPauseResume) {
pause_state = BadBleStateStringDelay;
worker_state = BadBleStatePaused; // Pause
}
bad_ble->st.state = worker_state;
continue;
} else if(
(flags == (unsigned)FuriFlagErrorTimeout) ||
(flags == (unsigned)FuriFlagErrorResource)) {
bool string_end = ducky_string_next(bad_ble);
if(string_end) {
bad_ble->stringdelay = 0;
worker_state = BadBleStateRunning;
}
} else {
furi_check((flags & FuriFlagError) == 0);
}
} else if(
(worker_state == BadBleStateFileError) ||
(worker_state == BadBleStateScriptError)) { // State: error
uint32_t flags =
bad_ble_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command
if(flags & WorkerEvtEnd) {
break;
}
}
}
bad_ble->hid->set_state_callback(bad_ble->hid_inst, NULL, NULL);
bad_ble->hid->deinit(bad_ble->hid_inst);
storage_file_close(script_file);
storage_file_free(script_file);
furi_string_free(bad_ble->line);
furi_string_free(bad_ble->line_prev);
furi_string_free(bad_ble->string_print);
FURI_LOG_I(WORKER_TAG, "End");
return 0;
}
static void bad_ble_script_set_default_keyboard_layout(BadBleScript* bad_ble) {
furi_assert(bad_ble);
memset(bad_ble->layout, HID_KEYBOARD_NONE, sizeof(bad_ble->layout));
memcpy(bad_ble->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_ble->layout)));
}
BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface) {
furi_assert(file_path);
BadBleScript* bad_ble = malloc(sizeof(BadBleScript));
bad_ble->file_path = furi_string_alloc();
furi_string_set(bad_ble->file_path, file_path);
bad_ble_script_set_default_keyboard_layout(bad_ble);
bad_ble->st.state = BadBleStateInit;
bad_ble->st.error[0] = '\0';
bad_ble->hid = bad_ble_hid_get_interface(interface);
bad_ble->thread = furi_thread_alloc_ex("BadBleWorker", 2048, bad_ble_worker, bad_ble);
furi_thread_start(bad_ble->thread);
return bad_ble;
} //-V773
void bad_ble_script_close(BadBleScript* bad_ble) {
furi_assert(bad_ble);
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtEnd);
furi_thread_join(bad_ble->thread);
furi_thread_free(bad_ble->thread);
furi_string_free(bad_ble->file_path);
free(bad_ble);
}
void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path) {
furi_assert(bad_ble);
if((bad_ble->st.state == BadBleStateRunning) || (bad_ble->st.state == BadBleStateDelay)) {
// do not update keyboard layout while a script is running
return;
}
File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
if(!furi_string_empty(layout_path)) { //-V1051
if(storage_file_open(
layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
uint16_t layout[128];
if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
memcpy(bad_ble->layout, layout, sizeof(layout));
}
}
storage_file_close(layout_file);
} else {
bad_ble_script_set_default_keyboard_layout(bad_ble);
}
storage_file_free(layout_file);
}
void bad_ble_script_start_stop(BadBleScript* bad_ble) {
furi_assert(bad_ble);
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtStartStop);
}
void bad_ble_script_pause_resume(BadBleScript* bad_ble) {
furi_assert(bad_ble);
furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtPauseResume);
}
BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) {
furi_assert(bad_ble);
return &(bad_ble->st);
}

View file

@ -0,0 +1,55 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <furi.h>
#include <furi_hal.h>
#include "bad_ble_hid.h"
typedef enum {
BadBleStateInit,
BadBleStateNotConnected,
BadBleStateIdle,
BadBleStateWillRun,
BadBleStateRunning,
BadBleStateDelay,
BadBleStateStringDelay,
BadBleStateWaitForBtn,
BadBleStatePaused,
BadBleStateDone,
BadBleStateScriptError,
BadBleStateFileError,
} BadBleWorkerState;
typedef struct {
BadBleWorkerState state;
size_t line_cur;
size_t line_nb;
uint32_t delay_remain;
size_t error_line;
char error[64];
} BadBleState;
typedef struct BadBleScript BadBleScript;
BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface);
void bad_ble_script_close(BadBleScript* bad_ble);
void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path);
void bad_ble_script_start(BadBleScript* bad_ble);
void bad_ble_script_stop(BadBleScript* bad_ble);
void bad_ble_script_start_stop(BadBleScript* bad_ble);
void bad_ble_script_pause_resume(BadBleScript* bad_ble);
BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,241 @@
#include <furi_hal.h>
#include "ducky_script.h"
#include "ducky_script_i.h"
typedef int32_t (*DuckyCmdCallback)(BadBleScript* bad_usb, const char* line, int32_t param);
typedef struct {
char* name;
DuckyCmdCallback callback;
int32_t param;
} DuckyCmd;
static int32_t ducky_fnc_delay(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint32_t delay_val = 0;
bool state = ducky_get_number(line, &delay_val);
if((state) && (delay_val > 0)) {
return (int32_t)delay_val;
}
return ducky_error(bad_usb, "Invalid number %s", line);
}
static int32_t ducky_fnc_defdelay(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
bool state = ducky_get_number(line, &bad_usb->defdelay);
if(!state) {
return ducky_error(bad_usb, "Invalid number %s", line);
}
return 0;
}
static int32_t ducky_fnc_strdelay(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
bool state = ducky_get_number(line, &bad_usb->stringdelay);
if(!state) {
return ducky_error(bad_usb, "Invalid number %s", line);
}
return 0;
}
static int32_t ducky_fnc_defstrdelay(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
bool state = ducky_get_number(line, &bad_usb->defstringdelay);
if(!state) {
return ducky_error(bad_usb, "Invalid number %s", line);
}
return 0;
}
static int32_t ducky_fnc_string(BadBleScript* bad_usb, const char* line, int32_t param) {
line = &line[ducky_get_command_len(line) + 1];
furi_string_set_str(bad_usb->string_print, line);
if(param == 1) {
furi_string_cat(bad_usb->string_print, "\n");
}
if(bad_usb->stringdelay == 0 &&
bad_usb->defstringdelay == 0) { // stringdelay not set - run command immediately
bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print));
if(!state) {
return ducky_error(bad_usb, "Invalid string %s", line);
}
} else { // stringdelay is set - run command in thread to keep handling external events
return SCRIPT_STATE_STRING_START;
}
return 0;
}
static int32_t ducky_fnc_repeat(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
bool state = ducky_get_number(line, &bad_usb->repeat_cnt);
if((!state) || (bad_usb->repeat_cnt == 0)) {
return ducky_error(bad_usb, "Invalid number %s", line);
}
return 0;
}
static int32_t ducky_fnc_sysrq(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line, true);
bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
bad_usb->hid->release_all(bad_usb->hid_inst);
return 0;
}
static int32_t ducky_fnc_altchar(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
ducky_numlock_on(bad_usb);
bool state = ducky_altchar(bad_usb, line);
if(!state) {
return ducky_error(bad_usb, "Invalid altchar %s", line);
}
return 0;
}
static int32_t ducky_fnc_altstring(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
ducky_numlock_on(bad_usb);
bool state = ducky_altstring(bad_usb, line);
if(!state) {
return ducky_error(bad_usb, "Invalid altstring %s", line);
}
return 0;
}
static int32_t ducky_fnc_hold(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line, true);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
bad_usb->key_hold_nb++;
if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) {
return ducky_error(bad_usb, "Too many keys are hold");
}
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
return 0;
}
static int32_t ducky_fnc_release(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line, true);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
if(bad_usb->key_hold_nb == 0) {
return ducky_error(bad_usb, "No keys are hold");
}
bad_usb->key_hold_nb--;
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
return 0;
}
static int32_t ducky_fnc_media(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_media_keycode_by_name(line);
if(key == HID_CONSUMER_UNASSIGNED) {
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
bad_usb->hid->consumer_press(bad_usb->hid_inst, key);
bad_usb->hid->consumer_release(bad_usb->hid_inst, key);
return 0;
}
static int32_t ducky_fnc_globe(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[ducky_get_command_len(line) + 1];
uint16_t key = ducky_get_keycode(bad_usb, line, true);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
bad_usb->hid->consumer_press(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE);
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
bad_usb->hid->consumer_release(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE);
return 0;
}
static int32_t ducky_fnc_waitforbutton(BadBleScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
UNUSED(bad_usb);
UNUSED(line);
return SCRIPT_STATE_WAIT_FOR_BTN;
}
static const DuckyCmd ducky_commands[] = {
{"REM", NULL, -1},
{"ID", NULL, -1},
{"DELAY", ducky_fnc_delay, -1},
{"STRING", ducky_fnc_string, 0},
{"STRINGLN", ducky_fnc_string, 1},
{"DEFAULT_DELAY", ducky_fnc_defdelay, -1},
{"DEFAULTDELAY", ducky_fnc_defdelay, -1},
{"STRINGDELAY", ducky_fnc_strdelay, -1},
{"STRING_DELAY", ducky_fnc_strdelay, -1},
{"DEFAULT_STRING_DELAY", ducky_fnc_defstrdelay, -1},
{"DEFAULTSTRINGDELAY", ducky_fnc_defstrdelay, -1},
{"REPEAT", ducky_fnc_repeat, -1},
{"SYSRQ", ducky_fnc_sysrq, -1},
{"ALTCHAR", ducky_fnc_altchar, -1},
{"ALTSTRING", ducky_fnc_altstring, -1},
{"ALTCODE", ducky_fnc_altstring, -1},
{"HOLD", ducky_fnc_hold, -1},
{"RELEASE", ducky_fnc_release, -1},
{"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1},
{"MEDIA", ducky_fnc_media, -1},
{"GLOBE", ducky_fnc_globe, -1},
};
#define TAG "BadBle"
#define WORKER_TAG TAG "Worker"
int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line) {
size_t cmd_word_len = strcspn(line, " ");
for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) {
size_t cmd_compare_len = strlen(ducky_commands[i].name);
if(cmd_compare_len != cmd_word_len) {
continue;
}
if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) {
if(ducky_commands[i].callback == NULL) {
return 0;
} else {
return (ducky_commands[i].callback)(bad_usb, line, ducky_commands[i].param);
}
}
}
return SCRIPT_STATE_CMD_UNKNOWN;
}

View file

@ -0,0 +1,76 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <furi.h>
#include <furi_hal.h>
#include "ducky_script.h"
#include "bad_ble_hid.h"
#define SCRIPT_STATE_ERROR (-1)
#define SCRIPT_STATE_END (-2)
#define SCRIPT_STATE_NEXT_LINE (-3)
#define SCRIPT_STATE_CMD_UNKNOWN (-4)
#define SCRIPT_STATE_STRING_START (-5)
#define SCRIPT_STATE_WAIT_FOR_BTN (-6)
#define FILE_BUFFER_LEN 16
struct BadBleScript {
FuriHalUsbHidConfig hid_cfg;
const BadBleHidApi* hid;
void* hid_inst;
FuriThread* thread;
BadBleState st;
FuriString* file_path;
uint8_t file_buf[FILE_BUFFER_LEN + 1];
uint8_t buf_start;
uint8_t buf_len;
bool file_end;
uint32_t defdelay;
uint32_t stringdelay;
uint32_t defstringdelay;
uint16_t layout[128];
FuriString* line;
FuriString* line_prev;
uint32_t repeat_cnt;
uint8_t key_hold_nb;
FuriString* string_print;
size_t string_print_pos;
};
uint16_t ducky_get_keycode(BadBleScript* bad_usb, const char* param, bool accept_chars);
uint32_t ducky_get_command_len(const char* line);
bool ducky_is_line_end(const char chr);
uint16_t ducky_get_keycode_by_name(const char* param);
uint16_t ducky_get_media_keycode_by_name(const char* param);
bool ducky_get_number(const char* param, uint32_t* val);
void ducky_numlock_on(BadBleScript* bad_usb);
bool ducky_numpad_press(BadBleScript* bad_usb, const char num);
bool ducky_altchar(BadBleScript* bad_usb, const char* charcode);
bool ducky_altstring(BadBleScript* bad_usb, const char* param);
bool ducky_string(BadBleScript* bad_usb, const char* param);
int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line);
int32_t ducky_error(BadBleScript* bad_usb, const char* text, ...);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,133 @@
#include <furi_hal.h>
#include "ducky_script_i.h"
typedef struct {
char* name;
uint16_t keycode;
} DuckyKey;
static const DuckyKey ducky_keys[] = {
{"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT},
{"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
{"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
{"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
{"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL},
{"CTRL", KEY_MOD_LEFT_CTRL},
{"CONTROL", KEY_MOD_LEFT_CTRL},
{"SHIFT", KEY_MOD_LEFT_SHIFT},
{"ALT", KEY_MOD_LEFT_ALT},
{"GUI", KEY_MOD_LEFT_GUI},
{"WINDOWS", KEY_MOD_LEFT_GUI},
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
{"LEFT", HID_KEYBOARD_LEFT_ARROW},
{"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW},
{"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
{"UPARROW", HID_KEYBOARD_UP_ARROW},
{"UP", HID_KEYBOARD_UP_ARROW},
{"ENTER", HID_KEYBOARD_RETURN},
{"BREAK", HID_KEYBOARD_PAUSE},
{"PAUSE", HID_KEYBOARD_PAUSE},
{"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
{"DELETE", HID_KEYBOARD_DELETE_FORWARD},
{"BACKSPACE", HID_KEYBOARD_DELETE},
{"END", HID_KEYBOARD_END},
{"ESC", HID_KEYBOARD_ESCAPE},
{"ESCAPE", HID_KEYBOARD_ESCAPE},
{"HOME", HID_KEYBOARD_HOME},
{"INSERT", HID_KEYBOARD_INSERT},
{"NUMLOCK", HID_KEYPAD_NUMLOCK},
{"PAGEUP", HID_KEYBOARD_PAGE_UP},
{"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
{"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
{"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK},
{"SPACE", HID_KEYBOARD_SPACEBAR},
{"TAB", HID_KEYBOARD_TAB},
{"MENU", HID_KEYBOARD_APPLICATION},
{"APP", HID_KEYBOARD_APPLICATION},
{"F1", HID_KEYBOARD_F1},
{"F2", HID_KEYBOARD_F2},
{"F3", HID_KEYBOARD_F3},
{"F4", HID_KEYBOARD_F4},
{"F5", HID_KEYBOARD_F5},
{"F6", HID_KEYBOARD_F6},
{"F7", HID_KEYBOARD_F7},
{"F8", HID_KEYBOARD_F8},
{"F9", HID_KEYBOARD_F9},
{"F10", HID_KEYBOARD_F10},
{"F11", HID_KEYBOARD_F11},
{"F12", HID_KEYBOARD_F12},
{"F13", HID_KEYBOARD_F13},
{"F14", HID_KEYBOARD_F14},
{"F15", HID_KEYBOARD_F15},
{"F16", HID_KEYBOARD_F16},
{"F17", HID_KEYBOARD_F17},
{"F18", HID_KEYBOARD_F18},
{"F19", HID_KEYBOARD_F19},
{"F20", HID_KEYBOARD_F20},
{"F21", HID_KEYBOARD_F21},
{"F22", HID_KEYBOARD_F22},
{"F23", HID_KEYBOARD_F23},
{"F24", HID_KEYBOARD_F24},
};
static const DuckyKey ducky_media_keys[] = {
{"POWER", HID_CONSUMER_POWER},
{"REBOOT", HID_CONSUMER_RESET},
{"SLEEP", HID_CONSUMER_SLEEP},
{"LOGOFF", HID_CONSUMER_AL_LOGOFF},
{"EXIT", HID_CONSUMER_AC_EXIT},
{"HOME", HID_CONSUMER_AC_HOME},
{"BACK", HID_CONSUMER_AC_BACK},
{"FORWARD", HID_CONSUMER_AC_FORWARD},
{"REFRESH", HID_CONSUMER_AC_REFRESH},
{"SNAPSHOT", HID_CONSUMER_SNAPSHOT},
{"PLAY", HID_CONSUMER_PLAY},
{"PAUSE", HID_CONSUMER_PAUSE},
{"PLAY_PAUSE", HID_CONSUMER_PLAY_PAUSE},
{"NEXT_TRACK", HID_CONSUMER_SCAN_NEXT_TRACK},
{"PREV_TRACK", HID_CONSUMER_SCAN_PREVIOUS_TRACK},
{"STOP", HID_CONSUMER_STOP},
{"EJECT", HID_CONSUMER_EJECT},
{"MUTE", HID_CONSUMER_MUTE},
{"VOLUME_UP", HID_CONSUMER_VOLUME_INCREMENT},
{"VOLUME_DOWN", HID_CONSUMER_VOLUME_DECREMENT},
{"FN", HID_CONSUMER_FN_GLOBE},
{"BRIGHT_UP", HID_CONSUMER_BRIGHTNESS_INCREMENT},
{"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT},
};
uint16_t ducky_get_keycode_by_name(const char* param) {
for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) {
size_t key_cmd_len = strlen(ducky_keys[i].name);
if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
(ducky_is_line_end(param[key_cmd_len]))) {
return ducky_keys[i].keycode;
}
}
return HID_KEYBOARD_NONE;
}
uint16_t ducky_get_media_keycode_by_name(const char* param) {
for(size_t i = 0; i < COUNT_OF(ducky_media_keys); i++) {
size_t key_cmd_len = strlen(ducky_media_keys[i].name);
if((strncmp(param, ducky_media_keys[i].name, key_cmd_len) == 0) &&
(ducky_is_line_end(param[key_cmd_len]))) {
return ducky_media_keys[i].keycode;
}
}
return HID_CONSUMER_UNASSIGNED;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

View file

@ -0,0 +1,30 @@
#include "bad_ble_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const bad_ble_scene_on_enter_handlers[])(void*) = {
#include "bad_ble_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "bad_ble_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const bad_ble_scene_on_exit_handlers[])(void* context) = {
#include "bad_ble_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers bad_ble_scene_handlers = {
.on_enter_handlers = bad_ble_scene_on_enter_handlers,
.on_event_handlers = bad_ble_scene_on_event_handlers,
.on_exit_handlers = bad_ble_scene_on_exit_handlers,
.scene_num = BadBleSceneNum,
};

View file

@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) BadBleScene##id,
typedef enum {
#include "bad_ble_scene_config.h"
BadBleSceneNum,
} BadBleScene;
#undef ADD_SCENE
extern const SceneManagerHandlers bad_ble_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "bad_ble_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "bad_ble_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "bad_ble_scene_config.h"
#undef ADD_SCENE

View file

@ -0,0 +1,59 @@
#include "../bad_ble_app_i.h"
enum SubmenuIndex {
ConfigIndexKeyboardLayout,
ConfigIndexBleUnpair,
};
void bad_ble_scene_config_select_callback(void* context, uint32_t index) {
BadBleApp* bad_ble = context;
view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index);
}
static void draw_menu(BadBleApp* bad_ble) {
VariableItemList* var_item_list = bad_ble->var_item_list;
variable_item_list_reset(var_item_list);
variable_item_list_add(var_item_list, "Keyboard Layout (Global)", 0, NULL, NULL);
variable_item_list_add(var_item_list, "Unpair Device", 0, NULL, NULL);
}
void bad_ble_scene_config_on_enter(void* context) {
BadBleApp* bad_ble = context;
VariableItemList* var_item_list = bad_ble->var_item_list;
variable_item_list_set_enter_callback(
var_item_list, bad_ble_scene_config_select_callback, bad_ble);
draw_menu(bad_ble);
variable_item_list_set_selected_item(var_item_list, 0);
view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfig);
}
bool bad_ble_scene_config_on_event(void* context, SceneManagerEvent event) {
BadBleApp* bad_ble = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == ConfigIndexKeyboardLayout) {
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout);
} else if(event.event == ConfigIndexBleUnpair) {
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfirmUnpair);
} else {
furi_crash("Unknown key type");
}
}
return consumed;
}
void bad_ble_scene_config_on_exit(void* context) {
BadBleApp* bad_ble = context;
VariableItemList* var_item_list = bad_ble->var_item_list;
variable_item_list_reset(var_item_list);
}

View file

@ -0,0 +1,7 @@
ADD_SCENE(bad_ble, file_select, FileSelect)
ADD_SCENE(bad_ble, work, Work)
ADD_SCENE(bad_ble, error, Error)
ADD_SCENE(bad_ble, config, Config)
ADD_SCENE(bad_ble, config_layout, ConfigLayout)
ADD_SCENE(bad_ble, confirm_unpair, ConfirmUnpair)
ADD_SCENE(bad_ble, unpair_done, UnpairDone)

View file

@ -0,0 +1,49 @@
#include "../bad_ble_app_i.h"
#include <storage/storage.h>
static bool bad_ble_layout_select(BadBleApp* bad_ble) {
furi_assert(bad_ble);
FuriString* predefined_path;
predefined_path = furi_string_alloc();
if(!furi_string_empty(bad_ble->keyboard_layout)) {
furi_string_set(predefined_path, bad_ble->keyboard_layout);
} else {
furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER);
}
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px);
browser_options.base_path = BAD_BLE_APP_PATH_LAYOUT_FOLDER;
browser_options.skip_assets = false;
// Input events and views are managed by file_browser
bool res = dialog_file_browser_show(
bad_ble->dialogs, bad_ble->keyboard_layout, predefined_path, &browser_options);
furi_string_free(predefined_path);
return res;
}
void bad_ble_scene_config_layout_on_enter(void* context) {
BadBleApp* bad_ble = context;
if(bad_ble_layout_select(bad_ble)) {
scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneWork);
} else {
scene_manager_previous_scene(bad_ble->scene_manager);
}
}
bool bad_ble_scene_config_layout_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
// BadBleApp* bad_ble = context;
return false;
}
void bad_ble_scene_config_layout_on_exit(void* context) {
UNUSED(context);
// BadBleApp* bad_ble = context;
}

View file

@ -0,0 +1,53 @@
#include "../bad_ble_app_i.h"
void bad_ble_scene_confirm_unpair_widget_callback(
GuiButtonType type,
InputType input_type,
void* context) {
UNUSED(input_type);
SceneManagerEvent event = {.type = SceneManagerEventTypeCustom, .event = type};
bad_ble_scene_confirm_unpair_on_event(context, event);
}
void bad_ble_scene_confirm_unpair_on_enter(void* context) {
BadBleApp* bad_ble = context;
Widget* widget = bad_ble->widget;
widget_add_button_element(
widget, GuiButtonTypeLeft, "Cancel", bad_ble_scene_confirm_unpair_widget_callback, context);
widget_add_button_element(
widget,
GuiButtonTypeRight,
"Unpair",
bad_ble_scene_confirm_unpair_widget_callback,
context);
widget_add_text_box_element(
widget, 0, 0, 128, 64, AlignCenter, AlignTop, "\e#Unpair the Device?\e#\n", false);
view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewWidget);
}
bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) {
BadBleApp* bad_ble = context;
SceneManager* scene_manager = bad_ble->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == GuiButtonTypeRight) {
scene_manager_next_scene(scene_manager, BadBleSceneUnpairDone);
} else if(event.event == GuiButtonTypeLeft) {
scene_manager_previous_scene(scene_manager);
}
}
return consumed;
}
void bad_ble_scene_confirm_unpair_on_exit(void* context) {
BadBleApp* bad_ble = context;
Widget* widget = bad_ble->widget;
widget_reset(widget);
}

View file

@ -0,0 +1,65 @@
#include "../bad_ble_app_i.h"
typedef enum {
BadBleCustomEventErrorBack,
} BadBleCustomEvent;
static void
bad_ble_scene_error_event_callback(GuiButtonType result, InputType type, void* context) {
furi_assert(context);
BadBleApp* app = context;
if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) {
view_dispatcher_send_custom_event(app->view_dispatcher, BadBleCustomEventErrorBack);
}
}
void bad_ble_scene_error_on_enter(void* context) {
BadBleApp* app = context;
if(app->error == BadBleAppErrorNoFiles) {
widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43);
widget_add_string_multiline_element(
app->widget,
81,
4,
AlignCenter,
AlignTop,
FontSecondary,
"No SD card or\napp data found.\nThis app will not\nwork without\nrequired files.");
widget_add_button_element(
app->widget, GuiButtonTypeLeft, "Back", bad_ble_scene_error_event_callback, app);
} else if(app->error == BadBleAppErrorCloseRpc) {
widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64);
widget_add_string_multiline_element(
app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nIs Active!");
widget_add_string_multiline_element(
app->widget,
3,
30,
AlignLeft,
AlignTop,
FontSecondary,
"Disconnect from\nPC or phone to\nuse this function.");
}
view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWidget);
}
bool bad_ble_scene_error_on_event(void* context, SceneManagerEvent event) {
BadBleApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == BadBleCustomEventErrorBack) {
view_dispatcher_stop(app->view_dispatcher);
consumed = true;
}
}
return consumed;
}
void bad_ble_scene_error_on_exit(void* context) {
BadBleApp* app = context;
widget_reset(app->widget);
}

View file

@ -0,0 +1,46 @@
#include "../bad_ble_app_i.h"
#include <furi_hal_power.h>
#include <storage/storage.h>
static bool bad_ble_file_select(BadBleApp* bad_ble) {
furi_assert(bad_ble);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px);
browser_options.base_path = BAD_BLE_APP_BASE_FOLDER;
browser_options.skip_assets = true;
// Input events and views are managed by file_browser
bool res = dialog_file_browser_show(
bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options);
return res;
}
void bad_ble_scene_file_select_on_enter(void* context) {
BadBleApp* bad_ble = context;
if(bad_ble->bad_ble_script) {
bad_ble_script_close(bad_ble->bad_ble_script);
bad_ble->bad_ble_script = NULL;
}
if(bad_ble_file_select(bad_ble)) {
scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork);
} else {
view_dispatcher_stop(bad_ble->view_dispatcher);
}
}
bool bad_ble_scene_file_select_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
// BadBleApp* bad_ble = context;
return false;
}
void bad_ble_scene_file_select_on_exit(void* context) {
UNUSED(context);
// BadBleApp* bad_ble = context;
}

View file

@ -0,0 +1,37 @@
#include "../bad_ble_app_i.h"
static void bad_ble_scene_unpair_done_popup_callback(void* context) {
BadBleApp* bad_ble = context;
scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneConfig);
}
void bad_ble_scene_unpair_done_on_enter(void* context) {
BadBleApp* bad_ble = context;
Popup* popup = bad_ble->popup;
popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58);
popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom);
popup_set_callback(popup, bad_ble_scene_unpair_done_popup_callback);
popup_set_context(popup, bad_ble);
popup_set_timeout(popup, 1000);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewPopup);
}
bool bad_ble_scene_unpair_done_on_event(void* context, SceneManagerEvent event) {
BadBleApp* bad_ble = context;
UNUSED(bad_ble);
UNUSED(event);
bool consumed = false;
return consumed;
}
void bad_ble_scene_unpair_done_on_exit(void* context) {
BadBleApp* bad_ble = context;
Popup* popup = bad_ble->popup;
popup_reset(popup);
}

View file

@ -0,0 +1,65 @@
#include "../helpers/ducky_script.h"
#include "../bad_ble_app_i.h"
#include "../views/bad_ble_view.h"
#include <furi_hal.h>
#include "toolbox/path.h"
void bad_ble_scene_work_button_callback(InputKey key, void* context) {
furi_assert(context);
BadBleApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, key);
}
bool bad_ble_scene_work_on_event(void* context, SceneManagerEvent event) {
BadBleApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InputKeyLeft) {
if(bad_ble_view_is_idle_state(app->bad_ble_view)) {
bad_ble_script_close(app->bad_ble_script);
app->bad_ble_script = NULL;
scene_manager_next_scene(app->scene_manager, BadBleSceneConfig);
}
consumed = true;
} else if(event.event == InputKeyOk) {
bad_ble_script_start_stop(app->bad_ble_script);
consumed = true;
} else if(event.event == InputKeyRight) {
bad_ble_script_pause_resume(app->bad_ble_script);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeTick) {
bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script));
}
return consumed;
}
void bad_ble_scene_work_on_enter(void* context) {
BadBleApp* app = context;
app->bad_ble_script = bad_ble_script_open(app->file_path, app->interface);
bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout);
FuriString* file_name;
file_name = furi_string_alloc();
path_extract_filename(app->file_path, file_name, true);
bad_ble_view_set_file_name(app->bad_ble_view, furi_string_get_cstr(file_name));
furi_string_free(file_name);
FuriString* layout;
layout = furi_string_alloc();
path_extract_filename(app->keyboard_layout, layout, true);
bad_ble_view_set_layout(app->bad_ble_view, furi_string_get_cstr(layout));
furi_string_free(layout);
bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script));
bad_ble_view_set_button_callback(app->bad_ble_view, bad_ble_scene_work_button_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWork);
}
void bad_ble_scene_work_on_exit(void* context) {
UNUSED(context);
}

View file

@ -0,0 +1,284 @@
#include "bad_ble_view.h"
#include "../helpers/ducky_script.h"
#include <toolbox/path.h>
#include <gui/elements.h>
#include <assets_icons.h>
#include "bad_ble_icons.h"
#define MAX_NAME_LEN 64
struct BadBle {
View* view;
BadBleButtonCallback callback;
void* context;
};
typedef struct {
char file_name[MAX_NAME_LEN];
char layout[MAX_NAME_LEN];
BadBleState state;
bool pause_wait;
uint8_t anim_frame;
} BadBleModel;
static void bad_ble_draw_callback(Canvas* canvas, void* _model) {
BadBleModel* model = _model;
FuriString* disp_str;
disp_str = furi_string_alloc_set(model->file_name);
elements_string_fit_width(canvas, disp_str, 128 - 2);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str));
if(strlen(model->layout) == 0) {
furi_string_set(disp_str, "(default)");
} else {
furi_string_printf(disp_str, "(%s)", model->layout);
}
elements_string_fit_width(canvas, disp_str, 128 - 2);
canvas_draw_str(
canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str));
furi_string_reset(disp_str);
canvas_draw_icon(canvas, 22, 24, &I_Bad_BLE_48x22);
BadBleWorkerState state = model->state.state;
if((state == BadBleStateIdle) || (state == BadBleStateDone) ||
(state == BadBleStateNotConnected)) {
elements_button_center(canvas, "Run");
elements_button_left(canvas, "Config");
} else if((state == BadBleStateRunning) || (state == BadBleStateDelay)) {
elements_button_center(canvas, "Stop");
if(!model->pause_wait) {
elements_button_right(canvas, "Pause");
}
} else if(state == BadBleStatePaused) {
elements_button_center(canvas, "End");
elements_button_right(canvas, "Resume");
} else if(state == BadBleStateWaitForBtn) {
elements_button_center(canvas, "Press to continue");
} else if(state == BadBleStateWillRun) {
elements_button_center(canvas, "Cancel");
}
if(state == BadBleStateNotConnected) {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to device");
} else if(state == BadBleStateWillRun) {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect");
} else if(state == BadBleStateFileError) {
canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File");
canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR");
} else if(state == BadBleStateScriptError) {
canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:");
canvas_set_font(canvas, FontSecondary);
furi_string_printf(disp_str, "line %zu", model->state.error_line);
canvas_draw_str_aligned(
canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str);
furi_string_set_str(disp_str, model->state.error);
elements_string_fit_width(canvas, disp_str, canvas_width(canvas));
canvas_draw_str_aligned(
canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str);
} else if(state == BadBleStateIdle) {
canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18);
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0");
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
} else if(state == BadBleStateRunning) {
if(model->anim_frame == 0) {
canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
} else {
canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21);
}
canvas_set_font(canvas, FontBigNumbers);
furi_string_printf(
disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
canvas_draw_str_aligned(
canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str);
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
} else if(state == BadBleStateDone) {
canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100");
furi_string_reset(disp_str);
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
} else if(state == BadBleStateDelay) {
if(model->anim_frame == 0) {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21);
} else {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21);
}
canvas_set_font(canvas, FontBigNumbers);
furi_string_printf(
disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
canvas_draw_str_aligned(
canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str);
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
canvas_set_font(canvas, FontSecondary);
furi_string_printf(disp_str, "delay %lus", model->state.delay_remain);
canvas_draw_str_aligned(
canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str);
} else if((state == BadBleStatePaused) || (state == BadBleStateWaitForBtn)) {
if(model->anim_frame == 0) {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21);
} else {
canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21);
}
canvas_set_font(canvas, FontBigNumbers);
furi_string_printf(
disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
canvas_draw_str_aligned(
canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str);
canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused");
furi_string_reset(disp_str);
} else {
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
}
furi_string_free(disp_str);
}
static bool bad_ble_input_callback(InputEvent* event, void* context) {
furi_assert(context);
BadBle* bad_ble = context;
bool consumed = false;
if(event->type == InputTypeShort) {
if(event->key == InputKeyLeft) {
consumed = true;
furi_assert(bad_ble->callback);
bad_ble->callback(event->key, bad_ble->context);
} else if(event->key == InputKeyOk) {
with_view_model(
bad_ble->view, BadBleModel * model, { model->pause_wait = false; }, true);
consumed = true;
furi_assert(bad_ble->callback);
bad_ble->callback(event->key, bad_ble->context);
} else if(event->key == InputKeyRight) {
with_view_model(
bad_ble->view,
BadBleModel * model,
{
if((model->state.state == BadBleStateRunning) ||
(model->state.state == BadBleStateDelay)) {
model->pause_wait = true;
}
},
true);
consumed = true;
furi_assert(bad_ble->callback);
bad_ble->callback(event->key, bad_ble->context);
}
}
return consumed;
}
BadBle* bad_ble_view_alloc(void) {
BadBle* bad_ble = malloc(sizeof(BadBle));
bad_ble->view = view_alloc();
view_allocate_model(bad_ble->view, ViewModelTypeLocking, sizeof(BadBleModel));
view_set_context(bad_ble->view, bad_ble);
view_set_draw_callback(bad_ble->view, bad_ble_draw_callback);
view_set_input_callback(bad_ble->view, bad_ble_input_callback);
return bad_ble;
}
void bad_ble_view_free(BadBle* bad_ble) {
furi_assert(bad_ble);
view_free(bad_ble->view);
free(bad_ble);
}
View* bad_ble_view_get_view(BadBle* bad_ble) {
furi_assert(bad_ble);
return bad_ble->view;
}
void bad_ble_view_set_button_callback(
BadBle* bad_ble,
BadBleButtonCallback callback,
void* context) {
furi_assert(bad_ble);
furi_assert(callback);
with_view_model(
bad_ble->view,
BadBleModel * model,
{
UNUSED(model);
bad_ble->callback = callback;
bad_ble->context = context;
},
true);
}
void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name) {
furi_assert(name);
with_view_model(
bad_ble->view,
BadBleModel * model,
{ strlcpy(model->file_name, name, MAX_NAME_LEN); },
true);
}
void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout) {
furi_assert(layout);
with_view_model(
bad_ble->view,
BadBleModel * model,
{ strlcpy(model->layout, layout, MAX_NAME_LEN); },
true);
}
void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st) {
furi_assert(st);
with_view_model(
bad_ble->view,
BadBleModel * model,
{
memcpy(&(model->state), st, sizeof(BadBleState));
model->anim_frame ^= 1;
if(model->state.state == BadBleStatePaused) {
model->pause_wait = false;
}
},
true);
}
bool bad_ble_view_is_idle_state(BadBle* bad_ble) {
bool is_idle = false;
with_view_model(
bad_ble->view,
BadBleModel * model,
{
if((model->state.state == BadBleStateIdle) ||
(model->state.state == BadBleStateDone) ||
(model->state.state == BadBleStateNotConnected)) {
is_idle = true;
}
},
false);
return is_idle;
}

View file

@ -0,0 +1,26 @@
#pragma once
#include <gui/view.h>
#include "../helpers/ducky_script.h"
typedef struct BadBle BadBle;
typedef void (*BadBleButtonCallback)(InputKey key, void* context);
BadBle* bad_ble_view_alloc(void);
void bad_ble_view_free(BadBle* bad_ble);
View* bad_ble_view_get_view(BadBle* bad_ble);
void bad_ble_view_set_button_callback(
BadBle* bad_ble,
BadBleButtonCallback callback,
void* context);
void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name);
void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout);
void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st);
bool bad_ble_view_is_idle_state(BadBle* bad_ble);

View file

@ -7,7 +7,7 @@
#define TAG "HidMouseClicker"
#define DEFAULT_CLICK_RATE 1
#define MAXIMUM_CLICK_RATE 60
#define MAXIMUM_CLICK_RATE 100
struct HidMouseClicker {
View* view;
@ -34,7 +34,9 @@ static void hid_mouse_clicker_start_or_restart_timer(void* context) {
HidMouseClickerModel * model,
{
furi_timer_start(
hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate);
hid_mouse_clicker->timer,
furi_kernel_get_tick_frequency() /
((model->rate) ? model->rate : MAXIMUM_CLICK_RATE));
},
true);
}
@ -75,7 +77,11 @@ static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) {
// Clicks/s
char label[20];
if(model->rate) {
snprintf(label, sizeof(label), "%d clicks/s", model->rate);
} else {
snprintf(label, sizeof(label), "max clicks/s");
}
elements_multiline_text_aligned(canvas, 28, 37, AlignCenter, AlignBottom, label);
canvas_draw_icon(canvas, 25, 20, &I_ButtonUp_7x4);
@ -139,7 +145,7 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) {
consumed = true;
break;
case InputKeyDown:
if(model->rate > 1) {
if(model->rate > 0) {
model->rate--;
}
rate_changed = true;

View file

@ -395,14 +395,15 @@ bool update_task_open_file(UpdateTask* update_task, FuriString* filename) {
return open_success;
}
static void update_task_worker_thread_cb(FuriThreadState state, void* context) {
UpdateTask* update_task = context;
static void
update_task_worker_thread_cb(FuriThread* thread, FuriThreadState state, void* context) {
UNUSED(context);
if(state != FuriThreadStateStopped) {
return;
}
if(furi_thread_get_return_code(update_task->thread) == UPDATE_TASK_NOERR) {
if(furi_thread_get_return_code(thread) == UPDATE_TASK_NOERR) {
furi_delay_ms(UPDATE_DELAY_OPERATION_OK);
furi_hal_power_reset();
}
@ -427,7 +428,6 @@ UpdateTask* update_task_alloc(void) {
furi_thread_alloc_ex("UpdateWorker", 5120, NULL, update_task);
furi_thread_set_state_callback(thread, update_task_worker_thread_cb);
furi_thread_set_state_context(thread, update_task);
#ifdef FURI_RAM_EXEC
UNUSED(update_task_worker_backup_restore);
furi_thread_set_callback(thread, update_task_worker_flash_writer);

View file

@ -1,65 +1,65 @@
# Flipper Application Manifests (.fam) {#app_manifests}
# FAM (Flipper App Manifests) {#app_manifests}
All components of Flipper Zero firmware — services, user applications, and system settings — are developed independently. Each component has a build system manifest file named `application.fam`, which defines the basic properties of that component and its relations to other parts of the system.
When building firmware, `fbt` collects all application manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](fbt.md) for details on build configurations.
When building firmware, `fbt` collects all app manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](fbt.md) for details on build configurations.
## Application definition
## App definition
A firmware component's properties are declared in a Python code snippet, forming a call to the `App()` function with various parameters.
Only two parameters are mandatory: **appid** and **apptype**. Others are optional and may only be meaningful for certain application types.
Only two parameters are mandatory: **appid** and **apptype**. Others are optional and may only be meaningful for certain app types.
### Parameters
- **appid**: string, application ID within the build system. It is used to specify which applications to include in the build configuration and resolve dependencies and conflicts.
- **appid**: string, app ID within the build system. It is used to specify which app to include in the build configuration and resolve dependencies and conflicts.
- **apptype**: member of FlipperAppType.\* enumeration. Valid values are:
| Enum member | Firmware component type |
| ----------- | ------------------------------------------------------------------------------------------- |
| SERVICE | System service, created at early startup |
| SYSTEM | Application is not being shown in any menus. It can be started by other apps or from CLI |
| APP | Regular application for the main menu |
| PLUGIN | Application to be built as a part of the firmware and to be placed in the Plugins menu |
| DEBUG | Application only visible in Debug menu with debug mode enabled |
| SYSTEM | App is not being shown in any menus. It can be started by other apps or from CLI |
| APP | Regular app for the main menu |
| PLUGIN | App to be built as a part of the firmware and to be placed in the Plugins menu |
| DEBUG | App only visible in Debug menu with debug mode enabled |
| ARCHIVE | One and only Archive app |
| SETTINGS | Application to be placed in the system settings menu |
| SETTINGS | App to be placed in the system settings menu |
| STARTUP | Callback function to run at system startup. Does not define a separate app |
| EXTERNAL | Application to be built as `.fap` plugin |
| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles |
| EXTERNAL | App to be built as `.fap` plugin |
| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and app bundles |
- **name**: name displayed in menus.
- **entry_point**: C function to be used as the application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points.
- **entry_point**: C function to be used as the app's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points.
- **flags**: internal flags for system apps. Do not use.
- **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. **For external applications**: specified definitions are used when building the application itself.
- **requires**: list of application IDs to include in the build configuration when the current application is referenced in the list of applications to build.
- **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, `fbt` will abort the firmware build process.
- **cdefines**: C preprocessor definitions to declare globally for other apps when the current app is included in the active build configuration. **For external apps**: specified definitions are used when building the app itself.
- **requires**: list of app IDs to include in the build configuration when the current app is referenced in the list of apps to build.
- **conflicts**: list of app IDs with which the current app conflicts. If any of them is found in the constructed app list, `fbt` will abort the firmware build process.
- **provides**: functionally identical to **_requires_** field.
- **stack_size**: stack size in bytes to allocate for an application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `top` and `free` CLI commands to profile your app's memory usage._
- **stack_size**: stack size in bytes to allocate for an app on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `top` and `free` CLI commands to profile your app's memory usage._
- **icon**: animated icon name from built-in assets to be used when building the app as a part of the firmware.
- **order**: order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._
- **sdk_headers**: list of C header files from this app's code to include in API definitions for external applications.
- **targets**: list of strings and target names with which this application is compatible. If not specified, the application is built for all targets. The default value is `["all"]`.
- **resources**: name of a folder within the application's source folder to be used for packacking SD card resources for this application. They will only be used if application is included in build configuration. The default value is `""`, meaning no resources are packaged.
- **order**: order of an app within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._
- **sdk_headers**: list of C header files from this app's code to include in API definitions for external apps.
- **targets**: list of strings and target names with which this app is compatible. If not specified, the app is built for all targets. The default value is `["all"]`.
- **resources**: name of a folder within the app's source folder to be used for packacking SD card resources for this app. They will only be used if app is included in build configuration. The default value is `""`, meaning no resources are packaged.
#### Parameters for external applications
#### Parameters for external apps
The following parameters are used only for [FAPs](./AppsOnSDCard.md):
- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. Paths starting with `"!"` are excluded from the list of sources. They can also include wildcard characters and directory names. For example, a value of `["*.c*", "!plugins"]` will include all C and C++ source files in the app folder except those in the `plugins` (and `lib`) folders. Paths with no wildcards (`*, ?`) are treated as full literal paths for both inclusion and exclusion.
- **fap_version**: string, application version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap.
- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Apps cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. Paths starting with `"!"` are excluded from the list of sources. They can also include wildcard characters and directory names. For example, a value of `["*.c*", "!plugins"]` will include all C and C++ source files in the app folder except those in the `plugins` (and `lib`) folders. Paths with no wildcards (`*, ?`) are treated as full literal paths for both inclusion and exclusion.
- **fap_version**: string, app version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap.
- **fap_icon**: name of a `.png` file, 1-bit color depth, 10x10px, to be embedded within `.fap` file.
- **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption.
- **fap_libs**: list of extra libraries to link the app against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption.
- **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within the apps folder in the file system.
- **fap_description**: string, may be empty. Short application description.
- **fap_author**: string, may be empty. Application's author.
- **fap_weburl**: string, may be empty. Application's homepage.
- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](AppsOnSDCard.md) for details.
- **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. `fbt` will run the specified command for each file in the list.
- **fal_embedded**: boolean, default `False`. Applies only to PLUGIN type. If `True`, the plugin will be embedded into host application's .fap file as a resource and extracted to `apps_assets/APPID` folder on its start. This allows plugins to be distributed as a part of the host application.
- **fap_description**: string, may be empty. Short app description.
- **fap_author**: string, may be empty. App's author.
- **fap_weburl**: string, may be empty. App's homepage.
- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this app. These images will be preprocessed and built alongside the app. See [FAP assets](AppsOnSDCard.md) for details.
- **fap_extbuild**: provides support for parts of app sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. `fbt` will run the specified command for each file in the list.
- **fal_embedded**: boolean, default `False`. Applies only to PLUGIN type. If `True`, the plugin will be embedded into host app's .fap file as a resource and extracted to `apps_assets/APPID` folder on its start. This allows plugins to be distributed as a part of the host app.
Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by `fbt`: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by `fbt`.
Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an app's temporary build folder. For that, you can use pattern expansion by `fbt`: `${FAP_WORK_DIR}` will be replaced with the path to the app's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the app's source folder. You can also use other variables defined internally by `fbt`.
Example for building an app from Rust sources:
@ -73,8 +73,8 @@ Example for building an app from Rust sources:
),
```
- **fap_private_libs**: list of additional libraries distributed as sources alongside the application. These libraries will be built as a part of the application build process.
Library sources must be placed in a subfolder of the `lib` folder within the application's source folder.
- **fap_private_libs**: list of additional libraries distributed as sources alongside the app. These libraries will be built as a part of the app build process.
Library sources must be placed in a subfolder of the `lib` folder within the app's source folder.
Each library is defined as a call to the `Lib()` function, accepting the following parameters:
- **name**: name of the library's folder. Required.
@ -82,7 +82,7 @@ Example for building an app from Rust sources:
- **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`.
- **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`.
- **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`.
- **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`.
- **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the app's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`.
Example for building an app with a private library:
@ -105,14 +105,14 @@ Example for building an app with a private library:
],
```
For that snippet, `fbt` will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, `fbt` will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in the `sources` list. Additionally, `fbt` will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources.
For the `loclass` library, `fbt` will add `lib/loclass` to the list of the include paths for the application and build all sources in that folder. Also, `fbt` will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases.
For that snippet, `fbt` will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, `fbt` will add `lib/mbedtls/include` to the list of include paths for the app and compile only the files specified in the `sources` list. Additionally, `fbt` will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources.
For the `loclass` library, `fbt` will add `lib/loclass` to the list of the included paths for the app and build all sources in that folder. Also, `fbt` will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases.
Both libraries will be linked with the application.
Both libraries will be linked with the app.
## .fam file contents
The `.fam` file contains one or more application definitions. For example, here's a part of `applications/service/bt/application.fam`:
The `.fam` file contains one or more app definitions. For example, here's a part of `applications/service/bt/application.fam`:
```python
App(

View file

@ -1,29 +1,29 @@
# FAP (Flipper Application Package) {#apps_on_sd_card}
# FAP (Flipper App Package) {#apps_on_sd_card}
[fbt](./fbt.md) supports building applications as FAP files. FAPs are essentially `.elf` executables with extra metadata and resources bundled in.
[fbt](./fbt.md) supports building apps as FAP files. FAPs are essentially `.elf` executables with extra metadata and resources bundled in.
FAPs are built with the `faps` target. They can also be deployed to the `dist` folder with the `fap_dist` target.
FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning).
## How to set up an application to be built as a FAP {#fap-howto}
## How to set up an app to be built as a FAP {#fap-howto}
FAPs are created and developed the same way as internal applications that are part of the firmware.
FAPs are created and developed the same way as internal apps that are part of the firmware.
To build your application as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest, and set its _apptype_ to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details.
To build your app as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in app. Then configure its `application.fam` manifest, and set its `apptype` to `FlipperAppType.EXTERNAL`. See [Flipper App Manifests](AppManifests.md) for more details.
- To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest.
- To build your app, run `./fbt fap_{APPID}`, where APPID is your app's ID in its manifest.
- To build your app and upload it over USB to run on Flipper, use `./fbt launch APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu).
- To build an app without uploading it to Flipper, use `./fbt build APPSRC=applications_user/path/to/app`. This command is also available in VSCode configuration as "Build App".
- To build all FAPs, run `./fbt faps` or `./fbt fap_dist`.
## FAP assets
FAPs can include static and animated images as private assets. They will be automatically compiled alongside application sources and can be referenced the same way as assets from the main firmware.
FAPs can include static and animated images as private assets. They will be automatically compiled alongside app sources and can be referenced the same way as assets from the main firmware.
To use that feature, put your images in a subfolder inside your application's folder, then reference that folder in your application's manifest in the `fap_icon_assets` field. See [Application Manifests](AppManifests.md) for more details.
To use that feature, put your images in a subfolder inside your app's folder, then reference that folder in your app's manifest in the `fap_icon_assets` field. See [Flipper App Manifests](AppManifests.md) for more details.
To use these assets in your application, put `#include "{APPID}_icons.h"` in your application's source code, where `{APPID}` is the `appid` value field from your application's manifest. Then you can use all icons from your application's assets the same way as if they were a part of `assets_icons.h` of the main firmware.
To use these assets in your app, put `#include "{APPID}_icons.h"` in your app's source code, where `{APPID}` is the `appid` value field from your app's manifest. Then you can use all icons from your app's assets the same way as if they were a part of `assets_icons.h` of the main firmware.
Images and animated icons should follow the same [naming convention](../assets/ReadMe.md) as those from the main firmware.
@ -33,11 +33,11 @@ Images and animated icons should follow the same [naming convention](../assets/R
With it, you can debug FAPs as if they were a part of the main firmware — inspect variables, set breakpoints, step through the code, etc.
If debugging session is active, firmware will trigger a breakpoint after loading a FAP it into memory, but before running any code from it. This allows you to set breakpoints in the FAP's code. Note that any breakpoints set before the FAP is loaded may need re-setting after the FAP is actually loaded, since before loading it debugger cannot know the exact address of the FAP's code.
If debugging session is active, firmware will trigger a breakpoint after loading a FAP into memory, but before running any code from it. This allows you to set breakpoints in the FAP's code. Note that any breakpoints set before the FAP is loaded may need re-setting after the FAP is actually loaded, since the debugger cannot know the exact address of the FAP's code before loading the FAP.
### Setting up debugging environment
The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](./fbt.md#nb) for details.
The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](fbt.md) for details.
To debug FAPs, do the following:
@ -45,23 +45,23 @@ To debug FAPs, do the following:
2. Flash it with `./fbt flash`
3. [Build your FAP](#fap-howto) and run it on Flipper
After that, you can attach with `./fbt debug` or VS Code and use all debug features.
After that, you can attach the debugger to the target MCU with `./fbt debug` or VS Code and use all debug features.
It is **important** that firmware and application build type (debug/release) match and that the matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work.
It is **important** that firmware and app build type (debug/release) match and that the matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work.
## How Flipper runs an application from an SD card
## How Flipper runs an app from an SD card
Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader application responsible for loading the FAP from the SD card, verifying its integrity and compatibility, copying it to RAM, and adjusting it for its new location.
Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader responsible for loading the FAP from the SD card, verifying its integrity and compatibility, copying it to RAM, and adjusting it for its new location.
Since FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of the firmware. Note that the amount of occupied RAM is less than the total FAP file size since only code and data sections are allocated, while the FAP file includes extra information only used at app load time.
Since the FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of the firmware. Note that the amount of occupied RAM is less than the total FAP file size since only code and data sections are allocated, while the FAP file includes extra information only used at app load time.
Applications are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. The App Loader checks if the application's major API version matches the firmware's major API version.
Apps are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. The App Loader checks if the app's major API version matches the firmware's major API version.
The App Loader allocates memory for the application and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the application.
The App Loader allocates memory for the app and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the app.
## API versioning {#api-versioning}
Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `targets/` directory.
Not all parts of firmware are available for external apps. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `targets/` directory.
`fbt` uses semantic versioning for the API. The major version is incremented when there are breaking changes in the API. The minor version is incremented when new features are added.
@ -78,6 +78,6 @@ API versioning is mostly automated by `fbt`. When rebuilding the firmware, `fbt`
### Symbol table {#symbol-table}
The symbol table is a list of symbols exported by firmware and available for external applications. It is generated by `fbt` from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` application.
The symbol table is a list of symbols exported by firmware and available for external apps. It is generated by `fbt` from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` app.
`fbt` also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The application won't be able to run on the device until all required symbols are provided in the symbol table.
`fbt` also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The app won't be able to run on the device until all required symbols are provided in the symbol table.

View file

@ -29,7 +29,7 @@ Depending on the UART selected for communication, the following pins area availa
## Frame structure
Each frame consists of a header (1 byte), contents (size depends of frame type) and checksum (1 byte) fields:
Each frame consists of a header (1 byte), contents (size depends on frame type) and checksum (1 byte) fields:
| Header (1 byte) | Contents (0 or more bytes) | Checksum (1 byte) |
|-----------------|----------------------------|-------------------|
@ -79,7 +79,7 @@ CONTROL frames are used to control various aspects of the communication and enab
|-----------------|-------------------|-------------------|
| 0x04 | Command | XOR checksum |
The `Command` field SHALL have one of the followind values:
The `Command` field SHALL have one of the following values:
| Command | Meaning | Note |
|---------|--------------------------|:----:|
@ -96,7 +96,7 @@ Notes:
### Data frame
DATA frames are used to transmit arbitrary data in either direction. Each DATA frame can hold up to 64 bytes. If an RPC session is curretly open, all received bytes are forwarded to it.
DATA frames are used to transmit arbitrary data in either direction. Each DATA frame can hold up to 64 bytes. If an RPC session is currently open, all received bytes are forwarded to it.
| Header (1 byte) | Contents (1 to 65 byte(s)) | Checksum (1 byte) |
|-----------------|----------------------------|-------------------|
@ -110,7 +110,7 @@ The `Data` field SHALL have the following structure:
## Communication flow
In order for the host to be able to detect the module, the respective feature must be enabled first. This can be done via the GUI by going to `Settings -> Expansion Modules` and selecting the required `Listen UART` or programmatically by calling `expansion_enable()`. Likewise, disabling this feature via the same GUI or by calling `expansion_disable()` will result in ceasing all communications and not being able to detect any connected modules.
In order for the host to be able to detect the module, the respective feature must be enabled first. This can be done via the GUI by going to `Settings Expansion Modules` and selecting the required `Listen UART` or programmatically by calling `expansion_enable()`. Likewise, disabling this feature via the same GUI or by calling `expansion_disable()` will result in ceasing all communications and not being able to detect any connected modules.
The communication is always initiated by the module by the means of shortly pulling the RX pin down. The host SHALL respond with a HEARTBEAT frame indicating that it is ready to receive requests. The module then MUST issue a BAUDRATE request within Tto. Failure to do so will result in the host dropping the connection and returning to its initial state.

View file

@ -1,13 +1,13 @@
# Run time checks and forced system crash {#furi_check}
The best way to protect system integrity is to reduce amount cases that we must handle and crash the system as early as possible.
For that purpose we have bunch of helpers located in Furi Core check.h.
For that purpose, we have a bunch of helpers located in Furi Core `check.h`.
## Couple notes before start
- Definition of Crash - log event, save crash information in RTC and reboot the system.
- Definition of Halt - log event, stall the system.
- Debug and production builds behaves differently: debug build will never reset system in order to preserve state for debugging.
- Definition of Crash log event, save crash information in RTC and reboot the system.
- Definition of Halt log event, stall the system.
- Debug and production builds behave differently: debug build will never reset system in order to preserve state for debugging.
- If you have debugger connected we will stop before reboot automatically.
- All helpers accept optional MESSAGE_CSTR: it can be in RAM or Flash memory, but only messages from Flash will be shown after system reboot.
- MESSAGE_CSTR can be NULL, but macros magic already doing it for you, so just don't.
@ -16,10 +16,10 @@ For that purpose we have bunch of helpers located in Furi Core check.h.
Assert condition in development environment and crash the system if CONDITION is false.
- Should be used at development stage in apps and services
- Keep in mind that release never contains this check
- Keep in mind that libraries never contains this check by default, use `LIB_DEBUG=1` if you need it
- Avoid putting function calls into CONDITION, since it may be omitted in some builds
- Should be used at development stage in apps and services.
- Keep in mind that release never contains this check.
- Keep in mind that libraries never contain this check by default, use `LIB_DEBUG=1` if you need it.
- Avoid putting function calls into CONDITION, since it may be omitted in some builds.
## `furi_check(CONDITION)` or `furi_check(CONDITION, MESSAGE_CSTR)`
@ -31,10 +31,10 @@ Always assert condition and crash the system if CONDITION is false.
Crash the system.
- Use it to crash the system. For example: if abnormal condition detected.
- Use it to crash the system. For example, if an abnormal condition is detected.
## `furi_halt()` or `furi_halt(MESSAGE_CSTR)`
Halt the system.
- We use it internally to shutdown flipper if poweroff is not possible.
- We use it internally to shutdown Flipper if poweroff is not possible.

View file

@ -5,7 +5,7 @@
On system startup, most of the peripheral devices are under reset and not clocked by default. This is done to reduce power consumption and to guarantee that the device will always be in the same state before use.
Some crucial peripherals are enabled right away by the system, others must be explicitly enabled by the user code.
**NOTE:** Here and afterwards the word *"system"* refers to any code belonging to the operating system, hardware drivers or built-in applications.
**NOTE:** Here and afterwards, the word *"system"* refers to any code belonging to the operating system, hardware drivers or built-in apps.
To **ENABLE** a peripheral, call `furi_hal_bus_enable()`. At the time of the call, the peripheral in question MUST be disabled, otherwise a crash will occur to indicate improper use. This means that any given peripheral cannot be enabled twice or more without disabling it first.
@ -24,7 +24,7 @@ Built-in peripherals are divided into three categories:
Below is the list of peripherals that are enabled by the system. The user code must NEVER attempt to disable them. If a corresponding API is provided, the user code must employ it in order to access the peripheral.
*Table 1* - Peripherals enabled by the system
*Table 1* Peripherals enabled by the system
| Peripheral | Enabled at |
| :-----------: | :-----------------------: |
@ -49,7 +49,7 @@ Below is the list of peripherals that are enabled and disabled by the system. Th
When not using the API, these peripherals MUST be enabled by the user code and then disabled when not needed anymore.
*Table 2* - Peripherals enabled and disabled by the system
*Table 2* Peripherals enabled and disabled by the system
| Peripheral | API header file |
| :-----------: | :-------------------: |
@ -69,7 +69,7 @@ Below is the list of peripherals that are not enabled by default and MUST be ena
Note that some of these peripherals may also be used by the system to implement its certain features.
The system will take over any given peripheral only when the respective feature is in use.
*Table 3* - Peripherals enabled and disabled by user
*Table 3* Peripherals enabled and disabled by user
| Peripheral | System | Purpose |
| :-----------: | :-------: | ------------------------------------- |
@ -93,7 +93,7 @@ The DMA1,2 peripherals are a special case in that they have multiple independent
Below is the list of DMA channels and their usage by the system.
*Table 4* - DMA channels
*Table 4* DMA channels
| DMA | Channel | System | Purpose |
| :---: | :-------: | :-------: | ------------------------- |

View file

@ -1,7 +1,7 @@
# Furi HAL Debugging {#furi_hal_debugging}
Some Furi subsystems got additional debugging features that can be enabled by adding additional defines to firmware compilation.
Usually they are used for low level tracing and profiling or signal redirection/duplication.
Some Furi subsystems have additional debugging features that can be enabled by adding additional defines to firmware compilation.
Usually, they are used for low level tracing and profiling or signal redirection/duplication.
## FuriHalOs
@ -10,9 +10,9 @@ Usually they are used for low level tracing and profiling or signal redirection/
There are 3 signals that will be exposed to external GPIO pins:
- `AWAKE` - `PA7` - High when system is busy with computations, low when sleeping. Can be used to track transitions to sleep mode.
- `TICK` - `PA6` - Flipped on system tick, only flips when no tick suppression in progress. Can be used to track tick skew and abnormal task scheduling.
- `SECOND` - `PA4` - Flipped each second. Can be used for tracing RT issue: time flow disturbance means system doesn't conforms Hard RT.
- `AWAKE` `PA7` High when system is busy with computations, low when sleeping. Can be used to track transitions to sleep mode.
- `TICK` `PA6` Flipped on system tick, only flips when no tick suppression in progress. Can be used to track tick skew and abnormal task scheduling.
- `SECOND` `PA4` Flipped each second. Can be used for tracing RT issue: time flow disturbance means system doesn't conform Hard RT.
@ -22,8 +22,8 @@ There are 3 signals that will be exposed to external GPIO pins:
There are 2 signals that will be exposed to external GPIO pins:
- `WFI` - `PB2` - Light sleep (wait for interrupt) used. Basically this is lightest and most non-breaking things power save mode. All function and debug should work correctly in this mode.
- `STOP` - `PC3` - STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible.
- `WFI` `PB2` — Light sleep (wait for interrupt) used. Basically, this is the lightest and most non-breaking things power save mode. All functions and debug should work correctly in this mode.
- `STOP` `PC3` STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible.
## FuriHalSD

View file

@ -25,20 +25,20 @@ A target definition file, `target.json`, is a JSON file that can contain the fol
* `excluded_modules`: list of strings specifying fbt library (module) names to exclude from being used to configure build environment.
## Applications & Hardware
## Apps & Hardware
Not all applications are available on different hardware targets.
Not all apps are available on different hardware targets.
* For applications built into the firmware, you have to specify a compatible application set using `FIRMWARE_APP_SET=...` fbt option. See [fbt docs](./fbt.md) for details on build configurations.
* For apps built into the firmware, you have to specify a compatible app set using `FIRMWARE_APP_SET=...` fbt option. See [fbt docs](./fbt.md) for details on build configurations.
* For applications built as external .faps, you have to explicitly specify compatible targets in application's manifest, `application.fam`. For example, to limit application to a single target, add `targets=["f7"],` to the manifest. It won't be built for other targets.
* For apps built as external FAPs, you have to explicitly specify compatible targets in the app's manifest, `application.fam`. For example, to limit the app to a single target, add `targets=["f7"],` to the manifest. It won't be built for other targets.
For details on application manifests, check out [their docs page](./AppManifests.md).
For details on app manifests, check out [their docs page](./AppManifests.md).
## Building Firmware for a Specific Target
You have to specify TARGET_HW (and, optionally, FIRMWARE_APP_SET) for `fbt` to build firmware for non-default target. For example, building and flashing debug firmware for f18 can be done with
You have to specify TARGET_HW (and, optionally, FIRMWARE_APP_SET) for `fbt` to build firmware for a non-default target. For example, building and flashing debug firmware for f18 can be done with
./fbt TARGET_HW=18 flash_usb_full

View file

@ -10,7 +10,7 @@ There are times when your Flipper feels blue and doesn't respond to any of your
- Release `LEFT` and `BACK`
This combo performs a hardware reset by pulling the MCU reset line down.
Main components involved: Keys -> DD8(NC7SZ32M5X, OR-gate) -> DD1(STM32WB55, MCU).
Main components involved: Keys → DD8(NC7SZ32M5X, OR-gate) → DD1(STM32WB55, MCU).
It won't work only in one case:
@ -26,7 +26,7 @@ It won't work only in one case:
- Release the `BACK` key
This combo performs a reset by switching SYS power line off and then on.
Main components involved: Keys -> DD6(bq25896, charger).
Main components involved: Keys DD6(bq25896, charger).
It won't work only in one case:

View file

@ -2,7 +2,7 @@
## Executing code from RAM
In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. System image executing in RAM has full write access to Flipper's entire flash memory — something that's not possible when running main code from the same flash.
In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. The system image executing in RAM has full write access to Flipper's entire flash memory — something that's not possible when running main code from the same flash.
We leverage that boot mode to perform OTA firmware updates, including operations on a radio stack running on the second MCU core.

View file

@ -5,7 +5,7 @@
Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they are correct.
They are crucial for writing robust, bug-free code.
Flipper Zero firmware includes a separate application called [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests).
Flipper Zero firmware includes a separate app called [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests).
It is run directly on Flipper devices in order to employ their hardware features and rule out any platform-related differences.
When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features.
@ -28,13 +28,13 @@ See [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/blob/d
#### Entry point
The common entry point for all tests is the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/test_index.c) source file.
The common entry point for all tests is the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests) app. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/test_index.c) source file.
#### Test assets
Some unit tests require external data in order to function. These files (commonly called assets) reside in the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.).
### Application-specific
### App-specific
#### Infrared
@ -50,9 +50,9 @@ To add unit tests for your protocol, follow these steps:
Each unit test has three sections:
1. `decoder` - takes in a raw signal and outputs decoded messages.
2. `encoder` - takes in decoded messages and outputs a raw signal.
3. `encoder_decoder` - takes in decoded messages, turns them into a raw signal, and then decodes again.
1. `decoder` takes in a raw signal and outputs decoded messages.
2. `encoder` takes in decoded messages and outputs a raw signal.
3. `encoder_decoder` takes in decoded messages, turns them into a raw signal, and then decodes again.
Infrared test asset files have an `.irtest` extension and are regular `.ir` files with a few additions.
Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder, these two are switched.
@ -61,4 +61,4 @@ Decoded data is represented in arrays (since a single raw signal may be decoded
##### Getting raw signals
Recording raw IR signals are possible using the Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format.
Recording raw IR signals is possible using Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards the Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format.

View file

@ -41,11 +41,11 @@ When the user presses a button, a whole set of parameters is transmitted to the
In order to add a particular air conditioner to the universal remote, 6 signals must be recorded: `Off`, `Dh`, `Cool_hi`, `Cool_lo`, `Heat_hi`, and `Heat_lo`.
Each signal (except `Off`) is recorded using the following algorithm:
1. Get the remote and press the **Power Button** so that the display shows that A/C is ON.
1. Get the remote and press the **POWER** button so that the display shows that A/C is ON.
2. Set the A/C to the corresponding mode (see table below), leaving other parameters such as fan speed or vane on **AUTO** (if applicable).
3. Press the **POWER** button to switch the A/C off.
4. Start learning a new remote on Flipper if it's the first button or press `+` to add a new button otherwise.
5. Point the remote to Flipper's IR receiver as directed and press **POWER** button once again.
5. Point the remote to Flipper's IR receiver as directed and press the **POWER** button once again.
6. Save the resulting signal under the specified name.
7. Repeat steps 2-6 for each signal from the table below.

View file

@ -0,0 +1,88 @@
# Debugging via the Devboard {#dev_board_debugging_guide}
On this page, you'll learn about how debugging via the Wi-Fi Developer Board works. To illustrate this process, we'll start a debug session for Flipper Zero's firmware in VS Code using the native Flipper Build Tool.
***
## Overview
The Developer Board acts as the debug probe, which provides a bridge between the IDE (integrated development environment) with a debugger running on a host computer and the target microcontroller (in your Flipper Zero). The user controls the debugging process on the computer connected to the Developer Board via [Wi-Fi](#dev_board_wifi_connection) or [USB cable](#dev_board_usb_connection).
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_hardware_CDN.jpg width=700
Data exchange between the Wi-Fi Developer Board and your Flipper Zero is conducted via the Serial Wire Debug interface. The following GPIO pins serve this purpose:
- **Pin 10:** Serial Wire Clock (SWCLK)
- **Pin 12:** Serial Wire Debug Data I/O (SWDIO)
To learn more about Flipper Zero pinout, visit [GPIO & modules in Flipper Docs](https://docs.flipper.net/gpio-and-modules).
***
## Prerequisites
### Step 1. Installing Git
You'll need Git installed on your computer to clone the firmware repository. If you don't have Git, install it following the [official installation guide](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
### Step 2. Building the firmware
Before starting debugging, you need to clone and build Flipper Zero firmware:
1. Open the **Terminal** (on Linux & macOS) or **PowerShell** (on Windows) in the directory where you want to store the firmware source code.
2. Clone the firmware repository:
```
git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git
cd flipperzero-firmware
```
3. Run the **Flipper Build Tool (FBT)** to build the firmware:
```
./fbt
```
***
## Debugging the firmware
From the **flipperzero-firmware** directory that you cloned earlier, run the following command:
```
./fbt flash
```
This will upload the firmware you've just built to your Flipper Zero via the Developer Board. After that, you can start debugging the firmware. We recommend using **VS Code** with the recommended extensions (as described below), and we have pre-made configurations for it.
To debug in **VS Code**, do the following:
1. In VS Code, open the **flipperzero-firmware** directory.
2. You should see a notification about recommended extensions. Install them.
If there were no notifications, open the **Extensions** tab, enter `@recommended` in the search bar, and install the workspace recommendations.
3. Run the `./fbt vscode_dist` command. This will generate the VS Code configuration files needed for debugging.
4. In VS Code, open the **Run and Debug** tab and select a debugger from the dropdown menu:
- **Attach FW (blackmagic):** Can be used via **Wi-Fi** or **USB**
- **Attach FW (DAP):** Can be used via **USB** only
Note that when debugging via USB, you need to make sure the selected debugger matches the debug mode on your Devboard. To check the debug mode on your Devboard, access the Devboard's web interface as described [here](#dev_board_wifi_connection) and check the **USB mode** field. If you want to use a different debug mode, enable this mode by following the steps in [Devboard debug modes](#dev_board_debug_modes).
5. If needed, flash your Flipper Zero with the `./fbt flash` command, then click the ▷ **Start Debugging** button in the debug sidebar to start the debugging session.
6. Note that starting a debug session halts the execution of the firmware, so you'll need to click the I▷ **Continue** button on the toolbar at the top of your VS Code window to continue execution.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_VS_Code.jpg width=900
> [!note]
> If you want to use a different debug mode on your Developer Board, visit [Devboard debug modes](#dev_board_debug_modes).
>
> If you want to read logs via the Developer Board, see [Reading logs via the Devboard](#dev_board_reading_logs).
>
> To learn about debugging in VS Code, see [VS Code official guide](https://code.visualstudio.com/docs/editor/debugging).

View file

@ -0,0 +1,33 @@
# Devboard debug modes {#dev_board_debug_modes}
The Wi-Fi Devboard for Flipper Zero supports **Black Magic** and **DAPLink** debug modes, and you can switch between them depending on your needs. Note that available modes depend on connection:
- **Wi-Fi:** Only **Black Magic** mode is available.
- **USB:** Switch between **Black Magic** (default) and **DAPLink**. Learn more about switching debug modes for USB connection below.
> [!note]
> Black Magic mode doesn't support RTOS threads, but you can still perform other debugging operations.
***
## Switching debug modes for USB connection
Switching debug modes for working via USB has to be done wirelessly (yes, you read that correctly). Additionally, depending on how the Devboard wireless connection is configured, you may need to follow different steps for **Wi-Fi access point mode** or **Wi-Fi client mode**:
1. If the Devboard isn't connected to your Flipper Zero, turn off your Flipper Zero and connect the Developer Board, then turn the device back on.
2. Access the Devboard's web interface:
- [Wi-Fi access point mode](#wifi-access-point)
- [Wi-Fi client mode](#wifi-client-mode)
3. In the **WiFi** tab, click the **USB mode** option and select **BlackMagicProbe** or **DapLink**.
4. Click **SAVE**, then click **REBOOT** to apply the changes.
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_switching_modes_CDN.jpg width=700
> [!note]
> After switching debug modes on your Devboard, remember to select the same debugger in **VS Code** in the **Run and Debug** tab, and click the ▷ **Start Debugging** button.

View file

@ -1,122 +1,112 @@
# Firmware update on Developer Board {#dev_board_fw_update}
It's important to regularly update your Developer Board to ensure that you have access to the latest features and bug fixes. This tutorial will guide you through the necessary steps to update the firmware of your Developer Board.
It's important to regularly update your Developer Board to ensure that you have access to the latest features and bug fixes. This page will guide you through the necessary steps to update the firmware of your Developer Board.
This tutorial assumes that you're familiar with the basics of the command line. If youre not, please refer to the [Windows](https://www.digitalcitizen.life/command-prompt-how-use-basic-commands/) or [MacOS/Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials.
> [!note]
> This guide assumes that you're familiar with the basics of the command line. If you're new to it, we recommend checking out these [Windows](https://learn.microsoft.com/en-us/powershell/scripting/learn/ps101/01-getting-started?view=powershell-7.4) or [macOS/Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials.
***
## Installing the micro Flipper Build Tool
## Step 1. Install the micro Flipper Build Tool
Micro Flipper Build Tool (uFBT) is a cross-platform tool that enables basic development tasks for Flipper Zero, such as building and debugging applications, flashing firmware, and creating VS Code development configurations.
is a cross-platform tool developed and supported by our team that enables basic development tasks for Flipper Zero, such as building and debugging applications, flashing firmware, creating VS Code development configurations, and flashing firmware to the Wi-Fi Developer Board.
Install uFBT on your computer by running the following command in the Terminal:
**On Linux & macOS:**
**For Linux & macOS:**
Run the following command in the Terminal:
```text
```
python3 -m pip install --upgrade ufbt
```
**For Windows:**
**On Windows:**
```text
py -m pip install --upgrade ufbt
```
1. Download the latest version of Python on
2. Run the following command in the PowerShell
If you want to learn more about uFBT, visit [the project's page](https://pypi.org/project/ufbt/).
```
py -m pip install --upgrade ufbt
```
***
## Connecting the Developer Board to your computer
## Step 2. Connect the Devboard to PC
To update the firmware, you need to switch your Developer Board to Bootloader mode, connect to a PC via a USB cable, and make sure that the PC detects the Developer Board:
1. List all of the serial devices on your computer.
**Windows**
- **macOS:** Run the `ls /dev/cu.*` command in the Terminal.
On Windows, go to Device Manager and expand the Ports (COM & LPT) section.
- **Linux:** Run the `ls /dev/tty*` command in the Terminal.
**macOS**
On macOS, you can run the following command in the Terminal:
```text
ls /dev/cu.*
```
**Linux**
On Linux, you can run the following command in the Terminal:
```text
ls /dev/tty*
```
View the devices in the list.
- **Windows:** Go to **Device Manager** and expand the **Ports (COM & LPT)** section.
2. Connect the Developer Board to your computer using a USB-C cable.
![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/Aq7gfMI-m_5H6sGGjwb4I_monosnap-miro-2023-07-19-19-47-39.jpg)
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_update_wired_connection.jpg width=700
3. Switch your Developer Board to Bootloader mode:
3.1. Press and hold the **BOOT** button.
1. Press and hold the **BOOT** button.
2. Press the **RESET** button while holding the **BOOT** button.
3. Release the **BOOT** button.
3.2. Press the **RESET** button while holding the **BOOT** button.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_to_bootloader.png width=700
3.3. Release the **BOOT** button.\
![You can easily switch the Dev Board to Bootloader mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KynP9iT6sJ3mXLaLyI82__image.png)
4. Repeat Step 1 and view the name of your Developer Board that appeared in the list.
For example, on macOS:
```text
/dev/cu.usbmodem01
```
4. Repeat **Step 1** and view the name of your Developer Board that appeared in the list.
***
## Flashing the firmware
## Step 3. Flash the firmware
To flash the firmware onto your Developer Board, run the following command in the terminal:
**On Linux & macOS:**
```text
```
python3 -m ufbt devboard_flash
```
**On Windows:** Run the following command in the PowerShell:
```
py -m ufbt devboard_flash
```
You should see the following message: `WiFi board flashed successfully`.
## If flashing failed
### If flashing failed
If you get an error message during the flashing process, such as this:
Occasionally, you might get an error message during the flashing process, such as:
```text
```
A fatal error occurred: Serial data stream stopped: Possible serial noise or corruption.
```
Or this:
*or*
```text
```
FileNotFoundError: [Errno 2] No such file or directory: '/dev/cu.usbmodem01'
```
Try doing the following:
To fix it, try doing the following:
* Disconnect the Developer Board from your computer, then reconnect it.
- Disconnect the Developer Board from your computer, then reconnect it. After that, switch your Developer Board to Bootloader mode once again, as described in
* Use a different USB port on your computer.
- Use a different USB port on your computer.
* Use a different USB-C cable.
- Use a different USB-C cable.
***
## Finishing the installation
After flashing the firmware:
## Step 4. Finish the installation
1. Reboot the Developer Board by pressing the **RESET** button.
![Reset the Developer Board](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/rcQeKARgrVwa51tLoo-qY_monosnap-miro-2023-07-20-18-29-33.jpg)
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_after_flashing.jpg width=700
2. Disconnect and reconnect the USB-C cable.
The Developer Board should appear as a serial device on your computer. Now, you can use it with the Black Magic Debug client of your choice.
You've successfully updated the firmware of your Developer Board!
If you followed the **Get started with the Devboard** guide, you're ready for the next step: [Step 3. Plug the Devboard into Flipper Zero](#dev_board_get_started_step-3).

View file

@ -1,178 +1,80 @@
# Get started with the Dev Board {#dev_board_get_started}
The Wi-Fi Developer Board serves as a tool to debug the Flipper Zero firmware. To debug the firmware, the initial step involves compiling the firmware from its source code. This process enables the debugging functionality within the firmware and generates all the necessary files required for debugging purposes.
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_box_CDN.jpg width=700
> **NOTE:** Building and debugging the Flipper Zero firmware is fully supported on MacOS and Linux. Support for Windows is in beta test.
Before you start using your Devboard, you need to prepare your Flipper Zero and Devboard for debugging. In this guide, we'll walk you through all the necessary steps and provide links to explore the Devboard's capabilities further.
***
## Updating the firmware of your Developer Board
## Step 1. Enable Debug Mode on your Flipper Zero
Update the firmware of your Developer Board before using it. For more information, visit [Firmware update on Developer Board](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/update).
Since the main purpose of the Developer board is to debug applications on Flipper Zero, you first need to enable Debug Mode. To do so, go to **Settings → System** and set **Debug** to **ON**.
\image html https://cdn.flipperzero.one/Flipper_Zero_enamble_debug_CDN.jpg width=700
> [!note]
> Debug Mode needs to be re-enabled after each update of Flipper Zero's firmware.
Debug Mode allows you to debug your apps for Flipper Zero, as well as access debugging options in apps via the user interface and CLI. To learn more about Flipper Zero CLI, visit [Command-line interface in Flipper Docs](https://docs.flipper.net/development/cli).
\image html https://cdn.flipperzero.one/Flipper_Zero_Command_Line_Interface_CDN.jpg width=700
***
## Installing Git
## Step 2. Update firmware on the Developer Board
You'll need Git installed on your computer to clone the firmware repository. If you don't have Git, install it by doing the following:
* **MacOS**
On MacOS, install the **Xcode Command Line Tools** package, which includes Git as one of the pre-installed command-line utilities, by running in the Terminal the following command:
```text
xcode-select --install
```
* **Linux**
On Linux, you can install Git using your package manager. For example, on Ubuntu, run in the Terminal the following command:
```text
sudo apt install git
```
For other distributions, refer to your package manager documentation.
The Developer Board comes with stock firmware that may not include all the latest features and bug fixes. To ensure optimal performance, please update your board's firmware using the instructions in [Firmware update on Devboard](#dev_board_fw_update).
***
## Building the firmware
## Step 3. Plug the Devboard into Flipper Zero {#dev_board_get_started_step-3}
First, clone the firmware repository:
Once your Developer Board firmware is up to date, you can proceed to plug it into your Flipper Zero. Two important things to keep in mind:
```text
git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git
cd flipperzero-firmware
```
1. **Power off your Flipper Zero before plugging in the Developer Board.**
Then, run the **Flipper Build Tool** (FBT) to build the firmware:
If you skip this step, you may corrupt the data stored on the microSD card. Connecting external modules with a large capacitive load may affect the microSD card's power supply since both the microSD card and external module are powered from the same 3.3 V power source inside Flipper Zero.
```text
./fbt
```
2. **Make sure the Developer Board is inserted all the way in.**
If your Flipper Zero isn't in a silicone case, insert the module all the way in so there is no gap between your Flipper Zero and the Devboard. You may need to apply more force to insert it completely. After that, press and hold the **BACK** button to power on your Flipper Zero.
\image html https://cdn.flipperzero.one/Flipper_Zero_external_module_without_case_CDN.jpg width=700
If your Flipper Zero is in a silicone case, insert the module all the way in so there is no gap in the middle between the silicone case and the module. After that, press and hold the **BACK** button to power on your Flipper Zero.
\image html https://cdn.flipperzero.one/Flipper_Zero_external_module_with_case_CDN.jpg width=700
***
## Connecting the Developer Board
## Step 4. Connect to a computer
The Developer Board can work in the **Wired** mode and two **Wireless** modes: **Wi-Fi access point (AP)** mode and **Wi-Fi client (STA)** mode. The Wired mode is the simplest to set up, but requires a USB Type-C cable. The Wireless modes are more complex to set up, but they allow you to debug your Flipper Zero wirelessly.
Now, you can connect the Developer Board to your computer via USB or Wi-Fi, depending on your needs. We described both methods in separate documents:
> **NOTE:** Use the following credentials when connecting to the Developer Board in **Wi-Fi access point** mode:\n
Name: **blackmagic**\n
Password: **iamwitcher**
## Wired
![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/jZdVlRTPVdSQVegzCyXp7_monosnap-miro-2023-06-22-16-28-06.jpg)
To connect the Developer Board in **Wired** mode, do the following:
1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on.
2. On your computer, open the **Terminal** and run the following:
* **MacOS**
```text
ls /dev/cu.*
```
* **Linux**
```text
ls /dev/tty*
```
Note the list of devices.
3. Connect the Developer Board to your computer via a USB-C cable.
4. Rerun the command. Two new devices have to appear: this is the Developer Board.
> **NOTE:** If the Developer Board doesn't appear in the list of devices, try using a different cable, USB port, or computer.
>
> **NOTE:** Flipper Zero logs can only be viewed when the Developer Board is connected via USB. The option to view logs over Wi-Fi will be added in future updates. For more information, visit [Reading logs via the Dev Board](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/reading-logs).
## Wireless
### Wi-Fi access point (AP) mode
![The Developer Board in Wi-Fi access point mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/tKRTMHAuruiLSEce2a8Ve_monosnap-miro-2023-06-22-16-39-17.jpg)
Out of the box, the Developer Board is configured to work as a **Wi-Fi access point**. This means it'll create its own Wi-Fi network to which you can connect. If your Developer Board doesn't create a Wi-Fi network, it is probably configured to work in **Wi-Fi client** mode. To reset your Developer Board back to **Wi-Fi access point** mode, press and hold the **BOOT** button for 10 seconds, then wait for the module to reboot.
![You can reconfigure the Developer Board mode by pressing and holding the BOOT button](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/57eELJsAwMxeZCEA1NMJw_monosnap-miro-2023-06-22-20-33-27.jpg)
To connect the Developer Board in **Wi-Fi access point** mode, do the following:
1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on.
2. Open Wi-Fi settings on your client device (phone, laptop, or other).
3. Connect to the network:
* Name: **blackmagic**
* Password: **iamwitcher**
4. To configure the Developer Board, open a browser and go to `http://192.168.4.1`.
### Wi-Fi client (STA) mode
![The Developer Board in Wi-Fi client mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/xLQpFyYPfUS5Cx0uQhrNd_monosnap-miro-2023-06-23-12-34-36.jpg)
To connect the Developer Board in **Wi-Fi client** mode, you need to configure it to connect to your Wi-Fi network by doing the following:
1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on.
2. Connect to the Developer Board in **Wi-Fi access point** mode.
3. In a browser, go to the configuration page on `http://192.168.4.1`.
4. Select the **STA** mode and enter your network's **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby networks.
5. Save the configuration and reboot the Developer Board.
![In the Wi-Fi tab, you can set the Developer Board mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/klbLVj8lz2bEvm7j4wRaj_monosnap-miro-2023-06-23-13-06-32.jpg)
After rebooting, the Developer Board connects to your Wi-Fi network. You can connect to the device using the mDNS name **blackmagic.local** or the IP address it got from your router (you'll have to figure this out yourself, every router is different).
After connecting to your debugger via <http://blackmagic.local>, you can find its IP address in the **SYS** tab. You can also change the debugger's mode to **AP** or **STA** there.
![In the SYS tab, you can view the IP address of your Developer Board](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/5XbUptlfqzlV0p6hRUqiG_monosnap-miro-2023-06-22-18-11-30.jpg)
- **[Via USB cable](#dev_board_usb_connection)** for debugging in DAP Link or Black Magic mode, and reading logs.
- [Via Wi-Fi](#dev_board_wifi_connection) for debugging in Black Magic mode.
***
## Debugging the firmware
## Next steps
Open the **Terminal** in the **flipperzero-firmware** directory that you cloned earlier and run the following command:
You are ready to debug now! To further explore what you can do with the Devboard, check out these pages:
```text
./fbt flash
```
- [Debugging via the Devboard](#dev_board_debugging_guide)
- [Devboard debug modes](#dev_board_debug_modes)
- [Reading logs via the Devboard](#dev_board_reading_logs)
These guides should help you get started with your Devboard. If you have any questions or you want to share your experience, don't hesitate to join our community on [Reddit](https://www.reddit.com/r/flipperzero/) and [Discord](https://discord.com/invite/flipper), where we have a dedicated #wifi-devboard channel.
This will upload the firmware you've just built to your Flipper Zero via the Developer Board. After that, you can start debugging the firmware using the [GDB](https://www.gnu.org/software/gdb/) debugger. We recommend using **VSCode** with the recommended extensions, and we have pre-made configurations for it.
To debug in **VSCode**, do the following:
1. In VSCode, open the **flipperzero-firmware** directory.
2. You should see a notification about recommended extensions. Install them.
If there were no notifications, open the **Extensions** tab, enter `@recommended` in the search bar, and install the workspace recommendations.
3. In the **Terminal**, run the `./fbt vscode_dist` command. This will generate the VSCode configuration files needed for debugging.
4. In VSCode, open the **Run and Debug** tab and select **Attach FW (blackmagic)** from the dropdown menu.
5. If needed, flash your Flipper Zero with the `./fbt flash` command, then click the **Play** button in the debug sidebar to start the debugging session.
6. Note that starting a debug session halts the execution of the firmware, so you'll need to click the **Continue** button on the toolbar at the top of your VSCode window to continue execution.
![Click Continue in the toolbar to continue execution of the firmware](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/lp8ygGaZ3DvWD3OSI9yGO_monosnap-miro-2023-06-23-17-58-09.jpg)
To learn about debugging, visit the following pages:
* [Debugging with GDB](https://sourceware.org/gdb/current/onlinedocs/gdb.pdf)
* [Debugging in VS Code](https://code.visualstudio.com/docs/editor/debugging)

View file

@ -8,9 +8,9 @@ The Developer Board allows you to read Flipper Zero logs via UART. Unlike readin
## Setting the log level
Depending on your needs, you can set the log level by going to **Main Menu -> Settings -> Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipperzero.one/basics/settings#d5TAt).
Depending on your needs, you can set the log level by going to **Main Menu → Settings → Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipper.net/basics/settings#d5TAt).
![You can manually set the preferred log level](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/INzQMw8QUsG9PXi30WFS0_monosnap-miro-2023-07-11-13-29-47.jpg)
\image html https://cdn.flipperzero.one/Flipper_Zero_log_level.jpg "You can manually set the preferred log level" width=700
***
@ -18,9 +18,9 @@ Depending on your needs, you can set the log level by going to **Main Menu -> Se
Depending on your operating system, you need to install an additional application on your computer to read logs via the Developer Board:
### MacOS
### macOS
On MacOS, you need to install the **minicom** communication program by doing the following:
On macOS, you need to install the **minicom** communication program by doing the following:
1. [Install Homebrew](https://brew.sh/) by running the following command in the Terminal:
@ -47,7 +47,7 @@ After installation of minicom on your macOS computer, you can connect to the Dev
Note the list of devices.
3. Connect the developer board to your computer using a USB Type-C cable.
![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png)
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700
4. Rerun the command. Two new devices have to appear: this is the Developer Board.
@ -100,7 +100,7 @@ After installation of minicom on your Linux computer, you can connect to the Dev
Note the list of devices.
3. Connect the developer board to your computer using a USB Type-C cable.
![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png)
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700
4. Rerun the command. Two new devices have to appear: this is the Developer Board.
@ -143,17 +143,17 @@ On Windows, do the following:
2. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on.
3. Connect the developer board to your computer using a USB Type-C cable.
![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png)
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700
4. Find the serial port that the developer board is connected to by going to **Device Manager -> Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board.
![Find the serial port in your Device Manager](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KKLQJK1lvqmI5iab3d__C_image.png)
4. Find the serial port that the developer board is connected to by going to **Device Manager Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_Device_Manager.png width=700
5. Run the PuTTY application and select **Serial** as the connection type.
6. Enter the port number you found in the previous step into the **Serial line** field.
7. Set the **Speed** parameter to **230400** and click **Open**.
![Set speed to 230400](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/ROBSJyfQ_CXiy4GUZcPbs_monosnap-miro-2023-07-12-13-56-47.jpg)
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_PuTTy.jpg width=700
8. View logs of your Flipper Zero in the PuTTY terminal window.

View file

@ -0,0 +1,22 @@
# USB connection to the Devboard {#dev_board_usb_connection}
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_USB_connection_CDN.jpg width=700
To connect to the Developer Board via USB, do the following:
1. If the Devboard isn't connected to your Flipper Zero, turn off your Flipper Zero and connect the Developer Board to it. Then, turn your Flipper Zero back on.
2. On your computer, check the list of serial devices.
- **macOS:** On your computer, run `ls /dev/cu.*` in the Terminal.
- **Linux:** On your computer, run `ls /dev/tty*` in the Terminal.
- **Windows:** Go to **Device Manager** and expand the **Ports (COM & LPT)** section.
3. Connect the Devboard to your computer via a USB-C cable.
4. Repeat **Step 2**. Two new devices will appear — this is the Developer Board.
> [!warning]
> If the Developer Board doesn't appear in the list of devices, try using a different cable, USB port, or computer.

View file

@ -0,0 +1,60 @@
# Wi-Fi connection to the Devboard {#dev_board_wifi_connection}
You can connect to the Developer Board wirelessly in two ways:
- **Wi-Fi access point mode (default):** The Devboard creates its own Wi-Fi network, which you can connect to in order to access its web interface and debug via Wi-Fi. The downside is that you will need to disconnect from your current Wi-Fi network, resulting in a loss of internet connection.
- **Wi-Fi client mode:** You can connect to the Devboard through an existing Wi-Fi network, allowing you to access the Devboard web interface and debug via Wi-Fi without losing your internet connection.
Let's go over both of these modes below.
***
## Wi-Fi access point (AP) mode {#wifi-access-point}
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_Access_Point_CDN.jpg width=700
Out of the box, the Developer Board is configured to work as a Wi-Fi access point. To connect the Developer Board in this mode, do the following:
1. Plug the Wi-Fi Devboard into your Flipper Zero by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on.
2. Open Wi-Fi settings on your client device (phone, laptop, or other).
3. Connect to the network:
Name: `blackmagic`
Password: `iamwitcher`
If your computer fails to find the **blackmagic** network, read the [troubleshooting section](#wifi-access-point_troubleshooting) below.
4. To access the Devboard's web interface, open a browser and go to <http://192.168.4.1> or <http://blackmagic.local>.
### If your computer fails to find the black magic network {#wifi-access-point_troubleshooting}
- Reset Wi-Fi connection on your computer.
- The Developer Board is probably configured to work in Wi-Fi client mode. → Reset your Developer Board settings to default by pressing and holding the **BOOT** button for **10 seconds**, then wait for the Devboard to reboot. After the reset, the Devboard will work in Wi-Fi access point mode.
\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot.jpg width=700
***
## Wi-Fi client (STA) mode {#wifi-client-mode}
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_STA_CDN.jpg width=700
To connect the Developer Board in **Wi-Fi client** mode, you need to configure it to connect to your Wi-Fi network by doing the following:
1. Plug the Wi-Fi Devboard into your Flipper Zero by turning off your Flipper Zero and connecting the Developer Board, and then turning the device back on.
2. Connect to the Developer Board in [Wi-Fi access point](#wifi-access-point) mode.
3. In a browser, go to the Devboard's web interface at <http://192.168.4.1> or <http://blackmagic.local>.
4. Select the **STA** mode and enter your network's **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby 2.4 GHz networks (5 GHz networks aren't supported).
5. Save the configuration and reboot the Developer Board.
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_connect_to_WiFi_CDN.jpg width=700
6. Now, you can access the Devboard's web interface at [http://blackmagic.local](https://blackmagic.local) via the existing Wi-Fi network without losing connection to the internet.

View file

@ -42,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.
PROJECT_NAME = "Flipper Zero Firmware"
PROJECT_NAME = "Flipper Developer Docs"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version

View file

@ -0,0 +1,7 @@
/**
@page app_publishing Publishing to the Apps Catalog
You can publish your app in the Flipper Apps Catalog. Users will be able to download your app and install it on their Flipper Zero via [mobile apps](https://flpr.app/) and [Flipper Lab](https://lab.flipper.net/apps). Check out the documentation below:
- [Apps Catalog: Contribution Guide](https://github.com/flipperdevices/flipper-application-catalog/blob/main/documentation/Contributing.md) — How to publish and update your app in the Apps Catalog
*/

View file

@ -1,12 +1,12 @@
/**
@page applications Application Programming
@page applications App Development
Flipper Zero features full support for custom applications which (usually) do not require any changes to the firmware.
Flipper Zero features full support for custom apps which (usually) do not require any changes to the firmware.
For easy application development, a software tool called [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) is available.
For easy app development, a software tool called [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) is available.
- @subpage vscode - Flipper Zero integration for VS Code
- @subpage apps_on_sd_card - Creating apps that can be dynamically loaded from the SD card
- @subpage app_manifests - How applications announce themselves to the system
- @subpage app_examples - Various application examples, complete with the source code
- @subpage apps_on_sd_card — Creating apps that can be dynamically loaded from the SD card
- @subpage app_manifests — How apps announce themselves to the system
- @subpage app_examples — Various app examples, complete with the source code
- @subpage app_publishing — Learn how to publish and update your app in the Apps Catalog
*/

View file

@ -1,10 +1,37 @@
/**
@page dev_board Developer Board
@page dev_board Wi-Fi Developer Board
[ESP32-based development board](https://shop.flipperzero.one/collections/flipper-zero-accessories/products/wifi-devboard).
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_laptop_CDN.jpg width=700
- @subpage dev_board_get_started - Quick start for new users
- @subpage dev_board_reading_logs - Find out what is currently happening on the system
- @subpage dev_board_fw_update - Keep the developer board up to date
Wi-Fi-enabled Developer Board brings debugging and firmware update capabilities to your Flipper Zero. The Developer Board is based on the ESP32-S2 MCU with custom firmware incorporating Black Magic Debug and CMSIS-DAP, and is built with ESP-IDF. It can flash and debug various microprocessors and microcontrollers (including the one used in your Flipper Zero) via Wi-Fi or USB cable.
The Developer Board provides a debug interface, allowing developers to halt program execution, set breakpoints, inspect variables and memory, and step through code execution.
<div align="center">
<a href="https://shop.flipperzero.one/products/wifi-devboard"><img src="https://cdn.flipperzero.one/Get_developer_board_button_green_600.png" width="350" align="center" alt="Get your Wi-Fi Developer Board"/></a>
</div>
<br>
Check out these guides to get started with the Devboard:
- @subpage dev_board_get_started — Quick start for new users
- @subpage dev_board_fw_update — Keep the Developer Board up to date
- @subpage dev_board_usb_connection — Instructions for Windows, macOS and Linux
- @subpage dev_board_wifi_connection — Instructions for Windows, macOS and Linux
- @subpage dev_board_debugging_guide — Learn how it works
- @subpage dev_board_debug_modes — Available modes and how to switch between them
- @subpage dev_board_reading_logs — Find out what is currently happening on the system
## Hardware
The Developer Board is equipped with an [ESP32-S2-WROVER](https://www.espressif.com/en/products/socs/esp32-s2) module, which includes built-in Wi-Fi capabilities. It also offers GPIO pins for easy connectivity to various targets. Additionally, the Developer Board features a USB Type-C connector for data transfer and power supply. For user interaction, the Developer Board has tactile switches.
\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_hardware_CDN.jpg width=700
## Additional resources
To learn more about the Wi-Fi Developer Board hardware, visit [Schematics in Flipper Docs](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/schematics).
For additional information about Flipper Zero GPIO pins, visit [GPIO & modules in Flipper Docs](https://docs.flipperzero.one/gpio-and-modules).
*/

View file

@ -3,7 +3,8 @@
Hardware and software tools for all kinds of programming.
- @subpage fbt - Official build and deployment tool for Flipper Zero
- @subpage dev_board - ESP32-based development board
- @subpage ota_updates - Standalone firmware self-update mechanism
- @subpage fbt — Official build and deployment tool for Flipper Zero
- @subpage vscode — Flipper Zero integration for VS Code
- @subpage dev_board — ESP32-based development board
- @subpage ota_updates — Standalone firmware self-update mechanism
*/

View file

@ -1,10 +1,13 @@
/**
@page app_examples Application Examples
@page app_examples App Examples
A collection of examples covering various aspects of application programming for Flipper Zero.
A collection of examples covering various aspects of app development for Flipper Zero.
- @subpage example_app_images - Using images and icons in an application
- @subpage example_app_assets - Using application-specific asset folders
- @subpage example_app_data - Using application-specific data folders
- @subpage example_thermo - Reading data from a 1-Wire thermometer
- @subpage example_number_input — Using a simple keyboard that limits user inputs to a full number (integer)
- @subpage example_app_images — Using images and icons in an app
- @subpage example_app_assets — Using app-specific asset folders
- @subpage example_app_data — Using app-specific data folders
- @subpage example_thermo — Reading data from a 1-Wire thermometer
You can find more app examples in the [repository on GitHub](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples).
*/

View file

@ -3,6 +3,6 @@
Expansion modules are special pieces of hardware designed to interface with Flipper's GPIO connector, such as the [Video Game Module](https://shop.flipperzero.one/collections/flipper-zero-accessories/products/video-game-module-for-flipper-zero).
- @subpage expansion_protocol - Transport protocol for smart expansion modules
- @subpage expansion_protocol Transport protocol for smart expansion modules
*/

View file

@ -9,5 +9,6 @@ Descriptions of various file formats used in Flipper Zero, grouped by applicatio
- @subpage lfrfid_file_format
- @subpage nfc_file_format
- @subpage subghz_file_format
- @subpage heatshrink_file_format
*/

View file

@ -48,11 +48,11 @@ $extrastylesheet
<tbody>
<tr id="projectrow">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"$logosize/></td>
<td id="projectlogo"><a style="background: none;" href="index.html"><img alt="Logo" src="$relpath^$projectlogo"$logosize/></a></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign">
<div id="projectname">$projectname<!--BEGIN PROJECT_NUMBER--><span id="projectnumber">&#160;$projectnumber</span><!--END PROJECT_NUMBER-->
<div id="projectname"><a href="index.html" style="background: none; color: var(--foreground_color) !important;">$projectname</a><!--BEGIN PROJECT_NUMBER--><span id="projectnumber">&#160;$projectnumber</span><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>

View file

@ -1,26 +1,26 @@
/**
@mainpage Overview
Welcome to the Flipper Zero Firmware Developer Documentation!
Welcome to the Flipper Developer Documentation!
This documentation is intended for developers who want to modify the firmware of the Flipper Zero.
This documentation is intended for developers interested in modifying the Flipper Zero firmware, creating Apps and JavaScript programs, or working on external hardware modules for the device.
If you are looking for the user manual, please visit the [User Documentation](https://docs.flipperzero.one/) instead.
If you are looking for the user manual, please visit the [User Documentation](https://docs.flipper.net/) instead.
The documentation is divided into several sections, with all of them accessible from the sidebar on the left:
The documentation is divided into several sections. All of them are accessible from the sidebar on the left:
- @ref applications - Writing applications for Flipper Zero
- @ref system - Understanding the firmware's internals
- @ref file_formats - Saving and loading data to and from files
- @ref dev_tools - Hardware and software tools for all kinds of programming
- @ref expansion - Additional modules to expand Flipper's consciousness
- @ref misc - Various useful pieces of information
- @ref js - JS-based scripting engine documentation
- @ref dev_tools — Hardware and software tools for all kinds of programming
- @ref system Understanding the firmware's internals
- @ref applications — Developing apps for Flipper Zero
- @ref js — JS-based scripting engine
- @ref expansion Additional modules to expand Flipper's consciousness
- @ref file_formats — Saving and loading data to and from files
- @ref misc — Various useful pieces of information
Aside from the manually-written documentation files, there's also a few automatically-generated ones at the bottom of the sidebar:
These sections are all manually written. There are also a few automatically generated ones at the bottom of the sidebar:
- [Data Structures](annotated.html) - Every data structure in a list
- [Files](files.html) - Source file tree with easy navigation
- [Data Structures](annotated.html) Every data structure in a list
- [Files](files.html) Source file tree with easy navigation
These are generated from the source code and are useful for quickly finding the source code or API documentation for a particular function or data structure.
*/

View file

@ -1,21 +1,22 @@
/**
@page js JavaScript
This page contains some information on the Flipper Zero scripting engine, which is based on a modified mJS library
This page contains some information on the Flipper Zero scripting engine, which is based on a modified mJS library.
- [Brief mJS description](https://github.com/cesanta/mjs/blob/master/README.md)
- @subpage js_data_types
- @subpage js_builtin
JavaScript Modules
JS modules use the Flipper app plugin system. Each module is compiled into a .fal library file and is located on a microSD card. Here is a list of implemented modules:
## JavaScript modules
- @subpage js_badusb - BadUSB module
- @subpage js_serial - Serial module
- @subpage js_math - Math module
- @subpage js_dialog - Dialog module
- @subpage js_submenu - Submenu module
- @subpage js_textbox - Textbox module
- @subpage js_notification - Notifications module
JS modules use the Flipper app plugin system. Each module is compiled into a `.fal` library file and is located on a microSD card. Here is a list of implemented modules:
- @subpage js_badusb — BadUSB module
- @subpage js_serial — Serial module
- @subpage js_math — Math module
- @subpage js_dialog — Dialog module
- @subpage js_submenu — Submenu module
- @subpage js_textbox — Textbox module
- @subpage js_notification — Notifications module
*/

View file

@ -3,7 +3,7 @@
Various pieces of information that do not fall into other categories.
- @subpage lfrfid_raw - Collecting raw data from LFRFID tags
- @subpage key_combos - Different key combination shortcuts for Flipper Zero
- @subpage universal_remotes - Creating and improving IR universal remote libraries
- @subpage lfrfid_raw — Collecting raw data from LF RFID tags
- @subpage key_combos Different key combination shortcuts for Flipper Zero
- @subpage universal_remotes Creating and improving IR universal remote libraries
*/

View file

@ -1,13 +1,13 @@
/**
@page system System Programming
Lower level aspects of software development for Flipper Zero.
Lower-level aspects of software development for Flipper Zero.
- @subpage unit_tests - Automated testing, a crucial part of the development process
- @subpage furi_check - Hard checks for exceptional situations
- @subpage furi_hal_bus - Access the on-chip peripherals in a safe way
- @subpage furi_hal_debugging - Low level debugging features
- @subpage hardware_targets - Support for different hardware platforms
- @subpage firmware_assets - Various files required for building the firmware
- @subpage dolphin_assets - Animations for the Dolphin game
- @subpage unit_tests Automated testing, a crucial part of the development process
- @subpage furi_check Hard checks for exceptional situations
- @subpage furi_hal_bus Access the on-chip peripherals in a safe way
- @subpage furi_hal_debugging — Low-level debugging features
- @subpage hardware_targets Support for different hardware platforms
- @subpage firmware_assets Various files required for building the firmware
- @subpage dolphin_assets Animations for the Dolphin game
*/

Some files were not shown because too many files have changed in this diff Show more