Merge branch 'ul-dev' into xfw-dev

This commit is contained in:
Willy-JL
2023-04-07 23:40:28 +01:00
107 changed files with 3653 additions and 607 deletions

1
.gitignore vendored
View File

@@ -64,6 +64,7 @@ openocd.log
# PVS Studio temporary files
.PVS-Studio/
PVS-Studio.log
*.PVS-Studio.*
.gdbinit

View File

@@ -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);
}

View File

@@ -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");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

View File

@@ -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) {

View File

@@ -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",
)

View File

@@ -0,0 +1,183 @@
// Author: github.com/kallanreed
#include <furi.h>
#include <furi_hal.h>
#include <infrared.h>
#include <infrared_worker.h>
#include <furi_hal_infrared.h>
#include <gui/gui.h>
#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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

View File

@@ -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);

View File

@@ -17,7 +17,10 @@ App(
name="base32",
),
Lib(
name="list",
name="base64",
),
Lib(
name="linked_list"
),
Lib(
name="timezone_utils",

View File

@@ -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 {

View File

@@ -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"

View File

@@ -1,7 +1,7 @@
#include "add.h"
#include <stdlib.h>
#include <lib/toolbox/args.h>
#include "../../../lib/list/list.h"
#include <linked_list.h>
#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) {

View File

@@ -3,7 +3,7 @@
#include <stdlib.h>
#include <ctype.h>
#include <lib/toolbox/args.h>
#include "../../../lib/list/list.h"
#include <linked_list.h>
#include "../../../services/config/config.h"
#include "../../cli_helpers.h"
#include "../../../ui/scene_director.h"

View File

@@ -1,7 +1,7 @@
#include "details.h"
#include <stdlib.h>
#include <lib/toolbox/args.h>
#include "../../../lib/list/list.h"
#include <linked_list.h>
#include "../../../types/token_info.h"
#include "../../../services/config/constants.h"
#include "../../cli_helpers.h"

View File

@@ -1,6 +1,6 @@
#include "list.h"
#include <stdlib.h>
#include "../../../lib/list/list.h"
#include <linked_list.h>
#include "../../../types/token_info.h"
#include "../../../services/config/constants.h"
#include "../../cli_helpers.h"

View File

@@ -2,7 +2,7 @@
#include <stdlib.h>
#include <lib/toolbox/args.h>
#include "../../../lib/list/list.h"
#include <linked_list.h>
#include "../../../types/token_info.h"
#include "../../../services/config/config.h"
#include "../../cli_helpers.h"

View File

@@ -2,11 +2,12 @@
#include <stdlib.h>
#include <lib/toolbox/args.h>
#include <linked_list.h>
#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 <memset_s.h>
#include "../../../services/crypto/crypto.h"
#include "../../../ui/scene_director.h"

View File

@@ -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);
}

View File

@@ -1,7 +1,7 @@
#include "update.h"
#include <stdlib.h>
#include <lib/toolbox/args.h>
#include "../../../lib/list/list.h"
#include <linked_list.h>
#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);
}

View File

@@ -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;
}

View File

@@ -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);
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);

View File

@@ -1,2 +1,14 @@
// Include Bluetooth token input automation
#define TOTP_BADBT_TYPE_ENABLED
#define TOTP_AUTOMATION_ICONS_ENABLED
// 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

View File

@@ -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;

View File

@@ -27,6 +27,7 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
/**
@@ -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);

View File

@@ -0,0 +1,72 @@
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005, Jouni Malinen <j@w1.fi>
*
* 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 <string.h>
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;
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
/**
* @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);

View File

@@ -1,4 +1,4 @@
#include "list.h"
#include "linked_list.h"
ListNode* list_init_head(void* data) {
ListNode* new = malloc(sizeof(ListNode));

View File

@@ -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; \
}

View File

@@ -1,7 +1,7 @@
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include "../list/list.h"
#include <linked_list.h>
#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 {

View File

@@ -26,12 +26,9 @@
#include <stdint.h>
#include <string.h>
#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. */

View File

@@ -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(

View File

@@ -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,

View File

@@ -4,7 +4,7 @@
#include <gui/gui.h>
#include <dialogs/dialogs.h>
#include "../features_config.h"
#include "../lib/list/list.h"
#include <linked_list.h>
#include "../ui/totp_scenes_enum.h"
#include "notification_method.h"
#include "automation_method.h"

View File

@@ -1,11 +1,11 @@
#include <furi_hal.h>
#include "token_info.h"
#include "stdlib.h"
#include <furi_hal.h>
#include <base32.h>
#include <base64.h>
#include <memset_s.h>
#include <strnlen.h>
#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;
}

View File

@@ -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);
/**

View File

@@ -0,0 +1,24 @@
#pragma once
/* GENERATED BY https://github.com/pavius/the-dot-factory */
#include <stdint.h>
// 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;

View File

@@ -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 */
};

View File

@@ -0,0 +1,9 @@
#pragma once
/* GENERATED BY https://github.com/pavius/the-dot-factory */
#include "../font_info.h"
#include <stdint.h>
/* Font data for ModeNine 15pt */
extern const FONT_INFO modeNine_15ptFontInfo;

View File

@@ -4,17 +4,17 @@
#include "../../scene_director.h"
#include "totp_input_text.h"
#include "../../../types/token_info.h"
#include "../../../lib/list/list.h"
#include <linked_list.h>
#include "../../../services/config/config.h"
#include "../../ui_controls.h"
#include "../../common_dialogs.h"
#include "../../../lib/roll_value/roll_value.h"
#include <roll_value.h>
#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) {

View File

