diff --git a/SConstruct b/SConstruct index b4c8d7b2903..12796796888 100644 --- a/SConstruct +++ b/SConstruct @@ -234,7 +234,7 @@ firmware_debug = distenv.PhonyTarget( ) distenv.Depends(firmware_debug, firmware_flash) -distenv.PhonyTarget( +firmware_blackmagic = distenv.PhonyTarget( "blackmagic", "${GDBPYCOM}", source=firmware_env["FW_ELF"], @@ -242,6 +242,7 @@ distenv.PhonyTarget( GDBREMOTE="${BLACKMAGIC_ADDR}", FBT_FAP_DEBUG_ELF_ROOT=firmware_env["FBT_FAP_DEBUG_ELF_ROOT"], ) +distenv.Depends(firmware_blackmagic, firmware_flash) # Debug alien elf debug_other_opts = [ diff --git a/applications/debug/ccid_test/ccid_test_app.c b/applications/debug/ccid_test/ccid_test_app.c index abb8ad3dd30..4158c1a6036 100644 --- a/applications/debug/ccid_test/ccid_test_app.c +++ b/applications/debug/ccid_test/ccid_test_app.c @@ -105,7 +105,7 @@ void ccid_test_app_free(CcidTestApp* app) { furi_record_close(RECORD_GUI); app->gui = NULL; - free(app->iso7816_handler); + iso7816_handler_free(app->iso7816_handler); // Free rest free(app); @@ -121,8 +121,7 @@ int32_t ccid_test_app(void* p) { furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true); - furi_hal_usb_ccid_set_callbacks( - (CcidCallbacks*)&app->iso7816_handler->ccid_callbacks, app->iso7816_handler); + iso7816_handler_set_usb_ccid_callbacks(); furi_hal_usb_ccid_insert_smartcard(); //handle button events @@ -142,7 +141,7 @@ int32_t ccid_test_app(void* p) { } //tear down USB - furi_hal_usb_ccid_set_callbacks(NULL, NULL); + iso7816_handler_reset_usb_ccid_callbacks(); furi_hal_usb_set_config(usb_mode_prev, NULL); //teardown view diff --git a/applications/debug/ccid_test/iso7816/iso7816_handler.c b/applications/debug/ccid_test/iso7816/iso7816_handler.c index 97214d1b226..8f0f758b2cd 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_handler.c +++ b/applications/debug/ccid_test/iso7816/iso7816_handler.c @@ -6,11 +6,17 @@ #include #include +#include "iso7816_handler.h" + #include "iso7816_t0_apdu.h" #include "iso7816_atr.h" -#include "iso7816_handler.h" #include "iso7816_response.h" +static Iso7816Handler* iso7816_handler; +static CcidCallbacks* ccid_callbacks; +static uint8_t* command_apdu_buffer; +static uint8_t* response_apdu_buffer; + void iso7816_icc_power_on_callback(uint8_t* atr_data, uint32_t* atr_data_len, void* context) { furi_check(context); @@ -40,12 +46,11 @@ void iso7816_xfr_datablock_callback( Iso7816Handler* handler = (Iso7816Handler*)context; - ISO7816_Response_APDU* response_apdu = (ISO7816_Response_APDU*)&handler->response_apdu_buffer; - - ISO7816_Command_APDU* command_apdu = (ISO7816_Command_APDU*)&handler->command_apdu_buffer; + ISO7816_Response_APDU* response_apdu = (ISO7816_Response_APDU*)response_apdu_buffer; + ISO7816_Command_APDU* command_apdu = (ISO7816_Command_APDU*)command_apdu_buffer; uint8_t result = iso7816_read_command_apdu( - command_apdu, pc_to_reader_datablock, pc_to_reader_datablock_len); + command_apdu, pc_to_reader_datablock, pc_to_reader_datablock_len, CCID_SHORT_APDU_SIZE); if(result == ISO7816_READ_COMMAND_APDU_OK) { handler->iso7816_process_command(command_apdu, response_apdu); @@ -61,8 +66,31 @@ void iso7816_xfr_datablock_callback( } Iso7816Handler* iso7816_handler_alloc() { - Iso7816Handler* handler = malloc(sizeof(Iso7816Handler)); - handler->ccid_callbacks.icc_power_on_callback = iso7816_icc_power_on_callback; - handler->ccid_callbacks.xfr_datablock_callback = iso7816_xfr_datablock_callback; - return handler; + iso7816_handler = malloc(sizeof(Iso7816Handler)); + + command_apdu_buffer = malloc(sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE); + response_apdu_buffer = malloc(sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE); + + ccid_callbacks = malloc(sizeof(CcidCallbacks)); + ccid_callbacks->icc_power_on_callback = iso7816_icc_power_on_callback; + ccid_callbacks->xfr_datablock_callback = iso7816_xfr_datablock_callback; + + return iso7816_handler; +} + +void iso7816_handler_set_usb_ccid_callbacks() { + furi_hal_usb_ccid_set_callbacks(ccid_callbacks, iso7816_handler); +} + +void iso7816_handler_reset_usb_ccid_callbacks() { + furi_hal_usb_ccid_set_callbacks(NULL, NULL); +} + +void iso7816_handler_free(Iso7816Handler* handler) { + free(ccid_callbacks); + + free(command_apdu_buffer); + free(response_apdu_buffer); + + free(handler); } diff --git a/applications/debug/ccid_test/iso7816/iso7816_handler.h b/applications/debug/ccid_test/iso7816/iso7816_handler.h index d67118ce6e7..4f9703e46b5 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_handler.h +++ b/applications/debug/ccid_test/iso7816/iso7816_handler.h @@ -5,14 +5,14 @@ #include "iso7816_t0_apdu.h" typedef struct { - CcidCallbacks ccid_callbacks; void (*iso7816_answer_to_reset)(Iso7816Atr* atr); void (*iso7816_process_command)( const ISO7816_Command_APDU* command, ISO7816_Response_APDU* response); - - uint8_t command_apdu_buffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE]; - uint8_t response_apdu_buffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE]; } Iso7816Handler; Iso7816Handler* iso7816_handler_alloc(); + +void iso7816_handler_free(Iso7816Handler* handler); +void iso7816_handler_set_usb_ccid_callbacks(); +void iso7816_handler_reset_usb_ccid_callbacks(); diff --git a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c index 216f2582f1a..023daebe22c 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c +++ b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c @@ -1,49 +1,48 @@ /* Implements rudimentary iso7816-3 support for APDU (T=0) */ #include #include -#include -#include #include "iso7816_t0_apdu.h" -//reads dataBuffer with dataLen size, translate it into a ISO7816_Command_APDU type +//reads pc_to_reader_datablock_len with pc_to_reader_datablock_len size, translate it into a ISO7816_Command_APDU type //extra data will be pointed to commandDataBuffer uint8_t iso7816_read_command_apdu( ISO7816_Command_APDU* command, - const uint8_t* dataBuffer, - uint32_t dataLen) { - command->CLA = dataBuffer[0]; - command->INS = dataBuffer[1]; - command->P1 = dataBuffer[2]; - command->P2 = dataBuffer[3]; + const uint8_t* pc_to_reader_datablock, + uint32_t pc_to_reader_datablock_len, + uint32_t max_apdu_size) { + command->CLA = pc_to_reader_datablock[0]; + command->INS = pc_to_reader_datablock[1]; + command->P1 = pc_to_reader_datablock[2]; + command->P2 = pc_to_reader_datablock[3]; - if(dataLen == 4) { + if(pc_to_reader_datablock_len == 4) { command->Lc = 0; command->Le = 0; command->LePresent = false; return ISO7816_READ_COMMAND_APDU_OK; - } else if(dataLen == 5) { + } else if(pc_to_reader_datablock_len == 5) { //short le command->Lc = 0; - command->Le = dataBuffer[4]; + command->Le = pc_to_reader_datablock[4]; command->LePresent = true; return ISO7816_READ_COMMAND_APDU_OK; - } else if(dataLen > 5 && dataBuffer[4] != 0x00) { + } else if(pc_to_reader_datablock_len > 5 && pc_to_reader_datablock[4] != 0x00) { //short lc - command->Lc = dataBuffer[4]; - if(command->Lc > 0 && command->Lc < CCID_SHORT_APDU_SIZE) { //-V560 - memcpy(command->Data, &dataBuffer[5], command->Lc); + command->Lc = pc_to_reader_datablock[4]; + if(command->Lc > 0 && command->Lc < max_apdu_size) { //-V560 + memcpy(command->Data, &pc_to_reader_datablock[5], command->Lc); //does it have a short le too? - if(dataLen == (uint32_t)(command->Lc + 5)) { + if(pc_to_reader_datablock_len == (uint32_t)(command->Lc + 5)) { command->Le = 0; command->LePresent = false; return ISO7816_READ_COMMAND_APDU_OK; - } else if(dataLen == (uint32_t)(command->Lc + 6)) { - command->Le = dataBuffer[dataLen - 1]; + } else if(pc_to_reader_datablock_len == (uint32_t)(command->Lc + 6)) { + command->Le = pc_to_reader_datablock[pc_to_reader_datablock_len - 1]; command->LePresent = true; return ISO7816_READ_COMMAND_APDU_OK; diff --git a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h index a21dfbafc33..bbd2a1b235e 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h +++ b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h @@ -34,7 +34,8 @@ typedef struct { uint8_t iso7816_read_command_apdu( ISO7816_Command_APDU* command, const uint8_t* pc_to_reader_datablock, - uint32_t pc_to_reader_datablock_len); + uint32_t pc_to_reader_datablock_len, + uint32_t max_apdu_size); void iso7816_write_response_apdu( const ISO7816_Response_APDU* response, uint8_t* reader_to_pc_datablock, diff --git a/applications/debug/expansion_test/expansion_test.c b/applications/debug/expansion_test/expansion_test.c index e3bcf4e9c92..665874f4766 100644 --- a/applications/debug/expansion_test/expansion_test.c +++ b/applications/debug/expansion_test/expansion_test.c @@ -6,18 +6,27 @@ * 13 -> 16 (USART TX to LPUART RX) * 14 -> 15 (USART RX to LPUART TX) * + * Optional: Connect an LED with an appropriate series resistor + * between pins 1 and 8. It will always be on if the device is + * connected to USB power, so unplug it before running the app. + * * What this application does: * * - Enables module support and emulates the module on a single device * (hence the above connection), * - Connects to the expansion module service, sets baud rate, + * - Enables OTG (5V) on GPIO via plain expansion protocol, + * - Waits 5 cycles of idle loop (1 second), * - Starts the RPC session, + * - Disables OTG (5V) on GPIO via RPC messages, + * - Waits 5 cycles of idle loop (1 second), * - Creates a directory at `/ext/ExpansionTest` and writes a file * named `test.txt` under it, * - Plays an audiovisual alert (sound and blinking display), - * - Waits 10 cycles of idle loop, + * - Enables OTG (5V) on GPIO via RPC messages, + * - Waits 5 cycles of idle loop (1 second), * - Stops the RPC session, - * - Waits another 10 cycles of idle loop, + * - Disables OTG (5V) on GPIO via plain expansion protocol, * - Exits (plays a sound if any of the above steps failed). */ #include @@ -302,6 +311,22 @@ static bool expansion_test_app_handshake(ExpansionTestApp* instance) { return success; } +static bool expansion_test_app_enable_otg(ExpansionTestApp* instance, bool enable) { + bool success = false; + + do { + const ExpansionFrameControlCommand command = enable ? + ExpansionFrameControlCommandEnableOtg : + ExpansionFrameControlCommandDisableOtg; + if(!expansion_test_app_send_control_request(instance, command)) break; + if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; + if(!expansion_test_app_is_success_response(&instance->frame)) break; + success = true; + } while(false); + + return success; +} + static bool expansion_test_app_start_rpc(ExpansionTestApp* instance) { bool success = false; @@ -396,6 +421,27 @@ static bool expansion_test_app_rpc_alert(ExpansionTestApp* instance) { return success; } +static bool expansion_test_app_rpc_enable_otg(ExpansionTestApp* instance, bool enable) { + bool success = false; + + instance->msg.command_id++; + instance->msg.command_status = PB_CommandStatus_OK; + instance->msg.which_content = PB_Main_gpio_set_otg_mode_tag; + instance->msg.content.gpio_set_otg_mode.mode = enable ? PB_Gpio_GpioOtgMode_ON : + PB_Gpio_GpioOtgMode_OFF; + instance->msg.has_next = false; + + do { + if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break; + if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break; + if(instance->msg.which_content != PB_Main_empty_tag) break; + if(instance->msg.command_status != PB_CommandStatus_OK) break; + success = true; + } while(false); + + return success; +} + static bool expansion_test_app_idle(ExpansionTestApp* instance, uint32_t num_cycles) { uint32_t num_cycles_done; for(num_cycles_done = 0; num_cycles_done < num_cycles; ++num_cycles_done) { @@ -434,13 +480,18 @@ int32_t expansion_test_app(void* p) { if(!expansion_test_app_send_presence(instance)) break; if(!expansion_test_app_wait_ready(instance)) break; if(!expansion_test_app_handshake(instance)) break; + if(!expansion_test_app_enable_otg(instance, true)) break; + if(!expansion_test_app_idle(instance, 5)) break; if(!expansion_test_app_start_rpc(instance)) break; + if(!expansion_test_app_rpc_enable_otg(instance, false)) break; + if(!expansion_test_app_idle(instance, 5)) break; if(!expansion_test_app_rpc_mkdir(instance)) break; if(!expansion_test_app_rpc_write(instance)) break; if(!expansion_test_app_rpc_alert(instance)) break; - if(!expansion_test_app_idle(instance, 10)) break; + if(!expansion_test_app_rpc_enable_otg(instance, true)) break; + if(!expansion_test_app_idle(instance, 5)) break; if(!expansion_test_app_stop_rpc(instance)) break; - if(!expansion_test_app_idle(instance, 10)) break; + if(!expansion_test_app_enable_otg(instance, false)) break; success = true; } while(false); diff --git a/applications/debug/rpc_debug_app/rpc_debug_app.c b/applications/debug/rpc_debug_app/rpc_debug_app.c index 1536b8918e9..1affeae29ff 100644 --- a/applications/debug/rpc_debug_app/rpc_debug_app.c +++ b/applications/debug/rpc_debug_app/rpc_debug_app.c @@ -24,7 +24,7 @@ static void rpc_debug_app_tick_event_callback(void* context) { static void rpc_debug_app_format_hex(const uint8_t* data, size_t data_size, char* buf, size_t buf_size) { if(data == NULL || data_size == 0) { - strncpy(buf, "", buf_size); + strlcpy(buf, "", buf_size); return; } diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c index 367ca7a4ff1..09d58c2c9b1 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c @@ -1,5 +1,7 @@ #include "../rpc_debug_app.h" +#include + static bool rpc_debug_app_scene_input_error_code_validator_callback( const char* text, FuriString* error, @@ -24,7 +26,7 @@ static void rpc_debug_app_scene_input_error_code_result_callback(void* context) void rpc_debug_app_scene_input_error_code_on_enter(void* context) { RpcDebugApp* app = context; - strncpy(app->text_store, "666", TEXT_STORE_SIZE); + strlcpy(app->text_store, "666", TEXT_STORE_SIZE); text_input_set_header_text(app->text_input, "Enter error code"); text_input_set_validator( app->text_input, rpc_debug_app_scene_input_error_code_validator_callback, NULL); @@ -44,9 +46,8 @@ bool rpc_debug_app_scene_input_error_code_on_event(void* context, SceneManagerEv if(event.type == SceneManagerEventTypeCustom) { if(event.event == RpcDebugAppCustomEventInputErrorCode) { - char* end; - int error_code = strtol(app->text_store, &end, 10); - if(!*end) { + uint32_t error_code; + if(strint_to_uint32(app->text_store, NULL, &error_code, 10) == StrintParseNoError) { rpc_system_app_set_error_code(app->rpc, error_code); } scene_manager_previous_scene(app->scene_manager); diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c index b07f8f4af27..cca293b665d 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c @@ -7,7 +7,7 @@ static void rpc_debug_app_scene_input_error_text_result_callback(void* context) void rpc_debug_app_scene_input_error_text_on_enter(void* context) { RpcDebugApp* app = context; - strncpy(app->text_store, "I'm a scary error message!", TEXT_STORE_SIZE); + strlcpy(app->text_store, "I'm a scary error message!", TEXT_STORE_SIZE); text_input_set_header_text(app->text_input, "Enter error text"); text_input_set_result_callback( app->text_input, diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c index 10d5e36f624..686a6f95df0 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c @@ -2,7 +2,7 @@ void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) { RpcDebugApp* app = context; - strncpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE); + strlcpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE); text_box_set_text(app->text_box, app->text_store); text_box_set_font(app->text_box, TextBoxFontHex); diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index be807168aef..4298dc33dbe 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -6,6 +6,8 @@ #include #include +#include + #include #include @@ -320,7 +322,7 @@ int32_t uart_echo_app(void* p) { uint32_t baudrate = DEFAULT_BAUD_RATE; if(p) { const char* baudrate_str = p; - if(sscanf(baudrate_str, "%lu", &baudrate) != 1) { + if(strint_to_uint32(baudrate_str, NULL, &baudrate, 10) != StrintParseNoError) { FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str); baudrate = DEFAULT_BAUD_RATE; } diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index f5f84ead797..c87305847af 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -220,3 +220,11 @@ App( entry_point="get_api", requires=["unit_tests"], ) + +App( + appid="test_strint", + sources=["tests/common/*.c", "tests/strint/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) diff --git a/applications/debug/unit_tests/tests/strint/strint_test.c b/applications/debug/unit_tests/tests/strint/strint_test.c new file mode 100644 index 00000000000..d8fd9113d37 --- /dev/null +++ b/applications/debug/unit_tests/tests/strint/strint_test.c @@ -0,0 +1,142 @@ +#include +#include + +#include "../test.h" // IWYU pragma: keep + +#include + +MU_TEST(strint_test_basic) { + uint32_t result = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123456", NULL, &result, 10)); + mu_assert_int_eq(123456, result); +} + +MU_TEST(strint_test_junk) { + uint32_t result = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32(" 123456 ", NULL, &result, 10)); + mu_assert_int_eq(123456, result); + mu_assert_int_eq( + StrintParseNoError, strint_to_uint32(" \r\n\r\n 123456 ", NULL, &result, 10)); + mu_assert_int_eq(123456, result); +} + +MU_TEST(strint_test_tail) { + uint32_t result = 0; + char* tail; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123456tail", &tail, &result, 10)); + mu_assert_int_eq(123456, result); + mu_assert_string_eq("tail", tail); + mu_assert_int_eq( + StrintParseNoError, strint_to_uint32(" \r\n 123456tail", &tail, &result, 10)); + mu_assert_int_eq(123456, result); + mu_assert_string_eq("tail", tail); +} + +MU_TEST(strint_test_errors) { + uint32_t result = 123; + mu_assert_int_eq(StrintParseAbsentError, strint_to_uint32("", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseAbsentError, strint_to_uint32(" asd\r\n", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseSignError, strint_to_uint32("+++123456", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseSignError, strint_to_uint32("-1", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq( + StrintParseOverflowError, + strint_to_uint32("0xAAAAAAAAAAAAAAAADEADBEEF!!!!!!", NULL, &result, 0)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseOverflowError, strint_to_uint32("4294967296", NULL, &result, 0)); + mu_assert_int_eq(123, result); + + int32_t result_i32 = 123; + mu_assert_int_eq( + StrintParseOverflowError, strint_to_int32("-2147483649", NULL, &result_i32, 0)); + mu_assert_int_eq(123, result_i32); +} + +MU_TEST(strint_test_bases) { + uint32_t result = 0; + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0x123", NULL, &result, 0)); + mu_assert_int_eq(0x123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0X123", NULL, &result, 0)); + mu_assert_int_eq(0x123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0xDEADBEEF", NULL, &result, 0)); + mu_assert_int_eq(0xDEADBEEF, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0xDEADBEEF", NULL, &result, 16)); + mu_assert_int_eq(0xDEADBEEF, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123", NULL, &result, 16)); + mu_assert_int_eq(0x123, result); + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123", NULL, &result, 0)); + mu_assert_int_eq(123, result); + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0123", NULL, &result, 0)); + mu_assert_int_eq(0123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0123", NULL, &result, 8)); + mu_assert_int_eq(0123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123", NULL, &result, 8)); + mu_assert_int_eq(0123, result); + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0b101", NULL, &result, 0)); + mu_assert_int_eq(0b101, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0b101", NULL, &result, 2)); + mu_assert_int_eq(0b101, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0B101", NULL, &result, 0)); + mu_assert_int_eq(0b101, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("101", NULL, &result, 2)); + mu_assert_int_eq(0b101, result); +} + +MU_TEST_SUITE(strint_test_limits) { + uint64_t result_u64 = 0; + mu_assert_int_eq( + StrintParseNoError, strint_to_uint64("18446744073709551615", NULL, &result_u64, 0)); + // `mu_assert_int_eq' does not support longs :( + mu_assert(UINT64_MAX == result_u64, "result does not equal UINT64_MAX"); + + int64_t result_i64 = 0; + mu_assert_int_eq( + StrintParseNoError, strint_to_int64("9223372036854775807", NULL, &result_i64, 0)); + mu_assert(INT64_MAX == result_i64, "result does not equal INT64_MAX"); + mu_assert_int_eq( + StrintParseNoError, strint_to_int64("-9223372036854775808", NULL, &result_i64, 0)); + mu_assert(INT64_MIN == result_i64, "result does not equal INT64_MIN"); + + uint32_t result_u32 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("4294967295", NULL, &result_u32, 0)); + mu_assert_int_eq(UINT32_MAX, result_u32); + + int32_t result_i32 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_int32("2147483647", NULL, &result_i32, 0)); + mu_assert_int_eq(INT32_MAX, result_i32); + mu_assert_int_eq(StrintParseNoError, strint_to_int32("-2147483648", NULL, &result_i32, 0)); + mu_assert_int_eq(INT32_MIN, result_i32); + + uint16_t result_u16 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint16("65535", NULL, &result_u16, 0)); + mu_assert_int_eq(UINT16_MAX, result_u16); + + int16_t result_i16 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_int16("32767", NULL, &result_i16, 0)); + mu_assert_int_eq(INT16_MAX, result_i16); + mu_assert_int_eq(StrintParseNoError, strint_to_int16("-32768", NULL, &result_i16, 0)); + mu_assert_int_eq(INT16_MIN, result_i16); +} + +MU_TEST_SUITE(test_strint_suite) { + MU_RUN_TEST(strint_test_basic); + MU_RUN_TEST(strint_test_junk); + MU_RUN_TEST(strint_test_tail); + MU_RUN_TEST(strint_test_errors); + MU_RUN_TEST(strint_test_bases); + MU_RUN_TEST(strint_test_limits); +} + +int run_minunit_test_strint(void) { + MU_RUN_SUITE(test_strint_suite); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_strint) diff --git a/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c b/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c index 2afdaf5c10c..d10d97c2404 100644 --- a/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c +++ b/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c @@ -13,8 +13,7 @@ static void example_number_input_scene_update_view(void* context) { dialog_ex_set_header(dialog_ex, "The number is", 64, 0, AlignCenter, AlignTop); - static char buffer[12]; //needs static for extended lifetime - + char buffer[12] = {}; snprintf(buffer, sizeof(buffer), "%ld", app->current_number); dialog_ex_set_text(dialog_ex, buffer, 64, 29, AlignCenter, AlignCenter); diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index 895f05ce76f..e5af819e997 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -257,7 +257,7 @@ static void example_thermo_draw_callback(Canvas* canvas, void* ctx) { snprintf(text_store, TEXT_STORE_SIZE, "Temperature: %+.1f%c", (double)temp, temp_units); } else { /* Or show a message that no data is available */ - strncpy(text_store, "-- No data --", TEXT_STORE_SIZE); + strlcpy(text_store, "-- No data --", TEXT_STORE_SIZE); } canvas_draw_str_aligned(canvas, middle_x, 58, AlignCenter, AlignBottom, text_store); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 2ee66955c58..ccc3caa811b 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "ducky_script.h" #include "ducky_script_i.h" @@ -64,7 +65,7 @@ uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept bool ducky_get_number(const char* param, uint32_t* val) { uint32_t value = 0; - if(sscanf(param, "%lu", &value) == 1) { + if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) { *val = value; return true; } diff --git a/applications/main/bad_usb/resources/badusb/Install_qFlipper_gnome.txt b/applications/main/bad_usb/resources/badusb/Install_qFlipper_gnome.txt new file mode 100644 index 00000000000..ec2ec108eec --- /dev/null +++ b/applications/main/bad_usb/resources/badusb/Install_qFlipper_gnome.txt @@ -0,0 +1,51 @@ +ID 1234:abcd Generic:USB Keyboard +REM Declare ourselves as a generic usb keyboard + +REM This will install qFlipper on Linux/Gnome, using the latest AppImage package + +REM Open a terminal +ALT F2 +DELAY 1000 +STRINGLN gnome-terminal --maximize +DELAY 1000 + +REM Ensure we have a folder to run executables from +STRINGLN mkdir -p $HOME/.local/bin + +REM Download the latest AppImage +STRINGLN curl -fsSL "https://update.flipperzero.one/qFlipper/release/linux-amd64/AppImage" -o "$HOME/.local/bin/qFlipper" +DELAY 1000 + +REM Make it executable +STRINGLN chmod +x $HOME/.local/bin/qFlipper + +REM Extract the appimage in /tmp to install icon and .desktop file +STRINGLN cd /tmp +STRINGLN $HOME/.local/bin/qFlipper --appimage-extract > /dev/null +STRINGLN sed "s@Exec=qFlipper@Exec=$HOME/.local/bin/qFlipper@" squashfs-root/usr/share/applications/qFlipper.desktop > $HOME/.local/share/applications/qFlipper.desktop +STRINGLN mkdir -p $HOME/.local/share/icons/hicolor/512x512/apps +STRINGLN cp squashfs-root/usr/share/icons/hicolor/512x512/apps/qFlipper.png $HOME/.local/share/icons/hicolor/512x512/apps/qFlipper.png +STRINGLN rm -rf squashfs-root +STRINGLN cd + +REM Depending on the Linux distribution and display manager +REM there might be several ways to update desktop entries +REM try all +STRINGLN xdg-desktop-menu forceupdate || true +STRINGLN update-desktop-database ~/.local/share/applications || true + +STRINGLN echo " +ENTER +REPEAT 60 +STRINGLN ========================================================================================== +STRINGLN qFlipper has been installed to $HOME/.local/bin/ +STRINGLN It should appear in your Applications menu. +STRINGLN If it does not, you might want to log out and log in again. +ENTER +STRINGLN If you prefer to run qFlipper from your terminal, either use the absolute path +STRINGLN or make sure $HOME/.local/bin/ is included in your PATH environment variable. +ENTER +STRINGLN Additional configurations might be required by your Linux distribution such as +STRINGLN group membership, udev rules or else. +STRINGLN ========================================================================================== +STRINGLN " diff --git a/applications/main/bad_usb/resources/badusb/demo_gnome.txt b/applications/main/bad_usb/resources/badusb/demo_gnome.txt new file mode 100644 index 00000000000..7d91de4126f --- /dev/null +++ b/applications/main/bad_usb/resources/badusb/demo_gnome.txt @@ -0,0 +1,87 @@ +ID 1234:abcd Generic:USB Keyboard +REM Declare ourselves as a generic usb keyboard +REM You can override this to use something else +REM Check the `lsusb` command to know your own devices IDs + +REM This is BadUSB demo script for Linux/Gnome + +REM Open terminal window +DELAY 1000 +ALT F2 +DELAY 500 +STRING gnome-terminal --maximize +DELAY 500 +ENTER +DELAY 750 + +REM Clear the screen in case some banner was displayed +STRING clear +ENTER + +REM Bigger shell script example +STRING cat > /dev/null << EOF +ENTER + +STRING Hello World! +ENTER + +DEFAULT_DELAY 50 + +STRING = +REPEAT 59 +ENTER +ENTER + +STRING _.-------.._ -, +ENTER +HOME +STRING .-"'''"--..,,_/ /'-, -, \ +ENTER +HOME +STRING .:" /:/ /'\ \ ,_..., '. | | +ENTER +HOME +STRING / ,----/:/ /'\ _\~'_-"' _; +ENTER +HOME +STRING ' / /'"""'\ \ \.~'_-' ,-"'/ +ENTER +HOME +STRING | | | 0 | | .-' ,/' / +ENTER +HOME +STRING | ,..\ \ ,.-"' ,/' / +ENTER +HOME +STRING ; : '/'""\' ,/--==,/-----, +ENTER +HOME +STRING | '-...| -.___-Z:_______J...---; +ENTER +HOME +STRING : ' _-' +ENTER +HOME +STRING _L_ _ ___ ___ ___ ___ ____--"' +ENTER +HOME +STRING | __|| | |_ _|| _ \| _ \| __|| _ \ +ENTER +HOME +STRING | _| | |__ | | | _/| _/| _| | / +ENTER +HOME +STRING |_| |____||___||_| |_| |___||_|_\ +ENTER +HOME +ENTER + +STRING Flipper Zero BadUSB feature is compatible with USB Rubber Ducky script format +ENTER +STRING More information about script syntax can be found here: +ENTER +STRING https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/file_formats/BadUsbScriptFormat.md +ENTER + +STRING EOF +ENTER diff --git a/applications/main/ibutton/ibutton.c b/applications/main/ibutton/ibutton.c index 765d5361253..eff44c6deb2 100644 --- a/applications/main/ibutton/ibutton.c +++ b/applications/main/ibutton/ibutton.c @@ -183,7 +183,7 @@ bool ibutton_load_key(iButton* ibutton, bool show_error) { FuriString* tmp = furi_string_alloc(); path_extract_filename(ibutton->file_path, tmp, true); - strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE); + strlcpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE); furi_string_free(tmp); } else if(show_error) { @@ -243,7 +243,7 @@ bool ibutton_delete_key(iButton* ibutton) { } void ibutton_reset_key(iButton* ibutton) { - memset(ibutton->key_name, 0, IBUTTON_KEY_NAME_SIZE + 1); + ibutton->key_name[0] = '\0'; furi_string_reset(ibutton->file_path); ibutton_key_reset(ibutton->key); } diff --git a/applications/main/ibutton/ibutton_i.h b/applications/main/ibutton/ibutton_i.h index fc2324c6356..73f1080807f 100644 --- a/applications/main/ibutton/ibutton_i.h +++ b/applications/main/ibutton/ibutton_i.h @@ -32,7 +32,7 @@ #define IBUTTON_APP_FILENAME_PREFIX "iBtn" #define IBUTTON_APP_FILENAME_EXTENSION ".ibtn" -#define IBUTTON_KEY_NAME_SIZE 22 +#define IBUTTON_KEY_NAME_SIZE 23 typedef enum { iButtonWriteModeInvalid, @@ -56,7 +56,7 @@ struct iButton { iButtonWriteMode write_mode; FuriString* file_path; - char key_name[IBUTTON_KEY_NAME_SIZE + 1]; + char key_name[IBUTTON_KEY_NAME_SIZE]; Submenu* submenu; ByteInput* byte_input; diff --git a/applications/main/ibutton/scenes/ibutton_scene_rpc.c b/applications/main/ibutton/scenes/ibutton_scene_rpc.c index f4f193a47e8..87f51f2d8b4 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_rpc.c +++ b/applications/main/ibutton/scenes/ibutton_scene_rpc.c @@ -1,22 +1,26 @@ #include "../ibutton_i.h" void ibutton_scene_rpc_on_enter(void* context) { - iButton* ibutton = context; + UNUSED(context); +} + +static void ibutton_rpc_start_emulation(iButton* ibutton) { Popup* popup = ibutton->popup; popup_set_header(popup, "iButton", 82, 28, AlignCenter, AlignBottom); - popup_set_text(popup, "RPC mode", 82, 32, AlignCenter, AlignTop); - + popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop); popup_set_icon(popup, 2, 14, &I_iButtonKey_49x44); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); + ibutton_worker_emulate_start(ibutton->worker, ibutton->key); + + ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); notification_message(ibutton->notifications, &sequence_display_backlight_on); } bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { iButton* ibutton = context; - Popup* popup = ibutton->popup; bool consumed = false; @@ -27,17 +31,13 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool result = false; if(ibutton_load_key(ibutton, false)) { - popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop); - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); - - ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); - ibutton_worker_emulate_start(ibutton->worker, ibutton->key); - + ibutton_rpc_start_emulation(ibutton); result = true; + } else { + rpc_system_app_set_error_code(ibutton->rpc, RpcAppSystemErrorCodeParseFile); + rpc_system_app_set_error_text(ibutton->rpc, "Cannot load key file"); } - rpc_system_app_confirm(ibutton->rpc, result); - } else if(event.event == iButtonCustomEventRpcExit) { rpc_system_app_confirm(ibutton->rpc, true); scene_manager_stop(ibutton->scene_manager); diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 75c874e73b3..a93fd766df3 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -297,7 +297,7 @@ static void infrared_free(InfraredApp* infrared) { free(infrared); } -bool infrared_add_remote_with_button( +InfraredErrorCode infrared_add_remote_with_button( const InfraredApp* infrared, const char* button_name, const InfraredSignal* signal) { @@ -310,21 +310,23 @@ bool infrared_add_remote_with_button( furi_string_cat_printf( new_path, "/%s%s", furi_string_get_cstr(new_name), INFRARED_APP_EXTENSION); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; do { - if(!infrared_remote_create(remote, furi_string_get_cstr(new_path))) break; - if(!infrared_remote_append_signal(remote, signal, button_name)) break; - success = true; + error = infrared_remote_create(remote, furi_string_get_cstr(new_path)); + if(INFRARED_ERROR_PRESENT(error)) break; + + error = infrared_remote_append_signal(remote, signal, button_name); } while(false); furi_string_free(new_name); furi_string_free(new_path); - return success; + return error; } -bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name) { +InfraredErrorCode + infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name) { InfraredRemote* remote = infrared->remote; const char* old_path = infrared_remote_get_path(remote); @@ -344,12 +346,13 @@ bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new path_append(new_path_fstr, furi_string_get_cstr(new_name_fstr)); furi_string_cat(new_path_fstr, INFRARED_APP_EXTENSION); - const bool success = infrared_remote_rename(remote, furi_string_get_cstr(new_path_fstr)); + const InfraredErrorCode error = + infrared_remote_rename(remote, furi_string_get_cstr(new_path_fstr)); furi_string_free(new_name_fstr); furi_string_free(new_path_fstr); - return success; + return error; } void infrared_tx_start(InfraredApp* infrared) { @@ -382,17 +385,16 @@ void infrared_tx_start(InfraredApp* infrared) { infrared->app_state.is_transmitting = true; } -void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index) { +InfraredErrorCode infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index) { furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote)); - if(infrared_remote_load_signal(infrared->remote, infrared->current_signal, button_index)) { + InfraredErrorCode error = + infrared_remote_load_signal(infrared->remote, infrared->current_signal, button_index); + + if(!INFRARED_ERROR_PRESENT(error)) { infrared_tx_start(infrared); - } else { - infrared_show_error_message( - infrared, - "Failed to load\n\"%s\"", - infrared_remote_get_signal_name(infrared->remote, button_index)); } + return error; } void infrared_tx_stop(InfraredApp* infrared) { @@ -415,7 +417,7 @@ void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback call furi_thread_start(infrared->task_thread); } -bool infrared_blocking_task_finalize(InfraredApp* infrared) { +InfraredErrorCode infrared_blocking_task_finalize(InfraredApp* infrared) { furi_thread_join(infrared->task_thread); return furi_thread_get_return_code(infrared->task_thread); } @@ -565,10 +567,18 @@ int32_t infrared_app(void* p) { is_rpc_mode = true; } else { const char* file_path = (const char*)p; - is_remote_loaded = infrared_remote_load(infrared->remote, file_path); - - if(!is_remote_loaded) { - infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path); + InfraredErrorCode error = infrared_remote_load(infrared->remote, file_path); + + if(!INFRARED_ERROR_PRESENT(error)) { + is_remote_loaded = true; + } else { + is_remote_loaded = false; + bool wrong_file_type = INFRARED_ERROR_CHECK(error, InfraredErrorCodeWrongFileType); + const char* format = wrong_file_type ? + "Library file\n\"%s\" can't be openned as a remote" : + "Failed to load\n\"%s\""; + + infrared_show_error_message(infrared, format, file_path); return -1; } diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 75d4e230d25..692cc967157 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -43,8 +43,8 @@ #define INFRARED_TEXT_STORE_NUM 2 #define INFRARED_TEXT_STORE_SIZE 128 -#define INFRARED_MAX_BUTTON_NAME_LENGTH 22 -#define INFRARED_MAX_REMOTE_NAME_LENGTH 22 +#define INFRARED_MAX_BUTTON_NAME_LENGTH 23 +#define INFRARED_MAX_REMOTE_NAME_LENGTH 23 #define INFRARED_APP_FOLDER EXT_PATH("infrared") #define INFRARED_APP_EXTENSION ".ir" @@ -174,9 +174,9 @@ typedef enum { * @param[in] infrared pointer to the application instance. * @param[in] name pointer to a zero-terminated string containing the signal name. * @param[in] signal pointer to the signal to be added. - * @return true if the remote was successfully created, false otherwise. + * @return InfraredErrorCodeNone if the remote was successfully created, otherwise error code. */ -bool infrared_add_remote_with_button( +InfraredErrorCode infrared_add_remote_with_button( const InfraredApp* infrared, const char* name, const InfraredSignal* signal); @@ -186,9 +186,10 @@ bool infrared_add_remote_with_button( * * @param[in] infrared pointer to the application instance. * @param[in] new_name pointer to a zero-terminated string containing the new remote name. - * @return true if the remote was successfully renamed, false otherwise. + * @return InfraredErrorCodeNone if the remote was successfully renamed, otherwise error code. */ -bool infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name); +InfraredErrorCode + infrared_rename_current_remote(const InfraredApp* infrared, const char* new_name); /** * @brief Begin transmission of the currently loaded signal. @@ -206,9 +207,9 @@ void infrared_tx_start(InfraredApp* infrared); * * @param[in,out] infrared pointer to the application instance. * @param[in] button_index index of the signal to be loaded. - * @returns true if the signal could be loaded, false otherwise. + * @returns InfraredErrorCodeNone if the signal could be loaded, otherwise error code. */ -void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); +InfraredErrorCode infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); /** * @brief Stop transmission of the currently loaded signal. @@ -236,9 +237,9 @@ void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback call * (e.g. to display the results), the caller code MUST set it explicitly. * * @param[in,out] infrared pointer to the application instance. - * @return true if the blocking task finished successfully, false otherwise. + * @return InfraredErrorCodeNone if the blocking task finished successfully, otherwise error code. */ -bool infrared_blocking_task_finalize(InfraredApp* infrared); +InfraredErrorCode infrared_blocking_task_finalize(InfraredApp* infrared); /** * @brief Set the internal text store with formatted text. diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index b28918489dd..8c7422d5ef9 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -50,10 +50,10 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const brute_force->db_filename = db_filename; } -bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { +InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { furi_assert(!brute_force->is_started); furi_assert(brute_force->db_filename); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); @@ -61,12 +61,15 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { InfraredSignal* signal = infrared_signal_alloc(); do { - if(!flipper_format_buffered_file_open_existing(ff, brute_force->db_filename)) break; + if(!flipper_format_buffered_file_open_existing(ff, brute_force->db_filename)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } bool signals_valid = false; - while(infrared_signal_read_name(ff, signal_name)) { - signals_valid = infrared_signal_read_body(signal, ff) && - infrared_signal_is_valid(signal); + while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) { + error = infrared_signal_read_body(signal, ff); + signals_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal); if(!signals_valid) break; InfraredBruteForceRecord* record = @@ -75,9 +78,7 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { ++(record->count); } } - if(!signals_valid) break; - success = true; } while(false); infrared_signal_free(signal); @@ -85,7 +86,7 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { flipper_format_free(ff); furi_record_close(RECORD_STORAGE); - return success; + return error; } bool infrared_brute_force_start( @@ -139,10 +140,12 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) { bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) { furi_assert(brute_force->is_started); + const bool success = infrared_signal_search_by_name_and_read( - brute_force->current_signal, - brute_force->ff, - furi_string_get_cstr(brute_force->current_record_name)); + brute_force->current_signal, + brute_force->ff, + furi_string_get_cstr(brute_force->current_record_name)) == + InfraredErrorCodeNone; if(success) { infrared_signal_transmit(brute_force->current_signal); } diff --git a/applications/main/infrared/infrared_brute_force.h b/applications/main/infrared/infrared_brute_force.h index 8eda08a63ec..8796422576b 100644 --- a/applications/main/infrared/infrared_brute_force.h +++ b/applications/main/infrared/infrared_brute_force.h @@ -10,6 +10,7 @@ #include #include +#include "infrared_error_code.h" /** * @brief InfraredBruteForce opaque type declaration. @@ -45,9 +46,9 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const * a infrared_brute_force_set_db_filename() call. * * @param[in,out] brute_force pointer to the instance to be updated. - * @returns true on success, false otherwise. + * @returns InfraredErrorCodeNone on success, otherwise error code. */ -bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force); +InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force); /** * @brief Start transmitting signals from a category stored in an InfraredBruteForce's instance dictionary. diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 169f0c63d94..b700cf12150 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "infrared_signal.h" @@ -176,25 +177,28 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) { return false; } - uint32_t* timings = malloc(sizeof(uint32_t) * MAX_TIMINGS_AMOUNT); - uint32_t frequency = atoi(frequency_str); - float duty_cycle = (float)atoi(duty_cycle_str) / 100; + uint32_t frequency; + uint32_t duty_cycle_u32; + if(strint_to_uint32(frequency_str, NULL, &frequency, 10) != StrintParseNoError || + strint_to_uint32(duty_cycle_str, NULL, &duty_cycle_u32, 10) != StrintParseNoError) + return false; + float duty_cycle = duty_cycle_u32 / 100.0f; str += strlen(frequency_str) + strlen(duty_cycle_str) + INFRARED_CLI_BUF_SIZE; + uint32_t* timings = malloc(sizeof(uint32_t) * MAX_TIMINGS_AMOUNT); size_t timings_size = 0; while(1) { while(*str == ' ') { ++str; } - char timing_str[INFRARED_CLI_BUF_SIZE]; - if(sscanf(str, "%9s", timing_str) != 1) { + uint32_t timing; + char* next_token; + if(strint_to_uint32(str, &next_token, &timing, 10) != StrintParseNoError) { break; } - - str += strlen(timing_str); - uint32_t timing = atoi(timing_str); + str = next_token; if((timing <= 0) || (timings_size >= MAX_TIMINGS_AMOUNT)) { break; @@ -228,9 +232,15 @@ static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) { static bool infrared_cli_save_signal(InfraredSignal* signal, FlipperFormat* file, const char* name) { - bool ret = infrared_signal_save(signal, file, name); - if(!ret) { - printf("Failed to save signal: \"%s\"\r\n", name); + bool ret = true; + InfraredErrorCode error = infrared_signal_save(signal, file, name); + if(INFRARED_ERROR_PRESENT(error)) { + printf( + "Failed to save signal: \"%s\" code: 0x%X index: 0x%02X\r\n", + name, + INFRARED_ERROR_GET_CODE(error), + INFRARED_ERROR_GET_INDEX(error)); + ret = false; } return ret; } @@ -292,7 +302,7 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o FuriString* tmp; tmp = furi_string_alloc(); - while(infrared_signal_read(signal, input_file, tmp)) { + while(infrared_signal_read(signal, input_file, tmp) == InfraredErrorCodeNone) { ret = false; if(!infrared_signal_is_valid(signal)) { printf("Invalid signal\r\n"); @@ -460,7 +470,7 @@ static void printf("Missing signal name.\r\n"); break; } - if(!infrared_brute_force_calculate_messages(brute_force)) { + if(infrared_brute_force_calculate_messages(brute_force) != InfraredErrorCodeNone) { printf("Invalid remote name.\r\n"); break; } diff --git a/applications/main/infrared/infrared_error_code.h b/applications/main/infrared/infrared_error_code.h new file mode 100644 index 00000000000..841721b1767 --- /dev/null +++ b/applications/main/infrared/infrared_error_code.h @@ -0,0 +1,45 @@ +#pragma once + +typedef enum { + InfraredErrorCodeNone = 0, + InfraredErrorCodeFileOperationFailed = 0x800000, + InfraredErrorCodeWrongFileType = 0x80000100, + InfraredErrorCodeWrongFileVersion = 0x80000200, + + //Common signal errors + InfraredErrorCodeSignalTypeUnknown = 0x80000300, + InfraredErrorCodeSignalNameNotFound = 0x80000400, + InfraredErrorCodeSignalUnableToReadType = 0x80000500, + InfraredErrorCodeSignalUnableToWriteType = 0x80000600, + + //Raw signal errors + InfraredErrorCodeSignalRawUnableToReadFrequency = 0x80000700, + InfraredErrorCodeSignalRawUnableToReadDutyCycle = 0x80000800, + InfraredErrorCodeSignalRawUnableToReadTimingsSize = 0x80000900, + InfraredErrorCodeSignalRawUnableToReadTooLongData = 0x80000A00, + InfraredErrorCodeSignalRawUnableToReadData = 0x80000B00, + + InfraredErrorCodeSignalRawUnableToWriteFrequency = 0x80000C00, + InfraredErrorCodeSignalRawUnableToWriteDutyCycle = 0x80000D00, + InfraredErrorCodeSignalRawUnableToWriteData = 0x80000E00, + + //Message signal errors + InfraredErrorCodeSignalMessageUnableToReadProtocol = 0x80000F00, + InfraredErrorCodeSignalMessageUnableToReadAddress = 0x80001000, + InfraredErrorCodeSignalMessageUnableToReadCommand = 0x80001100, + InfraredErrorCodeSignalMessageIsInvalid = 0x80001200, + + InfraredErrorCodeSignalMessageUnableToWriteProtocol = 0x80001300, + InfraredErrorCodeSignalMessageUnableToWriteAddress = 0x80001400, + InfraredErrorCodeSignalMessageUnableToWriteCommand = 0x80001500, +} InfraredErrorCode; + +#define INFRARED_ERROR_CODE_MASK (0xFFFFFF00) +#define INFRARED_ERROR_INDEX_MASK (0x000000FF) + +#define INFRARED_ERROR_GET_CODE(error) ((error) & INFRARED_ERROR_CODE_MASK) +#define INFRARED_ERROR_GET_INDEX(error) ((error) & INFRARED_ERROR_INDEX_MASK) +#define INFRARED_ERROR_SET_INDEX(code, index) ((code) |= ((index) & INFRARED_ERROR_INDEX_MASK)) + +#define INFRARED_ERROR_PRESENT(error) (INFRARED_ERROR_GET_CODE(error) != InfraredErrorCodeNone) +#define INFRARED_ERROR_CHECK(error, test_code) (INFRARED_ERROR_GET_CODE(error) == (test_code)) diff --git a/applications/main/infrared/infrared_remote.c b/applications/main/infrared/infrared_remote.c index 11bbf197bc8..9da08cbd41d 100644 --- a/applications/main/infrared/infrared_remote.c +++ b/applications/main/infrared/infrared_remote.c @@ -8,8 +8,9 @@ #define TAG "InfraredRemote" -#define INFRARED_FILE_HEADER "IR signals file" -#define INFRARED_FILE_VERSION (1) +#define INFRARED_FILE_HEADER "IR signals file" +#define INFRARED_LIBRARY_HEADER "IR library file" +#define INFRARED_FILE_VERSION (1) ARRAY_DEF(StringArray, const char*, M_CSTR_DUP_OPLIST); //-V575 @@ -34,7 +35,7 @@ typedef struct { const InfraredSignal* signal; } InfraredBatchTarget; -typedef bool ( +typedef InfraredErrorCode ( *InfraredBatchCallback)(const InfraredBatch* batch, const InfraredBatchTarget* target); InfraredRemote* infrared_remote_alloc(void) { @@ -80,7 +81,7 @@ const char* infrared_remote_get_signal_name(const InfraredRemote* remote, size_t return *StringArray_cget(remote->signal_names, index); } -bool infrared_remote_load_signal( +InfraredErrorCode infrared_remote_load_signal( const InfraredRemote* remote, InfraredSignal* signal, size_t index) { @@ -89,25 +90,27 @@ bool infrared_remote_load_signal( Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; do { const char* path = furi_string_get_cstr(remote->path); - if(!flipper_format_buffered_file_open_existing(ff, path)) break; + if(!flipper_format_buffered_file_open_existing(ff, path)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } - if(!infrared_signal_search_by_index_and_read(signal, ff, index)) { + error = infrared_signal_search_by_index_and_read(signal, ff, index); + if(INFRARED_ERROR_PRESENT(error)) { const char* signal_name = infrared_remote_get_signal_name(remote, index); FURI_LOG_E(TAG, "Failed to load signal '%s' from file '%s'", signal_name, path); break; } - - success = true; } while(false); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); - return success; + return error; } bool infrared_remote_get_signal_index( @@ -128,31 +131,35 @@ bool infrared_remote_get_signal_index( return false; } -bool infrared_remote_append_signal( +InfraredErrorCode infrared_remote_append_signal( InfraredRemote* remote, const InfraredSignal* signal, const char* name) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_file_alloc(storage); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; const char* path = furi_string_get_cstr(remote->path); do { - if(!flipper_format_file_open_append(ff, path)) break; - if(!infrared_signal_save(signal, ff, name)) break; + if(!flipper_format_file_open_append(ff, path)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } + + error = infrared_signal_save(signal, ff, name); + if(INFRARED_ERROR_PRESENT(error)) break; StringArray_push_back(remote->signal_names, name); - success = true; } while(false); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); - return success; + return error; } -static bool infrared_remote_batch_start( +static InfraredErrorCode infrared_remote_batch_start( InfraredRemote* remote, InfraredBatchCallback batch_callback, const InfraredBatchTarget* target) { @@ -179,33 +186,59 @@ static bool infrared_remote_batch_start( status = storage_common_stat(storage, path_out, NULL); } while(status == FSE_OK || status == FSE_EXIST); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; + StringArray_t buf_names; + StringArray_init_set(buf_names, remote->signal_names); do { - if(!flipper_format_buffered_file_open_existing(batch_context.ff_in, path_in)) break; - if(!flipper_format_buffered_file_open_always(batch_context.ff_out, path_out)) break; - if(!flipper_format_write_header_cstr( - batch_context.ff_out, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION)) + if(!flipper_format_buffered_file_open_existing(batch_context.ff_in, path_in) || + !flipper_format_buffered_file_open_always(batch_context.ff_out, path_out) || + !flipper_format_write_header_cstr( + batch_context.ff_out, INFRARED_FILE_HEADER, INFRARED_FILE_VERSION)) { + error = InfraredErrorCodeFileOperationFailed; break; - + } const size_t signal_count = infrared_remote_get_signal_count(remote); for(; batch_context.signal_index < signal_count; ++batch_context.signal_index) { - if(!infrared_signal_read( - batch_context.signal, batch_context.ff_in, batch_context.signal_name)) + error = infrared_signal_read( + batch_context.signal, batch_context.ff_in, batch_context.signal_name); + if(INFRARED_ERROR_PRESENT(error)) { + INFRARED_ERROR_SET_INDEX(error, batch_context.signal_index); break; - if(!batch_callback(&batch_context, target)) break; - } + } - if(batch_context.signal_index != signal_count) break; + error = batch_callback(&batch_context, target); + if(INFRARED_ERROR_PRESENT(error)) { + INFRARED_ERROR_SET_INDEX(error, batch_context.signal_index); + break; + } + } + if(INFRARED_ERROR_PRESENT(error)) break; - if(!flipper_format_buffered_file_close(batch_context.ff_out)) break; - if(!flipper_format_buffered_file_close(batch_context.ff_in)) break; + if(!flipper_format_buffered_file_close(batch_context.ff_out) || + !flipper_format_buffered_file_close(batch_context.ff_in)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } const FS_Error status = storage_common_rename(storage, path_out, path_in); - success = (status == FSE_OK || status == FSE_EXIST); + error = (status == FSE_OK || status == FSE_EXIST) ? InfraredErrorCodeNone : + InfraredErrorCodeFileOperationFailed; } while(false); + if(INFRARED_ERROR_PRESENT(error)) { + //Remove all temp data and rollback signal names + flipper_format_buffered_file_close(batch_context.ff_out); + flipper_format_buffered_file_close(batch_context.ff_in); + status = storage_common_stat(storage, path_out, NULL); + if(status == FSE_OK || status == FSE_EXIST) storage_common_remove(storage, path_out); + + StringArray_reset(remote->signal_names); + StringArray_set(remote->signal_names, buf_names); + } + + StringArray_clear(buf_names); infrared_signal_free(batch_context.signal); furi_string_free(batch_context.signal_name); flipper_format_free(batch_context.ff_out); @@ -214,15 +247,18 @@ static bool infrared_remote_batch_start( furi_record_close(RECORD_STORAGE); - return success; + return error; } -static bool infrared_remote_insert_signal_callback( +static InfraredErrorCode infrared_remote_insert_signal_callback( const InfraredBatch* batch, const InfraredBatchTarget* target) { // Insert a signal under the specified index if(batch->signal_index == target->signal_index) { - if(!infrared_signal_save(target->signal, batch->ff_out, target->signal_name)) return false; + InfraredErrorCode error = + infrared_signal_save(target->signal, batch->ff_out, target->signal_name); + if(INFRARED_ERROR_PRESENT(error)) return error; + StringArray_push_at( batch->remote->signal_names, target->signal_index, target->signal_name); } @@ -232,7 +268,7 @@ static bool infrared_remote_insert_signal_callback( batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name)); } -bool infrared_remote_insert_signal( +InfraredErrorCode infrared_remote_insert_signal( InfraredRemote* remote, const InfraredSignal* signal, const char* name, @@ -251,7 +287,7 @@ bool infrared_remote_insert_signal( remote, infrared_remote_insert_signal_callback, &insert_target); } -static bool infrared_remote_rename_signal_callback( +static InfraredErrorCode infrared_remote_rename_signal_callback( const InfraredBatch* batch, const InfraredBatchTarget* target) { const char* signal_name; @@ -268,7 +304,8 @@ static bool infrared_remote_rename_signal_callback( return infrared_signal_save(batch->signal, batch->ff_out, signal_name); } -bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name) { +InfraredErrorCode + infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name) { furi_assert(index < infrared_remote_get_signal_count(remote)); const InfraredBatchTarget rename_target = { @@ -281,7 +318,7 @@ bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const c remote, infrared_remote_rename_signal_callback, &rename_target); } -static bool infrared_remote_delete_signal_callback( +static InfraredErrorCode infrared_remote_delete_signal_callback( const InfraredBatch* batch, const InfraredBatchTarget* target) { if(batch->signal_index == target->signal_index) { @@ -294,10 +331,10 @@ static bool infrared_remote_delete_signal_callback( batch->signal, batch->ff_out, furi_string_get_cstr(batch->signal_name)); } - return true; + return InfraredErrorCodeNone; } -bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index) { +InfraredErrorCode infrared_remote_delete_signal(InfraredRemote* remote, size_t index) { furi_assert(index < infrared_remote_get_signal_count(remote)); const InfraredBatchTarget delete_target = { @@ -310,33 +347,35 @@ bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index) { remote, infrared_remote_delete_signal_callback, &delete_target); } -bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index) { +InfraredErrorCode + infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index) { const size_t signal_count = infrared_remote_get_signal_count(remote); furi_assert(index < signal_count); furi_assert(new_index < signal_count); - if(index == new_index) return true; + InfraredErrorCode error = InfraredErrorCodeNone; + if(index == new_index) return error; InfraredSignal* signal = infrared_signal_alloc(); char* signal_name = strdup(infrared_remote_get_signal_name(remote, index)); - bool success = false; - do { - if(!infrared_remote_load_signal(remote, signal, index)) break; - if(!infrared_remote_delete_signal(remote, index)) break; - if(!infrared_remote_insert_signal(remote, signal, signal_name, new_index)) break; + error = infrared_remote_load_signal(remote, signal, index); + if(INFRARED_ERROR_PRESENT(error)) break; - success = true; + error = infrared_remote_delete_signal(remote, index); + if(INFRARED_ERROR_PRESENT(error)) break; + + error = infrared_remote_insert_signal(remote, signal, signal_name, new_index); } while(false); free(signal_name); infrared_signal_free(signal); - return success; + return error; } -bool infrared_remote_create(InfraredRemote* remote, const char* path) { +InfraredErrorCode infrared_remote_create(InfraredRemote* remote, const char* path) { FURI_LOG_I(TAG, "Creating new file: '%s'", path); infrared_remote_reset(remote); @@ -358,45 +397,64 @@ bool infrared_remote_create(InfraredRemote* remote, const char* path) { flipper_format_free(ff); furi_record_close(RECORD_STORAGE); - return success; + return success ? InfraredErrorCodeNone : InfraredErrorCodeFileOperationFailed; } -bool infrared_remote_load(InfraredRemote* remote, const char* path) { +InfraredErrorCode infrared_remote_load(InfraredRemote* remote, const char* path) { FURI_LOG_I(TAG, "Loading file: '%s'", path); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); FuriString* tmp = furi_string_alloc(); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; do { - if(!flipper_format_buffered_file_open_existing(ff, path)) break; + if(!flipper_format_buffered_file_open_existing(ff, path)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } uint32_t version; - if(!flipper_format_read_header(ff, tmp, &version)) break; + if(!flipper_format_read_header(ff, tmp, &version)) { + error = InfraredErrorCodeFileOperationFailed; + break; + } + + if(furi_string_equal(tmp, INFRARED_LIBRARY_HEADER)) { + FURI_LOG_E(TAG, "Library file can't be loaded in this context"); + error = InfraredErrorCodeWrongFileType; + break; + } + + if(!furi_string_equal(tmp, INFRARED_FILE_HEADER)) { + error = InfraredErrorCodeWrongFileType; + FURI_LOG_E(TAG, "Filetype unknown"); + break; + } - if(!furi_string_equal(tmp, INFRARED_FILE_HEADER) || (version != INFRARED_FILE_VERSION)) + if(version != INFRARED_FILE_VERSION) { + error = InfraredErrorCodeWrongFileVersion; + FURI_LOG_E(TAG, "Wrong file version"); break; + } infrared_remote_set_path(remote, path); StringArray_reset(remote->signal_names); - while(infrared_signal_read_name(ff, tmp)) { + while(infrared_signal_read_name(ff, tmp) == InfraredErrorCodeNone) { StringArray_push_back(remote->signal_names, furi_string_get_cstr(tmp)); } - - success = true; } while(false); furi_string_free(tmp); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); - return success; + return error; } -bool infrared_remote_rename(InfraredRemote* remote, const char* new_path) { +InfraredErrorCode infrared_remote_rename(InfraredRemote* remote, const char* new_path) { const char* old_path = infrared_remote_get_path(remote); Storage* storage = furi_record_open(RECORD_STORAGE); @@ -409,10 +467,10 @@ bool infrared_remote_rename(InfraredRemote* remote, const char* new_path) { infrared_remote_set_path(remote, new_path); } - return success; + return success ? InfraredErrorCodeNone : InfraredErrorCodeFileOperationFailed; } -bool infrared_remote_remove(InfraredRemote* remote) { +InfraredErrorCode infrared_remote_remove(InfraredRemote* remote) { Storage* storage = furi_record_open(RECORD_STORAGE); const FS_Error status = storage_common_remove(storage, infrared_remote_get_path(remote)); furi_record_close(RECORD_STORAGE); @@ -423,5 +481,5 @@ bool infrared_remote_remove(InfraredRemote* remote) { infrared_remote_reset(remote); } - return success; + return success ? InfraredErrorCodeNone : InfraredErrorCodeFileOperationFailed; } diff --git a/applications/main/infrared/infrared_remote.h b/applications/main/infrared/infrared_remote.h index 4604a25ef0f..14416cd6598 100644 --- a/applications/main/infrared/infrared_remote.h +++ b/applications/main/infrared/infrared_remote.h @@ -105,12 +105,10 @@ bool infrared_remote_get_signal_index( * @param[in] remote pointer to the instance to load from. * @param[out] signal pointer to the signal to load into. Must be allocated. * @param[in] index index of the signal to be loaded. Must be less than the total signal count. - * @return true if the signal was successfully loaded, false otherwise. + * @return InfraredErrorCodeNone if the signal was successfully loaded, otherwise error code. */ -bool infrared_remote_load_signal( - const InfraredRemote* remote, - InfraredSignal* signal, - size_t index); +InfraredErrorCode + infrared_remote_load_signal(const InfraredRemote* remote, InfraredSignal* signal, size_t index); /** * @brief Append a signal to the file associated with an InfraredRemote instance. @@ -121,9 +119,9 @@ bool infrared_remote_load_signal( * @param[in,out] remote pointer to the instance to append to. * @param[in] signal pointer to the signal to be appended. * @param[in] name pointer to a zero-terminated string containing the name of the signal. - * @returns true if the signal was successfully appended, false otherwise. + * @returns InfraredErrorCodeNone if the signal was successfully appended, otherwise error code. */ -bool infrared_remote_append_signal( +InfraredErrorCode infrared_remote_append_signal( InfraredRemote* remote, const InfraredSignal* signal, const char* name); @@ -141,9 +139,10 @@ bool infrared_remote_append_signal( * @param[in] signal pointer to the signal to be inserted. * @param[in] name pointer to a zero-terminated string containing the name of the signal. * @param[in] index the index under which the signal shall be inserted. - * @returns true if the signal was successfully inserted, false otherwise. + * @returns InfraredErrorCodeNone if the signal was successfully inserted, otherwise error + * code describing what error happened ORed with index pointing which signal caused an error. */ -bool infrared_remote_insert_signal( +InfraredErrorCode infrared_remote_insert_signal( InfraredRemote* remote, const InfraredSignal* signal, const char* name, @@ -157,9 +156,10 @@ bool infrared_remote_insert_signal( * @param[in,out] remote pointer to the instance to be modified. * @param[in] index index of the signal to be renamed. Must be less than the total signal count. * @param[in] new_name pointer to a zero-terminated string containig the signal's new name. - * @returns true if the signal was successfully renamed, false otherwise. + * @returns InfraredErrorCodeNone if the signal was successfully renamed, otherwise error code. */ -bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name); +InfraredErrorCode + infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const char* new_name); /** * @brief Change a signal's position in the file associated with an InfraredRemote instance. @@ -169,17 +169,21 @@ bool infrared_remote_rename_signal(InfraredRemote* remote, size_t index, const c * @param[in,out] remote pointer to the instance to be modified. * @param[in] index index of the signal to be moved. Must be less than the total signal count. * @param[in] new_index index of the signal to be moved. Must be less than the total signal count. + * @returns InfraredErrorCodeNone if the signal was moved successfully, otherwise error + * code describing what error happened ORed with index pointing which signal caused an error. */ -bool infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index); +InfraredErrorCode + infrared_remote_move_signal(InfraredRemote* remote, size_t index, size_t new_index); /** * @brief Delete a signal in the file associated with an InfraredRemote instance. * * @param[in,out] remote pointer to the instance to be modified. * @param[in] index index of the signal to be deleted. Must be less than the total signal count. - * @returns true if the signal was successfully deleted, false otherwise. + * @returns InfraredErrorCodeNone if the signal was successfully deleted, otherwise error + * code describing what error happened ORed with index pointing which signal caused an error. */ -bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index); +InfraredErrorCode infrared_remote_delete_signal(InfraredRemote* remote, size_t index); /** * @brief Create a new file and associate it with an InfraredRemote instance. @@ -188,9 +192,9 @@ bool infrared_remote_delete_signal(InfraredRemote* remote, size_t index); * * @param[in,out] remote pointer to the instance to be assigned with a new file. * @param[in] path pointer to a zero-terminated string containing the full file path. - * @returns true if the file was successfully created, false otherwise. + * @returns InfraredErrorCodeNone if the file was successfully created, otherwise error code. */ -bool infrared_remote_create(InfraredRemote* remote, const char* path); +InfraredErrorCode infrared_remote_create(InfraredRemote* remote, const char* path); /** * @brief Associate an InfraredRemote instance with a file and load the signal names from it. @@ -200,9 +204,9 @@ bool infrared_remote_create(InfraredRemote* remote, const char* path); * * @param[in,out] remote pointer to the instance to be assigned with an existing file. * @param[in] path pointer to a zero-terminated string containing the full file path. - * @returns true if the file was successfully loaded, false otherwise. + * @returns InfraredErrorCodeNone if the file was successfully loaded, otherwise error code. */ -bool infrared_remote_load(InfraredRemote* remote, const char* path); +InfraredErrorCode infrared_remote_load(InfraredRemote* remote, const char* path); /** * @brief Rename the file associated with an InfraredRemote instance. @@ -211,9 +215,9 @@ bool infrared_remote_load(InfraredRemote* remote, const char* path); * * @param[in,out] remote pointer to the instance to be modified. * @param[in] new_path pointer to a zero-terminated string containing the new full file path. - * @returns true if the file was successfully renamed, false otherwise. + * @returns InfraredErrorCodeNone if the file was successfully renamed, otherwise error code. */ -bool infrared_remote_rename(InfraredRemote* remote, const char* new_path); +InfraredErrorCode infrared_remote_rename(InfraredRemote* remote, const char* new_path); /** * @brief Remove the file associated with an InfraredRemote instance. @@ -224,6 +228,6 @@ bool infrared_remote_rename(InfraredRemote* remote, const char* new_path); * infrared_remote_create() or infrared_remote_load() are successfully executed. * * @param[in,out] remote pointer to the instance to be modified. - * @returns true if the file was successfully removed, false otherwise. + * @returns InfraredErrorCodeNone if the file was successfully removed, otherwise error code. */ -bool infrared_remote_remove(InfraredRemote* remote); +InfraredErrorCode infrared_remote_remove(InfraredRemote* remote); diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index a4cb28bf0ed..75643f1d3b1 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -101,104 +101,177 @@ static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) { return true; } -static inline bool +static inline InfraredErrorCode infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) { const char* protocol_name = infrared_get_protocol_name(message->protocol); - return flipper_format_write_string_cstr( - ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_PARSED) && - flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_PROTOCOL_KEY, protocol_name) && - flipper_format_write_hex( - ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message->address, 4) && - flipper_format_write_hex( - ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message->command, 4); + InfraredErrorCode error = InfraredErrorCodeNone; + do { + if(!flipper_format_write_string_cstr( + ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_PARSED)) { + error = InfraredErrorCodeSignalUnableToWriteType; + break; + } + + if(!flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_PROTOCOL_KEY, protocol_name)) { + error = InfraredErrorCodeSignalMessageUnableToWriteProtocol; + break; + } + + if(!flipper_format_write_hex( + ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message->address, 4)) { + error = InfraredErrorCodeSignalMessageUnableToWriteAddress; + break; + } + + if(!flipper_format_write_hex( + ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message->command, 4)) { + error = InfraredErrorCodeSignalMessageUnableToWriteCommand; + break; + } + + } while(false); + + return error; } -static inline bool infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) { +static inline InfraredErrorCode + infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) { furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT); - return flipper_format_write_string_cstr( - ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_RAW) && - flipper_format_write_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &raw->frequency, 1) && - flipper_format_write_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &raw->duty_cycle, 1) && - flipper_format_write_uint32( - ff, INFRARED_SIGNAL_DATA_KEY, raw->timings, raw->timings_size); + + InfraredErrorCode error = InfraredErrorCodeNone; + do { + if(!flipper_format_write_string_cstr( + ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_RAW)) { + error = InfraredErrorCodeSignalUnableToWriteType; + break; + } + + if(!flipper_format_write_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &raw->frequency, 1)) { + error = InfraredErrorCodeSignalRawUnableToWriteFrequency; + break; + } + + if(!flipper_format_write_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &raw->duty_cycle, 1)) { + error = InfraredErrorCodeSignalRawUnableToWriteDutyCycle; + break; + } + + if(!flipper_format_write_uint32( + ff, INFRARED_SIGNAL_DATA_KEY, raw->timings, raw->timings_size)) { + error = InfraredErrorCodeSignalRawUnableToWriteData; + break; + } + } while(false); + return error; } -static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) { +static inline InfraredErrorCode + infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) { FuriString* buf; buf = furi_string_alloc(); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; do { - if(!flipper_format_read_string(ff, INFRARED_SIGNAL_PROTOCOL_KEY, buf)) break; + if(!flipper_format_read_string(ff, INFRARED_SIGNAL_PROTOCOL_KEY, buf)) { + error = InfraredErrorCodeSignalMessageUnableToReadProtocol; + break; + } InfraredMessage message; message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf)); - if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message.address, 4)) + if(!flipper_format_read_hex( + ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message.address, 4)) { + error = InfraredErrorCodeSignalMessageUnableToReadAddress; + break; + } + if(!flipper_format_read_hex( + ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message.command, 4)) { + error = InfraredErrorCodeSignalMessageUnableToReadCommand; break; - if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message.command, 4)) + } + + if(!infrared_signal_is_message_valid(&message)) { + error = InfraredErrorCodeSignalMessageIsInvalid; break; - if(!infrared_signal_is_message_valid(&message)) break; + } infrared_signal_set_message(signal, &message); - success = true; } while(false); furi_string_free(buf); - return success; + return error; } -static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) { - bool success = false; +static inline InfraredErrorCode + infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) { + InfraredErrorCode error = InfraredErrorCodeNone; do { uint32_t frequency; - if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &frequency, 1)) break; + if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &frequency, 1)) { + error = InfraredErrorCodeSignalRawUnableToReadFrequency; + break; + } float duty_cycle; - if(!flipper_format_read_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &duty_cycle, 1)) break; + if(!flipper_format_read_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &duty_cycle, 1)) { + error = InfraredErrorCodeSignalRawUnableToReadDutyCycle; + break; + } uint32_t timings_size; - if(!flipper_format_get_value_count(ff, INFRARED_SIGNAL_DATA_KEY, &timings_size)) break; + if(!flipper_format_get_value_count(ff, INFRARED_SIGNAL_DATA_KEY, &timings_size)) { + error = InfraredErrorCodeSignalRawUnableToReadTimingsSize; + break; + } - if(timings_size > MAX_TIMINGS_AMOUNT) break; + if(timings_size > MAX_TIMINGS_AMOUNT) { + error = InfraredErrorCodeSignalRawUnableToReadTooLongData; + break; + } uint32_t* timings = malloc(sizeof(uint32_t) * timings_size); if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_DATA_KEY, timings, timings_size)) { + error = InfraredErrorCodeSignalRawUnableToReadData; free(timings); break; } + infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle); free(timings); - success = true; + error = InfraredErrorCodeNone; } while(false); - return success; + return error; } -bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { +InfraredErrorCode infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { FuriString* tmp = furi_string_alloc(); - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; do { - if(!flipper_format_read_string(ff, INFRARED_SIGNAL_TYPE_KEY, tmp)) break; + if(!flipper_format_read_string(ff, INFRARED_SIGNAL_TYPE_KEY, tmp)) { + error = InfraredErrorCodeSignalUnableToReadType; + break; + } if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_RAW)) { - if(!infrared_signal_read_raw(signal, ff)) break; + error = infrared_signal_read_raw(signal, ff); } else if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_PARSED)) { - if(!infrared_signal_read_message(signal, ff)) break; + error = infrared_signal_read_message(signal, ff); } else { FURI_LOG_E(TAG, "Unknown signal type: %s", furi_string_get_cstr(tmp)); + error = InfraredErrorCodeSignalTypeUnknown; break; } - - success = true; } while(false); furi_string_free(tmp); - return success; + + return error; } InfraredSignal* infrared_signal_alloc(void) { @@ -270,68 +343,88 @@ const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal) return &signal->payload.message; } -bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name) { +InfraredErrorCode + infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name) { + InfraredErrorCode error = InfraredErrorCodeNone; + if(!flipper_format_write_comment_cstr(ff, "") || !flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_NAME_KEY, name)) { - return false; + error = InfraredErrorCodeFileOperationFailed; } else if(signal->is_raw) { - return infrared_signal_save_raw(&signal->payload.raw, ff); + error = infrared_signal_save_raw(&signal->payload.raw, ff); } else { - return infrared_signal_save_message(&signal->payload.message, ff); + error = infrared_signal_save_message(&signal->payload.message, ff); } + + return error; } -bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) { - bool success = false; +InfraredErrorCode + infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) { + InfraredErrorCode error = InfraredErrorCodeNone; do { - if(!infrared_signal_read_name(ff, name)) break; - if(!infrared_signal_read_body(signal, ff)) break; + error = infrared_signal_read_name(ff, name); + if(INFRARED_ERROR_PRESENT(error)) break; - success = true; //-V779 + error = infrared_signal_read_body(signal, ff); } while(false); - return success; + return error; } -bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) { - return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name); +InfraredErrorCode infrared_signal_read_name(FlipperFormat* ff, FuriString* name) { + return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name) ? + InfraredErrorCodeNone : + InfraredErrorCodeSignalNameNotFound; } -bool infrared_signal_search_by_name_and_read( +InfraredErrorCode infrared_signal_search_by_name_and_read( InfraredSignal* signal, FlipperFormat* ff, const char* name) { - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; FuriString* tmp = furi_string_alloc(); - while(infrared_signal_read_name(ff, tmp)) { + do { + error = infrared_signal_read_name(ff, tmp); + if(INFRARED_ERROR_PRESENT(error)) break; + if(furi_string_equal(tmp, name)) { - success = infrared_signal_read_body(signal, ff); + error = infrared_signal_read_body(signal, ff); break; } - } + } while(true); furi_string_free(tmp); - return success; + return error; } -bool infrared_signal_search_by_index_and_read( +InfraredErrorCode infrared_signal_search_by_index_and_read( InfraredSignal* signal, FlipperFormat* ff, size_t index) { - bool success = false; + InfraredErrorCode error = InfraredErrorCodeNone; FuriString* tmp = furi_string_alloc(); - for(uint32_t i = 0; infrared_signal_read_name(ff, tmp); ++i) { + for(uint32_t i = 0;; ++i) { + error = infrared_signal_read_name(ff, tmp); + if(INFRARED_ERROR_PRESENT(error)) { + INFRARED_ERROR_SET_INDEX(error, i); + break; + } + if(i == index) { - success = infrared_signal_read_body(signal, ff); + error = infrared_signal_read_body(signal, ff); + if(INFRARED_ERROR_PRESENT(error)) { + INFRARED_ERROR_SET_INDEX(error, i); + } break; } } furi_string_free(tmp); - return success; + return error; } void infrared_signal_transmit(const InfraredSignal* signal) { diff --git a/applications/main/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h index dcfde6e729a..3fa7768b30f 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/applications/main/infrared/infrared_signal.h @@ -8,6 +8,7 @@ */ #pragma once +#include "infrared_error_code.h" #include #include @@ -136,9 +137,10 @@ const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal) * @param[in,out] signal pointer to the instance to be read into. * @param[in,out] ff pointer to the FlipperFormat file instance to read from. * @param[out] name pointer to the string to hold the signal name. Must be properly allocated. - * @returns true if a signal was successfully read, false otherwise (e.g. no more signals to read). + * @returns InfraredErrorCodeNone if a signal was successfully read, otherwise error code */ -bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name); +InfraredErrorCode + infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name); /** * @brief Read a signal name from a FlipperFormat file. @@ -147,9 +149,9 @@ bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* * * @param[in,out] ff pointer to the FlipperFormat file instance to read from. * @param[out] name pointer to the string to hold the signal name. Must be properly allocated. - * @returns true if a signal name was successfully read, false otherwise (e.g. no more signals to read). + * @returns InfraredErrorCodeNone if a signal name was successfully read, otherwise error code */ -bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name); +InfraredErrorCode infrared_signal_read_name(FlipperFormat* ff, FuriString* name); /** * @brief Read a signal from a FlipperFormat file. @@ -158,9 +160,9 @@ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name); * * @param[in,out] ff pointer to the FlipperFormat file instance to read from. * @param[out] body pointer to the InfraredSignal instance to hold the signal body. Must be properly allocated. - * @returns true if a signal body was successfully read, false otherwise (e.g. syntax error). + * @returns InfraredErrorCodeNone if a signal body was successfully read, otherwise error code. */ -bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff); +InfraredErrorCode infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff); /** * @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance. @@ -171,9 +173,9 @@ bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff); * @param[in,out] signal pointer to the instance to be read into. * @param[in,out] ff pointer to the FlipperFormat file instance to read from. * @param[in] name pointer to a zero-terminated string containing the requested signal name. - * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). + * @returns InfraredErrorCodeNone if a signal was found and successfully read, otherwise error code. */ -bool infrared_signal_search_by_name_and_read( +InfraredErrorCode infrared_signal_search_by_name_and_read( InfraredSignal* signal, FlipperFormat* ff, const char* name); @@ -187,9 +189,9 @@ bool infrared_signal_search_by_name_and_read( * @param[in,out] signal pointer to the instance to be read into. * @param[in,out] ff pointer to the FlipperFormat file instance to read from. * @param[in] index the requested signal index. - * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). + * @returns InfraredErrorCodeNone if a signal was found and successfully read, otherwise error code. */ -bool infrared_signal_search_by_index_and_read( +InfraredErrorCode infrared_signal_search_by_index_and_read( InfraredSignal* signal, FlipperFormat* ff, size_t index); @@ -203,8 +205,10 @@ bool infrared_signal_search_by_index_and_read( * @param[in] signal pointer to the instance holding the signal to be saved. * @param[in,out] ff pointer to the FlipperFormat file instance to write to. * @param[in] name pointer to a zero-terminated string contating the name of the signal. + * @returns InfraredErrorCodeNone if a signal was successfully saved, otherwise error code */ -bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name); +InfraredErrorCode + infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name); /** * @brief Transmit a signal contained in an InfraredSignal instance. diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 68abaf2592a..df958ffba27 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -858,3 +858,41 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 4348 4439 520 1646 520 1646 520 1646 519 1646 520 561 520 561 520 1646 519 561 520 561 520 562 519 562 519 561 520 1646 520 1647 518 563 518 1646 519 562 519 561 520 561 520 562 519 562 519 561 520 1648 517 1647 519 1646 519 1647 519 1646 520 1646 520 1645 520 1647 519 561 520 561 520 562 519 562 519 562 519 562 519 561 520 562 519 561 520 1646 520 562 519 1647 518 1646 520 562 519 560 521 561 520 561 520 561 520 562 519 562 519 560 521 562 519 562 519 560 521 1646 520 1646 520 561 520 562 519 561 520 562 519 561 520 561 520 561 520 561 520 561 520 1647 518 1646 520 562 519 562 519 561 520 1646 520 561 520 5409 4348 4440 519 1645 521 1646 519 1645 521 1645 521 561 520 561 520 1644 522 561 520 561 520 561 520 560 521 562 519 1646 520 1646 520 562 519 1644 522 561 520 561 520 561 520 561 520 561 520 561 520 1646 520 1645 520 1646 520 1645 521 1646 520 1646 520 1644 522 1645 521 560 521 560 521 561 520 561 520 560 521 560 521 561 520 561 520 561 520 1645 521 562 519 1645 521 1645 520 561 520 562 519 561 520 561 520 561 520 560 521 560 521 560 521 560 521 561 520 560 521 1646 520 1646 520 561 520 560 521 559 522 560 521 561 520 561 520 560 521 560 521 560 521 1646 520 1645 520 561 520 560 521 560 521 1645 521 561 520 +# +# Model: Airwell AW-HKD012-N91 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4387 4398 547 1609 547 530 547 1610 547 1611 545 530 547 530 547 1608 547 530 548 530 547 1611 545 532 546 532 547 1609 547 1610 547 531 547 1608 548 530 547 530 548 530 547 1610 546 1609 547 1610 547 1609 547 1609 547 1611 545 1609 548 1610 546 530 547 530 548 529 549 531 547 531 546 531 547 1608 548 1610 547 1608 548 533 545 1608 548 532 546 532 546 1611 545 532 547 532 545 530 548 1608 547 530 549 1608 547 1609 548 5203 4386 4398 547 1609 546 530 547 1609 546 1607 548 531 547 531 547 1609 547 530 548 531 547 1609 547 531 547 531 547 1608 547 1613 544 531 546 1609 547 531 547 531 547 532 546 1609 547 1609 546 1609 547 1609 547 1608 547 1608 548 1608 548 1609 547 530 547 530 547 530 547 532 546 530 547 530 548 1610 546 1608 547 1609 547 530 547 1609 547 530 547 530 548 1609 546 530 548 530 547 532 546 1610 546 531 546 1608 548 1608 548 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4388 4398 547 1608 548 531 546 1610 546 1609 547 530 547 529 548 1608 548 532 547 530 548 1612 544 529 549 530 548 1608 547 1609 547 531 546 1608 548 1607 549 529 549 1608 549 1609 548 1608 548 1608 548 1611 545 1608 548 530 548 1609 547 531 547 530 548 530 548 531 547 529 549 530 548 530 547 531 547 530 548 530 547 529 549 530 548 532 547 530 548 1609 547 1610 547 1608 548 1609 547 1608 548 1608 548 1608 548 1608 548 5203 4388 4396 549 1609 547 529 549 1610 546 1608 548 529 549 530 547 1609 547 530 548 529 549 1608 548 531 547 532 546 1609 547 1609 547 530 548 1609 548 1609 548 529 548 1608 548 1609 548 1609 547 1609 547 1608 548 1609 547 532 546 1608 548 531 548 531 548 530 548 530 548 531 547 530 548 531 548 531 547 530 548 530 548 530 548 531 547 529 549 529 549 1609 548 1608 548 1609 547 1608 548 1608 548 1608 548 1607 549 1607 549 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4384 4400 572 1585 571 505 572 1583 573 1584 572 508 570 503 575 1584 572 505 572 506 572 1583 573 504 573 506 571 1586 570 1585 572 532 546 1586 570 1585 571 506 571 1585 571 1583 573 1586 570 1583 573 1584 572 1589 569 505 572 1585 571 506 571 506 573 506 572 505 573 532 545 504 574 509 570 1611 545 506 572 1582 574 506 572 507 571 507 571 507 570 1584 572 507 571 1587 569 506 572 1584 572 1585 571 1583 573 1612 544 5179 4386 4400 570 1584 572 507 571 1583 572 1585 571 506 572 506 572 1584 572 505 572 504 574 1584 572 507 571 504 574 1583 573 1585 572 507 571 1584 572 1610 545 508 571 1587 569 1583 573 1583 573 1585 571 1585 572 1585 572 505 572 1584 572 505 573 507 572 506 571 504 574 505 573 505 574 508 571 1585 571 507 571 1585 571 506 571 506 572 504 574 505 572 1586 570 507 571 1586 570 505 573 1584 572 1585 571 1587 569 1584 573 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4386 4398 575 1582 574 503 575 1583 573 1582 574 505 573 504 574 1582 574 506 572 508 570 1583 573 504 574 505 573 1583 573 1584 573 505 573 1582 575 1583 574 504 574 1582 574 1583 573 1583 573 1583 573 1585 571 1586 572 504 573 1584 572 504 573 505 573 505 573 505 573 504 573 506 571 1583 574 505 573 1583 573 1583 573 1584 572 1583 573 505 572 505 573 504 574 1583 574 505 573 505 573 504 574 505 572 1584 572 1584 573 5178 4387 4400 571 1583 573 504 574 1584 572 1584 572 507 572 504 574 1582 574 505 572 505 573 1583 573 504 574 504 574 1582 574 1584 573 503 574 1583 573 1582 574 505 573 1583 573 1582 575 1583 573 1610 546 1584 572 1583 573 505 573 1610 546 506 572 505 573 504 574 504 574 505 573 505 573 1584 573 505 573 1582 574 1584 572 1583 573 1583 573 504 574 503 575 504 574 1585 571 507 571 504 573 506 572 505 572 1584 572 1585 571 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4388 4399 547 1608 548 530 548 1610 547 1610 547 529 549 529 548 1608 548 530 548 530 548 1607 549 533 545 531 548 1608 548 1610 546 531 547 1609 547 1608 548 529 549 1609 547 1609 547 1609 547 1609 547 1609 547 1608 548 529 548 1638 519 530 548 530 548 530 548 529 550 528 549 530 548 530 548 1608 548 530 548 1609 548 1610 547 1609 547 531 546 529 549 1608 548 530 548 1609 548 530 548 529 548 530 548 1609 548 1609 548 5205 4387 4398 547 1609 548 531 546 1609 547 1609 547 530 548 531 546 1609 547 531 548 530 573 1583 573 507 571 506 572 1583 573 1582 574 504 574 1581 575 1582 574 506 572 1583 574 1583 573 1583 573 1585 571 1584 572 1585 570 507 571 1582 574 505 574 532 545 505 573 505 572 506 571 505 573 505 573 1584 572 506 572 1583 573 1584 572 1583 573 505 572 504 573 1583 573 505 573 1586 571 506 572 505 573 507 572 1583 573 1584 572 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4388 4399 572 1583 573 532 546 1585 571 1583 574 503 575 505 573 1584 572 504 574 505 573 1584 573 506 572 504 573 1584 573 1584 572 505 574 1611 545 506 573 1583 573 1585 571 1586 545 1609 547 531 547 1611 545 1608 548 1607 549 530 548 529 548 531 548 532 546 1610 546 533 545 530 547 1609 547 1610 547 1609 547 533 545 529 548 530 548 530 547 531 546 530 547 530 548 533 544 1608 548 1608 548 1610 546 1606 550 1609 547 5203 4388 4397 548 1609 547 531 547 1608 548 1608 548 530 548 530 548 1608 548 531 547 531 547 1610 546 531 547 530 548 1609 547 1611 546 532 547 1609 547 531 547 1608 548 1610 546 1609 547 1608 548 530 547 1609 547 1608 548 1609 547 531 546 530 548 530 547 530 547 1608 548 532 547 534 545 1608 548 1608 548 1609 547 530 548 531 547 531 547 532 546 531 546 531 547 532 546 530 548 1608 547 1608 548 1610 546 1608 548 1608 548 \ No newline at end of file diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 4d5868ccea1..9df664a7b88 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1694,69 +1694,69 @@ type: parsed protocol: RC6 address: 00 00 00 00 command: 0C 00 00 00 -# +# name: Mute type: parsed protocol: RC6 address: 00 00 00 00 command: 0D 00 00 00 -# +# name: Vol_up type: parsed protocol: RC6 address: 00 00 00 00 command: 10 00 00 00 -# +# name: Vol_dn type: parsed protocol: RC6 address: 00 00 00 00 command: 11 00 00 00 -# +# name: Ch_next type: parsed protocol: RC6 address: 00 00 00 00 command: 20 00 00 00 -# +# name: Ch_prev type: parsed protocol: RC6 address: 00 00 00 00 command: 21 00 00 00 -# +# # Model TCL 50P715X1 -# +# name: Power type: parsed protocol: RCA address: 0F 00 00 00 command: 54 00 00 00 -# +# name: Mute type: parsed protocol: RCA address: 0F 00 00 00 command: FC 00 00 00 -# +# name: Vol_up type: parsed protocol: RCA address: 0F 00 00 00 command: F4 00 00 00 -# +# name: Vol_dn type: parsed protocol: RCA address: 0F 00 00 00 command: 74 00 00 00 -# +# name: Ch_next type: parsed protocol: RCA address: 0F 00 00 00 command: B4 00 00 00 -# +# name: Ch_prev type: parsed protocol: RCA @@ -1770,31 +1770,31 @@ type: parsed protocol: NECext address: 01 72 00 00 command: 5C A3 00 00 -# +# name: Power type: parsed protocol: NECext address: 01 72 00 00 command: 1E E1 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 01 72 00 00 command: 0A F5 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 01 72 00 00 command: 06 F9 00 00 -# +# name: Ch_next type: parsed protocol: NECext address: 01 72 00 00 command: 48 B7 00 00 -# +# name: Ch_prev type: parsed protocol: NECext @@ -1836,39 +1836,39 @@ type: parsed protocol: NEC address: 04 00 00 00 command: 08 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 04 00 00 00 command: 02 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 04 00 00 00 command: 03 00 00 00 -# +# name: Ch_next type: parsed protocol: NEC address: 04 00 00 00 command: 00 00 00 00 -# +# name: Ch_prev type: parsed protocol: NEC address: 04 00 00 00 command: 01 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 04 00 00 00 command: 09 00 00 00 -# +# # Emerson TV -# +# name: Power type: parsed protocol: NECext @@ -1880,28 +1880,66 @@ type: parsed protocol: NECext address: 84 E0 00 00 command: 50 AF 00 00 -# +# name: Ch_prev type: parsed protocol: NECext address: 84 E0 00 00 command: 51 AE 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 84 E0 00 00 command: 60 9F 00 00 -# +# name: Vol_dn type: parsed protocol: NECext address: 84 E0 00 00 command: 61 9E 00 00 -# +# name: Mute type: parsed protocol: NECext address: 84 E0 00 00 command: 64 9B 00 00 +# +# TCL 75S451 +# +name: Power +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 17 E8 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 20 DF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 0F F0 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 10 EF 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 19 E6 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 33 CC 00 00 diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 9bdcdc4a823..a52f141c47f 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -34,12 +34,12 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) { static int32_t infrared_scene_universal_common_task_callback(void* context) { InfraredApp* infrared = context; - const bool success = infrared_brute_force_calculate_messages(infrared->brute_force); + const InfraredErrorCode error = infrared_brute_force_calculate_messages(infrared->brute_force); view_dispatcher_send_custom_event( infrared->view_dispatcher, infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0)); - return success; + return error; } void infrared_scene_universal_common_on_enter(void* context) { @@ -93,9 +93,9 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases); } } else if(event_type == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); - if(!task_success) { + if(INFRARED_ERROR_PRESENT(task_error)) { scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); } else { view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index 90a2633d338..63d71318c23 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -11,12 +11,12 @@ static int32_t infrared_scene_edit_delete_task_callback(void* context) { InfraredAppState* app_state = &infrared->app_state; const InfraredEditTarget edit_target = app_state->edit_target; - bool success; + InfraredErrorCode error = InfraredErrorCodeNone; if(edit_target == InfraredEditTargetButton) { furi_assert(app_state->current_button_index != InfraredButtonIndexNone); - success = infrared_remote_delete_signal(infrared->remote, app_state->current_button_index); + error = infrared_remote_delete_signal(infrared->remote, app_state->current_button_index); } else if(edit_target == InfraredEditTargetRemote) { - success = infrared_remote_remove(infrared->remote); + error = infrared_remote_remove(infrared->remote); } else { furi_crash(); } @@ -24,7 +24,7 @@ static int32_t infrared_scene_edit_delete_task_callback(void* context) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); - return success; + return error; } void infrared_scene_edit_delete_on_enter(void* context) { @@ -39,11 +39,15 @@ void infrared_scene_edit_delete_on_enter(void* context) { const int32_t current_button_index = infrared->app_state.current_button_index; furi_check(current_button_index != InfraredButtonIndexNone); - if(!infrared_remote_load_signal(remote, infrared->current_signal, current_button_index)) { + InfraredErrorCode error = + infrared_remote_load_signal(remote, infrared->current_signal, current_button_index); + if(INFRARED_ERROR_PRESENT(error)) { + const char* format = + (INFRARED_ERROR_CHECK(error, InfraredErrorCodeSignalRawUnableToReadTooLongData)) ? + "Failed to delete\n\"%s\" is too long.\nTry to edit file from pc" : + "Failed to load\n\"%s\""; infrared_show_error_message( - infrared, - "Failed to load\n\"%s\"", - infrared_remote_get_signal_name(remote, current_button_index)); + infrared, format, infrared_remote_get_signal_name(remote, current_button_index)); scene_manager_previous_scene(infrared->scene_manager); return; } @@ -107,18 +111,30 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) infrared_blocking_task_start(infrared, infrared_scene_edit_delete_task_callback); } else if(event.event == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); InfraredAppState* app_state = &infrared->app_state; - if(task_success) { + if(!INFRARED_ERROR_PRESENT(task_error)) { scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone); } else { - const char* edit_target_text = - app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; - infrared_show_error_message(infrared, "Failed to\ndelete %s", edit_target_text); - - const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; + if(INFRARED_ERROR_CHECK( + task_error, InfraredErrorCodeSignalRawUnableToReadTooLongData)) { + const uint8_t index = INFRARED_ERROR_GET_INDEX(task_error); + const char* format = + "Failed to delete\n\"%s\" is too long.\nTry to edit file from pc"; + infrared_show_error_message( + infrared, + format, + infrared_remote_get_signal_name(infrared->remote, index)); + } else { + const char* edit_target_text = + app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; + infrared_show_error_message( + infrared, "Failed to\ndelete %s", edit_target_text); + } + + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneRemote}; scene_manager_search_and_switch_to_previous_scene_one_of( scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index fcac1fc15e0..1c6415c24cd 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -2,14 +2,14 @@ static int32_t infrared_scene_edit_move_task_callback(void* context) { InfraredApp* infrared = context; - const bool success = infrared_remote_move_signal( + const InfraredErrorCode error = infrared_remote_move_signal( infrared->remote, infrared->app_state.prev_button_index, infrared->app_state.current_button_index); view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); - return success; + return error; } static void infrared_scene_edit_move_button_callback( @@ -51,14 +51,26 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { infrared_blocking_task_start(infrared, infrared_scene_edit_move_task_callback); } else if(event.event == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); - - if(!task_success) { - const char* signal_name = infrared_remote_get_signal_name( - infrared->remote, infrared->app_state.current_button_index); - infrared_show_error_message(infrared, "Failed to move\n\"%s\"", signal_name); - scene_manager_search_and_switch_to_previous_scene( - infrared->scene_manager, InfraredSceneRemoteList); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); + + if(INFRARED_ERROR_PRESENT(task_error)) { + const char* format = "Failed to move\n\"%s\""; + uint8_t signal_index = infrared->app_state.prev_button_index; + + if(INFRARED_ERROR_CHECK( + task_error, InfraredErrorCodeSignalRawUnableToReadTooLongData)) { + signal_index = INFRARED_ERROR_GET_INDEX(task_error); + format = "Failed to move\n\"%s\" is too long.\nTry to edit file from pc"; + } + furi_assert(format); + + const char* signal_name = + infrared_remote_get_signal_name(infrared->remote, signal_index); + infrared_show_error_message(infrared, format, signal_name); + + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneRemote}; + scene_manager_search_and_switch_to_previous_scene_one_of( + infrared->scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } else { view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index a546ad6e50d..ea49080a1f5 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -8,13 +8,13 @@ static int32_t infrared_scene_edit_rename_task_callback(void* context) { InfraredAppState* app_state = &infrared->app_state; const InfraredEditTarget edit_target = app_state->edit_target; - bool success; + InfraredErrorCode error = InfraredErrorCodeNone; if(edit_target == InfraredEditTargetButton) { furi_assert(app_state->current_button_index != InfraredButtonIndexNone); - success = infrared_remote_rename_signal( + error = infrared_remote_rename_signal( infrared->remote, app_state->current_button_index, infrared->text_store[0]); } else if(edit_target == InfraredEditTargetRemote) { - success = infrared_rename_current_remote(infrared, infrared->text_store[0]); + error = infrared_rename_current_remote(infrared, infrared->text_store[0]); } else { furi_crash(); } @@ -22,7 +22,7 @@ static int32_t infrared_scene_edit_rename_task_callback(void* context) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); - return success; + return error; } void infrared_scene_edit_rename_on_enter(void* context) { @@ -39,7 +39,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { furi_check(current_button_index != InfraredButtonIndexNone); enter_name_length = INFRARED_MAX_BUTTON_NAME_LENGTH; - strncpy( + strlcpy( infrared->text_store[0], infrared_remote_get_signal_name(remote, current_button_index), enter_name_length); @@ -47,7 +47,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { } else if(edit_target == InfraredEditTargetRemote) { text_input_set_header_text(text_input, "Name the remote"); enter_name_length = INFRARED_MAX_REMOTE_NAME_LENGTH; - strncpy(infrared->text_store[0], infrared_remote_get_name(remote), enter_name_length); + strlcpy(infrared->text_store[0], infrared_remote_get_name(remote), enter_name_length); FuriString* folder_path; folder_path = furi_string_alloc(); @@ -89,17 +89,30 @@ bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) infrared_blocking_task_start(infrared, infrared_scene_edit_rename_task_callback); } else if(event.event == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); InfraredAppState* app_state = &infrared->app_state; - if(task_success) { + if(!INFRARED_ERROR_PRESENT(task_error)) { scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone); } else { - const char* edit_target_text = - app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; - infrared_show_error_message(infrared, "Failed to\nrename %s", edit_target_text); - scene_manager_search_and_switch_to_previous_scene( - scene_manager, InfraredSceneRemoteList); + bool long_signal = INFRARED_ERROR_CHECK( + task_error, InfraredErrorCodeSignalRawUnableToReadTooLongData); + + const char* format = "Failed to rename\n%s"; + const char* target = infrared->app_state.edit_target == InfraredEditTargetButton ? + "button" : + "file"; + if(long_signal) { + format = "Failed to rename\n\"%s\" is too long.\nTry to edit file from pc"; + target = infrared_remote_get_signal_name( + infrared->remote, INFRARED_ERROR_GET_INDEX(task_error)); + } + + infrared_show_error_message(infrared, format, target); + + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneRemote}; + scene_manager_search_and_switch_to_previous_scene_one_of( + scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } app_state->current_button_index = InfraredButtonIndexNone; diff --git a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c index be46a869168..7ffc9644baf 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c +++ b/applications/main/infrared/scenes/infrared_scene_learn_enter_name.c @@ -41,12 +41,12 @@ bool infrared_scene_learn_enter_name_on_event(void* context, SceneManagerEvent e if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeTextEditDone) { const char* signal_name = infrared->text_store[0]; - const bool success = + const InfraredErrorCode error = infrared->app_state.is_learning_new_remote ? infrared_add_remote_with_button(infrared, signal_name, signal) : infrared_remote_append_signal(infrared->remote, signal, signal_name); - if(success) { + if(!INFRARED_ERROR_PRESENT(error)) { scene_manager_next_scene(scene_manager, InfraredSceneLearnDone); dolphin_deed(DolphinDeedIrSave); } else { diff --git a/applications/main/infrared/scenes/infrared_scene_remote.c b/applications/main/infrared/scenes/infrared_scene_remote.c index 6c1d1ec4e39..6dd7aae8bf8 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote.c +++ b/applications/main/infrared/scenes/infrared_scene_remote.c @@ -85,7 +85,13 @@ bool infrared_scene_remote_on_event(void* context, SceneManagerEvent event) { if(custom_type == InfraredCustomEventTypeTransmitStarted) { furi_assert(button_index >= 0); - infrared_tx_start_button_index(infrared, button_index); + InfraredErrorCode error = infrared_tx_start_button_index(infrared, button_index); + if(INFRARED_ERROR_PRESENT(error)) { + infrared_show_error_message( + infrared, + "Failed to load\n\"%s\"", + infrared_remote_get_signal_name(infrared->remote, button_index)); + } consumed = true; } else if(custom_type == InfraredCustomEventTypeTransmitStopped) { infrared_tx_stop(infrared); diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 9c880ac2fb9..93665769cf5 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -2,11 +2,11 @@ static int32_t infrared_scene_remote_list_task_callback(void* context) { InfraredApp* infrared = context; - const bool success = + const InfraredErrorCode error = infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path)); view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); - return success; + return error; } static void infrared_scene_remote_list_select_and_load(InfraredApp* infrared) { @@ -38,13 +38,19 @@ bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); - if(task_success) { + if(!INFRARED_ERROR_PRESENT(task_error)) { scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote); } else { + bool wrong_file_type = + INFRARED_ERROR_CHECK(task_error, InfraredErrorCodeWrongFileType); + const char* format = wrong_file_type ? + "Library file\n\"%s\" can't be openned as a remote" : + "Failed to load\n\"%s\""; + infrared_show_error_message( - infrared, "Failed to load\n\"%s\"", furi_string_get_cstr(infrared->file_path)); + infrared, format, furi_string_get_cstr(infrared->file_path)); infrared_scene_remote_list_select_and_load(infrared); } } diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 4c263a11752..8f9dc433881 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -11,27 +11,33 @@ typedef enum { static int32_t infrared_scene_rpc_task_callback(void* context) { InfraredApp* infrared = context; - const bool success = + const InfraredErrorCode error = infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path)); view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); - return success; + return error; } void infrared_scene_rpc_on_enter(void* context) { InfraredApp* infrared = context; + scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); +} + +static void infrared_scene_rpc_show(InfraredApp* infrared) { Popup* popup = infrared->popup; popup_set_header(popup, "Infrared", 89, 42, AlignCenter, AlignBottom); popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); + popup_set_text(popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61); - popup_set_context(popup, context); + popup_set_context(popup, infrared); popup_set_callback(popup, infrared_popup_closed_callback); view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); - scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); + scene_manager_set_scene_state( + infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending); notification_message(infrared->notifications, &sequence_display_backlight_on); } @@ -51,25 +57,24 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { } } else if(event.event == InfraredCustomEventTypeTaskFinished) { - const bool task_success = infrared_blocking_task_finalize(infrared); + const InfraredErrorCode task_error = infrared_blocking_task_finalize(infrared); - if(task_success) { + if(!INFRARED_ERROR_PRESENT(task_error)) { const char* remote_name = infrared_remote_get_name(infrared->remote); infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); - } else { - infrared_text_store_set( - infrared, 0, "failed to load\n%s", furi_string_get_cstr(infrared->file_path)); - } + FuriString* str = furi_string_alloc(); + furi_string_printf( + str, "Failed to load\n%s", furi_string_get_cstr(infrared->file_path)); - popup_set_text( - infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); - - rpc_system_app_confirm(infrared->rpc_ctx, task_success); + rpc_system_app_set_error_code(infrared->rpc_ctx, RpcAppSystemErrorCodeParseFile); + rpc_system_app_set_error_text(infrared->rpc_ctx, furi_string_get_cstr(str)); + furi_string_free(str); + } + rpc_system_app_confirm(infrared->rpc_ctx, !INFRARED_ERROR_PRESENT(task_error)); } else if( event.event == InfraredCustomEventTypeRpcButtonPressName || event.event == InfraredCustomEventTypeRpcButtonPressIndex) { @@ -88,10 +93,21 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { TAG, "Sending signal with index \"%ld\"", app_state->current_button_index); } if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { - infrared_tx_start_button_index(infrared, app_state->current_button_index); - scene_manager_set_scene_state( - infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending); - result = true; + InfraredErrorCode error = + infrared_tx_start_button_index(infrared, app_state->current_button_index); + if(!INFRARED_ERROR_PRESENT(error)) { + const char* remote_name = infrared_remote_get_name(infrared->remote); + infrared_text_store_set(infrared, 0, "emulating\n%s", remote_name); + + infrared_scene_rpc_show(infrared); + result = true; + } else { + rpc_system_app_set_error_code( + infrared->rpc_ctx, RpcAppSystemErrorCodeInternalParse); + rpc_system_app_set_error_text( + infrared->rpc_ctx, "Cannot load button data"); + result = false; + } } } rpc_system_app_confirm(infrared->rpc_ctx, result); diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c index 906218d74f0..65ffa1ef658 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_rpc.c @@ -2,23 +2,30 @@ void lfrfid_scene_rpc_on_enter(void* context) { LfRfid* app = context; + app->rpc_state = LfRfidRpcStateIdle; +} + +static void lfrfid_rpc_start_emulation(LfRfid* app) { Popup* popup = app->popup; + lfrfid_text_store_set(app, "emulating\n%s", furi_string_get_cstr(app->file_name)); + popup_set_header(popup, "LF RFID", 89, 42, AlignCenter, AlignBottom); - popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); + popup_set_text(popup, app->text_store, 89, 44, AlignCenter, AlignTop); popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61); view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidViewPopup); - notification_message(app->notifications, &sequence_display_backlight_on); + lfrfid_worker_start_thread(app->lfworker); + lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); - app->rpc_state = LfRfidRpcStateIdle; + notification_message(app->notifications, &sequence_display_backlight_on); + notification_message(app->notifications, &sequence_blink_start_magenta); + app->rpc_state = LfRfidRpcStateEmulating; } bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { LfRfid* app = context; - Popup* popup = app->popup; - UNUSED(event); bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { @@ -34,16 +41,11 @@ bool lfrfid_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool result = false; if(app->rpc_state == LfRfidRpcStateIdle) { if(lfrfid_load_key_data(app, app->file_path, false)) { - lfrfid_worker_start_thread(app->lfworker); - lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); - app->rpc_state = LfRfidRpcStateEmulating; - - lfrfid_text_store_set( - app, "emulating\n%s", furi_string_get_cstr(app->file_name)); - popup_set_text(popup, app->text_store, 89, 44, AlignCenter, AlignTop); - - notification_message(app->notifications, &sequence_blink_start_magenta); + lfrfid_rpc_start_emulation(app); result = true; + } else { + rpc_system_app_set_error_code(app->rpc_ctx, RpcAppSystemErrorCodeParseFile); + rpc_system_app_set_error_text(app->rpc_ctx, "Cannot load key file"); } } rpc_system_app_confirm(app->rpc_ctx, result); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c index 810242fbc6a..6ceb4bce028 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c @@ -1,6 +1,6 @@ #include "iso14443_3a_render.h" -void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size) { +void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* data, size_t size) { for(size_t i = 0; i < size; i++) { furi_string_cat_printf(str, " %02X", data[i]); } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h index 34e347aa35e..b8151556631 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h @@ -11,7 +11,7 @@ void nfc_render_iso14443_3a_info( void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str); -void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size); +void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* data, size_t size); void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 7a51e3d8696..4fece16be5a 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -117,7 +117,7 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { if(!mf_classic_is_card_read(data)) { submenu_add_item( submenu, - "Detect Reader", + "Extract MF Keys", SubmenuIndexDetectReader, nfc_protocol_support_common_submenu_callback, instance); @@ -155,7 +155,7 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { if(!mf_classic_is_card_read(data)) { submenu_add_item( submenu, - "Detect Reader", + "Extract MF Keys", SubmenuIndexDetectReader, nfc_protocol_support_common_submenu_callback, instance); diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c index c4ad67ff8bc..ef83d194279 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c @@ -10,22 +10,29 @@ static void nfc_render_mf_ultralight_pages_count(const MfUltralightData* data, F } void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str) { + MfUltralightConfigPages* config; + bool all_pages = mf_ultralight_is_all_data_read(data); - if(all_pages) { + bool has_config = mf_ultralight_get_config_page(data, &config); + + if(!has_config) { + furi_string_cat_printf(str, "\e#Already Unlocked!"); + } else if(all_pages) { furi_string_cat_printf(str, "\e#All Pages Are Unlocked!"); } else { furi_string_cat_printf(str, "\e#Some Pages Are Locked!"); } - MfUltralightConfigPages* config; - mf_ultralight_get_config_page(data, &config); + if(has_config) { + furi_string_cat_printf(str, "\nPassword: "); + nfc_render_iso14443_3a_format_bytes( + str, config->password.data, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE); - furi_string_cat_printf(str, "\nPassword: "); - nfc_render_iso14443_3a_format_bytes( - str, config->password.data, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE); - - furi_string_cat_printf(str, "\nPACK: "); - nfc_render_iso14443_3a_format_bytes(str, config->pack.data, MF_ULTRALIGHT_AUTH_PACK_SIZE); + furi_string_cat_printf(str, "\nPACK: "); + nfc_render_iso14443_3a_format_bytes(str, config->pack.data, MF_ULTRALIGHT_AUTH_PACK_SIZE); + } else { + furi_string_cat_printf(str, "\nThis card does not support\npassword protection!"); + } nfc_render_mf_ultralight_pages_count(data, str); } diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 630b3beef6f..7a07404fdc1 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -717,6 +717,10 @@ static bool nfc_protocol_support_scene_rpc_on_event(NfcApp* instance, SceneManag if(nfc_load_file(instance, instance->file_path, false)) { nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance); success = true; + } else { + rpc_system_app_set_error_code( + instance->rpc_ctx, RpcAppSystemErrorCodeParseFile); + rpc_system_app_set_error_text(instance->rpc_ctx, "Cannot load key file"); } } rpc_system_app_confirm(instance->rpc_ctx, success); diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index bdf9c0e2f83..5365d6bef68 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -488,7 +488,7 @@ int32_t nfc_app(void* p) { nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen); furi_string_set(nfc->file_path, args); - if(nfc_load_file(nfc, nfc->file_path, false)) { + if(nfc_load_file(nfc, nfc->file_path, true)) { nfc_show_initial_scene_for_device(nfc); } else { view_dispatcher_stop(nfc->view_dispatcher); diff --git a/applications/main/nfc/plugins/supported_cards/itso.c b/applications/main/nfc/plugins/supported_cards/itso.c index 1b61c26e7c7..a9be0a1f931 100644 --- a/applications/main/nfc/plugins/supported_cards/itso.c +++ b/applications/main/nfc/plugins/supported_cards/itso.c @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -72,7 +73,10 @@ static bool itso_parse(const NfcDevice* device, FuriString* parsed_data) { dateBuff[17] = '\0'; // DateStamp is defined in BS EN 1545 - Days passed since 01/01/1997 - uint32_t dateStamp = (int)strtol(datep, NULL, 16); + uint32_t dateStamp; + if(strint_to_uint32(datep, NULL, &dateStamp, 16) != StrintParseNoError) { + return false; + } uint32_t unixTimestamp = dateStamp * 24 * 60 * 60 + 852076800U; furi_string_set(parsed_data, "\e#ITSO Card\n"); diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index 53857b8495e..b981b719a92 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -29,7 +29,11 @@ void nfc_scene_start_on_enter(void* context) { submenu_add_item(submenu, "Read", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc); submenu_add_item( - submenu, "Detect Reader", SubmenuIndexDetectReader, nfc_scene_start_submenu_callback, nfc); + submenu, + "Extract MF Keys", + SubmenuIndexDetectReader, + nfc_scene_start_submenu_callback, + nfc); submenu_add_item(submenu, "Saved", SubmenuIndexSaved, nfc_scene_start_submenu_callback, nfc); submenu_add_item( submenu, "Extra Actions", SubmenuIndexExtraAction, nfc_scene_start_submenu_callback, nfc); diff --git a/applications/main/subghz/helpers/subghz_error_type.h b/applications/main/subghz/helpers/subghz_error_type.h deleted file mode 100644 index 0f86d6ea7d1..00000000000 --- a/applications/main/subghz/helpers/subghz_error_type.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include - -/** SubGhzErrorType */ -typedef enum { - SubGhzErrorTypeNoError = 0, /** There are no errors */ - SubGhzErrorTypeParseFile = - 1, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */ - SubGhzErrorTypeOnlyRX = - 2, /** Transmission on this frequency is blocked by regional settings */ - SubGhzErrorTypeParserOthers = 3, /** Error in protocol parameters description */ -} SubGhzErrorType; diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index f8bf066d549..c1476746d4a 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -8,23 +8,33 @@ typedef enum { void subghz_scene_rpc_on_enter(void* context) { SubGhz* subghz = context; + scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); +} + +static void subghz_format_file_name_tmp(SubGhz* subghz) { + FuriString* file_name; + file_name = furi_string_alloc(); + path_extract_filename(subghz->file_path, file_name, true); + snprintf( + subghz->file_name_tmp, SUBGHZ_MAX_LEN_NAME, "loaded\n%s", furi_string_get_cstr(file_name)); + furi_string_free(file_name); +} + +static void subghz_scene_rpc_emulation_show(SubGhz* subghz) { Popup* popup = subghz->popup; + subghz_format_file_name_tmp(subghz); popup_set_header(popup, "Sub-GHz", 89, 42, AlignCenter, AlignBottom); - popup_set_text(popup, "RPC mode", 89, 44, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 12, &I_RFIDDolphinSend_97x61); + popup_set_text(popup, subghz->file_name_tmp, 89, 44, AlignCenter, AlignTop); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); - scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); - notification_message(subghz->notifications, &sequence_display_backlight_on); } bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; - Popup* popup = subghz->popup; bool consumed = false; SubGhzRpcState state = scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneRpc); @@ -43,13 +53,15 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { switch( subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) { case SubGhzTxRxStartTxStateErrorOnlyRx: - rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeOnlyRX); + rpc_system_app_set_error_code( + subghz->rpc_ctx, RpcAppSystemErrorCodeRegionLock); rpc_system_app_set_error_text( subghz->rpc_ctx, "Transmission on this frequency is restricted in your region"); break; case SubGhzTxRxStartTxStateErrorParserOthers: - rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeParserOthers); + rpc_system_app_set_error_code( + subghz->rpc_ctx, RpcAppSystemErrorCodeInternalParse); rpc_system_app_set_error_text( subghz->rpc_ctx, "Error in protocol parameters description"); break; @@ -57,7 +69,8 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { default: //if(SubGhzTxRxStartTxStateOk) result = true; subghz_blink_start(subghz); - state = SubGhzRpcStateTx; + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateTx); break; } } @@ -69,29 +82,19 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { subghz_blink_stop(subghz); result = true; } - state = SubGhzRpcStateIdle; + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); rpc_system_app_confirm(subghz->rpc_ctx, result); } else if(event.event == SubGhzCustomEventSceneRpcLoad) { bool result = false; if(state == SubGhzRpcStateIdle) { if(subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false)) { + subghz_scene_rpc_emulation_show(subghz); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateLoaded); result = true; - FuriString* file_name; - file_name = furi_string_alloc(); - path_extract_filename(subghz->file_path, file_name, true); - - snprintf( - subghz->file_name_tmp, - SUBGHZ_MAX_LEN_NAME, - "loaded\n%s", - furi_string_get_cstr(file_name)); - popup_set_text(popup, subghz->file_name_tmp, 89, 44, AlignCenter, AlignTop); - - furi_string_free(file_name); } else { - rpc_system_app_set_error_code(subghz->rpc_ctx, SubGhzErrorTypeParseFile); + rpc_system_app_set_error_code(subghz->rpc_ctx, RpcAppSystemErrorCodeParseFile); rpc_system_app_set_error_text(subghz->rpc_ctx, "Cannot parse file"); } } diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 08ba0ba8252..cdf218acae4 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -6,7 +6,7 @@ #include #include -#define MAX_TEXT_INPUT_LEN 22 +#define MAX_TEXT_INPUT_LEN 23 void subghz_scene_save_name_text_input_callback(void* context) { furi_assert(context); @@ -39,7 +39,7 @@ void subghz_scene_save_name_on_enter(void* context) { FuriString* dir_name = furi_string_alloc(); if(!subghz_path_is_file(subghz->file_path)) { - char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0}; + char file_name_buf[SUBGHZ_MAX_LEN_NAME]; name_generator_make_auto(file_name_buf, SUBGHZ_MAX_LEN_NAME, SUBGHZ_APP_FILENAME_PREFIX); @@ -62,7 +62,7 @@ void subghz_scene_save_name_on_enter(void* context) { furi_string_set(subghz->file_path, dir_name); } - strncpy(subghz->file_name_tmp, furi_string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); + strlcpy(subghz->file_name_tmp, furi_string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); text_input_set_header_text(text_input, "Name signal"); text_input_set_result_callback( text_input, diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 4f5c4cb623b..6375f2eee4d 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -3,18 +3,20 @@ #include #include -#include -#include +#include +#include #include #include #include #include -#include #include #include #include +#include +#include + #include "helpers/subghz_chat.h" #include @@ -71,9 +73,8 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { uint32_t frequency = 433920000; if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &frequency, 10) != + StrintParseNoError) { cli_print_usage("subghz tx_carrier", "", furi_string_get_cstr(args)); return; } @@ -115,9 +116,8 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { uint32_t frequency = 433920000; if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &frequency, 10) != + StrintParseNoError) { cli_print_usage("subghz rx_carrier", "", furi_string_get_cstr(args)); return; } @@ -181,23 +181,14 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf( - furi_string_get_cstr(args), - "%lx %lu %lu %lu %lu", - &key, - &frequency, - &te, - &repeat, - &device_ind); - if(ret != 5) { - printf( - "sscanf returned %d, key: %lx, frequency: %lu, te: %lu, repeat: %lu, device: %lu\r\n ", - ret, - key, - frequency, - te, - repeat, - device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &key, 16); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &te, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &repeat, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz tx", "<3 Byte Key: in hex> ", @@ -314,10 +305,11 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); - if(ret != 2) { - printf( - "sscanf returned %d, frequency: %lu device: %lu\r\n", ret, frequency, device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz rx", " ", @@ -401,9 +393,8 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { uint32_t frequency = 433920000; if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &frequency, 10) != + StrintParseNoError) { cli_print_usage("subghz rx", "", furi_string_get_cstr(args)); return; } @@ -622,9 +613,11 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) } if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &repeat, &device_ind); - if(ret != 2) { - printf("sscanf returned %d, repeat: %lu device: %lu\r\n", ret, repeat, device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz tx_from_file:", " ", @@ -936,10 +929,11 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); - if(ret != 2) { - printf("sscanf returned %d, Frequency: %lu\r\n", ret, frequency); - printf("sscanf returned %d, Device: %lu\r\n", ret, device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz chat", " ", diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 9e58f3947dc..08687a4f79f 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -1,7 +1,6 @@ #pragma once #include "helpers/subghz_types.h" -#include "helpers/subghz_error_type.h" #include #include "subghz.h" #include "views/receiver.h" diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index b4eeebbe638..c3539813b1c 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -9,6 +9,7 @@ #include #include #include +#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" @@ -361,9 +362,9 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) { } furi_string_free(light_name); // Read light value from the rest of the string - char* end_ptr; - uint32_t value = strtoul(furi_string_get_cstr(args), &end_ptr, 0); - if(!(value < 256 && *end_ptr == '\0')) { + uint32_t value; + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &value, 0) != StrintParseNoError || + value >= 256) { cli_print_usage("led", " <0-255>", furi_string_get_cstr(args)); return; } diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index dd2ae76a1c5..858efb9fe7a 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -104,7 +104,7 @@ void animation_manager_set_dummy_mode_state(AnimationManager* animation_manager, } } -static void animation_manager_check_blocking_callback(const void* message, void* context) { +static void animation_manager_storage_callback(const void* message, void* context) { const StorageEvent* storage_event = message; switch(storage_event->type) { @@ -123,6 +123,22 @@ static void animation_manager_check_blocking_callback(const void* message, void* } } +static void animation_manager_dolphin_callback(const void* message, void* context) { + const DolphinPubsubEvent* dolphin_event = message; + + switch(*dolphin_event) { + case DolphinPubsubEventUpdate: + furi_assert(context); + AnimationManager* animation_manager = context; + if(animation_manager->check_blocking_callback) { + animation_manager->check_blocking_callback(animation_manager->context); + } + break; + default: + break; + } +} + static void animation_manager_timer_callback(void* context) { furi_assert(context); AnimationManager* animation_manager = context; @@ -300,12 +316,12 @@ AnimationManager* animation_manager_alloc(void) { Storage* storage = furi_record_open(RECORD_STORAGE); animation_manager->pubsub_subscription_storage = furi_pubsub_subscribe( - storage_get_pubsub(storage), animation_manager_check_blocking_callback, animation_manager); + storage_get_pubsub(storage), animation_manager_storage_callback, animation_manager); furi_record_close(RECORD_STORAGE); Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); animation_manager->pubsub_subscription_dolphin = furi_pubsub_subscribe( - dolphin_get_pubsub(dolphin), animation_manager_check_blocking_callback, animation_manager); + dolphin_get_pubsub(dolphin), animation_manager_dolphin_callback, animation_manager); furi_record_close(RECORD_DOLPHIN); animation_manager->blocking_shown_sd_ok = true; diff --git a/applications/services/desktop/animations/animation_storage.c b/applications/services/desktop/animations/animation_storage.c index 9ee85727ba1..a055257765a 100644 --- a/applications/services/desktop/animations/animation_storage.c +++ b/applications/services/desktop/animations/animation_storage.c @@ -52,8 +52,7 @@ static bool animation_storage_load_single_manifest_info( if(furi_string_cmp_str(read_string, name)) break; flipper_format_set_strict_mode(file, true); - manifest_info->name = malloc(furi_string_size(read_string) + 1); - strcpy((char*)manifest_info->name, furi_string_get_cstr(read_string)); + manifest_info->name = strdup(furi_string_get_cstr(read_string)); if(!flipper_format_read_uint32(file, "Min butthurt", &u32value, 1)) break; manifest_info->min_butthurt = u32value; @@ -105,9 +104,7 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis storage_animation->manifest_info.name = NULL; if(!flipper_format_read_string(file, "Name", read_string)) break; - storage_animation->manifest_info.name = malloc(furi_string_size(read_string) + 1); - strcpy( - (char*)storage_animation->manifest_info.name, furi_string_get_cstr(read_string)); + storage_animation->manifest_info.name = strdup(furi_string_get_cstr(read_string)); if(!flipper_format_read_uint32(file, "Min butthurt", &u32value, 1)) break; storage_animation->manifest_info.min_butthurt = u32value; @@ -401,8 +398,7 @@ static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFo furi_string_replace_all(str, "\\n", "\n"); - FURI_CONST_ASSIGN_PTR(bubble->bubble.text, malloc(furi_string_size(str) + 1)); - strcpy((char*)bubble->bubble.text, furi_string_get_cstr(str)); + FURI_CONST_ASSIGN_PTR(bubble->bubble.text, strdup(furi_string_get_cstr(str))); if(!flipper_format_read_string(ff, "AlignH", str)) break; if(!animation_storage_cast_align(str, (Align*)&bubble->bubble.align_h)) break; diff --git a/applications/services/desktop/helpers/pin_code.c b/applications/services/desktop/helpers/pin_code.c index d1a37ed24c6..82e117a53d0 100644 --- a/applications/services/desktop/helpers/pin_code.c +++ b/applications/services/desktop/helpers/pin_code.c @@ -58,7 +58,8 @@ static uint32_t desktop_pin_code_pack(const DesktopPinCode* pin_code) { } bool desktop_pin_code_is_set(void) { - return furi_hal_rtc_get_pin_value() >> DESKTOP_PIN_CODE_LENGTH_OFFSET; + uint8_t length = furi_hal_rtc_get_pin_value() >> DESKTOP_PIN_CODE_LENGTH_OFFSET; + return length >= DESKTOP_PIN_CODE_MIN_LEN && length <= DESKTOP_PIN_CODE_MAX_LEN; } void desktop_pin_code_set(const DesktopPinCode* pin_code) { diff --git a/applications/services/desktop/helpers/pin_code.h b/applications/services/desktop/helpers/pin_code.h index 848c915b6ca..d0695794bf6 100644 --- a/applications/services/desktop/helpers/pin_code.h +++ b/applications/services/desktop/helpers/pin_code.h @@ -3,6 +3,7 @@ #include #include +#define DESKTOP_PIN_CODE_MIN_LEN (4) #define DESKTOP_PIN_CODE_MAX_LEN (10) typedef struct { diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c index c89a143c876..3c18f166c71 100644 --- a/applications/services/desktop/views/desktop_view_pin_input.c +++ b/applications/services/desktop/views/desktop_view_pin_input.c @@ -13,7 +13,7 @@ #define DEFAULT_PIN_X 64 #define DEFAULT_PIN_Y 32 -#define MIN_PIN_LENGTH 4 +#define MIN_PIN_LENGTH DESKTOP_PIN_CODE_MIN_LEN #define MAX_PIN_LENGTH DESKTOP_PIN_CODE_MAX_LEN struct DesktopViewPinInput { @@ -103,7 +103,7 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu furi_assert(canvas); furi_assert(model); - uint8_t draw_pin_size = MAX(4, model->pin.length + 1); + uint8_t draw_pin_size = MAX(MIN_PIN_LENGTH, model->pin.length + 1); if(model->locked_input || (model->pin.length == MAX_PIN_LENGTH)) { draw_pin_size = model->pin.length; } diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index dd2ecd2ba15..5d8dc61cb9f 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -47,6 +47,26 @@ void dolphin_deed(DolphinDeed deed) { furi_record_close(RECORD_DOLPHIN); } +void dolphin_get_settings(Dolphin* dolphin, DolphinSettings* settings) { + furi_check(dolphin); + furi_check(settings); + + DolphinEvent event; + event.type = DolphinEventTypeSettingsGet; + event.settings = settings; + dolphin_event_send_wait(dolphin, &event); +} + +void dolphin_set_settings(Dolphin* dolphin, DolphinSettings* settings) { + furi_check(dolphin); + furi_check(settings); + + DolphinEvent event; + event.type = DolphinEventTypeSettingsSet; + event.settings = settings; + dolphin_event_send_wait(dolphin, &event); +} + DolphinStats dolphin_stats(Dolphin* dolphin) { furi_check(dolphin); @@ -204,14 +224,16 @@ static bool dolphin_process_event(FuriEventLoopObject* object, void* context) { if(event.type == DolphinEventTypeDeed) { dolphin_state_on_deed(dolphin->state, event.deed); - DolphinPubsubEvent event = DolphinPubsubEventUpdate; - furi_pubsub_publish(dolphin->pubsub, &event); + DolphinPubsubEvent pubsub_event = DolphinPubsubEventUpdate; + furi_pubsub_publish(dolphin->pubsub, &pubsub_event); furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS); furi_event_loop_timer_start(dolphin->flush_timer, FLUSH_TIMEOUT_TICKS); } else if(event.type == DolphinEventTypeStats) { event.stats->icounter = dolphin->state->data.icounter; - event.stats->butthurt = dolphin->state->data.butthurt; + event.stats->butthurt = (dolphin->state->data.flags & DolphinFlagHappyMode) ? + 0 : + dolphin->state->data.butthurt; event.stats->timestamp = dolphin->state->data.timestamp; event.stats->level = dolphin_get_level(dolphin->state->data.icounter); event.stats->level_up_is_pending = @@ -228,6 +250,15 @@ static bool dolphin_process_event(FuriEventLoopObject* object, void* context) { dolphin_state_load(dolphin->state); furi_event_loop_timer_start(dolphin->butthurt_timer, BUTTHURT_INCREASE_PERIOD_TICKS); + } else if(event.type == DolphinEventTypeSettingsGet) { + event.settings->happy_mode = dolphin->state->data.flags & DolphinFlagHappyMode; + + } else if(event.type == DolphinEventTypeSettingsSet) { + dolphin->state->data.flags &= ~DolphinFlagHappyMode; + if(event.settings->happy_mode) dolphin->state->data.flags |= DolphinFlagHappyMode; + dolphin->state->dirty = true; + dolphin_state_save(dolphin->state); + } else { furi_crash(); } diff --git a/applications/services/dolphin/dolphin.h b/applications/services/dolphin/dolphin.h index 01da7f3f239..95f851a51be 100644 --- a/applications/services/dolphin/dolphin.h +++ b/applications/services/dolphin/dolphin.h @@ -21,6 +21,10 @@ typedef struct { bool level_up_is_pending; } DolphinStats; +typedef struct { + bool happy_mode; +} DolphinSettings; + typedef enum { DolphinPubsubEventUpdate, } DolphinPubsubEvent; @@ -31,6 +35,10 @@ typedef enum { */ void dolphin_deed(DolphinDeed deed); +void dolphin_get_settings(Dolphin* dolphin, DolphinSettings* settings); + +void dolphin_set_settings(Dolphin* dolphin, DolphinSettings* settings); + /** Retrieve dolphin stats * Thread safe, blocking */ diff --git a/applications/services/dolphin/dolphin_i.h b/applications/services/dolphin/dolphin_i.h index 6a6b3dfd814..8e8d40825dd 100644 --- a/applications/services/dolphin/dolphin_i.h +++ b/applications/services/dolphin/dolphin_i.h @@ -13,6 +13,8 @@ typedef enum { DolphinEventTypeFlush, DolphinEventTypeLevel, DolphinEventTypeReloadState, + DolphinEventTypeSettingsGet, + DolphinEventTypeSettingsSet, } DolphinEventType; typedef struct { @@ -21,6 +23,7 @@ typedef struct { union { DolphinDeed deed; DolphinStats* stats; + DolphinSettings* settings; }; } DolphinEvent; diff --git a/applications/services/dolphin/helpers/dolphin_state.h b/applications/services/dolphin/helpers/dolphin_state.h index bdbd98d4737..23a7d7b5f53 100644 --- a/applications/services/dolphin/helpers/dolphin_state.h +++ b/applications/services/dolphin/helpers/dolphin_state.h @@ -5,6 +5,10 @@ #include "dolphin_deed.h" +typedef enum { + DolphinFlagHappyMode = 1, +} DolphinFlags; + typedef struct DolphinState DolphinState; typedef struct { uint8_t icounter_daily_limit[DolphinAppMAX]; diff --git a/applications/services/expansion/expansion_protocol.h b/applications/services/expansion/expansion_protocol.h index 6ed818f82dd..a8d682330e9 100644 --- a/applications/services/expansion/expansion_protocol.h +++ b/applications/services/expansion/expansion_protocol.h @@ -64,8 +64,28 @@ typedef enum { * @brief Enumeration of suported control commands. */ typedef enum { - ExpansionFrameControlCommandStartRpc = 0x00, /**< Start an RPC session. */ - ExpansionFrameControlCommandStopRpc = 0x01, /**< Stop an open RPC session. */ + /** @brief Start an RPC session. + * + * Must only be used while the RPC session is NOT active. + */ + ExpansionFrameControlCommandStartRpc = 0x00, + /** @brief Stop an open RPC session. + * + * Must only be used while the RPC session IS active. + */ + ExpansionFrameControlCommandStopRpc = 0x01, + /** @brief Enable OTG (5V) on external GPIO. + * + * Must only be used while the RPC session is NOT active, + * otherwise OTG is to be controlled via RPC messages. + */ + ExpansionFrameControlCommandEnableOtg = 0x02, + /** @brief Disable OTG (5V) on external GPIO. + * + * Must only be used while the RPC session is NOT active, + * otherwise OTG is to be controlled via RPC messages. + */ + ExpansionFrameControlCommandDisableOtg = 0x03, } ExpansionFrameControlCommand; #pragma pack(push, 1) diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index 449d02cffc9..c05b9cc8547 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -245,9 +245,18 @@ static bool expansion_worker_handle_state_connected( do { if(rx_frame->header.type == ExpansionFrameTypeControl) { - if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break; - instance->state = ExpansionWorkerStateRpcActive; - if(!expansion_worker_rpc_session_open(instance)) break; + const uint8_t command = rx_frame->content.control.command; + if(command == ExpansionFrameControlCommandStartRpc) { + if(!expansion_worker_rpc_session_open(instance)) break; + instance->state = ExpansionWorkerStateRpcActive; + } else if(command == ExpansionFrameControlCommandEnableOtg) { + furi_hal_power_enable_otg(); + } else if(command == ExpansionFrameControlCommandDisableOtg) { + furi_hal_power_disable_otg(); + } else { + break; + } + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { @@ -279,9 +288,14 @@ static bool expansion_worker_handle_state_rpc_active( if(size_consumed != rx_frame->content.data.size) break; } else if(rx_frame->header.type == ExpansionFrameTypeControl) { - if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break; - instance->state = ExpansionWorkerStateConnected; - expansion_worker_rpc_session_close(instance); + const uint8_t command = rx_frame->content.control.command; + if(command == ExpansionFrameControlCommandStopRpc) { + instance->state = ExpansionWorkerStateConnected; + expansion_worker_rpc_session_close(instance); + } else { + break; + } + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; } else if(rx_frame->header.type == ExpansionFrameTypeStatus) { diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 5b38c5c7916..3e12c09cca0 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -185,6 +185,70 @@ void elements_button_right(Canvas* canvas, const char* str) { canvas_invert_color(canvas); } +void elements_button_up(Canvas* canvas, const char* str) { + furi_check(canvas); + + const Icon* icon = &I_ButtonUp_7x4; + + const size_t button_height = 12; + const size_t vertical_offset = 3; + const size_t horizontal_offset = 3; + const size_t string_width = canvas_string_width(canvas, str); + const int32_t icon_h_offset = 3; + const int32_t icon_width_with_offset = icon_get_width(icon) + icon_h_offset; + const int32_t icon_v_offset = icon_get_height(icon) + (int32_t)vertical_offset; + const size_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const int32_t x = 0; + const int32_t y = 0 + button_height; + + int32_t line_x = x + button_width; + int32_t line_y = y - button_height; + + canvas_draw_box(canvas, x, line_y, button_width, button_height); + canvas_draw_line(canvas, line_x + 0, line_y, line_x + 0, y - 1); + canvas_draw_line(canvas, line_x + 1, line_y, line_x + 1, y - 2); + canvas_draw_line(canvas, line_x + 2, line_y, line_x + 2, y - 3); + + canvas_invert_color(canvas); + canvas_draw_icon(canvas, x + horizontal_offset, y - icon_v_offset, icon); + canvas_draw_str( + canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); + canvas_invert_color(canvas); +} + +void elements_button_down(Canvas* canvas, const char* str) { + furi_check(canvas); + + const Icon* icon = &I_ButtonDown_7x4; + + const size_t button_height = 12; + const size_t vertical_offset = 3; + const size_t horizontal_offset = 3; + const size_t string_width = canvas_string_width(canvas, str); + const int32_t icon_h_offset = 3; + const int32_t icon_width_with_offset = icon_get_width(icon) + icon_h_offset; + const int32_t icon_v_offset = icon_get_height(icon) + vertical_offset + 1; + const size_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const int32_t x = canvas_width(canvas); + const int32_t y = button_height; + + int32_t line_x = x - button_width; + int32_t line_y = y - button_height; + + canvas_draw_box(canvas, line_x, line_y, button_width, button_height); + canvas_draw_line(canvas, line_x - 1, line_y, line_x - 1, y - 1); + canvas_draw_line(canvas, line_x - 2, line_y, line_x - 2, y - 2); + canvas_draw_line(canvas, line_x - 3, line_y, line_x - 3, y - 3); + + canvas_invert_color(canvas); + canvas_draw_str(canvas, x - button_width + horizontal_offset, y - vertical_offset, str); + canvas_draw_icon( + canvas, x - horizontal_offset - icon_get_width(icon), y - icon_v_offset, icon); + canvas_invert_color(canvas); +} + void elements_button_center(Canvas* canvas, const char* str) { furi_check(canvas); diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 88a00481514..0ec0f86cb7b 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -96,6 +96,28 @@ void elements_button_left(Canvas* canvas, const char* str); */ void elements_button_right(Canvas* canvas, const char* str); +/** + * @brief This function draws a button in the top left corner of the canvas with icon and string. + * + * The design and layout of the button is defined within this function. + * + * @param[in] canvas This is a pointer to the @c Canvas structure where the button will be drawn. + * @param[in] str This is a pointer to the character string that will be drawn within the button. + * + */ +void elements_button_up(Canvas* canvas, const char* str); + +/** + * @brief This function draws a button in the top right corner of the canvas with icon and string. + * + * The design and layout of the button is defined within this function. + * + * @param[in] canvas This is a pointer to the @c Canvas structure where the button will be drawn. + * @param[in] str This is a pointer to the character string that will be drawn within the button. + * + */ +void elements_button_down(Canvas* canvas, const char* str); + /** Draw button in center * * @param canvas Canvas instance diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index 6e85401dabe..be94ed9ab9b 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -33,7 +33,7 @@ typedef struct { static const uint8_t keyboard_origin_x = 7; static const uint8_t keyboard_origin_y = 31; -static const uint8_t keyboard_row_count = 2; +static const int8_t keyboard_row_count = 2; static const uint8_t enter_symbol = '\r'; static const uint8_t backspace_symbol = '\b'; static const uint8_t max_drawable_bytes = 8; @@ -649,11 +649,11 @@ static void byte_input_view_draw_callback(Canvas* canvas, void* _model) { } canvas_set_font(canvas, FontKeyboard); // Draw keyboard - for(uint8_t row = 0; row < keyboard_row_count; row++) { + for(int8_t row = 0; row < keyboard_row_count; row++) { const uint8_t column_count = byte_input_get_row_size(row); const ByteInputKey* keys = byte_input_get_row(row); - for(size_t column = 0; column < column_count; column++) { + for(uint8_t column = 0; column < column_count; column++) { if(keys[column].value == enter_symbol) { canvas_set_color(canvas, ColorBlack); if(model->selected_row == row && model->selected_column == column) { diff --git a/applications/services/gui/modules/dialog_ex.c b/applications/services/gui/modules/dialog_ex.c index 7171f6892b8..d27de124206 100644 --- a/applications/services/gui/modules/dialog_ex.c +++ b/applications/services/gui/modules/dialog_ex.c @@ -10,7 +10,7 @@ struct DialogEx { }; typedef struct { - const char* text; + FuriString* text; uint8_t x; uint8_t y; Align horizontal; @@ -28,16 +28,15 @@ typedef struct { TextElement text; IconElement icon; - const char* left_text; - const char* center_text; - const char* right_text; + FuriString* left_text; + FuriString* center_text; + FuriString* right_text; } DialogExModel; static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { DialogExModel* model = _model; // Prepare canvas - canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); if(model->icon.icon != NULL) { @@ -46,94 +45,94 @@ static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { // Draw header canvas_set_font(canvas, FontPrimary); - if(model->header.text != NULL) { + if(furi_string_size(model->header.text)) { elements_multiline_text_aligned( canvas, model->header.x, model->header.y, model->header.horizontal, model->header.vertical, - model->header.text); + furi_string_get_cstr(model->header.text)); } // Draw text canvas_set_font(canvas, FontSecondary); - if(model->text.text != NULL) { + if(furi_string_size(model->text.text)) { elements_multiline_text_aligned( canvas, model->text.x, model->text.y, model->text.horizontal, model->text.vertical, - model->text.text); + furi_string_get_cstr(model->text.text)); } // Draw buttons - if(model->left_text != NULL) { - elements_button_left(canvas, model->left_text); + if(furi_string_size(model->left_text)) { + elements_button_left(canvas, furi_string_get_cstr(model->left_text)); } - if(model->center_text != NULL) { - elements_button_center(canvas, model->center_text); + if(furi_string_size(model->center_text)) { + elements_button_center(canvas, furi_string_get_cstr(model->center_text)); } - if(model->right_text != NULL) { - elements_button_right(canvas, model->right_text); + if(furi_string_size(model->right_text)) { + elements_button_right(canvas, furi_string_get_cstr(model->right_text)); } } static bool dialog_ex_view_input_callback(InputEvent* event, void* context) { DialogEx* dialog_ex = context; bool consumed = false; - const char* left_text = NULL; - const char* center_text = NULL; - const char* right_text = NULL; + bool left_text_present = false; + bool center_text_present = false; + bool right_text_present = false; with_view_model( dialog_ex->view, DialogExModel * model, { - left_text = model->left_text; - center_text = model->center_text; - right_text = model->right_text; + left_text_present = furi_string_size(model->left_text); + center_text_present = furi_string_size(model->center_text); + right_text_present = furi_string_size(model->right_text); }, - true); + false); if(dialog_ex->callback) { if(event->type == InputTypeShort) { - if(event->key == InputKeyLeft && left_text != NULL) { + if(event->key == InputKeyLeft && left_text_present) { dialog_ex->callback(DialogExResultLeft, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyOk && center_text != NULL) { + } else if(event->key == InputKeyOk && center_text_present) { dialog_ex->callback(DialogExResultCenter, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyRight && right_text != NULL) { + } else if(event->key == InputKeyRight && right_text_present) { dialog_ex->callback(DialogExResultRight, dialog_ex->context); consumed = true; } } if(event->type == InputTypePress && dialog_ex->enable_extended_events) { - if(event->key == InputKeyLeft && left_text != NULL) { + if(event->key == InputKeyLeft && left_text_present) { dialog_ex->callback(DialogExPressLeft, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyOk && center_text != NULL) { + } else if(event->key == InputKeyOk && center_text_present) { dialog_ex->callback(DialogExPressCenter, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyRight && right_text != NULL) { + } else if(event->key == InputKeyRight && right_text_present) { dialog_ex->callback(DialogExPressRight, dialog_ex->context); consumed = true; } } if(event->type == InputTypeRelease && dialog_ex->enable_extended_events) { - if(event->key == InputKeyLeft && left_text != NULL) { + if(event->key == InputKeyLeft && left_text_present) { dialog_ex->callback(DialogExReleaseLeft, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyOk && center_text != NULL) { + } else if(event->key == InputKeyOk && center_text_present) { dialog_ex->callback(DialogExReleaseCenter, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyRight && right_text != NULL) { + } else if(event->key == InputKeyRight && right_text_present) { dialog_ex->callback(DialogExReleaseRight, dialog_ex->context); consumed = true; } @@ -154,13 +153,13 @@ DialogEx* dialog_ex_alloc(void) { dialog_ex->view, DialogExModel * model, { - model->header.text = NULL; + model->header.text = furi_string_alloc(); model->header.x = 0; model->header.y = 0; model->header.horizontal = AlignLeft; model->header.vertical = AlignBottom; - model->text.text = NULL; + model->text.text = furi_string_alloc(); model->text.x = 0; model->text.y = 0; model->text.horizontal = AlignLeft; @@ -170,17 +169,28 @@ DialogEx* dialog_ex_alloc(void) { model->icon.y = 0; model->icon.icon = NULL; - model->left_text = NULL; - model->center_text = NULL; - model->right_text = NULL; + model->left_text = furi_string_alloc(); + model->center_text = furi_string_alloc(); + model->right_text = furi_string_alloc(); }, - true); + false); dialog_ex->enable_extended_events = false; return dialog_ex; } void dialog_ex_free(DialogEx* dialog_ex) { furi_check(dialog_ex); + with_view_model( + dialog_ex->view, + DialogExModel * model, + { + furi_string_free(model->header.text); + furi_string_free(model->text.text); + furi_string_free(model->left_text); + furi_string_free(model->center_text); + furi_string_free(model->right_text); + }, + false); view_free(dialog_ex->view); free(dialog_ex); } @@ -212,7 +222,7 @@ void dialog_ex_set_header( dialog_ex->view, DialogExModel * model, { - model->header.text = text; + furi_string_set(model->header.text, text ? text : ""); model->header.x = x; model->header.y = y; model->header.horizontal = horizontal; @@ -233,7 +243,7 @@ void dialog_ex_set_text( dialog_ex->view, DialogExModel * model, { - model->text.text = text; + furi_string_set(model->text.text, text ? text : ""); model->text.x = x; model->text.y = y; model->text.horizontal = horizontal; @@ -257,34 +267,44 @@ void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* i void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text) { furi_check(dialog_ex); - with_view_model(dialog_ex->view, DialogExModel * model, { model->left_text = text; }, true); + with_view_model( + dialog_ex->view, + DialogExModel * model, + { furi_string_set(model->left_text, text ? text : ""); }, + true); } void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text) { furi_check(dialog_ex); - with_view_model(dialog_ex->view, DialogExModel * model, { model->center_text = text; }, true); + with_view_model( + dialog_ex->view, + DialogExModel * model, + { furi_string_set(model->center_text, text ? text : ""); }, + true); } void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text) { furi_check(dialog_ex); - with_view_model(dialog_ex->view, DialogExModel * model, { model->right_text = text; }, true); + with_view_model( + dialog_ex->view, + DialogExModel * model, + { furi_string_set(model->right_text, text ? text : ""); }, + true); } void dialog_ex_reset(DialogEx* dialog_ex) { furi_check(dialog_ex); - TextElement clean_text_el = { - .text = NULL, .x = 0, .y = 0, .horizontal = AlignLeft, .vertical = AlignLeft}; - IconElement clean_icon_el = {.icon = NULL, .x = 0, .y = 0}; with_view_model( dialog_ex->view, DialogExModel * model, { - model->header = clean_text_el; - model->text = clean_text_el; - model->icon = clean_icon_el; - model->left_text = NULL; - model->center_text = NULL; - model->right_text = NULL; + model->icon.icon = NULL; + furi_string_reset(model->header.text); + furi_string_reset(model->text.text); + + furi_string_reset(model->left_text); + furi_string_reset(model->center_text); + furi_string_reset(model->right_text); }, true); dialog_ex->context = NULL; diff --git a/applications/services/gui/modules/number_input.c b/applications/services/gui/modules/number_input.c index 777e5574729..5a5e56c1ce0 100644 --- a/applications/services/gui/modules/number_input.c +++ b/applications/services/gui/modules/number_input.c @@ -3,6 +3,7 @@ #include #include #include +#include struct NumberInput { View* view; @@ -163,7 +164,11 @@ static void number_input_handle_right(NumberInputModel* model) { } static bool is_number_too_large(NumberInputModel* model) { - int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10); + int64_t value; + if(strint_to_int64(furi_string_get_cstr(model->text_buffer), NULL, &value, 10) != + StrintParseNoError) { + return true; + } if(value > (int64_t)model->max_value) { return true; } @@ -171,7 +176,11 @@ static bool is_number_too_large(NumberInputModel* model) { } static bool is_number_too_small(NumberInputModel* model) { - int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10); + int64_t value; + if(strint_to_int64(furi_string_get_cstr(model->text_buffer), NULL, &value, 10) != + StrintParseNoError) { + return true; + } if(value < (int64_t)model->min_value) { return true; } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 7e42a4488e0..b76b38c25c9 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -94,10 +94,20 @@ static void loader_show_gui_error( if(status.value == LoaderStatusErrorUnknownApp && loader_find_external_application_by_name(name) != NULL) { // Special case for external apps - dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop); + const char* header = NULL; + const char* text = NULL; + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_sd_status(storage) == FSE_OK) { + header = "Update needed"; + text = "Update firmware\nto run this app"; + } else { + header = "SD card needed"; + text = "Install SD card\nto run this app"; + } + furi_record_close(RECORD_STORAGE); + dialog_message_set_header(message, header, 64, 3, AlignCenter, AlignTop); dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); - dialog_message_set_text( - message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop); + dialog_message_set_text(message, text, 3, 26, AlignLeft, AlignTop); dialog_message_show(dialogs, message); } else if(status.value == LoaderStatusErrorUnknownApp) { loader_dialog_prepare_and_show(dialogs, &err_app_not_found); @@ -707,7 +717,7 @@ static bool loader_do_signal(Loader* loader, uint32_t signal, void* arg) { static bool loader_do_get_application_name(Loader* loader, FuriString* name) { if(loader_is_application_running(loader)) { - furi_string_set(name, furi_thread_get_name(loader->app.thread)); + furi_string_set(name, furi_thread_get_name(furi_thread_get_id(loader->app.thread))); return true; } diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index a0254f0d0c5..f3ea30df2ab 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -4,6 +4,7 @@ #include #include #include +#include #include static void loader_cli_print_usage(void) { @@ -89,18 +90,22 @@ static void loader_cli_close(Loader* loader) { static void loader_cli_signal(FuriString* args, Loader* loader) { uint32_t signal; - void* arg = NULL; + uint32_t arg = 0; + StrintParseError parse_err = 0; + char* args_cstr = (char*)furi_string_get_cstr(args); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &signal, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &arg, 16); - if(!sscanf(furi_string_get_cstr(args), "%lu %p", &signal, &arg)) { + if(parse_err) { printf("Signal must be a decimal number\r\n"); } else if(!loader_is_locked(loader)) { printf("No application is running\r\n"); } else { - const bool is_handled = loader_signal(loader, signal, arg); + const bool is_handled = loader_signal(loader, signal, (void*)arg); printf( "Signal %lu with argument 0x%p was %s\r\n", signal, - arg, + (void*)arg, is_handled ? "handled" : "ignored"); } } diff --git a/applications/services/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h index 4ee5a24d379..aa6fd81cc81 100644 --- a/applications/services/rpc/rpc_app.h +++ b/applications/services/rpc/rpc_app.h @@ -13,6 +13,7 @@ #pragma once #include "rpc.h" +#include "rpc_app_error_codes.h" #ifdef __cplusplus extern "C" { diff --git a/applications/services/rpc/rpc_app_error_codes.h b/applications/services/rpc/rpc_app_error_codes.h new file mode 100644 index 00000000000..fc0edd4d2d1 --- /dev/null +++ b/applications/services/rpc/rpc_app_error_codes.h @@ -0,0 +1,11 @@ +#pragma once + +/** + * @brief Enumeration of possible error codes for application which can be started through rpc + */ +typedef enum { + RpcAppSystemErrorCodeNone, /** There are no errors */ + RpcAppSystemErrorCodeParseFile, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */ + RpcAppSystemErrorCodeRegionLock, /** Requested function is blocked by regional settings */ + RpcAppSystemErrorCodeInternalParse, /** Error in protocol parameters description, or some data in opened file are unsupported */ +} RpcAppSystemErrorCode; diff --git a/applications/services/rpc/rpc_gpio.c b/applications/services/rpc/rpc_gpio.c index 09e7385052e..40fc898a09d 100644 --- a/applications/services/rpc/rpc_gpio.c +++ b/applications/services/rpc/rpc_gpio.c @@ -2,6 +2,7 @@ #include "rpc_i.h" #include "gpio.pb.h" #include +#include #include static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) { @@ -188,6 +189,44 @@ void rpc_system_gpio_set_input_pull(const PB_Main* request, void* context) { free(response); } +void rpc_system_gpio_get_otg_mode(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_get_otg_mode_tag); + + RpcSession* session = context; + + const bool otg_enabled = furi_hal_power_is_otg_enabled(); + + PB_Main* response = malloc(sizeof(PB_Main)); + response->command_id = request->command_id; + response->which_content = PB_Main_gpio_get_otg_mode_response_tag; + response->content.gpio_get_otg_mode_response.mode = otg_enabled ? PB_Gpio_GpioOtgMode_ON : + PB_Gpio_GpioOtgMode_OFF; + + rpc_send_and_release(session, response); + + free(response); +} + +void rpc_system_gpio_set_otg_mode(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_set_otg_mode_tag); + + RpcSession* session = context; + + const PB_Gpio_GpioOtgMode mode = request->content.gpio_set_otg_mode.mode; + + if(mode == PB_Gpio_GpioOtgMode_OFF) { + furi_hal_power_disable_otg(); + } else { + furi_hal_power_enable_otg(); + } + + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); +} + void* rpc_system_gpio_alloc(RpcSession* session) { furi_assert(session); @@ -212,5 +251,11 @@ void* rpc_system_gpio_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_gpio_set_input_pull; rpc_add_handler(session, PB_Main_gpio_set_input_pull_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_gpio_get_otg_mode; + rpc_add_handler(session, PB_Main_gpio_get_otg_mode_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_gpio_set_otg_mode; + rpc_add_handler(session, PB_Main_gpio_set_otg_mode_tag, &rpc_handler); + return NULL; } diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 89991aa86c7..163535f9a6c 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include @@ -226,9 +226,7 @@ static void rpc_system_storage_list_root(const PB_Main* request, void* context) response.content.storage_list_response.file[i].data = NULL; response.content.storage_list_response.file[i].size = 0; response.content.storage_list_response.file[i].type = PB_Storage_File_FileType_DIR; - char* str = malloc(strlen(hard_coded_dirs[i]) + 1); - strcpy(str, hard_coded_dirs[i]); - response.content.storage_list_response.file[i].name = str; + response.content.storage_list_response.file[i].name = strdup(hard_coded_dirs[i]); } rpc_send_and_release(session, &response); @@ -656,7 +654,7 @@ static void rpc_system_storage_backup_create_process(const PB_Main* request, voi rpc_system_storage_reset_state(rpc_storage, session, true); - bool backup_ok = lfs_backup_create( + bool backup_ok = int_backup_create( rpc_storage->api, request->content.storage_backup_create_request.archive_path); rpc_send_and_release_empty( @@ -676,7 +674,7 @@ static void rpc_system_storage_backup_restore_process(const PB_Main* request, vo rpc_system_storage_reset_state(rpc_storage, session, true); - bool backup_ok = lfs_backup_unpack( + bool backup_ok = int_backup_unpack( rpc_storage->api, request->content.storage_backup_restore_request.archive_path); rpc_send_and_release_empty( diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 6dbeb0d36b6..072db1305bb 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -504,7 +504,7 @@ FS_Error storage_sd_info(Storage* storage, SDInfo* info); */ FS_Error storage_sd_status(Storage* storage); -/******************* Internal LFS Functions *******************/ +/************ Internal Storage Backup/Restore ************/ typedef void (*StorageNameConverter)(FuriString*); diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index a18b289408c..441b58da66b 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -3,8 +3,9 @@ #include #include -#include #include +#include +#include #include #include #include @@ -267,9 +268,8 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args File* file = storage_file_alloc(api); uint32_t buffer_size; - int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size); - - if(parsed_count != 1) { + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) != + StrintParseNoError) { storage_cli_print_usage(); } else if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { uint64_t file_size = storage_file_size(file); @@ -307,9 +307,8 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args File* file = storage_file_alloc(api); uint32_t buffer_size; - int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size); - - if(parsed_count != 1) { + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) != + StrintParseNoError) { storage_cli_print_usage(); } else { if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { diff --git a/applications/settings/desktop_settings/desktop_settings_app.c b/applications/settings/desktop_settings/desktop_settings_app.c index ab7782a7c4a..e8bc8995782 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.c +++ b/applications/settings/desktop_settings/desktop_settings_app.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -42,6 +43,7 @@ DesktopSettingsApp* desktop_settings_app_alloc(void) { app->pin_input_view = desktop_view_pin_input_alloc(); app->pin_setup_howto_view = desktop_settings_view_pin_setup_howto_alloc(); app->pin_setup_howto2_view = desktop_settings_view_pin_setup_howto2_alloc(); + app->dialog_ex = dialog_ex_alloc(); view_dispatcher_add_view( app->view_dispatcher, DesktopSettingsAppViewMenu, submenu_get_view(app->submenu)); @@ -63,6 +65,8 @@ DesktopSettingsApp* desktop_settings_app_alloc(void) { app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto2, desktop_settings_view_pin_setup_howto2_get_view(app->pin_setup_howto2_view)); + view_dispatcher_add_view( + app->view_dispatcher, DesktopSettingsAppViewDialogEx, dialog_ex_get_view(app->dialog_ex)); return app; } @@ -75,12 +79,14 @@ void desktop_settings_app_free(DesktopSettingsApp* app) { view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinInput); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto); view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewIdPinSetupHowto2); + view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewDialogEx); variable_item_list_free(app->variable_item_list); submenu_free(app->submenu); popup_free(app->popup); desktop_view_pin_input_free(app->pin_input_view); desktop_settings_view_pin_setup_howto_free(app->pin_setup_howto_view); desktop_settings_view_pin_setup_howto2_free(app->pin_setup_howto2_view); + dialog_ex_free(app->dialog_ex); // View dispatcher view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); diff --git a/applications/settings/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h index 348180fbf2f..18bc9fa28e9 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,7 @@ typedef enum { DesktopSettingsAppViewIdPinInput, DesktopSettingsAppViewIdPinSetupHowto, DesktopSettingsAppViewIdPinSetupHowto2, + DesktopSettingsAppViewDialogEx, } DesktopSettingsAppView; typedef struct { @@ -36,6 +38,7 @@ typedef struct { DesktopViewPinInput* pin_input_view; DesktopSettingsViewPinSetupHowto* pin_setup_howto_view; DesktopSettingsViewPinSetupHowto2* pin_setup_howto2_view; + DialogEx* dialog_ex; DesktopPinCode pincode_buffer; bool pincode_buffer_filled; diff --git a/applications/settings/desktop_settings/desktop_settings_custom_event.h b/applications/settings/desktop_settings/desktop_settings_custom_event.h new file mode 100644 index 00000000000..87978069e06 --- /dev/null +++ b/applications/settings/desktop_settings/desktop_settings_custom_event.h @@ -0,0 +1,26 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + // reserve 100 for button presses, submenu selections, etc. + DesktopSettingsCustomEventExit = 100, + DesktopSettingsCustomEventDone, + + DesktopSettingsCustomEvent1stPinEntered, + DesktopSettingsCustomEventPinsEqual, + DesktopSettingsCustomEventPinsDifferent, + + DesktopSettingsCustomEventSetPin, + DesktopSettingsCustomEventChangePin, + DesktopSettingsCustomEventDisablePin, + + DesktopSettingsCustomEventSetDefault, + DesktopSettingsCustomEventSetDummy, +} DesktopSettingsCustomEvent; + +#ifdef __cplusplus +} +#endif diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h index 4a1d0370162..37d845625be 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_config.h @@ -12,3 +12,5 @@ ADD_SCENE(desktop_settings, pin_setup_done, PinSetupDone) ADD_SCENE(desktop_settings, quick_apps_menu, QuickAppsMenu) ADD_SCENE(desktop_settings, quick_apps_direction_menu, QuickAppsDirectionMenu) + +ADD_SCENE(desktop_settings, happy_mode, HappyMode) diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c index 74d09b2ac39..17ef8835874 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_favorite.c @@ -209,7 +209,7 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) { submenu_reset(app->submenu); // Prevent menu from being shown when we exiting scene - strncpy( + strlcpy( curr_favorite_app->name_or_path, furi_string_get_cstr(temp_path), sizeof(curr_favorite_app->name_or_path)); @@ -219,7 +219,7 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e size_t app_index = event.event - 2; const char* name = favorite_fap_get_app_name(app_index); if(name) - strncpy( + strlcpy( curr_favorite_app->name_or_path, name, sizeof(curr_favorite_app->name_or_path)); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_happy_mode.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_happy_mode.c new file mode 100644 index 00000000000..31fcbfd2a9f --- /dev/null +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_happy_mode.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include + +#include "desktop_settings_scene.h" +#include "../desktop_settings_app.h" +#include "../desktop_settings_custom_event.h" + +static void desktop_settings_scene_happy_mode_done_callback(DialogExResult result, void* context) { + DesktopSettingsApp* app = context; + DolphinSettings settings; + Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); + dolphin_get_settings(dolphin, &settings); + settings.happy_mode = (result == DialogExResultRight); + dolphin_set_settings(dolphin, &settings); + furi_record_close(RECORD_DOLPHIN); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); +} + +void desktop_settings_scene_happy_mode_on_enter(void* context) { + DesktopSettingsApp* app = context; + Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); + DolphinSettings settings; + dolphin_get_settings(dolphin, &settings); + furi_record_close(RECORD_DOLPHIN); + + dialog_ex_set_header(app->dialog_ex, "Happy Mode", 64, 0, AlignCenter, AlignTop); + dialog_ex_set_text( + app->dialog_ex, + "I will never get angry at you\nfor not spending time with me\nas long as this mode is enabled", + 64, + 30, + AlignCenter, + AlignCenter); + dialog_ex_set_left_button_text(app->dialog_ex, settings.happy_mode ? "Disable" : "Go back"); + dialog_ex_set_right_button_text( + app->dialog_ex, settings.happy_mode ? "Keep enabled" : "Enable"); + dialog_ex_set_result_callback(app->dialog_ex, desktop_settings_scene_happy_mode_done_callback); + dialog_ex_set_context(app->dialog_ex, app); + view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewDialogEx); +} + +bool desktop_settings_scene_happy_mode_on_event(void* context, SceneManagerEvent event) { + DesktopSettingsApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DesktopSettingsCustomEventExit: + scene_manager_previous_scene(app->scene_manager); + consumed = true; + break; + default: + furi_crash(); + } + } + return consumed; +} + +void desktop_settings_scene_happy_mode_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c index 1e64165314c..0d154335946 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_auth.c @@ -3,15 +3,12 @@ #include #include #include "../desktop_settings_app.h" +#include "../desktop_settings_custom_event.h" #include #include #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" -#define SCENE_EVENT_EXIT (0U) -#define SCENE_EVENT_PINS_EQUAL (1U) -#define SCENE_EVENT_PINS_DIFFERENT (2U) - static void pin_auth_done_callback(const DesktopPinCode* pin_code, void* context) { furi_assert(pin_code); furi_assert(context); @@ -20,15 +17,17 @@ static void pin_auth_done_callback(const DesktopPinCode* pin_code, void* context app->pincode_buffer = *pin_code; if(desktop_pin_code_check(pin_code)) { - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); + view_dispatcher_send_custom_event( + app->view_dispatcher, DesktopSettingsCustomEventPinsEqual); } else { - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); + view_dispatcher_send_custom_event( + app->view_dispatcher, DesktopSettingsCustomEventPinsDifferent); } } static void pin_auth_back_callback(void* context) { DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_auth_on_enter(void* context) { @@ -54,13 +53,13 @@ bool desktop_settings_scene_pin_auth_on_event(void* context, SceneManagerEvent e if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_PINS_DIFFERENT: + case DesktopSettingsCustomEventPinsDifferent: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppScenePinError, SCENE_STATE_PIN_ERROR_WRONG); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinError); consumed = true; break; - case SCENE_EVENT_PINS_EQUAL: { + case DesktopSettingsCustomEventPinsEqual: { uint32_t state = scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppScenePinAuth); if(state == SCENE_STATE_PIN_AUTH_CHANGE_PIN) { @@ -73,7 +72,7 @@ bool desktop_settings_scene_pin_auth_on_event(void* context, SceneManagerEvent e consumed = true; break; } - case SCENE_EVENT_EXIT: + case DesktopSettingsCustomEventExit: scene_manager_search_and_switch_to_previous_scene( app->scene_manager, DesktopSettingsAppScenePinMenu); consumed = true; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c index abcce66da7a..a97ce8aaa57 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c @@ -3,15 +3,14 @@ #include #include "../desktop_settings_app.h" +#include "../desktop_settings_custom_event.h" #include #include "desktop_settings_scene.h" -#define SCENE_EVENT_EXIT (0U) - static void pin_disable_back_callback(void* context) { furi_assert(context); DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_disable_on_enter(void* context) { @@ -35,7 +34,7 @@ bool desktop_settings_scene_pin_disable_on_event(void* context, SceneManagerEven if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_EXIT: + case DesktopSettingsCustomEventExit: scene_manager_search_and_switch_to_previous_scene( app->scene_manager, DesktopSettingsAppScenePinMenu); consumed = true; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c index 711683c3fea..695f431a006 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_error.c @@ -8,20 +8,19 @@ #include "desktop_settings_scene_i.h" #include #include "../desktop_settings_app.h" - -#define SCENE_EVENT_EXIT (0U) +#include "../desktop_settings_custom_event.h" static void pin_error_back_callback(void* context) { furi_assert(context); DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } static void pin_error_done_callback(const DesktopPinCode* pin_code, void* context) { UNUSED(pin_code); furi_assert(context); DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_error_on_enter(void* context) { @@ -55,7 +54,7 @@ bool desktop_settings_scene_pin_error_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_EXIT: + case DesktopSettingsCustomEventExit: scene_manager_previous_scene(app->scene_manager); consumed = true; break; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c index e0c66cb2889..cf8436dc90b 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c @@ -4,10 +4,7 @@ #include "../desktop_settings_app.h" #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" - -#define SCENE_EVENT_SET_PIN 0 -#define SCENE_EVENT_CHANGE_PIN 1 -#define SCENE_EVENT_DISABLE_PIN 2 +#include "../desktop_settings_custom_event.h" static void desktop_settings_scene_pin_menu_submenu_callback(void* context, uint32_t index) { DesktopSettingsApp* app = context; @@ -23,7 +20,7 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) { submenu_add_item( submenu, "Set PIN", - SCENE_EVENT_SET_PIN, + DesktopSettingsCustomEventSetPin, desktop_settings_scene_pin_menu_submenu_callback, app); @@ -31,14 +28,14 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) { submenu_add_item( submenu, "Change PIN", - SCENE_EVENT_CHANGE_PIN, + DesktopSettingsCustomEventChangePin, desktop_settings_scene_pin_menu_submenu_callback, app); submenu_add_item( submenu, "Remove PIN", - SCENE_EVENT_DISABLE_PIN, + DesktopSettingsCustomEventDisablePin, desktop_settings_scene_pin_menu_submenu_callback, app); } @@ -54,11 +51,11 @@ bool desktop_settings_scene_pin_menu_on_event(void* context, SceneManagerEvent e if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_SET_PIN: + case DesktopSettingsCustomEventSetPin: scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto); consumed = true; break; - case SCENE_EVENT_CHANGE_PIN: + case DesktopSettingsCustomEventChangePin: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppScenePinAuth, @@ -66,7 +63,7 @@ bool desktop_settings_scene_pin_menu_on_event(void* context, SceneManagerEvent e scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinAuth); consumed = true; break; - case SCENE_EVENT_DISABLE_PIN: + case DesktopSettingsCustomEventDisablePin: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppScenePinAuth, SCENE_STATE_PIN_AUTH_DISABLE); scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinAuth); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c index 08f5fcb3fcc..95f50d2e11c 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup.c @@ -8,11 +8,7 @@ #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" #include - -#define SCENE_EVENT_EXIT (0U) -#define SCENE_EVENT_1ST_PIN_ENTERED (1U) -#define SCENE_EVENT_PINS_EQUAL (2U) -#define SCENE_EVENT_PINS_DIFFERENT (3U) +#include "../desktop_settings_custom_event.h" static void pin_setup_done_callback(const DesktopPinCode* pin_code, void* context) { furi_assert(pin_code); @@ -22,20 +18,23 @@ static void pin_setup_done_callback(const DesktopPinCode* pin_code, void* contex if(!app->pincode_buffer_filled) { app->pincode_buffer = *pin_code; app->pincode_buffer_filled = true; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_1ST_PIN_ENTERED); + view_dispatcher_send_custom_event( + app->view_dispatcher, DesktopSettingsCustomEvent1stPinEntered); } else { app->pincode_buffer_filled = false; if(desktop_pin_code_is_equal(&app->pincode_buffer, pin_code)) { - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_EQUAL); + view_dispatcher_send_custom_event( + app->view_dispatcher, DesktopSettingsCustomEventPinsEqual); } else { - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_PINS_DIFFERENT); + view_dispatcher_send_custom_event( + app->view_dispatcher, DesktopSettingsCustomEventPinsDifferent); } } } static void pin_setup_back_callback(void* context) { DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_EXIT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_setup_on_enter(void* context) { @@ -60,7 +59,7 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_1ST_PIN_ENTERED: + case DesktopSettingsCustomEvent1stPinEntered: desktop_view_pin_input_set_label_button(app->pin_input_view, "OK"); desktop_view_pin_input_set_label_primary(app->pin_input_view, 0, 0, NULL); desktop_view_pin_input_set_label_secondary( @@ -69,7 +68,7 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent desktop_view_pin_input_unlock_input(app->pin_input_view); consumed = true; break; - case SCENE_EVENT_PINS_DIFFERENT: + case DesktopSettingsCustomEventPinsDifferent: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppScenePinError, @@ -77,11 +76,11 @@ bool desktop_settings_scene_pin_setup_on_event(void* context, SceneManagerEvent scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinError); consumed = true; break; - case SCENE_EVENT_PINS_EQUAL: + case DesktopSettingsCustomEventPinsEqual: scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupHowto2); consumed = true; break; - case SCENE_EVENT_EXIT: { + case DesktopSettingsCustomEventExit: { uint32_t scene_found; scene_found = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, DesktopSettingsAppScenePinMenu); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c index aa3d2214e54..ad5784b551c 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_done.c @@ -5,18 +5,17 @@ #include #include "../desktop_settings_app.h" +#include "../desktop_settings_custom_event.h" #include #include #include "desktop_settings_scene.h" -#define SCENE_EVENT_DONE (0U) - static void pin_setup_done_callback(const DesktopPinCode* pin_code, void* context) { furi_assert(pin_code); furi_assert(context); DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EVENT_DONE); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventDone); } void desktop_settings_scene_pin_setup_done_on_enter(void* context) { @@ -48,7 +47,7 @@ bool desktop_settings_scene_pin_setup_done_on_event(void* context, SceneManagerE if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_DONE: { + case DesktopSettingsCustomEventDone: { bool scene_found = false; scene_found = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, DesktopSettingsAppScenePinMenu); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c index 31eec3871ad..69cdcc074bc 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto.c @@ -5,12 +5,11 @@ #include "desktop_settings_scene.h" #include "../desktop_settings_app.h" #include "../views/desktop_settings_view_pin_setup_howto.h" - -#define SCENE_EXIT_EVENT (0U) +#include "../desktop_settings_custom_event.h" static void desktop_settings_scene_pin_lock_done_callback(void* context) { DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_setup_howto_on_enter(void* context) { @@ -27,7 +26,7 @@ bool desktop_settings_scene_pin_setup_howto_on_event(void* context, SceneManager if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EXIT_EVENT: + case DesktopSettingsCustomEventExit: scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetup); consumed = true; break; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c index efa39f1f08c..c67ab4c790e 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_setup_howto2.c @@ -4,18 +4,16 @@ #include "desktop_settings_scene.h" #include "../desktop_settings_app.h" #include "../views/desktop_settings_view_pin_setup_howto2.h" - -#define SCENE_EXIT_EVENT (0U) -#define SCENE_DONE_EVENT (1U) +#include "../desktop_settings_custom_event.h" static void desktop_settings_scene_pin_setup_howto2_done_callback(void* context) { DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_DONE_EVENT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventDone); } static void desktop_settings_scene_pin_setup_howto2_exit_callback(void* context) { DesktopSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, SCENE_EXIT_EVENT); + view_dispatcher_send_custom_event(app->view_dispatcher, DesktopSettingsCustomEventExit); } void desktop_settings_scene_pin_setup_howto2_on_enter(void* context) { @@ -35,12 +33,12 @@ bool desktop_settings_scene_pin_setup_howto2_on_event(void* context, SceneManage if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_DONE_EVENT: { + case DesktopSettingsCustomEventDone: { scene_manager_next_scene(app->scene_manager, DesktopSettingsAppScenePinSetupDone); consumed = true; break; } - case SCENE_EXIT_EVENT: { + case DesktopSettingsCustomEventExit: { bool scene_found = false; scene_found = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, DesktopSettingsAppScenePinMenu); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c index a7000204f5b..baaee5c1e99 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_quick_apps_menu.c @@ -2,12 +2,10 @@ #include #include "../desktop_settings_app.h" +#include "../desktop_settings_custom_event.h" #include "desktop_settings_scene.h" #include "desktop_settings_scene_i.h" -#define SCENE_EVENT_SET_DEFAULT (0U) -#define SCENE_EVENT_SET_DUMMY (1U) - static void desktop_settings_scene_quick_apps_menu_submenu_callback(void* context, uint32_t index) { DesktopSettingsApp* app = context; @@ -22,14 +20,14 @@ void desktop_settings_scene_quick_apps_menu_on_enter(void* context) { submenu_add_item( submenu, "Default Mode", - SCENE_EVENT_SET_DEFAULT, + DesktopSettingsCustomEventSetDefault, desktop_settings_scene_quick_apps_menu_submenu_callback, app); submenu_add_item( submenu, "Dummy Mode", - SCENE_EVENT_SET_DUMMY, + DesktopSettingsCustomEventSetDummy, desktop_settings_scene_quick_apps_menu_submenu_callback, app); @@ -44,7 +42,7 @@ bool desktop_settings_scene_quick_apps_menu_on_event(void* context, SceneManager if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SCENE_EVENT_SET_DEFAULT: + case DesktopSettingsCustomEventSetDefault: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu, @@ -53,7 +51,7 @@ bool desktop_settings_scene_quick_apps_menu_on_event(void* context, SceneManager app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu); consumed = true; break; - case SCENE_EVENT_SET_DUMMY: + case DesktopSettingsCustomEventSetDummy: scene_manager_set_scene_state( app->scene_manager, DesktopSettingsAppSceneQuickAppsDirectionMenu, diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c index 7b3e5b96b9e..95bdcdf406c 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_start.c @@ -9,6 +9,7 @@ typedef enum { DesktopSettingsAutoLockDelay, DesktopSettingsClockDisplay, DesktopSettingsFavoriteApps, + DesktopSettingsHappyMode, } DesktopSettingsEntry; #define AUTO_LOCK_DELAY_COUNT 6 @@ -77,7 +78,7 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_list, "Show Clock", CLOCK_ENABLE_COUNT, - desktop_settings_scene_start_clock_enable_changed, // + desktop_settings_scene_start_clock_enable_changed, app); value_index = @@ -87,6 +88,8 @@ void desktop_settings_scene_start_on_enter(void* context) { variable_item_list_add(variable_item_list, "Set Quick Access Apps", 1, NULL, NULL); + variable_item_list_add(variable_item_list, "Happy Mode", 1, NULL, NULL); + variable_item_list_set_enter_callback( variable_item_list, desktop_settings_scene_start_var_list_enter_callback, app); @@ -107,6 +110,10 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneQuickAppsMenu); break; + case DesktopSettingsHappyMode: + scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneHappyMode); + break; + default: break; } diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c index 6cd9c5c6769..6328a238134 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c @@ -1,4 +1,5 @@ #include "../power_settings_app.h" +#include void power_settings_scene_power_off_dialog_callback(DialogExResult result, void* context) { furi_assert(context); @@ -9,11 +10,23 @@ void power_settings_scene_power_off_dialog_callback(DialogExResult result, void* void power_settings_scene_power_off_on_enter(void* context) { PowerSettingsApp* app = context; DialogEx* dialog = app->dialog; + Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); + DolphinSettings settings; + dolphin_get_settings(dolphin, &settings); + furi_record_close(RECORD_DOLPHIN); - dialog_ex_set_header(dialog, "Turn Off Device?", 64, 0, AlignCenter, AlignTop); - dialog_ex_set_text( - dialog, " I will be\nwaiting for\n you here...", 78, 14, AlignLeft, AlignTop); - dialog_ex_set_icon(dialog, 14, 10, &I_dolph_cry_49x54); + dialog_ex_set_header( + dialog, + "Turn Off Device?", + 64, + settings.happy_mode ? 32 : 0, + AlignCenter, + settings.happy_mode ? AlignCenter : AlignTop); + if(!settings.happy_mode) { + dialog_ex_set_text( + dialog, " I will be\nwaiting for\n you here...", 78, 14, AlignLeft, AlignTop); + dialog_ex_set_icon(dialog, 14, 10, &I_dolph_cry_49x54); + } dialog_ex_set_left_button_text(dialog, "Cancel"); dialog_ex_set_right_button_text(dialog, "Power Off"); dialog_ex_set_result_callback(dialog, power_settings_scene_power_off_dialog_callback); diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index 0b734c0f497..56a16bd9d31 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include typedef void (*cmd_handler)(FuriString* args); @@ -35,7 +35,7 @@ static void updater_cli_install(FuriString* manifest_path) { static void updater_cli_backup(FuriString* args) { printf("Backup /int to '%s'\r\n", furi_string_get_cstr(args)); Storage* storage = furi_record_open(RECORD_STORAGE); - bool success = lfs_backup_create(storage, furi_string_get_cstr(args)); + bool success = int_backup_create(storage, furi_string_get_cstr(args)); furi_record_close(RECORD_STORAGE); printf("Result: %s\r\n", success ? "OK" : "FAIL"); } @@ -43,7 +43,7 @@ static void updater_cli_backup(FuriString* args) { static void updater_cli_restore(FuriString* args) { printf("Restore /int from '%s'\r\n", furi_string_get_cstr(args)); Storage* storage = furi_record_open(RECORD_STORAGE); - bool success = lfs_backup_unpack(storage, furi_string_get_cstr(args)); + bool success = int_backup_unpack(storage, furi_string_get_cstr(args)); furi_record_close(RECORD_STORAGE); printf("Result: %s\r\n", success ? "OK" : "FAIL"); } diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index 8f051ff77a2..cca488475ea 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -22,8 +22,8 @@ static const char* update_task_stage_descr[] = { [UpdateTaskStageRadioInstall] = "Installing radio FW", [UpdateTaskStageRadioBusy] = "Core 2 busy", [UpdateTaskStageOBValidation] = "Validating opt. bytes", - [UpdateTaskStageLfsBackup] = "Backing up LFS", - [UpdateTaskStageLfsRestore] = "Restoring LFS", + [UpdateTaskStageIntBackup] = "Backing up configuration", + [UpdateTaskStageIntRestore] = "Restoring configuration", [UpdateTaskStageResourcesFileCleanup] = "Cleaning up files", [UpdateTaskStageResourcesDirCleanup] = "Cleaning up directories", [UpdateTaskStageResourcesFileUnpack] = "Extracting resources", @@ -82,7 +82,7 @@ static const struct { }, #ifndef FURI_RAM_EXEC { - .stage = UpdateTaskStageLfsBackup, + .stage = UpdateTaskStageIntBackup, .percent_min = 0, .percent_max = 100, .descr = "FS R/W error", @@ -193,10 +193,10 @@ static const struct { #endif #ifndef FURI_RAM_EXEC { - .stage = UpdateTaskStageLfsRestore, + .stage = UpdateTaskStageIntRestore, .percent_min = 0, .percent_max = 100, - .descr = "LFS I/O error", + .descr = "SD card I/O error", }, { .stage = UpdateTaskStageResourcesFileCleanup, @@ -245,7 +245,7 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { [UpdateTaskStageProgress] = STAGE_DEF(UpdateTaskStageGroupMisc, 0), [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 45), - [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), + [UpdateTaskStageIntBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 25), @@ -259,7 +259,7 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { [UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 100), [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 20), - [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 5), + [UpdateTaskStageIntRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 5), [UpdateTaskStageResourcesFileCleanup] = STAGE_DEF(UpdateTaskStageGroupResources, 100), [UpdateTaskStageResourcesDirCleanup] = STAGE_DEF(UpdateTaskStageGroupResources, 50), diff --git a/applications/system/updater/util/update_task.h b/applications/system/updater/util/update_task.h index 52bdfdbd235..c346c55fa4d 100644 --- a/applications/system/updater/util/update_task.h +++ b/applications/system/updater/util/update_task.h @@ -16,7 +16,7 @@ typedef enum { UpdateTaskStageProgress = 0, UpdateTaskStageReadManifest, - UpdateTaskStageLfsBackup, + UpdateTaskStageIntBackup, UpdateTaskStageRadioImageValidate, UpdateTaskStageRadioErase, @@ -30,7 +30,7 @@ typedef enum { UpdateTaskStageFlashWrite, UpdateTaskStageFlashValidate, - UpdateTaskStageLfsRestore, + UpdateTaskStageIntRestore, UpdateTaskStageResourcesFileCleanup, UpdateTaskStageResourcesDirCleanup, UpdateTaskStageResourcesFileUnpack, diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index 8d5039a16da..1b5bea25b30 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -21,14 +21,14 @@ static bool update_task_pre_update(UpdateTask* update_task) { backup_file_path = furi_string_alloc(); path_concat( furi_string_get_cstr(update_task->update_path), - LFS_BACKUP_DEFAULT_FILENAME, + INT_BACKUP_DEFAULT_FILENAME, backup_file_path); - update_task_set_progress(update_task, UpdateTaskStageLfsBackup, 0); + update_task_set_progress(update_task, UpdateTaskStageIntBackup, 0); /* to avoid bootloops */ furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal); if((success = - lfs_backup_create(update_task->storage, furi_string_get_cstr(backup_file_path)))) { + int_backup_create(update_task->storage, furi_string_get_cstr(backup_file_path)))) { furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeUpdate); } @@ -145,12 +145,12 @@ static bool update_task_post_update(UpdateTask* update_task) { do { path_concat( furi_string_get_cstr(update_task->update_path), - LFS_BACKUP_DEFAULT_FILENAME, + INT_BACKUP_DEFAULT_FILENAME, file_path); - update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0); + update_task_set_progress(update_task, UpdateTaskStageIntRestore, 0); - CHECK_RESULT(lfs_backup_unpack(update_task->storage, furi_string_get_cstr(file_path))); + CHECK_RESULT(int_backup_unpack(update_task->storage, furi_string_get_cstr(file_path))); if(update_task->state.groups & UpdateTaskStageGroupResources) { TarUnpackProgress progress = { diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index e7e1bbbedc6..a464815f0e0 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -341,7 +341,7 @@ int32_t update_task_worker_flash_writer(void* context) { } furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); - // Format LFS before restoring backup on next boot + // Clean up /int before restoring backup on next boot furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal); #ifdef FURI_NDEBUG // Production diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_0.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_0.png new file mode 100755 index 00000000000..9dcf6883973 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_1.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_1.png new file mode 100755 index 00000000000..1e1f54496e0 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_10.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_10.png new file mode 100755 index 00000000000..33c4c421471 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_11.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_11.png new file mode 100755 index 00000000000..c1630d2ed74 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_12.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_12.png new file mode 100755 index 00000000000..87770ab5539 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_13.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_13.png new file mode 100755 index 00000000000..cf7ccad0378 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_14.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_14.png new file mode 100755 index 00000000000..e023c4757c4 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_15.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_15.png new file mode 100755 index 00000000000..6298e385533 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_16.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_16.png new file mode 100755 index 00000000000..9e6b02100b7 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_17.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_17.png new file mode 100755 index 00000000000..c0c09bc6a86 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_18.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_18.png new file mode 100755 index 00000000000..319479a44d2 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_19.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_19.png new file mode 100755 index 00000000000..e9589f6366d Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_2.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_2.png new file mode 100755 index 00000000000..9f1b98c92c4 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_20.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_20.png new file mode 100755 index 00000000000..367ac6697ff Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_21.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_21.png new file mode 100755 index 00000000000..fabb4825f88 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_21.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_22.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_22.png new file mode 100755 index 00000000000..fccf72df6a5 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_22.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_23.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_23.png new file mode 100755 index 00000000000..a44d3665461 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_23.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_24.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_24.png new file mode 100755 index 00000000000..92586ee2ac6 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_24.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_25.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_25.png new file mode 100755 index 00000000000..6b4898d6ec8 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_25.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_26.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_26.png new file mode 100755 index 00000000000..4c5fbaed107 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_26.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_27.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_27.png new file mode 100755 index 00000000000..0db3adebf9f Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_27.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_28.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_28.png new file mode 100755 index 00000000000..635f647f475 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_28.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_29.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_29.png new file mode 100755 index 00000000000..3ba774243ef Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_29.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_3.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_3.png new file mode 100755 index 00000000000..264f617036f Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_30.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_30.png new file mode 100755 index 00000000000..3206c9dfd1d Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_30.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_31.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_31.png new file mode 100755 index 00000000000..f5d88201799 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_31.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_32.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_32.png new file mode 100755 index 00000000000..b86058e75cc Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_32.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_33.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_33.png new file mode 100755 index 00000000000..02902e4be53 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_33.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_34.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_34.png new file mode 100755 index 00000000000..46e0b3c1f31 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_34.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_35.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_35.png new file mode 100755 index 00000000000..0eb88b0c28a Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_35.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_36.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_36.png new file mode 100755 index 00000000000..801386cff91 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_36.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_37.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_37.png new file mode 100755 index 00000000000..b9b37b7319b Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_37.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_38.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_38.png new file mode 100755 index 00000000000..e6d1bb8db89 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_38.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_39.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_39.png new file mode 100755 index 00000000000..2120a086f9a Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_39.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_4.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_4.png new file mode 100755 index 00000000000..db25c5c17dc Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_40.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_40.png new file mode 100755 index 00000000000..94f7fa87c16 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_40.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_5.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_5.png new file mode 100755 index 00000000000..03f257d9976 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_6.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_6.png new file mode 100755 index 00000000000..8ec45c9c856 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_7.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_7.png new file mode 100755 index 00000000000..a0e0e4c1efd Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_8.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_8.png new file mode 100755 index 00000000000..4af49035583 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/frame_9.png b/assets/dolphin/external/L1_Procrastinating_128x64/frame_9.png new file mode 100755 index 00000000000..071f7421a11 Binary files /dev/null and b/assets/dolphin/external/L1_Procrastinating_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Procrastinating_128x64/meta.txt b/assets/dolphin/external/L1_Procrastinating_128x64/meta.txt new file mode 100755 index 00000000000..5017e506c6a --- /dev/null +++ b/assets/dolphin/external/L1_Procrastinating_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 26 +Active frames: 23 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 9 11 12 13 12 14 15 16 15 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 37 38 37 38 37 39 40 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 2 +Y: 36 +Text: I'll just get\nback to work +AlignH: Right +AlignV: Center +StartFrame: 42 +EndFrame: 47 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 5ab996f5918..94183bd9ae4 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -188,7 +188,7 @@ Min butthurt: 0 Max butthurt: 12 Min level: 3 Max level: 3 -Weight: 4 +Weight: 3 Name: L1_Akira_128x64 Min butthurt: 0 @@ -202,4 +202,11 @@ Min butthurt: 0 Max butthurt: 12 Min level: 3 Max level: 3 +Weight: 4 + +Name: L1_Procrastinating_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 Weight: 6 diff --git a/assets/protobuf b/assets/protobuf index 816de200a4a..6c7c0d55e82 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 816de200a4a43efc25c5b92d6a57fc982d7e988a +Subproject commit 6c7c0d55e82cb89223cf4890a540af4cff837fa7 diff --git a/documentation/ExpansionModules.md b/documentation/ExpansionModules.md index 470564e5743..fd9703adcc4 100644 --- a/documentation/ExpansionModules.md +++ b/documentation/ExpansionModules.md @@ -73,7 +73,7 @@ If the requested baud rate is supported by the host, it SHALL respond with a STA ### Control frame -CONTROL frames are used to control various aspects of the communication. As of now, the sole purpose of CONTROL frames is to start and stop the RPC session. +CONTROL frames are used to control various aspects of the communication and enable/disable various device features. | Header (1 byte) | Contents (1 byte) | Checksum (1 byte) | |-----------------|-------------------|-------------------| @@ -81,10 +81,18 @@ CONTROL frames are used to control various aspects of the communication. As of n The `Command` field SHALL have one of the followind values: -| Command | Meaning | -|---------|-------------------| -| 0x00 | Start RPC session | -| 0x01 | Stop RPC session | +| Command | Meaning | Note | +|---------|--------------------------|:----:| +| 0x00 | Start RPC session | 1 | +| 0x01 | Stop RPC session | 2 | +| 0x02 | Enable OTG (5V) on GPIO | 3 | +| 0x03 | Disable OTG (5V) on GPIO | 3 | + +Notes: + +1. Must only be used while the RPC session NOT active. +2. Must only be used while the RPC session IS active. +3. See 1, otherwise OTG is to be controlled via RPC messages. ### Data frame diff --git a/documentation/OTA.md b/documentation/OTA.md index 0456eab1f91..9783a704770 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -83,7 +83,7 @@ Even if something goes wrong, updater allows you to retry failed operations and | | | **50** | Package has mismatching HW target | | | | **60** | Missing DFU file | | | | **80** | Missing radio firmware file | -| Backing up LFS | **2** | **0-100** | FS read/write error | +| Backing up configuration| **2** | **0-100** | FS read/write error | | Checking radio FW | **3** | **0-99** | Error reading radio firmware file | | | | **100** | CRC mismatch | | Uninstalling radio FW | **4** | **0** | SHCI Delete command error | @@ -101,7 +101,7 @@ Even if something goes wrong, updater allows you to retry failed operations and | | | **99-100** | Corrupted DFU file | | Writing flash | **10** | **0-100** | Block read/write error | | Validating flash | **11** | **0-100** | Block read/write error | -| Restoring LFS | **12** | **0-100** | FS read/write error | +| Restoring configuration | **12** | **0-100** | FS read/write error | | Updating resources | **13-15** | **0-100** | SD card read/write error | ## Building update packages diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index 2a6cd51d32e..f4f008a71b6 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -71,9 +71,9 @@ FuriEventLoop* furi_event_loop_alloc(void) { PendingQueue_init(instance->pending_queue); // Clear notification state and value - xTaskNotifyStateClearIndexed(instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX); - ulTaskNotifyValueClearIndexed( - instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0xFFFFFFFF); + TaskHandle_t task = (TaskHandle_t)instance->thread_id; + xTaskNotifyStateClearIndexed(task, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX); + ulTaskNotifyValueClearIndexed(task, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0xFFFFFFFF); return instance; } @@ -178,7 +178,7 @@ static void furi_event_loop_process_waiting_list(FuriEventLoop* instance) { static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) { if(flags) { xTaskNotifyIndexed( - instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, flags, eSetBits); + (TaskHandle_t)instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, flags, eSetBits); } } @@ -186,10 +186,11 @@ void furi_event_loop_run(FuriEventLoop* instance) { furi_check(instance); furi_check(instance->thread_id == furi_thread_get_current_id()); + FuriThread* thread = furi_thread_get_current(); + // Set the default signal callback if none was previously set - if(furi_thread_get_signal_callback(instance->thread_id) == NULL) { - furi_thread_set_signal_callback( - instance->thread_id, furi_event_loop_signal_callback, instance); + if(furi_thread_get_signal_callback(thread) == NULL) { + furi_thread_set_signal_callback(thread, furi_event_loop_signal_callback, instance); } furi_event_loop_init_tick(instance); @@ -233,8 +234,8 @@ void furi_event_loop_run(FuriEventLoop* instance) { } // Disable the default signal callback - if(furi_thread_get_signal_callback(instance->thread_id) == furi_event_loop_signal_callback) { - furi_thread_set_signal_callback(instance->thread_id, NULL, NULL); + if(furi_thread_get_signal_callback(thread) == furi_event_loop_signal_callback) { + furi_thread_set_signal_callback(thread, NULL, NULL); } } @@ -242,7 +243,10 @@ void furi_event_loop_stop(FuriEventLoop* instance) { furi_check(instance); xTaskNotifyIndexed( - instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagStop, eSetBits); + (TaskHandle_t)instance->thread_id, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagStop, + eSetBits); } /* @@ -265,7 +269,10 @@ void furi_event_loop_pend_callback( PendingQueue_push_front(instance->pending_queue, item); xTaskNotifyIndexed( - instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagPending, eSetBits); + (TaskHandle_t)instance->thread_id, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagPending, + eSetBits); } /* @@ -473,7 +480,10 @@ static void furi_event_loop_item_notify(FuriEventLoopItem* instance) { FURI_CRITICAL_EXIT(); xTaskNotifyIndexed( - owner->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagEvent, eSetBits); + (TaskHandle_t)owner->thread_id, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagEvent, + eSetBits); } static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance) { diff --git a/furi/core/event_loop_timer.c b/furi/core/event_loop_timer.c index 03b6c513234..f4a79bb4f17 100644 --- a/furi/core/event_loop_timer.c +++ b/furi/core/event_loop_timer.c @@ -65,7 +65,10 @@ static void furi_event_loop_timer_enqueue_request( TimerQueue_push_back(instance->timer_queue, timer); xTaskNotifyIndexed( - instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagTimer, eSetBits); + (TaskHandle_t)instance->thread_id, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagTimer, + eSetBits); } /* diff --git a/furi/core/thread.c b/furi/core/thread.c index 69c6b0f04e8..60cc628acb3 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -97,7 +97,7 @@ static void furi_thread_body(void* context) { furi_thread_set_state(thread, FuriThreadStateRunning); if(thread->heap_trace_enabled == true) { - memmgr_heap_enable_thread_trace(thread); + memmgr_heap_enable_thread_trace((FuriThreadId)thread); } thread->ret = thread->callback(thread->context); @@ -106,14 +106,14 @@ static void furi_thread_body(void* context) { if(thread->heap_trace_enabled == true) { furi_delay_ms(33); - thread->heap_size = memmgr_heap_get_thread_memory(thread); + thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)thread); furi_log_print_format( thread->heap_size ? FuriLogLevelError : FuriLogLevelInfo, TAG, "%s allocation balance: %zu", thread->name ? thread->name : "Thread", thread->heap_size); - memmgr_heap_disable_thread_trace(thread); + memmgr_heap_disable_thread_trace((FuriThreadId)thread); } furi_check(thread->state == FuriThreadStateRunning); @@ -275,7 +275,7 @@ void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) { FuriThreadPriority furi_thread_get_priority(FuriThread* thread) { furi_check(thread); - TaskHandle_t hTask = furi_thread_get_id(thread); + TaskHandle_t hTask = (TaskHandle_t)thread; return (FuriThreadPriority)uxTaskPriorityGet(hTask); } @@ -390,7 +390,7 @@ bool furi_thread_join(FuriThread* thread) { FuriThreadId furi_thread_get_id(FuriThread* thread) { furi_check(thread); - return thread; + return (FuriThreadId)thread; } void furi_thread_enable_heap_trace(FuriThread* thread) { @@ -418,7 +418,7 @@ int32_t furi_thread_get_return_code(FuriThread* thread) { } FuriThreadId furi_thread_get_current_id(void) { - return xTaskGetCurrentTaskHandle(); + return (FuriThreadId)xTaskGetCurrentTaskHandle(); } FuriThread* furi_thread_get_current(void) { @@ -624,15 +624,16 @@ bool furi_thread_enumerate(FuriThreadList* thread_list) { FuriThreadListItem* item = furi_thread_list_get_or_insert(thread_list, (FuriThread*)task[i].xHandle); - item->thread = (FuriThreadId)task[i].xHandle; - item->app_id = furi_thread_get_appid(item->thread); + FuriThreadId thread_id = (FuriThreadId)task[i].xHandle; + item->thread = (FuriThread*)thread_id; + item->app_id = furi_thread_get_appid(thread_id); item->name = task[i].pcTaskName; item->priority = task[i].uxCurrentPriority; item->stack_address = (uint32_t)tcb->pxStack; - size_t thread_heap = memmgr_heap_get_thread_memory(item->thread); + size_t thread_heap = memmgr_heap_get_thread_memory(thread_id); item->heap = thread_heap == MEMMGR_HEAP_UNKNOWN ? 0u : thread_heap; item->stack_size = (tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t); - item->stack_min_free = furi_thread_get_stack_space(item->thread); + item->stack_min_free = furi_thread_get_stack_space(thread_id); item->state = furi_thread_state_name(task[i].eCurrentState); item->counter_previous = item->counter_current; item->counter_current = task[i].ulRunTimeCounter; diff --git a/furi/core/timer.c b/furi/core/timer.c index 1ca56f0fa45..21952cf1233 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -3,18 +3,20 @@ #include "kernel.h" #include +#include #include struct FuriTimer { StaticTimer_t container; FuriTimerCallback cb_func; void* cb_context; - volatile bool can_be_removed; }; // IMPORTANT: container MUST be the FIRST struct member static_assert(offsetof(FuriTimer, container) == 0); +#define TIMER_DELETED_EVENT (1U << 0) + static void TimerCallback(TimerHandle_t hTimer) { FuriTimer* instance = pvTimerGetTimerID(hTimer); furi_check(instance); @@ -42,9 +44,8 @@ static void furi_timer_epilogue(void* context, uint32_t arg) { furi_assert(context); UNUSED(arg); - FuriTimer* instance = context; - - instance->can_be_removed = true; + EventGroupHandle_t hEvent = context; + xEventGroupSetBits(hEvent, TIMER_DELETED_EVENT); } void furi_timer_free(FuriTimer* instance) { @@ -53,11 +54,13 @@ void furi_timer_free(FuriTimer* instance) { TimerHandle_t hTimer = (TimerHandle_t)instance; furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); - furi_check(xTimerPendFunctionCall(furi_timer_epilogue, instance, 0, portMAX_DELAY) == pdPASS); - while(!instance->can_be_removed) { - furi_delay_tick(2); - } + StaticEventGroup_t event_container; + EventGroupHandle_t hEvent = xEventGroupCreateStatic(&event_container); + furi_check(xTimerPendFunctionCall(furi_timer_epilogue, hEvent, 0, portMAX_DELAY) == pdPASS); + + xEventGroupWaitBits(hEvent, TIMER_DELETED_EVENT, 0, pdTRUE, portMAX_DELAY); + vEventGroupDelete(hEvent); free(instance); } diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index b9d33169c9a..8a16dbfd958 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "flipper_format_stream.h" #include "flipper_format_stream_i.h" @@ -396,14 +397,16 @@ bool flipper_format_stream_read_value_line( #endif case FlipperStreamValueInt32: { int32_t* data = _data; - scan_values = sscanf(furi_string_get_cstr(value), "%" PRIi32, &data[i]); + if(strint_to_int32(furi_string_get_cstr(value), NULL, &data[i], 10) == + StrintParseNoError) { + scan_values = 1; + } }; break; case FlipperStreamValueUint32: { uint32_t* data = _data; - // Minus sign is allowed in scanf() for unsigned numbers, resulting in unintentionally huge values with no error reported - if(!furi_string_start_with(value, "-")) { - scan_values = - sscanf(furi_string_get_cstr(value), "%" PRIu32, &data[i]); + if(strint_to_uint32(furi_string_get_cstr(value), NULL, &data[i], 10) == + StrintParseNoError) { + scan_values = 1; } }; break; case FlipperStreamValueHexUint64: { diff --git a/lib/lfrfid/protocols/protocol_gproxii.c b/lib/lfrfid/protocols/protocol_gproxii.c index 73cbe8f39e0..ab1d0b94d8c 100644 --- a/lib/lfrfid/protocols/protocol_gproxii.c +++ b/lib/lfrfid/protocols/protocol_gproxii.c @@ -38,12 +38,53 @@ void protocol_gproxii_free(ProtocolGProxII* protocol) { free(protocol); } -uint8_t* protocol_gproxii_get_data(ProtocolGProxII* proto) { - return proto->data; +uint8_t* protocol_gproxii_get_data(ProtocolGProxII* protocol) { + return protocol->decoded_data; +} + +bool wiegand_check(uint64_t fc_and_card, bool even_parity, bool odd_parity, int card_len) { + uint8_t even_parity_sum = 0; + uint8_t odd_parity_sum = 1; + switch(card_len) { + case 26: + for(int8_t i = 12; i < 24; i++) { + if(((fc_and_card >> i) & 1) == 1) { + even_parity_sum++; + } + } + if(even_parity_sum % 2 != even_parity) return false; + + for(int8_t i = 0; i < 12; i++) { + if(((fc_and_card >> i) & 1) == 1) { + odd_parity_sum++; + } + } + if(odd_parity_sum % 2 != odd_parity) return false; + break; + case 36: + for(int8_t i = 17; i < 34; i++) { + if(((fc_and_card >> i) & 1) == 1) { + even_parity_sum++; + } + } + if(even_parity_sum % 2 != even_parity) return false; + + for(int8_t i = 0; i < 17; i++) { + if(((fc_and_card >> i) & 1) == 1) { + odd_parity_sum++; + } + } + if(odd_parity_sum % 2 != odd_parity) return false; + break; + default: + furi_crash(); + } + return true; } void protocol_gproxii_decoder_start(ProtocolGProxII* protocol) { memset(protocol->data, 0, GPROXII_ENCODED_BYTE_FULL_SIZE); + memset(protocol->decoded_data, 0, GPROXII_DATA_SIZE); protocol->last_short = false; } @@ -73,13 +114,13 @@ static bool protocol_gproxii_can_be_decoded(ProtocolGProxII* protocol) { // XORVALUE LLLLLL DD PPPPPPPPPPPPPPPP E FFFFFFFF CCCCCCCCCCCCCCCC O UUUUUUUUUUUUUU // 10010000 011010 11 0000000100000000 0 00000000 0000000000000001 0 00000000000000 - Profile: 256 FC: 0 Card: 1 - // 72 Bit Guardall/Verex/Chubb GProx II 36 bit key with 26 bit profile - // 0 10 20 30 40 50 60 70 - // | | | | | | | | - // 01234567 890123 45 67890123456789012345678901 2 34567890 1234567890123456 7 8901 + // 72 Bit Guardall/Verex/Chubb GProx II 36 bit key with 16 bit profile + // 0 10 20 30 40 50 60 70 + // | | | | | | | | + // 01234567 890123 45 67890123 45678901 2 34567890123456 78901234567890123456 7 8901 // -------------------------------------------------------------------------------- - // XORVALUE LLLLLL DD PPPPPPPPPPPPPPPPPPPPPPPPPP E FFFFFFFF CCCCCCCCCCCCCCCC O UUUU - // 10111000 100100 10 00000001000000000000000000 1 01000000 1000100010111000 1 0000 - Profile: 262144 FC: 64 Card: 35000 + // XORVALUE LLLLLL DD PPPPPPPP PPPPPPPP E UUUUUUFFFFFFFF UUUUCCCCCCCCCCCCCCCC O UUUU + // 10111000 100100 10 00000001 00000000 0 00000000010100 00001000100010111000 1 0000 - Profile: 256 FC: 20 Card: 35000 // X = XOR Key, L = Message length, D = 2 bit check digits, P = Profile, E = Wiegand leading even parity // F = Faclity code, C = Card number, O = Wiegand trailing odd parity, U = Unused bits @@ -88,8 +129,9 @@ static bool protocol_gproxii_can_be_decoded(ProtocolGProxII* protocol) { if(bit_lib_get_bits(protocol->data, 0, 6) != 0b111110) return false; // Check always 0 parity on every 5th bit after preamble - if(bit_lib_test_parity(protocol->data, 5, GPROXII_ENCODED_BIT_SIZE, BitLibParityAlways0, 5)) + if(!bit_lib_test_parity(protocol->data, 6, GPROXII_ENCODED_BIT_SIZE, BitLibParityAlways0, 5)) { return false; + } // Start GProx II decode bit_lib_copy_bits(protocol->decoded_data, 0, GPROXII_ENCODED_BIT_SIZE, protocol->data, 6); @@ -109,11 +151,23 @@ static bool protocol_gproxii_can_be_decoded(ProtocolGProxII* protocol) { // Check card length is either 26 or 36 int card_len = bit_lib_get_bits(protocol->decoded_data, 8, 6); - if(card_len == 26 || card_len == 36) { - return true; + + // wiegand parity + if(card_len == 26) { + uint64_t fc_and_card = bit_lib_get_bits_64(protocol->decoded_data, 33, 24); + bool even_parity = bit_lib_get_bits(protocol->decoded_data, 32, 1); + bool odd_parity = bit_lib_get_bits(protocol->decoded_data, 57, 1); + if(!wiegand_check(fc_and_card, even_parity, odd_parity, card_len)) return false; + } else if(card_len == 36) { + uint64_t fc_and_card = bit_lib_get_bits_64(protocol->decoded_data, 33, 34); + uint8_t even_parity = bit_lib_get_bits(protocol->decoded_data, 32, 1); + uint8_t odd_parity = bit_lib_get_bits(protocol->decoded_data, 67, 1); + if(!wiegand_check(fc_and_card, even_parity, odd_parity, card_len)) return false; } else { return false; // If we don't get a 26 or 36 it's not a known card type } + + return true; } bool protocol_gproxii_decoder_feed(ProtocolGProxII* protocol, bool level, uint32_t duration) { @@ -189,7 +243,7 @@ void protocol_gproxii_render_data(ProtocolGProxII* protocol, FuriString* result) // Print FC, Card and Length furi_string_cat_printf( result, - "FC: %hhu Card: %hu LEN: %hhu\n", + "FC: %u Card: %u LEN: %hhu\n", bit_lib_get_bits(protocol->decoded_data, 33, 8), bit_lib_get_bits_16(protocol->decoded_data, 41, 16), card_len); @@ -204,17 +258,17 @@ void protocol_gproxii_render_data(ProtocolGProxII* protocol, FuriString* result) // Print FC, Card and Length furi_string_cat_printf( result, - "FC: %hhu Card: %hu LEN: %hhu\n", - bit_lib_get_bits(protocol->decoded_data, 43, 8), + "FC: %u Card: %u LEN: %hhu\n", + bit_lib_get_bits_16(protocol->decoded_data, 33, 14), bit_lib_get_bits_16(protocol->decoded_data, 51, 16), card_len); // XOR Key, CRC and Profile furi_string_cat_printf( result, - "XOR: %hhu CRC: %hhu P: %06lX", + "XOR: %hhu CRC: %hhu P: %04hX", xor_code, crc_code, - bit_lib_get_bits_32(protocol->decoded_data, 16, 26)); + bit_lib_get_bits_16(protocol->decoded_data, 16, 16)); } else { furi_string_cat_printf(result, "Read Error\n"); } diff --git a/lib/lfrfid/tools/t5577.c b/lib/lfrfid/tools/t5577.c index 59ec7d27226..ce97cb32261 100644 --- a/lib/lfrfid/tools/t5577.c +++ b/lib/lfrfid/tools/t5577.c @@ -13,9 +13,6 @@ #define T5577_OPCODE_PAGE_1 0b11 #define T5577_OPCODE_RESET 0b00 -#define T5577_BLOCKS_IN_PAGE_0 8 -#define T5577_BLOCKS_IN_PAGE_1 4 - static void t5577_start(void) { furi_hal_rfid_tim_read_start(125000, 0.5); diff --git a/lib/lfrfid/tools/t5577.h b/lib/lfrfid/tools/t5577.h index 026e7290bb0..49c0b571447 100644 --- a/lib/lfrfid/tools/t5577.h +++ b/lib/lfrfid/tools/t5577.h @@ -7,6 +7,8 @@ extern "C" { #endif #define LFRFID_T5577_BLOCK_COUNT 8 +#define T5577_BLOCKS_IN_PAGE_0 8 +#define T5577_BLOCKS_IN_PAGE_1 4 // T5577 block 0 definitions, thanks proxmark3! #define LFRFID_T5577_POR_DELAY 0x00000001 diff --git a/lib/mjs/common/frozen/frozen.c b/lib/mjs/common/frozen/frozen.c index 923e6a556b4..7646864c06c 100644 --- a/lib/mjs/common/frozen/frozen.c +++ b/lib/mjs/common/frozen/frozen.c @@ -37,7 +37,7 @@ #ifdef _WIN32 #undef snprintf #undef vsnprintf -#define snprintf cs_win_snprintf +#define snprintf cs_win_snprintf #define vsnprintf cs_win_vsnprintf int cs_win_snprintf(char* str, size_t size, const char* format, ...); int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap); @@ -150,7 +150,8 @@ static int json_isspace(int ch) { } static void json_skip_whitespaces(struct frozen* f) { - while(f->cur < f->end && json_isspace(*f->cur)) f->cur++; + while(f->cur < f->end && json_isspace(*f->cur)) + f->cur++; } static int json_cur(struct frozen* f) { @@ -263,15 +264,18 @@ static int json_parse_number(struct frozen* f) { f->cur += 2; EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); EXPECT(json_isxdigit(f->cur[0]), JSON_STRING_INVALID); - while(f->cur < f->end && json_isxdigit(f->cur[0])) f->cur++; + while(f->cur < f->end && json_isxdigit(f->cur[0])) + f->cur++; } else { EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); - while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + while(f->cur < f->end && json_isdigit(f->cur[0])) + f->cur++; if(f->cur < f->end && f->cur[0] == '.') { f->cur++; EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); - while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + while(f->cur < f->end && json_isdigit(f->cur[0])) + f->cur++; } if(f->cur < f->end && (f->cur[0] == 'e' || f->cur[0] == 'E')) { f->cur++; @@ -279,7 +283,8 @@ static int json_parse_number(struct frozen* f) { if((f->cur[0] == '+' || f->cur[0] == '-')) f->cur++; EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); - while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + while(f->cur < f->end && json_isdigit(f->cur[0])) + f->cur++; } } json_truncate_path(f, fstate.path_len); @@ -639,8 +644,7 @@ int json_vprintf(struct json_out* out, const char* fmt, va_list xap) { int need_len, size = sizeof(buf); char fmt2[20]; va_list ap_copy; - strncpy(fmt2, fmt, n + 1 > (int)sizeof(fmt2) ? sizeof(fmt2) : (size_t)n + 1); - fmt2[n + 1] = '\0'; + strlcpy(fmt2, fmt, sizeof(fmt2)); va_copy(ap_copy, ap); need_len = vsnprintf(pbuf, size, fmt2, ap_copy); @@ -1047,7 +1051,7 @@ int json_vscanf(const char* s, int len, const char* fmt, va_list ap) { while(fmt[i] != '\0') { if(fmt[i] == '{') { - strcat(path, "."); + strlcat(path, ".", sizeof(path)); i++; } else if(fmt[i] == '}') { if((p = strrchr(path, '.')) != NULL) *p = '\0'; @@ -1160,7 +1164,8 @@ struct json_setf_data { static int get_matched_prefix_len(const char* s1, const char* s2) { int i = 0; - while(s1[i] && s2[i] && s1[i] == s2[i]) i++; + while(s1[i] && s2[i] && s1[i] == s2[i]) + i++; return i; } @@ -1235,7 +1240,8 @@ int json_vsetf( /* Trim comma after the value that begins at object/array start */ if(s[data.prev - 1] == '{' || s[data.prev - 1] == '[') { int i = data.end; - while(i < len && json_isspace(s[i])) i++; + while(i < len && json_isspace(s[i])) + i++; if(s[i] == ',') data.end = i + 1; /* Point after comma */ } json_printf(out, "%.*s", len - data.end, s + data.end); @@ -1305,7 +1311,8 @@ struct prettify_data { }; static void indent(struct json_out* out, int level) { - while(level-- > 0) out->printer(out, " ", 2); + while(level-- > 0) + out->printer(out, " ", 2); } static void print_key(struct prettify_data* pd, const char* path, const char* name, int name_len) { diff --git a/lib/mjs/mjs_json.c b/lib/mjs/mjs_json.c index 829b3b4c0f8..654eeb3d655 100644 --- a/lib/mjs/mjs_json.c +++ b/lib/mjs/mjs_json.c @@ -138,8 +138,7 @@ MJS_PRIVATE mjs_err_t to_json_or_debug( vp < mjs->json_visited_stack.buf + mjs->json_visited_stack.len; vp += sizeof(mjs_val_t)) { if(*(mjs_val_t*)vp == v) { - strncpy(buf, "[Circular]", size); - len = 10; + len = strlcpy(buf, "[Circular]", size); goto clean; } } diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 1ccde73a4d7..60737250beb 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -4,6 +4,7 @@ #include #include #include +#include #define TAG "SubGhzFileEncoderWorker" @@ -45,27 +46,26 @@ void subghz_file_encoder_worker_add_level_duration( } bool subghz_file_encoder_worker_data_parse(SubGhzFileEncoderWorker* instance, const char* strStart) { - char* str1; - bool res = false; // Line sample: "RAW_Data: -1, 2, -2..." - // Look for a key in the line - str1 = strstr(strStart, "RAW_Data: "); + // Look for the key in the line + char* str = strstr(strStart, "RAW_Data: "); + bool res = false; - if(str1 != NULL) { + if(str) { // Skip key - str1 = strchr(str1, ' '); - - // Check that there is still an element in the line - while(strchr(str1, ' ') != NULL) { - str1 = strchr(str1, ' '); + str = strchr(str, ' '); - // Skip space - str1 += 1; - subghz_file_encoder_worker_add_level_duration(instance, atoi(str1)); + // Parse next element + int32_t duration; + while(strint_to_int32(str, &str, &duration, 10) == StrintParseNoError) { + subghz_file_encoder_worker_add_level_duration(instance, duration); + if(*str == ',') str++; // could also be `\0` } + res = true; } + return res; } diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 11e01a8c9da..03b8999c4d3 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -29,6 +29,7 @@ env.Append( File("stream/file_stream.h"), File("stream/string_stream.h"), File("stream/buffered_file_stream.h"), + File("strint.h"), File("protocols/protocol_dict.h"), File("pretty_format.h"), File("hex.h"), diff --git a/lib/toolbox/args.c b/lib/toolbox/args.c index aa790ad68c5..914b093bac1 100644 --- a/lib/toolbox/args.c +++ b/lib/toolbox/args.c @@ -1,5 +1,7 @@ #include "args.h" #include "hex.h" +#include "strint.h" +#include "m-core.h" size_t args_get_first_word_length(FuriString* args) { size_t ws = furi_string_search_char(args, ' '); @@ -21,7 +23,9 @@ bool args_read_int_and_trim(FuriString* args, int* value) { return false; } - if(sscanf(furi_string_get_cstr(args), "%d", value) == 1) { + int32_t temp; + if(strint_to_int32(furi_string_get_cstr(args), NULL, &temp, 10) == StrintParseNoError) { + *value = temp; furi_string_right(args, cmd_length); furi_string_trim(args); return true; diff --git a/lib/toolbox/strint.c b/lib/toolbox/strint.c new file mode 100644 index 00000000000..8c7f36976a5 --- /dev/null +++ b/lib/toolbox/strint.c @@ -0,0 +1,121 @@ +#include "strint.h" + +#include + +// Splitting out the actual parser helps reduce code size. The manually +// monomorphized `strint_to_*`s are just wrappers around this generic +// implementation. +/** + * @brief Converts a string to a `uint64_t` and an auxillary sign bit, checking + * the bounds of the integer. + * @param [in] str Input string + * @param [out] end Pointer to first character after the number in input string + * @param [out] abs_out Absolute part of result + * @param [out] negative_out Sign part of result (true=negative, false=positive) + * @param [in] base Integer base + * @param [in] max_abs_negative Largest permissible absolute part of result if + * the sign is negative + * @param [in] max_positive Largest permissible absolute part of result if the + * sign is positive + */ +StrintParseError strint_to_uint64_internal( + const char* str, + char** end, + uint64_t* abs_out, + bool* negative_out, + uint8_t base, + uint64_t max_abs_negative, + uint64_t max_positive) { + // skip whitespace + while(((*str >= '\t') && (*str <= '\r')) || *str == ' ') { + str++; + } + + // read sign + bool negative = false; + if(*str == '+' || *str == '-') { + if(*str == '-') negative = true; + str++; + } + if(*str == '+' || *str == '-') return StrintParseSignError; + if(max_abs_negative == 0 && negative) return StrintParseSignError; + + // infer base + // not assigning directly to `base' to permit prefixes with explicit bases + uint8_t inferred_base = 0; + if(strncasecmp(str, "0x", 2) == 0) { + inferred_base = 16; + str += 2; + } else if(strncasecmp(str, "0b", 2) == 0) { + inferred_base = 2; + str += 2; + } else if(*str == '0') { + inferred_base = 8; + str++; + } else { + inferred_base = 10; + } + if(base == 0) base = inferred_base; + + // read digits + uint64_t limit = negative ? max_abs_negative : max_positive; + uint64_t mul_limit = limit / base; + uint64_t result = 0; + int read_total = 0; + while(*str != 0) { + int digit_value; + if(*str >= '0' && *str <= '9') { + digit_value = *str - '0'; + } else if(*str >= 'A' && *str <= 'Z') { + digit_value = *str - 'A' + 10; + } else if(*str >= 'a' && *str <= 'z') { + digit_value = *str - 'a' + 10; + } else { + break; + } + + if(digit_value >= base) { + break; + } + + if(result > mul_limit) return StrintParseOverflowError; + result *= base; + if(result > limit - digit_value) return StrintParseOverflowError; + result += digit_value; + + read_total++; + str++; + } + + if(read_total == 0) { + if(inferred_base == 8) { + // there's just a single zero + result = 0; + } else { + return StrintParseAbsentError; + } + } + + if(abs_out) *abs_out = result; + if(negative_out) *negative_out = negative; + if(end) *end = (char*)str; // rabbit hole: https://c-faq.com/ansi/constmismatch.html + return StrintParseNoError; +} + +#define STRINT_MONO(name, ret_type, neg_abs_limit, pos_limit) \ + StrintParseError name(const char* str, char** end, ret_type* out, uint8_t base) { \ + uint64_t absolute; \ + bool negative; \ + StrintParseError err = strint_to_uint64_internal( \ + str, end, &absolute, &negative, base, (neg_abs_limit), (pos_limit)); \ + if(err) return err; \ + if(out) *out = (negative ? (-(ret_type)absolute) : ((ret_type)absolute)); \ + return StrintParseNoError; \ + } + +STRINT_MONO(strint_to_uint64, uint64_t, 0, UINT64_MAX) +STRINT_MONO(strint_to_int64, int64_t, (uint64_t)INT64_MAX + 1, INT64_MAX) +STRINT_MONO(strint_to_uint32, uint32_t, 0, UINT32_MAX) +STRINT_MONO(strint_to_int32, int32_t, (uint64_t)INT32_MAX + 1, INT32_MAX) +STRINT_MONO(strint_to_uint16, uint16_t, 0, UINT16_MAX) +STRINT_MONO(strint_to_int16, int16_t, (uint64_t)INT16_MAX + 1, INT16_MAX) diff --git a/lib/toolbox/strint.h b/lib/toolbox/strint.h new file mode 100644 index 00000000000..c27cdcb4ea0 --- /dev/null +++ b/lib/toolbox/strint.h @@ -0,0 +1,70 @@ +/** + * @file strint.h + * Performs conversions between strings and integers. + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** String to integer conversion error */ +typedef enum { + StrintParseNoError, //!< Conversion performed successfully + StrintParseSignError, //!< Multiple leading `+` or `-` characters, or leading `-` character if the type is unsigned + StrintParseAbsentError, //!< No valid digits after the leading whitespace, sign and prefix + StrintParseOverflowError, //!< Result does not fit in the requested type +} StrintParseError; + +/** See `strint_to_uint32` */ +StrintParseError strint_to_uint64(const char* str, char** end, uint64_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_int64(const char* str, char** end, int64_t* out, uint8_t base); + +/** Converts a string to a `uint32_t` + * + * @param[in] str Input string + * @param[out] end Pointer to first character after the number in input string + * @param[out] out Parse result + * @param[in] base Integer base + * + * @return Parse error + * + * Parses the number in the input string. The number may be surrounded by + * whitespace characters to the left and any non-digit characters to the right. + * What's considered a digit is determined by the input base in the following + * order: `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ`. The number may be prefixed + * with either a `+` or a `-` to indicate its sign. The pointer to the first + * character after the leading whitespace, allowed prefixes and digits is + * assigned to `end`. + * + * If the input base is 0, the base is inferred from the leading characters of + * the number: + * - If it starts with `0x`, it's read in base 16; + * - If it starts with a `0`, it's read in base 8; + * - If it starts with `0b`, it's read in base 2. + * - Otherwise, it's read in base 10. + * + * For a description of the return codes, see `StrintParseError`. If the return + * code is something other than `StrintParseNoError`, the values at `end` and + * `out` are unaltered. + */ +StrintParseError strint_to_uint32(const char* str, char** end, uint32_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_int32(const char* str, char** end, int32_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_uint16(const char* str, char** end, uint16_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_int16(const char* str, char** end, int16_t* out, uint8_t base); + +#ifdef __cplusplus +} +#endif diff --git a/lib/update_util/lfs_backup.c b/lib/update_util/int_backup.c similarity index 77% rename from lib/update_util/lfs_backup.c rename to lib/update_util/int_backup.c index 7786524ef64..a904db247f1 100644 --- a/lib/update_util/lfs_backup.c +++ b/lib/update_util/int_backup.c @@ -1,4 +1,4 @@ -#include "lfs_backup.h" +#include "int_backup.h" #include @@ -9,7 +9,7 @@ #include #include -#define LFS_BACKUP_DEFAULT_LOCATION EXT_PATH(LFS_BACKUP_DEFAULT_FILENAME) +#define INT_BACKUP_DEFAULT_LOCATION EXT_PATH(INT_BACKUP_DEFAULT_FILENAME) static void backup_name_converter(FuriString* filename) { if(furi_string_empty(filename) || (furi_string_get_char(filename, 0) == '.')) { @@ -34,18 +34,18 @@ static void backup_name_converter(FuriString* filename) { } } -bool lfs_backup_create(Storage* storage, const char* destination) { +bool int_backup_create(Storage* storage, const char* destination) { const char* final_destination = - destination && strlen(destination) ? destination : LFS_BACKUP_DEFAULT_LOCATION; + destination && strlen(destination) ? destination : INT_BACKUP_DEFAULT_LOCATION; return storage_int_backup(storage, final_destination) == FSE_OK; } -bool lfs_backup_exists(Storage* storage, const char* source) { - const char* final_source = source && strlen(source) ? source : LFS_BACKUP_DEFAULT_LOCATION; +bool int_backup_exists(Storage* storage, const char* source) { + const char* final_source = source && strlen(source) ? source : INT_BACKUP_DEFAULT_LOCATION; return storage_common_stat(storage, final_source, NULL) == FSE_OK; } -bool lfs_backup_unpack(Storage* storage, const char* source) { - const char* final_source = source && strlen(source) ? source : LFS_BACKUP_DEFAULT_LOCATION; +bool int_backup_unpack(Storage* storage, const char* source) { + const char* final_source = source && strlen(source) ? source : INT_BACKUP_DEFAULT_LOCATION; return storage_int_restore(storage, final_source, backup_name_converter) == FSE_OK; } diff --git a/lib/update_util/int_backup.h b/lib/update_util/int_backup.h new file mode 100644 index 00000000000..168efda5043 --- /dev/null +++ b/lib/update_util/int_backup.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#define INT_BACKUP_DEFAULT_FILENAME "backup.tar" + +#ifdef __cplusplus +extern "C" { +#endif + +bool int_backup_create(Storage* storage, const char* destination); +bool int_backup_exists(Storage* storage, const char* source); +bool int_backup_unpack(Storage* storage, const char* source); + +#ifdef __cplusplus +} +#endif diff --git a/lib/update_util/lfs_backup.h b/lib/update_util/lfs_backup.h deleted file mode 100644 index 5a7738c86ed..00000000000 --- a/lib/update_util/lfs_backup.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include - -#define LFS_BACKUP_DEFAULT_FILENAME "backup.tar" - -#ifdef __cplusplus -extern "C" { -#endif - -bool lfs_backup_create(Storage* storage, const char* destination); -bool lfs_backup_exists(Storage* storage, const char* source); -bool lfs_backup_unpack(Storage* storage, const char* source); - -#ifdef __cplusplus -} -#endif diff --git a/lib/update_util/resources/manifest.c b/lib/update_util/resources/manifest.c index 580a76d457c..92d84a779fc 100644 --- a/lib/update_util/resources/manifest.c +++ b/lib/update_util/resources/manifest.c @@ -1,6 +1,7 @@ #include "manifest.h" #include +#include #include struct ResourceManifestReader { @@ -97,7 +98,12 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res furi_string_right( resource_manifest->linebuf, sizeof(resource_manifest->entry.hash) * 2 + 1); - resource_manifest->entry.size = atoi(furi_string_get_cstr(resource_manifest->linebuf)); + if(strint_to_uint32( + furi_string_get_cstr(resource_manifest->linebuf), + NULL, + &resource_manifest->entry.size, + 10) != StrintParseNoError) + break; /* Remove size */ size_t offs = furi_string_search_char(resource_manifest->linebuf, ':'); diff --git a/scripts/debug/flipperapps.py b/scripts/debug/flipperapps.py index 6dba89a5640..81aa43c34cf 100644 --- a/scripts/debug/flipperapps.py +++ b/scripts/debug/flipperapps.py @@ -124,7 +124,7 @@ def invoke(self, arg, from_tty): print(f"Set '{arg}' as debug info lookup path for Flipper external apps") helper.attach_to_fw() gdb.events.stop.connect(helper.handle_stop) - gdb.events.exited.connect(helper.handle_exit) + gdb.events.gdb_exiting.connect(helper.handle_exit) except gdb.error as e: print(f"Support for Flipper external apps debug is not available: {e}") diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 290531321d6..1a55278dcc2 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -47,6 +47,7 @@ def generate(env): PVSOPTIONS=[ "@.pvsoptions", "-j${PVSNCORES}", + "--disableLicenseExpirationCheck", # "--incremental", # kinda broken on PVS side ], PVSCONVOPTIONS=[ diff --git a/scripts/imglint.py b/scripts/imglint.py index fc63f33555a..f25fea4f5e7 100644 --- a/scripts/imglint.py +++ b/scripts/imglint.py @@ -4,7 +4,7 @@ from pathlib import Path from flipper.app import App -from PIL import Image, ImageOps +from PIL import Image _logger = logging.getLogger(__name__) diff --git a/scripts/update.py b/scripts/update.py index e880bced844..47a5eeb27b0 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -import io import math import os import shutil @@ -8,7 +7,6 @@ import zlib from os.path import exists, join -import heatshrink2 from flipper.app import App from flipper.assets.coprobin import CoproBinary, get_stack_type from flipper.assets.heatshrink_stream import HeatshrinkDataStreamHeader @@ -35,7 +33,12 @@ class Main(App): ) FLASH_BASE = 0x8000000 - MIN_LFS_PAGES = 6 + FLASH_PAGE_SIZE = 4 * 1024 + MIN_GAP_PAGES = 2 + + # Update stage file larger than that is not loadable without fix + # https://github.com/flipperdevices/flipperzero-firmware/pull/3676 + UPDATER_SIZE_THRESHOLD = 128 * 1024 HEATSHRINK_WINDOW_SIZE = 13 HEATSHRINK_LOOKAHEAD_SIZE = 6 @@ -117,7 +120,7 @@ def generate(self): self.logger.error( f"You are trying to bundle a non-standard stack type '{self.args.radiotype}'." ) - self.disclaimer() + self.show_disclaimer() return 1 if radio_addr == 0: @@ -130,7 +133,9 @@ def generate(self): if not exists(self.args.directory): os.makedirs(self.args.directory) + updater_stage_size = os.stat(self.args.stage).st_size shutil.copyfile(self.args.stage, join(self.args.directory, stage_basename)) + dfu_size = 0 if self.args.dfu: dfu_size = os.stat(self.args.dfu).st_size @@ -146,10 +151,10 @@ def generate(self): ): return 3 - if not self.layout_check(dfu_size, radio_addr): + if not self.layout_check(updater_stage_size, dfu_size, radio_addr): self.logger.warn("Memory layout looks suspicious") - if not self.args.disclaimer == "yes": - self.disclaimer() + if self.args.disclaimer != "yes": + self.show_disclaimer() return 2 if self.args.splash: @@ -198,22 +203,33 @@ def generate(self): return 0 - def layout_check(self, fw_size, radio_addr): + def layout_check(self, stage_size, fw_size, radio_addr): + if stage_size > self.UPDATER_SIZE_THRESHOLD: + self.logger.warn( + f"Updater size {stage_size}b > {self.UPDATER_SIZE_THRESHOLD}b and is not loadable on older firmwares!" + ) + if fw_size == 0 or radio_addr == 0: self.logger.info("Cannot validate layout for partial package") return True - lfs_span = radio_addr - self.FLASH_BASE - fw_size - self.logger.debug(f"Expected LFS size: {lfs_span}") - lfs_span_pages = lfs_span / (4 * 1024) - if lfs_span_pages < self.MIN_LFS_PAGES: + fw2stack_gap = radio_addr - self.FLASH_BASE - fw_size + self.logger.debug(f"Expected reserved space size: {fw2stack_gap}") + fw2stack_gap_pages = fw2stack_gap / self.FLASH_PAGE_SIZE + if fw2stack_gap_pages < 0: + self.logger.warn( + f"Firmware image overlaps C2 region and is not programmable!" + ) + return False + + elif fw2stack_gap_pages < self.MIN_GAP_PAGES: self.logger.warn( - f"Expected LFS size is too small (~{int(lfs_span_pages)} pages)" + f"Expected reserved flash size is too small (~{int(fw2stack_gap_pages)} page(s), need >={self.MIN_GAP_PAGES} page(s))" ) return False return True - def disclaimer(self): + def show_disclaimer(self): self.logger.error( "You might brick your device into a state in which you'd need an SWD programmer to fix it." ) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 57cbd1d62e8..a8da2b36630 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,72.5,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -13,13 +13,13 @@ Header,+,applications/services/gui/icon_i.h,, Header,+,applications/services/gui/modules/button_menu.h,, Header,+,applications/services/gui/modules/button_panel.h,, Header,+,applications/services/gui/modules/byte_input.h,, -Header,+,applications/services/gui/modules/number_input.h,, Header,+,applications/services/gui/modules/dialog_ex.h,, Header,+,applications/services/gui/modules/empty_screen.h,, Header,+,applications/services/gui/modules/file_browser.h,, Header,+,applications/services/gui/modules/file_browser_worker.h,, Header,+,applications/services/gui/modules/loading.h,, Header,+,applications/services/gui/modules/menu.h,, +Header,+,applications/services/gui/modules/number_input.h,, Header,+,applications/services/gui/modules/popup.h,, Header,+,applications/services/gui/modules/submenu.h,, Header,+,applications/services/gui/modules/text_box.h,, @@ -170,6 +170,7 @@ Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, +Header,+,lib/toolbox/strint.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/varint.h,, @@ -723,11 +724,6 @@ Function,+,byte_input_free,void,ByteInput* Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" -Function,+,number_input_alloc,NumberInput*, -Function,+,number_input_free,void,NumberInput* -Function,+,number_input_get_view,View*,NumberInput* -Function,+,number_input_set_header_text,void,"NumberInput*, const char*" -Function,+,number_input_set_result_callback,void,"NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t" Function,-,bzero,void,"void*, size_t" Function,+,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* @@ -873,6 +869,8 @@ Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp Function,+,dolphin_deed_get_weight,uint8_t,DolphinDeed Function,+,dolphin_flush,void,Dolphin* Function,+,dolphin_get_pubsub,FuriPubSub*,Dolphin* +Function,+,dolphin_get_settings,void,"Dolphin*, DolphinSettings*" +Function,+,dolphin_set_settings,void,"Dolphin*, DolphinSettings*" Function,+,dolphin_stats,DolphinStats,Dolphin* Function,+,dolphin_upgrade_level,void,Dolphin* Function,-,dprintf,int,"int, const char*, ..." @@ -883,8 +881,10 @@ Function,+,elements_bold_rounded_frame,void,"Canvas*, int32_t, int32_t, size_t, Function,+,elements_bubble,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_bubble_str,void,"Canvas*, int32_t, int32_t, const char*, Align, Align" Function,+,elements_button_center,void,"Canvas*, const char*" +Function,+,elements_button_down,void,"Canvas*, const char*" Function,+,elements_button_left,void,"Canvas*, const char*" Function,+,elements_button_right,void,"Canvas*, const char*" +Function,+,elements_button_up,void,"Canvas*, const char*" Function,+,elements_frame,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_multiline_text,void,"Canvas*, int32_t, int32_t, const char*" Function,+,elements_multiline_text_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" @@ -2197,6 +2197,11 @@ Function,+,notification_internal_message_block,void,"NotificationApp*, const Not Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" Function,-,nrand48,long,unsigned short[3] +Function,+,number_input_alloc,NumberInput*, +Function,+,number_input_free,void,NumberInput* +Function,+,number_input_get_view,View*,NumberInput* +Function,+,number_input_set_header_text,void,"NumberInput*, const char*" +Function,+,number_input_set_result_callback,void,"NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t" Function,-,on_exit,int,"void (*)(int, void*), void*" Function,+,onewire_host_alloc,OneWireHost*,const GpioPin* Function,+,onewire_host_free,void,OneWireHost* @@ -2587,7 +2592,13 @@ Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" Function,+,string_stream_alloc,Stream*, -Function,-,strlcat,size_t,"char*, const char*, size_t" +Function,+,strint_to_int16,StrintParseError,"const char*, char**, int16_t*, uint8_t" +Function,+,strint_to_int32,StrintParseError,"const char*, char**, int32_t*, uint8_t" +Function,+,strint_to_int64,StrintParseError,"const char*, char**, int64_t*, uint8_t" +Function,+,strint_to_uint16,StrintParseError,"const char*, char**, uint16_t*, uint8_t" +Function,+,strint_to_uint32,StrintParseError,"const char*, char**, uint32_t*, uint8_t" +Function,+,strint_to_uint64,StrintParseError,"const char*, char**, uint64_t*, uint8_t" +Function,+,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" Function,+,strlen,size_t,const char* Function,-,strlwr,char*,char* @@ -2750,7 +2761,7 @@ Function,+,view_dispatcher_alloc,ViewDispatcher*, Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* Function,+,view_dispatcher_free,void,ViewDispatcher* -Function,-,view_dispatcher_get_event_loop,FuriEventLoop*,ViewDispatcher* +Function,+,view_dispatcher_get_event_loop,FuriEventLoop*,ViewDispatcher* Function,+,view_dispatcher_remove_view,void,"ViewDispatcher*, uint32_t" Function,+,view_dispatcher_run,void,ViewDispatcher* Function,+,view_dispatcher_send_custom_event,void,"ViewDispatcher*, uint32_t" diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 247c2ee2d5b..b2860692104 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -40,6 +40,7 @@ void furi_hal_init(void) { furi_hal_interrupt_init(); furi_hal_flash_init(); furi_hal_resources_init(); + furi_hal_region_init(); furi_hal_spi_config_init(); furi_hal_spi_dma_init(); furi_hal_speaker_init(); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e44b3356c67..c294a3b7d03 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,72.5,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -242,6 +242,7 @@ Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, +Header,+,lib/toolbox/strint.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/varint.h,, @@ -955,6 +956,8 @@ Function,+,dolphin_deed_get_app_limit,uint8_t,DolphinApp Function,+,dolphin_deed_get_weight,uint8_t,DolphinDeed Function,+,dolphin_flush,void,Dolphin* Function,+,dolphin_get_pubsub,FuriPubSub*,Dolphin* +Function,+,dolphin_get_settings,void,"Dolphin*, DolphinSettings*" +Function,+,dolphin_set_settings,void,"Dolphin*, DolphinSettings*" Function,+,dolphin_stats,DolphinStats,Dolphin* Function,+,dolphin_upgrade_level,void,Dolphin* Function,-,dprintf,int,"int, const char*, ..." @@ -965,8 +968,10 @@ Function,+,elements_bold_rounded_frame,void,"Canvas*, int32_t, int32_t, size_t, Function,+,elements_bubble,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_bubble_str,void,"Canvas*, int32_t, int32_t, const char*, Align, Align" Function,+,elements_button_center,void,"Canvas*, const char*" +Function,+,elements_button_down,void,"Canvas*, const char*" Function,+,elements_button_left,void,"Canvas*, const char*" Function,+,elements_button_right,void,"Canvas*, const char*" +Function,+,elements_button_up,void,"Canvas*, const char*" Function,+,elements_frame,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_multiline_text,void,"Canvas*, int32_t, int32_t, const char*" Function,+,elements_multiline_text_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" @@ -3264,7 +3269,13 @@ Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" Function,+,string_stream_alloc,Stream*, -Function,-,strlcat,size_t,"char*, const char*, size_t" +Function,+,strint_to_int16,StrintParseError,"const char*, char**, int16_t*, uint8_t" +Function,+,strint_to_int32,StrintParseError,"const char*, char**, int32_t*, uint8_t" +Function,+,strint_to_int64,StrintParseError,"const char*, char**, int64_t*, uint8_t" +Function,+,strint_to_uint16,StrintParseError,"const char*, char**, uint16_t*, uint8_t" +Function,+,strint_to_uint32,StrintParseError,"const char*, char**, uint32_t*, uint8_t" +Function,+,strint_to_uint64,StrintParseError,"const char*, char**, uint64_t*, uint8_t" +Function,+,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" Function,+,strlen,size_t,const char* Function,-,strlwr,char*,char* @@ -3591,7 +3602,7 @@ Function,+,view_dispatcher_alloc,ViewDispatcher*, Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* Function,+,view_dispatcher_free,void,ViewDispatcher* -Function,-,view_dispatcher_get_event_loop,FuriEventLoop*,ViewDispatcher* +Function,+,view_dispatcher_get_event_loop,FuriEventLoop*,ViewDispatcher* Function,+,view_dispatcher_remove_view,void,"ViewDispatcher*, uint32_t" Function,+,view_dispatcher_run,void,ViewDispatcher* Function,+,view_dispatcher_send_custom_event,void,"ViewDispatcher*, uint32_t" diff --git a/targets/f7/furi_hal/furi_hal_rtc.h b/targets/f7/furi_hal/furi_hal_rtc.h index 030b464cf7a..c5eab12ec5f 100644 --- a/targets/f7/furi_hal/furi_hal_rtc.h +++ b/targets/f7/furi_hal/furi_hal_rtc.h @@ -9,6 +9,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -44,7 +45,7 @@ typedef enum { FuriHalRtcRegisterHeader, /**< RTC structure header */ FuriHalRtcRegisterSystem, /**< Various system bits */ FuriHalRtcRegisterVersion, /**< Pointer to Version */ - FuriHalRtcRegisterLfsFingerprint, /**< LFS geometry fingerprint */ + FuriHalRtcRegisterLfsFingerprint FURI_DEPRECATED, /**< LFS geometry fingerprint */ FuriHalRtcRegisterFaultData, /**< Pointer to last fault message */ FuriHalRtcRegisterPinFails, /**< Failed PINs count */ /* Index of FS directory entry corresponding to FW update to be applied */ diff --git a/targets/f7/furi_hal/furi_hal_version.c b/targets/f7/furi_hal/furi_hal_version.c index bd449e59936..2859ae3626c 100644 --- a/targets/f7/furi_hal/furi_hal_version.c +++ b/targets/f7/furi_hal/furi_hal_version.c @@ -99,7 +99,7 @@ static void furi_hal_version_set_name(const char* name) { "xFlipper %s", furi_hal_version.name); } else { - snprintf(furi_hal_version.device_name, FURI_HAL_VERSION_DEVICE_NAME_LENGTH, "xFlipper"); + strlcpy(furi_hal_version.device_name, "xFlipper", FURI_HAL_VERSION_DEVICE_NAME_LENGTH); } furi_hal_version.device_name[0] = AD_TYPE_COMPLETE_LOCAL_NAME;