Merge remote-tracking branch 'origin/dev' into nestednonces

This commit is contained in:
Aleksandr Kutuzov 2024-09-07 13:47:07 +01:00
commit 9ea7a4671a
110 changed files with 2019 additions and 697 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1,49 +1,48 @@
/* Implements rudimentary iso7816-3 support for APDU (T=0) */
#include <stdint.h>
#include <string.h>
#include <furi.h>
#include <furi_hal.h>
#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;

View File

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

View File

@ -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 <furi.h>
@ -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);

View File

@ -1,5 +1,7 @@
#include "../rpc_debug_app.h"
#include <lib/toolbox/strint.h>
static bool rpc_debug_app_scene_input_error_code_validator_callback(
const char* text,
FuriString* error,
@ -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);

View File

@ -6,6 +6,8 @@
#include <gui/view_dispatcher.h>
#include <gui/modules/dialog_ex.h>
#include <lib/toolbox/strint.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
@ -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;
}

View File

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

View File

@ -0,0 +1,142 @@
#include <furi.h>
#include <furi_hal.h>
#include "../test.h" // IWYU pragma: keep
#include <toolbox/strint.h>
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)

View File

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

View File

@ -3,6 +3,7 @@
#include <gui/gui.h>
#include <input/input.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include <storage/storage.h>
#include "ducky_script.h"
#include "ducky_script_i.h"
@ -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;
}

View File

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

View File

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

View File

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

View File

@ -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);
InfraredErrorCode error = infrared_remote_load(infrared->remote, file_path);
if(!is_remote_loaded) {
infrared_show_error_message(infrared, "Failed to load\n\"%s\"", 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;
}

View File

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

View File

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

View File

@ -10,6 +10,7 @@
#include <stdint.h>
#include <stdbool.h>
#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.

View File

@ -5,6 +5,7 @@
#include <furi_hal_infrared.h>
#include <flipper_format.h>
#include <toolbox/args.h>
#include <toolbox/strint.h>
#include <m-dict.h>
#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;
}

View File

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

View File

@ -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;
}
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) ||
!flipper_format_buffered_file_close(batch_context.ff_in)) {
error = InfraredErrorCodeFileOperationFailed;
break;
}
if(batch_context.signal_index != signal_count) break;
if(!flipper_format_buffered_file_close(batch_context.ff_out)) break;
if(!flipper_format_buffered_file_close(batch_context.ff_in)) 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(!furi_string_equal(tmp, INFRARED_FILE_HEADER) || (version != INFRARED_FILE_VERSION))
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(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;
}

View File

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

View File

@ -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))
}
if(!flipper_format_read_hex(
ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message.command, 4)) {
error = InfraredErrorCodeSignalMessageUnableToReadCommand;
break;
if(!infrared_signal_is_message_valid(&message)) break;
}
if(!infrared_signal_is_message_valid(&message)) {
error = InfraredErrorCodeSignalMessageIsInvalid;
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(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_RAW)) {
if(!infrared_signal_read_raw(signal, ff)) break;
} else if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_PARSED)) {
if(!infrared_signal_read_message(signal, ff)) break;
} else {
FURI_LOG_E(TAG, "Unknown signal type: %s", furi_string_get_cstr(tmp));
if(!flipper_format_read_string(ff, INFRARED_SIGNAL_TYPE_KEY, tmp)) {
error = InfraredErrorCodeSignalUnableToReadType;
break;
}
success = true;
if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_RAW)) {
error = infrared_signal_read_raw(signal, ff);
} else if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_PARSED)) {
error = infrared_signal_read_message(signal, ff);
} else {
FURI_LOG_E(TAG, "Unknown signal type: %s", furi_string_get_cstr(tmp));
error = InfraredErrorCodeSignalTypeUnknown;
break;
}
} 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) {

View File

@ -8,6 +8,7 @@
*/
#pragma once
#include "infrared_error_code.h"
#include <flipper_format/flipper_format.h>
#include <infrared/encoder_decoder/infrared.h>
@ -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.

View File

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

View File

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

View File

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

View File

@ -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);
const InfraredErrorCode task_error = 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);
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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@
#include <flipper_application/flipper_application.h>
#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
#include <lib/toolbox/strint.h>
#include <applications/services/locale/locale.h>
#include <datetime/datetime.h>
@ -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");

