This commit is contained in:
Willy-JL
2024-03-17 04:35:59 +00:00
264 changed files with 2203 additions and 904 deletions

View File

@@ -19,7 +19,7 @@ bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
furi_string_set(app->file_path, ANY_PATH("badkb/demo_windows.txt"));
furi_string_set(app->file_path, ANY_PATH("badusb/demo_windows.txt"));
scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser);
consumed = true;
} else if(event.type == SceneManagerEventTypeTick) {

View File

@@ -112,7 +112,7 @@ MU_TEST_SUITE(test_datetime_validate_datetime) {
MU_TEST(test_datetime_timestamp_to_datetime_min) {
uint32_t test_value = 0;
DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 0};
DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 4};
DateTime result = {0};
datetime_timestamp_to_datetime(test_value, &result);
@@ -122,7 +122,7 @@ MU_TEST(test_datetime_timestamp_to_datetime_min) {
MU_TEST(test_datetime_timestamp_to_datetime_max) {
uint32_t test_value = UINT32_MAX;
DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 0};
DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 7};
DateTime result = {0};
datetime_timestamp_to_datetime(test_value, &result);
@@ -141,10 +141,26 @@ MU_TEST(test_datetime_timestamp_to_datetime_to_timestamp) {
mu_assert_int_eq(test_value, result);
}
MU_TEST(test_datetime_timestamp_to_datetime_weekday) {
uint32_t test_value = 1709748421; // Wed Mar 06 18:07:01 2024 UTC
DateTime datetime = {0};
datetime_timestamp_to_datetime(test_value, &datetime);
mu_assert_int_eq(datetime.hour, 18);
mu_assert_int_eq(datetime.minute, 7);
mu_assert_int_eq(datetime.second, 1);
mu_assert_int_eq(datetime.day, 6);
mu_assert_int_eq(datetime.month, 3);
mu_assert_int_eq(datetime.weekday, 3);
mu_assert_int_eq(datetime.year, 2024);
}
MU_TEST_SUITE(test_datetime_timestamp_to_datetime_suite) {
MU_RUN_TEST(test_datetime_timestamp_to_datetime_min);
MU_RUN_TEST(test_datetime_timestamp_to_datetime_max);
MU_RUN_TEST(test_datetime_timestamp_to_datetime_to_timestamp);
MU_RUN_TEST(test_datetime_timestamp_to_datetime_weekday);
}
MU_TEST(test_datetime_datetime_to_timestamp_min) {

View File

@@ -14,7 +14,7 @@ static const char* tab_default_paths[] = {
[ArchiveTabSubGhz] = EXT_PATH("subghz"),
[ArchiveTabLFRFID] = EXT_PATH("lfrfid"),
[ArchiveTabInfrared] = EXT_PATH("infrared"),
[ArchiveTabBadKb] = EXT_PATH("badkb"),
[ArchiveTabBadKb] = EXT_PATH("badusb"),
[ArchiveTabU2f] = "/app:u2f",
[ArchiveTabApplications] = EXT_PATH("apps"),
[ArchiveTabSearch] = "/app:search",

View File

@@ -3,7 +3,6 @@
#include <furi_hal.h>
#include <storage/storage.h>
#include <lib/toolbox/path.h>
#include <momentum/momentum.h>
#include <lib/flipper_format/flipper_format.h>
#include <bt/bt_service/bt_i.h>
#include "helpers/ducky_script_i.h"
@@ -29,7 +28,7 @@ static void bad_kb_app_tick_event_callback(void* context) {
scene_manager_handle_tick_event(app->scene_manager);
}
static void bad_kb_load_settings(BadKbApp* app) {
void bad_kb_load_settings(BadKbApp* app) {
furi_string_reset(app->keyboard_layout);
BadKbConfig* cfg = &app->config;
@@ -37,35 +36,59 @@ static void bad_kb_load_settings(BadKbApp* app) {
FlipperFormat* file = flipper_format_file_alloc(storage);
if(flipper_format_file_open_existing(file, BAD_KB_SETTINGS_PATH)) {
FuriString* tmp_str = furi_string_alloc();
if(!flipper_format_read_string(file, "Keyboard_Layout", app->keyboard_layout)) {
furi_string_reset(app->keyboard_layout);
flipper_format_rewind(file);
}
if(flipper_format_read_string(file, "Bt_Name", tmp_str) && !furi_string_empty(tmp_str)) {
if(!flipper_format_read_bool(file, "Is_Bt", &app->is_bt, 1)) {
app->is_bt = false;
flipper_format_rewind(file);
}
if(!flipper_format_read_bool(file, "Bt_Remember", &app->bt_remember, 1)) {
app->bt_remember = false;
flipper_format_rewind(file);
}
if(flipper_format_read_string(file, "Bt_Name", tmp_str)) {
strlcpy(cfg->ble.name, furi_string_get_cstr(tmp_str), sizeof(cfg->ble.name));
} else {
strcpy(cfg->ble.name, "");
cfg->ble.name[0] = '\0';
flipper_format_rewind(file);
}
if(!flipper_format_read_hex(
file, "Bt_Mac", (uint8_t*)&cfg->ble.mac, sizeof(cfg->ble.mac))) {
memset(cfg->ble.mac, 0, sizeof(cfg->ble.mac));
flipper_format_rewind(file);
}
if(flipper_format_read_string(file, "Usb_Manuf", tmp_str) && !furi_string_empty(tmp_str)) {
if(flipper_format_read_string(file, "Usb_Manuf", tmp_str)) {
strlcpy(cfg->usb.manuf, furi_string_get_cstr(tmp_str), sizeof(cfg->usb.manuf));
} else {
strcpy(cfg->usb.manuf, "");
cfg->usb.manuf[0] = '\0';
flipper_format_rewind(file);
}
if(flipper_format_read_string(file, "Usb_Product", tmp_str) &&
!furi_string_empty(tmp_str)) {
if(flipper_format_read_string(file, "Usb_Product", tmp_str)) {
strlcpy(cfg->usb.product, furi_string_get_cstr(tmp_str), sizeof(cfg->usb.product));
} else {
strcpy(cfg->usb.product, "");
cfg->usb.product[0] = '\0';
flipper_format_rewind(file);
}
if(!flipper_format_read_uint32(file, "Usb_Vid", &cfg->usb.vid, 1)) {
cfg->usb.vid = 0;
flipper_format_rewind(file);
}
if(!flipper_format_read_uint32(file, "Usb_Pid", &cfg->usb.pid, 1)) {
cfg->usb.pid = 0;
flipper_format_rewind(file);
}
furi_string_free(tmp_str);
flipper_format_file_close(file);
}
@@ -93,6 +116,8 @@ static void bad_kb_save_settings(BadKbApp* app) {
FlipperFormat* file = flipper_format_file_alloc(storage);
if(flipper_format_file_open_always(file, BAD_KB_SETTINGS_PATH)) {
flipper_format_write_string(file, "Keyboard_Layout", app->keyboard_layout);
flipper_format_write_bool(file, "Is_Bt", &app->is_bt, 1);
flipper_format_write_bool(file, "Bt_Remember", &app->bt_remember, 1);
flipper_format_write_string_cstr(file, "Bt_Name", cfg->ble.name);
flipper_format_write_hex(file, "Bt_Mac", (uint8_t*)&cfg->ble.mac, sizeof(cfg->ble.mac));
flipper_format_write_string_cstr(file, "Usb_Manuf", cfg->usb.manuf);
@@ -265,14 +290,6 @@ void bad_kb_config_refresh(BadKbApp* app) {
// Reload config page
scene_manager_next_scene(app->scene_manager, BadKbSceneConfig);
scene_manager_previous_scene(app->scene_manager);
// Update settings
if(momentum_settings.bad_bt != app->is_bt ||
momentum_settings.bad_bt_remember != app->bt_remember) {
momentum_settings.bad_bt = app->is_bt;
momentum_settings.bad_bt_remember = app->bt_remember;
momentum_settings_save();
}
}
BadKbApp* bad_kb_app_alloc(char* arg) {
@@ -308,8 +325,6 @@ BadKbApp* bad_kb_app_alloc(char* arg) {
Bt* bt = furi_record_open(RECORD_BT);
app->bt = bt;
app->bt->suppress_pin_screen = true;
app->is_bt = momentum_settings.bad_bt;
app->bt_remember = momentum_settings.bad_bt_remember;
bad_kb_config_adjust(&app->config);
// Save prev config

View File

@@ -102,6 +102,8 @@ typedef enum {
void bad_kb_app_show_loading_popup(BadKbApp* app, bool show);
void bad_kb_load_settings(BadKbApp* app);
int32_t bad_kb_conn_apply(BadKbApp* app);
void bad_kb_conn_reset(BadKbApp* app);

View File

@@ -2,7 +2,7 @@
#include <storage/storage.h>
#define BAD_KB_APP_BASE_FOLDER EXT_PATH("badkb")
#define BAD_KB_APP_BASE_FOLDER EXT_PATH("badusb")
#define BAD_KB_KEYS_PATH BAD_KB_APP_BASE_FOLDER "/.badkb.keys"
#define BAD_KB_SETTINGS_PATH BAD_KB_APP_BASE_FOLDER "/.badkb.settings"
#define BAD_KB_APP_PATH_LAYOUT_FOLDER BAD_KB_APP_BASE_FOLDER "/assets/layouts"

View File

@@ -1,7 +1,6 @@
#include "../bad_kb_app_i.h"
#include "furi_hal_power.h"
#include "furi_hal_usb.h"
#include <momentum/momentum.h>
enum VarItemListIndex {
VarItemListIndexKeyboardLayout,

View File

@@ -8,15 +8,16 @@ static bool bad_kb_file_select(BadKbApp* bad_kb) {
bad_kb_app_show_loading_popup(bad_kb, true);
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_dir_exists(storage, EXT_PATH("badusb"))) {
if(storage_dir_exists(storage, EXT_PATH("badkb"))) {
DialogMessage* message = dialog_message_alloc();
dialog_message_set_header(message, "Migrate BadUSB?", 64, 0, AlignCenter, AlignTop);
dialog_message_set_header(message, "Migrate Scripts?", 64, 0, AlignCenter, AlignTop);
dialog_message_set_buttons(message, "No", NULL, "Yes");
dialog_message_set_text(
message,
"A badusb folder was found!\n"
"Momentum uses the badkb folder.\n"
"Want to transfer the files?",
"Momentum uses the 'badusb'\n"
"folder for compatibility.\n"
"Want to migrate from\n"
"'badkb' folder?",
64,
32,
AlignCenter,
@@ -25,7 +26,12 @@ static bool bad_kb_file_select(BadKbApp* bad_kb) {
dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
if(res == DialogMessageButtonRight) {
storage_common_migrate(storage, EXT_PATH("badusb"), BAD_KB_APP_BASE_FOLDER);
storage_common_migrate(storage, EXT_PATH("badkb"), BAD_KB_APP_BASE_FOLDER);
if(bad_kb->conn_init_thread) {
furi_thread_join(bad_kb->conn_init_thread);
}
bad_kb_load_settings(bad_kb);
bad_kb_config_adjust(&bad_kb->config);
}
}
storage_simply_mkdir(storage, BAD_KB_APP_BASE_FOLDER);

View File

@@ -4,7 +4,6 @@
#include <toolbox/path.h>
#include <gui/elements.h>
#include <assets_icons.h>
#include <momentum/momentum.h>
#include <bt/bt_service/bt_i.h>
#define MAX_NAME_LEN 64

View File

@@ -11,11 +11,20 @@ App(
fap_category="iButton",
)
App(
appid="ibutton_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="ibutton_cli_plugin_ep",
requires=["cli"],
sources=["ibutton_cli.c"],
)
App(
appid="ibutton_start",
apptype=FlipperAppType.STARTUP,
targets=["f7"],
entry_point="ibutton_on_system_start",
sources=["ibutton_cli.c"],
sources=["ibutton_start.c"],
order=60,
)

View File

@@ -8,19 +8,6 @@
#include <ibutton/ibutton_worker.h>
#include <ibutton/ibutton_protocols.h>
static void ibutton_cli(Cli* cli, FuriString* args, void* context);
// app cli function
void ibutton_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli, cli);
furi_record_close(RECORD_CLI);
#else
UNUSED(ibutton_cli);
#endif
}
static void ibutton_cli_print_usage() {
printf("Usage:\r\n");
printf("ikey read\r\n");
@@ -252,3 +239,15 @@ void ibutton_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "ibutton_cli",
.ep_api_version = 1,
.entry_point = &ibutton_cli,
};
const FlipperAppPluginDescriptor* ibutton_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,11 @@
#include <cli/cli_i.h>
static void ibutton_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("ibutton_cli", 1, cli, args, context);
}
void ibutton_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli_wrapper, cli);
furi_record_close(RECORD_CLI);
}

View File

@@ -14,14 +14,23 @@ App(
)
App(
appid="infrared_start",
apptype=FlipperAppType.STARTUP,
appid="infrared_cli",
targets=["f7"],
entry_point="infrared_on_system_start",
apptype=FlipperAppType.PLUGIN,
entry_point="infrared_cli_plugin_ep",
requires=["cli"],
sources=[
"infrared_cli.c",
"infrared_brute_force.c",
"infrared_signal.c",
],
)
App(
appid="infrared_start",
apptype=FlipperAppType.STARTUP,
targets=["f7"],
entry_point="infrared_on_system_start",
sources=["infrared_start.c"],
order=20,
)

View File

@@ -6,7 +6,8 @@
#define TAG "InfraredApp"
#define INFRARED_TX_MIN_INTERVAL_MS 50U
#define INFRARED_TX_MIN_INTERVAL_MS (50U)
#define INFRARED_TASK_STACK_SIZE (2048UL)
static const NotificationSequence*
infrared_notification_sequences[InfraredNotificationMessageCount] = {
@@ -128,6 +129,8 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path)
static InfraredApp* infrared_alloc() {
InfraredApp* infrared = malloc(sizeof(InfraredApp));
infrared->task_thread =
furi_thread_alloc_ex("InfraredTask", INFRARED_TASK_STACK_SIZE, NULL, infrared);
infrared->file_path = furi_string_alloc();
infrared->button_name = furi_string_alloc();
@@ -192,6 +195,10 @@ static InfraredApp* infrared_alloc() {
view_dispatcher_add_view(
view_dispatcher, InfraredViewMove, infrared_move_view_get_view(infrared->move_view));
infrared->loading = loading_alloc();
view_dispatcher_add_view(
view_dispatcher, InfraredViewLoading, loading_get_view(infrared->loading));
if(app_state->is_debug_enabled) {
infrared->debug_view = infrared_debug_view_alloc();
view_dispatcher_add_view(
@@ -201,7 +208,6 @@ static InfraredApp* infrared_alloc() {
}
infrared->button_panel = button_panel_alloc();
infrared->loading = loading_alloc();
infrared->progress = infrared_progress_view_alloc();
infrared->last_settings = infrared_last_settings_alloc();
@@ -213,6 +219,10 @@ static InfraredApp* infrared_alloc() {
static void infrared_free(InfraredApp* infrared) {
furi_assert(infrared);
furi_thread_join(infrared->task_thread);
furi_thread_free(infrared->task_thread);
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
InfraredAppState* app_state = &infrared->app_state;
@@ -246,13 +256,15 @@ static void infrared_free(InfraredApp* infrared) {
view_dispatcher_remove_view(view_dispatcher, InfraredViewMove);
infrared_move_view_free(infrared->move_view);
view_dispatcher_remove_view(view_dispatcher, InfraredViewLoading);
loading_free(infrared->loading);
if(app_state->is_debug_enabled) {
view_dispatcher_remove_view(view_dispatcher, InfraredViewDebugView);
infrared_debug_view_free(infrared->debug_view);
}
button_panel_free(infrared->button_panel);
loading_free(infrared->loading);
infrared_progress_view_free(infrared->progress);
view_dispatcher_free(view_dispatcher);
@@ -393,6 +405,17 @@ void infrared_tx_stop(InfraredApp* infrared) {
infrared->app_state.last_transmit_time = furi_get_tick();
}
void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) {
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewLoading);
furi_thread_set_callback(infrared->task_thread, callback);
furi_thread_start(infrared->task_thread);
}
bool infrared_blocking_task_finalize(InfraredApp* infrared) {
furi_thread_join(infrared->task_thread);
return furi_thread_get_return_code(infrared->task_thread);
}
void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
@@ -413,21 +436,6 @@ void infrared_play_notification_message(
notification_message(infrared->notifications, infrared_notification_sequences[message]);
}
void infrared_show_loading_popup(const InfraredApp* infrared, bool show) {
ViewStack* view_stack = infrared->view_stack;
Loading* loading = infrared->loading;
if(show) {
// Raise timer priority so that animations can play
furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
view_stack_add_view(view_stack, loading_get_view(loading));
} else {
view_stack_remove_view(view_stack, loading_get_view(loading));
// Restore default timer priority
furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
}
}
void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) {
va_list args;
va_start(args, fmt);

View File

@@ -20,12 +20,13 @@
#include <gui/modules/button_panel.h>
#include <gui/modules/variable_item_list.h>
#include <rpc/rpc_app.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include <notification/notification_messages.h>
#include <infrared_worker.h>
#include <infrared/worker/infrared_worker.h>
#include "infrared_app.h"
#include "infrared_remote.h"
@@ -38,9 +39,6 @@
#include "views/infrared_debug_view.h"
#include "views/infrared_move_view.h"
#include "rpc/rpc_app.h"
#include <furi_hal_infrared.h>
#define INFRARED_FILE_NAME_SIZE 100
#define INFRARED_TEXT_STORE_NUM 2
#define INFRARED_TEXT_STORE_SIZE 128
@@ -124,6 +122,7 @@ struct InfraredApp {
Loading* loading; /**< Standard view for informing about long operations. */
InfraredProgressView* progress; /**< Custom view for showing brute force progress. */
FuriThread* task_thread; /**< Pointer to a FuriThread instance for concurrent tasks. */
FuriString* file_path; /**< Full path to the currently loaded file. */
FuriString* button_name; /**< Name of the button requested in RPC mode. */
/** Arbitrary text storage for various inputs. */
@@ -147,6 +146,7 @@ typedef enum {
InfraredViewDebugView,
InfraredViewMove,
InfraredViewVariableItemList,
InfraredViewLoading,
} InfraredView;
/**
@@ -216,6 +216,29 @@ void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index);
*/
void infrared_tx_stop(InfraredApp* infrared);
/**
* @brief Start a blocking task in a separate thread.
*
* Before starting a blocking task, the current view will be replaced
* with a busy animation. All subsequent user input will be ignored.
*
* @param[in,out] infrared pointer to the application instance.
* @param[in] callback pointer to the function to be run in the thread.
*/
void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback);
/**
* @brief Wait for a blocking task to finish and get the result.
*
* The busy animation shown during the infrared_blocking_task_start() call
* will NOT be hidden and WILL remain on screen. If another view is needed
* (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.
*/
bool infrared_blocking_task_finalize(InfraredApp* infrared);
/**
* @brief Set the internal text store with formatted text.
*
@@ -245,17 +268,6 @@ void infrared_play_notification_message(
const InfraredApp* infrared,
InfraredNotificationMessage message);
/**
* @brief Show a loading pop-up screen.
*
* In order for this to work, a Stack view must be currently active and
* the main view must be added to it.
*
* @param[in] infrared pointer to the application instance.
* @param[in] show whether to show or hide the pop-up.
*/
void infrared_show_loading_popup(const InfraredApp* infrared, bool show);
/**
* @brief Show a formatted error messsage.
*

View File

@@ -10,8 +10,10 @@
#include "infrared_signal.h"
#include "infrared_brute_force.h"
#define INFRARED_CLI_BUF_SIZE 10
#define INFRARED_ASSETS_FOLDER "infrared/assets"
#define INFRARED_CLI_BUF_SIZE (10U)
#define INFRARED_CLI_FILE_NAME_SIZE (256U)
#define INFRARED_FILE_EXTENSION ".ir"
#define INFRARED_ASSETS_FOLDER EXT_PATH("infrared/assets")
#define INFRARED_BRUTE_FORCE_DUMMY_INDEX 0
DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST)
@@ -66,6 +68,37 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
}
}
static void infrared_cli_print_universal_remotes(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* dir = storage_file_alloc(storage);
do {
if(!storage_dir_open(dir, INFRARED_ASSETS_FOLDER)) break;
FileInfo file_info;
char file_name[INFRARED_CLI_FILE_NAME_SIZE];
while(storage_dir_read(dir, &file_info, file_name, sizeof(file_name))) {
if(file_info.flags & FSF_DIRECTORY) {
continue;
}
char* file_ext = strstr(file_name, INFRARED_FILE_EXTENSION);
if((file_ext == NULL) || (strcmp(file_ext, INFRARED_FILE_EXTENSION) != 0)) {
continue;
}
*file_ext = '\0';
printf("%s ", file_name);
}
printf("\r\n");
} while(false);
storage_file_free(dir);
furi_record_close(RECORD_STORAGE);
}
static void infrared_cli_print_usage(void) {
printf("Usage:\r\n");
printf("\tir rx [raw]\r\n");
@@ -85,8 +118,9 @@ static void infrared_cli_print_usage(void) {
printf("\tir decode <input_file> [<output_file>]\r\n");
printf("\tir universal <remote_name> <signal_name>\r\n");
printf("\tir universal list <remote_name>\r\n");
// TODO FL-3496: Do not hardcode universal remote names
printf("\tAvailable universal remotes: tv audio ac projector\r\n");
printf("\tAvailable universal remotes: ");
infrared_cli_print_universal_remotes();
}
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
@@ -211,7 +245,6 @@ static bool infrared_cli_decode_raw_signal(
size_t i;
for(i = 0; i < raw_signal->timings_size; ++i) {
// TODO FL-3523: Any infrared_check_decoder_ready() magic?
const InfraredMessage* message = infrared_decode(decoder, level, raw_signal->timings[i]);
if(message) {
@@ -365,7 +398,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
FuriString* remote_path = furi_string_alloc_printf(
"%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name));
"%s/%s%s",
INFRARED_ASSETS_FOLDER,
furi_string_get_cstr(remote_name),
INFRARED_FILE_EXTENSION);
do {
if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(remote_path))) {
@@ -413,7 +449,7 @@ static void
infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) {
InfraredBruteForce* brute_force = infrared_brute_force_alloc();
FuriString* remote_path = furi_string_alloc_printf(
"%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name));
"%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name));
infrared_brute_force_set_db_filename(brute_force, furi_string_get_cstr(remote_path));
infrared_brute_force_add_record(
@@ -508,12 +544,15 @@ static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
furi_string_free(command);
}
void infrared_on_system_start() {
#ifdef SRV_CLI
Cli* cli = (Cli*)furi_record_open(RECORD_CLI);
cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(infrared_cli_start_ir);
#endif
#include <flipper_application/flipper_application.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "infrared_cli",
.ep_api_version = 1,
.entry_point = &infrared_cli_start_ir,
};
const FlipperAppPluginDescriptor* infrared_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -14,6 +14,7 @@ enum InfraredCustomEventType {
InfraredCustomEventTypePopupClosed,
InfraredCustomEventTypeButtonSelected,
InfraredCustomEventTypeBackPressed,
InfraredCustomEventTypeTaskFinished,
InfraredCustomEventTypeRpcLoadFile,
InfraredCustomEventTypeRpcExit,

View File

@@ -0,0 +1,11 @@
#include <cli/cli_i.h>
static void infrared_cli_start_ir_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("infrared_cli", 1, cli, args, context);
}
void infrared_on_system_start() {
Cli* cli = (Cli*)furi_record_open(RECORD_CLI);
cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir_wrapper, NULL);
furi_record_close(RECORD_CLI);
}

View File

@@ -1,43 +1,43 @@
Filetype: IR library file
Version: 1
# Last Updated 16th Aug, 2023
# Last Checked 16th Aug, 2023
# Last Updated 21st Feb, 2024
# Last Checked 21st Feb, 2024
#
# Model: Electrolux EACM-16 HP/N3
name: Off
type: raw
frequency: 38000
duty_cycle: 0.33
duty_cycle: 0.330000
data: 502 3436 510 475 509 476 508 477 507 477 507 479 505 480 504 480 504 490 504 481 502 482 501 483 563 420 511 474 510 475 509 476 508 485 561 423 508 476 508 477 507 478 506 479 505 480 504 481 503 517 508 476 508 478 506 479 505 479 505 481 503 483 521 1456 501 498 507 479 505 480 504 481 503 482 501 483 563 421 562 422 509 499 506 479 505 480 504 481 503 482 502 484 510 1451 506 479 505 1542 562 1396 509 471 502 476 508 469 504 3425 511
#
name: Dh
type: raw
frequency: 38000
duty_cycle: 0.33
duty_cycle: 0.330000
data: 507 3430 506 479 505 480 504 481 503 481 503 483 501 485 509 1453 504 1465 503 482 502 483 511 473 500 485 509 476 508 477 507 478 506 487 507 477 507 478 506 479 505 480 504 482 502 483 501 484 500 523 503 482 502 484 500 485 509 476 508 476 508 478 506 1456 501 501 504 482 502 483 501 484 500 485 509 476 508 477 507 1455 502 509 506 479 505 1457 500 485 509 476 508 1454 503 482 502 483 501 568 499 1459 509 1450 507 471 502 474 510 3421 505
#
name: Cool_hi
type: raw
frequency: 38000
duty_cycle: 0.33
duty_cycle: 0.330000
data: 504 3433 503 482 502 484 510 474 510 475 509 476 508 478 506 1456 564 1405 510 475 509 476 508 502 482 477 507 478 506 479 505 480 504 489 505 480 504 481 503 482 502 483 511 473 511 474 510 475 509 509 506 479 505 480 504 481 503 482 512 473 511 474 510 476 508 1469 509 475 509 476 508 477 507 478 506 479 505 480 504 481 503 505 510 475 509 502 482 503 481 504 480 505 478 507 477 1459 509 560 507 1451 506 473 511 493 480 1450 507 3422 503
#
name: Cool_lo
type: raw
frequency: 38000
duty_cycle: 0.33
duty_cycle: 0.330000
data: 525 3615 530 506 561 474 562 474 562 473 563 473 531 505 562 1502 528 1542 562 474 562 474 530 505 531 504 532 504 532 504 616 419 533 510 589 447 526 509 527 509 527 509 527 508 528 508 528 507 529 542 525 510 526 509 527 509 527 509 527 508 528 508 528 1535 527 524 533 503 533 503 533 502 534 502 534 502 534 501 535 501 525 534 533 502 534 502 534 501 535 502 534 1529 533 503 533 503 533 587 533 497 528 501 524 1536 526 501 524 3609 526
#
name: Heat_hi
type: raw
frequency: 38000
duty_cycle: 0.33
duty_cycle: 0.330000
data: 531 3406 530 455 529 456 528 457 537 447 537 448 535 450 534 1429 528 1442 536 448 536 449 534 451 532 452 532 453 530 454 530 455 529 464 530 454 529 456 528 457 537 448 536 449 535 450 533 451 533 490 535 449 534 450 534 451 533 452 532 453 531 455 529 1433 534 1443 535 449 535 450 534 452 531 453 530 454 530 455 529 456 538 472 532 452 532 454 530 1433 535 1427 530 1432 536 1427 530 1431 537 1511 530 448 536 1422 535 1423 534 1422 535 3395 530
#
name: Heat_lo
type: raw
frequency: 38000
duty_cycle: 0.33
duty_cycle: 0.330000
data: 506 3430 506 478 506 479 505 480 504 481 503 482 502 484 500 1463 505 1465 503 482 502 483 501 484 500 485 509 476 508 477 507 478 506 486 508 477 507 478 506 479 505 480 504 481 503 482 502 483 500 523 502 482 502 483 501 484 500 485 509 476 508 478 506 1455 502 498 507 478 506 479 505 481 503 482 501 483 500 484 500 485 509 500 505 481 502 482 502 1461 507 1455 502 1459 509 476 508 477 507 563 504 1453 504 1454 503 1454 503 1453 504 3426 499
#
# Model: Hisense Generic
@@ -959,3 +959,65 @@ type: raw
frequency: 38000
duty_cycle: 0.330000
data: 664 17757 3057 8901 528 469 550 1434 556 465 553 440 529 465 554 439 555 439 555 438 555 440 553 1435 552 443 550 470 524 1466 522 472 522 472 521 1467 523 1466 549 1440 549 1440 548 1440 548 445 549 445 549 445 549 446 548 470 524 471 523 471 523 471 523 472 522 472 522 473 522 472 523 472 548 446 549 445 550 445 548 446 548 446 548 446 548 446 548 447 547 470 524 471 523 471 523 471 523 471 523 474 519 474 521 474 521 473 522 473 546 447 547 1441 548 1442 546 1442 547 1442 546 2947 3023 8935 522 1466 522 472 522 498 496 498 495 499 496 498 496 498 521 473 522 471 523 1466 522 471 523 471 522 1466 523 471 523 1467 522 1467 522 1467 521 1493 495 1494 496 1493 521 473 522 472 522 471 523 472 522 471 523 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 495 499 496 498 522 473 522 472 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 499 495 500 494 500 495 499 496 498 522 2947 3023 8937 521 1468 520 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1470 518 500 494 500 494 500 494 500 494 1494 521 1468 521 473 521 1468 520 1468 520 1469 520 1469 520 1470 518 1495 493 1496 493 1495 494 500 519 475 520 474 520 1469 519 1469 520 1469 519 474 520 474 520 475 519 477 517 500 494 1496 493 1496 493 1496 493 500 495 1495 519 475 518 475 519 475 518 476 518 475 519 1470 519 500 494 500 494 501 493 501 493 501 493 1499 490 1497 493 1497 492 1496 518
#
name: Heat_hi
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 148 110377 9082 4422 707 499 706 500 704 1603 703 1606 701 505 700 507 699 506 700 507 699 506 700 1607 700 1608 700 1609 699 506 699 507 699 506 699 507 699 507 699 506 700 507 699 507 699 507 699 1608 699 1607 699 507 699 507 699 507 699 507 699 507 699 1609 699 507 699 1608 699 507 699 507 699 1609 699 507 699 19940 700 506 700 506 699 507 699 507 699 506 700 506 700 507 699 507 699 507 700 507 699 506 699 507 699 507 699 507 699 507 699 507 699 507 699 507 699 507 698 507 700 507 699 507 699 507 699 507 699 507 699 508 698 508 698 507 699 507 699 508 698 1610 699 508 698
#
name: Cool_hi
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 315 101050 3094 3056 3093 4437 580 1648 572 534 576 1649 582 525 574 530 580 1646 574 1653 578 529 570 534 576 529 571 534 576 529 570 1655 576 1651 580 527 572 532 578 1647 573 1654 577 1651 580 526 573 531 579 526 573 531 579 526 573 531 579 526 573 531 579 526 573 531 579 525 574 531 579 525 574 531 579 1646 574 532 578 526 573 531 579 526 573 531 579 526 573 1652 579 527 572 1653 578 528 571 534 576 528 571 533 577 528 571 533 577 528 572 533 577 528 572 532 578 527 572 532 578 527 572 532 578 526 573 1652 579 527 572 532 578 527 572 532 578 527 572 532 578 526 573 531 579 526 573 531 579 526 573 531 579 525 574 530 580 525 574 530 580 525 574 530 580 524 575 529 581 524 575 529 571 534 576 528 571 533 577 528 571 533 577 528 571 533 577 527 572 532 578 527 572 532 578 526 573 531 579 526 573 531 579 525 574 531 579 525 574 530 580 525 574 1650 581 525 574 1651 580 1647 573 533 577 527 572 1653 578 528 572 1654 577 1650 581 1646 574 71637 254
#
name: Cool_lo
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 284 19161 3098 3053 3096 4435 572 1656 575 532 578 1648 572 534 576 530 570 1682 549 1652 579 527 572 534 576 1649 571 1656 575 1652 579 1649 571 1656 575 531 579 527 572 1653 578 1649 571 1656 575 531 579 527 572 532 578 527 572 533 577 527 572 533 577 527 573 532 578 527 572 532 578 527 573 532 578 527 572 1652 579 527 572 533 577 528 571 533 577 528 571 533 577 1648 572 533 577 1649 571 535 575 530 569 536 574 531 569 536 574 530 569 536 574 530 570 535 575 530 570 535 575 530 569 535 575 530 569 535 575 1649 571 535 575 531 568 536 574 531 568 536 574 531 568 536 574 531 569 536 574 530 569 536 574 530 569 535 575 530 569 535 575 530 569 535 575 530 570 535 575 529 570 534 576 529 570 534 576 529 570 534 576 528 571 534 576 528 571 534 576 528 571 534 576 528 571 534 576 528 571 533 577 528 571 533 577 528 572 533 577 528 571 533 577 528 572 1652 579 527 572 1653 578 529 570 534 576 529 570 535 575 529 570 1654 577 1677 554 1673 547
#
name: Cool_lo
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 301 132136 5036 2167 337 1766 361 689 358 692 366 684 363 1770 357 692 366 684 363 718 329 690 357 1776 361 687 360 1773 364 1767 360 689 358 1775 362 1769 357 1774 363 1768 359 1773 364 684 363 718 329 1773 364 684 363 718 329 691 356 694 364 716 331 719 328 1775 362 1769 358 1774 363 1768 359 1772 365 714 333 1770 357 1774 363 716 331 719 328 722 336 715 332 718 329 721 326 724 334 716 331 719 328 722 336 715 332 718 329 1773 364 1767 360 1772 354 1777 360 719 328 721 326 725 333 717 330 29455 5036 2139 354 1777 360 688 359 691 367 714 333 1770 356 692 366 684 363 687 360 690 357 1776 361 688 359 1773 364 1768 359 689 358 1775 362 1769 357 1774 363 1768 359 1773 364 684 363 687 360 1773 364 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 688 359 691 356 1777 360 1771 355 693 365 685 362 1771 355 693 365 1768 359 1773 364 1767 360 689 358 692 366 685 362 1771 355 1775 362 687 360 690 357 1775 362 687 360 690 357 693 365 716 331 689 358 1774 363 686 361 689 358 692 366 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 688 359 691 356 694 364 686 361 689 358 692 366 685 362 1771 355 693 365 1768 358 1773 364 684 363 687 360 690 357 693 365 1768 359 690 357 1776 361 688 359 691 356 694 364 686 361 689 358 692 366 685 362 1770 356 693 365 685 362 688 359 691 356 1777 360 1771 355 693 365 686 361 689 358 692 366 685 362 1770 356
#
# Model: Fujitsu ASYG24KMTB
#
name: Dh
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 3302 1639 405 423 407 420 410 1234 405 421 409 1210 440 387 432 420 410 417 413 1231 408 1238 412 388 431 422 408 392 438 1233 406 1238 412 389 430 396 434 419 411 390 440 413 406 394 436 391 439 388 431 421 409 417 413 414 405 421 409 392 438 1233 406 421 409 417 413 414 405 395 435 418 412 388 432 421 409 1236 414 387 432 420 410 390 440 388 431 1213 437 1208 431 1239 411 1234 405 1213 437 1208 431 1214 436 1235 415 386 433 420 410 1234 405 422 408 418 412 389 431 396 434 1211 439 388 432 422 408 418 412 1233 406 1238 412 415 404 422 408 1237 413 388 432 422 408 1236 414 1205 434 1210 440 1205 434 392 438 390 440 1231 408 392 438 415 404 396 434 392 438 415 404 422 408 419 411 416 414 412 407 394 436 417 413 414 405 421 409 417 413 1232 407 419 411 390 440 1204 435 418 412 415 415 412 407 419 411 1208 431 421 409 418 412 388 432 421 409 392 438 415 404 395 435 1211 439 388 432 1213 437 416 414 386 433 1212 438 415 404 396 434 392 438 416 414 413 406 420 410 417 413 1231 409 418 412 389 430 1240 410 391 439 1205 434 420 410 390 440 387 432 420 410 417 413
#
name: Cool_hi
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 3293 1649 405 396 434 419 411 1207 432 395 435 1236 414 413 406 394 436 417 413 1232 407 1212 438 415 404 396 434 419 411 1234 405 1239 411 417 413 414 405 421 409 419 411 389 430 423 407 393 437 416 414 387 432 394 436 417 413 388 431 421 409 1236 414 387 432 421 409 418 412 414 405 422 408 418 412 415 404 1240 410 391 439 414 405 421 409 419 411 1234 405 1239 411 1208 431 1239 411 1235 404 1214 436 1210 440 1205 434 419 411 416 414 1204 435 418 412 415 404 396 434 393 437 1235 404 396 434 420 410 416 414 1231 408 1210 440 387 432 421 409 1235 415 414 405 395 435 418 412 1232 407 420 410 1208 431 422 408 1237 413 415 404 396 434 419 411 416 414 413 406 420 410 417 413 414 405 421 409 418 412 415 404 422 408 419 411 416 414 413 406 1238 412 415 404 422 408 1237 413 414 405 421 409 418 412 416 414 1231 408 418 412 415 404 422 408 419 411 416 414 413 406 420 410 1235 404 423 407 420 410 1234 405 422 408 1210 440 414 405 421 409 392 438 415 404 422 408 420 410 417 413 1231 408 419 411 416 414 413 406 1237 413 415 404 1239 411 417 413 1232 407 420 410 417 413
#
name: Cool_lo
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 3302 1614 440 414 405 421 409 1235 414 413 406 1238 411 415 404 422 408 419 411 1234 405 1240 409 418 412 415 404 422 408 1237 412 1232 407 420 410 417 413 414 405 422 408 419 411 416 414 413 406 420 410 417 413 414 405 421 409 418 412 415 404 1239 410 418 412 415 404 422 408 419 411 416 414 413 406 420 410 1234 405 422 408 419 411 416 414 414 405 1239 411 1207 432 1239 410 1235 414 1230 409 1236 413 1232 407 1237 412 415 415 412 407 1237 412 414 405 422 408 419 411 416 414 1231 408 419 411 416 414 413 406 1238 411 1206 433 421 409 418 412 1206 433 421 409 418 412 1206 433 1238 412 1207 432 1213 436 390 440 1232 407 420 410 417 413 414 405 421 409 418 412 415 404 422 408 419 411 416 414 413 406 421 409 418 412 415 404 422 408 419 411 1233 406 421 409 418 412 1232 407 420 410 418 412 389 430 422 408 1210 440 414 405 395 435 418 412 415 404 423 407 420 410 416 414 414 405 422 408 418 412 415 404 1240 409 1235 414 413 406 420 410 417 413 414 405 422 408 419 411 416 414 1231 408 418 412 415 404 1240 409 1209 440 387 432 1212 437 1234 405 1214 435 1209 440 1204 435
#
name: Heat_hi
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 3301 1615 439 415 404 422 408 1210 439 414 405 1239 410 416 414 414 405 395 435 1209 440 1205 434 419 411 417 413 414 405 1212 437 1207 432 422 408 419 411 416 414 414 405 421 409 418 412 415 404 422 408 393 437 416 414 413 406 420 410 417 413 1205 434 420 410 417 413 414 405 421 409 418 412 415 404 422 408 1210 439 414 405 422 408 419 411 417 413 1205 434 1236 413 1206 433 1211 438 1207 432 1212 437 1209 440 1204 435 418 412 415 415 1204 435 418 412 415 404 422 408 419 411 1208 441 412 407 420 410 417 413 1205 434 1237 412 414 405 422 408 1210 439 415 404 396 434 419 411 1207 432 1213 436 417 413 1205 434 420 410 417 413 1231 408 419 411 416 414 413 406 421 409 418 412 414 405 422 408 419 411 415 404 424 406 421 409 418 412 415 404 1239 410 417 413 414 405 1239 410 416 414 413 406 422 408 419 411 1233 406 421 409 418 412 415 404 423 407 419 411 416 414 413 406 1238 411 416 414 413 406 421 409 1235 414 1230 409 418 412 415 415 412 407 420 410 417 413 414 405 422 408 1236 413 413 406 421 409 1235 414 1204 435 1210 439 1206 433 1212 437 1207 432 395 435 1236 413
#
name: Heat_lo
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 3295 1646 408 420 410 390 440 1205 434 393 437 1234 405 421 409 419 411 415 415 1230 409 1210 440 414 405 421 409 418 412 1233 406 1212 437 416 414 413 406 394 436 418 412 415 404 422 408 393 437 416 414 413 406 420 410 417 413 414 405 421 409 1236 413 414 405 421 409 418 412 415 404 423 407 419 411 416 414 1231 408 418 412 415 415 412 407 421 409 1235 414 1204 435 1209 441 1205 434 1210 440 1205 434 1212 437 1207 432 395 435 419 411 1233 406 421 409 418 412 415 404 422 408 1238 411 415 404 396 434 419 411 1234 405 1239 410 417 413 414 405 1213 436 417 413 414 405 1213 436 1235 404 1214 435 1209 441 413 406 421 409 418 412 1206 433 394 436 418 412 388 431 422 408 419 411 415 415 386 433 420 410 418 412 414 405 422 408 419 411 389 430 1241 408 418 412 415 404 1240 409 417 413 414 405 395 435 419 411 1233 406 395 435 418 412 415 404 422 408 419 411 416 414 413 406 421 409 1236 413 413 406 394 436 1235 414 1230 409 418 412 415 404 422 408 420 410 417 413 414 405 421 409 1236 413 413 406 420 410 417 413 1232 407 1237 412 416 414 1230 409 1236 413 1205 434 1210 439
#
name: Off
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 3302 1640 404 423 407 420 410 1212 437 390 440 1234 405 395 435 392 438 415 415 1207 432 1242 407 420 410 391 439 414 405 1243 406 1241 408 392 438 415 415 386 433 393 437 390 440 414 405 396 434 419 411 389 441 412 407 420 410 390 440 387 432 1242 407 393 437 390 440 414 405 395 435 392 438 389 430 396 434 1240 409 417 413 414 405 395 435 419 411 1237 412 389 430 396 434 393 437 416 414 387 432 394 436 1212 437 389 441 1234 405 1217 432 1241 408 1213 436 1212 437 1210 439

View File

@@ -32,9 +32,23 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) {
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
}
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);
view_dispatcher_send_custom_event(
infrared->view_dispatcher,
infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0));
return success;
}
void infrared_scene_universal_common_on_enter(void* context) {
InfraredApp* infrared = context;
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel));
// Load universal remote data in background
infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback);
}
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
@@ -58,26 +72,36 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e
if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
consumed = true;
}
consumed = true;
}
} else {
if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(scene_manager);
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(infrared_custom_event_get_type(event.event) ==
InfraredCustomEventTypeButtonSelected) {
uint16_t event_type;
int16_t event_value;
infrared_custom_event_unpack(event.event, &event_type, &event_value);
if(event_type == InfraredCustomEventTypeButtonSelected) {
uint32_t record_count;
if(infrared_brute_force_start(
brute_force, infrared_custom_event_get_value(event.event), &record_count)) {
if(infrared_brute_force_start(brute_force, event_value, &record_count)) {
dolphin_deed(DolphinDeedIrSend);
infrared_scene_universal_common_show_popup(infrared, record_count);
} else {
scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases);
}
consumed = true;
} else if(event_type == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);
if(!task_success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
} else {
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
}
}
consumed = true;
}
}

View File

@@ -1,5 +1,7 @@
#include "../infrared_app_i.h"
#include <furi_hal_infrared.h>
uint8_t value_index_ir;
#define DEB_PINS_COUNT (sizeof(infrared_debug_cfg_variables_text) / sizeof(char* const))

View File

@@ -6,12 +6,33 @@ static void
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
}
static int32_t infrared_scene_edit_delete_task_callback(void* context) {
InfraredApp* infrared = context;
InfraredAppState* app_state = &infrared->app_state;
const InfraredEditTarget edit_target = app_state->edit_target;
bool success;
if(edit_target == InfraredEditTargetButton) {
furi_assert(app_state->current_button_index != InfraredButtonIndexNone);
success = infrared_remote_delete_signal(infrared->remote, app_state->current_button_index);
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_remote_remove(infrared->remote);
} else {
furi_crash();
}
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished);
return success;
}
void infrared_scene_edit_delete_on_enter(void* context) {
InfraredApp* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
InfraredRemote* remote = infrared->remote;
const InfraredEditTarget edit_target = infrared->app_state.edit_target;
if(edit_target == InfraredEditTargetButton) {
dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop);
@@ -70,10 +91,7 @@ void infrared_scene_edit_delete_on_enter(void* context) {
dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_delete_dialog_result_callback);
dialog_ex_set_context(dialog_ex, context);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
view_stack_add_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex));
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
}
bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) {
@@ -84,39 +102,30 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event)
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(scene_manager);
consumed = true;
} else if(event.event == DialogExResultRight) {
bool success = false;
InfraredRemote* remote = infrared->remote;
// Delete a button or a remote in a separate thread
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);
InfraredAppState* app_state = &infrared->app_state;
const InfraredEditTarget edit_target = app_state->edit_target;
if(edit_target == InfraredEditTargetButton) {
furi_assert(app_state->current_button_index != InfraredButtonIndexNone);
infrared_show_loading_popup(infrared, true);
success = infrared_remote_delete_signal(remote, app_state->current_button_index);
infrared_show_loading_popup(infrared, false);
app_state->current_button_index = InfraredButtonIndexNone;
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_remote_remove(remote);
app_state->current_button_index = InfraredButtonIndexNone;
} else {
furi_crash();
}
if(success) {
if(task_success) {
scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone);
} else {
infrared_show_error_message(
infrared,
"Failed to\ndelete %s",
edit_target == InfraredEditTargetButton ? "button" : "file");
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};
scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, possible_scenes, COUNT_OF(possible_scenes));
}
consumed = true;
app_state->current_button_index = InfraredButtonIndexNone;
}
consumed = true;
}
return consumed;
@@ -124,5 +133,5 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event)
void infrared_scene_edit_delete_on_exit(void* context) {
InfraredApp* infrared = context;
view_stack_remove_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex));
dialog_ex_reset(infrared->dialog_ex);
}

View File

@@ -1,5 +1,17 @@
#include "../infrared_app_i.h"
static int32_t infrared_scene_edit_move_task_callback(void* context) {
InfraredApp* infrared = context;
const bool success = 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;
}
static void infrared_scene_edit_move_button_callback(
uint32_t index_old,
uint32_t index_new,
@@ -26,10 +38,7 @@ void infrared_scene_edit_move_on_enter(void* context) {
infrared_move_view_set_callback(
infrared->move_view, infrared_scene_edit_move_button_callback, infrared);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
view_stack_add_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view));
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove);
}
bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) {
@@ -38,25 +47,23 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeButtonSelected) {
infrared_show_loading_popup(infrared, true);
const bool button_moved = infrared_remote_move_signal(
infrared->remote,
infrared->app_state.prev_button_index,
infrared->app_state.current_button_index);
infrared_show_loading_popup(infrared, false);
// Move the button in a separate thread
infrared_blocking_task_start(infrared, infrared_scene_edit_move_task_callback);
if(!button_moved) {
infrared_show_error_message(
infrared,
"Failed to move\n\"%s\"",
infrared_remote_get_signal_name(
infrared->remote, infrared->app_state.current_button_index));
} else if(event.event == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);
if(!task_success) {
const char* signal_name = infrared_remote_get_signal_name(
infrared->remote, infrared->app_state.current_button_index);
infrared_show_error_message(infrared, "Failed to move\n\"%s\"", signal_name);
scene_manager_search_and_switch_to_previous_scene(
infrared->scene_manager, InfraredSceneRemoteList);
} else {
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove);
}
consumed = true;
}
consumed = true;
}
return consumed;
@@ -64,6 +71,5 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) {
void infrared_scene_edit_move_on_exit(void* context) {
InfraredApp* infrared = context;
view_stack_remove_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view));
infrared_move_view_reset(infrared->move_view);
}

View File

@@ -3,6 +3,28 @@
#include <string.h>
#include <toolbox/path.h>
static int32_t infrared_scene_edit_rename_task_callback(void* context) {
InfraredApp* infrared = context;
InfraredAppState* app_state = &infrared->app_state;
const InfraredEditTarget edit_target = app_state->edit_target;
bool success;
if(edit_target == InfraredEditTargetButton) {
furi_assert(app_state->current_button_index != InfraredButtonIndexNone);
success = 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]);
} else {
furi_crash();
}
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished);
return success;
}
void infrared_scene_edit_rename_on_enter(void* context) {
InfraredApp* infrared = context;
InfraredRemote* remote = infrared->remote;
@@ -53,49 +75,36 @@ void infrared_scene_edit_rename_on_enter(void* context) {
enter_name_length,
false);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal);
view_stack_add_view(infrared->view_stack, text_input_get_view(infrared->text_input));
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput);
}
bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) {
InfraredApp* infrared = context;
InfraredRemote* remote = infrared->remote;
SceneManager* scene_manager = infrared->scene_manager;
InfraredAppState* app_state = &infrared->app_state;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeTextEditDone) {
bool success = false;
const InfraredEditTarget edit_target = app_state->edit_target;
if(edit_target == InfraredEditTargetButton) {
const int32_t current_button_index = app_state->current_button_index;
furi_assert(current_button_index != InfraredButtonIndexNone);
infrared_show_loading_popup(infrared, true);
success = infrared_remote_rename_signal(
remote, current_button_index, infrared->text_store[0]);
infrared_show_loading_popup(infrared, false);
app_state->current_button_index = InfraredButtonIndexNone;
} else if(edit_target == InfraredEditTargetRemote) {
success = infrared_rename_current_remote(infrared, infrared->text_store[0]);
} else {
furi_crash();
}
// Rename a button or a remote in a separate thread
infrared_blocking_task_start(infrared, infrared_scene_edit_rename_task_callback);
if(success) {
} else if(event.event == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);
InfraredAppState* app_state = &infrared->app_state;
if(task_success) {
scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone);
} else {
infrared_show_error_message(
infrared,
"Failed to\nrename %s",
edit_target == InfraredEditTargetButton ? "button" : "file");
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);
}
consumed = true;
app_state->current_button_index = InfraredButtonIndexNone;
}
consumed = true;
}
return consumed;
@@ -105,12 +114,10 @@ void infrared_scene_edit_rename_on_exit(void* context) {
InfraredApp* infrared = context;
TextInput* text_input = infrared->text_input;
view_stack_remove_view(infrared->view_stack, text_input_get_view(text_input));
void* validator_context = text_input_get_validator_callback_context(text_input);
text_input_set_validator(text_input, NULL, NULL);
ValidatorIsFile* validator_context = text_input_get_validator_callback_context(text_input);
if(validator_context) {
validator_is_file_free((ValidatorIsFile*)validator_context);
validator_is_file_free(validator_context);
}
text_input_reset(text_input);
}

View File

@@ -1,41 +1,56 @@
#include "../infrared_app_i.h"
void infrared_scene_remote_list_on_enter(void* context) {
static int32_t infrared_scene_remote_list_task_callback(void* context) {
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
ViewDispatcher* view_dispatcher = infrared->view_dispatcher;
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack);
const bool success =
infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path));
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished);
return success;
}
static void infrared_scene_remote_list_select_and_load(InfraredApp* infrared) {
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px);
browser_options.base_path = INFRARED_APP_FOLDER;
while(dialog_file_browser_show(
infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options)) {
const char* file_path = furi_string_get_cstr(infrared->file_path);
const bool file_selected = dialog_file_browser_show(
infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options);
infrared_show_loading_popup(infrared, true);
const bool remote_loaded = infrared_remote_load(infrared->remote, file_path);
infrared_show_loading_popup(infrared, false);
if(file_selected) {
// Load the remote in a separate thread
infrared_blocking_task_start(infrared, infrared_scene_remote_list_task_callback);
if(remote_loaded) {
scene_manager_next_scene(scene_manager, InfraredSceneRemote);
return;
} else {
infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path);
}
} else {
scene_manager_previous_scene(infrared->scene_manager);
}
}
scene_manager_previous_scene(scene_manager);
void infrared_scene_remote_list_on_enter(void* context) {
InfraredApp* infrared = context;
infrared_scene_remote_list_select_and_load(infrared);
}
bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
InfraredApp* infrared = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);
if(task_success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote);
} else {
infrared_show_error_message(
infrared, "Failed to load\n\"%s\"", furi_string_get_cstr(infrared->file_path));
infrared_scene_remote_list_select_and_load(infrared);
}
}
consumed = true;
}
return consumed;
}

View File

@@ -9,6 +9,15 @@ typedef enum {
InfraredRpcStateSending,
} InfraredRpcState;
static int32_t infrared_scene_rpc_task_callback(void* context) {
InfraredApp* infrared = context;
const bool success =
infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path));
view_dispatcher_send_custom_event(
infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished);
return success;
}
void infrared_scene_rpc_on_enter(void* context) {
InfraredApp* infrared = context;
Popup* popup = infrared->popup;
@@ -22,9 +31,7 @@ void infrared_scene_rpc_on_enter(void* context) {
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);
notification_message(infrared->notifications, &sequence_display_backlight_on);
}
@@ -33,76 +40,89 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
InfraredRpcState state =
InfraredAppState* app_state = &infrared->app_state;
InfraredRpcState rpc_state =
scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc);
if(event.event == InfraredCustomEventTypeBackPressed) {
view_dispatcher_stop(infrared->view_dispatcher);
} else if(event.event == InfraredCustomEventTypePopupClosed) {
view_dispatcher_stop(infrared->view_dispatcher);
} else if(event.event == InfraredCustomEventTypeRpcLoadFile) {
bool result = false;
if(state == InfraredRpcStateIdle) {
result = infrared_remote_load(
infrared->remote, furi_string_get_cstr(infrared->file_path));
if(result) {
scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
}
}
const char* remote_name = infrared_remote_get_name(infrared->remote);
infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name);
if(event.event == InfraredCustomEventTypeRpcLoadFile) {
if(rpc_state == InfraredRpcStateIdle) {
// Load the remote in a separate thread
infrared_blocking_task_start(infrared, infrared_scene_rpc_task_callback);
}
} else if(event.event == InfraredCustomEventTypeTaskFinished) {
const bool task_success = infrared_blocking_task_finalize(infrared);
if(task_success) {
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));
}
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, result);
} else if(
event.event == InfraredCustomEventTypeRpcButtonPressName ||
event.event == InfraredCustomEventTypeRpcButtonPressIndex) {
bool result = false;
if(state == InfraredRpcStateLoaded) {
if(rpc_state == InfraredRpcStateLoaded) {
if(event.event == InfraredCustomEventTypeRpcButtonPressName) {
const char* button_name = furi_string_get_cstr(infrared->button_name);
size_t index;
const bool index_found =
infrared_remote_get_signal_index(infrared->remote, button_name, &index);
infrared->app_state.current_button_index =
index_found ? (signed)index : InfraredButtonIndexNone;
app_state->current_button_index = index_found ? (signed)index :
InfraredButtonIndexNone;
FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name);
} else {
FURI_LOG_D(
TAG,
"Sending signal with index \"%ld\"",
infrared->app_state.current_button_index);
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, infrared->app_state.current_button_index);
infrared_tx_start_button_index(infrared, app_state->current_button_index);
scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending);
result = true;
}
}
rpc_system_app_confirm(infrared->rpc_ctx, result);
} else if(event.event == InfraredCustomEventTypeRpcButtonRelease) {
bool result = false;
if(state == InfraredRpcStateSending) {
if(rpc_state == InfraredRpcStateSending) {
infrared_tx_stop(infrared);
result = true;
scene_manager_set_scene_state(
infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded);
}
rpc_system_app_confirm(infrared->rpc_ctx, result);
} else if(event.event == InfraredCustomEventTypeRpcExit) {
scene_manager_stop(infrared->scene_manager);
view_dispatcher_stop(infrared->view_dispatcher);
rpc_system_app_confirm(infrared->rpc_ctx, true);
} else if(event.event == InfraredCustomEventTypeRpcSessionClose) {
} else if(
event.event == InfraredCustomEventTypeRpcExit ||
event.event == InfraredCustomEventTypeRpcSessionClose ||
event.event == InfraredCustomEventTypePopupClosed) {
scene_manager_stop(infrared->scene_manager);
view_dispatcher_stop(infrared->view_dispatcher);
if(event.event == InfraredCustomEventTypeRpcExit) {
rpc_system_app_confirm(infrared->rpc_ctx, true);
}
}
consumed = true;
}
return consumed;
}
@@ -112,5 +132,6 @@ void infrared_scene_rpc_on_exit(void* context) {
InfraredRpcStateSending) {
infrared_tx_stop(infrared);
}
popup_reset(infrared->popup);
}

View File

@@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_ac_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
@@ -122,16 +120,7 @@ void infrared_scene_universal_ac_on_enter(void* context) {
button_panel_add_label(button_panel, 22, 10, FontPrimary, "ACs");
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
infrared_scene_universal_common_on_enter(context);
}
bool infrared_scene_universal_ac_on_event(void* context, SceneManagerEvent event) {

View File

@@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_audio_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
@@ -119,16 +117,7 @@ void infrared_scene_universal_audio_on_enter(void* context) {
button_panel_add_label(button_panel, 18, 10, FontPrimary, "Audio");
button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
infrared_scene_universal_common_on_enter(context);
}
bool infrared_scene_universal_audio_on_event(void* context, SceneManagerEvent event) {

View File

@@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_digital_sign_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
@@ -71,16 +69,7 @@ void infrared_scene_universal_digital_sign_on_enter(void* context) {
button_panel_add_label(button_panel, 1, 11, FontPrimary, "Digital Signs");
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
infrared_scene_universal_common_on_enter(context);
}
bool infrared_scene_universal_digital_sign_on_event(void* context, SceneManagerEvent event) {

View File

@@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_fan_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
@@ -97,16 +95,7 @@ void infrared_scene_universal_fan_on_enter(void* context) {
button_panel_add_label(button_panel, 20, 11, FontPrimary, "Fans");
button_panel_add_icon(button_panel, 34, 68, &I_speed_text_30x30);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
infrared_scene_universal_common_on_enter(context);
}
bool infrared_scene_universal_fan_on_event(void* context, SceneManagerEvent event) {

View File

@@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_led_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
@@ -71,16 +69,7 @@ void infrared_scene_universal_led_on_enter(void* context) {
button_panel_add_label(button_panel, 21, 11, FontPrimary, "LEDs");
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
infrared_scene_universal_common_on_enter(context);
}
bool infrared_scene_universal_led_on_event(void* context, SceneManagerEvent event) {

View File

@@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_monitor_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
@@ -72,16 +70,7 @@ void infrared_scene_universal_monitor_on_enter(void* context) {
button_panel_add_label(button_panel, 10, 11, FontPrimary, "Monitors");
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
infrared_scene_universal_common_on_enter(context);
}
bool infrared_scene_universal_monitor_on_event(void* context, SceneManagerEvent event) {

View File

@@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_projector_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
@@ -94,16 +92,7 @@ void infrared_scene_universal_projector_on_enter(void* context) {
button_panel_add_label(button_panel, 7, 11, FontPrimary, "Projectors");
button_panel_add_icon(button_panel, 34, 68, &I_vol_ac_text_30x30);
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
infrared_scene_universal_common_on_enter(context);
}
bool infrared_scene_universal_projector_on_event(void* context, SceneManagerEvent event) {

View File

@@ -3,8 +3,6 @@
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_tv_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
InfraredApp* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
@@ -95,16 +93,7 @@ void infrared_scene_universal_tv_on_enter(void* context) {
button_panel_add_label(button_panel, 22, 10, FontPrimary, "TVs");
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
infrared_scene_universal_common_on_enter(context);
}
bool infrared_scene_universal_tv_on_event(void* context, SceneManagerEvent event) {

View File

@@ -11,11 +11,20 @@ App(
fap_category="RFID",
)
App(
appid="lfrfid_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="lfrfid_cli_plugin_ep",
requires=["cli"],
sources=["lfrfid_cli.c"],
)
App(
appid="lfrfid_start",
targets=["f7"],
apptype=FlipperAppType.STARTUP,
entry_point="lfrfid_on_system_start",
sources=["lfrfid_cli.c"],
sources=["lfrfid_start.c"],
order=50,
)

View File

@@ -14,15 +14,6 @@
#include <lfrfid/lfrfid_raw_file.h>
#include <toolbox/pulse_protocols/pulse_glue.h>
static void lfrfid_cli(Cli* cli, FuriString* args, void* context);
// app cli function
void lfrfid_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL);
furi_record_close(RECORD_CLI);
}
static void lfrfid_cli_print_usage() {
printf("Usage:\r\n");
printf("rfid read <optional: normal | indala> - read in ASK/PSK mode\r\n");
@@ -577,3 +568,15 @@ static void lfrfid_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "lfrfid_cli",
.ep_api_version = 1,
.entry_point = &lfrfid_cli,
};
const FlipperAppPluginDescriptor* lfrfid_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,11 @@
#include <cli/cli_i.h>
static void lfrfid_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("lfrfid_cli", 1, cli, args, context);
}
void lfrfid_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}

View File

@@ -242,11 +242,21 @@ MomentumApp* momentum_app_alloc() {
CharList_init(app->mainmenu_app_exes);
Stream* stream = file_stream_alloc(storage);
FuriString* line = furi_string_alloc();
if(file_stream_open(stream, MAINMENU_APPS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
stream_read_line(stream, line);
uint32_t version;
if(file_stream_open(stream, MAINMENU_APPS_PATH, FSAM_READ, FSOM_OPEN_EXISTING) &&
stream_read_line(stream, line) &&
sscanf(furi_string_get_cstr(line), "MenuAppList Version %lu", &version) == 1 &&
version <= 1) {
while(stream_read_line(stream, line)) {
furi_string_replace_all(line, "\r", "");
furi_string_replace_all(line, "\n", "");
if(version == 0) {
if(!furi_string_cmp(line, "RFID")) {
furi_string_set(line, "125 kHz RFID");
} else if(!furi_string_cmp(line, "SubGHz")) {
furi_string_set(line, "Sub-GHz");
}
}
CharList_push_back(app->mainmenu_app_exes, strdup(furi_string_get_cstr(line)));
flipper_application_load_name_and_icon(line, storage, NULL, line);
if(!furi_string_cmp(line, "Momentum")) {

View File

@@ -1,8 +1,6 @@
#include "../momentum_app.h"
enum VarItemListIndex {
VarItemListIndexBadkbMode,
VarItemListIndexBadbtRemember,
VarItemListIndexSubghzFreqs,
VarItemListIndexSubghzExtend,
VarItemListIndexGpioPins,
@@ -13,22 +11,6 @@ void momentum_app_scene_protocols_var_item_list_callback(void* context, uint32_t
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
static void momentum_app_scene_protocols_bad_bt_changed(VariableItem* item) {
MomentumApp* app = variable_item_get_context(item);
bool value = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, value ? "BT" : "USB");
momentum_settings.bad_bt = value;
app->save_settings = true;
}
static void momentum_app_scene_protocols_bad_bt_remember_changed(VariableItem* item) {
MomentumApp* app = variable_item_get_context(item);
bool value = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, value ? "ON" : "OFF");
momentum_settings.bad_bt_remember = value;
app->save_settings = true;
}
static void momentum_app_scene_protocols_subghz_extend_changed(VariableItem* item) {
MomentumApp* app = variable_item_get_context(item);
app->subghz_extend = variable_item_get_current_value_index(item);
@@ -49,20 +31,6 @@ void momentum_app_scene_protocols_on_enter(void* context) {
VariableItemList* var_item_list = app->var_item_list;
VariableItem* item;
item = variable_item_list_add(
var_item_list, "BadKB Mode", 2, momentum_app_scene_protocols_bad_bt_changed, app);
variable_item_set_current_value_index(item, momentum_settings.bad_bt);
variable_item_set_current_value_text(item, momentum_settings.bad_bt ? "BT" : "USB");
item = variable_item_list_add(
var_item_list,
"BadBT Remember",
2,
momentum_app_scene_protocols_bad_bt_remember_changed,
app);
variable_item_set_current_value_index(item, momentum_settings.bad_bt_remember);
variable_item_set_current_value_text(item, momentum_settings.bad_bt_remember ? "ON" : "OFF");
item = variable_item_list_add(var_item_list, "SubGHz Freqs", 0, NULL, app);
variable_item_set_current_value_text(item, ">");

View File

@@ -263,11 +263,20 @@ App(
sources=["plugins/supported_cards/sonicare.c"],
)
App(
appid="nfc_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="nfc_cli_plugin_ep",
requires=["cli"],
sources=["nfc_cli.c"],
)
App(
appid="nfc_start",
targets=["f7"],
apptype=FlipperAppType.STARTUP,
entry_point="nfc_on_system_start",
sources=["nfc_cli.c"],
sources=["nfc_start.c"],
order=30,
)

View File

@@ -63,12 +63,14 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
void nfc_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "nfc", CliCommandFlagDefault, nfc_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(nfc_cli);
#endif
#include <flipper_application/flipper_application.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "nfc_cli",
.ep_api_version = 1,
.entry_point = &nfc_cli,
};
const FlipperAppPluginDescriptor* nfc_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,11 @@
#include <cli/cli_i.h>
static void nfc_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("nfc_cli", 1, cli, args, context);
}
void nfc_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "nfc", CliCommandFlagDefault, nfc_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}

View File

@@ -1,70 +1,139 @@
#include "nfc_supported_card_plugin.h"
#include <flipper_application.h>
#include <bit_lib/bit_lib.h>
#include <flipper_application/flipper_application.h>
#include <nfc/nfc_device.h>
#include "protocols/mf_classic/mf_classic.h"
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include <bit_lib.h>
#include <locale/locale.h>
#define TAG "Bip"
#define SECTOR_BLOCK_OFFSET(sector, block) (((sector) * 4) + (block))
#define BIP_CARD_ID_SECTOR_NUMBER (0)
#define BIP_BALANCE_SECTOR_NUMBER (8)
#define BIP_TRIP_TIME_WINDOW_SECTOR_NUMBER (5)
#define BIP_LAST_TOP_UPS_SECTOR_NUMBER (10)
#define BIP_TRIPS_INFO_SECTOR_NUMBER (11)
static const uint64_t bip_keys_a[] = {
0x3a42f33af429,
0x6338a371c0ed,
0xf124c2578ad0,
0x32ac3b90ac13,
0x4ad1e273eaf1,
0xe2c42591368a,
0x2a3c347a1200,
0x16f3d5ab1139,
0x937a4fff3011,
0x35c3d2caee88,
0x693143f10368,
0xa3f97428dd01,
0x63f17a449af0,
0xc4652c54261c,
0xd49e2826664f,
0x3df14c8000a1,
typedef struct {
DateTime datetime;
uint16_t amount;
} BipTransaction;
typedef struct {
uint64_t a;
uint64_t b;
} MfClassicKeyPair;
static const MfClassicKeyPair bip_1k_keys[] = {
{.a = 0x3a42f33af429, .b = 0x1fc235ac1309},
{.a = 0x6338a371c0ed, .b = 0x243f160918d1},
{.a = 0xf124c2578ad0, .b = 0x9afc42372af1},
{.a = 0x32ac3b90ac13, .b = 0x682d401abb09},
{.a = 0x4ad1e273eaf1, .b = 0x067db45454a9},
{.a = 0xe2c42591368a, .b = 0x15fc4c7613fe},
{.a = 0x2a3c347a1200, .b = 0x68d30288910a},
{.a = 0x16f3d5ab1139, .b = 0xf59a36a2546d},
{.a = 0x937a4fff3011, .b = 0x64e3c10394c2},
{.a = 0x35c3d2caee88, .b = 0xb736412614af},
{.a = 0x693143f10368, .b = 0x324f5df65310},
{.a = 0xa3f97428dd01, .b = 0x643fb6de2217},
{.a = 0x63f17a449af0, .b = 0x82f435dedf01},
{.a = 0xc4652c54261c, .b = 0x0263de1278f3},
{.a = 0xd49e2826664f, .b = 0x51284c3686a6},
{.a = 0x3df14c8000a1, .b = 0x6a470d54127c},
};
static const uint64_t bip_keys_b[] = {
0x1fc235ac1309,
0x243f160918d1,
0x9afc42372af1,
0x682d401abb09,
0x067db45454a9,
0x15fc4c7613fe,
0x68d30288910a,
0xf59a36a2546d,
0x64e3c10394c2,
0xb736412614af,
0x324f5df65310,
0x643fb6de2217,
0x82f435dedf01,
0x0263de1278f3,
0x51284c3686a6,
0x6a470d54127c,
};
static void bip_parse_datetime(const MfClassicBlock* block, DateTime* parsed_data) {
furi_assert(block);
furi_assert(parsed_data);
parsed_data->day = (((block->data[1] << 8) + block->data[0]) >> 6) & 0x1f;
parsed_data->month = (((block->data[1] << 8) + block->data[0]) >> 11) & 0xf;
parsed_data->year = 2000 + ((((block->data[2] << 8) + block->data[1]) >> 7) & 0x1f);
parsed_data->hour = (((block->data[3] << 8) + block->data[2]) >> 4) & 0x1f;
parsed_data->minute = (((block->data[3] << 8) + block->data[2]) >> 9) & 0x3f;
parsed_data->second = (((block->data[4] << 8) + block->data[3]) >> 7) & 0x3f;
}
static void bip_print_datetime(const DateTime* datetime, FuriString* str) {
furi_assert(datetime);
furi_assert(str);
LocaleDateFormat date_format = locale_get_date_format();
const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/";
FuriString* date_str = furi_string_alloc();
locale_format_date(date_str, datetime, date_format, separator);
FuriString* time_str = furi_string_alloc();
locale_format_time(time_str, datetime, locale_get_time_format(), true);
furi_string_cat_printf(
str, "%s %s", furi_string_get_cstr(date_str), furi_string_get_cstr(time_str));
furi_string_free(date_str);
furi_string_free(time_str);
}
static int datetime_cmp(const DateTime* dt_1, const DateTime* dt_2) {
furi_assert(dt_1);
furi_assert(dt_2);
if(dt_1->year != dt_2->year) {
return dt_1->year - dt_2->year;
}
if(dt_1->month != dt_2->month) {
return dt_1->month - dt_2->month;
}
if(dt_1->day != dt_2->day) {
return dt_1->day - dt_2->day;
}
if(dt_1->hour != dt_2->hour) {
return dt_1->hour - dt_2->hour;
}
if(dt_1->minute != dt_2->minute) {
return dt_1->minute - dt_2->minute;
}
if(dt_1->second != dt_2->second) {
return dt_1->second - dt_2->second;
}
return 0;
}
static bool is_bip_block_empty(const MfClassicBlock* block) {
furi_assert(block);
// check if all but last byte are zero (last is checksum)
for(size_t i = 0; i < sizeof(block->data) - 1; i++) {
if(block->data[i] != 0) {
return false;
}
}
return true;
}
bool bip_verify(Nfc* nfc) {
bool verified = true;
bool verified = false;
const uint8_t verify_sector = 0;
uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
do {
const uint8_t verify_sector = 0;
uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
MfClassicKey key_a_0 = {};
bit_lib_num_to_bytes_be(bip_keys_a[0], COUNT_OF(key_a_0.data), key_a_0.data);
MfClassicKey key = {};
bit_lib_num_to_bytes_be(bip_1k_keys[0].a, COUNT_OF(key.data), key.data);
MfClassicAuthContext auth_ctx = {};
MfClassicError error =
mf_classic_poller_sync_auth(nfc, block_num, &key_a_0, MfClassicKeyTypeA, &auth_ctx);
MfClassicAuthContext auth_ctx = {};
MfClassicError error =
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
if(error == MfClassicErrorNotPresent) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
verified = false;
}
if(error == MfClassicErrorNone) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
break;
}
verified = true;
} while(false);
return verified;
}
@@ -79,31 +148,33 @@ static bool bip_read(Nfc* nfc, NfcDevice* device) {
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
do {
MfClassicType type = MfClassicType1k;
MfClassicType type = MfClassicTypeMini;
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
if(error == MfClassicErrorNotPresent) {
FURI_LOG_W(TAG, "Card not MIFARE Classic 1k");
break;
}
if(error != MfClassicErrorNone) break;
data->type = type;
MfClassicDeviceKeys keys = {};
if(type != MfClassicType1k) break;
MfClassicDeviceKeys keys = {
.key_a_mask = 0,
.key_b_mask = 0,
};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(bip_keys_a[i], sizeof(MfClassicKey), keys.key_a[i].data);
bit_lib_num_to_bytes_be(bip_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(bip_keys_b[i], sizeof(MfClassicKey), keys.key_b[i].data);
bit_lib_num_to_bytes_be(bip_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
FURI_BIT_SET(keys.key_b_mask, i);
}
error = mf_classic_poller_sync_read(nfc, &keys, data);
if(error == MfClassicErrorNotPresent) {
FURI_LOG_W(TAG, "Failed to read data. Bad keys?");
FURI_LOG_W(TAG, "Failed to read data");
break;
}
nfc_device_set_data(device, NfcProtocolMfClassic, data);
is_read = true;
is_read = (error == MfClassicErrorNone);
} while(false);
mf_classic_free(data);
@@ -111,183 +182,91 @@ static bool bip_read(Nfc* nfc, NfcDevice* device) {
return is_read;
}
typedef struct {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} BipTimestamp;
static void parse_bip_timestamp(const MfClassicBlock* block, BipTimestamp* timestamp) {
furi_assert(block);
furi_assert(timestamp);
timestamp->day = (((block->data[1] << 8) + block->data[0]) >> 6) & 0x1f;
timestamp->month = (((block->data[1] << 8) + block->data[0]) >> 11) & 0xf;
timestamp->year = 2000 + ((((block->data[2] << 8) + block->data[1]) >> 7) & 0x1f);
timestamp->hour = (((block->data[3] << 8) + block->data[2]) >> 4) & 0x1f;
timestamp->minute = (((block->data[3] << 8) + block->data[2]) >> 9) & 0x3f;
timestamp->second = (((block->data[4] << 8) + block->data[3]) >> 7) & 0x3f;
}
static int compare_bip_timestamp(const BipTimestamp* t1, const BipTimestamp* t2) {
furi_assert(t1);
furi_assert(t2);
if(t1->year != t2->year) {
return t1->year - t2->year;
}
if(t1->month != t2->month) {
return t1->month - t2->month;
}
if(t1->day != t2->day) {
return t1->day - t2->day;
}
if(t1->hour != t2->hour) {
return t1->hour - t2->hour;
}
if(t1->minute != t2->minute) {
return t1->minute - t2->minute;
}
if(t1->second != t2->second) {
return t1->second - t2->second;
}
return 0;
}
static void print_bip_timestamp(const BipTimestamp* timestamp, FuriString* str) {
furi_assert(timestamp);
furi_assert(str);
furi_string_cat_printf(
str,
"%04u-%02u-%02u %02u:%02u:%02u",
timestamp->year,
timestamp->month,
timestamp->day,
timestamp->hour,
timestamp->minute,
timestamp->second);
}
static bool is_bip_block_empty(const MfClassicBlock* block) {
furi_assert(block);
// check if all but last byte are zero (last is checksum)
for(size_t i = 0; i < sizeof(block->data) - 1; i++) {
if(block->data[i] != 0) {
return false;
}
}
return true;
}
static void parse_uint16_le(const uint8_t* data, uint16_t* value) {
furi_assert(data);
furi_assert(value);
*value = (data[0]) | (data[1] << 8);
}
static void parse_uint32_le(const uint8_t* data, uint32_t* value) {
furi_assert(data);
furi_assert(value);
*value = (data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
}
static void parse_uint16_txn_amount(const uint8_t* data, uint16_t* value) {
furi_assert(data);
furi_assert(value);
parse_uint16_le(data, value);
*value = *value >> 2;
}
typedef struct {
BipTimestamp timestamp;
uint16_t amount;
} BipTransaction;
static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
furi_assert(parsed_data);
bool parsed = true;
struct {
uint32_t card_id;
uint16_t balance;
uint16_t flags;
BipTimestamp trip_time_window;
DateTime trip_time_window;
BipTransaction top_ups[3];
BipTransaction charges[3];
} bip_data = {
.card_id = 0,
.balance = 0,
.flags = 0,
.trip_time_window = {0, 0, 0, 0, 0, 0},
.top_ups =
{
{{0, 0, 0, 0, 0, 0}, 0},
{{0, 0, 0, 0, 0, 0}, 0},
{{0, 0, 0, 0, 0, 0}, 0},
},
.charges =
{
{{0, 0, 0, 0, 0, 0}, 0},
{{0, 0, 0, 0, 0, 0}, 0},
{{0, 0, 0, 0, 0, 0}, 0},
},
};
} bip_data = {0};
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
bool parsed = false;
do {
// verify first sector keys
// verify sector 0 key A
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0);
if(data->type != MfClassicType1k) break;
uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
if(key != bip_keys_a[0]) {
parsed = false;
if(key != bip_1k_keys[0].a) {
break;
}
// verify sector 0 key B
key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6);
if(key != bip_keys_b[0]) {
parsed = false;
if(key != bip_1k_keys[0].b) {
break;
}
// Get Card ID, little-endian 4 bytes at sector 0 block 1, bytes 4-7
parse_uint32_le(&data->block[SECTOR_BLOCK_OFFSET(0, 1)].data[4], &bip_data.card_id);
const uint8_t card_id_start_block_num =
mf_classic_get_first_block_num_of_sector(BIP_CARD_ID_SECTOR_NUMBER);
const uint8_t* block_start_ptr = &data->block[card_id_start_block_num + 1].data[0];
bip_data.card_id = bit_lib_bytes_to_num_le(block_start_ptr + 4, 4);
// Get balance, little-endian 2 bytes at sector 8 block 1, bytes 0-1
parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[0], &bip_data.balance);
const uint8_t balance_start_block_num =
mf_classic_get_first_block_num_of_sector(BIP_BALANCE_SECTOR_NUMBER);
block_start_ptr = &data->block[balance_start_block_num + 1].data[0];
bip_data.balance = bit_lib_bytes_to_num_le(block_start_ptr, 2);
// Get balance flags (negative balance, etc.), little-endian 2 bytes at sector 8 block 1, bytes 2-3
parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[2], &bip_data.flags);
bip_data.flags = bit_lib_bytes_to_num_le(block_start_ptr + 2, 2);
// Get trip time window, proprietary format, at sector 5 block 1, bytes 0-7
parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(5, 1)], &bip_data.trip_time_window);
const uint8_t trip_time_window_start_block_num =
mf_classic_get_first_block_num_of_sector(BIP_TRIP_TIME_WINDOW_SECTOR_NUMBER);
const MfClassicBlock* trip_window_block_ptr =
&data->block[trip_time_window_start_block_num + 1];
bip_parse_datetime(trip_window_block_ptr, &bip_data.trip_time_window);
// Last 3 top-ups: sector 10, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 9-10
const uint8_t top_ups_start_block_num =
mf_classic_get_first_block_num_of_sector(BIP_LAST_TOP_UPS_SECTOR_NUMBER);
for(size_t i = 0; i < 3; i++) {
if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(10, i)])) {
continue;
}
const MfClassicBlock* block = &data->block[top_ups_start_block_num + i];
if(is_bip_block_empty(block)) continue;
BipTransaction* top_up = &bip_data.top_ups[i];
parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(10, i)], &top_up->timestamp);
parse_uint16_txn_amount(
&data->block[SECTOR_BLOCK_OFFSET(10, i)].data[9], &top_up->amount);
bip_parse_datetime(block, &top_up->datetime);
top_up->amount = bit_lib_bytes_to_num_le(&block->data[9], 2) >> 2;
}
// Last 3 charges (i.e. trips), sector 11, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 10-11
const uint8_t trips_start_block_num =
mf_classic_get_first_block_num_of_sector(BIP_TRIPS_INFO_SECTOR_NUMBER);
for(size_t i = 0; i < 3; i++) {
if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(11, i)])) {
continue;
}
const MfClassicBlock* block = &data->block[trips_start_block_num + i];
if(is_bip_block_empty(block)) continue;
BipTransaction* charge = &bip_data.charges[i];
parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(11, i)], &charge->timestamp);
parse_uint16_txn_amount(
&data->block[SECTOR_BLOCK_OFFSET(11, i)].data[10], &charge->amount);
bip_parse_datetime(block, &charge->datetime);
charge->amount = bit_lib_bytes_to_num_le(&block->data[10], 2) >> 2;
}
// All data is now parsed and stored in bip_data, now print it
@@ -303,14 +282,14 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) {
bip_data.balance,
bip_data.flags);
print_bip_timestamp(&bip_data.trip_time_window, parsed_data);
bip_print_datetime(&bip_data.trip_time_window, parsed_data);
// Find newest top-up
size_t newest_top_up = 0;
for(size_t i = 1; i < 3; i++) {
const BipTimestamp* newest = &bip_data.top_ups[newest_top_up].timestamp;
const BipTimestamp* current = &bip_data.top_ups[i].timestamp;
if(compare_bip_timestamp(current, newest) > 0) {
const DateTime* newest = &bip_data.top_ups[newest_top_up].datetime;
const DateTime* current = &bip_data.top_ups[i].datetime;
if(datetime_cmp(current, newest) > 0) {
newest_top_up = i;
}
}
@@ -320,15 +299,15 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) {
for(size_t i = 0; i < 3; i++) {
const BipTransaction* top_up = &bip_data.top_ups[(3u + newest_top_up - i) % 3];
furi_string_cat_printf(parsed_data, "\n+$%d\n @", top_up->amount);
print_bip_timestamp(&top_up->timestamp, parsed_data);
bip_print_datetime(&top_up->datetime, parsed_data);
}
// Find newest charge
size_t newest_charge = 0;
for(size_t i = 1; i < 3; i++) {
const BipTimestamp* newest = &bip_data.charges[newest_charge].timestamp;
const BipTimestamp* current = &bip_data.charges[i].timestamp;
if(compare_bip_timestamp(current, newest) > 0) {
const DateTime* newest = &bip_data.charges[newest_charge].datetime;
const DateTime* current = &bip_data.charges[i].datetime;
if(datetime_cmp(current, newest) > 0) {
newest_charge = i;
}
}
@@ -338,7 +317,7 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) {
for(size_t i = 0; i < 3; i++) {
const BipTransaction* charge = &bip_data.charges[(3u + newest_charge - i) % 3];
furi_string_cat_printf(parsed_data, "\n-$%d\n @", charge->amount);
print_bip_timestamp(&charge->timestamp, parsed_data);
bip_print_datetime(&charge->datetime, parsed_data);
}
parsed = true;

View File

@@ -1,6 +1,16 @@
App(
appid="onewire_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="onewire_cli_plugin_ep",
requires=["cli"],
sources=["onewire_cli.c"],
)
App(
appid="onewire_start",
apptype=FlipperAppType.STARTUP,
entry_point="onewire_on_system_start",
sources=["onewire_start.c"],
order=60,
)

View File

@@ -6,18 +6,6 @@
#include <one_wire/one_wire_host.h>
static void onewire_cli(Cli* cli, FuriString* args, void* context);
void onewire_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli, cli);
furi_record_close(RECORD_CLI);
#else
UNUSED(onewire_cli);
#endif
}
static void onewire_cli_print_usage() {
printf("Usage:\r\n");
printf("onewire search\r\n");
@@ -70,3 +58,15 @@ void onewire_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "onewire_cli",
.ep_api_version = 1,
.entry_point = &onewire_cli,
};
const FlipperAppPluginDescriptor* onewire_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,11 @@
#include <cli/cli_i.h>
static void onewire_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("onewire_cli", 1, cli, args, context);
}
void onewire_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli_wrapper, cli);
furi_record_close(RECORD_CLI);
}

View File

@@ -32,13 +32,22 @@ App(
fap_category="Sub-GHz",
)
App(
appid="subghz_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="subghz_cli_plugin_ep",
requires=["cli"],
sources=["subghz_cli.c", "helpers/subghz_chat.c"],
)
App(
appid="subghz_start",
targets=["f7"],
apptype=FlipperAppType.STARTUP,
entry_point="subghz_on_system_start",
# sources=["subghz_cli.c", "helpers/subghz_chat.c"],
# sources=["subghz_start.c"],
order=40,
)

View File

@@ -1197,17 +1197,14 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
void subghz_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
#include <flipper_application/flipper_application.h>
cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command, NULL);
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "subghz_cli",
.ep_api_version = 1,
.entry_point = &subghz_cli_command,
};
// psst RM... i know you dont care much about errors, but if you ever see this... incompatible pointer type :3
cli_add_command(cli, "chat", CliCommandFlagDefault, subghz_cli_command_chat, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(subghz_cli_command);
#endif
const FlipperAppPluginDescriptor* subghz_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,17 @@
#include <cli/cli_i.h>
static void subghz_cli_command_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("subghz_cli", 1, cli, args, context);
}
static void subghz_cli_command_chat_wrapper(Cli* cli, FuriString* args, void* context) {
furi_string_replace_at(args, 0, 0, "chat ");
subghz_cli_command_wrapper(cli, args, context);
}
void subghz_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command_wrapper, NULL);
cli_add_command(cli, "chat", CliCommandFlagDefault, subghz_cli_command_chat_wrapper, NULL);
furi_record_close(RECORD_CLI);
}

View File

@@ -17,6 +17,15 @@ App(
sdk_headers=["bt_service/bt.h", "bt_settings.h"],
)
App(
appid="bt_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="bt_cli_plugin_ep",
requires=["cli"],
sources=["bt_cli.c"],
)
App(
appid="bt_start",
apptype=FlipperAppType.STARTUP,

View File

@@ -5,7 +5,7 @@
#include <ble/ble.h>
#include "bt_settings.h"
#include "bt_service/bt.h"
#include "bt_service/bt_i.h"
#include <profiles/serial_profile.h>
static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) {
@@ -181,12 +181,10 @@ static void bt_cli_print_usage() {
static void bt_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(context);
furi_record_open(RECORD_BT);
Bt* bt = furi_record_open(RECORD_BT);
FuriString* cmd;
cmd = furi_string_alloc();
BtSettings bt_settings;
bt_settings_load(&bt_settings);
do {
if(!args_read_string_and_trim(args, cmd)) {
@@ -219,7 +217,7 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) {
bt_cli_print_usage();
} while(false);
if(bt_settings.enabled) {
if(bt->bt_settings.enabled) {
furi_hal_bt_start_advertising();
}
@@ -227,12 +225,14 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) {
furi_record_close(RECORD_BT);
}
void bt_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(bt_cli);
#endif
#include <flipper_application/flipper_application.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "bt_cli",
.ep_api_version = 1,
.entry_point = &bt_cli,
};
const FlipperAppPluginDescriptor* bt_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,12 @@
#include <cli/cli_i.h>
#include "bt_service/bt.h"
static void bt_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("bt_cli", 1, cli, args, context);
}
void bt_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}

View File

@@ -4,6 +4,10 @@
#include <furi_hal_version.h>
#include <loader/loader.h>
#include <flipper_application/flipper_application.h>
#include <loader/firmware_api/firmware_api.h>
#include <inttypes.h>
#define TAG "CliSrv"
#define CLI_INPUT_LEN_LIMIT 256
@@ -481,3 +485,58 @@ int32_t cli_srv(void* p) {
return 0;
}
void cli_plugin_wrapper(
const char* handler_name,
uint32_t handler_version,
Cli* cli,
FuriString* args,
void* context) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperApplication* plugin_app = flipper_application_alloc(storage, firmware_api_interface);
do {
FuriString* full_handler_path =
furi_string_alloc_printf(EXT_PATH("apps_data/cli/plugins/%s.fal"), handler_name);
FlipperApplicationPreloadStatus preload_res =
flipper_application_preload(plugin_app, furi_string_get_cstr(full_handler_path));
furi_string_free(full_handler_path);
if(preload_res != FlipperApplicationPreloadStatusSuccess) {
printf("Failed to preload CLI plugin. Code: %d\r\n", preload_res);
break;
}
if(!flipper_application_is_plugin(plugin_app)) {
printf("CLI plugin file is not a library\r\n");
break;
}
FlipperApplicationLoadStatus load_status = flipper_application_map_to_memory(plugin_app);
if(load_status != FlipperApplicationLoadStatusSuccess) {
printf("Failed to load CLI plugin file. Code %d\r\n", load_status);
break;
}
const FlipperAppPluginDescriptor* app_descriptor =
flipper_application_plugin_get_descriptor(plugin_app);
if(strcmp(app_descriptor->appid, handler_name) != 0) {
printf("CLI plugin type doesn't match\r\n");
break;
}
if(app_descriptor->ep_api_version != handler_version) {
printf(
"CLI plugin version %" PRIu32 " doesn't match\r\n",
app_descriptor->ep_api_version);
break;
}
const CliCallback handler = app_descriptor->entry_point;
handler(cli, args, context);
} while(false);
flipper_application_free(plugin_app);
furi_record_close(RECORD_STORAGE);
}

View File

@@ -62,6 +62,16 @@ void cli_putc(Cli* cli, char c);
void cli_stdout_callback(void* _cookie, const char* data, size_t size);
// Wraps CLI commands to load from plugin file
// Must call from CLI context, like dummy CLI command callback
// You need to setup the plugin to compile correctly separately
void cli_plugin_wrapper(
const char* handler_name,
uint32_t handler_version,
Cli* cli,
FuriString* args,
void* context);
#ifdef __cplusplus
}
#endif

View File

@@ -1,6 +1,16 @@
App(
appid="crypto_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="crypto_cli_plugin_ep",
requires=["cli"],
sources=["crypto_cli.c"],
)
App(
appid="crypto_start",
apptype=FlipperAppType.STARTUP,
entry_point="crypto_on_system_start",
sources=["crypto_start.c"],
order=10,
)

View File

@@ -316,12 +316,14 @@ static void crypto_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
void crypto_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(crypto_cli);
#endif
#include <flipper_application/flipper_application.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "crypto_cli",
.ep_api_version = 1,
.entry_point = &crypto_cli,
};
const FlipperAppPluginDescriptor* crypto_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,11 @@
#include <cli/cli_i.h>
static void crypto_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("crypto_cli", 1, cli, args, context);
}
void crypto_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}

View File

@@ -1,6 +1,8 @@
/**
* @file view_dispatcher.h
* GUI: ViewDispatcher API
* @brief GUI: ViewDispatcher API
*
* @warning Views added to a ViewDispatcher MUST NOT be in a ViewStack at the same time.
*/
#pragma once

View File

@@ -1,3 +1,9 @@
/**
* @file view_holder.h
* @brief GUI: ViewHolder API
*
* @warning View added to a ViewHolder MUST NOT be in a ViewStack at the same time.
*/
#pragma once
#include <gui/view.h>

View File

@@ -1,11 +1,13 @@
/**
* @file view_stack.h
* GUI: ViewStack API
* @brief GUI: ViewStack API
*
* ViewStack accumulates several Views in one stack.
* Draw callbacks are called sequentially starting from
* first added. Input callbacks are called in reverse order.
* Consumed input is not passed on underlying layers.
*
* @warning Views added to a ViewStack MUST NOT be in a ViewDispatcher or a ViewHolder at the same time.
*/
#pragma once

View File

@@ -8,3 +8,12 @@ App(
order=80,
sdk_headers=["input.h"],
)
App(
appid="input_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="input_cli_plugin_ep",
requires=["cli"],
sources=["input_cli.c"],
)

View File

@@ -54,6 +54,12 @@ const char* input_get_type_name(InputType type) {
}
}
#include <cli/cli_i.h>
static void input_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("input_cli", 1, cli, args, context);
}
int32_t input_srv(void* p) {
UNUSED(p);
input = malloc(sizeof(Input));
@@ -69,7 +75,9 @@ int32_t input_srv(void* p) {
#ifdef SRV_CLI
input->cli = furi_record_open(RECORD_CLI);
cli_add_command(input->cli, "input", CliCommandFlagParallelSafe, input_cli, input);
cli_add_command(input->cli, "input", CliCommandFlagParallelSafe, input_cli_wrapper, input);
#else
UNUSED(input_cli_wrapper);
#endif
input->pin_states = malloc(input_pins_count * sizeof(InputPinState));

View File

@@ -222,3 +222,15 @@ void input_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
#include <flipper_application/flipper_application.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "input_cli",
.ep_api_version = 1,
.entry_point = &input_cli,
};
const FlipperAppPluginDescriptor* input_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -14,6 +14,15 @@ App(
],
)
App(
appid="loader_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="loader_cli_plugin_ep",
requires=["cli"],
sources=["loader_cli.c"],
)
App(
appid="loader_start",
apptype=FlipperAppType.STARTUP,

View File

@@ -104,12 +104,14 @@ static void loader_cli(Cli* cli, FuriString* args, void* context) {
furi_record_close(RECORD_LOADER);
}
void loader_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(loader_cli);
#endif
#include <flipper_application/flipper_application.h>
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "loader_cli",
.ep_api_version = 1,
.entry_point = &loader_cli,
};
const FlipperAppPluginDescriptor* loader_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,12 @@
#include <cli/cli_i.h>
#include "loader.h"
static void loader_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("loader_cli", 1, cli, args, context);
}
void loader_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}

View File

@@ -17,6 +17,15 @@ App(
sdk_headers=["power_service/power.h", "power_settings.h"],
)
App(
appid="power_cli",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="power_cli_plugin_ep",
requires=["cli"],
sources=["power_cli.c"],
)
App(
appid="power_start",
apptype=FlipperAppType.STARTUP,

View File

@@ -106,14 +106,14 @@ void power_cli(Cli* cli, FuriString* args, void* context) {
furi_string_free(cmd);
}
void power_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
#include <flipper_application/flipper_application.h>
cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli, NULL);
static const FlipperAppPluginDescriptor plugin_descriptor = {
.appid = "power_cli",
.ep_api_version = 1,
.entry_point = &power_cli,
};
furi_record_close(RECORD_CLI);
#else
UNUSED(power_cli);
#endif
const FlipperAppPluginDescriptor* power_cli_plugin_ep() {
return &plugin_descriptor;
}

View File

@@ -0,0 +1,11 @@
#include <cli/cli_i.h>
static void power_cli_wrapper(Cli* cli, FuriString* args, void* context) {
cli_plugin_wrapper("power_cli", 1, cli, args, context);
}
void power_on_system_start() {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli_wrapper, NULL);
furi_record_close(RECORD_CLI);
}

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