diff --git a/.gitignore b/.gitignore index 067264322..e33c10125 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,7 @@ openocd.log # PVS Studio temporary files .PVS-Studio/ PVS-Studio.log +*.PVS-Studio.* .gdbinit diff --git a/applications/external/avr_isp_programmer/avr_isp_app.c b/applications/external/avr_isp_programmer/avr_isp_app.c index e199f0c12..740dc3610 100644 --- a/applications/external/avr_isp_programmer/avr_isp_app.c +++ b/applications/external/avr_isp_programmer/avr_isp_app.c @@ -21,13 +21,6 @@ static void avr_isp_app_tick_event_callback(void* context) { AvrIspApp* avr_isp_app_alloc() { AvrIspApp* app = malloc(sizeof(AvrIspApp)); - // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering - uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { - furi_hal_power_enable_otg(); - furi_delay_ms(10); - } - app->file_path = furi_string_alloc(); furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); app->error = AvrIspErrorNoError; @@ -102,6 +95,13 @@ AvrIspApp* avr_isp_app_alloc() { AvrIspViewChipDetect, avr_isp_chip_detect_view_get_view(app->avr_isp_chip_detect_view)); + // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering + uint8_t attempts = 0; + while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { + furi_hal_power_enable_otg(); + furi_delay_ms(10); + } + scene_manager_next_scene(app->scene_manager, AvrIspSceneStart); return app; @@ -110,6 +110,11 @@ AvrIspApp* avr_isp_app_alloc() { void avr_isp_app_free(AvrIspApp* app) { furi_assert(app); + // Disable 5v power + if(furi_hal_power_is_otg_enabled()) { + furi_hal_power_disable_otg(); + } + // Submenu view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewSubmenu); submenu_free(app->submenu); @@ -159,11 +164,6 @@ void avr_isp_app_free(AvrIspApp* app) { // Path strings furi_string_free(app->file_path); - // Disable 5v power - if(furi_hal_power_is_otg_enabled()) { - furi_hal_power_disable_otg(); - } - free(app); } diff --git a/applications/external/dap_link/gui/views/dap_main_view.c b/applications/external/dap_link/gui/views/dap_main_view.c index c5c8f9dff..f54c5e3d5 100644 --- a/applications/external/dap_link/gui/views/dap_main_view.c +++ b/applications/external/dap_link/gui/views/dap_main_view.c @@ -51,10 +51,10 @@ static void dap_main_view_draw_callback(Canvas* canvas, void* _model) { canvas_set_color(canvas, ColorBlack); if(model->dap_active) { canvas_draw_icon(canvas, 14, 16, &I_ArrowUpFilled_12x18); - canvas_draw_icon(canvas, 28, 16, &I_ArrowDownFilled_12x18); + canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpFilled_12x18, IconRotation180); } else { canvas_draw_icon(canvas, 14, 16, &I_ArrowUpEmpty_12x18); - canvas_draw_icon(canvas, 28, 16, &I_ArrowDownEmpty_12x18); + canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpEmpty_12x18, IconRotation180); } switch(model->mode) { @@ -76,9 +76,9 @@ static void dap_main_view_draw_callback(Canvas* canvas, void* _model) { } if(model->rx_active) { - canvas_draw_icon(canvas, 101, 16, &I_ArrowDownFilled_12x18); + canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpFilled_12x18, IconRotation180); } else { - canvas_draw_icon(canvas, 101, 16, &I_ArrowDownEmpty_12x18); + canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpEmpty_12x18, IconRotation180); } canvas_draw_str_aligned(canvas, 100, 38, AlignCenter, AlignTop, "UART"); diff --git a/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png b/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png deleted file mode 100644 index 6007f74ab..000000000 Binary files a/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png and /dev/null differ diff --git a/applications/external/dap_link/icons/ArrowDownFilled_12x18.png b/applications/external/dap_link/icons/ArrowDownFilled_12x18.png deleted file mode 100644 index 5541e7723..000000000 Binary files a/applications/external/dap_link/icons/ArrowDownFilled_12x18.png and /dev/null differ diff --git a/applications/external/gpio_reader_b/views/gpio_usb_uart.c b/applications/external/gpio_reader_b/views/gpio_usb_uart.c index c7406d29b..f71dcccab 100644 --- a/applications/external/gpio_reader_b/views/gpio_usb_uart.c +++ b/applications/external/gpio_reader_b/views/gpio_usb_uart.c @@ -80,9 +80,9 @@ static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 48, 14, &I_ArrowUpEmpty_14x15); if(model->rx_active) - canvas_draw_icon(canvas, 48, 34, &I_ArrowDownFilled_14x15); + canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180); else - canvas_draw_icon(canvas, 48, 34, &I_ArrowDownEmpty_14x15); + canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180); } static bool gpio_usb_uart_input_callback(InputEvent* event, void* context) { diff --git a/applications/external/ir_scope/application.fam b/applications/external/ir_scope/application.fam new file mode 100644 index 000000000..f99e14515 --- /dev/null +++ b/applications/external/ir_scope/application.fam @@ -0,0 +1,11 @@ +App( + appid="ir_scope", + name="IR Scope", + apptype=FlipperAppType.EXTERNAL, + entry_point="ir_scope_app", + cdefines=["APP_IR_SCOPE"], + requires=["gui"], + stack_size=2 * 1024, + fap_icon="ir_scope.png", + fap_category="Tools", +) diff --git a/applications/external/ir_scope/ir_scope.c b/applications/external/ir_scope/ir_scope.c new file mode 100644 index 000000000..408ba3cc7 --- /dev/null +++ b/applications/external/ir_scope/ir_scope.c @@ -0,0 +1,183 @@ +// Author: github.com/kallanreed +#include +#include +#include +#include +#include +#include + +#define TAG "IR Scope" +#define COLS 128 +#define ROWS 8 + +typedef struct { + bool autoscale; + uint16_t us_per_sample; + size_t timings_cnt; + uint32_t* timings; + uint32_t timings_sum; + FuriMutex* mutex; +} IRScopeState; + +static void state_set_autoscale(IRScopeState* state) { + if(state->autoscale) state->us_per_sample = state->timings_sum / (ROWS * COLS); +} + +static void canvas_draw_str_outline(Canvas* canvas, int x, int y, const char* str) { + canvas_set_color(canvas, ColorWhite); + for(int y1 = -1; y1 <= 1; ++y1) + for(int x1 = -1; x1 <= 1; ++x1) canvas_draw_str(canvas, x + x1, y + y1, str); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_str(canvas, x, y, str); +} + +static void render_callback(Canvas* canvas, void* ctx) { + const IRScopeState* state = (IRScopeState*)ctx; + + furi_mutex_acquire(state->mutex, FuriWaitForever); + + canvas_clear(canvas); + canvas_draw_frame(canvas, 0, 0, 128, 64); + + // Draw the signal chart. + bool on = false; + bool done = false; + size_t ix = 0; + int timing_cols = -1; // Count of columns used to draw the current timing + for(size_t row = 0; row < ROWS && !done; ++row) { + for(size_t col = 0; col < COLS && !done; ++col) { + done = ix >= state->timings_cnt; + + if(!done && timing_cols < 0) { + timing_cols = state->timings[ix] / state->us_per_sample; + on = !on; + } + + if(timing_cols == 0) ++ix; + + int y = row * 8 + 7; + canvas_draw_line(canvas, col, y, col, y - (on ? 5 : 0)); + --timing_cols; + } + } + + canvas_set_font(canvas, FontSecondary); + if(state->autoscale) + canvas_draw_str_outline(canvas, 100, 64, "Auto"); + else { + char buf[20]; + snprintf(buf, sizeof(buf), "%uus", state->us_per_sample); + canvas_draw_str_outline(canvas, 100, 64, buf); + } + + furi_mutex_release(state->mutex); +} + +static void input_callback(InputEvent* input_event, void* ctx) { + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +static void ir_received_callback(void* ctx, InfraredWorkerSignal* signal) { + furi_check(signal); + IRScopeState* state = (IRScopeState*)ctx; + + furi_mutex_acquire(state->mutex, FuriWaitForever); + + const uint32_t* timings; + infrared_worker_get_raw_signal(signal, &timings, &state->timings_cnt); + + if(state->timings) { + free(state->timings); + state->timings_sum = 0; + } + + state->timings = malloc(state->timings_cnt * sizeof(uint32_t)); + + // Copy and sum. + for(size_t i = 0; i < state->timings_cnt; ++i) { + state->timings[i] = timings[i]; + state->timings_sum += timings[i]; + } + + state_set_autoscale(state); + + furi_mutex_release(state->mutex); +} + +int32_t ir_scope_app(void* p) { + UNUSED(p); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + furi_check(event_queue); + + if(furi_hal_infrared_is_busy()) { + FURI_LOG_E(TAG, "Infrared is busy."); + return -1; + } + + IRScopeState state = { + .autoscale = false, .us_per_sample = 200, .timings = NULL, .timings_cnt = 0, .mutex = NULL}; + state.mutex = furi_mutex_alloc(FuriMutexTypeNormal); + if(!state.mutex) { + FURI_LOG_E(TAG, "Cannot create mutex."); + return -1; + } + + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state); + view_port_input_callback_set(view_port, input_callback, event_queue); + + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + InfraredWorker* worker = infrared_worker_alloc(); + infrared_worker_rx_enable_signal_decoding(worker, false); + infrared_worker_rx_enable_blink_on_receiving(worker, true); + infrared_worker_rx_set_received_signal_callback(worker, ir_received_callback, &state); + infrared_worker_rx_start(worker); + + InputEvent event; + bool processing = true; + while(processing && + furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) { + if(event.type == InputTypeRelease) { + furi_mutex_acquire(state.mutex, FuriWaitForever); + + if(event.key == InputKeyBack) { + processing = false; + } else if(event.key == InputKeyUp) { + state.us_per_sample = MIN(1000, state.us_per_sample + 25); + state.autoscale = false; + } else if(event.key == InputKeyDown) { + state.us_per_sample = MAX(25, state.us_per_sample - 25); + state.autoscale = false; + } else if(event.key == InputKeyOk) { + state.autoscale = !state.autoscale; + if(state.autoscale) + state_set_autoscale(&state); + else + state.us_per_sample = 200; + } + + view_port_update(view_port); + furi_mutex_release(state.mutex); + } + } + + // Clean up. + infrared_worker_rx_stop(worker); + infrared_worker_free(worker); + + if(state.timings) free(state.timings); + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(state.mutex); + + return 0; +} diff --git a/applications/external/ir_scope/ir_scope.png b/applications/external/ir_scope/ir_scope.png new file mode 100644 index 000000000..c0d7eaba0 Binary files /dev/null and b/applications/external/ir_scope/ir_scope.png differ diff --git a/applications/external/subghz_remote/subghz_remote_app.c b/applications/external/subghz_remote/subghz_remote_app.c index 424717490..940968592 100644 --- a/applications/external/subghz_remote/subghz_remote_app.c +++ b/applications/external/subghz_remote/subghz_remote_app.c @@ -386,17 +386,35 @@ bool subghz_remote_key_load( FURI_LOG_E(TAG, "Could not read Protocol."); break; } + if(!furi_string_cmp_str(preset->protocol, "RAW")) { subghz_protocol_raw_gen_fff_data(fff_data, path); + // repeat + if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) { + FURI_LOG_E(TAG, "Unable to insert or update Repeat"); + break; + } + if(!flipper_format_rewind(fff_data)) { + FURI_LOG_E(TAG, "Rewind error"); + return false; + } } else { stream_copy_full( flipper_format_get_raw_stream(fff_file), flipper_format_get_raw_stream(fff_data)); + // repeat + if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) { + FURI_LOG_E(TAG, "Unable to insert or update Repeat"); + break; + } + if(!flipper_format_rewind(fff_data)) { + FURI_LOG_E(TAG, "Rewind error"); + return false; + } } - // repeat - if(!flipper_format_insert_or_update_uint32(fff_file, "Repeat", &preset->repeat, 1)) { - FURI_LOG_E(TAG, "Unable to insert or update Repeat"); - break; + if(!flipper_format_rewind(fff_file)) { + FURI_LOG_E(TAG, "Rewind error"); + return false; } preset->decoder = subghz_receiver_search_decoder_base_by_name( @@ -435,9 +453,6 @@ bool subghz_remote_save_protocol_to_file(FlipperFormat* fff_file, const char* de path_extract_dirname(dev_file_name, file_dir); do { - flipper_format_delete_key(fff_file, "Repeat"); - //flipper_format_delete_key(fff_file, "Manufacture"); - if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) { FURI_LOG_E(TAG, "(save) Cannot mkdir"); break; @@ -475,14 +490,18 @@ void subghz_remote_tx_stop(SubGHzRemote* app) { //FURI_LOG_I(TAG, "TX Done!"); subghz_transmitter_stop(app->tx_transmitter); - FURI_LOG_D(TAG, "Checking if protocol is dynamic"); + //FURI_LOG_D(TAG, "Checking if protocol is dynamic"); const SubGhzProtocolRegistry* protocol_registry_items = subghz_environment_get_protocol_registry(app->environment); const SubGhzProtocol* proto = subghz_protocol_registry_get_by_name( protocol_registry_items, furi_string_get_cstr(app->txpreset->protocol)); - FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type); + //FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type); + if(proto && proto->type == SubGhzProtocolTypeDynamic) { - FURI_LOG_D(TAG, "Protocol is dynamic. Saving key"); + //FURI_LOG_D(TAG, "Protocol is dynamic. Saving key"); + // Remove repeat if it was present + flipper_format_delete_key(app->tx_fff_data, "Repeat"); + subghz_remote_save_protocol_to_file(app->tx_fff_data, app->tx_file_path); keeloq_reset_mfname(); @@ -682,15 +701,15 @@ static void render_callback(Canvas* canvas, void* ctx) { break; case 2: canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_down_7x9); + canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180); break; case 3: canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 115, 18, &I_Pin_arrow_right_9x7); + canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90); break; case 4: canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); - canvas_draw_icon(canvas, 115, 18, &I_Pin_arrow_left_9x7); + canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270); break; case 5: canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13); diff --git a/applications/external/totp/application.fam b/applications/external/totp/application.fam index 096716e74..e454c3cd6 100644 --- a/applications/external/totp/application.fam +++ b/applications/external/totp/application.fam @@ -17,7 +17,10 @@ App( name="base32", ), Lib( - name="list", + name="base64", + ), + Lib( + name="linked_list" ), Lib( name="timezone_utils", diff --git a/applications/external/totp/cli/cli_helpers.c b/applications/external/totp/cli/cli_helpers.c index 9c524b426..36b98cf65 100644 --- a/applications/external/totp/cli/cli_helpers.c +++ b/applications/external/totp/cli/cli_helpers.c @@ -41,7 +41,9 @@ bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input) { } else if(c == CliSymbolAsciiETX) { cli_nl(); return false; - } else if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + } else if( + (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + c == '/' || c == '=' || c == '+') { if(mask_user_input) { putc('*', stdout); } else { diff --git a/applications/external/totp/cli/cli_helpers.h b/applications/external/totp/cli/cli_helpers.h index f3f8d963d..dd5a282d4 100644 --- a/applications/external/totp/cli/cli_helpers.h +++ b/applications/external/totp/cli/cli_helpers.h @@ -14,24 +14,13 @@ #define DOCOPT_OPTIONS "[options]" #define DOCOPT_DEFAULT(val) "[default: " val "]" -#define TOTP_CLI_PRINTF(format, ...) \ - do { \ - _Pragma(STRINGIFY(GCC diagnostic push)) \ - _Pragma(STRINGIFY(GCC diagnostic ignored "-Wdouble-promotion")) \ - printf(format, ##__VA_ARGS__); \ - _Pragma(STRINGIFY(GCC diagnostic pop)) \ - } while(false) +#define TOTP_CLI_PRINTF(format, ...) printf(format, ##__VA_ARGS__) -#define TOTP_CLI_PRINTF_COLORFUL(color, format, ...) \ - do { \ - _Pragma(STRINGIFY(GCC diagnostic push)) \ - _Pragma(STRINGIFY(GCC diagnostic ignored "-Wdouble-promotion")) \ - printf("\e[%s", color); \ - printf(format, ##__VA_ARGS__); \ - printf("\e[0m"); \ - fflush(stdout); \ - _Pragma(STRINGIFY(GCC diagnostic pop)) \ - } while(false) +#define TOTP_CLI_PRINTF_COLORFUL(color, format, ...) \ + printf("\e[%s", color); \ + printf(format, ##__VA_ARGS__); \ + printf("\e[0m"); \ + fflush(stdout) #define TOTP_CLI_COLOR_ERROR "91m" #define TOTP_CLI_COLOR_WARNING "93m" diff --git a/applications/external/totp/cli/commands/add/add.c b/applications/external/totp/cli/commands/add/add.c index 4cfd4fe06..3549e785b 100644 --- a/applications/external/totp/cli/commands/add/add.c +++ b/applications/external/totp/cli/commands/add/add.c @@ -1,7 +1,7 @@ #include "add.h" #include #include -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../../../services/config/config.h" #include "../../../services/convert/convert.h" @@ -17,11 +17,11 @@ void totp_cli_command_add_docopt_commands() { void totp_cli_command_add_docopt_usage() { TOTP_CLI_PRINTF( " " TOTP_CLI_COMMAND_NAME - " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL( + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING))) " " DOCOPT_OPTIONAL( DOCOPT_OPTION( - TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, + TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT( - TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n"); + TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n"); } void totp_cli_command_add_docopt_arguments() { @@ -35,11 +35,21 @@ void totp_cli_command_add_docopt_options() { TOTP_CLI_COMMAND_ARG_ALGO)) " Token hashing algorithm. Must be one of: " TOTP_TOKEN_ALGO_SHA1_NAME ", " TOTP_TOKEN_ALGO_SHA256_NAME ", " TOTP_TOKEN_ALGO_SHA512_NAME + ", " TOTP_TOKEN_ALGO_STEAM_NAME " " DOCOPT_DEFAULT(TOTP_TOKEN_ALGO_SHA1_NAME) "\r\n"); TOTP_CLI_PRINTF(" " DOCOPT_OPTION( TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT( - TOTP_CLI_COMMAND_ARG_DIGITS)) " Number of digits to generate, one of: 6, 8 " DOCOPT_DEFAULT("6") "\r\n"); + TOTP_CLI_COMMAND_ARG_DIGITS)) " Number of digits to generate, one of: 5, 6, 8 " DOCOPT_DEFAULT("6") "\r\n"); + + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ARG_SECRET_ENCODING)) " Token secret encoding, one of " PLAIN_TOKEN_ENCODING_BASE32_NAME + ", " PLAIN_TOKEN_ENCODING_BASE64_NAME + " " DOCOPT_DEFAULT( + PLAIN_TOKEN_ENCODING_BASE32_NAME) "\r\n"); + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT( @@ -83,13 +93,16 @@ void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cl // Read optional arguments bool mask_user_input = true; + PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32; while(args_read_string_and_trim(args, temp_str)) { bool parsed = false; if(!totp_cli_try_read_algo(token_info, temp_str, args, &parsed) && !totp_cli_try_read_digits(token_info, temp_str, args, &parsed) && !totp_cli_try_read_duration(token_info, temp_str, args, &parsed) && !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) && - !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed)) { + !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed) && + !totp_cli_try_read_plain_token_secret_encoding( + temp_str, args, &parsed, &token_secret_encoding)) { totp_cli_printf_unknown_argument(temp_str); } @@ -115,31 +128,34 @@ void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cl TOTP_CLI_DELETE_LAST_LINE(); - if(!token_info_set_secret( - token_info, - furi_string_get_cstr(temp_str), - furi_string_size(temp_str), - plugin_state->iv)) { - TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n"); - furi_string_secure_free(temp_str); - token_info_free(token_info); - return; - } - - furi_string_secure_free(temp_str); - bool load_generate_token_scene = false; if(plugin_state->current_scene == TotpSceneGenerateToken) { totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); load_generate_token_scene = true; } - TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, token_info, furi_check); - plugin_state->tokens_count++; - if(totp_config_file_save_new_token(token_info) == TotpConfigFileUpdateSuccess) { - TOTP_CLI_PRINTF_SUCCESS("Token \"%s\" has been successfully added\r\n", token_info->name); + bool secret_set = token_info_set_secret( + token_info, + furi_string_get_cstr(temp_str), + furi_string_size(temp_str), + token_secret_encoding, + plugin_state->iv); + + furi_string_secure_free(temp_str); + + if(secret_set) { + TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, token_info, furi_check); + plugin_state->tokens_count++; + + if(totp_config_file_save_new_token(token_info) == TotpConfigFileUpdateSuccess) { + TOTP_CLI_PRINTF_SUCCESS( + "Token \"%s\" has been successfully added\r\n", token_info->name); + } else { + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + } } else { - TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + token_info_free(token_info); + TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n"); } if(load_generate_token_scene) { diff --git a/applications/external/totp/cli/commands/delete/delete.c b/applications/external/totp/cli/commands/delete/delete.c index 96a205f57..a45525e4b 100644 --- a/applications/external/totp/cli/commands/delete/delete.c +++ b/applications/external/totp/cli/commands/delete/delete.c @@ -3,7 +3,7 @@ #include #include #include -#include "../../../lib/list/list.h" +#include #include "../../../services/config/config.h" #include "../../cli_helpers.h" #include "../../../ui/scene_director.h" diff --git a/applications/external/totp/cli/commands/details/details.c b/applications/external/totp/cli/commands/details/details.c index aa16295a4..1b9289454 100644 --- a/applications/external/totp/cli/commands/details/details.c +++ b/applications/external/totp/cli/commands/details/details.c @@ -1,7 +1,7 @@ #include "details.h" #include #include -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../../../services/config/constants.h" #include "../../cli_helpers.h" diff --git a/applications/external/totp/cli/commands/list/list.c b/applications/external/totp/cli/commands/list/list.c index 6314b1b33..951c102a0 100644 --- a/applications/external/totp/cli/commands/list/list.c +++ b/applications/external/totp/cli/commands/list/list.c @@ -1,6 +1,6 @@ #include "list.h" #include -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../../../services/config/constants.h" #include "../../cli_helpers.h" diff --git a/applications/external/totp/cli/commands/move/move.c b/applications/external/totp/cli/commands/move/move.c index 265c7bc64..5c4bdfcd6 100644 --- a/applications/external/totp/cli/commands/move/move.c +++ b/applications/external/totp/cli/commands/move/move.c @@ -2,7 +2,7 @@ #include #include -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../../../services/config/config.h" #include "../../cli_helpers.h" diff --git a/applications/external/totp/cli/commands/pin/pin.c b/applications/external/totp/cli/commands/pin/pin.c index b5b4943d6..92c9c59c4 100644 --- a/applications/external/totp/cli/commands/pin/pin.c +++ b/applications/external/totp/cli/commands/pin/pin.c @@ -2,11 +2,12 @@ #include #include +#include #include "../../../types/token_info.h" #include "../../../types/user_pin_codes.h" #include "../../../services/config/config.h" #include "../../cli_helpers.h" -#include "../../../lib/polyfills/memset_s.h" +#include #include "../../../services/crypto/crypto.h" #include "../../../ui/scene_director.h" diff --git a/applications/external/totp/cli/commands/timezone/timezone.c b/applications/external/totp/cli/commands/timezone/timezone.c index 265d80e53..61e4fa065 100644 --- a/applications/external/totp/cli/commands/timezone/timezone.c +++ b/applications/external/totp/cli/commands/timezone/timezone.c @@ -35,7 +35,7 @@ void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* arg if(*strtof_endptr == 0 && tz >= -12.75f && tz <= 12.75f) { plugin_state->timezone_offset = tz; if(totp_config_file_update_timezone_offset(tz) == TotpConfigFileUpdateSuccess) { - TOTP_CLI_PRINTF_SUCCESS("Timezone is set to %f\r\n", tz); + TOTP_CLI_PRINTF_SUCCESS("Timezone is set to %f\r\n", (double)tz); } else { TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); } @@ -50,7 +50,8 @@ void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* arg TOTP_CLI_PRINTF_ERROR("Invalid timezone offset\r\n"); } } else { - TOTP_CLI_PRINTF_INFO("Current timezone offset is %f\r\n", plugin_state->timezone_offset); + TOTP_CLI_PRINTF_INFO( + "Current timezone offset is %f\r\n", (double)plugin_state->timezone_offset); } furi_string_free(temp_str); } \ No newline at end of file diff --git a/applications/external/totp/cli/commands/update/update.c b/applications/external/totp/cli/commands/update/update.c index 669a36db8..bba7cad35 100644 --- a/applications/external/totp/cli/commands/update/update.c +++ b/applications/external/totp/cli/commands/update/update.c @@ -1,7 +1,7 @@ #include "update.h" #include #include -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../../../services/config/config.h" #include "../../../services/convert/convert.h" @@ -18,17 +18,20 @@ void totp_cli_command_update_docopt_commands() { void totp_cli_command_update_docopt_usage() { TOTP_CLI_PRINTF( " " TOTP_CLI_COMMAND_NAME - " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_UPDATE) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_NAME_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME))) " " DOCOPT_OPTIONAL( + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_UPDATE) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_NAME_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME))) " " DOCOPT_OPTIONAL( DOCOPT_OPTION( TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT( - TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n"); + TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n"); } void totp_cli_command_update_docopt_options() { TOTP_CLI_PRINTF(" " DOCOPT_OPTION( TOTP_CLI_COMMAND_ARG_NAME_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME)) " Token name\r\n"); + + TOTP_CLI_PRINTF(" " DOCOPT_SWITCH( + TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) " Update token secret\r\n"); } static bool @@ -85,6 +88,7 @@ void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, // Read optional arguments bool mask_user_input = true; bool update_token_secret = false; + PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32; while(args_read_string_and_trim(args, temp_str)) { bool parsed = false; if(!totp_cli_try_read_name(token_info, temp_str, args, &parsed) && @@ -93,7 +97,9 @@ void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, !totp_cli_try_read_duration(token_info, temp_str, args, &parsed) && !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) && !totp_cli_try_read_change_secret_flag(temp_str, &parsed, &update_token_secret) && - !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed)) { + !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed) && + !totp_cli_try_read_plain_token_secret_encoding( + temp_str, args, &parsed, &token_secret_encoding)) { totp_cli_printf_unknown_argument(temp_str); } @@ -105,54 +111,55 @@ void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, } } + bool token_secret_read = false; if(update_token_secret) { // Reading token secret furi_string_reset(temp_str); TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n"); - if(!totp_cli_read_line(cli, temp_str, mask_user_input) || - !totp_cli_ensure_authenticated(plugin_state, cli)) { - TOTP_CLI_DELETE_LAST_LINE(); - TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n"); - furi_string_secure_free(temp_str); - token_info_free(token_info); - return; - } - + token_secret_read = totp_cli_read_line(cli, temp_str, mask_user_input); TOTP_CLI_DELETE_LAST_LINE(); - - if(token_info->token != NULL) { - free(token_info->token); - } - - if(!token_info_set_secret( - token_info, - furi_string_get_cstr(temp_str), - furi_string_size(temp_str), - plugin_state->iv)) { - TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n"); - furi_string_secure_free(temp_str); - token_info_free(token_info); - return; + if(!token_secret_read) { + TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n"); } } - furi_string_secure_free(temp_str); - bool load_generate_token_scene = false; if(plugin_state->current_scene == TotpSceneGenerateToken) { totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); load_generate_token_scene = true; } - list_item->data = token_info; + bool token_secret_set = false; + if(update_token_secret && token_secret_read) { + if(token_info->token != NULL) { + free(token_info->token); + } + token_secret_set = token_info_set_secret( + token_info, + furi_string_get_cstr(temp_str), + furi_string_size(temp_str), + token_secret_encoding, + plugin_state->iv); + if(!token_secret_set) { + TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n"); + } + } - if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) { - TOTP_CLI_PRINTF_SUCCESS( - "Token \"%s\" has been successfully updated\r\n", token_info->name); - token_info_free(existing_token_info); + furi_string_secure_free(temp_str); + + if(!update_token_secret || (token_secret_read && token_secret_set)) { + list_item->data = token_info; + + if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) { + TOTP_CLI_PRINTF_SUCCESS( + "Token \"%s\" has been successfully updated\r\n", token_info->name); + token_info_free(existing_token_info); + } else { + TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); + list_item->data = existing_token_info; + token_info_free(token_info); + } } else { - TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE(); - list_item->data = existing_token_info; token_info_free(token_info); } diff --git a/applications/external/totp/cli/common_command_arguments.c b/applications/external/totp/cli/common_command_arguments.c index 0ef121add..9ed9f0126 100644 --- a/applications/external/totp/cli/common_command_arguments.c +++ b/applications/external/totp/cli/common_command_arguments.c @@ -108,5 +108,34 @@ bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* return true; } + return false; +} + +bool totp_cli_try_read_plain_token_secret_encoding( + FuriString* arg, + FuriString* args, + bool* parsed, + PlainTokenSecretEncoding* secret_encoding) { + if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX) == 0) { + if(!args_read_string_and_trim(args, arg)) { + totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX); + } else { + if(furi_string_cmpi_str(arg, PLAIN_TOKEN_ENCODING_BASE32_NAME) == 0) { + *secret_encoding = PLAIN_TOKEN_ENCODING_BASE32; + *parsed = true; + } else if(furi_string_cmpi_str(arg, PLAIN_TOKEN_ENCODING_BASE64_NAME) == 0) { + *secret_encoding = PLAIN_TOKEN_ENCODING_BASE64; + *parsed = true; + } else { + TOTP_CLI_PRINTF_ERROR( + "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX + "\"\r\n", + furi_string_get_cstr(arg)); + } + } + + return true; + } + return false; } \ No newline at end of file diff --git a/applications/external/totp/cli/common_command_arguments.h b/applications/external/totp/cli/common_command_arguments.h index 85413a321..be01c216d 100644 --- a/applications/external/totp/cli/common_command_arguments.h +++ b/applications/external/totp/cli/common_command_arguments.h @@ -15,6 +15,8 @@ #define TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX "-b" #define TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE "feature" #define TOTP_CLI_COMMAND_ARG_INDEX "index" +#define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX "-e" +#define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING "encoding" void totp_cli_printf_unknown_argument(const FuriString* arg); void totp_cli_printf_missed_argument_value(char* arg); @@ -34,4 +36,10 @@ bool totp_cli_try_read_automation_features( FuriString* arg, FuriString* args, bool* parsed); -bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* unsecure_flag); \ No newline at end of file +bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* unsecure_flag); + +bool totp_cli_try_read_plain_token_secret_encoding( + FuriString* arg, + FuriString* args, + bool* parsed, + PlainTokenSecretEncoding* secret_encoding); \ No newline at end of file diff --git a/applications/external/totp/features_config.h b/applications/external/totp/features_config.h index d848b0acd..68f976e96 100644 --- a/applications/external/totp/features_config.h +++ b/applications/external/totp/features_config.h @@ -1,2 +1,14 @@ +// Include Bluetooth token input automation #define TOTP_BADBT_TYPE_ENABLED -#define TOTP_AUTOMATION_ICONS_ENABLED \ No newline at end of file + +// Include token input automation icons on the main screen +#define TOTP_AUTOMATION_ICONS_ENABLED + +// List of compatible firmwares +#define TOTP_FIRMWARE_OFFICIAL_STABLE 1 +#define TOTP_FIRMWARE_OFFICIAL_DEV 2 +#define TOTP_FIRMWARE_XTREME 3 +// End of list + +// Target firmware to build for +#define TOTP_TARGET_FIRMWARE TOTP_FIRMWARE_XTREME diff --git a/applications/external/totp/lib/base32/base32.c b/applications/external/totp/lib/base32/base32.c index 9781c831f..827aa1e94 100644 --- a/applications/external/totp/lib/base32/base32.c +++ b/applications/external/totp/lib/base32/base32.c @@ -17,10 +17,10 @@ #include "base32.h" -int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize) { +size_t base32_decode(const uint8_t* encoded, uint8_t* result, size_t bufSize) { int buffer = 0; int bitsLeft = 0; - int count = 0; + size_t count = 0; for(const uint8_t* ptr = encoded; count < bufSize && *ptr; ++ptr) { uint8_t ch = *ptr; if(ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { @@ -43,7 +43,7 @@ int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize) { } else if(ch >= '2' && ch <= '7') { ch -= '2' - 26; } else { - return -1; + return 0; } buffer |= ch; diff --git a/applications/external/totp/lib/base32/base32.h b/applications/external/totp/lib/base32/base32.h index dea1a1c81..8cb9bddcb 100644 --- a/applications/external/totp/lib/base32/base32.h +++ b/applications/external/totp/lib/base32/base32.h @@ -27,6 +27,7 @@ #pragma once +#include #include /** @@ -34,6 +35,6 @@ * @param encoded Base-32 encoded bytes * @param[out] result result output buffer * @param bufSize result output buffer size - * @return Decoded result length in bytes if successfully decoded; \c -1 otherwise + * @return Decoded result length in bytes if successfully decoded; \c 0 otherwise */ -int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize); +size_t base32_decode(const uint8_t* encoded, uint8_t* result, size_t bufSize); diff --git a/applications/external/totp/lib/base64/base64.c b/applications/external/totp/lib/base64/base64.c new file mode 100644 index 000000000..1dfcf8814 --- /dev/null +++ b/applications/external/totp/lib/base64/base64.c @@ -0,0 +1,72 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + */ + +#include "base64.h" +#include + +static const uint8_t dtable[] = {0x3e, 0x80, 0x80, 0x80, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x80, 0x80, 0x80, 0x0, 0x80, + 0x80, 0x80, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33}; +// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static uint8_t get_dtable_value(uint8_t index) { + return (index < 43 || index > 122) ? 0x80 : dtable[index - 43]; +} + +uint8_t* base64_decode(const uint8_t* src, size_t len, size_t* out_len, size_t* out_size) { + uint8_t *out; + uint8_t *pos; + uint8_t in[4]; + uint8_t block[4]; + uint8_t tmp; + size_t i; + size_t count; + size_t olen; + + count = 0; + for(i = 0; i < len; i++) { + if(get_dtable_value(src[i]) != 0x80) count++; + } + + if(count == 0 || count % 4) return NULL; + olen = count / 4 * 3; + pos = out = malloc(olen); + *out_size = olen; + if(out == NULL) return NULL; + count = 0; + for(i = 0; i < len; i++) { + tmp = get_dtable_value(src[i]); + if(tmp == 0x80) continue; + in[count] = src[i]; + block[count] = tmp; + count++; + if(count == 4) { + *pos++ = (block[0] << 2) | (block[1] >> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + } + } + if(pos > out) { + if(in[2] == '=') + pos -= 2; + else if(in[3] == '=') + pos--; + } + *out_len = pos - out; + return out; +} \ No newline at end of file diff --git a/applications/external/totp/lib/base64/base64.h b/applications/external/totp/lib/base64/base64.h new file mode 100644 index 000000000..059ec5a90 --- /dev/null +++ b/applications/external/totp/lib/base64/base64.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +/** + * @brief Decodes Base-64 encoded bytes into plain bytes. + * @param src Base-64 encoded bytes + * @param len Base-64 encoded bytes count + * @param[out] out_len decoded buffer length + * @param[out] out_size decoded buffer allocated size + * @return Decoded result buffer if successfully decoded; \c NULL otherwise + */ +uint8_t* base64_decode(const uint8_t* src, size_t len, size_t* out_len, size_t* out_size); \ No newline at end of file diff --git a/applications/external/totp/lib/list/list.c b/applications/external/totp/lib/linked_list/linked_list.c similarity index 99% rename from applications/external/totp/lib/list/list.c rename to applications/external/totp/lib/linked_list/linked_list.c index f7abb6c8e..23d10c548 100644 --- a/applications/external/totp/lib/list/list.c +++ b/applications/external/totp/lib/linked_list/linked_list.c @@ -1,4 +1,4 @@ -#include "list.h" +#include "linked_list.h" ListNode* list_init_head(void* data) { ListNode* new = malloc(sizeof(ListNode)); diff --git a/applications/external/totp/lib/list/list.h b/applications/external/totp/lib/linked_list/linked_list.h similarity index 79% rename from applications/external/totp/lib/list/list.h rename to applications/external/totp/lib/linked_list/linked_list.h index c52d4c25a..3c938e59a 100644 --- a/applications/external/totp/lib/list/list.h +++ b/applications/external/totp/lib/linked_list/linked_list.h @@ -84,19 +84,15 @@ ListNode* list_insert_at(ListNode* head, uint16_t index, void* data); void list_free(ListNode* head); #define TOTP_LIST_INIT_OR_ADD(head, item, assert) \ - do { \ - if(head == NULL) { \ - head = list_init_head(item); \ - assert(head != NULL); \ - } else { \ - assert(list_add(head, item) != NULL); \ - } \ - } while(false) + if(head == NULL) { \ + head = list_init_head(item); \ + assert(head != NULL); \ + } else { \ + assert(list_add(head, item) != NULL); \ + } #define TOTP_LIST_FOREACH(head, node, action) \ - do { \ - ListNode* node = head; \ - while(node != NULL) { \ - action node = node->next; \ - } \ - } while(false) + ListNode* node = head; \ + while(node != NULL) { \ + action node = node->next; \ + } diff --git a/applications/external/totp/services/config/config.c b/applications/external/totp/services/config/config.c index 349d2ede9..fca83942e 100644 --- a/applications/external/totp/services/config/config.c +++ b/applications/external/totp/services/config/config.c @@ -1,7 +1,7 @@ #include "config.h" #include #include -#include "../list/list.h" +#include #include "../../types/common.h" #include "../../types/token_info.h" #include "../../features_config.h" @@ -139,10 +139,11 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF furi_string_printf( temp_str, - " # Token hashing algorithm to use during code generation. Supported options are %s, %s and %s. If you are not use which one to use - use %s", + " # Token hashing algorithm to use during code generation. Supported options are %s, %s, %s, and %s. If you are not use which one to use - use %s", TOTP_TOKEN_ALGO_SHA1_NAME, TOTP_TOKEN_ALGO_SHA256_NAME, TOTP_TOKEN_ALGO_SHA512_NAME, + TOTP_TOKEN_ALGO_STEAM_NAME, TOTP_TOKEN_ALGO_SHA1_NAME); flipper_format_write_comment(fff_data_file, temp_str); furi_string_printf( @@ -152,7 +153,7 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF flipper_format_write_comment_cstr( fff_data_file, - "# How many digits there should be in generated code. Available options are 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6"); + "# How many digits there should be in generated code. Available options are 5, 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6"); furi_string_printf(temp_str, "%s: 6", TOTP_CONFIG_KEY_TOKEN_DIGITS); flipper_format_write_comment(fff_data_file, temp_str); flipper_format_write_comment_cstr(fff_data_file, " "); @@ -686,6 +687,7 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state) tokenInfo, furi_string_get_cstr(temp_str), furi_string_size(temp_str), + PLAIN_TOKEN_ENCODING_BASE32, &plugin_state->iv[0])) { FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has plain secret", tokenInfo->name); } else { diff --git a/applications/external/totp/services/hmac/sha512.c b/applications/external/totp/services/hmac/sha512.c index f04c4d593..b56dd0f2e 100644 --- a/applications/external/totp/services/hmac/sha512.c +++ b/applications/external/totp/services/hmac/sha512.c @@ -26,12 +26,9 @@ #include #include -#ifdef WORDS_BIGENDIAN -#define SWAP(n) (n) -#else #include "byteswap.h" + #define SWAP(n) swap_uint64(n) -#endif /* This array contains the bytes used to pad the buffer to the next 128-byte boundary. */ diff --git a/applications/external/totp/services/totp/totp.c b/applications/external/totp/services/totp/totp.c index 1a20d58c1..f6e0401e6 100644 --- a/applications/external/totp/services/totp/totp.c +++ b/applications/external/totp/services/totp/totp.c @@ -11,7 +11,7 @@ #include "../hmac/byteswap.h" #include "../../lib/timezone_utils/timezone_utils.h" -#define HMAC_MAX_SIZE 64 +#define HMAC_MAX_RESULT_SIZE HMAC_SHA512_RESULT_SIZE /** * @brief Generates the timeblock for a time in seconds. @@ -29,19 +29,17 @@ uint64_t totp_timecode(uint8_t interval, uint64_t for_time) { /** * @brief Generates an OTP (One Time Password) * @param algo hashing algorithm to be used - * @param digits desired TOTP code length * @param plain_secret plain token secret * @param plain_secret_length plain token secret length * @param input input data for OTP code generation * @return OTP code if code was successfully generated; 0 otherwise */ -uint32_t otp_generate( +uint64_t otp_generate( TOTP_ALGO algo, - uint8_t digits, const uint8_t* plain_secret, size_t plain_secret_length, uint64_t input) { - uint8_t hmac[HMAC_MAX_SIZE] = {0}; + uint8_t hmac[HMAC_MAX_RESULT_SIZE] = {0}; uint64_t input_swapped = swap_uint64(input); @@ -55,14 +53,12 @@ uint32_t otp_generate( uint64_t i_code = ((hmac[offset] & 0x7F) << 24 | (hmac[offset + 1] & 0xFF) << 16 | (hmac[offset + 2] & 0xFF) << 8 | (hmac[offset + 3] & 0xFF)); - i_code %= (uint64_t)pow(10, digits); return i_code; } -uint32_t totp_at( +uint64_t totp_at( TOTP_ALGO algo, - uint8_t digits, const uint8_t* plain_secret, size_t plain_secret_length, uint64_t for_time, @@ -71,11 +67,7 @@ uint32_t totp_at( uint64_t for_time_adjusted = timezone_offset_apply(for_time, timezone_offset_from_hours(timezone)); return otp_generate( - algo, - digits, - plain_secret, - plain_secret_length, - totp_timecode(interval, for_time_adjusted)); + algo, plain_secret, plain_secret_length, totp_timecode(interval, for_time_adjusted)); } static int totp_algo_sha1( diff --git a/applications/external/totp/services/totp/totp.h b/applications/external/totp/services/totp/totp.h index 3f45a0223..d578f6ea9 100644 --- a/applications/external/totp/services/totp/totp.h +++ b/applications/external/totp/services/totp/totp.h @@ -39,7 +39,6 @@ extern const TOTP_ALGO TOTP_ALGO_SHA512; /** * @brief Generates a OTP key using the totp algorithm. * @param algo hashing algorithm to be used - * @param digits desired TOTP code length * @param plain_secret plain token secret * @param plain_secret_length plain token secret length * @param for_time the time the generated key will be created for @@ -47,9 +46,8 @@ extern const TOTP_ALGO TOTP_ALGO_SHA512; * @param interval token lifetime in seconds * @return TOTP code if code was successfully generated; 0 otherwise */ -uint32_t totp_at( +uint64_t totp_at( TOTP_ALGO algo, - uint8_t digits, const uint8_t* plain_secret, size_t plain_secret_length, uint64_t for_time, diff --git a/applications/external/totp/types/plugin_state.h b/applications/external/totp/types/plugin_state.h index 59a218ce3..b1d34a662 100644 --- a/applications/external/totp/types/plugin_state.h +++ b/applications/external/totp/types/plugin_state.h @@ -4,7 +4,7 @@ #include #include #include "../features_config.h" -#include "../lib/list/list.h" +#include #include "../ui/totp_scenes_enum.h" #include "notification_method.h" #include "automation_method.h" diff --git a/applications/external/totp/types/token_info.c b/applications/external/totp/types/token_info.c index fd9699ecf..b8196c56b 100644 --- a/applications/external/totp/types/token_info.c +++ b/applications/external/totp/types/token_info.c @@ -1,11 +1,11 @@ -#include #include "token_info.h" -#include "stdlib.h" +#include +#include +#include +#include +#include #include "common.h" -#include "../lib/base32/base32.h" #include "../services/crypto/crypto.h" -#include "../lib/polyfills/memset_s.h" -#include "../lib/polyfills/strnlen.h" TokenInfo* token_info_alloc() { TokenInfo* tokenInfo = malloc(sizeof(TokenInfo)); @@ -26,15 +26,32 @@ void token_info_free(TokenInfo* token_info) { bool token_info_set_secret( TokenInfo* token_info, - const char* base32_token_secret, + const char* plain_token_secret, size_t token_secret_length, + PlainTokenSecretEncoding plain_token_secret_encoding, const uint8_t* iv) { if(token_secret_length == 0) return false; + uint8_t* plain_secret; + size_t plain_secret_length; + size_t plain_secret_size; + if(plain_token_secret_encoding == PLAIN_TOKEN_ENCODING_BASE32) { + plain_secret_size = token_secret_length; + plain_secret = malloc(plain_secret_size); + furi_check(plain_secret != NULL); + plain_secret_length = + base32_decode((const uint8_t*)plain_token_secret, plain_secret, plain_secret_size); + } else if(plain_token_secret_encoding == PLAIN_TOKEN_ENCODING_BASE64) { + plain_secret_length = 0; + plain_secret = base64_decode( + (const uint8_t*)plain_token_secret, + token_secret_length, + &plain_secret_length, + &plain_secret_size); + furi_check(plain_secret != NULL); + } else { + return false; + } - uint8_t* plain_secret = malloc(token_secret_length); - furi_check(plain_secret != NULL); - int plain_secret_length = - base32_decode((const uint8_t*)base32_token_secret, plain_secret, token_secret_length); bool result; if(plain_secret_length > 0) { token_info->token = @@ -44,13 +61,16 @@ bool token_info_set_secret( result = false; } - memset_s(plain_secret, token_secret_length, 0, token_secret_length); + memset_s(plain_secret, plain_secret_size, 0, plain_secret_size); free(plain_secret); return result; } bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) { switch(digits) { + case 5: + token_info->digits = TOTP_5_DIGITS; + return true; case 6: token_info->digits = TOTP_6_DIGITS; return true; @@ -89,6 +109,11 @@ bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) return true; } + if(furi_string_cmpi_str(str, TOTP_TOKEN_ALGO_STEAM_NAME) == 0) { + token_info->algo = STEAM; + return true; + } + return false; } @@ -100,6 +125,8 @@ char* token_info_get_algo_as_cstr(const TokenInfo* token_info) { return TOTP_TOKEN_ALGO_SHA256_NAME; case SHA512: return TOTP_TOKEN_ALGO_SHA512_NAME; + case STEAM: + return TOTP_TOKEN_ALGO_STEAM_NAME; default: break; } diff --git a/applications/external/totp/types/token_info.h b/applications/external/totp/types/token_info.h index fea0c09d7..688e8028d 100644 --- a/applications/external/totp/types/token_info.h +++ b/applications/external/totp/types/token_info.h @@ -7,10 +7,14 @@ #define TOTP_TOKEN_DURATION_DEFAULT 30 #define TOTP_TOKEN_ALGO_SHA1_NAME "sha1" +#define TOTP_TOKEN_ALGO_STEAM_NAME "steam" #define TOTP_TOKEN_ALGO_SHA256_NAME "sha256" #define TOTP_TOKEN_ALGO_SHA512_NAME "sha512" #define TOTP_TOKEN_MAX_LENGTH 255 +#define PLAIN_TOKEN_ENCODING_BASE32_NAME "base32" +#define PLAIN_TOKEN_ENCODING_BASE64_NAME "base64" + #define TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME "none" #define TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME "enter" #define TOTP_TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME "tab" @@ -19,6 +23,7 @@ typedef uint8_t TokenHashAlgo; typedef uint8_t TokenDigitsCount; typedef uint8_t TokenAutomationFeature; +typedef uint8_t PlainTokenSecretEncoding; /** * @brief Hashing algorithm to be used to generate token @@ -37,13 +42,23 @@ enum TokenHashAlgos { /** * @brief SHA512 hashing algorithm */ - SHA512 + SHA512, + + /** + * @brief Algorithm used by Steam (Valve) + */ + STEAM }; /** * @brief Token digits count to be generated. */ enum TokenDigitsCounts { + /** + * @brief 6 digits + */ + TOTP_5_DIGITS = 5, + /** * @brief 6 digits */ @@ -80,6 +95,11 @@ enum TokenAutomationFeatures { TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER = 0b100 }; +enum PlainTokenSecretEncodings { + PLAIN_TOKEN_ENCODING_BASE32 = 0, + PLAIN_TOKEN_ENCODING_BASE64 = 1 +}; + #define TOTP_TOKEN_DIGITS_MAX_COUNT 8 /** @@ -139,13 +159,15 @@ void token_info_free(TokenInfo* token_info); * @param token_info instance where secret should be updated * @param base32_token_secret plain token secret in Base32 format * @param token_secret_length plain token secret length + * @param plain_token_secret_encoding plain token secret encoding * @param iv initialization vecor (IV) to be used for encryption * @return \c true if token successfully set; \c false otherwise */ bool token_info_set_secret( TokenInfo* token_info, - const char* base32_token_secret, + const char* plain_token_secret, size_t token_secret_length, + PlainTokenSecretEncoding plain_token_secret_encoding, const uint8_t* iv); /** diff --git a/applications/external/totp/ui/fonts/font_info.h b/applications/external/totp/ui/fonts/font_info.h new file mode 100644 index 000000000..86c131ec9 --- /dev/null +++ b/applications/external/totp/ui/fonts/font_info.h @@ -0,0 +1,24 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include + +// This structure describes a single character's display information +typedef struct { + const uint8_t width; // width, in bits (or pixels), of the character + const uint16_t + offset; // offset of the character's bitmap, in bytes, into the the FONT_INFO's data array + +} FONT_CHAR_INFO; + +// Describes a single font +typedef struct { + const uint8_t height; // height, in pages (8 pixels), of the font's characters + const uint8_t startChar; // the first character in the font (e.g. in charInfo and data) + const uint8_t endChar; // the last character in the font + const uint8_t spacePixels; // number of pixels that a space character takes up + const FONT_CHAR_INFO* charInfo; // pointer to array of char information + const uint8_t* data; // pointer to generated array of character visual representation + +} FONT_INFO; \ No newline at end of file diff --git a/applications/external/totp/ui/fonts/mode-nine/mode_nine.c b/applications/external/totp/ui/fonts/mode-nine/mode_nine.c new file mode 100644 index 000000000..add4f47ef --- /dev/null +++ b/applications/external/totp/ui/fonts/mode-nine/mode_nine.c @@ -0,0 +1,940 @@ +#include "mode_nine.h" + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for ModeNine 15pt +*/ + +/* Character bitmaps for ModeNine 15pt */ +const uint8_t modeNine_15ptBitmaps[] = { + /* @0 '-' (10 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @28 '0' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @56 '1' (10 pixels wide) */ + 0x30, + 0x00, + 0x38, + 0x00, + 0x3C, + 0x00, + 0x3C, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0xFC, + 0x00, + 0xFC, + 0x00, + + /* @84 '2' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x80, + 0x03, + 0xFC, + 0x01, + 0xFE, + 0x00, + 0x07, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + + /* @112 '3' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x80, + 0x03, + 0xC0, + 0x01, + 0xE0, + 0x00, + 0x70, + 0x00, + 0xF8, + 0x00, + 0xFC, + 0x01, + 0x80, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @140 '4' (10 pixels wide) */ + 0xE0, + 0x00, + 0xF0, + 0x00, + 0xF8, + 0x00, + 0xDC, + 0x00, + 0xCE, + 0x00, + 0xC7, + 0x00, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + + /* @168 '5' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x80, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @196 '6' (10 pixels wide) */ + 0xF0, + 0x00, + 0xFC, + 0x00, + 0x0E, + 0x00, + 0x06, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @224 '7' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x00, + 0x03, + 0x80, + 0x01, + 0xC0, + 0x01, + 0xE0, + 0x00, + 0x30, + 0x00, + 0x18, + 0x00, + 0x1C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + + /* @252 '8' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @280 '9' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x07, + 0x03, + 0xFE, + 0x03, + 0xFC, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x80, + 0x01, + 0xC0, + 0x01, + 0xFC, + 0x00, + 0x3C, + 0x00, + + /* @308 'B' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + + /* @336 'C' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @364 'D' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + + /* @392 'F' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + + /* @420 'G' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xC3, + 0x03, + 0xC3, + 0x03, + 0x03, + 0x03, + 0x07, + 0x03, + 0xFE, + 0x03, + 0xFC, + 0x03, + + /* @448 'H' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @476 'J' (10 pixels wide) */ + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @504 'K' (10 pixels wide) */ + 0x83, + 0x03, + 0xC3, + 0x01, + 0xE3, + 0x00, + 0x73, + 0x00, + 0x3B, + 0x00, + 0x1F, + 0x00, + 0x0F, + 0x00, + 0x0F, + 0x00, + 0x1F, + 0x00, + 0x3B, + 0x00, + 0x73, + 0x00, + 0xE3, + 0x00, + 0xC3, + 0x01, + 0x83, + 0x03, + + /* @532 'M' (10 pixels wide) */ + 0x03, + 0x03, + 0x87, + 0x03, + 0xCF, + 0x03, + 0xFF, + 0x03, + 0x7B, + 0x03, + 0x33, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @560 'N' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x07, + 0x03, + 0x0F, + 0x03, + 0x1F, + 0x03, + 0x3B, + 0x03, + 0x73, + 0x03, + 0xE3, + 0x03, + 0xC3, + 0x03, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @588 'P' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + + /* @616 'Q' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x73, + 0x03, + 0xE7, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x03, + + /* @644 'R' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + 0x1F, + 0x00, + 0x3B, + 0x00, + 0x73, + 0x00, + 0xE3, + 0x00, + 0xC3, + 0x01, + 0x83, + 0x03, + + /* @672 'T' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + + /* @700 'V' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x86, + 0x01, + 0x86, + 0x01, + 0xCC, + 0x00, + 0xCC, + 0x00, + 0x78, + 0x00, + 0x78, + 0x00, + 0x30, + 0x00, + + /* @728 'W' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0xFF, + 0x03, + 0xFE, + 0x01, + + /* @756 'X' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xCE, + 0x01, + 0xFC, + 0x00, + 0xFC, + 0x00, + 0xCE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @784 'Y' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xCE, + 0x01, + 0xFC, + 0x00, + 0x78, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, +}; + +/* Character descriptors for ModeNine 15pt */ +/* { [Char width in bits], [Offset into modeNine_15ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO modeNine_15ptDescriptors[] = { + {10, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {10, 28}, /* 0 */ + {10, 56}, /* 1 */ + {10, 84}, /* 2 */ + {10, 112}, /* 3 */ + {10, 140}, /* 4 */ + {10, 168}, /* 5 */ + {10, 196}, /* 6 */ + {10, 224}, /* 7 */ + {10, 252}, /* 8 */ + {10, 280}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {10, 308}, /* B */ + {10, 336}, /* C */ + {10, 364}, /* D */ + {0, 0}, /* E */ + {10, 392}, /* F */ + {10, 420}, /* G */ + {10, 448}, /* H */ + {0, 0}, /* I */ + {10, 476}, /* J */ + {10, 504}, /* K */ + {0, 0}, /* L */ + {10, 532}, /* M */ + {10, 560}, /* N */ + {0, 0}, /* O */ + {10, 588}, /* P */ + {10, 616}, /* Q */ + {10, 644}, /* R */ + {0, 0}, /* S */ + {10, 672}, /* T */ + {0, 0}, /* U */ + {10, 700}, /* V */ + {10, 728}, /* W */ + {10, 756}, /* X */ + {10, 784}, /* Y */ +}; + +/* Font information for ModeNine 15pt */ +const FONT_INFO modeNine_15ptFontInfo = { + 14, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + modeNine_15ptDescriptors, /* Character descriptor array */ + modeNine_15ptBitmaps, /* Character bitmap array */ +}; diff --git a/applications/external/totp/ui/fonts/mode-nine/mode_nine.h b/applications/external/totp/ui/fonts/mode-nine/mode_nine.h new file mode 100644 index 000000000..67fa33afe --- /dev/null +++ b/applications/external/totp/ui/fonts/mode-nine/mode_nine.h @@ -0,0 +1,9 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" +#include + +/* Font data for ModeNine 15pt */ +extern const FONT_INFO modeNine_15ptFontInfo; diff --git a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c index f35f0238b..800d7e672 100644 --- a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c +++ b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c @@ -4,17 +4,17 @@ #include "../../scene_director.h" #include "totp_input_text.h" #include "../../../types/token_info.h" -#include "../../../lib/list/list.h" +#include #include "../../../services/config/config.h" #include "../../ui_controls.h" #include "../../common_dialogs.h" -#include "../../../lib/roll_value/roll_value.h" +#include #include "../../../types/nullable.h" #include "../generate_token/totp_scene_generate_token.h" -char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512"}; -char* TOKEN_DIGITS_TEXT_LIST[] = {"6 digits", "8 digits"}; -TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = {TOTP_6_DIGITS, TOTP_8_DIGITS}; +char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512", "Steam"}; +char* TOKEN_DIGITS_TEXT_LIST[] = {"5 digits", "6 digits", "8 digits"}; +TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = {TOTP_5_DIGITS, TOTP_6_DIGITS, TOTP_8_DIGITS}; typedef enum { TokenNameTextBox, @@ -95,6 +95,8 @@ void totp_scene_add_new_token_activate( scene_state->screen_y_offset = 0; + scene_state->digits_count_index = 1; + scene_state->input_state = NULL; scene_state->duration = TOTP_TOKEN_DURATION_DEFAULT; scene_state->duration_text = furi_string_alloc(); @@ -216,10 +218,10 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState break; case InputKeyRight: if(scene_state->selected_control == TokenAlgoSelect) { - totp_roll_value_uint8_t(&scene_state->algo, 1, SHA1, SHA512, RollOverflowBehaviorRoll); + totp_roll_value_uint8_t(&scene_state->algo, 1, SHA1, STEAM, RollOverflowBehaviorRoll); } else if(scene_state->selected_control == TokenLengthSelect) { totp_roll_value_uint8_t( - &scene_state->digits_count_index, 1, 0, 1, RollOverflowBehaviorRoll); + &scene_state->digits_count_index, 1, 0, 2, RollOverflowBehaviorRoll); } else if(scene_state->selected_control == TokenDurationSelect) { totp_roll_value_uint8_t(&scene_state->duration, 15, 15, 255, RollOverflowBehaviorStop); update_duration_text(scene_state); @@ -227,11 +229,10 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState break; case InputKeyLeft: if(scene_state->selected_control == TokenAlgoSelect) { - totp_roll_value_uint8_t( - &scene_state->algo, -1, SHA1, SHA512, RollOverflowBehaviorRoll); + totp_roll_value_uint8_t(&scene_state->algo, -1, SHA1, STEAM, RollOverflowBehaviorRoll); } else if(scene_state->selected_control == TokenLengthSelect) { totp_roll_value_uint8_t( - &scene_state->digits_count_index, -1, 0, 1, RollOverflowBehaviorRoll); + &scene_state->digits_count_index, -1, 0, 2, RollOverflowBehaviorRoll); } else if(scene_state->selected_control == TokenDurationSelect) { totp_roll_value_uint8_t( &scene_state->duration, -15, 15, 255, RollOverflowBehaviorStop); @@ -268,6 +269,7 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState tokenInfo, scene_state->token_secret, scene_state->token_secret_length, + PLAIN_TOKEN_ENCODING_BASE32, &plugin_state->iv[0]); if(token_secret_set) { diff --git a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c index 1671542b8..93fd3d915 100644 --- a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c +++ b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c @@ -8,7 +8,7 @@ #include "../../constants.h" #include "../../../services/config/config.h" #include "../../../services/convert/convert.h" -#include "../../../lib/roll_value/roll_value.h" +#include #include "../../../types/nullable.h" #include "../../../features_config.h" #ifdef TOTP_BADBT_TYPE_ENABLED diff --git a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c index d3b27fdc7..f27b24835 100644 --- a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c +++ b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c @@ -19,7 +19,9 @@ #ifdef TOTP_BADBT_TYPE_ENABLED #include "../../../workers/bt_type_code/bt_type_code.h" #endif +#include "../../fonts/mode-nine/mode_nine.h" +static const char* STEAM_ALGO_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY"; static const uint8_t PROGRESS_BAR_MARGIN = 3; static const uint8_t PROGRESS_BAR_HEIGHT = 4; @@ -121,13 +123,21 @@ static const NotificationSequence* return (NotificationSequence*)scene_state->notification_sequence_badusb; } -static void int_token_to_str(uint32_t i_token_code, char* str, TokenDigitsCount len) { +static void + int_token_to_str(uint64_t i_token_code, char* str, TokenDigitsCount len, TokenHashAlgo algo) { if(i_token_code == OTP_ERROR) { memset(&str[0], '-', len); } else { - for(int i = len - 1; i >= 0; i--) { - str[i] = CONVERT_DIGIT_TO_CHAR(i_token_code % 10); - i_token_code = i_token_code / 10; + if(algo == STEAM) { + for(uint8_t i = 0; i < len; i++) { + str[i] = STEAM_ALGO_ALPHABET[i_token_code % 26]; + i_token_code = i_token_code / 26; + } + } else { + for(int8_t i = len - 1; i >= 0; i--) { + str[i] = CONVERT_DIGIT_TO_CHAR(i_token_code % 10); + i_token_code = i_token_code / 10; + } } } @@ -137,6 +147,7 @@ static void int_token_to_str(uint32_t i_token_code, char* str, TokenDigitsCount static TOTP_ALGO get_totp_algo_impl(TokenHashAlgo algo) { switch(algo) { case SHA1: + case STEAM: return TOTP_ALGO_SHA1; case SHA256: return TOTP_ALGO_SHA256; @@ -161,6 +172,27 @@ static void update_totp_params(PluginState* const plugin_state) { } } +static void draw_totp_code(Canvas* const canvas, const SceneState* const scene_state) { + uint8_t code_length = scene_state->current_token->digits; + uint8_t char_width = modeNine_15ptFontInfo.charInfo[0].width; + uint8_t total_length = code_length * (char_width + modeNine_15ptFontInfo.spacePixels); + uint8_t offset_x = (SCREEN_WIDTH - total_length) >> 1; + uint8_t offset_y = SCREEN_HEIGHT_CENTER - (modeNine_15ptFontInfo.height >> 1); + for(uint8_t i = 0; i < code_length; i++) { + char ch = scene_state->last_code[i]; + uint8_t char_index = ch - modeNine_15ptFontInfo.startChar; + canvas_draw_xbm( + canvas, + offset_x, + offset_y, + char_width, + modeNine_15ptFontInfo.height, + &modeNine_15ptFontInfo.data[modeNine_15ptFontInfo.charInfo[char_index].offset]); + + offset_x += char_width + modeNine_15ptFontInfo.spacePixels; + } +} + void totp_scene_generate_token_init(const PluginState* plugin_state) { UNUSED(plugin_state); } @@ -274,19 +306,19 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_ int_token_to_str( totp_at( get_totp_algo_impl(tokenInfo->algo), - tokenInfo->digits, key, key_length, curr_ts, plugin_state->timezone_offset, tokenInfo->duration), scene_state->last_code, - tokenInfo->digits); + tokenInfo->digits, + tokenInfo->algo); memset_s(key, key_length, 0, key_length); free(key); } else { furi_mutex_acquire(scene_state->last_code_update_sync, FuriWaitForever); - int_token_to_str(0, scene_state->last_code, tokenInfo->digits); + int_token_to_str(0, scene_state->last_code, tokenInfo->digits, tokenInfo->algo); } furi_mutex_release(scene_state->last_code_update_sync); @@ -322,14 +354,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_ canvas_set_color(canvas, ColorBlack); } - canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str_aligned( - canvas, - SCREEN_WIDTH_CENTER, - SCREEN_HEIGHT_CENTER, - AlignCenter, - AlignCenter, - scene_state->last_code); + draw_totp_code(canvas, scene_state); const uint8_t TOKEN_LIFETIME = scene_state->current_token->duration; float percentDone = (float)(TOKEN_LIFETIME - curr_ts % TOKEN_LIFETIME) / (float)TOKEN_LIFETIME; diff --git a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c index 167762602..6c6986c65 100644 --- a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c +++ b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c @@ -6,13 +6,13 @@ #include "../../constants.h" #include "../../scene_director.h" #include "../../../services/config/config.h" -#include "../../../lib/list/list.h" +#include #include "../../../types/token_info.h" #include "../generate_token/totp_scene_generate_token.h" #include "../add_new_token/totp_scene_add_new_token.h" #include "../app_settings/totp_app_settings.h" #include "../../../types/nullable.h" -#include "../../../lib/roll_value/roll_value.h" +#include #define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3) #define SCREEN_HEIGHT_THIRD_CENTER (SCREEN_HEIGHT_THIRD >> 1) diff --git a/applications/external/totp/workers/bt_type_code/bt_type_code.c b/applications/external/totp/workers/bt_type_code/bt_type_code.c index b84dfead9..ec4c1619d 100644 --- a/applications/external/totp/workers/bt_type_code/bt_type_code.c +++ b/applications/external/totp/workers/bt_type_code/bt_type_code.c @@ -1,5 +1,6 @@ #include "bt_type_code.h" #include +#include #include #include "../../types/common.h" #include "../../types/token_info.h" @@ -11,6 +12,26 @@ static inline bool totp_type_code_worker_stop_requested() { return furi_thread_flags_get() & TotpBtTypeCodeWorkerEventStop; } +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME +static void totp_type_code_worker_bt_set_app_mac(uint8_t* mac) { + uint8_t max_i; + size_t uid_size = furi_hal_version_uid_size(); + if(uid_size < 6) { + max_i = uid_size; + } else { + max_i = 6; + } + + const uint8_t* uid = furi_hal_version_uid(); + memcpy(mac, uid, max_i); + for(uint8_t i = max_i; i < 6; i++) { + mac[i] = 0; + } + + mac[0] = 0b10; +} +#endif + static void totp_type_code_worker_type_code(TotpBtTypeCodeWorkerContext* context) { uint8_t i = 0; do { @@ -30,7 +51,7 @@ static void totp_type_code_worker_type_code(TotpBtTypeCodeWorkerContext* context } static int32_t totp_type_code_worker_callback(void* context) { - furi_assert(context); + furi_check(context); FuriMutex* context_mutex = furi_mutex_alloc(FuriMutexTypeNormal); if(context_mutex == NULL) { return 251; @@ -74,7 +95,7 @@ void totp_bt_type_code_worker_start( char* code_buf, uint8_t code_buf_length, FuriMutex* code_buf_update_sync) { - furi_assert(context != NULL); + furi_check(context != NULL); context->string = code_buf; context->string_length = code_buf_length; context->string_sync = code_buf_update_sync; @@ -87,7 +108,7 @@ void totp_bt_type_code_worker_start( } void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context) { - furi_assert(context != NULL); + furi_check(context != NULL); furi_thread_flags_set(furi_thread_get_id(context->thread), TotpBtTypeCodeWorkerEventStop); furi_thread_join(context->thread); furi_thread_free(context->thread); @@ -98,7 +119,7 @@ void totp_bt_type_code_worker_notify( TotpBtTypeCodeWorkerContext* context, TotpBtTypeCodeWorkerEvent event, uint8_t flags) { - furi_assert(context != NULL); + furi_check(context != NULL); context->flags = flags; furi_thread_flags_set(furi_thread_get_id(context->thread), event); } @@ -114,11 +135,33 @@ TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init() { furi_hal_bt_reinit(); furi_delay_ms(200); bt_keys_storage_set_storage_path(context->bt, HID_BT_KEYS_STORAGE_PATH); + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME + totp_type_code_worker_bt_set_app_mac(&context->bt_mac[0]); + memcpy( + &context->previous_bt_name[0], + furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard), + TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN); + memcpy( + &context->previous_bt_mac[0], + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN); + char new_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN]; + snprintf(new_name, sizeof(new_name), "%s TOTP Auth", furi_hal_version_get_name_ptr()); + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, new_name); + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, context->bt_mac); +#endif + if(!bt_set_profile(context->bt, BtProfileHidKeyboard)) { FURI_LOG_E(LOGGING_TAG, "Failed to switch BT to keyboard HID profile"); } furi_hal_bt_start_advertising(); + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME + bt_enable_peer_key_update(context->bt); +#endif + context->is_advertising = true; bt_set_status_changed_callback(context->bt, connection_status_changed_callback, context); @@ -126,7 +169,7 @@ TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init() { } void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context) { - furi_assert(context != NULL); + furi_check(context != NULL); if(context->thread != NULL) { totp_bt_type_code_worker_stop(context); @@ -142,6 +185,11 @@ void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context) { furi_delay_ms(200); bt_keys_storage_set_default_path(context->bt); +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, context->previous_bt_name); + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, context->previous_bt_mac); +#endif + if(!bt_set_profile(context->bt, BtProfileSerial)) { FURI_LOG_E(LOGGING_TAG, "Failed to switch BT to Serial profile"); } diff --git a/applications/external/totp/workers/bt_type_code/bt_type_code.h b/applications/external/totp/workers/bt_type_code/bt_type_code.h index 46fdb7aff..edbe52e14 100644 --- a/applications/external/totp/workers/bt_type_code/bt_type_code.h +++ b/applications/external/totp/workers/bt_type_code/bt_type_code.h @@ -4,6 +4,12 @@ #include #include #include +#include "../../features_config.h" + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME +#define TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN 18 +#define TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE +#endif typedef uint8_t TotpBtTypeCodeWorkerEvent; @@ -16,6 +22,11 @@ typedef struct { Bt* bt; bool is_advertising; bool is_connected; +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME + uint8_t bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN]; + char previous_bt_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN + 1]; + uint8_t previous_bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN]; +#endif } TotpBtTypeCodeWorkerContext; enum TotpBtTypeCodeWorkerEvents { diff --git a/applications/external/totp/workers/common.c b/applications/external/totp/workers/common.c index 5ca5d4fdf..8ad0c2b46 100644 --- a/applications/external/totp/workers/common.c +++ b/applications/external/totp/workers/common.c @@ -3,17 +3,15 @@ #include #include "../../services/convert/convert.h" -static const uint8_t hid_number_keys[10] = { - HID_KEYBOARD_0, - HID_KEYBOARD_1, - HID_KEYBOARD_2, - HID_KEYBOARD_3, - HID_KEYBOARD_4, - HID_KEYBOARD_5, - HID_KEYBOARD_6, - HID_KEYBOARD_7, - HID_KEYBOARD_8, - HID_KEYBOARD_9}; +static const uint8_t hid_number_keys[] = { + HID_KEYBOARD_0, HID_KEYBOARD_1, HID_KEYBOARD_2, HID_KEYBOARD_3, HID_KEYBOARD_4, + HID_KEYBOARD_5, HID_KEYBOARD_6, HID_KEYBOARD_7, HID_KEYBOARD_8, HID_KEYBOARD_9, + HID_KEYBOARD_A, HID_KEYBOARD_B, HID_KEYBOARD_C, HID_KEYBOARD_D, HID_KEYBOARD_E, + HID_KEYBOARD_F, HID_KEYBOARD_G, HID_KEYBOARD_H, HID_KEYBOARD_I, HID_KEYBOARD_J, + HID_KEYBOARD_K, HID_KEYBOARD_L, HID_KEYBOARD_M, HID_KEYBOARD_N, HID_KEYBOARD_O, + HID_KEYBOARD_P, HID_KEYBOARD_Q, HID_KEYBOARD_R, HID_KEYBOARD_S, HID_KEYBOARD_T, + HID_KEYBOARD_U, HID_KEYBOARD_V, HID_KEYBOARD_W, HID_KEYBOARD_X, HID_KEYBOARD_Y, + HID_KEYBOARD_Z}; static uint32_t get_keystroke_delay(TokenAutomationFeature features) { if(features & TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER) { @@ -49,10 +47,18 @@ void totp_type_code_worker_execute_automation( TokenAutomationFeature features) { furi_delay_ms(500); uint8_t i = 0; + totp_type_code_worker_press_key( + HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features); + while(i < string_length && string[i] != 0) { - uint8_t digit = CONVERT_CHAR_TO_DIGIT(string[i]); - if(digit > 9) break; - uint8_t hid_kb_key = hid_number_keys[digit]; + uint8_t char_index = CONVERT_CHAR_TO_DIGIT(string[i]); + if(char_index > 9) { + char_index = string[i] - 0x41 + 10; + } + + if(char_index > 35) break; + + uint8_t hid_kb_key = hid_number_keys[char_index]; totp_type_code_worker_press_key(hid_kb_key, key_press_fn, key_release_fn, features); furi_delay_ms(get_keystroke_delay(features)); i++; @@ -68,4 +74,7 @@ void totp_type_code_worker_execute_automation( furi_delay_ms(get_keystroke_delay(features)); totp_type_code_worker_press_key(HID_KEYBOARD_TAB, key_press_fn, key_release_fn, features); } + + totp_type_code_worker_press_key( + HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features); } \ No newline at end of file diff --git a/applications/external/totp/workers/usb_type_code/usb_type_code.c b/applications/external/totp/workers/usb_type_code/usb_type_code.c index d3e09a065..5f7ccddf8 100644 --- a/applications/external/totp/workers/usb_type_code/usb_type_code.c +++ b/applications/external/totp/workers/usb_type_code/usb_type_code.c @@ -41,7 +41,7 @@ static void totp_type_code_worker_type_code(TotpUsbTypeCodeWorkerContext* contex } static int32_t totp_type_code_worker_callback(void* context) { - furi_assert(context); + furi_check(context); FuriMutex* context_mutex = furi_mutex_alloc(FuriMutexTypeNormal); if(context_mutex == NULL) { return 251; @@ -89,7 +89,7 @@ TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start( } void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context) { - furi_assert(context != NULL); + furi_check(context != NULL); furi_thread_flags_set(furi_thread_get_id(context->thread), TotpUsbTypeCodeWorkerEventStop); furi_thread_join(context->thread); furi_thread_free(context->thread); @@ -101,7 +101,7 @@ void totp_usb_type_code_worker_notify( TotpUsbTypeCodeWorkerContext* context, TotpUsbTypeCodeWorkerEvent event, uint8_t flags) { - furi_assert(context != NULL); + furi_check(context != NULL); context->flags = flags; furi_thread_flags_set(furi_thread_get_id(context->thread), event); } \ No newline at end of file diff --git a/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c b/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c index a9f998124..38a5a20e4 100644 --- a/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c +++ b/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c @@ -111,11 +111,21 @@ void uart_terminal_scene_console_output_on_enter(void* context) { uart_terminal_uart_set_handle_rx_data_cb( app->uart, uart_terminal_console_output_handle_rx_data_cb); // setup callback for rx thread - // Send command with newline '\n' + // Send command with CR+LF or newline '\n' if(app->is_command && app->selected_tx_string) { - uart_terminal_uart_tx( - (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string)); - uart_terminal_uart_tx((uint8_t*)("\n"), 1); + if(app->TERMINAL_MODE == 1) { + // char buffer[240]; + // snprintf(buffer, 240, "%s\r\n", (app->selected_tx_string)); + // uart_terminal_uart_tx((unsigned char *)buffer, strlen(buffer)); + uart_terminal_uart_tx( + (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string)); + uart_terminal_uart_tx((uint8_t*)("\r"), 1); + uart_terminal_uart_tx((uint8_t*)("\n"), 1); + } else { + uart_terminal_uart_tx( + (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string)); + uart_terminal_uart_tx((uint8_t*)("\n"), 1); + } } } @@ -144,4 +154,4 @@ void uart_terminal_scene_console_output_on_exit(void* context) { //if(app->is_command) { // uart_terminal_uart_tx((uint8_t*)("exit\n"), strlen("exit\n")); //} -} +} \ No newline at end of file diff --git a/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c b/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c index db783e9b2..d4de748b6 100644 --- a/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c +++ b/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c @@ -31,6 +31,7 @@ const UART_TerminalItem items[NUM_MENU_ITEMS] = { FOCUS_CONSOLE_TOGGLE, NO_TIP}, {"Send command", {""}, 1, {""}, INPUT_ARGS, FOCUS_CONSOLE_END, NO_TIP}, + {"Send AT command", {""}, 1, {"AT"}, INPUT_ARGS, FOCUS_CONSOLE_END, NO_TIP}, {"Fast cmd", {"help", "uptime", "date", "df -h", "ps", "dmesg", "reboot", "poweroff"}, 8, diff --git a/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c b/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c index 62c0792d4..e200ba266 100644 --- a/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c +++ b/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c @@ -25,7 +25,13 @@ void uart_terminal_scene_text_input_on_enter(void* context) { // Setup view UART_TextInput* text_input = app->text_input; // Add help message to header - uart_text_input_set_header_text(text_input, "Send command to UART"); + if(0 == strncmp("AT", app->selected_tx_string, strlen("AT"))) { + app->TERMINAL_MODE = 1; + uart_text_input_set_header_text(text_input, "Send AT command to UART"); + } else { + app->TERMINAL_MODE = 0; + uart_text_input_set_header_text(text_input, "Send command to UART"); + } uart_text_input_set_result_callback( text_input, uart_terminal_scene_text_input_callback, diff --git a/applications/external/uart_terminal/uart_terminal_app_i.h b/applications/external/uart_terminal/uart_terminal_app_i.h index 6b1996eb5..a0c4bb81f 100644 --- a/applications/external/uart_terminal/uart_terminal_app_i.h +++ b/applications/external/uart_terminal/uart_terminal_app_i.h @@ -12,7 +12,7 @@ #include #include "uart_text_input.h" -#define NUM_MENU_ITEMS (4) +#define NUM_MENU_ITEMS (5) #define UART_TERMINAL_TEXT_BOX_STORE_SIZE (4096) #define UART_TERMINAL_TEXT_INPUT_STORE_SIZE (512) @@ -40,6 +40,7 @@ struct UART_TerminalApp { bool focus_console_start; bool show_stopscan_tip; int BAUDRATE; + int TERMINAL_MODE; //1=AT mode, 0=other mode }; typedef enum { diff --git a/applications/external/uart_terminal/uart_text_input.c b/applications/external/uart_terminal/uart_text_input.c index 4a571b127..7d400b81e 100644 --- a/applications/external/uart_terminal/uart_text_input.c +++ b/applications/external/uart_terminal/uart_text_input.c @@ -1,6 +1,7 @@ #include "uart_text_input.h" #include #include "uart_terminal_icons.h" +#include "uart_terminal_app_i.h" #include struct UART_TextInput { @@ -36,6 +37,8 @@ static const uint8_t keyboard_origin_x = 1; static const uint8_t keyboard_origin_y = 29; static const uint8_t keyboard_row_count = 4; +#define mode_AT "Send AT command to UART" + #define ENTER_KEY '\r' #define BACKSPACE_KEY '\b' @@ -163,6 +166,47 @@ static bool char_is_lowercase(char letter) { return (letter >= 0x61 && letter <= 0x7A); } +static bool char_is_uppercase(char letter) { + return (letter >= 0x41 && letter <= 0x5A); +} + +static char char_to_lowercase(const char letter) { + switch(letter) { + case ' ': + return 0x5f; + break; + case ')': + return 0x28; + break; + case '}': + return 0x7b; + break; + case ']': + return 0x5b; + break; + case '\\': + return 0x2f; + break; + case ':': + return 0x3b; + break; + case ',': + return 0x2e; + break; + case '?': + return 0x21; + break; + case '>': + return 0x3c; + break; + } + if(char_is_uppercase(letter)) { + return (letter + 0x20); + } else { + return letter; + } +} + static char char_to_uppercase(const char letter) { switch(letter) { case '_': @@ -193,7 +237,7 @@ static char char_to_uppercase(const char letter) { return 0x3e; break; } - if(isalpha(letter)) { + if(char_is_lowercase(letter)) { return (letter - 0x20); } else { return letter; @@ -209,7 +253,7 @@ static void uart_text_input_backspace_cb(UART_TextInputModel* model) { static void uart_text_input_view_draw_callback(Canvas* canvas, void* _model) { UART_TextInputModel* model = _model; - uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; + //uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0; uint8_t needed_string_width = canvas_width(canvas) - 8; uint8_t start_pos = 4; @@ -291,15 +335,12 @@ static void uart_text_input_view_draw_callback(Canvas* canvas, void* _model) { } else { canvas_set_color(canvas, ColorBlack); } - - if(model->clear_default_text || - (text_length == 0 && char_is_lowercase(keys[column].text))) { + if(0 == strcmp(model->header, mode_AT)) { canvas_draw_glyph( canvas, keyboard_origin_x + keys[column].x, keyboard_origin_y + keys[column].y, - //char_to_uppercase(keys[column].text)); - keys[column].text); + char_to_uppercase(keys[column].text)); } else { canvas_draw_glyph( canvas, @@ -372,10 +413,18 @@ static void uart_text_input_handle_ok( char selected = get_selected_char(model); uint8_t text_length = strlen(model->text_buffer); - if(shift) { + if(0 == strcmp(model->header, mode_AT)) { selected = char_to_uppercase(selected); } + if(shift) { + if(0 == strcmp(model->header, mode_AT)) { + selected = char_to_lowercase(selected); + } else { + selected = char_to_uppercase(selected); + } + } + if(selected == ENTER_KEY) { if(model->validator_callback && (!model->validator_callback( @@ -392,9 +441,6 @@ static void uart_text_input_handle_ok( text_length = 0; } if(text_length < (model->text_buffer_size - 1)) { - if(text_length == 0 && char_is_lowercase(selected)) { - //selected = char_to_uppercase(selected); - } model->text_buffer[text_length] = selected; model->text_buffer[text_length + 1] = 0; } diff --git a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c index 5d007b12f..f2fddd40c 100644 --- a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c +++ b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c @@ -2,11 +2,15 @@ #define TAG "WSProtocolLaCrosse_TX141THBv2" +#define LACROSSE_TX141TH_BV2_BIT_COUNT 41 + /* * Help * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse_tx141x.c * - * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u + * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u - 41 bit + * or + * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | -40 bit * - i: identification; changes on battery switch * - c: lfsr_digest8_reflect; * - u: unknown; @@ -17,10 +21,10 @@ */ static const SubGhzBlockConst ws_protocol_lacrosse_tx141thbv2_const = { - .te_short = 250, - .te_long = 500, + .te_short = 208, + .te_long = 417, .te_delta = 120, - .min_count_bit_for_found = 41, + .min_count_bit_for_found = 40, }; struct WSProtocolDecoderLaCrosse_TX141THBv2 { @@ -102,14 +106,14 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context) { static bool ws_protocol_lacrosse_tx141thbv2_check_crc(WSProtocolDecoderLaCrosse_TX141THBv2* instance) { if(!instance->decoder.decode_data) return false; - uint8_t msg[] = { - instance->decoder.decode_data >> 33, - instance->decoder.decode_data >> 25, - instance->decoder.decode_data >> 17, - instance->decoder.decode_data >> 9}; + uint64_t data = instance->decoder.decode_data; + if(instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) { + data >>= 1; + } + uint8_t msg[] = {data >> 32, data >> 24, data >> 16, data >> 8}; uint8_t crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4); - return (crc == ((instance->decoder.decode_data >> 1) & 0xFF)); + return (crc == (data & 0xFF)); } /** @@ -117,14 +121,43 @@ static bool * @param instance Pointer to a WSBlockGeneric* instance */ static void ws_protocol_lacrosse_tx141thbv2_remote_controller(WSBlockGeneric* instance) { - instance->id = instance->data >> 33; - instance->battery_low = (instance->data >> 32) & 1; - instance->btn = (instance->data >> 31) & 1; - instance->channel = ((instance->data >> 29) & 0x03) + 1; - instance->temp = ((float)((instance->data >> 17) & 0x0FFF) - 500.0f) / 10.0f; - instance->humidity = (instance->data >> 9) & 0xFF; + uint64_t data = instance->data; + if(instance->data_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) { + data >>= 1; + } + instance->id = data >> 32; + instance->battery_low = (data >> 31) & 1; + instance->btn = (data >> 30) & 1; + instance->channel = ((data >> 28) & 0x03) + 1; + instance->temp = ((float)((data >> 16) & 0x0FFF) - 500.0f) / 10.0f; + instance->humidity = (data >> 8) & 0xFF; } +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static bool ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + WSProtocolDecoderLaCrosse_TX141THBv2* instance, + uint32_t te_last, + uint32_t te_current) { + furi_assert(instance); + bool ret = false; + if(DURATION_DIFF( + te_last + te_current, + ws_protocol_lacrosse_tx141thbv2_const.te_short + + ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) { + if(te_last > te_current) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + ret = true; + } + + return ret; +} void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration) { furi_assert(context); WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; @@ -132,7 +165,7 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin switch(instance->decoder.parser_step) { case LaCrosse_TX141THBv2DecoderStepReset: if((level) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; instance->decoder.te_last = duration; @@ -146,33 +179,17 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin } else { if((DURATION_DIFF( instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { //Found preambule instance->header_count++; } else if(instance->header_count == 4) { - if((DURATION_DIFF( - instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, - ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - subghz_protocol_blocks_add_bit(&instance->decoder, 1); + if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + instance, instance->decoder.te_last, duration)) { + instance->decoder.decode_data = instance->decoder.decode_data & 1; + instance->decoder.decode_count_bit = 1; instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; } else { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; @@ -198,37 +215,26 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) { if((instance->decoder.decode_count_bit == - ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) && - ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) { - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic); - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); + ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) || + (instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT)) { + if(ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 1; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; + break; } - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - instance->header_count = 1; - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; - break; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 0); - instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; - } else if( - (DURATION_DIFF( - instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_long) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta) && - (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) < - ws_protocol_lacrosse_tx141thbv2_const.te_delta)) { - subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + instance, instance->decoder.te_last, duration)) { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; } else { instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; diff --git a/applications/main/gpio/views/gpio_usb_uart.c b/applications/main/gpio/views/gpio_usb_uart.c index 837f2e3ec..3234309a2 100644 --- a/applications/main/gpio/views/gpio_usb_uart.c +++ b/applications/main/gpio/views/gpio_usb_uart.c @@ -80,9 +80,9 @@ static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) { canvas_draw_icon(canvas, 48, 14, &I_ArrowUpEmpty_14x15); if(model->rx_active) - canvas_draw_icon(canvas, 48, 34, &I_ArrowDownFilled_14x15); + canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180); else - canvas_draw_icon(canvas, 48, 34, &I_ArrowDownEmpty_14x15); + canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpEmpty_14x15, IconRotation180); } static bool gpio_usb_uart_input_callback(InputEvent* event, void* context) { diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 3c5982427..270fd7a21 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -35,6 +35,12 @@ typedef enum { SubGhzSpeakerStateEnable, } SubGhzSpeakerState; +/** SubGhzStarLineIgnore state */ +typedef enum { + SubGhzStarLineIgnoreDisable, + SubGhzStarLineIgnoreEnable, +} SubGhzStarLineIgnoreState; + /** SubGhzRxKeyState state */ typedef enum { SubGhzRxKeyStateIDLE, diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index c0112199c..08fed3aa4 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -147,6 +147,16 @@ void subghz_scene_receiver_on_enter(void* context) { subghz_receiver_set_rx_callback( subghz->txrx->receiver, subghz_scene_add_to_history_callback, subghz); + if(subghz->txrx->starline_state == SubGhzStarLineIgnoreEnable) { + SubGhzProtocolDecoderBase* protocoldecoderbase = NULL; + protocoldecoderbase = + subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, "Star Line"); + if(protocoldecoderbase) { + subghz_protocol_decoder_base_set_decoder_callback( + protocoldecoderbase, NULL, subghz->txrx->receiver); + } + } + subghz->state_notifications = SubGhzNotificationStateRx; if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { subghz_rx_end(subghz); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index ff51e2f4d..6a6cf860b 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -6,6 +6,7 @@ enum SubGhzSettingIndex { SubGhzSettingIndexHopping, SubGhzSettingIndexModulation, SubGhzSettingIndexBinRAW, + SubGhzSettingIndexIgnoreStarline, SubGhzSettingIndexSound, SubGhzSettingIndexLock, SubGhzSettingIndexRAWThresholdRSSI, @@ -68,6 +69,15 @@ const uint32_t bin_raw_value[BIN_RAW_COUNT] = { SubGhzProtocolFlag_Decodable, SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_BinRAW, }; +#define STAR_LINE_COUNT 2 +const char* const star_line_text[STAR_LINE_COUNT] = { + "OFF", + "ON", +}; +const uint32_t star_line_value[STAR_LINE_COUNT] = { + SubGhzStarLineIgnoreDisable, + SubGhzStarLineIgnoreEnable, +}; uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) { furi_assert(context); @@ -217,6 +227,14 @@ static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* it subghz->txrx->raw_threshold_rssi = raw_threshold_rssi_value[index]; } +static void subghz_scene_receiver_config_set_starline(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, star_line_text[index]); + subghz->txrx->starline_state = star_line_value[index]; +} + static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { furi_assert(context); SubGhz* subghz = context; @@ -291,6 +309,21 @@ void subghz_scene_receiver_config_on_enter(void* context) { variable_item_set_current_value_text(item, bin_raw_text[value_index]); } + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != + SubGhzCustomEventManagerSet) { + item = variable_item_list_add( + subghz->variable_item_list, + "Ignore StarLine:", + STAR_LINE_COUNT, + subghz_scene_receiver_config_set_starline, + subghz); + + value_index = + value_index_uint32(subghz->txrx->starline_state, star_line_value, STAR_LINE_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, star_line_text[value_index]); + } + // Enable speaker, will send all incoming noises and signals to speaker so you can listen how your remote sounds like :) item = variable_item_list_add( subghz->variable_item_list, diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index eb9e7d620..431d5d2d7 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -558,7 +558,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { subghz, "AM650", 433920000, - (key & 0x00FFFFFF) | 0x04000000, + (key & 0x000FFFFF) | 0x04700000, 0x2, 0x0021, "AN-Motors")) { diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 393dd667d..adabe4aad 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -73,6 +73,7 @@ struct SubGhzTxRx { SubGhzTxRxState txrx_state; SubGhzHopperState hopper_state; SubGhzSpeakerState speaker_state; + SubGhzStarLineIgnoreState starline_state; uint8_t hopper_timeout; uint8_t hopper_idx_frequency; SubGhzRxKeyState rx_key_state; diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index dab7fefc3..a578ecfcb 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -36,7 +36,7 @@ static void desktop_loader_callback(const void* message, void* context) { static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) { UNUSED(context); furi_assert(canvas); - canvas_draw_icon(canvas, 0, 0, &I_Lock_8x8); + canvas_draw_icon(canvas, 0, 0, &I_Lock_7x8); } static bool desktop_custom_event_callback(void* context, uint32_t event) { @@ -216,7 +216,7 @@ Desktop* desktop_alloc() { // Lock icon desktop->lock_icon_viewport = view_port_alloc(); - view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_8x8)); + view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_7x8)); view_port_draw_callback_set( desktop->lock_icon_viewport, desktop_lock_icon_draw_callback, desktop); view_port_enabled_set(desktop->lock_icon_viewport, false); diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index b86bf2929..d3dadd7d7 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -115,16 +115,18 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu } else { switch(model->pin.data[i]) { case InputKeyDown: - canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_down_7x9); + canvas_draw_icon_ex( + canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9, IconRotation180); break; case InputKeyUp: - canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9); + canvas_draw_icon_ex(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9, IconRotation0); break; case InputKeyLeft: - canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_left_9x7); + canvas_draw_icon_ex( + canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation270); break; case InputKeyRight: - canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_right_9x7); + canvas_draw_icon_ex(canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation90); break; default: furi_assert(0); @@ -147,7 +149,8 @@ static void desktop_view_pin_input_draw(Canvas* canvas, void* context) { desktop_view_pin_input_draw_cells(canvas, model); if((model->pin.length > 0) && !model->locked_input) { - canvas_draw_icon(canvas, 4, 53, &I_Pin_back_full_40x8); + canvas_draw_icon(canvas, 4, 53, &I_Pin_back_arrow_10x8); + canvas_draw_str(canvas, 16, 60, "= clear"); } if(model->button_label && ((model->pin.length >= MIN_PIN_SIZE) || model->locked_input)) { diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index daefeb73d..fc4064282 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -236,7 +236,7 @@ void canvas_draw_bitmap( y += canvas->offset_y; uint8_t* bitmap_data = NULL; compress_icon_decode(canvas->compress_icon, compressed_bitmap_data, &bitmap_data); - u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data); + canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap_data, IconRotation0); } void canvas_draw_icon_animation( @@ -252,13 +252,138 @@ void canvas_draw_icon_animation( uint8_t* icon_data = NULL; compress_icon_decode( canvas->compress_icon, icon_animation_get_data(icon_animation), &icon_data); - u8g2_DrawXBM( + canvas_draw_u8g2_bitmap( &canvas->fb, x, y, icon_animation_get_width(icon_animation), icon_animation_get_height(icon_animation), - icon_data); + icon_data, + IconRotation0); +} + +static void canvas_draw_u8g2_bitmap_int( + u8g2_t* u8g2, + u8g2_uint_t x, + u8g2_uint_t y, + u8g2_uint_t w, + u8g2_uint_t h, + bool mirror, + bool rotation, + const uint8_t* bitmap) { + u8g2_uint_t blen; + blen = w; + blen += 7; + blen >>= 3; + + if(rotation && !mirror) { + x += w + 1; + } else if(mirror && !rotation) { + y += h - 1; + } + + while(h > 0) { + const uint8_t* b = bitmap; + uint16_t len = w; + uint16_t x0 = x; + uint16_t y0 = y; + uint8_t mask; + uint8_t color = u8g2->draw_color; + uint8_t ncolor = (color == 0 ? 1 : 0); + mask = 1; + + while(len > 0) { + if(u8x8_pgm_read(b) & mask) { + u8g2->draw_color = color; + u8g2_DrawHVLine(u8g2, x0, y0, 1, 0); + } else if(u8g2->bitmap_transparency == 0) { + u8g2->draw_color = ncolor; + u8g2_DrawHVLine(u8g2, x0, y0, 1, 0); + } + + if(rotation) { + y0++; + } else { + x0++; + } + + mask <<= 1; + if(mask == 0) { + mask = 1; + b++; + } + len--; + } + + u8g2->draw_color = color; + bitmap += blen; + + if(mirror) { + if(rotation) { + x++; + } else { + y--; + } + } else { + if(rotation) { + x--; + } else { + y++; + } + } + h--; + } +} + +void canvas_draw_u8g2_bitmap( + u8g2_t* u8g2, + u8g2_uint_t x, + u8g2_uint_t y, + u8g2_uint_t w, + u8g2_uint_t h, + const uint8_t* bitmap, + IconRotation rotation) { + u8g2_uint_t blen; + blen = w; + blen += 7; + blen >>= 3; +#ifdef U8G2_WITH_INTERSECTION + if(u8g2_IsIntersection(u8g2, x, y, x + w, y + h) == 0) return; +#endif /* U8G2_WITH_INTERSECTION */ + + switch(rotation) { + case IconRotation0: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 0, 0, bitmap); + break; + case IconRotation90: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 0, 1, bitmap); + break; + case IconRotation180: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 1, 0, bitmap); + break; + case IconRotation270: + canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 1, 1, bitmap); + break; + default: + break; + } +} + +void canvas_draw_icon_ex( + Canvas* canvas, + uint8_t x, + uint8_t y, + const Icon* icon, + IconRotation rotation) { + furi_assert(canvas); + furi_assert(icon); + + x += canvas->offset_x; + y += canvas->offset_y; + uint8_t* icon_data = NULL; + compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); + canvas_draw_u8g2_bitmap( + &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, rotation); } void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) { @@ -269,7 +394,8 @@ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) { y += canvas->offset_y; uint8_t* icon_data = NULL; compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); - u8g2_DrawXBM(&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data); + canvas_draw_u8g2_bitmap( + &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, IconRotation0); } void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y) { @@ -379,7 +505,7 @@ void canvas_draw_xbm( furi_assert(canvas); x += canvas->offset_x; y += canvas->offset_y; - u8g2_DrawXBM(&canvas->fb, x, y, w, h, bitmap); + canvas_draw_u8g2_bitmap(&canvas->fb, x, y, w, h, bitmap, IconRotation0); } void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch) { diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 79ad2bcc5..9d8cf607f 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -65,6 +65,22 @@ typedef struct { uint8_t descender; } CanvasFontParameters; +/** Icon flip */ +typedef enum { + IconFlipNone, + IconFlipHorizontal, + IconFlipVertical, + IconFlipBoth, +} IconFlip; + +/** Icon rotation */ +typedef enum { + IconRotation0, + IconRotation90, + IconRotation180, + IconRotation270, +} IconRotation; + /** Canvas anonymous structure */ typedef struct Canvas Canvas; @@ -218,6 +234,22 @@ void canvas_draw_bitmap( uint8_t height, const uint8_t* compressed_bitmap_data); +/** Draw icon at position defined by x,y with rotation and flip. + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param icon Icon instance + * @param flip IconFlip + * @param rotation IconRotation + */ +void canvas_draw_icon_ex( + Canvas* canvas, + uint8_t x, + uint8_t y, + const Icon* icon, + IconRotation rotation); + /** Draw animation at position defined by x,y. * * @param canvas Canvas instance diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index aadb5200f..f26fe8a89 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -83,6 +83,25 @@ void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation); */ CanvasOrientation canvas_get_orientation(const Canvas* canvas); +/** Draw a u8g2 bitmap + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param width width + * @param height height + * @param bitmap bitmap + * @param rotation rotation + */ +void canvas_draw_u8g2_bitmap( + u8g2_t* u8g2, + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + const uint8_t* bitmap, + uint8_t rotation); + #ifdef __cplusplus } #endif diff --git a/applications/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp index 814dd82c9..52e86efc2 100644 --- a/applications/services/loader/firmware_api/firmware_api.cpp +++ b/applications/services/loader/firmware_api/firmware_api.cpp @@ -4,7 +4,7 @@ #include /* Generated table */ -#include +#include static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); diff --git a/assets/icons/GPIO/ArrowDownEmpty_14x15.png b/assets/icons/GPIO/ArrowDownEmpty_14x15.png deleted file mode 100644 index 8c6d54f9c..000000000 Binary files a/assets/icons/GPIO/ArrowDownEmpty_14x15.png and /dev/null differ diff --git a/assets/icons/GPIO/ArrowDownFilled_14x15.png b/assets/icons/GPIO/ArrowDownFilled_14x15.png deleted file mode 100644 index 6cef0f4a7..000000000 Binary files a/assets/icons/GPIO/ArrowDownFilled_14x15.png and /dev/null differ diff --git a/assets/icons/PIN/Pin_arrow_down_7x9.png b/assets/icons/PIN/Pin_arrow_down_7x9.png deleted file mode 100644 index 9687397af..000000000 Binary files a/assets/icons/PIN/Pin_arrow_down_7x9.png and /dev/null differ diff --git a/assets/icons/PIN/Pin_arrow_left_9x7.png b/assets/icons/PIN/Pin_arrow_left_9x7.png deleted file mode 100644 index fb4ded78f..000000000 Binary files a/assets/icons/PIN/Pin_arrow_left_9x7.png and /dev/null differ diff --git a/assets/icons/PIN/Pin_arrow_right_9x7.png b/assets/icons/PIN/Pin_arrow_right_9x7.png deleted file mode 100644 index 97648d176..000000000 Binary files a/assets/icons/PIN/Pin_arrow_right_9x7.png and /dev/null differ diff --git a/assets/icons/PIN/Pin_back_full_40x8.png b/assets/icons/PIN/Pin_back_full_40x8.png deleted file mode 100644 index cd1301512..000000000 Binary files a/assets/icons/PIN/Pin_back_full_40x8.png and /dev/null differ diff --git a/assets/icons/StatusBar/Lock_8x8.png b/assets/icons/StatusBar/Lock_8x8.png deleted file mode 100644 index 01fb0eb6b..000000000 Binary files a/assets/icons/StatusBar/Lock_8x8.png and /dev/null differ diff --git a/firmware.scons b/firmware.scons index a094765af..c7fdc6392 100644 --- a/firmware.scons +++ b/firmware.scons @@ -68,7 +68,7 @@ env = ENV.Clone( ], }, }, - SDK_APISYMS=None, + FW_API_TABLE=None, _APP_ICONS=None, ) @@ -241,7 +241,7 @@ Depends( [ fwenv["FW_VERSION_JSON"], fwenv["FW_ASSETS_HEADERS"], - fwenv["SDK_APISYMS"], + fwenv["FW_API_TABLE"], fwenv["_APP_ICONS"], ], ) diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv index a76c3a6ee..f1b598b00 100644 --- a/firmware/targets/f18/api_symbols.csv +++ b/firmware/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,20.0,, +Version,+,20.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -540,6 +540,7 @@ Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t" Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*" Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*" +Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation" Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" @@ -876,12 +877,6 @@ Function,-,furi_hal_clock_resume_tick,void, Function,-,furi_hal_clock_suspend_tick,void, Function,-,furi_hal_clock_switch_to_hsi,void, Function,-,furi_hal_clock_switch_to_pll,void, -Function,-,compress_alloc,Compress*,uint16_t -Function,-,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*" -Function,-,compress_free,void,Compress* -Function,-,compress_icon_decode,void,"const uint8_t*, uint8_t**" -Function,-,compress_icon_init,void, Function,+,furi_hal_console_disable,void, Function,+,furi_hal_console_enable,void, Function,+,furi_hal_console_init,void, diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 943a91f50..51174ab95 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -655,12 +655,14 @@ Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t" Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*" Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*" Function,+,canvas_draw_icon_bitmap,void,"Canvas*, uint8_t, uint8_t, int16_t, int16_t, const Icon*" +Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation" Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" Function,+,canvas_draw_str,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection" +Function,+,canvas_draw_u8g2_bitmap,void,"u8g2_t*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*, uint8_t" Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*" Function,-,canvas_frame_set,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" Function,-,canvas_free,void,Canvas* diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/firmware/targets/f7/fatfs/user_diskio.c index 8b693886f..1a9559a90 100644 --- a/firmware/targets/f7/fatfs/user_diskio.c +++ b/firmware/targets/f7/fatfs/user_diskio.c @@ -22,16 +22,14 @@ #include #include "sector_cache.h" -static volatile DSTATUS Stat = STA_NOINIT; - static DSTATUS driver_check_status(BYTE lun) { UNUSED(lun); - Stat = STA_NOINIT; - if(sd_get_card_state() == SdSpiStatusOK) { - Stat &= ~STA_NOINIT; + DSTATUS status = 0; + if(sd_get_card_state() != SdSpiStatusOK) { + status = STA_NOINIT; } - return Stat; + return status; } static DSTATUS driver_initialize(BYTE pdrv); @@ -127,6 +125,16 @@ static bool sd_device_write(uint32_t* buff, uint32_t sector, uint32_t count) { * @retval DSTATUS: Operation status */ static DSTATUS driver_initialize(BYTE pdrv) { + UNUSED(pdrv); + return RES_OK; +} + +/** + * @brief Gets Disk Status + * @param pdrv: Physical drive number (0..) + * @retval DSTATUS: Operation status + */ +static DSTATUS driver_status(BYTE pdrv) { furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; @@ -138,16 +146,6 @@ static DSTATUS driver_initialize(BYTE pdrv) { return status; } -/** - * @brief Gets Disk Status - * @param pdrv: Physical drive number (0..) - * @retval DSTATUS: Operation status - */ -static DSTATUS driver_status(BYTE pdrv) { - UNUSED(pdrv); - return Stat; -} - /** * @brief Reads Sector(s) * @param pdrv: Physical drive number (0..) @@ -244,15 +242,15 @@ static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT coun * @retval DRESULT: Operation result */ static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) { - UNUSED(pdrv); DRESULT res = RES_ERROR; SD_CardInfo CardInfo; - if(Stat & STA_NOINIT) return RES_NOTRDY; - furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast); furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast; + DSTATUS status = driver_check_status(pdrv); + if(status & STA_NOINIT) return RES_NOTRDY; + switch(cmd) { /* Make sure that no pending write process */ case CTRL_SYNC: diff --git a/firmware/targets/f7/src/dfu.c b/firmware/targets/f7/src/dfu.c index b060bc8d2..7e094b4c4 100644 --- a/firmware/targets/f7/src/dfu.c +++ b/firmware/targets/f7/src/dfu.c @@ -2,29 +2,24 @@ #include #include #include -#include #include #include +#include +#include void flipper_boot_dfu_show_splash() { // Initialize - CompressIcon* compress_icon = compress_icon_alloc(); + Canvas* canvas = canvas_init(); - u8g2_t* fb = malloc(sizeof(u8g2_t)); - memset(fb, 0, sizeof(u8g2_t)); - u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); - u8g2_InitDisplay(fb); - u8g2_SetDrawColor(fb, 0x01); - uint8_t* splash_data = NULL; - compress_icon_decode(compress_icon, icon_get_data(&I_DFU_128x50), &splash_data); - u8g2_DrawXBM(fb, 0, 64 - 50, 128, 50, splash_data); - u8g2_SetFont(fb, u8g2_font_helvB08_tr); - u8g2_DrawStr(fb, 2, 8, "Update & Recovery Mode"); - u8g2_DrawStr(fb, 2, 21, "DFU Started"); - u8g2_SetPowerSave(fb, 0); - u8g2_SendBuffer(fb); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); - compress_icon_free(compress_icon); + canvas_draw_icon(canvas, 0, 64 - 50, &I_DFU_128x50); + canvas_draw_str(canvas, 2, 8, "Update & Recovery Mode"); + canvas_draw_str(canvas, 2, 21, "DFU Started"); + canvas_commit(canvas); + + canvas_free(canvas); } void flipper_boot_dfu_exec() { diff --git a/firmware/targets/f7/src/recovery.c b/firmware/targets/f7/src/recovery.c index d037e8118..49d780d47 100644 --- a/firmware/targets/f7/src/recovery.c +++ b/firmware/targets/f7/src/recovery.c @@ -2,44 +2,43 @@ #include #include #include -#include #include #include +#include +#include #define COUNTER_VALUE (136U) -static void flipper_boot_recovery_draw_splash(u8g2_t* fb, size_t progress) { +static void flipper_boot_recovery_draw_progress(Canvas* canvas, size_t progress) { if(progress < COUNTER_VALUE) { // Fill the progress bar while the progress is going down - u8g2_SetDrawColor(fb, 0x01); - u8g2_DrawRFrame(fb, 59, 41, 69, 8, 2); + canvas_draw_rframe(canvas, 59, 41, 69, 8, 2); size_t width = (COUNTER_VALUE - progress) * 68 / COUNTER_VALUE; - u8g2_DrawBox(fb, 60, 42, width, 6); + canvas_draw_box(canvas, 60, 42, width, 6); } else { - u8g2_SetDrawColor(fb, 0x00); - u8g2_DrawRBox(fb, 59, 41, 69, 8, 2); + canvas_draw_rframe(canvas, 59, 41, 69, 8, 2); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 60, 42, 67, 6); + canvas_set_color(canvas, ColorBlack); } - u8g2_SendBuffer(fb); + canvas_commit(canvas); +} + +void flipper_boot_recovery_draw_splash(Canvas* canvas) { + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + + canvas_draw_icon(canvas, 0, 0, &I_Erase_pin_128x64); + + canvas_commit(canvas); } void flipper_boot_recovery_exec() { - u8g2_t* fb = malloc(sizeof(u8g2_t)); - u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); - u8g2_InitDisplay(fb); + Canvas* canvas = canvas_init(); - CompressIcon* compress_icon = compress_icon_alloc(); - uint8_t* splash_data = NULL; - compress_icon_decode(compress_icon, icon_get_data(&I_Erase_pin_128x64), &splash_data); - - u8g2_ClearBuffer(fb); - u8g2_SetDrawColor(fb, 0x01); - - // Draw the recovery picture - u8g2_DrawXBM(fb, 0, 0, 128, 64, splash_data); - u8g2_SendBuffer(fb); - u8g2_SetPowerSave(fb, 0); - compress_icon_free(compress_icon); + // Show recovery splashscreen + flipper_boot_recovery_draw_splash(canvas); size_t counter = COUNTER_VALUE; while(counter) { @@ -53,7 +52,7 @@ void flipper_boot_recovery_exec() { counter = COUNTER_VALUE; } - flipper_boot_recovery_draw_splash(fb, counter); + flipper_boot_recovery_draw_progress(canvas, counter); } if(!counter) { diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py index d2a58f3fb..e4c567993 100644 --- a/scripts/fbt_tools/fbt_assets.py +++ b/scripts/fbt_tools/fbt_assets.py @@ -1,6 +1,6 @@ from SCons.Builder import Builder from SCons.Action import Action -from SCons.Errors import SConsEnvironmentError +from SCons.Errors import StopError import os import subprocess @@ -90,7 +90,7 @@ def proto_ver_generator(target, source, env): source_dir=src_dir, ) except (subprocess.CalledProcessError, EnvironmentError) as e: - raise SConsEnvironmentError("Git: describe failed") + raise StopError("Git: describe failed") git_major, git_minor = git_describe.split(".") version_file_data = ( diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index ef0c2d301..4ac1c6873 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -21,6 +21,10 @@ from fbt.sdk.cache import SdkCache from fbt.util import extract_abs_dir_path +_FAP_META_SECTION = ".fapmeta" +_FAP_FILEASSETS_SECTION = ".fapassets" + + @dataclass class FlipperExternalAppInfo: app: FlipperApplication @@ -234,6 +238,8 @@ def BuildAppElf(env, app): def prepare_app_metadata(target, source, env): + metadata_node = next(filter(lambda t: t.name.endswith(_FAP_META_SECTION), target)) + sdk_cache = SdkCache(env["SDK_DEFINITION"].path, load_version_only=True) if not sdk_cache.is_buildable(): @@ -242,8 +248,7 @@ def prepare_app_metadata(target, source, env): ) app = env["APP"] - meta_file_name = source[0].path + ".meta" - with open(meta_file_name, "wb") as f: + with open(metadata_node.abspath, "wb") as f: f.write( assemble_manifest_data( app_manifest=app, @@ -337,24 +342,26 @@ def embed_app_metadata_emitter(target, source, env): if app.apptype == FlipperAppType.PLUGIN: target[0].name = target[0].name.replace(".fap", ".fal") - meta_file_name = source[0].path + ".meta" - target.append("#" + meta_file_name) + target.append(env.File(source[0].abspath + _FAP_META_SECTION)) if app.fap_file_assets: - files_section = source[0].path + ".files.section" - target.append("#" + files_section) + target.append(env.File(source[0].abspath + _FAP_FILEASSETS_SECTION)) return (target, source) def prepare_app_files(target, source, env): + files_section_node = next( + filter(lambda t: t.name.endswith(_FAP_FILEASSETS_SECTION), target) + ) + app = env["APP"] - directory = app._appdir.Dir(app.fap_file_assets) + directory = env.Dir(app._apppath).Dir(app.fap_file_assets) if not directory.exists(): raise UserError(f"File asset directory {directory} does not exist") bundler = FileBundler(directory.abspath) - bundler.export(source[0].path + ".files.section") + bundler.export(files_section_node.abspath) def generate_embed_app_metadata_actions(source, target, env, for_signature): @@ -367,15 +374,15 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): objcopy_str = ( "${OBJCOPY} " "--remove-section .ARM.attributes " - "--add-section .fapmeta=${SOURCE}.meta " + "--add-section ${_FAP_META_SECTION}=${SOURCE}${_FAP_META_SECTION} " ) if app.fap_file_assets: actions.append(Action(prepare_app_files, "$APPFILE_COMSTR")) - objcopy_str += "--add-section .fapassets=${SOURCE}.files.section " + objcopy_str += "--add-section ${_FAP_FILEASSETS_SECTION}=${SOURCE}${_FAP_FILEASSETS_SECTION} " objcopy_str += ( - "--set-section-flags .fapmeta=contents,noload,readonly,data " + "--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data " "--strip-debug --strip-unneeded " "--add-gnu-debuglink=${SOURCE} " "${SOURCES} ${TARGET}" @@ -391,6 +398,51 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): return Action(actions) +def AddAppLaunchTarget(env, appname, launch_target_name): + deploy_sources, flipp_dist_paths, validators = [], [], [] + run_script_extra_ars = "" + + def _add_dist_targets(app_artifacts): + validators.append(app_artifacts.validator) + for _, ext_path in app_artifacts.dist_entries: + deploy_sources.append(app_artifacts.compact) + flipp_dist_paths.append(f"/ext/{ext_path}") + return app_artifacts + + def _add_host_app_to_targets(host_app): + artifacts_app_to_run = env["EXT_APPS"].get(host_app.appid, None) + _add_dist_targets(artifacts_app_to_run) + for plugin in host_app._plugins: + _add_dist_targets(env["EXT_APPS"].get(plugin.appid, None)) + + artifacts_app_to_run = env.GetExtAppByIdOrPath(appname) + if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: + # We deploy host app instead + host_app = env["APPMGR"].get(artifacts_app_to_run.app.requires[0]) + + if host_app: + if host_app.apptype == FlipperAppType.EXTERNAL: + _add_host_app_to_targets(host_app) + else: + # host app is a built-in app + run_script_extra_ars = f"-a {host_app.name}" + _add_dist_targets(artifacts_app_to_run) + else: + raise UserError("Host app is unknown") + else: + _add_host_app_to_targets(artifacts_app_to_run.app) + + # print(deploy_sources, flipp_dist_paths) + env.PhonyTarget( + launch_target_name, + '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', + source=deploy_sources, + FLIPPER_FILE_TARGETS=flipp_dist_paths, + EXTRA_ARGS=run_script_extra_ars, + ) + env.Alias(launch_target_name, validators) + + def generate(env, **kw): env.SetDefault( EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}", @@ -410,10 +462,14 @@ def generate(env, **kw): EXT_APPS={}, # appid -> FlipperExternalAppInfo EXT_LIBS={}, _APP_ICONS=[], + _FAP_META_SECTION=_FAP_META_SECTION, + _FAP_FILEASSETS_SECTION=_FAP_FILEASSETS_SECTION, ) env.AddMethod(BuildAppElf) env.AddMethod(GetExtAppByIdOrPath) + env.AddMethod(AddAppLaunchTarget) + env.Append( BUILDERS={ "FapDist": Builder( diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 324819818..90d0831eb 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -38,13 +38,13 @@ def ProcessSdkDepends(env, filename): return depends -def prebuild_sdk_emitter(target, source, env): +def api_amalgam_emitter(target, source, env): target.append(env.ChangeFileExtension(target[0], ".d")) target.append(env.ChangeFileExtension(target[0], ".i.c")) return target, source -def prebuild_sdk_create_origin_file(target, source, env): +def api_amalgam_gen_origin_header(target, source, env): mega_file = env.subst("${TARGET}.c", target=target[0]) with open(mega_file, "wt") as sdk_c: sdk_c.write( @@ -87,6 +87,7 @@ class SdkMeta: class SdkTreeBuilder: SDK_DIR_SUBST = "SDK_ROOT_DIR" SDK_APP_EP_SUBST = "SDK_APP_EP_SUBST" + HEADER_EXTENSIONS = [".h", ".hpp"] def __init__(self, env, target, source) -> None: self.env = env @@ -111,7 +112,10 @@ class SdkTreeBuilder: lines = LogicalLines(deps_f).readlines() _, depends = lines[0].split(":", 1) self.header_depends = list( - filter(lambda fname: fname.endswith(".h"), depends.split()), + filter( + lambda fname: any(map(fname.endswith, self.HEADER_EXTENSIONS)), + depends.split(), + ), ) self.header_depends.append(self.sdk_env.subst("${LINKER_SCRIPT_PATH}")) self.header_depends.append(self.sdk_env.subst("${SDK_DEFINITION}")) @@ -180,12 +184,12 @@ class SdkTreeBuilder: self._generate_sdk_meta() -def deploy_sdk_tree_action(target, source, env): +def deploy_sdk_header_tree_action(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) return sdk_tree.deploy_action() -def deploy_sdk_tree_emitter(target, source, env): +def deploy_sdk_header_tree_emitter(target, source, env): sdk_tree = SdkTreeBuilder(env, target, source) return sdk_tree.emitter(target, source, env) @@ -224,7 +228,7 @@ def _check_sdk_is_up2date(sdk_cache: SdkCache): ) -def validate_sdk_cache(source, target, env): +def validate_api_cache(source, target, env): # print(f"Generating SDK for {source[0]} to {target[0]}") current_sdk = SdkCollector() current_sdk.process_source_file_for_sdk(source[0].path) @@ -237,7 +241,7 @@ def validate_sdk_cache(source, target, env): _check_sdk_is_up2date(sdk_cache) -def generate_sdk_symbols(source, target, env): +def generate_api_table(source, target, env): sdk_cache = SdkCache(source[0].path) _check_sdk_is_up2date(sdk_cache) @@ -249,11 +253,11 @@ def generate_sdk_symbols(source, target, env): def generate(env, **kw): if not env["VERBOSE"]: env.SetDefault( - SDK_PREGEN_COMSTR="\tPREGEN\t${TARGET}", - SDK_COMSTR="\tSDKSRC\t${TARGET}", + SDK_AMALGAMATE_HEADER_COMSTR="\tAPIPREP\t${TARGET}", + SDK_AMALGAMATE_PP_COMSTR="\tAPIPP\t${TARGET}", SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}", - SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}", - SDKDEPLOY_COMSTR="\tSDKTREE\t${TARGET}", + APITABLE_GENERATOR_COMSTR="\tAPITBL\t${TARGET}", + SDKTREE_COMSTR="\tSDKTREE\t${TARGET}", ) # Filtering out things cxxheaderparser cannot handle @@ -274,40 +278,40 @@ def generate(env, **kw): env.AddMethod(ProcessSdkDepends) env.Append( BUILDERS={ - "SDKPrebuilder": Builder( - emitter=prebuild_sdk_emitter, + "ApiAmalgamator": Builder( + emitter=api_amalgam_emitter, action=[ Action( - prebuild_sdk_create_origin_file, - "$SDK_PREGEN_COMSTR", + api_amalgam_gen_origin_header, + "$SDK_AMALGAMATE_HEADER_COMSTR", ), Action( "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c", - "$SDK_COMSTR", + "$SDK_AMALGAMATE_PP_COMSTR", ), ], suffix=".i", ), - "SDKTree": Builder( + "SDKHeaderTreeExtractor": Builder( action=Action( - deploy_sdk_tree_action, - "$SDKDEPLOY_COMSTR", + deploy_sdk_header_tree_action, + "$SDKTREE_COMSTR", ), - emitter=deploy_sdk_tree_emitter, + emitter=deploy_sdk_header_tree_emitter, src_suffix=".d", ), - "SDKSymUpdater": Builder( + "ApiTableValidator": Builder( action=Action( - validate_sdk_cache, + validate_api_cache, "$SDKSYM_UPDATER_COMSTR", ), suffix=".csv", src_suffix=".i", ), - "SDKSymGenerator": Builder( + "ApiSymbolTable": Builder( action=Action( - generate_sdk_symbols, - "$SDKSYM_GENERATOR_COMSTR", + generate_api_table, + "$APITABLE_GENERATOR_COMSTR", ), suffix=".h", src_suffix=".csv", diff --git a/scripts/fbt_tools/fbt_tweaks.py b/scripts/fbt_tools/fbt_tweaks.py index a903d4033..700f66d23 100644 --- a/scripts/fbt_tools/fbt_tweaks.py +++ b/scripts/fbt_tools/fbt_tweaks.py @@ -1,4 +1,6 @@ import SCons.Warnings as Warnings +from SCons.Errors import UserError + # from SCons.Script.Main import find_deepest_user_frame @@ -36,6 +38,11 @@ def fbt_warning(e): def generate(env): + if env.get("UFBT_WORK_DIR"): + raise UserError( + "You're trying to use a new format SDK on a legacy ufbt version. " + "Please update ufbt to a version from PyPI: https://pypi.org/project/ufbt/" + ) Warnings._warningOut = fbt_warning diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 47e11236d..7b56ee0d0 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -56,11 +56,11 @@ class StorageErrorCode(enum.Enum): class FlipperStorageException(Exception): - def __init__(self, message): - super().__init__(f"Storage error: {message}") - - def __init__(self, path: str, error_code: StorageErrorCode): - super().__init__(f"Storage error: path '{path}': {error_code.value}") + @staticmethod + def from_error_code(path: str, error_code: StorageErrorCode): + return FlipperStorageException( + f"Storage error: path '{path}': {error_code.value}" + ) class BufferedRead: @@ -247,7 +247,9 @@ class FlipperStorage: if self.has_error(answer): last_error = self.get_error(answer) self.read.until(self.CLI_PROMPT) - raise FlipperStorageException(filename_to, last_error) + raise FlipperStorageException.from_error_code( + filename_to, last_error + ) self.port.write(filedata) self.read.until(self.CLI_PROMPT) @@ -319,7 +321,7 @@ class FlipperStorage: StorageErrorCode.INVALID_NAME, ): return False - raise FlipperStorageException(path, error_code) + raise FlipperStorageException.from_error_code(path, error_code) return True @@ -333,7 +335,7 @@ class FlipperStorage: def _check_no_error(self, response, path=None): if self.has_error(response): - raise FlipperStorageException(self.get_error(response)) + raise FlipperStorageException.from_error_code(self.get_error(response)) def size(self, path: str): """file size on Flipper""" diff --git a/scripts/get_env.py b/scripts/get_env.py index e2da6eda5..f661f38d6 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -31,9 +31,10 @@ def parse_args(): def get_commit_json(event): context = ssl._create_unverified_context() - with urllib.request.urlopen( - event["pull_request"]["_links"]["commits"]["href"], context=context - ) as commit_file: + commit_url = event["pull_request"]["base"]["repo"]["commits_url"].replace( + "{/sha}", f"/{event['after']}" + ) + with urllib.request.urlopen(commit_url, context=context) as commit_file: commit_json = json.loads(commit_file.read().decode("utf-8")) return commit_json @@ -43,8 +44,8 @@ def get_details(event, args): current_time = datetime.datetime.utcnow().date() if args.type == "pull": commit_json = get_commit_json(event) - data["commit_comment"] = shlex.quote(commit_json[-1]["commit"]["message"]) - data["commit_hash"] = commit_json[-1]["sha"] + data["commit_comment"] = shlex.quote(commit_json["commit"]["message"]) + data["commit_hash"] = commit_json["sha"] ref = event["pull_request"]["head"]["ref"] data["pull_id"] = event["pull_request"]["number"] data["pull_name"] = shlex.quote(event["pull_request"]["title"]) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index ce261954c..2d5b1ae79 100644 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 -from flipper.app import App -from os.path import join, exists, relpath -from os import makedirs, walk, environ -from update import Main as UpdateMain +import json import shutil -import zipfile import tarfile +import zipfile +from os import makedirs, walk, environ +from os.path import exists, join, relpath, basename, split + from ansi.color import fg +from flipper.app import App +from update import Main as UpdateMain class ProjectDir: @@ -54,12 +56,19 @@ class Main(App): if project_name == "firmware" and filetype != "elf": project_name = "full" - return self.get_dist_file_name(project_name, filetype) + dist_target_path = self.get_dist_file_name(project_name, filetype) + self.note_dist_component( + project_name, filetype, self.get_dist_path(dist_target_path) + ) + return dist_target_path + + def note_dist_component(self, component: str, extension: str, srcpath: str) -> None: + self._dist_components[f"{component}.{extension}"] = srcpath def get_dist_file_name(self, dist_artifact_type: str, filetype: str) -> str: return f"{self.DIST_FILE_PREFIX}{self.target}-{dist_artifact_type}-{self.args.suffix}.{filetype}" - def get_dist_file_path(self, filename: str) -> str: + def get_dist_path(self, filename: str) -> str: return join(self.output_dir_path, filename) def copy_single_project(self, project: ProjectDir) -> None: @@ -69,32 +78,26 @@ class Main(App): if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")): shutil.copyfile( src_file, - self.get_dist_file_path( - self.get_project_file_name(project, filetype) - ), + self.get_dist_path(self.get_project_file_name(project, filetype)), ) - for foldertype in ("sdk", "lib"): + for foldertype in ("sdk_headers", "lib"): if exists(sdk_folder := join(obj_directory, foldertype)): - self.package_zip(foldertype, sdk_folder) + self.note_dist_component(foldertype, "dir", sdk_folder) - def package_zip(self, foldertype, sdk_folder): + # TODO: remove this after everyone migrates to new uFBT + self.create_zip_stub("lib") + + def create_zip_stub(self, foldertype): with zipfile.ZipFile( - self.get_dist_file_path(self.get_dist_file_name(foldertype, "zip")), + self.get_dist_path(self.get_dist_file_name(foldertype, "zip")), "w", zipfile.ZIP_DEFLATED, - ) as zf: - for root, _, files in walk(sdk_folder): - for file in files: - zf.write( - join(root, file), - relpath( - join(root, file), - sdk_folder, - ), - ) + ) as _: + pass def copy(self) -> int: - self.projects = dict( + self._dist_components: dict[str, str] = dict() + self.projects: dict[str, ProjectDir] = dict( map( lambda pd: (pd.project, pd), map(ProjectDir, self.args.project), @@ -122,12 +125,18 @@ class Main(App): try: shutil.rmtree(self.output_dir_path) except Exception as ex: - pass + self.logger.warn(f"Failed to clean output directory: {ex}") if not exists(self.output_dir_path): + self.logger.debug(f"Creating output directory {self.output_dir_path}") makedirs(self.output_dir_path) + for folder in ("debug", "scripts"): + if exists(folder): + self.note_dist_component(folder, "dir", folder) + for project in self.projects.values(): + self.logger.debug(f"Copying {project.project} for {project.target}") self.copy_single_project(project) self.logger.info( @@ -137,69 +146,140 @@ class Main(App): ) if self.args.version: - bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[ - : self.DIST_FOLDER_MAX_NAME_LENGTH - ] - bundle_dir = join(self.output_dir_path, bundle_dir_name) - bundle_args = [ - "generate", - "-d", - bundle_dir, - "-v", - self.args.version, - "-t", - self.target, - "--dfu", - self.get_dist_file_path( - self.get_project_file_name(self.projects["firmware"], "dfu") - ), - "--stage", - self.get_dist_file_path( - self.get_project_file_name(self.projects["updater"], "bin") - ), - ] - if self.args.resources: - bundle_args.extend( - ( - "-r", - self.args.resources, - ) - ) - bundle_args.extend(self.other_args) - log_custom_fz_name = environ.get("CUSTOM_FLIPPER_NAME", None) or "" - if ( - (log_custom_fz_name != "") - and (len(log_custom_fz_name) <= 8) - and (log_custom_fz_name.isalnum()) - and (log_custom_fz_name.isascii()) - ): - self.logger.info( - f"Flipper Custom Name is set:\n\tName: {log_custom_fz_name} : length - {len(log_custom_fz_name)} chars" - ) + if bundle_result := self.bundle_update_package(): + return bundle_result - if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: - self.logger.info( - fg.boldgreen( - f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" - ) - ) - - # Create tgz archive - with tarfile.open( - join( - self.output_dir_path, - f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz", - ), - "w:gz", - compresslevel=9, - format=tarfile.USTAR_FORMAT, - ) as tar: - tar.add(bundle_dir, arcname=bundle_dir_name) - - return bundle_result + required_components = ("firmware.elf", "full.bin", "update.dir") + if all( + map( + lambda c: c in self._dist_components, + required_components, + ) + ): + self.bundle_sdk() return 0 + def bundle_sdk(self): + self.logger.info("Bundling SDK") + components_paths = dict() + + sdk_components_keys = ( + "full.bin", + "firmware.elf", + "update.dir", + "sdk_headers.dir", + "lib.dir", + "debug.dir", + "scripts.dir", + ) + + with zipfile.ZipFile( + self.get_dist_path(self.get_dist_file_name("sdk", "zip")), + "w", + zipfile.ZIP_DEFLATED, + ) as zf: + for component_key in sdk_components_keys: + component_path = self._dist_components.get(component_key) + components_paths[component_key] = basename(component_path) + + if component_key.endswith(".dir"): + for root, dirnames, files in walk(component_path): + if "__pycache__" in dirnames: + dirnames.remove("__pycache__") + for file in files: + zf.write( + join(root, file), + join( + components_paths[component_key], + relpath( + join(root, file), + component_path, + ), + ), + ) + else: + zf.write(component_path, basename(component_path)) + + zf.writestr( + "components.json", + json.dumps( + { + "meta": { + "hw_target": self.target, + "flavor": self.flavor, + "version": self.args.version, + }, + "components": components_paths, + } + ), + ) + + def bundle_update_package(self): + self.logger.debug( + f"Generating update bundle with version {self.args.version} for {self.target}" + ) + bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[ + : self.DIST_FOLDER_MAX_NAME_LENGTH + ] + bundle_dir = self.get_dist_path(bundle_dir_name) + bundle_args = [ + "generate", + "-d", + bundle_dir, + "-v", + self.args.version, + "-t", + self.target, + "--dfu", + self.get_dist_path( + self.get_project_file_name(self.projects["firmware"], "dfu") + ), + "--stage", + self.get_dist_path( + self.get_project_file_name(self.projects["updater"], "bin") + ), + ] + if self.args.resources: + bundle_args.extend( + ( + "-r", + self.args.resources, + ) + ) + bundle_args.extend(self.other_args) + + log_custom_fz_name = ( + environ.get("CUSTOM_FLIPPER_NAME", None) + or "" + ) + if (log_custom_fz_name != "") and (len(log_custom_fz_name) <= 8) and (log_custom_fz_name.isalnum()) and (log_custom_fz_name.isascii()): + self.logger.info(f"Flipper Custom Name is set:\n\tName: {log_custom_fz_name} : length - {len(log_custom_fz_name)} chars") + + if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: + self.note_dist_component("update", "dir", bundle_dir) + self.logger.info( + fg.boldgreen( + f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" + ) + ) + + # Create tgz archive + with tarfile.open( + join( + self.output_dir_path, + bundle_tgz := f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz", + ), + "w:gz", + compresslevel=9, + format=tarfile.USTAR_FORMAT, + ) as tar: + self.note_dist_component( + "update", "tgz", self.get_dist_path(bundle_tgz) + ) + tar.add(bundle_dir, arcname=bundle_dir_name) + return bundle_result + if __name__ == "__main__": Main()() diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py index 441bc7cc8..390b1f263 100644 --- a/scripts/serial_cli.py +++ b/scripts/serial_cli.py @@ -8,7 +8,7 @@ import sys def main(): logger = logging.getLogger() if not (port := resolve_port(logger, "auto")): - logger.error("Is Flipper connected over USB and isn't in DFU mode?") + logger.error("Is Flipper connected over USB and is it not in DFU mode?") return 1 subprocess.call( [ diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 8587f6d0e..9d45b7e9d 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -15,10 +15,12 @@ if not ["%FBT_NOENV%"] == [""] ( set "FLIPPER_TOOLCHAIN_VERSION=21" -if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( - set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" +if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( + set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%" ) +set "FBT_TOOLCHAIN_ROOT=%FBT_TOOLCHAIN_PATH%\toolchain\x86_64-windows" + set "FBT_TOOLCHAIN_VERSION_FILE=%FBT_TOOLCHAIN_ROOT%\VERSION" if not exist "%FBT_TOOLCHAIN_ROOT%" ( diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index cefa55d7e..be4e405c4 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -4,9 +4,15 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; -SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"21"}"; -FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; + +if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then + FBT_TOOLCHAIN_PATH_WAS_SET=0; +else + FBT_TOOLCHAIN_PATH_WAS_SET=1; +fi + +FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$DEFAULT_SCRIPT_PATH}"; FBT_VERBOSE="${FBT_VERBOSE:-""}"; fbtenv_show_usage() @@ -60,7 +66,6 @@ fbtenv_restore_env() unset SAVED_PYTHONPATH; unset SAVED_PYTHONHOME; - unset SCRIPT_PATH; unset FBT_TOOLCHAIN_VERSION; unset FBT_TOOLCHAIN_PATH; } @@ -104,13 +109,14 @@ fbtenv_set_shell_prompt() return 0; # all other shells } -fbtenv_check_script_path() +fbtenv_check_env_vars() { - if [ ! -x "$SCRIPT_PATH/fbt" ] && [ ! -x "$SCRIPT_PATH/ufbt" ] ; then - echo "Please source this script from [u]fbt root directory, or specify 'SCRIPT_PATH' variable manually"; + # Return error if FBT_TOOLCHAIN_PATH is not set before script is sourced or if fbt executable is not in DEFAULT_SCRIPT_PATH + if [ "$FBT_TOOLCHAIN_PATH_WAS_SET" -eq 0 ] && [ ! -x "$DEFAULT_SCRIPT_PATH/fbt" ] && [ ! -x "$DEFAULT_SCRIPT_PATH/ufbt" ] ; then + echo "Please source this script from [u]fbt root directory, or specify 'FBT_TOOLCHAIN_PATH' variable manually"; echo "Example:"; - printf "\tSCRIPT_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n"; - echo "If current directory is right, type 'unset SCRIPT_PATH' and try again" + printf "\tFBT_TOOLCHAIN_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n"; + echo "If current directory is right, type 'unset FBT_TOOLCHAIN_PATH' and try again" return 1; fi return 0; @@ -217,7 +223,7 @@ fbtenv_show_unpack_percentage() fbtenv_unpack_toolchain() { - echo "Unpacking toolchain:"; + echo "Unpacking toolchain to '$FBT_TOOLCHAIN_PATH/toolchain':"; tar -xvf "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" -C "$FBT_TOOLCHAIN_PATH/toolchain" 2>&1 | fbtenv_show_unpack_percentage; mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1; @@ -225,7 +231,7 @@ fbtenv_unpack_toolchain() return 0; } -fbtenv_clearing() +fbtenv_cleanup() { printf "Cleaning up.."; if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then @@ -280,14 +286,14 @@ fbtenv_download_toolchain() fbtenv_check_tar || return 1; TOOLCHAIN_TAR="$(basename "$TOOLCHAIN_URL")"; TOOLCHAIN_DIR="$(echo "$TOOLCHAIN_TAR" | sed "s/-$FBT_TOOLCHAIN_VERSION.tar.gz//g")"; - trap fbtenv_clearing 2; # trap will be restored in fbtenv_clearing + trap fbtenv_cleanup 2; # trap will be restored in fbtenv_cleanup if ! fbtenv_check_downloaded_toolchain; then fbtenv_curl_wget_check || return 1; fbtenv_download_toolchain_tar || return 1; fi fbtenv_remove_old_tooclhain; fbtenv_unpack_toolchain || return 1; - fbtenv_clearing; + fbtenv_cleanup; return 0; } @@ -306,8 +312,8 @@ fbtenv_main() fbtenv_restore_env; return 0; fi - fbtenv_check_if_sourced_multiple_times; # many source it's just a warning - fbtenv_check_script_path || return 1; + fbtenv_check_if_sourced_multiple_times; + fbtenv_check_env_vars || return 1; fbtenv_check_download_toolchain || return 1; fbtenv_set_shell_prompt; fbtenv_print_version; diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct new file mode 100644 index 000000000..a82189c14 --- /dev/null +++ b/scripts/ufbt/SConstruct @@ -0,0 +1,393 @@ +from SCons.Platform import TempFileMunge +from SCons.Node import FS +from SCons.Errors import UserError + +import os +import multiprocessing +import pathlib + +SetOption("num_jobs", multiprocessing.cpu_count()) +SetOption("max_drift", 1) +# SetOption("silent", False) + +ufbt_state_dir = Dir(os.environ.get("UFBT_STATE_DIR", "#.ufbt")) +ufbt_script_dir = Dir(os.environ.get("UFBT_SCRIPT_DIR")) + +ufbt_current_sdk_dir = ufbt_state_dir.Dir("current") + +SConsignFile(ufbt_state_dir.File(".sconsign.dblite").abspath) + +ufbt_variables = SConscript("commandline.scons") + +forward_os_env = { + # Import PATH from OS env - scons doesn't do that by default + "PATH": os.environ["PATH"], +} + +# Proxying environment to child processes & scripts +variables_to_forward = [ + # CI/CD variables + "WORKFLOW_BRANCH_OR_TAG", + "DIST_SUFFIX", + # Python & other tools + "HOME", + "APPDATA", + "PYTHONHOME", + "PYTHONNOUSERSITE", + "TMP", + "TEMP", + # Colors for tools + "TERM", +] + +if proxy_env := GetOption("proxy_env"): + variables_to_forward.extend(proxy_env.split(",")) + +for env_value_name in variables_to_forward: + if environ_value := os.environ.get(env_value_name, None): + forward_os_env[env_value_name] = environ_value + +# Core environment init - loads SDK state, sets up paths, etc. +core_env = Environment( + variables=ufbt_variables, + ENV=forward_os_env, + UFBT_STATE_DIR=ufbt_state_dir, + UFBT_CURRENT_SDK_DIR=ufbt_current_sdk_dir, + UFBT_SCRIPT_DIR=ufbt_script_dir, + toolpath=[ufbt_current_sdk_dir.Dir("scripts/ufbt/site_tools")], + tools=[ + "ufbt_state", + ("ufbt_help", {"vars": ufbt_variables}), + ], +) + +if "update" in BUILD_TARGETS: + SConscript( + "update.scons", + exports={"core_env": core_env}, + ) + +if "purge" in BUILD_TARGETS: + core_env.Execute(Delete(ufbt_state_dir)) + print("uFBT state purged") + Exit(0) + +# Now we can import stuff bundled with SDK - it was added to sys.path by ufbt_state + +from fbt.util import ( + tempfile_arg_esc_func, + single_quote, + extract_abs_dir, + extract_abs_dir_path, + wrap_tempfile, + path_as_posix, +) +from fbt.appmanifest import FlipperAppType +from fbt.sdk.cache import SdkCache + +# Base environment with all tools loaded from SDK +env = core_env.Clone( + toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")], + tools=[ + "fbt_tweaks", + ( + "crosscc", + { + "toolchain_prefix": "arm-none-eabi-", + "versions": (" 10.3",), + }, + ), + "fwbin", + "python3", + "sconsrecursiveglob", + "sconsmodular", + "ccache", + "fbt_apps", + "fbt_extapps", + "fbt_assets", + ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}), + ], + FBT_FAP_DEBUG_ELF_ROOT=ufbt_state_dir.Dir("build"), + TEMPFILE=TempFileMunge, + MAXLINELENGTH=2048, + PROGSUFFIX=".elf", + TEMPFILEARGESCFUNC=tempfile_arg_esc_func, + SINGLEQUOTEFUNC=single_quote, + ABSPATHGETTERFUNC=extract_abs_dir_path, + APPS=[], + UFBT_API_VERSION=SdkCache( + core_env.subst("$SDK_DEFINITION"), load_version_only=True + ).version, + APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}\n\t\tTarget: ${TARGET_HW}, API: ${UFBT_API_VERSION}", +) + +wrap_tempfile(env, "LINKCOM") +wrap_tempfile(env, "ARCOM") + +# print(env.Dump()) + +# Dist env + +dist_env = env.Clone( + tools=[ + "fbt_dist", + "fbt_debugopts", + "openocd", + "blackmagic", + "jflash", + "textfile", + ], + ENV=os.environ, + OPENOCD_OPTS=[ + "-f", + "interface/stlink.cfg", + "-c", + "transport select hla_swd", + "-f", + "${FBT_DEBUG_DIR}/stm32wbx.cfg", + "-c", + "stm32wbx.cpu configure -rtos auto", + ], +) + +openocd_target = dist_env.OpenOCDFlash( + dist_env["UFBT_STATE_DIR"].File("flash"), + dist_env["FW_BIN"], + OPENOCD_COMMAND=[ + "-c", + "program ${SOURCE.posix} reset exit 0x08000000", + ], +) +dist_env.Alias("firmware_flash", openocd_target) +dist_env.Alias("flash", openocd_target) +if env["FORCE"]: + env.AlwaysBuild(openocd_target) + +firmware_debug = dist_env.PhonyTarget( + "debug", + "${GDBPYCOM}", + source=dist_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE}", + GDBREMOTE="${OPENOCD_GDB_PIPE}", + FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), +) + +dist_env.PhonyTarget( + "blackmagic", + "${GDBPYCOM}", + source=dist_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBREMOTE="${BLACKMAGIC_ADDR}", + FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), +) + +dist_env.PhonyTarget( + "flash_blackmagic", + "$GDB $GDBOPTS $SOURCES $GDBFLASH", + source=dist_env["FW_ELF"], + GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", + GDBREMOTE="${BLACKMAGIC_ADDR}", + GDBFLASH=[ + "-ex", + "load", + "-ex", + "quit", + ], +) + +flash_usb_full = dist_env.UsbInstall( + dist_env["UFBT_STATE_DIR"].File("usbinstall"), + [], +) +dist_env.AlwaysBuild(flash_usb_full) +dist_env.Alias("flash_usb", flash_usb_full) +dist_env.Alias("flash_usb_full", flash_usb_full) + +# App build environment + +appenv = env.Clone( + CCCOM=env["CCCOM"].replace("$CFLAGS", "$CFLAGS_APP $CFLAGS"), + CXXCOM=env["CXXCOM"].replace("$CXXFLAGS", "$CXXFLAGS_APP $CXXFLAGS"), + LINKCOM=env["LINKCOM"].replace("$LINKFLAGS", "$LINKFLAGS_APP $LINKFLAGS"), + COMPILATIONDB_USE_ABSPATH=True, +) + + +original_app_dir = Dir(appenv.subst("$UFBT_APP_DIR")) +app_mount_point = Dir("#/app/") +app_mount_point.addRepository(original_app_dir) + +appenv.LoadAppManifest(app_mount_point) +appenv.PrepareApplicationsBuild() + +####################### + +apps_artifacts = appenv["EXT_APPS"] + +apps_to_build_as_faps = [ + FlipperAppType.PLUGIN, + FlipperAppType.EXTERNAL, +] + +known_extapps = [ + app + for apptype in apps_to_build_as_faps + for app in appenv["APPBUILD"].get_apps_of_type(apptype, True) +] +for app in known_extapps: + app_artifacts = appenv.BuildAppElf(app) + app_src_dir = extract_abs_dir(app_artifacts.app._appdir) + app_artifacts.installer = [ + appenv.Install(app_src_dir.Dir("dist"), app_artifacts.compact), + appenv.Install(app_src_dir.Dir("dist").Dir("debug"), app_artifacts.debug), + ] + +if appenv["FORCE"]: + appenv.AlwaysBuild([extapp.compact for extapp in apps_artifacts.values()]) + +# Final steps - target aliases + +install_and_check = [ + (extapp.installer, extapp.validator) for extapp in apps_artifacts.values() +] +Alias( + "faps", + install_and_check, +) +Default(install_and_check) + +# Compilation database + +fwcdb = appenv.CompilationDatabase( + original_app_dir.Dir(".vscode").File("compile_commands.json") +) + +AlwaysBuild(fwcdb) +Precious(fwcdb) +NoClean(fwcdb) +if len(apps_artifacts): + Default(fwcdb) + + +# launch handler +runnable_apps = appenv["APPBUILD"].get_apps_of_type(FlipperAppType.EXTERNAL, True) + +app_to_launch = None +if len(runnable_apps) == 1: + app_to_launch = runnable_apps[0].appid +elif len(runnable_apps) > 1: + # more than 1 app - try to find one with matching id + app_to_launch = appenv.subst("$APPID") + + +def ambiguous_app_call(**kw): + raise UserError( + f"More than one app is runnable: {', '.join(app.appid for app in runnable_apps)}. Please specify an app with APPID=..." + ) + + +if app_to_launch: + appenv.AddAppLaunchTarget(app_to_launch, "launch") +else: + dist_env.PhonyTarget("launch", Action(ambiguous_app_call, None)) + +# cli handler + +appenv.PhonyTarget( + "cli", + '${PYTHON3} "${FBT_SCRIPT_DIR}/serial_cli.py"', +) + +# Linter + +dist_env.PhonyTarget( + "lint", + "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}", + source=original_app_dir.File(".clang-format"), + LINT_SOURCES=[original_app_dir], +) + +dist_env.PhonyTarget( + "format", + "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}", + source=original_app_dir.File(".clang-format"), + LINT_SOURCES=[original_app_dir], +) + + +# Prepare vscode environment +def _path_as_posix(path): + return pathlib.Path(path).as_posix() + + +vscode_dist = [] +project_template_dir = dist_env["UFBT_SCRIPT_ROOT"].Dir("project_template") +for template_file in project_template_dir.Dir(".vscode").glob("*"): + vscode_dist.append( + dist_env.Substfile( + original_app_dir.Dir(".vscode").File(template_file.name), + template_file, + SUBST_DICT={ + "@UFBT_VSCODE_PATH_SEP@": os.path.pathsep, + "@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@": pathlib.Path( + dist_env.WhereIs("arm-none-eabi-gcc") + ).parent.as_posix(), + "@UFBT_TOOLCHAIN_GCC@": _path_as_posix( + dist_env.WhereIs("arm-none-eabi-gcc") + ), + "@UFBT_TOOLCHAIN_GDB_PY@": _path_as_posix( + dist_env.WhereIs("arm-none-eabi-gdb-py") + ), + "@UFBT_TOOLCHAIN_OPENOCD@": _path_as_posix(dist_env.WhereIs("openocd")), + "@UFBT_APP_DIR@": _path_as_posix(original_app_dir.abspath), + "@UFBT_ROOT_DIR@": _path_as_posix(Dir("#").abspath), + "@UFBT_DEBUG_DIR@": dist_env["FBT_DEBUG_DIR"], + "@UFBT_DEBUG_ELF_DIR@": _path_as_posix( + dist_env["FBT_FAP_DEBUG_ELF_ROOT"].abspath + ), + "@UFBT_FIRMWARE_ELF@": _path_as_posix(dist_env["FW_ELF"].abspath), + }, + ) + ) + +for config_file in project_template_dir.glob(".*"): + if isinstance(config_file, FS.Dir): + continue + vscode_dist.append(dist_env.Install(original_app_dir, config_file)) + +dist_env.Precious(vscode_dist) +dist_env.NoClean(vscode_dist) +dist_env.Alias("vscode_dist", vscode_dist) + + +# Creating app from base template + +dist_env.SetDefault(FBT_APPID=appenv.subst("$APPID") or "template") +app_template_dist = [] +for template_file in project_template_dir.Dir("app_template").glob("*"): + dist_file_name = dist_env.subst(template_file.name) + if template_file.name.endswith(".png"): + app_template_dist.append( + dist_env.InstallAs(original_app_dir.File(dist_file_name), template_file) + ) + else: + app_template_dist.append( + dist_env.Substfile( + original_app_dir.File(dist_file_name), + template_file, + SUBST_DICT={ + "@FBT_APPID@": dist_env.subst("$FBT_APPID"), + }, + ) + ) + +AddPostAction( + app_template_dist[-1], + [ + Mkdir(original_app_dir.Dir("images")), + Touch(original_app_dir.Dir("images").File(".gitkeep")), + ], +) +dist_env.Precious(app_template_dist) +dist_env.NoClean(app_template_dist) +dist_env.Alias("create", app_template_dist) diff --git a/scripts/ufbt/commandline.scons b/scripts/ufbt/commandline.scons new file mode 100644 index 000000000..9af5e9bce --- /dev/null +++ b/scripts/ufbt/commandline.scons @@ -0,0 +1,90 @@ +AddOption( + "--proxy-env", + action="store", + dest="proxy_env", + default="", + help="Comma-separated list of additional environment variables to pass to child SCons processes", +) + +AddOption( + "--channel", + action="store", + dest="sdk_channel", + choices=["dev", "rc", "release"], + default="", + help="Release channel to use for SDK", +) + +AddOption( + "--branch", + action="store", + dest="sdk_branch", + help="Custom main repo branch to use for SDK", +) + +AddOption( + "--hw-target", + action="store", + dest="sdk_target", + help="SDK Hardware target", +) + +vars = Variables("ufbt_options.py", ARGUMENTS) + +vars.AddVariables( + BoolVariable( + "VERBOSE", + help="Print full commands", + default=False, + ), + BoolVariable( + "FORCE", + help="Force target action (for supported targets)", + default=False, + ), + # These 2 are inherited from SDK + # BoolVariable( + # "DEBUG", + # help="Enable debug build", + # default=True, + # ), + # BoolVariable( + # "COMPACT", + # help="Optimize for size", + # default=False, + # ), + PathVariable( + "OTHER_ELF", + help="Path to prebuilt ELF file to debug", + validator=PathVariable.PathAccept, + default="", + ), + ( + "OPENOCD_OPTS", + "Options to pass to OpenOCD", + "", + ), + ( + "BLACKMAGIC", + "Blackmagic probe location", + "auto", + ), + ( + "OPENOCD_ADAPTER_SERIAL", + "OpenOCD adapter serial number", + "auto", + ), + ( + "APPID", + "Application id", + "", + ), + PathVariable( + "UFBT_APP_DIR", + help="Application dir to work with", + validator=PathVariable.PathIsDir, + default="", + ), +) + +Return("vars") diff --git a/scripts/ufbt/project_template/.clang-format b/scripts/ufbt/project_template/.clang-format new file mode 100644 index 000000000..4b76f7fa4 --- /dev/null +++ b/scripts/ufbt/project_template/.clang-format @@ -0,0 +1,191 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 99 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 10 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Never +SpaceBeforeParensOptions: + AfterControlStatements: false + AfterForeachMacros: false + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: false + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: c++03 +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/scripts/ufbt/project_template/.editorconfig b/scripts/ufbt/project_template/.editorconfig new file mode 100644 index 000000000..a31ef8e75 --- /dev/null +++ b/scripts/ufbt/project_template/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.{cpp,h,c,py,sh}] +indent_style = space +indent_size = 4 + +[{Makefile,*.mk}] +indent_size = tab diff --git a/scripts/ufbt/project_template/.gitignore b/scripts/ufbt/project_template/.gitignore new file mode 100644 index 000000000..e2a15a10a --- /dev/null +++ b/scripts/ufbt/project_template/.gitignore @@ -0,0 +1,4 @@ +dist/* +.vscode +.clang-format +.editorconfig \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/c_cpp_properties.json b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json new file mode 100644 index 000000000..922a9091b --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json @@ -0,0 +1,14 @@ +{ + "configurations": [ + { + "name": "main", + "compilerPath": "@UFBT_TOOLCHAIN_GCC@", + "intelliSenseMode": "gcc-arm", + "compileCommands": "${workspaceFolder}/.vscode/compile_commands.json", + "configurationProvider": "ms-vscode.cpptools", + "cStandard": "gnu17", + "cppStandard": "c++17" + }, + ], + "version": 4 +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/extensions.json b/scripts/ufbt/project_template/.vscode/extensions.json new file mode 100644 index 000000000..35f90700a --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/extensions.json @@ -0,0 +1,18 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "ms-python.black-formatter", + "ms-vscode.cpptools", + "amiralizadeh9480.cpp-helper", + "marus25.cortex-debug", + "zxh404.vscode-proto3", + "augustocdias.tasks-shell-input" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + "twxs.cmake", + "ms-vscode.cmake-tools" + ] +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/launch.json b/scripts/ufbt/project_template/.vscode/launch.json new file mode 100644 index 000000000..d9c98dcc1 --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/launch.json @@ -0,0 +1,98 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "inputs": [ + // { + // "id": "BLACKMAGIC", + // "type": "command", + // "command": "shellCommand.execute", + // "args": { + // "useSingleResult": true, + // "env": { + // "PATH": "${workspaceFolder};${env:PATH}" + // }, + // "command": "./fbt get_blackmagic", + // "description": "Get Blackmagic device", + // } + // }, + ], + "configurations": [ + { + "name": "Attach FW (ST-Link)", + "cwd": "${workspaceFolder}", + "executable": "@UFBT_FIRMWARE_ELF@", + "request": "attach", + "type": "cortex-debug", + "servertype": "openocd", + "device": "stlink", + "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "configFiles": [ + "interface/stlink.cfg", + "@UFBT_DEBUG_DIR@/stm32wbx.cfg" + ], + "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperapps.py", + "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + ], + // "showDevDebugOutput": "raw", + }, + { + "name": "Attach FW (DAP)", + "cwd": "${workspaceFolder}", + "executable": "@UFBT_FIRMWARE_ELF@", + "request": "attach", + "type": "cortex-debug", + "servertype": "openocd", + "device": "cmsis-dap", + "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "configFiles": [ + "interface/cmsis-dap.cfg", + "@UFBT_DEBUG_DIR@/stm32wbx.cfg" + ], + "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperapps.py", + "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + ], + // "showDevDebugOutput": "raw", + }, + // { + // "name": "Attach FW (blackmagic)", + // "cwd": "${workspaceFolder}", + // "executable": "@UFBT_FIRMWARE_ELF@", + // "request": "attach", + // "type": "cortex-debug", + // "servertype": "external", + // "gdbTarget": "${input:BLACKMAGIC}", + // "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + // "rtos": "FreeRTOS", + // "postAttachCommands": [ + // "monitor swdp_scan", + // "attach 1", + // "set confirm off", + // "set mem inaccessible-by-default off", + // "source @UFBT_DEBUG_DIR@/flipperapps.py", + // "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + // ] + // // "showDevDebugOutput": "raw", + // }, + { + "name": "Attach FW (JLink)", + "cwd": "${workspaceFolder}", + "executable": "@UFBT_FIRMWARE_ELF@", + "request": "attach", + "type": "cortex-debug", + "servertype": "jlink", + "interface": "swd", + "device": "STM32WB55RG", + "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", + "rtos": "FreeRTOS", + "postAttachCommands": [ + "source @UFBT_DEBUG_DIR@/flipperapps.py", + "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" + ] + // "showDevDebugOutput": "raw", + }, + ] +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/settings.json b/scripts/ufbt/project_template/.vscode/settings.json new file mode 100644 index 000000000..33cd3f035 --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "cortex-debug.enableTelemetry": false, + "cortex-debug.variableUseNaturalFormat": false, + "cortex-debug.showRTOS": true, + "cortex-debug.armToolchainPath": "@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@", + "cortex-debug.openocdPath": "@UFBT_TOOLCHAIN_OPENOCD@", + "cortex-debug.gdbPath": "@UFBT_TOOLCHAIN_GDB_PY@", + "editor.formatOnSave": true, + "files.associations": { + "*.scons": "python", + "SConscript": "python", + "SConstruct": "python", + "*.fam": "python" + }, + "cortex-debug.registerUseNaturalFormat": false, + "python.analysis.typeCheckingMode": "off", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/.vscode/tasks.json b/scripts/ufbt/project_template/.vscode/tasks.json new file mode 100644 index 000000000..6343bba7b --- /dev/null +++ b/scripts/ufbt/project_template/.vscode/tasks.json @@ -0,0 +1,54 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "options": { + "env": { + "PATH": "${workspaceFolder}@UFBT_VSCODE_PATH_SEP@${env:PATH}@UFBT_VSCODE_PATH_SEP@@UFBT_ROOT_DIR@" + } + }, + "tasks": [ + { + "label": "Launch App on Flipper", + "group": "build", + "type": "shell", + "command": "ufbt launch" + }, + { + "label": "Build", + "group": "build", + "type": "shell", + "command": "ufbt" + }, + { + "label": "Flash FW (ST-Link)", + "group": "build", + "type": "shell", + "command": "ufbt FORCE=1 flash" + }, + // { + // "label": "[NOTIMPL] Flash FW (blackmagic)", + // "group": "build", + // "type": "shell", + // "command": "ufbt flash_blackmagic" + // }, + // { + // "label": "[NOTIMPL] Flash FW (JLink)", + // "group": "build", + // "type": "shell", + // "command": "ufbt FORCE=1 jflash" + // }, + { + "label": "Flash FW (USB, with resources)", + "group": "build", + "type": "shell", + "command": "ufbt FORCE=1 flash_usb" + }, + { + "label": "Update uFBT SDK", + "group": "build", + "type": "shell", + "command": "ufbt update" + } + ] +} \ No newline at end of file diff --git a/scripts/ufbt/project_template/app_template/${FBT_APPID}.c b/scripts/ufbt/project_template/app_template/${FBT_APPID}.c new file mode 100644 index 000000000..9b8113cb5 --- /dev/null +++ b/scripts/ufbt/project_template/app_template/${FBT_APPID}.c @@ -0,0 +1,12 @@ +#include + +/* generated by fbt from .png files in images folder */ +#include <@FBT_APPID@_icons.h> + +int32_t @FBT_APPID@_app(void* p) { + UNUSED(p); + FURI_LOG_I("TEST", "Hello world"); + FURI_LOG_I("TEST", "I'm @FBT_APPID@!"); + + return 0; +} diff --git a/scripts/ufbt/project_template/app_template/${FBT_APPID}.png b/scripts/ufbt/project_template/app_template/${FBT_APPID}.png new file mode 100644 index 000000000..59e6c185f Binary files /dev/null and b/scripts/ufbt/project_template/app_template/${FBT_APPID}.png differ diff --git a/scripts/ufbt/project_template/app_template/application.fam b/scripts/ufbt/project_template/app_template/application.fam new file mode 100644 index 000000000..31fadb207 --- /dev/null +++ b/scripts/ufbt/project_template/app_template/application.fam @@ -0,0 +1,17 @@ +# For details & more options, see documentation/AppManifests.md in firmware repo + +App( + appid="@FBT_APPID@", # Must be unique + name="App @FBT_APPID@", # Displayed in menus + apptype=FlipperAppType.EXTERNAL, + entry_point="@FBT_APPID@_app", + stack_size=2 * 1024, + fap_category="Misc", + # Optional values + # fap_version=(0, 1), # (major, minor) + fap_icon="@FBT_APPID@.png", # 10x10 1-bit PNG + # fap_description="A simple app", + # fap_author="J. Doe", + # fap_weburl="https://github.com/user/@FBT_APPID@", + fap_icon_assets="images", # Image assets to compile for this application +) diff --git a/scripts/ufbt/site_init.py b/scripts/ufbt/site_init.py new file mode 100644 index 000000000..557085ede --- /dev/null +++ b/scripts/ufbt/site_init.py @@ -0,0 +1,36 @@ +from SCons.Script import GetBuildFailures +import SCons.Errors + +import atexit +from ansi.color import fg, fx + + +def bf_to_str(bf): + """Convert an element of GetBuildFailures() to a string + in a useful way.""" + + if bf is None: # unknown targets product None in list + return "(unknown tgt)" + elif isinstance(bf, SCons.Errors.StopError): + return fg.yellow(str(bf)) + elif bf.node: + return fg.yellow(str(bf.node)) + ": " + bf.errstr + elif bf.filename: + return fg.yellow(bf.filename) + ": " + bf.errstr + return fg.yellow("unknown failure: ") + bf.errstr + + +def display_build_status(): + """Display the build status. Called by atexit. + Here you could do all kinds of complicated things.""" + bf = GetBuildFailures() + if bf: + # bf is normally a list of build failures; if an element is None, + # it's because of a target that scons doesn't know anything about. + failures_message = "\n".join([bf_to_str(x) for x in bf if x is not None]) + print() + print(fg.brightred(fx.bold("*" * 10 + " FBT ERRORS " + "*" * 10))) + print(failures_message) + + +atexit.register(display_build_status) diff --git a/scripts/ufbt/site_tools/ufbt_help.py b/scripts/ufbt/site_tools/ufbt_help.py new file mode 100644 index 000000000..da6ff6e51 --- /dev/null +++ b/scripts/ufbt/site_tools/ufbt_help.py @@ -0,0 +1,53 @@ +targets_help = """Configuration variables: +""" + +tail_help = """ + +TASKS: + (* - not supported yet) + + launch: + Upload and start application over USB + vscode_dist: + Configure application in current directory for development in VSCode. + create: + Copy application template to current directory. Set APPID=myapp to create an app with id 'myapp'. + +Building: + faps: + Build all FAP apps + fap_{APPID}, launch APPSRC={APPID}: + Build FAP app with appid={APPID}; upload & start it over USB + +Flashing & debugging: + flash, flash_blackmagic, *jflash: + Flash firmware to target using debug probe + flash_usb, flash_usb_full: + Install firmware using self-update package + debug, debug_other, blackmagic: + Start GDB + +Other: + cli: + Open a Flipper CLI session over USB + lint: + run linter for C code + format: + reformat C code + +How to create a new application: + 1. Create a new directory for your application and cd into it. + 2. Run `ufbt vscode_dist create APPID=myapp` + 3. In VSCode, open the folder and start editing. + 4. Run `ufbt launch` to build and upload your application. +""" + + +def generate(env, **kw): + vars = kw["vars"] + basic_help = vars.GenerateHelpText(env) + env.Help(targets_help + basic_help + tail_help) + + +def exists(env): + return True diff --git a/scripts/ufbt/site_tools/ufbt_state.py b/scripts/ufbt/site_tools/ufbt_state.py new file mode 100644 index 000000000..6ba8c6962 --- /dev/null +++ b/scripts/ufbt/site_tools/ufbt_state.py @@ -0,0 +1,117 @@ +from SCons.Errors import StopError +from SCons.Warnings import warn, WarningOnByDefault + +import json +import os +import sys +import pathlib +from functools import reduce + + +def _load_sdk_data(sdk_root): + split_vars = { + "cc_args", + "cpp_args", + "linker_args", + "linker_libs", + } + subst_vars = split_vars | { + "sdk_symbols", + } + sdk_data = {} + with open(os.path.join(sdk_root, "sdk.opts")) as f: + sdk_json_data = json.load(f) + replacements = { + sdk_json_data["app_ep_subst"]: "${APP_ENTRY}", + sdk_json_data["sdk_path_subst"]: sdk_root.replace("\\", "/"), + sdk_json_data["map_file_subst"]: "${TARGET}", + } + + def do_value_substs(src_value): + if isinstance(src_value, str): + return reduce( + lambda acc, kv: acc.replace(*kv), replacements.items(), src_value + ) + elif isinstance(src_value, list): + return [do_value_substs(v) for v in src_value] + else: + return src_value + + for key, value in sdk_json_data.items(): + if key in split_vars: + value = value.split() + if key in subst_vars: + value = do_value_substs(value) + sdk_data[key] = value + + return sdk_data + + +def _load_state_file(state_dir_node, filename: str) -> dict: + state_path = os.path.join(state_dir_node.abspath, filename) + if not os.path.exists(state_path): + raise StopError(f"State file {state_path} not found") + + with open(state_path, "r") as f: + return json.load(f) + + +def generate(env, **kw): + sdk_current_sdk_dir_node = env["UFBT_CURRENT_SDK_DIR"] + + sdk_components_filename = kw.get("SDK_COMPONENTS", "components.json") + ufbt_state_filename = kw.get("UFBT_STATE", "ufbt_state.json") + + sdk_state = _load_state_file(sdk_current_sdk_dir_node, sdk_components_filename) + ufbt_state = _load_state_file(sdk_current_sdk_dir_node, ufbt_state_filename) + + if not (sdk_components := sdk_state.get("components", {})): + raise StopError("SDK state file doesn't contain components data") + + sdk_data = _load_sdk_data( + sdk_current_sdk_dir_node.Dir(sdk_components["sdk_headers.dir"]).abspath + ) + + if not sdk_state["meta"]["hw_target"].endswith(sdk_data["hardware"]): + raise StopError("SDK state file doesn't match hardware target") + + if sdk_state["meta"]["version"] != ufbt_state["version"]: + warn( + WarningOnByDefault, + f"Version mismatch: SDK state vs uFBT: {sdk_state['meta']['version']} vs {ufbt_state['version']}", + ) + + scripts_dir = sdk_current_sdk_dir_node.Dir(sdk_components["scripts.dir"]) + env.SetDefault( + # Paths + SDK_DEFINITION=env.File(sdk_data["sdk_symbols"]), + FBT_DEBUG_DIR=pathlib.Path( + sdk_current_sdk_dir_node.Dir(sdk_components["debug.dir"]).abspath + ).as_posix(), + FBT_SCRIPT_DIR=scripts_dir, + LIBPATH=sdk_current_sdk_dir_node.Dir(sdk_components["lib.dir"]), + FW_ELF=sdk_current_sdk_dir_node.File(sdk_components["firmware.elf"]), + FW_BIN=sdk_current_sdk_dir_node.File(sdk_components["full.bin"]), + UPDATE_BUNDLE_DIR=sdk_current_sdk_dir_node.Dir(sdk_components["update.dir"]), + SVD_FILE="${FBT_DEBUG_DIR}/STM32WB55_CM4.svd", + # Build variables + ROOT_DIR=env.Dir("#"), + FIRMWARE_BUILD_CFG="firmware", + TARGET_HW=int(sdk_data["hardware"]), + CFLAGS_APP=sdk_data["cc_args"], + CXXFLAGS_APP=sdk_data["cpp_args"], + LINKFLAGS_APP=sdk_data["linker_args"], + LIBS=sdk_data["linker_libs"], + # ufbt state + # UFBT_STATE_DIR=ufbt_state_dir_node, + # UFBT_CURRENT_SDK_DIR=sdk_current_sdk_dir_node, + UFBT_STATE=ufbt_state, + UFBT_BOOTSTRAP_SCRIPT="${UFBT_SCRIPT_DIR}/bootstrap.py", + UFBT_SCRIPT_ROOT=scripts_dir.Dir("ufbt"), + ) + + sys.path.insert(0, env["FBT_SCRIPT_DIR"].abspath) + + +def exists(env): + return True diff --git a/scripts/ufbt/update.scons b/scripts/ufbt/update.scons new file mode 100644 index 000000000..9658e0bb2 --- /dev/null +++ b/scripts/ufbt/update.scons @@ -0,0 +1,37 @@ +from SCons.Errors import StopError + +Import("core_env") + +update_env = core_env.Clone( + toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")], + tools=["python3"], +) +print("Updating SDK...") +ufbt_state = update_env["UFBT_STATE"] + +update_args = [ + "--ufbt-dir", + f'"{update_env["UFBT_STATE_DIR"]}"', +] + +if branch_name := GetOption("sdk_branch"): + update_args.extend(["--branch", branch_name]) +elif channel_name := GetOption("sdk_channel"): + update_args.extend(["--channel", channel_name]) +elif branch_name := ufbt_state.get("branch", None): + update_args.extend(["--branch", branch_name]) +elif channel_name := ufbt_state.get("channel", None): + update_args.extend(["--channel", channel_name]) +else: + raise StopError("No branch or channel specified for SDK update") + +if hw_target := GetOption("sdk_target"): + update_args.extend(["--hw-target", hw_target]) +else: + update_args.extend(["--hw-target", ufbt_state["hw_target"]]) + +update_env.Replace(UPDATE_ARGS=update_args) +result = update_env.Execute( + update_env.subst('$PYTHON3 "$UFBT_BOOTSTRAP_SCRIPT" $UPDATE_ARGS'), +) +Exit(result) diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 4af966007..7789f95b1 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -113,86 +113,47 @@ Alias( extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], []) + if appsrc := appenv.subst("$APPSRC"): - deploy_sources, flipp_dist_paths, validators = [], [], [] - run_script_extra_ars = "" + appenv.AddAppLaunchTarget(appsrc, "launch_app") - def _add_dist_targets(app_artifacts): - validators.append(app_artifacts.validator) - for _, ext_path in app_artifacts.dist_entries: - deploy_sources.append(app_artifacts.compact) - flipp_dist_paths.append(f"/ext/{ext_path}") - return app_artifacts - - def _add_host_app_to_targets(host_app): - artifacts_app_to_run = appenv["EXT_APPS"].get(host_app.appid, None) - _add_dist_targets(artifacts_app_to_run) - for plugin in host_app._plugins: - _add_dist_targets(appenv["EXT_APPS"].get(plugin.appid, None)) - - artifacts_app_to_run = appenv.GetExtAppByIdOrPath(appsrc) - if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: - # We deploy host app instead - host_app = appenv["APPMGR"].get(artifacts_app_to_run.app.requires[0]) - - if host_app: - if host_app.apptype == FlipperAppType.EXTERNAL: - _add_host_app_to_targets(host_app) - else: - # host app is a built-in app - run_script_extra_ars = f"-a {host_app.name}" - _add_dist_targets(artifacts_app_to_run) - else: - raise UserError("Host app is unknown") - else: - _add_host_app_to_targets(artifacts_app_to_run.app) - - # print(deploy_sources, flipp_dist_paths) - appenv.PhonyTarget( - "launch_app", - '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', - source=deploy_sources, - FLIPPER_FILE_TARGETS=flipp_dist_paths, - EXTRA_ARGS=run_script_extra_ars, - ) - appenv.Alias("launch_app", validators) # SDK management -sdk_origin_path = "${BUILD_DIR}/sdk_origin" -sdk_source = appenv.SDKPrebuilder( - sdk_origin_path, +amalgamated_api = "${BUILD_DIR}/sdk_origin" +sdk_source = appenv.ApiAmalgamator( + amalgamated_api, # Deps on root SDK headers and generated files (appenv["SDK_HEADERS"], appenv["FW_ASSETS_HEADERS"]), ) # Extra deps on headers included in deeper levels # Available on second and subsequent builds -Depends(sdk_source, appenv.ProcessSdkDepends(f"{sdk_origin_path}.d")) +Depends(sdk_source, appenv.ProcessSdkDepends(f"{amalgamated_api}.d")) -appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk") -sdk_tree = appenv.SDKTree(appenv["SDK_DIR"], sdk_origin_path) +appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk_headers") +sdk_header_tree = appenv.SDKHeaderTreeExtractor(appenv["SDK_DIR"], amalgamated_api) # AlwaysBuild(sdk_tree) -Alias("sdk_tree", sdk_tree) -extapps.sdk_tree = sdk_tree +Alias("sdk_tree", sdk_header_tree) +extapps.sdk_tree = sdk_header_tree -sdk_apicheck = appenv.SDKSymUpdater(appenv["SDK_DEFINITION"], sdk_origin_path) -Precious(sdk_apicheck) -NoClean(sdk_apicheck) -AlwaysBuild(sdk_apicheck) -Alias("sdk_check", sdk_apicheck) +api_check = appenv.ApiTableValidator(appenv["SDK_DEFINITION"], amalgamated_api) +Precious(api_check) +NoClean(api_check) +AlwaysBuild(api_check) +Alias("api_check", api_check) -sdk_apisyms = appenv.SDKSymGenerator( - "${BUILD_DIR}/assets/compiled/symbols.h", appenv["SDK_DEFINITION"] +firmware_apitable = appenv.ApiSymbolTable( + "${BUILD_DIR}/assets/compiled/firmware_api_table.h", appenv["SDK_DEFINITION"] ) -Alias("api_syms", sdk_apisyms) +Alias("api_table", firmware_apitable) ENV.Replace( - SDK_APISYMS=sdk_apisyms, + FW_API_TABLE=firmware_apitable, _APP_ICONS=appenv["_APP_ICONS"], ) if appenv["FORCE"]: - appenv.AlwaysBuild(sdk_source, sdk_tree, sdk_apicheck, sdk_apisyms) + appenv.AlwaysBuild(sdk_source, sdk_header_tree, api_check, firmware_apitable) Return("extapps")