View File

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

View File

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

View File

@ -3,18 +3,20 @@
#include <furi.h>
#include <furi_hal.h>
#include <lib/toolbox/args.h>
#include <lib/subghz/subghz_keystore.h>
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/subghz_keystore.h>
#include <lib/subghz/receiver.h>
#include <lib/subghz/transmitter.h>
#include <lib/subghz/subghz_file_encoder_worker.h>
#include <lib/subghz/protocols/protocol_items.h>
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
#include <lib/subghz/devices/devices.h>
#include <lib/subghz/devices/cc1101_configs.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include "helpers/subghz_chat.h"
#include <notification/notification_messages.h>
@ -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", "<Frequency: in Hz>", 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", "<Frequency: in Hz>", 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> <Frequency: in Hz> <Te us> <Repeat count> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",
@ -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",
"<Frequency: in Hz> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",
@ -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", "<Frequency: in Hz>", 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:",
"<file_name: path_file> <Repeat count> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",
@ -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",
"<Frequency: in Hz> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",

View File

@ -1,7 +1,6 @@
#pragma once
#include "helpers/subghz_types.h"
#include "helpers/subghz_error_type.h"
#include <lib/subghz/types.h>
#include "subghz.h"
#include "views/receiver.h"

View File

@ -9,6 +9,7 @@
#include <notification/notification_messages.h>
#include <loader/loader.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
// 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", "<r|g|b|bl> <0-255>", furi_string_get_cstr(args));
return;
}

View File

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

View File

@ -3,6 +3,7 @@
#include <stdint.h>
#include <stdbool.h>
#define DESKTOP_PIN_CODE_MIN_LEN (4)
#define DESKTOP_PIN_CODE_MAX_LEN (10)
typedef struct {

View File

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

View File

@ -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);
@ -211,7 +231,9 @@ static bool dolphin_process_event(FuriEventLoopObject* object, void* context) {
} 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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@
#include <gui/elements.h>
#include <furi.h>
#include <assets_icons.h>
#include <lib/toolbox/strint.h>
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;
}

View File

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

View File

@ -4,6 +4,7 @@
#include <cli/cli.h>
#include <applications.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include <notification/notification_messages.h>
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");
}
}

View File

@ -13,6 +13,7 @@
#pragma once
#include "rpc.h"
#include "rpc_app_error_codes.h"
#ifdef __cplusplus
extern "C" {

View File

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

View File

@ -2,6 +2,7 @@
#include "rpc_i.h"
#include "gpio.pb.h"
#include <furi_hal_gpio.h>
#include <furi_hal_power.h>
#include <furi_hal_resources.h>
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;
}

View File

@ -7,7 +7,7 @@
#include <storage/storage.h>
#include <lib/toolbox/md5_calc.h>
#include <lib/toolbox/path.h>
#include <update_util/lfs_backup.h>
#include <update_util/int_backup.h>
#include <toolbox/tar/tar_archive.h>
#include <pb_decode.h>
@ -656,7 +656,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 +676,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(

View File

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

View File

@ -3,8 +3,9 @@
#include <cli/cli.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/md5_calc.h>
#include <lib/toolbox/dir_walk.h>
#include <lib/toolbox/md5_calc.h>
#include <lib/toolbox/strint.h>
#include <lib/toolbox/tar/tar_archive.h>
#include <storage/storage.h>
#include <storage/storage_sd_api.h>
@ -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)) {

View File

@ -1,5 +1,6 @@
#include <furi.h>
#include <gui/modules/popup.h>
#include <gui/modules/dialog_ex.h>
#include <gui/scene_manager.h>
#include <desktop/desktop.h>
@ -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);

View File