@@ -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 <roll_value.h>
#include "../../../types/nullable.h"
#include "../../../features_config.h"
#ifdef TOTP_BADBT_TYPE_ENABLED

View File

@@ -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;

View File

@@ -6,13 +6,13 @@
#include "../../constants.h"
#include "../../scene_director.h"
#include "../../../services/config/config.h"
#include "../../../lib/list/list.h"
#include <linked_list.h>
#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 <roll_value.h>
#define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3)
#define SCREEN_HEIGHT_THIRD_CENTER (SCREEN_HEIGHT_THIRD >> 1)

View File

@@ -1,5 +1,6 @@
#include "bt_type_code.h"
#include <furi_hal_bt_hid.h>
#include <bt/bt_service/bt_i.h>
#include <storage/storage.h>
#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");
}

View File

@@ -4,6 +4,12 @@
#include <furi/furi.h>
#include <furi_hal.h>
#include <bt/bt_service/bt.h>
#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 {

View File

@@ -3,17 +3,15 @@
#include <furi_hal.h>
#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);
}

View File

@@ -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);
}

View File

@@ -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"));
//}
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -12,7 +12,7 @@
#include <gui/modules/variable_item_list.h>
#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 {

View File

@@ -1,6 +1,7 @@
#include "uart_text_input.h"
#include <gui/elements.h>
#include "uart_terminal_icons.h"
#include "uart_terminal_app_i.h"
#include <furi.h>
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;
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -35,6 +35,12 @@ typedef enum {
SubGhzSpeakerStateEnable,
} SubGhzSpeakerState;
/** SubGhzStarLineIgnore state */
typedef enum {
SubGhzStarLineIgnoreDisable,
SubGhzStarLineIgnoreEnable,
} SubGhzStarLineIgnoreState;
/** SubGhzRxKeyState state */
typedef enum {
SubGhzRxKeyStateIDLE,

View File

@@ -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);

View File

@@ -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,

View File

@@ -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")) {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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)) {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -4,7 +4,7 @@
#include <flipper_application/api_hashtable/compilesort.hpp>
/* Generated table */
#include <symbols.h>
#include <firmware_api_table.h>
static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 B

View File

@@ -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"],
],
)

View File

@@ -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,
1 entry status name type params
2 Version + 20.0 20.1
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
540 Function + canvas_draw_glyph void Canvas*, uint8_t, uint8_t, uint16_t
541 Function + canvas_draw_icon void Canvas*, uint8_t, uint8_t, const Icon*
542 Function + canvas_draw_icon_animation void Canvas*, uint8_t, uint8_t, IconAnimation*
543 Function + canvas_draw_icon_ex void Canvas*, uint8_t, uint8_t, const Icon*, IconRotation
544 Function + canvas_draw_line void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t
545 Function + canvas_draw_rbox void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t
546 Function + canvas_draw_rframe void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t
877 Function - furi_hal_clock_suspend_tick void
878 Function - furi_hal_clock_switch_to_hsi void
879 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
880 Function + furi_hal_console_disable void
881 Function + furi_hal_console_enable void
882 Function + furi_hal_console_init void

View File

@@ -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*
1 entry status name type params
655 Function + canvas_draw_icon void Canvas*, uint8_t, uint8_t, const Icon*
656 Function + canvas_draw_icon_animation void Canvas*, uint8_t, uint8_t, IconAnimation*
657 Function + canvas_draw_icon_bitmap void Canvas*, uint8_t, uint8_t, int16_t, int16_t, const Icon*
658 Function + canvas_draw_icon_ex void Canvas*, uint8_t, uint8_t, const Icon*, IconRotation
659 Function + canvas_draw_line void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t
660 Function + canvas_draw_rbox void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t
661 Function + canvas_draw_rframe void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t
662 Function + canvas_draw_str void Canvas*, uint8_t, uint8_t, const char*
663 Function + canvas_draw_str_aligned void Canvas*, uint8_t, uint8_t, Align, Align, const char*
664 Function + canvas_draw_triangle void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection
665 Function + canvas_draw_u8g2_bitmap void u8g2_t*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*, uint8_t
666 Function + canvas_draw_xbm void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*
667 Function - canvas_frame_set void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t
668 Function - canvas_free void Canvas*

View File

@@ -22,16 +22,14 @@
#include <furi_hal.h>
#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:

View File

@@ -2,29 +2,24 @@
#include <furi_hal.h>
#include <flipper.h>
#include <alt_boot.h>
#include <u8g2_glue.h>
#include <assets_icons.h>
#include <toolbox/compress.h>
#include <gui/canvas.h>
#include <gui/canvas_i.h>
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() {

View File

@@ -2,44 +2,43 @@
#include <furi_hal.h>
#include <flipper.h>
#include <alt_boot.h>
#include <u8g2_glue.h>
#include <assets_icons.h>
#include <toolbox/compress.h>
#include <gui/canvas.h>
#include <gui/canvas_i.h>
#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) {

View File

@@ -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 = (

View File

@@ -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(

View File

@@ -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",

View File

@@ -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

View File

@@ -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"""

View File

@@ -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"])

View File

@@ -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()()

View File

@@ -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(
[

View File

@@ -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%" (

View File

@@ -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;

393
scripts/ufbt/SConstruct Normal file
View File

@@ -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)

View File

@@ -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")

View File

@@ -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
...

View File

@@ -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

View File

@@ -0,0 +1,4 @@
dist/*
.vscode
.clang-format
.editorconfig

View File

@@ -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
}

View File

@@ -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"
]
}

View File

@@ -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",
},
]
}

View File

@@ -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"
}
}

View File

@@ -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"
}
]
}

View File

@@ -0,0 +1,12 @@
#include <furi.h>
/* 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;
}

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