@ -6,6 +6,7 @@
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/dialog_ex.h>
#include <dialogs/dialogs.h>
#include <assets_icons.h>
@ -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;

View File

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

View File

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

View File

@ -0,0 +1,64 @@
#include <furi.h>
#include <gui/scene_manager.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/dialog_ex.h>
#include <dolphin/dolphin.h>
#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);
}

View File

@ -3,15 +3,12 @@
#include <gui/scene_manager.h>
#include <desktop/helpers/pin_code.h>
#include "../desktop_settings_app.h"
#include "../desktop_settings_custom_event.h"
#include <desktop/desktop_settings.h>
#include <desktop/views/desktop_view_pin_input.h>
#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;

View File

@ -3,15 +3,14 @@
#include <gui/modules/popup.h>
#include "../desktop_settings_app.h"
#include "../desktop_settings_custom_event.h"
#include <desktop/desktop_settings.h>
#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;

View File

@ -8,20 +8,19 @@
#include "desktop_settings_scene_i.h"
#include <desktop/helpers/pin_code.h>
#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;

View File

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

View File

@ -8,11 +8,7 @@
#include "desktop_settings_scene.h"
#include "desktop_settings_scene_i.h"
#include <desktop/helpers/pin_code.h>
#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);

View File

@ -5,18 +5,17 @@
#include <gui/view_dispatcher.h>
#include "../desktop_settings_app.h"
#include "../desktop_settings_custom_event.h"
#include <desktop/desktop_settings.h>
#include <desktop/views/desktop_view_pin_input.h>
#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);

View File

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

View File

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

View File

@ -2,12 +2,10 @@
#include <applications.h>
#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,

View File

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

View File

@ -1,4 +1,5 @@
#include "../power_settings_app.h"
#include <dolphin/dolphin.h>
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);

View File

@ -8,7 +8,7 @@
#include <toolbox/tar/tar_archive.h>
#include <toolbox/args.h>
#include <update_util/update_manifest.h>
#include <update_util/lfs_backup.h>
#include <update_util/int_backup.h>
#include <update_util/update_operation.h>
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");
}

View File

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

View File

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

View File

@ -7,7 +7,7 @@
#include <desktop/helpers/slideshow_filename.h>
#include <toolbox/path.h>
#include <update_util/dfu_file.h>
#include <update_util/lfs_backup.h>
#include <update_util/int_backup.h>
#include <update_util/update_operation.h>
#include <update_util/resources/manifest.h>
#include <toolbox/tar/tar_archive.h>
@ -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 = {

View File

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

@ -1 +1 @@
Subproject commit 816de200a4a43efc25c5b92d6a57fc982d7e988a
Subproject commit 6c7c0d55e82cb89223cf4890a540af4cff837fa7

View File

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

View File

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

View File

@ -1,5 +1,6 @@
#include <inttypes.h>
#include <toolbox/hex.h>
#include <toolbox/strint.h>
#include <core/check.h>
#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: {

View File

@ -39,11 +39,12 @@ void protocol_gproxii_free(ProtocolGProxII* protocol) {
}
uint8_t* protocol_gproxii_get_data(ProtocolGProxII* proto) {
return proto->data;
return proto->decoded_data;
}
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;
}
@ -88,8 +89,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);

View File

@ -4,6 +4,7 @@
#include <flipper_format/flipper_format.h>
#include <flipper_format/flipper_format_i.h>
#include <lib/subghz/devices/devices.h>
#include <lib/toolbox/strint.h>
#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, ' ');
str = strchr(str, ' ');
// Check that there is still an element in the line
while(strchr(str1, ' ') != NULL) {
str1 = strchr(str1, ' ');
// 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;
}

View File

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

View File

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

121
lib/toolbox/strint.c Normal file
View File

@ -0,0 +1,121 @@
#include "strint.h"
#include <string.h>
// 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)

70
lib/toolbox/strint.h Normal file
View File

@ -0,0 +1,70 @@
/**
* @file strint.h
* Performs conversions between strings and integers.
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#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

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