From 0428e82b14065e36cdae352b1b59a6ededa3c6f8 Mon Sep 17 00:00:00 2001 From: porta Date: Thu, 12 Sep 2024 19:32:07 +0300 Subject: [PATCH 01/36] Fix USB-UART bridge exit screen stopping the bridge prematurely (#3892) * fix: exit screen stopping bridge prematurely * refactor: merge exit confirmation scene into main usb uart scene --- .../main/gpio/scenes/gpio_scene_config.h | 1 - .../gpio/scenes/gpio_scene_exit_confirm.c | 44 ------------------- .../main/gpio/scenes/gpio_scene_usb_uart.c | 32 +++++++++----- 3 files changed, 21 insertions(+), 56 deletions(-) delete mode 100644 applications/main/gpio/scenes/gpio_scene_exit_confirm.c diff --git a/applications/main/gpio/scenes/gpio_scene_config.h b/applications/main/gpio/scenes/gpio_scene_config.h index d6fd24d19d3..3406e42d3a2 100644 --- a/applications/main/gpio/scenes/gpio_scene_config.h +++ b/applications/main/gpio/scenes/gpio_scene_config.h @@ -3,4 +3,3 @@ ADD_SCENE(gpio, test, Test) ADD_SCENE(gpio, usb_uart, UsbUart) ADD_SCENE(gpio, usb_uart_cfg, UsbUartCfg) ADD_SCENE(gpio, usb_uart_close_rpc, UsbUartCloseRpc) -ADD_SCENE(gpio, exit_confirm, ExitConfirm) diff --git a/applications/main/gpio/scenes/gpio_scene_exit_confirm.c b/applications/main/gpio/scenes/gpio_scene_exit_confirm.c deleted file mode 100644 index efb0734a314..00000000000 --- a/applications/main/gpio/scenes/gpio_scene_exit_confirm.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "gpio_app_i.h" - -void gpio_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { - GpioApp* app = context; - - view_dispatcher_send_custom_event(app->view_dispatcher, result); -} - -void gpio_scene_exit_confirm_on_enter(void* context) { - GpioApp* app = context; - DialogEx* dialog = app->dialog; - - dialog_ex_set_context(dialog, app); - dialog_ex_set_left_button_text(dialog, "Exit"); - dialog_ex_set_right_button_text(dialog, "Stay"); - dialog_ex_set_header(dialog, "Exit USB-UART?", 22, 12, AlignLeft, AlignTop); - dialog_ex_set_result_callback(dialog, gpio_scene_exit_confirm_dialog_callback); - - view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewExitConfirm); -} - -bool gpio_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { - GpioApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == DialogExResultRight) { - consumed = scene_manager_previous_scene(app->scene_manager); - } else if(event.event == DialogExResultLeft) { - scene_manager_search_and_switch_to_previous_scene(app->scene_manager, GpioSceneStart); - } - } else if(event.type == SceneManagerEventTypeBack) { - consumed = true; - } - - return consumed; -} - -void gpio_scene_exit_confirm_on_exit(void* context) { - GpioApp* app = context; - - // Clean view - dialog_ex_reset(app->dialog); -} diff --git a/applications/main/gpio/scenes/gpio_scene_usb_uart.c b/applications/main/gpio/scenes/gpio_scene_usb_uart.c index 9a3514ca4f5..e3e7e8c24a0 100644 --- a/applications/main/gpio/scenes/gpio_scene_usb_uart.c +++ b/applications/main/gpio/scenes/gpio_scene_usb_uart.c @@ -6,7 +6,7 @@ typedef struct { UsbUartState state; } SceneUsbUartBridge; -static SceneUsbUartBridge* scene_usb_uart; +static SceneUsbUartBridge* scene_usb_uart = NULL; void gpio_scene_usb_uart_callback(GpioCustomEvent event, void* context) { furi_assert(context); @@ -14,10 +14,21 @@ void gpio_scene_usb_uart_callback(GpioCustomEvent event, void* context) { view_dispatcher_send_custom_event(app->view_dispatcher, event); } +void gpio_scene_usb_uart_dialog_callback(DialogExResult result, void* context) { + GpioApp* app = context; + if(result == DialogExResultLeft) { + usb_uart_disable(app->usb_uart_bridge); + free(scene_usb_uart); + scene_usb_uart = NULL; + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, GpioSceneStart); + } else { + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); + } +} + void gpio_scene_usb_uart_on_enter(void* context) { GpioApp* app = context; - uint32_t prev_state = scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUart); - if(prev_state == 0) { + if(!scene_usb_uart) { scene_usb_uart = malloc(sizeof(SceneUsbUartBridge)); scene_usb_uart->cfg.vcp_ch = 0; scene_usb_uart->cfg.uart_ch = 0; @@ -31,7 +42,6 @@ void gpio_scene_usb_uart_on_enter(void* context) { usb_uart_get_state(app->usb_uart_bridge, &scene_usb_uart->state); gpio_usb_uart_set_callback(app->gpio_usb_uart, gpio_scene_usb_uart_callback, app); - scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 0); view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); notification_message(app->notifications, &sequence_display_backlight_enforce_on); } @@ -39,11 +49,16 @@ void gpio_scene_usb_uart_on_enter(void* context) { bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { GpioApp* app = context; if(event.type == SceneManagerEventTypeCustom) { - scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 1); scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCfg); return true; } else if(event.type == SceneManagerEventTypeBack) { - scene_manager_next_scene(app->scene_manager, GpioSceneExitConfirm); + DialogEx* dialog = app->dialog; + dialog_ex_set_context(dialog, app); + dialog_ex_set_left_button_text(dialog, "Exit"); + dialog_ex_set_right_button_text(dialog, "Stay"); + dialog_ex_set_header(dialog, "Exit USB-UART?", 22, 12, AlignLeft, AlignTop); + dialog_ex_set_result_callback(dialog, gpio_scene_usb_uart_dialog_callback); + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewExitConfirm); return true; } else if(event.type == SceneManagerEventTypeTick) { uint32_t tx_cnt_last = scene_usb_uart->state.tx_cnt; @@ -61,10 +76,5 @@ bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { void gpio_scene_usb_uart_on_exit(void* context) { GpioApp* app = context; - uint32_t prev_state = scene_manager_get_scene_state(app->scene_manager, GpioSceneUsbUart); - if(prev_state == 0) { - usb_uart_disable(app->usb_uart_bridge); - free(scene_usb_uart); - } notification_message(app->notifications, &sequence_display_backlight_enforce_auto); } From b670d5b6e2081bec5abcf3a693db5ce3af5d8ab2 Mon Sep 17 00:00:00 2001 From: porta Date: Fri, 13 Sep 2024 20:31:07 +0300 Subject: [PATCH 02/36] [FL-3885] Put errno into TCB (#3893) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: thread-safe errno * ci: fix pvs warning * ci: silence pvs warning * fix: 🤯 * test: convert test app into a unit test --- .../unit_tests/tests/furi/furi_errno_test.c | 51 +++++++++++++++++++ .../debug/unit_tests/tests/furi/furi_test.c | 6 +++ targets/f7/inc/FreeRTOSConfig.h | 13 +++-- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 applications/debug/unit_tests/tests/furi/furi_errno_test.c diff --git a/applications/debug/unit_tests/tests/furi/furi_errno_test.c b/applications/debug/unit_tests/tests/furi/furi_errno_test.c new file mode 100644 index 00000000000..b42e7c0828e --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_errno_test.c @@ -0,0 +1,51 @@ +#include +#include +#include "../test.h" // IWYU pragma: keep + +#define TAG "ErrnoTest" +#define THREAD_CNT 16 +#define ITER_CNT 1000 + +static int32_t errno_fuzzer(void* context) { + int start_value = (int)context; + int32_t fails = 0; + + for(int i = start_value; i < start_value + ITER_CNT; i++) { + errno = i; + furi_thread_yield(); + if(errno != i) fails++; + } + + for(int i = 0; i < ITER_CNT; i++) { + errno = 0; + furi_thread_yield(); + UNUSED(strtol("123456", NULL, 10)); // -V530 + furi_thread_yield(); + if(errno != 0) fails++; + + errno = 0; + furi_thread_yield(); + UNUSED(strtol("123456123456123456123456123456123456123456123456", NULL, 10)); // -V530 + furi_thread_yield(); + if(errno != ERANGE) fails++; + } + + return fails; +} + +void test_errno_saving(void) { + FuriThread* threads[THREAD_CNT]; + + for(int i = 0; i < THREAD_CNT; i++) { + int start_value = i * ITER_CNT; + threads[i] = furi_thread_alloc_ex("ErrnoFuzzer", 1024, errno_fuzzer, (void*)start_value); + furi_thread_set_priority(threads[i], FuriThreadPriorityNormal); + furi_thread_start(threads[i]); + } + + for(int i = 0; i < THREAD_CNT; i++) { + furi_thread_join(threads[i]); + mu_assert_int_eq(0, furi_thread_get_return_code(threads[i])); + furi_thread_free(threads[i]); + } +} diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index be579d2b8c0..2a76d5184c4 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -8,6 +8,7 @@ void test_furi_concurrent_access(void); void test_furi_pubsub(void); void test_furi_memmgr(void); void test_furi_event_loop(void); +void test_errno_saving(void); static int foo = 0; @@ -42,6 +43,10 @@ MU_TEST(mu_test_furi_event_loop) { test_furi_event_loop(); } +MU_TEST(mu_test_errno_saving) { + test_errno_saving(); +} + MU_TEST_SUITE(test_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_check); @@ -51,6 +56,7 @@ MU_TEST_SUITE(test_suite) { MU_RUN_TEST(mu_test_furi_pubsub); MU_RUN_TEST(mu_test_furi_memmgr); MU_RUN_TEST(mu_test_furi_event_loop); + MU_RUN_TEST(mu_test_errno_saving); } int run_minunit_test_furi(void) { diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 2948faef934..82cda2c6da7 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -2,6 +2,7 @@ #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include +#include #pragma GCC diagnostic ignored "-Wredundant-decls" #endif @@ -26,6 +27,7 @@ #define configUSE_16_BIT_TICKS 0 #define configMAX_PRIORITIES (32) #define configMINIMAL_STACK_SIZE ((uint16_t)128) +#define configUSE_POSIX_ERRNO 1 /* Heap size determined automatically by linker */ // #define configTOTAL_HEAP_SIZE ((size_t)0) @@ -146,9 +148,14 @@ standard names. */ #define configOVERRIDE_DEFAULT_TICK_CONFIGURATION \ 1 /* required only for Keil but does not hurt otherwise */ -#define traceTASK_SWITCHED_IN() \ - extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ - furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB->pxStack) +#define traceTASK_SWITCHED_IN() \ + extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ + furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB->pxStack); \ + errno = pxCurrentTCB->iTaskErrno +// ^^^^^ acquire errno directly from TCB because FreeRTOS assigns its `FreeRTOS_errno' _after_ our hook is called + +// referencing `FreeRTOS_errno' here vvvvv because FreeRTOS calls our hook _before_ copying the value into the TCB, hence a manual write to the TCB would get overwritten +#define traceTASK_SWITCHED_OUT() FreeRTOS_errno = errno #define portCLEAN_UP_TCB(pxTCB) \ extern void furi_thread_cleanup_tcb_event(TaskHandle_t task); \ From 19a3736fe5dab5d01220660b318fed824a2d6e22 Mon Sep 17 00:00:00 2001 From: porta Date: Sun, 15 Sep 2024 18:01:42 +0300 Subject: [PATCH 03/36] [FL-3891] Folder rename fails (#3896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix, refactor: storage is_subdir API * docs: fix incorrect comment * test: new storage apis * test: use temporary path * style: fix formatting * UnitTest: storage path macros naming * UnitTest: storage path macros naming part 2 Co-authored-by: あく --- .../unit_tests/tests/storage/storage_test.c | 87 +++++++++++++++---- applications/services/storage/storage.h | 25 +++--- .../services/storage/storage_external_api.c | 18 ++-- .../services/storage/storage_message.h | 2 +- .../services/storage/storage_processing.c | 18 +++- targets/f18/api_symbols.csv | 5 +- targets/f7/api_symbols.csv | 5 +- 7 files changed, 120 insertions(+), 40 deletions(-) diff --git a/applications/debug/unit_tests/tests/storage/storage_test.c b/applications/debug/unit_tests/tests/storage/storage_test.c index f317fbf6803..75c52ef9a7a 100644 --- a/applications/debug/unit_tests/tests/storage/storage_test.c +++ b/applications/debug/unit_tests/tests/storage/storage_test.c @@ -6,9 +6,10 @@ // This is a hack to access internal storage functions and definitions #include -#define UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/" path) +#define UNIT_TESTS_RESOURCES_PATH(path) EXT_PATH("unit_tests/" path) +#define UNIT_TESTS_PATH(path) EXT_PATH(".tmp/unit_tests/" path) -#define STORAGE_LOCKED_FILE EXT_PATH("locked_file.test") +#define STORAGE_LOCKED_FILE UNIT_TESTS_PATH("locked_file.test") #define STORAGE_LOCKED_DIR STORAGE_INT_PATH_PREFIX #define STORAGE_TEST_DIR UNIT_TESTS_PATH("test_dir") @@ -369,33 +370,78 @@ MU_TEST(storage_file_rename) { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); - mu_check(write_file_13DA(storage, EXT_PATH("file.old"))); - mu_check(check_file_13DA(storage, EXT_PATH("file.old"))); + mu_check(write_file_13DA(storage, UNIT_TESTS_PATH("file.old"))); + mu_check(check_file_13DA(storage, UNIT_TESTS_PATH("file.old"))); mu_assert_int_eq( - FSE_OK, storage_common_rename(storage, EXT_PATH("file.old"), EXT_PATH("file.new"))); - mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, EXT_PATH("file.old"), NULL)); - mu_assert_int_eq(FSE_OK, storage_common_stat(storage, EXT_PATH("file.new"), NULL)); - mu_check(check_file_13DA(storage, EXT_PATH("file.new"))); - mu_assert_int_eq(FSE_OK, storage_common_remove(storage, EXT_PATH("file.new"))); + FSE_OK, + storage_common_rename(storage, UNIT_TESTS_PATH("file.old"), UNIT_TESTS_PATH("file.new"))); + mu_assert_int_eq( + FSE_NOT_EXIST, storage_common_stat(storage, UNIT_TESTS_PATH("file.old"), NULL)); + mu_assert_int_eq(FSE_OK, storage_common_stat(storage, UNIT_TESTS_PATH("file.new"), NULL)); + mu_check(check_file_13DA(storage, UNIT_TESTS_PATH("file.new"))); + mu_assert_int_eq(FSE_OK, storage_common_remove(storage, UNIT_TESTS_PATH("file.new"))); storage_file_free(file); furi_record_close(RECORD_STORAGE); } +static const char* dir_rename_tests[][2] = { + {UNIT_TESTS_PATH("dir.old"), UNIT_TESTS_PATH("dir.new")}, + {UNIT_TESTS_PATH("test_dir"), UNIT_TESTS_PATH("test_dir-new")}, + {UNIT_TESTS_PATH("test"), UNIT_TESTS_PATH("test-test")}, +}; + MU_TEST(storage_dir_rename) { Storage* storage = furi_record_open(RECORD_STORAGE); - storage_dir_create(storage, EXT_PATH("dir.old")); + for(size_t i = 0; i < COUNT_OF(dir_rename_tests); i++) { + const char* old_path = dir_rename_tests[i][0]; + const char* new_path = dir_rename_tests[i][1]; + + storage_dir_create(storage, old_path); + mu_check(storage_dir_rename_check(storage, old_path)); + + mu_assert_int_eq(FSE_OK, storage_common_rename(storage, old_path, new_path)); + mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, old_path, NULL)); + mu_check(storage_dir_rename_check(storage, new_path)); + + storage_dir_remove(storage, new_path); + mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, new_path, NULL)); + } + + furi_record_close(RECORD_STORAGE); +} - mu_check(storage_dir_rename_check(storage, EXT_PATH("dir.old"))); +MU_TEST(storage_equiv_and_subdir) { + Storage* storage = furi_record_open(RECORD_STORAGE); mu_assert_int_eq( - FSE_OK, storage_common_rename(storage, EXT_PATH("dir.old"), EXT_PATH("dir.new"))); - mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, EXT_PATH("dir.old"), NULL)); - mu_check(storage_dir_rename_check(storage, EXT_PATH("dir.new"))); + true, + storage_common_equivalent_path(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah"))); + mu_assert_int_eq( + true, + storage_common_equivalent_path( + storage, UNIT_TESTS_PATH("blah/"), UNIT_TESTS_PATH("blah/"))); + mu_assert_int_eq( + false, + storage_common_equivalent_path( + storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah-blah"))); + mu_assert_int_eq( + false, + storage_common_equivalent_path( + storage, UNIT_TESTS_PATH("blah/"), UNIT_TESTS_PATH("blah-blah/"))); - storage_dir_remove(storage, EXT_PATH("dir.new")); - mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, EXT_PATH("dir.new"), NULL)); + mu_assert_int_eq( + true, storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah"))); + mu_assert_int_eq( + true, + storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah/blah"))); + mu_assert_int_eq( + false, + storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah/blah"), UNIT_TESTS_PATH("blah"))); + mu_assert_int_eq( + false, + storage_common_is_subdir(storage, UNIT_TESTS_PATH("blah"), UNIT_TESTS_PATH("blah-blah"))); furi_record_close(RECORD_STORAGE); } @@ -403,10 +449,13 @@ MU_TEST(storage_dir_rename) { MU_TEST_SUITE(storage_rename) { MU_RUN_TEST(storage_file_rename); MU_RUN_TEST(storage_dir_rename); + MU_RUN_TEST(storage_equiv_and_subdir); Storage* storage = furi_record_open(RECORD_STORAGE); - storage_dir_remove(storage, EXT_PATH("dir.old")); - storage_dir_remove(storage, EXT_PATH("dir.new")); + for(size_t i = 0; i < COUNT_OF(dir_rename_tests); i++) { + storage_dir_remove(storage, dir_rename_tests[i][0]); + storage_dir_remove(storage, dir_rename_tests[i][1]); + } furi_record_close(RECORD_STORAGE); } @@ -653,7 +702,7 @@ MU_TEST(test_md5_calc) { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); - const char* path = UNIT_TESTS_PATH("storage/md5.txt"); + const char* path = UNIT_TESTS_RESOURCES_PATH("storage/md5.txt"); const char* md5_cstr = "2a456fa43e75088fdde41c93159d62a2"; const uint8_t md5[MD5_HASH_SIZE] = { 0x2a, diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 072db1305bb..ea0ff24ade8 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -401,21 +401,26 @@ bool storage_common_exists(Storage* storage, const char* path); * - /int/Test and /int/test -> false (Case-sensitive storage), * - /ext/Test and /ext/test -> true (Case-insensitive storage). * - * If the truncate parameter is set to true, the second path will be - * truncated to be no longer than the first one. It is useful to determine - * whether path2 is a subdirectory of path1. - * * @param storage pointer to a storage API instance. * @param path1 pointer to a zero-terminated string containing the first path. * @param path2 pointer to a zero-terminated string containing the second path. - * @param truncate whether to truncate path2 to be no longer than path1. * @return true if paths are equivalent, false otherwise. */ -bool storage_common_equivalent_path( - Storage* storage, - const char* path1, - const char* path2, - bool truncate); +bool storage_common_equivalent_path(Storage* storage, const char* path1, const char* path2); + +/** + * @brief Check whether a path is a subpath of another path. + * + * This function respects storage-defined equivalence rules + * (see `storage_common_equivalent_path`). + * + * @param storage pointer to a storage API instance. + * @param parent pointer to a zero-terminated string containing the parent path. + * @param child pointer to a zero-terminated string containing the child path. + * @return true if `child` is a subpath of `parent`, or if `child` is equivalent + * to `parent`; false otherwise. + */ +bool storage_common_is_subdir(Storage* storage, const char* parent, const char* child); /******************* Error Functions *******************/ diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index 7803e8f6a22..ecc5330af50 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -493,13 +493,13 @@ FS_Error storage_common_rename(Storage* storage, const char* old_path, const cha } // Cannot rename a directory to itself or to a nested directory - if(storage_common_equivalent_path(storage, old_path, new_path, true)) { + if(storage_common_is_subdir(storage, old_path, new_path)) { error = FSE_INVALID_NAME; break; } // Renaming a regular file to itself does nothing and always succeeds - } else if(storage_common_equivalent_path(storage, old_path, new_path, false)) { + } else if(storage_common_equivalent_path(storage, old_path, new_path)) { error = FSE_OK; break; } @@ -816,11 +816,11 @@ bool storage_common_exists(Storage* storage, const char* path) { return storage_common_stat(storage, path, &file_info) == FSE_OK; } -bool storage_common_equivalent_path( +static bool storage_internal_equivalent_path( Storage* storage, const char* path1, const char* path2, - bool truncate) { + bool check_subdir) { furi_check(storage); S_API_PROLOGUE; @@ -829,7 +829,7 @@ bool storage_common_equivalent_path( .cequivpath = { .path1 = path1, .path2 = path2, - .truncate = truncate, + .check_subdir = check_subdir, .thread_id = furi_thread_get_current_id(), }}; @@ -839,6 +839,14 @@ bool storage_common_equivalent_path( return S_RETURN_BOOL; } +bool storage_common_equivalent_path(Storage* storage, const char* path1, const char* path2) { + return storage_internal_equivalent_path(storage, path1, path2, false); +} + +bool storage_common_is_subdir(Storage* storage, const char* parent, const char* child) { + return storage_internal_equivalent_path(storage, parent, child, true); +} + /****************** ERROR ******************/ const char* storage_error_get_desc(FS_Error error_id) { diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index cd45906d4ac..ba34aa7c087 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -72,7 +72,7 @@ typedef struct { typedef struct { const char* path1; const char* path2; - bool truncate; + bool check_subdir; FuriThreadId thread_id; } SADataCEquivPath; diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 8d86dd38516..7a328051d23 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -694,7 +694,23 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { storage_path_trim_trailing_slashes(path2); storage_process_alias(app, path1, message->data->cequivpath.thread_id, false); storage_process_alias(app, path2, message->data->cequivpath.thread_id, false); - if(message->data->cequivpath.truncate) { + if(message->data->cequivpath.check_subdir) { + // by appending slashes at the end and then truncating the second path, we can + // effectively check for shared path components: + // example 1: + // path1: "/ext/blah" -> "/ext/blah/" -> "/ext/blah/" + // path2: "/ext/blah-blah" -> "/ect/blah-blah/" -> "/ext/blah-" + // results unequal, conclusion: path2 is not a subpath of path1 + // example 2: + // path1: "/ext/blah" -> "/ext/blah/" -> "/ext/blah/" + // path2: "/ext/blah/blah" -> "/ect/blah/blah/" -> "/ext/blah/" + // results equal, conclusion: path2 is a subpath of path1 + // example 3: + // path1: "/ext/blah/blah" -> "/ect/blah/blah/" -> "/ext/blah/blah/" + // path2: "/ext/blah" -> "/ext/blah/" -> "/ext/blah/" + // results unequal, conclusion: path2 is not a subpath of path1 + furi_string_push_back(path1, '/'); + furi_string_push_back(path2, '/'); furi_string_left(path2, furi_string_size(path1)); } message->return_data->bool_value = diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 553cf14720a..1d9ac28698b 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,73.0,, +Version,+,74.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -2491,9 +2491,10 @@ Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_ Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" -Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*, _Bool" +Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" +Function,+,storage_common_is_subdir,_Bool,"Storage*, const char*, const char*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_migrate,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 5d5c2a707a9..cb3037e48f9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,73.0,, +Version,+,74.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -3168,9 +3168,10 @@ Function,+,st25tb_save,_Bool,"const St25tbData*, FlipperFormat*" Function,+,st25tb_set_uid,_Bool,"St25tbData*, const uint8_t*, size_t" Function,+,st25tb_verify,_Bool,"St25tbData*, const FuriString*" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" -Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*, _Bool" +Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*" Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" +Function,+,storage_common_is_subdir,_Bool,"Storage*, const char*, const char*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_migrate,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" From 913a86bbec7c9590692e00c1a7b0a74c36a4360c Mon Sep 17 00:00:00 2001 From: EntranceJew Date: Sun, 15 Sep 2024 10:07:04 -0500 Subject: [PATCH 04/36] kerel typo (#3901) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- furi/core/kernel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/furi/core/kernel.h b/furi/core/kernel.h index 2973a90e577..8362746e21e 100644 --- a/furi/core/kernel.h +++ b/furi/core/kernel.h @@ -79,7 +79,7 @@ void furi_delay_tick(uint32_t ticks); * * @warning This should never be called in interrupt request context. * - * @param[in] tick The tick until which kerel should delay task execution + * @param[in] tick The tick until which kernel should delay task execution * * @return The furi status. */ From 09a7cc2b46e7d4a2eaafface07a3b18968543ca9 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 2 Oct 2024 21:10:19 +0900 Subject: [PATCH 05/36] [FL-3805] Fix EM4100 T5577 writing block order (#3904) --- lib/lfrfid/protocols/protocol_em4100.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index eca14c04663..8851a5369cc 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -335,8 +335,8 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { request->t5577.block[0] = (LFRFID_T5577_MODULATION_MANCHESTER | protocol_em4100_get_t5577_bitrate(protocol) | (2 << LFRFID_T5577_MAXBLOCK_SHIFT)); - request->t5577.block[1] = protocol->encoded_data; - request->t5577.block[2] = protocol->encoded_data >> 32; + request->t5577.block[1] = protocol->encoded_data >> 32; + request->t5577.block[2] = protocol->encoded_data; request->t5577.blocks_to_write = 3; result = true; } From 3c93761d1d45467af08de10def6040098caf849e Mon Sep 17 00:00:00 2001 From: jay candel Date: Thu, 3 Oct 2024 00:28:24 +0800 Subject: [PATCH 06/36] [IR] universal remote additions (#3922) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * multiple new additions Hisense K321UW, Soniq E55V13A, Soniq E32W13B and 2 others * updated with proper names Viano STV65UHD4K Hisense K321UW Hisense EN2B27 Soniq E55V13A Soniq E32W13B * format tv.ir * Update tv.ir * new universal ac additions Maytag M6X06F2A Panasonic CS-E9HKR * new universal audio additions Sony MHC_GSX75 Elac EA101EQ-G Philips FW750C Pioneer VSX-D1-S * remove final # audio.ir * Scripts: update deprecated methods use in python scripts * Scripts: add comment reading support to fff, preserve comments in infrared cleanup script * Scripts: improved infrared files cleanup script * Scripts: add missing new line at the end of file in infrared file cleanup script * Infrared: cleanup universal remotes Co-authored-by: あく --- .../infrared/resources/infrared/assets/ac.ir | 76 ++++++-- .../resources/infrared/assets/audio.ir | 169 ++++++++++------ .../resources/infrared/assets/projector.ir | 56 +++--- .../infrared/resources/infrared/assets/tv.ir | 181 +++++++++++------- scripts/flipper/utils/fff.py | 13 +- scripts/infrared.py | 30 ++- scripts/sconsdist.py | 2 +- scripts/update.py | 8 +- 8 files changed, 345 insertions(+), 190 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index df958ffba27..0a182c5d94e 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -796,31 +796,31 @@ 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 @@ -834,25 +834,25 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 4349 4437 549 1615 551 1614 551 1614 551 1617 549 531 550 530 551 1615 550 531 550 532 549 530 551 531 550 530 551 1615 550 1614 551 531 550 1615 551 529 552 531 550 530 551 533 548 530 551 530 551 1616 549 1615 550 1616 550 1615 550 1614 551 1616 550 1615 551 1615 550 531 550 531 550 530 551 529 552 530 551 530 551 529 552 530 551 531 550 1615 550 532 549 1615 550 1616 550 531 550 531 550 530 551 530 551 529 552 532 549 530 551 530 551 531 550 529 552 531 550 1615 551 530 551 530 551 530 551 531 550 530 551 531 550 530 551 531 550 531 550 531 550 1616 550 1618 547 532 549 529 552 530 551 1615 551 1615 550 5379 4350 4436 550 1616 549 1615 551 1614 552 1615 550 529 552 530 551 1614 552 530 551 529 552 531 550 531 550 531 550 1614 552 1614 551 530 551 1615 550 530 551 530 551 530 551 530 551 531 550 532 549 1616 549 1615 551 1614 552 1615 550 1614 551 1616 550 1614 552 1615 550 529 552 530 551 530 551 530 551 531 550 531 550 530 551 530 551 531 550 1615 550 530 551 1615 550 1615 551 530 551 530 551 530 551 530 551 530 551 530 551 529 552 530 551 531 550 532 549 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 531 550 531 550 531 550 530 551 531 550 1615 551 1615 551 532 549 531 550 531 550 1616 549 1614 552 -# +# name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.330000 data: 4350 4438 549 1615 551 1614 552 1616 549 1616 550 530 551 531 550 1615 551 530 551 529 552 531 550 530 551 531 550 1614 551 1616 550 531 550 1616 549 530 551 531 550 530 551 529 552 530 551 531 550 1616 549 1616 550 1616 549 1616 550 1615 551 1614 551 1614 552 1615 551 530 551 531 550 530 551 531 550 531 550 529 552 532 549 531 550 530 551 1613 552 530 551 531 550 529 552 532 549 530 551 530 551 531 550 531 550 530 551 530 551 530 551 531 550 530 551 531 550 531 550 1615 551 529 552 530 551 530 551 530 551 530 551 530 551 530 551 530 551 532 549 531 550 531 550 532 549 531 550 531 550 530 551 530 551 5132 4351 4435 552 1616 550 1615 550 1615 551 1613 553 531 550 530 551 1615 550 530 551 531 550 531 550 530 551 532 549 1616 550 1616 549 530 551 1615 551 530 551 531 550 530 551 530 551 530 551 531 550 1615 551 1615 551 1614 551 1615 550 1615 551 1615 550 1615 550 1616 550 530 551 530 551 531 550 532 549 530 551 530 551 531 550 531 550 531 550 1615 550 530 551 530 551 530 551 529 552 531 550 530 551 531 550 531 550 530 551 530 551 531 550 530 551 530 551 530 551 531 550 1616 550 530 551 529 552 530 551 531 550 532 549 530 551 530 551 529 552 531 550 529 552 530 551 530 551 531 550 531 550 529 552 531 550 -# +# name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.330000 data: 4350 4436 550 1617 549 1615 550 1615 550 1617 548 530 551 531 550 1615 551 531 550 531 550 530 551 530 551 531 550 1614 552 1615 550 530 551 1614 551 531 550 531 550 531 550 529 552 532 549 530 551 1617 549 1616 549 1615 551 1619 547 1615 550 1615 550 1616 549 1616 550 530 551 531 550 530 551 530 551 531 550 530 551 529 552 529 552 530 551 1617 548 533 548 1615 551 1613 552 530 551 531 550 531 550 530 551 530 551 532 549 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 531 550 531 550 532 549 531 550 530 551 531 550 533 548 531 550 530 551 1617 548 1616 549 530 551 531 550 532 549 532 549 532 549 5200 4349 4436 550 1615 551 1615 551 1615 550 1616 550 531 550 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 1616 549 1615 551 530 551 1615 551 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 1616 550 1616 550 1615 550 1617 548 1616 549 1616 550 1615 550 531 550 530 551 531 550 531 550 532 549 530 551 531 550 531 550 532 549 1616 550 531 550 1616 550 1615 550 531 550 530 551 531 550 531 550 531 550 531 550 531 550 532 549 532 549 531 550 532 549 531 550 1616 550 531 550 530 551 532 549 532 549 530 551 532 549 531 550 532 549 531 550 1616 549 1617 549 531 550 530 551 531 550 532 549 532 549 -# +# name: Heat_hi type: raw frequency: 38000 duty_cycle: 0.330000 data: 4350 4437 547 1618 548 1620 546 1620 546 1619 547 534 547 535 546 1619 547 534 547 536 545 536 545 535 546 535 546 1619 547 1620 545 534 523 1644 546 535 522 559 546 535 546 534 547 535 546 535 545 1620 546 1620 546 1620 546 1619 547 1619 546 1619 547 1620 545 1620 546 535 546 534 547 537 520 558 523 558 547 534 547 536 521 559 522 559 522 1644 546 535 546 535 522 560 545 536 521 559 522 559 522 558 523 559 522 560 521 559 522 559 522 560 521 559 522 561 520 1644 521 1645 520 559 522 559 522 559 522 559 522 559 522 560 521 560 521 560 521 561 520 559 522 560 521 559 522 559 522 559 522 1644 522 559 522 5341 4349 4439 520 1645 521 1645 521 1646 519 1645 521 560 521 561 520 1645 521 560 521 560 521 559 522 560 521 561 520 1646 520 1645 521 561 520 1645 521 561 520 560 521 560 521 560 521 560 521 561 520 1644 522 1644 522 1645 520 1645 521 1645 521 1645 520 1646 520 1644 522 561 520 560 521 560 521 561 520 560 521 561 520 561 520 561 520 560 521 1646 520 562 519 561 520 561 520 562 519 560 521 560 521 561 520 561 520 560 521 560 521 561 520 560 521 560 521 562 519 1646 520 1645 521 561 520 561 520 561 520 560 521 560 521 561 520 560 521 559 522 560 521 561 520 561 520 560 521 562 519 559 522 1645 521 561 520 -# +# name: Heat_lo type: raw frequency: 38000 @@ -866,33 +866,79 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 4387 4398 547 1609 547 530 547 1610 547 1611 545 530 547 530 547 1608 547 530 548 530 547 1611 545 532 546 532 547 1609 547 1610 547 531 547 1608 548 530 547 530 548 530 547 1610 546 1609 547 1610 547 1609 547 1609 547 1611 545 1609 548 1610 546 530 547 530 548 529 549 531 547 531 546 531 547 1608 548 1610 547 1608 548 533 545 1608 548 532 546 532 546 1611 545 532 547 532 545 530 548 1608 547 530 549 1608 547 1609 548 5203 4386 4398 547 1609 546 530 547 1609 546 1607 548 531 547 531 547 1609 547 530 548 531 547 1609 547 531 547 531 547 1608 547 1613 544 531 546 1609 547 531 547 531 547 532 546 1609 547 1609 546 1609 547 1609 547 1608 547 1608 548 1608 548 1609 547 530 547 530 547 530 547 532 546 530 547 530 548 1610 546 1608 547 1609 547 530 547 1609 547 530 547 530 548 1609 546 530 548 530 547 532 546 1610 546 531 546 1608 548 1608 548 -# +# name: Cool_hi type: raw frequency: 38000 duty_cycle: 0.330000 data: 4388 4398 547 1608 548 531 546 1610 546 1609 547 530 547 529 548 1608 548 532 547 530 548 1612 544 529 549 530 548 1608 547 1609 547 531 546 1608 548 1607 549 529 549 1608 549 1609 548 1608 548 1608 548 1611 545 1608 548 530 548 1609 547 531 547 530 548 530 548 531 547 529 549 530 548 530 547 531 547 530 548 530 547 529 549 530 548 532 547 530 548 1609 547 1610 547 1608 548 1609 547 1608 548 1608 548 1608 548 1608 548 5203 4388 4396 549 1609 547 529 549 1610 546 1608 548 529 549 530 547 1609 547 530 548 529 549 1608 548 531 547 532 546 1609 547 1609 547 530 548 1609 548 1609 548 529 548 1608 548 1609 548 1609 547 1609 547 1608 548 1609 547 532 546 1608 548 531 548 531 548 530 548 530 548 531 547 530 548 531 548 531 547 530 548 530 548 530 548 531 547 529 549 529 549 1609 548 1608 548 1609 547 1608 548 1608 548 1608 548 1607 549 1607 549 -# +# name: Cool_lo type: raw frequency: 38000 duty_cycle: 0.330000 data: 4384 4400 572 1585 571 505 572 1583 573 1584 572 508 570 503 575 1584 572 505 572 506 572 1583 573 504 573 506 571 1586 570 1585 572 532 546 1586 570 1585 571 506 571 1585 571 1583 573 1586 570 1583 573 1584 572 1589 569 505 572 1585 571 506 571 506 573 506 572 505 573 532 545 504 574 509 570 1611 545 506 572 1582 574 506 572 507 571 507 571 507 570 1584 572 507 571 1587 569 506 572 1584 572 1585 571 1583 573 1612 544 5179 4386 4400 570 1584 572 507 571 1583 572 1585 571 506 572 506 572 1584 572 505 572 504 574 1584 572 507 571 504 574 1583 573 1585 572 507 571 1584 572 1610 545 508 571 1587 569 1583 573 1583 573 1585 571 1585 572 1585 572 505 572 1584 572 505 573 507 572 506 571 504 574 505 573 505 574 508 571 1585 571 507 571 1585 571 506 571 506 572 504 574 505 572 1586 570 507 571 1586 570 505 573 1584 572 1585 571 1587 569 1584 573 -# +# name: Heat_hi type: raw frequency: 38000 duty_cycle: 0.330000 data: 4386 4398 575 1582 574 503 575 1583 573 1582 574 505 573 504 574 1582 574 506 572 508 570 1583 573 504 574 505 573 1583 573 1584 573 505 573 1582 575 1583 574 504 574 1582 574 1583 573 1583 573 1583 573 1585 571 1586 572 504 573 1584 572 504 573 505 573 505 573 505 573 504 573 506 571 1583 574 505 573 1583 573 1583 573 1584 572 1583 573 505 572 505 573 504 574 1583 574 505 573 505 573 504 574 505 572 1584 572 1584 573 5178 4387 4400 571 1583 573 504 574 1584 572 1584 572 507 572 504 574 1582 574 505 572 505 573 1583 573 504 574 504 574 1582 574 1584 573 503 574 1583 573 1582 574 505 573 1583 573 1582 575 1583 573 1610 546 1584 572 1583 573 505 573 1610 546 506 572 505 573 504 574 504 574 505 573 505 573 1584 573 505 573 1582 574 1584 572 1583 573 1583 573 504 574 503 575 504 574 1585 571 507 571 504 573 506 572 505 572 1584 572 1585 571 -# +# name: Heat_lo type: raw frequency: 38000 duty_cycle: 0.330000 data: 4388 4399 547 1608 548 530 548 1610 547 1610 547 529 549 529 548 1608 548 530 548 530 548 1607 549 533 545 531 548 1608 548 1610 546 531 547 1609 547 1608 548 529 549 1609 547 1609 547 1609 547 1609 547 1609 547 1608 548 529 548 1638 519 530 548 530 548 530 548 529 550 528 549 530 548 530 548 1608 548 530 548 1609 548 1610 547 1609 547 531 546 529 549 1608 548 530 548 1609 548 530 548 529 548 530 548 1609 548 1609 548 5205 4387 4398 547 1609 548 531 546 1609 547 1609 547 530 548 531 546 1609 547 531 548 530 573 1583 573 507 571 506 572 1583 573 1582 574 504 574 1581 575 1582 574 506 572 1583 574 1583 573 1583 573 1585 571 1584 572 1585 570 507 571 1582 574 505 574 532 545 505 573 505 572 506 571 505 573 505 573 1584 572 506 572 1583 573 1584 572 1583 573 505 572 504 573 1583 573 505 573 1586 571 506 572 505 573 507 572 1583 573 1584 572 -# +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4388 4399 572 1583 573 532 546 1585 571 1583 574 503 575 505 573 1584 572 504 574 505 573 1584 573 506 572 504 573 1584 573 1584 572 505 574 1611 545 506 573 1583 573 1585 571 1586 545 1609 547 531 547 1611 545 1608 548 1607 549 530 548 529 548 531 548 532 546 1610 546 533 545 530 547 1609 547 1610 547 1609 547 533 545 529 548 530 548 530 547 531 546 530 547 530 548 533 544 1608 548 1608 548 1610 546 1606 550 1609 547 5203 4388 4397 548 1609 547 531 547 1608 548 1608 548 530 548 530 548 1608 548 531 547 531 547 1610 546 531 547 530 548 1609 547 1611 546 532 547 1609 547 531 547 1608 548 1610 546 1609 547 1608 548 530 547 1609 547 1608 548 1609 547 531 546 530 548 530 547 530 547 1608 548 532 547 534 545 1608 548 1608 548 1609 547 530 548 531 547 531 547 532 546 531 546 531 547 532 546 530 548 1608 547 1608 548 1610 546 1608 548 1608 548 +# +# Model: Panasonic CS-E9HKR +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3466 1748 445 367 502 1292 444 422 444 420 445 422 443 420 445 421 445 420 445 363 503 420 445 422 444 420 445 424 445 1292 445 421 445 451 414 421 444 421 444 421 445 422 443 423 446 1292 443 1292 445 1292 445 421 497 372 444 1293 445 420 445 421 444 421 444 421 445 421 444 421 444 420 445 422 444 420 445 421 444 421 444 421 445 422 443 356 509 420 445 421 445 429 436 421 444 421 444 414 452 420 445 421 444 421 444 423 443 421 444 421 444 421 444 423 443 420 445 424 445 1291 444 1294 444 421 444 421 444 421 444 422 444 423 445 9989 3463 1752 444 424 445 1293 443 422 444 392 473 421 444 416 449 423 443 421 444 421 444 421 444 423 443 422 443 425 444 1292 444 423 443 422 443 419 446 421 444 423 443 421 444 426 443 1292 444 1294 443 1294 443 421 444 425 444 1294 444 422 443 421 444 422 443 424 442 421 444 421 444 421 444 423 444 421 444 422 443 421 444 423 443 425 444 1293 444 421 444 421 444 1293 444 423 446 1292 445 321 546 421 444 421 444 385 480 423 443 425 444 1291 445 1293 443 422 445 422 443 421 444 421 496 370 444 422 443 421 496 370 443 421 444 1291 497 1239 444 1284 504 1237 447 1293 444 425 444 1293 496 371 494 1241 495 370 496 369 497 297 517 421 496 370 495 369 496 370 496 370 495 370 495 369 496 372 494 370 495 369 496 370 495 370 496 370 495 373 495 1240 495 1241 495 1240 497 369 496 371 494 371 443 422 496 369 496 368 497 370 496 369 496 372 497 1239 496 1241 496 1241 496 370 495 310 555 371 495 371 494 368 497 370 495 370 496 361 504 370 495 369 496 372 494 372 494 369 496 371 494 371 495 373 495 1240 497 370 495 371 495 370 495 369 496 370 495 370 495 1242 495 373 492 370 495 366 500 369 496 371 495 370 495 371 495 369 496 371 494 370 495 371 495 371 494 371 494 370 495 372 494 380 488 1241 496 374 495 1241 495 1240 495 1240 495 1241 495 1242 494 1244 494 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3467 1751 442 425 444 1294 443 424 442 422 443 423 442 422 443 422 444 421 444 421 444 424 441 423 443 421 444 425 444 1294 443 424 442 423 442 422 443 425 440 423 443 423 442 427 442 1292 443 1322 415 1293 444 422 443 425 443 1295 443 424 441 421 444 423 442 424 442 425 440 423 442 423 442 424 442 292 573 421 444 423 442 422 444 422 443 422 443 423 442 425 441 423 442 425 440 421 444 424 442 422 443 422 443 422 443 424 442 422 443 422 443 422 443 424 442 421 444 426 443 1292 443 1294 444 423 442 422 443 423 442 424 442 426 442 9989 3465 1753 442 425 444 1292 445 424 442 424 441 421 444 422 443 422 444 423 442 423 442 352 513 423 443 423 442 425 443 1296 441 423 443 422 443 422 443 424 441 423 443 422 443 427 442 1293 442 1322 415 1293 444 421 444 425 443 1294 444 422 443 422 443 421 444 425 441 423 442 422 443 421 444 423 443 421 444 422 443 422 443 424 442 425 444 1261 475 422 443 422 443 1292 444 1293 442 1295 441 424 442 422 443 422 443 427 442 1322 415 1292 444 1292 444 1294 442 423 443 422 443 421 444 422 443 423 443 421 444 421 444 423 442 421 444 1291 444 1293 443 1294 442 1195 541 1293 444 425 443 1293 443 422 444 1322 414 421 444 422 443 423 443 422 443 423 442 422 443 425 441 422 443 422 443 423 442 423 443 422 443 422 443 421 444 423 444 421 444 424 444 1292 444 1293 444 1294 442 422 443 421 444 423 443 422 443 421 444 422 443 424 442 421 444 426 443 1291 444 1293 443 1293 444 422 443 421 444 422 444 421 444 422 443 421 444 423 443 423 442 421 444 422 443 423 444 421 444 421 444 421 444 422 444 425 444 1294 443 422 443 423 444 424 441 422 443 422 443 422 443 1294 443 422 443 421 444 424 442 421 444 413 452 421 444 423 443 421 444 422 443 422 443 422 444 421 444 422 443 422 443 423 443 426 443 1294 442 421 444 423 442 1293 443 1293 443 421 444 422 444 362 505 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3467 1751 444 424 444 1291 446 422 444 420 445 420 445 419 446 421 445 378 487 421 444 420 445 420 446 421 444 423 446 1321 415 451 415 420 445 420 445 419 446 421 445 419 446 422 447 1290 445 1291 445 1291 446 420 445 424 445 1293 445 420 445 420 445 420 445 421 445 420 445 420 445 419 446 421 445 420 445 421 444 420 445 421 445 420 445 420 445 420 445 421 445 419 446 420 445 420 445 420 446 420 445 421 444 419 446 422 444 420 445 420 445 421 444 421 445 421 444 424 445 1290 445 1292 446 420 445 421 444 420 445 423 443 424 444 9988 3465 1751 446 423 445 1291 446 421 445 420 445 420 445 420 445 422 444 420 445 420 445 421 444 421 446 421 444 424 445 1291 446 421 445 421 444 420 445 420 445 420 446 421 444 423 446 1291 444 1293 444 1292 445 420 445 423 446 1294 444 419 446 420 445 421 444 422 444 420 445 421 444 420 445 421 445 421 444 420 445 420 445 422 444 421 444 420 445 420 445 421 444 1320 415 1291 445 1292 444 422 444 420 445 419 446 420 445 421 445 421 444 424 445 1291 446 421 445 420 445 420 445 421 444 421 445 421 444 421 444 420 445 420 445 1291 445 1292 443 1290 445 1291 446 1292 445 424 444 1293 444 421 444 1292 445 422 443 421 444 381 485 420 445 420 445 420 445 422 444 421 444 420 445 421 444 421 445 421 444 421 444 420 445 421 445 421 444 424 445 1291 444 1292 445 1291 446 420 445 419 446 422 444 421 444 420 445 420 445 421 445 420 445 423 446 1291 444 1291 446 1292 445 420 445 420 445 422 444 420 445 420 445 420 445 422 444 421 444 420 445 419 446 420 446 420 445 419 446 419 446 421 445 423 445 1293 444 419 446 421 445 420 445 419 446 420 445 420 445 1292 445 420 445 420 445 421 445 420 445 419 446 419 446 420 446 419 446 420 445 420 445 420 446 420 445 420 445 420 445 421 445 419 446 420 445 423 446 1292 445 1290 445 1290 445 1292 444 1291 445 1293 445 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3463 1751 443 424 445 1293 443 422 444 420 445 421 444 421 444 422 444 421 444 421 444 421 444 353 513 422 443 425 444 1293 444 422 444 421 444 420 445 421 444 422 444 421 444 425 444 1291 444 1294 443 1293 444 421 444 424 445 1294 444 420 445 421 444 416 449 422 444 421 444 421 444 421 444 422 444 420 445 421 444 422 443 422 444 421 444 421 444 420 445 373 493 420 445 421 444 420 445 422 444 420 445 422 443 421 444 422 444 421 444 421 444 422 443 421 445 421 444 424 445 1291 444 1294 444 421 444 420 445 420 445 422 444 424 444 9991 3463 1751 445 425 444 1293 444 422 444 421 444 420 445 421 444 423 443 421 444 421 444 421 444 422 444 421 444 424 445 1293 444 422 444 420 445 422 443 421 444 424 442 421 444 425 444 1292 443 1292 445 1292 445 421 444 424 445 1293 445 421 444 421 444 421 444 422 444 421 444 421 444 422 443 422 444 423 442 421 444 423 442 423 443 425 443 1293 444 423 442 421 444 1292 444 422 443 425 443 1295 443 421 444 368 549 373 443 1293 496 1240 495 1240 495 1241 496 371 495 370 495 370 495 370 495 371 495 370 495 369 496 371 494 371 494 1240 442 1293 495 1240 496 1241 496 1240 497 373 496 1241 496 371 494 1184 553 370 495 370 495 370 496 370 443 423 494 370 443 424 495 370 442 423 442 422 443 424 442 423 442 423 442 425 440 424 442 424 441 427 442 1294 441 1294 443 1294 443 424 441 423 442 425 442 421 444 423 442 424 441 425 441 424 441 426 443 1293 442 1295 442 1295 441 424 441 394 471 425 441 423 442 423 442 424 441 424 442 424 441 423 442 423 442 424 442 423 442 424 441 423 442 424 442 428 440 1295 442 424 441 425 441 424 441 424 441 423 442 424 441 1294 443 424 441 424 441 424 442 423 442 423 442 423 442 425 441 424 441 424 441 424 441 424 442 424 441 449 416 423 442 424 442 427 442 1295 442 424 441 424 441 1295 442 427 442 1295 441 425 441 426 441 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3461 1753 439 429 440 1298 439 427 439 426 439 426 439 426 439 427 439 426 439 426 439 426 439 427 439 426 439 430 438 1298 438 428 438 426 439 427 438 427 438 427 439 427 438 430 414 1321 439 1299 413 1323 414 451 414 455 414 1324 414 452 413 451 414 453 413 436 430 452 413 452 413 451 414 453 413 452 413 451 414 452 413 454 412 452 413 452 413 452 413 453 413 452 413 452 413 452 413 454 413 453 412 452 413 452 413 453 413 452 413 452 413 452 413 453 413 452 413 456 412 1323 412 1325 413 452 413 453 412 453 412 454 412 455 390 10044 3438 1779 412 457 412 1324 413 454 412 453 412 453 412 453 412 454 412 453 412 453 412 454 411 455 411 453 412 457 389 1347 390 477 389 476 389 476 389 476 389 402 465 475 390 479 390 1346 389 1348 389 1347 390 476 389 479 390 1348 390 476 389 476 389 476 389 477 389 475 390 476 389 476 389 477 389 476 389 476 389 476 389 478 388 480 389 1347 390 475 390 476 389 1347 390 476 389 479 389 1349 389 476 389 476 389 476 389 477 389 476 389 479 390 1348 389 477 389 476 389 476 389 476 389 476 390 476 389 476 389 476 389 476 389 1346 389 1347 389 1346 389 1347 390 1346 390 479 390 1347 390 476 389 1374 363 474 415 454 388 480 386 475 414 454 387 474 391 476 414 455 387 473 392 476 414 453 389 476 389 475 390 475 390 476 390 476 390 479 389 1346 389 1347 390 1348 389 476 389 476 389 477 389 476 389 476 389 475 390 477 389 440 425 479 389 1345 390 1347 390 1347 390 476 389 475 390 477 389 476 389 476 389 476 389 477 389 476 389 476 389 475 390 477 389 476 389 476 389 476 389 477 389 479 390 1347 390 476 389 477 389 476 389 476 389 476 389 476 389 1347 390 476 389 476 389 477 389 503 362 476 389 476 389 477 389 475 390 476 389 476 389 477 389 476 389 476 389 476 389 477 389 479 389 1347 390 479 390 1347 390 1348 389 476 389 476 389 477 389 477 390 +# name: Off type: raw frequency: 38000 duty_cycle: 0.330000 -data: 4388 4399 572 1583 573 532 546 1585 571 1583 574 503 575 505 573 1584 572 504 574 505 573 1584 573 506 572 504 573 1584 573 1584 572 505 574 1611 545 506 573 1583 573 1585 571 1586 545 1609 547 531 547 1611 545 1608 548 1607 549 530 548 529 548 531 548 532 546 1610 546 533 545 530 547 1609 547 1610 547 1609 547 533 545 529 548 530 548 530 547 531 546 530 547 530 548 533 544 1608 548 1608 548 1610 546 1606 550 1609 547 5203 4388 4397 548 1609 547 531 547 1608 548 1608 548 530 548 530 548 1608 548 531 547 531 547 1610 546 531 547 530 548 1609 547 1611 546 532 547 1609 547 531 547 1608 548 1610 546 1609 547 1608 548 530 547 1609 547 1608 548 1609 547 531 546 530 548 530 547 530 547 1608 548 532 547 534 545 1608 548 1608 548 1609 547 530 548 531 547 531 547 532 546 531 546 531 547 532 546 530 548 1608 547 1608 548 1610 546 1608 548 1608 548 \ No newline at end of file +data: 3489 1725 493 375 493 1167 569 375 491 373 492 372 493 374 492 373 493 372 493 373 442 423 492 375 493 372 492 378 490 1245 443 423 493 374 491 375 490 374 442 424 492 374 491 379 440 1293 492 1245 443 1294 442 423 491 379 441 1296 491 375 441 423 442 423 442 425 441 423 442 425 440 424 441 425 441 424 441 421 444 424 441 425 441 423 442 423 442 423 442 453 413 424 442 451 414 424 441 426 440 424 441 424 441 424 441 425 441 424 441 424 441 424 441 426 440 425 440 427 442 1322 413 1296 442 424 441 423 442 424 441 424 442 427 441 9994 3463 1755 440 428 441 1296 441 426 440 425 440 426 439 425 440 426 440 423 442 425 440 425 440 372 494 425 440 428 440 1297 440 426 440 425 440 426 439 426 439 427 439 425 440 429 439 1297 439 1297 439 1297 440 426 439 429 440 1299 439 426 439 426 439 428 437 427 439 426 439 426 439 427 438 427 439 427 438 427 438 427 438 429 437 427 414 451 414 451 414 418 447 1323 414 452 413 455 414 1325 413 452 413 452 413 452 413 453 413 452 413 456 413 1324 413 453 413 452 413 453 412 452 413 454 412 453 412 453 412 453 412 452 413 1324 412 1323 412 1324 412 1324 412 1325 412 456 413 1324 412 404 461 1325 412 453 412 453 412 454 412 453 412 453 412 454 411 455 411 453 412 453 412 453 412 454 412 453 412 453 412 453 412 454 413 453 412 457 412 1323 413 1324 413 1324 412 453 412 453 412 454 413 453 412 453 412 453 412 454 412 452 413 456 412 1323 413 1324 413 1323 414 453 412 452 413 453 413 452 413 452 413 452 413 453 413 450 415 452 413 452 413 453 413 452 413 451 439 427 438 428 439 430 439 1298 439 426 439 428 438 426 439 428 437 426 439 427 438 1298 439 427 438 426 439 427 439 426 439 426 439 425 440 427 440 426 439 426 439 426 439 425 441 426 439 425 440 425 440 427 439 426 439 425 440 429 439 1297 440 1297 440 425 440 425 440 426 441 427 440 +# +# Model: Maytag M6X06F2A +# +name: Off +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 02 00 00 00 diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index 6303278acf9..f3ff853333c 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -1,7 +1,7 @@ Filetype: IR library file Version: 1 # -# Model: NoName Unknown Audio remote +# Model: Yamaha RAV15 and NoName Unknown Audio remote name: Play type: parsed protocol: NEC @@ -69,37 +69,6 @@ protocol: NECext address: 84 79 00 00 command: 02 FD 00 00 # -# Model: Yamaha RAV15 -name: Play -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 43 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 15 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 07 00 00 00 -# -name: Next -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 40 00 00 00 -# -name: Prev -type: parsed -protocol: NEC -address: 00 00 00 00 -command: 44 00 00 00 -# # Model: Yamaha RX-V375 name: Power type: parsed @@ -200,43 +169,43 @@ address: 04 00 00 00 command: 09 00 00 00 # # Model: Samsung HW-K450 Soundbar -# +# name: Power type: raw frequency: 38000 duty_cycle: 0.330000 data: 4637 4376 612 419 584 420 584 420 583 421 582 1427 531 1477 531 472 532 472 557 1452 556 1451 557 1451 557 1452 556 447 557 448 556 449 555 449 555 4453 554 450 554 450 554 451 553 450 554 451 553 451 553 451 553 450 554 1455 553 1454 554 1454 554 451 553 1454 554 1454 554 1455 553 1454 554 451 553 450 554 450 554 1455 553 55439 4554 4458 555 449 555 449 555 450 554 450 554 1455 553 1454 554 451 553 450 554 1454 554 1454 554 1454 554 1455 553 450 554 451 553 451 553 451 553 4453 554 451 553 451 552 451 553 451 553 451 553 451 553 451 553 451 553 1455 553 1455 553 1455 553 451 553 1455 553 1455 553 1455 553 1455 553 451 553 451 552 451 553 1455 553 -# +# name: Play type: raw frequency: 38000 duty_cycle: 0.330000 data: 4636 4380 612 392 612 394 610 394 610 419 583 1399 557 1452 556 473 556 448 556 1428 581 1453 555 1453 555 1453 555 449 555 450 554 451 553 452 552 4457 551 452 552 452 552 452 552 452 552 452 552 1457 551 452 552 1457 551 452 552 452 552 453 551 1457 552 1457 551 452 552 1457 551 452 552 1457 551 1457 551 1457 552 452 552 55450 4551 4461 553 451 553 452 552 452 552 452 552 1456 552 1456 552 452 552 452 552 1456 552 1456 552 1456 552 1456 552 452 552 452 552 453 551 453 551 4456 551 453 551 453 551 453 551 453 551 453 551 1457 551 453 551 1457 552 453 551 454 550 454 550 1457 552 1457 551 454 550 1457 551 454 550 1458 551 1457 551 1458 550 454 550 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 4640 4405 583 420 583 421 582 421 583 422 581 1427 531 1478 530 473 531 472 557 1452 557 1452 556 1452 556 1452 556 448 556 448 556 449 555 450 554 4454 554 451 553 451 553 451 553 451 553 1455 554 1455 553 1455 553 451 553 1455 553 1456 553 1456 553 451 553 451 553 451 553 451 554 1455 554 451 553 452 553 451 553 1456 553 55447 4556 4458 555 449 555 450 554 450 554 450 554 1455 553 1455 553 451 553 451 553 1455 553 1455 553 1455 553 1455 553 451 553 451 553 451 553 451 553 4454 553 451 553 450 554 451 553 450 554 1455 553 1455 553 1455 553 451 553 1455 553 1455 553 1455 553 451 553 451 553 451 553 450 554 1455 553 451 553 451 553 451 553 1455 553 -# +# name: Vol_dn type: raw frequency: 38000 duty_cycle: 0.330000 data: 4636 4378 613 393 611 392 612 393 611 393 557 1451 558 1450 610 420 583 421 557 1427 581 1452 556 1452 556 1452 556 448 555 449 555 450 554 450 554 4455 553 451 553 451 553 451 553 451 553 451 553 451 553 452 552 1456 553 1456 552 1456 553 1456 552 451 553 1456 553 1456 552 1456 553 451 553 451 553 452 552 451 553 1456 552 55452 4553 4461 553 450 554 451 553 451 553 451 553 1456 553 1456 552 451 553 451 553 1456 552 1455 553 1455 553 1455 553 451 553 451 553 451 553 451 553 4456 552 451 553 451 553 451 553 451 553 451 553 451 553 451 553 1456 552 1455 553 1455 553 1455 553 451 553 1455 553 1456 552 1456 552 451 553 451 553 451 553 451 553 1456 552 -# +# name: Prev type: raw frequency: 38000 duty_cycle: 0.330000 data: 255 113623 4638 4378 613 391 612 392 559 446 558 446 558 1477 531 1477 532 472 532 472 532 1476 532 1476 532 1476 532 1477 531 473 555 449 555 449 555 450 554 4455 554 450 554 450 554 450 554 450 554 1455 554 1455 554 450 554 1455 554 450 555 450 554 450 554 1455 554 451 553 451 553 1455 554 450 554 1455 554 1456 553 1455 554 450 554 -# +# name: Next type: raw frequency: 38000 duty_cycle: 0.330000 data: 4557 4430 611 392 610 394 559 445 559 446 558 1451 558 1477 531 448 556 472 532 1476 532 1477 532 1477 531 1477 531 473 556 449 555 449 555 450 554 4454 554 450 554 450 554 450 554 450 555 450 554 450 554 1455 554 1455 554 450 554 450 554 450 554 1455 554 1455 554 1455 553 451 553 450 554 1455 554 1455 554 1455 554 450 554 55458 4555 4459 554 450 554 450 554 450 554 450 554 1455 553 1455 553 450 554 450 554 1455 553 1455 553 1455 553 1455 553 450 554 450 554 450 554 450 554 4454 554 450 554 450 554 450 554 451 553 450 554 450 554 1455 553 1455 553 451 553 450 554 450 554 1455 553 1455 553 1455 553 450 554 451 553 1455 554 1455 553 1455 553 450 554 -# +# name: Mute type: raw frequency: 38000 @@ -285,7 +254,7 @@ type: parsed protocol: NECext address: 10 E7 00 00 command: 41 BE 00 00 -# +# # Model: Grundig CMS 5000 name: Power type: parsed @@ -304,7 +273,7 @@ type: parsed protocol: NECext address: 30 FC 00 00 command: 0D F2 00 00 -# +# name: Vol_dn type: parsed protocol: NECext @@ -316,19 +285,19 @@ type: parsed protocol: NECext address: 30 FC 00 00 command: 13 EC 00 00 -# +# name: Prev type: parsed protocol: NECext address: 30 FC 00 00 command: 11 EE 00 00 -# +# name: Mute type: parsed protocol: NECext address: 30 FC 00 00 command: 0C F3 00 00 -# +# # Model: Panasonic SA-PM193 name: Power type: parsed @@ -347,31 +316,31 @@ type: parsed protocol: Kaseikyo address: AA 02 20 00 command: A0 00 00 00 -# +# name: Vol_up type: parsed protocol: Kaseikyo address: A0 02 20 00 command: 00 02 00 00 -# +# name: Vol_dn type: parsed protocol: Kaseikyo address: A0 02 20 00 command: 10 02 00 00 -# +# name: Next type: parsed protocol: Kaseikyo address: AC 02 20 01 command: A1 00 00 00 -# +# name: Prev type: parsed protocol: Kaseikyo address: AC 02 20 01 command: 91 00 00 00 -# +# name: Mute type: parsed protocol: Kaseikyo @@ -476,33 +445,125 @@ protocol: NEC address: 00 00 00 00 command: 85 00 00 00 # -#Sony audio remote RM-SC3 +# Sony audio remote RM-SC3 name: Power type: parsed protocol: SIRC address: 10 00 00 00 command: 15 00 00 00 -# +# name: Vol_up type: parsed protocol: SIRC address: 10 00 00 00 command: 12 00 00 00 -# +# name: Vol_dn type: parsed protocol: SIRC address: 10 00 00 00 command: 13 00 00 00 -# +# name: Play type: parsed protocol: SIRC20 address: 3A 07 00 00 command: 32 00 00 00 -# +# name: Pause type: parsed protocol: SIRC20 address: 3A 07 00 00 command: 39 00 00 00 +# +# Model: Sony MHC_GSX75 +# +name: Pause +type: parsed +protocol: SIRC20 +address: 3A 07 00 00 +command: 38 00 00 00 +# +# Model: Elac EA101EQ-G +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 46 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 18 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 55 00 00 00 +# +# Model: Philips FW750C +# +name: Vol_up +type: parsed +protocol: RC5 +address: 10 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 10 00 00 00 +command: 11 00 00 00 +# +name: Play +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 35 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 36 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 10 00 00 00 +command: 0D 00 00 00 +# +name: Power +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 0C 00 00 00 +# +# Model: Pioneer VSX-D1-S +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 0A F5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5C A3 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1E E1 00 00 diff --git a/applications/main/infrared/resources/infrared/assets/projector.ir b/applications/main/infrared/resources/infrared/assets/projector.ir index dcfcb06ffad..5a161d153cd 100644 --- a/applications/main/infrared/resources/infrared/assets/projector.ir +++ b/applications/main/infrared/resources/infrared/assets/projector.ir @@ -1,6 +1,6 @@ Filetype: IR library file Version: 1 -# +# # Model: Smart name: Power type: parsed @@ -14,7 +14,7 @@ type: parsed protocol: NECext address: 83 55 00 00 command: 90 6F 00 00 -# +# # Model: Epson name: Power type: parsed @@ -82,7 +82,7 @@ type: parsed protocol: NECext address: 00 30 00 00 command: 83 7C 00 00 -# +# name: Vol_up type: parsed protocol: NECext @@ -106,13 +106,13 @@ type: parsed protocol: NECext address: 87 4E 00 00 command: 29 D6 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: 87 4E 00 00 command: 08 F7 00 00 -# +# name: Vol_dn type: parsed protocol: NECext @@ -360,12 +360,6 @@ protocol: NECext address: 33 00 00 00 command: 0B F4 00 00 # -name: Power -type: parsed -protocol: NECext -address: 83 55 00 00 -command: 90 6F 00 00 -# name: Vol_dn type: parsed protocol: NECext @@ -629,19 +623,19 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 243 27700 170 27632 246 27694 282 27595 307 27497 241 27696 177 27710 164 27644 245 27629 246 27712 174 27638 211 27736 131 27741 306 27504 214 27727 135 27749 132 27761 126 27744 131 27753 127 27764 121 27767 132 27773 307 27577 131 27706 213 27761 129 27759 128 27770 125 27694 213 27751 307 27578 131 27737 131 27745 304 27575 335 27540 124 27752 132 27749 132 27747 134 27757 134 27758 127 27762 131 27748 131 27750 122 27749 130 27748 125 27772 131 27774 136 27762 135 27686 215 27742 131 27749 132 27756 133 27764 126 24073 9255 4460 672 488 618 541 619 541 619 1675 619 1676 618 542 618 542 618 542 618 1676 618 542 618 543 617 1678 616 568 592 1702 592 1702 592 1703 617 543 617 543 617 1677 617 543 617 1678 615 544 616 544 616 544 616 1678 616 1679 615 544 616 1679 615 545 615 1679 615 1679 615 1679 615 40240 9173 2273 591 -# +# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 219 27658 217 27663 216 27658 216 27634 216 27642 215 27646 217 27662 217 27637 216 27649 216 27649 218 27656 217 27658 215 27640 214 27636 217 27649 216 27644 218 27635 217 27630 215 27645 216 27631 215 27632 216 27650 216 27628 217 27630 214 27627 217 27623 215 27632 215 27641 216 27634 214 27633 215 27648 215 27648 217 27651 215 27635 216 27629 216 27630 216 2021 9254 4461 618 542 618 542 618 542 618 1675 619 1676 618 541 619 541 619 542 618 1677 617 543 617 543 617 1678 616 568 592 1702 592 1702 618 1676 618 542 618 542 618 543 617 1677 617 543 617 544 616 1678 616 544 616 1678 616 1678 616 1678 616 544 616 1678 616 1678 616 544 616 1678 616 40239 9200 2247 617 99930 110 27739 119 27738 123 27750 126 27738 175 27617 214 27716 203 27604 213 27639 217 27631 214 27722 136 27753 119 27736 175 27618 246 27683 177 27619 245 27685 171 55486 244 27693 158 27635 241 27695 170 27693 129 27717 340 27530 113 27757 106 27751 124 27728 172 27707 126 27666 215 27708 123 27733 123 -# +# name: Vol_dn type: parsed protocol: NECext address: 18 E9 00 00 command: 49 B6 00 00 -# +# name: Power type: parsed protocol: NEC @@ -653,13 +647,13 @@ type: parsed protocol: NEC address: 02 00 00 00 command: 48 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 02 00 00 00 command: 40 00 00 00 -# +# name: Mute type: parsed protocol: NEC @@ -683,7 +677,7 @@ type: parsed protocol: NECext address: B8 57 00 00 command: 1E E1 00 00 -# +# name: Vol_up type: parsed protocol: NECext @@ -701,13 +695,13 @@ type: parsed protocol: NEC address: 32 00 00 00 command: 8F 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 32 00 00 00 command: 8C 00 00 00 -# +# name: Mute type: raw frequency: 38000 @@ -719,43 +713,37 @@ type: parsed protocol: NEC address: 00 00 00 00 command: A8 00 00 00 -# +# name: Mute type: parsed protocol: NEC address: 00 00 00 00 command: 88 00 00 00 -# +# name: Vol_dn type: parsed protocol: NEC address: 00 00 00 00 command: 9C 00 00 00 -# +# name: Vol_up type: parsed protocol: NEC address: 00 00 00 00 command: 8C 00 00 00 # -name: Power -type: parsed -protocol: NECext -address: 87 45 00 00 -command: 17 E8 00 00 -# name: Vol_up type: raw frequency: 38000 duty_cycle: 0.330000 data: 9064 4354 666 1559 666 1562 662 1586 638 475 636 477 635 477 635 478 635 1590 635 1591 634 478 635 1591 634 478 634 478 635 478 634 1591 635 478 634 1591 634 478 635 478 634 478 635 1591 634 478 634 1591 635 478 634 478 634 1591 634 1591 635 1591 634 478 635 1591 634 478 634 1591 635 40957 9035 2144 634 95483 9047 2155 632 95484 9048 2153 633 -# +# name: Vol_dn type: parsed protocol: NECext address: 87 45 00 00 command: 50 AF 00 00 -# +# name: Mute type: raw frequency: 38000 @@ -767,13 +755,13 @@ type: parsed protocol: NECext address: FF FF 00 00 command: E8 17 00 00 -# +# name: Vol_up type: parsed protocol: NECext address: FF FF 00 00 command: BD 42 00 00 -# +# name: Vol_dn type: parsed protocol: NECext @@ -785,13 +773,13 @@ type: parsed protocol: Kaseikyo address: 41 54 32 00 command: 05 00 00 00 -# +# name: Vol_up type: parsed protocol: Kaseikyo address: 41 54 32 00 command: 70 01 00 00 -# +# name: Vol_dn type: parsed protocol: Kaseikyo diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 9df664a7b88..724cb90cec7 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1687,14 +1687,6 @@ protocol: NEC address: 40 00 00 00 command: 10 00 00 00 # -# Philips OLED 934/12 -# -name: Power -type: parsed -protocol: RC6 -address: 00 00 00 00 -command: 0C 00 00 00 -# name: Mute type: parsed protocol: RC6 @@ -1725,44 +1717,6 @@ protocol: RC6 address: 00 00 00 00 command: 21 00 00 00 # -# Model TCL 50P715X1 -# -name: Power -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: 54 00 00 00 -# -name: Mute -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: FC 00 00 00 -# -name: Vol_up -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: F4 00 00 00 -# -name: Vol_dn -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: 74 00 00 00 -# -name: Ch_next -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: B4 00 00 00 -# -name: Ch_prev -type: parsed -protocol: RCA -address: 0F 00 00 00 -command: 34 00 00 00 -# # Model: JTC Genesis 5.5 # name: Mute @@ -1829,26 +1783,6 @@ protocol: NEC address: 01 00 00 00 command: 17 00 00 00 # -# Visio TV -# -name: Power -type: parsed -protocol: NEC -address: 04 00 00 00 -command: 08 00 00 00 -# -name: Vol_up -type: parsed -protocol: NEC -address: 04 00 00 00 -command: 02 00 00 00 -# -name: Vol_dn -type: parsed -protocol: NEC -address: 04 00 00 00 -command: 03 00 00 00 -# name: Ch_next type: parsed protocol: NEC @@ -1861,12 +1795,6 @@ protocol: NEC address: 04 00 00 00 command: 01 00 00 00 # -name: Mute -type: parsed -protocol: NEC -address: 04 00 00 00 -command: 09 00 00 00 -# # Emerson TV # name: Power @@ -1942,4 +1870,111 @@ type: parsed protocol: NECext address: EA C7 00 00 command: 33 CC 00 00 - +# +# Model: Soniq E32W13B +# +name: Power +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 0E F1 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 1A E5 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 49 B6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 43 BC 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 51 AE 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 4D B2 00 00 +# +# Model: Hisense EN2B27 +# +name: Power +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 0D F2 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 0E F1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 44 BB 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 43 BC 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 4A B5 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 4B B4 00 00 +# +# Model: Viano STV65UHD4K +# +name: Power +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1E E1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 06 F9 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5B A4 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 18 E7 00 00 diff --git a/scripts/flipper/utils/fff.py b/scripts/flipper/utils/fff.py index 3175a1b0041..44727dc622a 100644 --- a/scripts/flipper/utils/fff.py +++ b/scripts/flipper/utils/fff.py @@ -32,6 +32,16 @@ def readKeyValue(self): raise Exception("Unexpected line: not `key:value`") return data[0].strip(), data[1].strip() + def readComment(self): + if self.cursor == len(self.lines): + raise EOFError() + line = self.lines[self.cursor].strip() + if line.startswith("#"): + self.cursor += 1 + return line[1:].strip() + else: + return None + def readKey(self, key: str): k, v = self.readKeyValue() if k != key: @@ -67,7 +77,7 @@ def writeEmptyLine(self): self.writeLine("") def writeComment(self, text: str): - if text: + if text and len(text): self.writeLine(f"# {text}") else: self.writeLine("#") @@ -104,3 +114,4 @@ def load(self, filename: str): def save(self, filename: str): with open(filename, "w", newline="\n") as file: file.write("\n".join(self.lines)) + file.write("\n") diff --git a/scripts/infrared.py b/scripts/infrared.py index 9fa44a90aa0..af5d91a5670 100755 --- a/scripts/infrared.py +++ b/scripts/infrared.py @@ -27,37 +27,51 @@ def cleanup(self): return 1 data = [] - unique = {} + unique_combo = {} + unique_payload = {} while True: try: d = {} + d["comments"] = [] + while (comment := f.readComment()) is not None: + d["comments"].append(comment) d["name"] = f.readKey("name") d["type"] = f.readKey("type") - key = None + key_combo = f'{d["name"]}' + key_payload = None if d["type"] == "parsed": d["protocol"] = f.readKey("protocol") d["address"] = f.readKey("address") d["command"] = f.readKey("command") - key = f'{d["protocol"]}{d["address"]}{d["command"]}' + key_payload = f'{d["protocol"]}{d["address"]}{d["command"]}' + key_combo += key_payload elif d["type"] == "raw": d["frequency"] = f.readKey("frequency") d["duty_cycle"] = f.readKey("duty_cycle") d["data"] = f.readKey("data") - key = f'{d["frequency"]}{d["duty_cycle"]}{d["data"]}' + key_payload = f'{d["frequency"]}{d["duty_cycle"]}{d["data"]}' + key_combo += key_payload else: raise Exception(f'Unknown type: {d["type"]}') - if not key in unique: - unique[key] = d + + if not key_combo in unique_combo: + unique_combo[key_combo] = d data.append(d) + # Check payload only + if not key_payload in unique_payload: + unique_payload[key_payload] = d + else: + self.logger.warning(f"Duplicate payload, check manually: {d}") else: - self.logger.warn(f"Duplicate key: {key}") + self.logger.info(f"Duplicate data removed: {d}") except EOFError: break # Form new file f = FlipperFormatFile() f.setHeader(filetype, version) for i in data: - f.writeComment(None) + for comment in i["comments"]: + f.writeComment(comment) f.writeKey("name", i["name"]) f.writeKey("type", i["type"]) if i["type"] == "parsed": diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 314eabecd5b..8759c3b8437 100755 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -121,7 +121,7 @@ def copy(self) -> int: try: shutil.rmtree(self.output_dir_path) except Exception as ex: - self.logger.warn(f"Failed to clean output directory: {ex}") + self.logger.warning(f"Failed to clean output directory: {ex}") if not exists(self.output_dir_path): self.logger.debug(f"Creating output directory {self.output_dir_path}") diff --git a/scripts/update.py b/scripts/update.py index 47a5eeb27b0..2b66207442b 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -152,7 +152,7 @@ def generate(self): return 3 if not self.layout_check(updater_stage_size, dfu_size, radio_addr): - self.logger.warn("Memory layout looks suspicious") + self.logger.warning("Memory layout looks suspicious") if self.args.disclaimer != "yes": self.show_disclaimer() return 2 @@ -205,7 +205,7 @@ def generate(self): def layout_check(self, stage_size, fw_size, radio_addr): if stage_size > self.UPDATER_SIZE_THRESHOLD: - self.logger.warn( + self.logger.warning( f"Updater size {stage_size}b > {self.UPDATER_SIZE_THRESHOLD}b and is not loadable on older firmwares!" ) @@ -217,13 +217,13 @@ def layout_check(self, stage_size, fw_size, radio_addr): self.logger.debug(f"Expected reserved space size: {fw2stack_gap}") fw2stack_gap_pages = fw2stack_gap / self.FLASH_PAGE_SIZE if fw2stack_gap_pages < 0: - self.logger.warn( + self.logger.warning( f"Firmware image overlaps C2 region and is not programmable!" ) return False elif fw2stack_gap_pages < self.MIN_GAP_PAGES: - self.logger.warn( + self.logger.warning( f"Expected reserved flash size is too small (~{int(fw2stack_gap_pages)} page(s), need >={self.MIN_GAP_PAGES} page(s))" ) return False From 7fc209f0016f2cb7d33339423c93825dae0c17e5 Mon Sep 17 00:00:00 2001 From: IRecabarren Date: Wed, 2 Oct 2024 13:41:40 -0300 Subject: [PATCH 07/36] New layout for BadUSB (es-LA) (#3916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add files via upload * Readme: add SAST section Co-authored-by: あく --- ReadMe.md | 4 ++++ .../resources/badusb/assets/layouts/es-LA.kl | Bin 0 -> 256 bytes 2 files changed, 4 insertions(+) create mode 100644 applications/main/bad_usb/resources/badusb/assets/layouts/es-LA.kl diff --git a/ReadMe.md b/ReadMe.md index f4c0dffedc3..15235e6fcf6 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -120,3 +120,7 @@ Also, see `ReadMe.md` files inside those directories for further details. - Website: [flipperzero.one](https://flipperzero.one) - Forum: [forum.flipperzero.one](https://forum.flipperzero.one/) - Kickstarter: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers) + +## SAST Tools + +- [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. diff --git a/applications/main/bad_usb/resources/badusb/assets/layouts/es-LA.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/es-LA.kl new file mode 100644 index 0000000000000000000000000000000000000000..2a6c78adcb13098a557c0278329df7bb0802e798 GIT binary patch literal 256 zcmaLLHx9y30KiboqK8iCHFN=y5-F7NABOv%fjJA_o}t{0JJ<61s|Opm?o8Z!Fy-Xg z%#0Tw-W=9my2Ih<500`&AJVnwv24sv1`x11BZ@`9XoOA%()Af wu3TG~hTI~2%tM7LHR?2I(juZwhb}$(3>Y#ZX3T^sGv+K Date: Wed, 2 Oct 2024 19:11:13 +0200 Subject: [PATCH 08/36] Prevent idle priority threads from potentially starving the FreeRTOS idle task (#3909) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FuriThread: Make FuriThreadPriorityIdle equal to the FreeRTOS one, remove FuriThreadPriorityNone This magic constant was meaningless, FuriThreadPriorityNormal is now assigned by default instead. * Make furi_thread_list_process private Its 'runtime' parameter is to be obtained from FreeRTOS, which means apps cannot do it. * DirectDraw: Remove an useless include and fix memory leak Makes this debug app compileable with uFBT out of the box Co-authored-by: あく --- applications/debug/direct_draw/direct_draw.c | 4 ++-- furi/core/thread.c | 16 +++++++++------- furi/core/thread.h | 5 ++--- furi/core/thread_list.c | 2 +- furi/core/thread_list.h | 8 -------- furi/core/thread_list_i.h | 19 +++++++++++++++++++ targets/f18/api_symbols.csv | 3 +-- targets/f7/api_symbols.csv | 3 +-- 8 files changed, 35 insertions(+), 25 deletions(-) create mode 100644 furi/core/thread_list_i.h diff --git a/applications/debug/direct_draw/direct_draw.c b/applications/debug/direct_draw/direct_draw.c index bc1e5545977..1e45a6d949b 100644 --- a/applications/debug/direct_draw/direct_draw.c +++ b/applications/debug/direct_draw/direct_draw.c @@ -1,6 +1,5 @@ #include #include -#include #include #define BUFFER_SIZE (32U) @@ -42,10 +41,11 @@ static DirectDraw* direct_draw_alloc(void) { static void direct_draw_free(DirectDraw* instance) { furi_pubsub_unsubscribe(instance->input, instance->input_subscription); - instance->canvas = NULL; gui_direct_draw_release(instance->gui); furi_record_close(RECORD_GUI); furi_record_close(RECORD_INPUT_EVENTS); + + free(instance); } static void direct_draw_block(Canvas* canvas, uint32_t size, uint32_t counter) { diff --git a/furi/core/thread.c b/furi/core/thread.c index 60cc628acb3..65787c0e0f7 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -1,5 +1,5 @@ #include "thread.h" -#include "thread_list.h" +#include "thread_list_i.h" #include "kernel.h" #include "memmgr.h" #include "memmgr_heap.h" @@ -65,6 +65,9 @@ struct FuriThread { // IMPORTANT: container MUST be the FIRST struct member static_assert(offsetof(FuriThread, container) == 0); +// Our idle priority should be equal to the one from FreeRTOS +static_assert(FuriThreadPriorityIdle == tskIDLE_PRIORITY); + static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size); static int32_t __furi_thread_stdout_flush(FuriThread* thread); @@ -145,6 +148,8 @@ static void furi_thread_init_common(FuriThread* thread) { furi_thread_set_appid(thread, "driver"); } + thread->priority = FuriThreadPriorityNormal; + FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); if(mode == FuriHalRtcHeapTrackModeAll) { thread->heap_trace_enabled = true; @@ -269,7 +274,7 @@ void furi_thread_set_context(FuriThread* thread, void* context) { void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) { furi_check(thread); furi_check(thread->state == FuriThreadStateStopped); - furi_check(priority >= FuriThreadPriorityIdle && priority <= FuriThreadPriorityIsr); + furi_check(priority <= FuriThreadPriorityIsr); thread->priority = priority; } @@ -281,9 +286,7 @@ FuriThreadPriority furi_thread_get_priority(FuriThread* thread) { void furi_thread_set_current_priority(FuriThreadPriority priority) { furi_check(priority <= FuriThreadPriorityIsr); - - UBaseType_t new_priority = priority ? priority : FuriThreadPriorityNormal; - vTaskPrioritySet(NULL, new_priority); + vTaskPrioritySet(NULL, priority); } FuriThreadPriority furi_thread_get_current_priority(void) { @@ -345,7 +348,6 @@ void furi_thread_start(FuriThread* thread) { furi_thread_set_state(thread, FuriThreadStateStarting); uint32_t stack_depth = thread->stack_size / sizeof(StackType_t); - UBaseType_t priority = thread->priority ? thread->priority : FuriThreadPriorityNormal; thread->is_active = true; @@ -355,7 +357,7 @@ void furi_thread_start(FuriThread* thread) { thread->name, stack_depth, thread, - priority, + thread->priority, thread->stack_buffer, &thread->container) == (TaskHandle_t)thread); } diff --git a/furi/core/thread.h b/furi/core/thread.h index e8cdeaeafb6..d90ece85d6e 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -30,11 +30,10 @@ typedef enum { * @brief Enumeration of possible FuriThread priorities. */ typedef enum { - FuriThreadPriorityNone = 0, /**< Uninitialized, choose system default */ - FuriThreadPriorityIdle = 1, /**< Idle priority */ + FuriThreadPriorityIdle = 0, /**< Idle priority */ FuriThreadPriorityLowest = 14, /**< Lowest */ FuriThreadPriorityLow = 15, /**< Low */ - FuriThreadPriorityNormal = 16, /**< Normal */ + FuriThreadPriorityNormal = 16, /**< Normal, system default */ FuriThreadPriorityHigh = 17, /**< High */ FuriThreadPriorityHighest = 18, /**< Highest */ FuriThreadPriorityIsr = diff --git a/furi/core/thread_list.c b/furi/core/thread_list.c index 5355a896caf..e542c192b0f 100644 --- a/furi/core/thread_list.c +++ b/furi/core/thread_list.c @@ -84,7 +84,7 @@ FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, Fur } void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick) { - furi_check(instance); + furi_assert(instance); instance->runtime_previous = instance->runtime_current; instance->runtime_current = runtime; diff --git a/furi/core/thread_list.h b/furi/core/thread_list.h index d01aa24a04e..f51aae8cd46 100644 --- a/furi/core/thread_list.h +++ b/furi/core/thread_list.h @@ -68,14 +68,6 @@ FuriThreadListItem* furi_thread_list_get_at(FuriThreadList* instance, size_t pos */ FuriThreadListItem* furi_thread_list_get_or_insert(FuriThreadList* instance, FuriThread* thread); -/** Process items in the FuriThreadList instance - * - * @param instance The instance - * @param[in] runtime The runtime of the system since start - * @param[in] tick The tick when processing happened - */ -void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick); - /** Get percent of time spent in ISR * * @param instance The instance diff --git a/furi/core/thread_list_i.h b/furi/core/thread_list_i.h new file mode 100644 index 00000000000..44fe0a9cbcf --- /dev/null +++ b/furi/core/thread_list_i.h @@ -0,0 +1,19 @@ +#pragma once + +#include "thread_list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Process items in the FuriThreadList instance + * + * @param instance The instance + * @param[in] runtime The runtime of the system since start + * @param[in] tick The tick when processing happened + */ +void furi_thread_list_process(FuriThreadList* instance, uint32_t runtime, uint32_t tick); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 1d9ac28698b..e808f0748d5 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,74.0,, +Version,+,75.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1652,7 +1652,6 @@ Function,+,furi_thread_list_free,void,FuriThreadList* Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t" Function,+,furi_thread_list_get_isr_time,float,FuriThreadList* Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*" -Function,+,furi_thread_list_process,void,"FuriThreadList*, uint32_t, uint32_t" Function,+,furi_thread_list_size,size_t,FuriThreadList* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index cb3037e48f9..7317f7f0471 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,74.0,, +Version,+,75.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1866,7 +1866,6 @@ Function,+,furi_thread_list_free,void,FuriThreadList* Function,+,furi_thread_list_get_at,FuriThreadListItem*,"FuriThreadList*, size_t" Function,+,furi_thread_list_get_isr_time,float,FuriThreadList* Function,+,furi_thread_list_get_or_insert,FuriThreadListItem*,"FuriThreadList*, FuriThread*" -Function,+,furi_thread_list_process,void,"FuriThreadList*, uint32_t, uint32_t" Function,+,furi_thread_list_size,size_t,FuriThreadList* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" From 00c1611c33919069b7a3af679dbb6afc6a92bd3d Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 3 Oct 2024 02:51:07 +0900 Subject: [PATCH 09/36] Improve bit_buffer.h docs (#3783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve bit_buffer.h docs * Toolbox: update doxygen comments fix spelling * Toolbox: update bit lib docs Co-authored-by: あく Co-authored-by: gornekich --- lib/toolbox/bit_buffer.h | 378 +++++++++++++++++++++------------------ 1 file changed, 208 insertions(+), 170 deletions(-) diff --git a/lib/toolbox/bit_buffer.h b/lib/toolbox/bit_buffer.h index 5c50e729c60..bd95ec95ce5 100644 --- a/lib/toolbox/bit_buffer.h +++ b/lib/toolbox/bit_buffer.h @@ -1,3 +1,9 @@ +/** Bit Buffer + * + * Various bits and bytes manipulation tools. + * + * @file bit_buffer.h + */ #pragma once #include @@ -10,118 +16,128 @@ extern "C" { typedef struct BitBuffer BitBuffer; -// Construction, deletion, reset - -/** - * Allocate a BitBuffer instance. +/** Allocate a BitBuffer instance. * - * @param [in] capacity_bytes maximum buffer capacity, in bytes - * @return pointer to the allocated BitBuffer instance + * @param[in] capacity_bytes maximum buffer capacity, in bytes + * + * @return pointer to the allocated BitBuffer instance */ BitBuffer* bit_buffer_alloc(size_t capacity_bytes); -/** - * Delete a BitBuffer instance. +/** Delete a BitBuffer instance. * - * @param [in,out] buf pointer to a BitBuffer instance + * @param[in,out] buf pointer to a BitBuffer instance */ void bit_buffer_free(BitBuffer* buf); -/** - * Clear all data from a BitBuffer instance. +/** Clear all data from a BitBuffer instance. * - * @param [in,out] buf pointer to a BitBuffer instance + * @param[in,out] buf pointer to a BitBuffer instance */ void bit_buffer_reset(BitBuffer* buf); // Copy and write -/** - * Copy another BitBuffer instance's contents to this one, replacing - * all of the original data. - * The destination capacity must be no less than the source data size. +/** Copy another BitBuffer instance's contents to this one, replacing all of the + * original data. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] other pointer to a BitBuffer instance to copy from - * @note + * @warning The destination capacity must be no less than the source data + * size. + * + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] other pointer to a BitBuffer instance to copy from */ void bit_buffer_copy(BitBuffer* buf, const BitBuffer* other); -/** - * Copy all BitBuffer instance's contents to this one, starting from start_index, - * replacing all of the original data. - * The destination capacity must be no less than the source data size - * counting from start_index. +/** Copy all BitBuffer instance's contents to this one, starting from + * start_index, replacing all of the original data. + * + * @warning The destination capacity must be no less than the source data + * size counting from start_index. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] other pointer to a BitBuffer instance to copy from - * @param [in] start_index index to begin copying source data from + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] other pointer to a BitBuffer instance to copy from + * @param[in] start_index index to begin copying source data from */ void bit_buffer_copy_right(BitBuffer* buf, const BitBuffer* other, size_t start_index); -/** - * Copy all BitBuffer instance's contents to this one, ending with end_index, +/** Copy all BitBuffer instance's contents to this one, ending with end_index, * replacing all of the original data. - * The destination capacity must be no less than the source data size - * counting to end_index. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] other pointer to a BitBuffer instance to copy from - * @param [in] end_index index to end copying source data at + * @warning The destination capacity must be no less than the source data + * size counting to end_index. + * + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] other pointer to a BitBuffer instance to copy from + * @param[in] end_index index to end copying source data at */ void bit_buffer_copy_left(BitBuffer* buf, const BitBuffer* other, size_t end_index); -/** - * Copy a byte array to a BitBuffer instance, replacing all of the original data. - * The destination capacity must be no less than the source data size. +/** Copy a byte array to a BitBuffer instance, replacing all of the original + * data. + * + * @warning The destination capacity must be no less than the source data + * size. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] data pointer to the byte array to be copied - * @param [in] size_bytes size of the data to be copied, in bytes + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] data pointer to the byte array to be copied + * @param[in] size_bytes size of the data to be copied, in bytes */ void bit_buffer_copy_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes); -/** - * Copy a byte array to a BitBuffer instance, replacing all of the original data. - * The destination capacity must be no less than the source data size. +/** Copy a byte array to a BitBuffer instance, replacing all of the original + * data. + * + * @warning The destination capacity must be no less than the source data + * size. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] data pointer to the byte array to be copied - * @param [in] size_bits size of the data to be copied, in bits + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] data pointer to the byte array to be copied + * @param[in] size_bits size of the data to be copied, in bits */ void bit_buffer_copy_bits(BitBuffer* buf, const uint8_t* data, size_t size_bits); -/** - * Copy a byte with parity array to a BitBuffer instance, replacing all of the original data. - * The destination capacity must be no less than the source data size. +/** Copy a byte with parity array to a BitBuffer instance, replacing all of the + * original data. * - * @param [in,out] buf pointer to a BitBuffer instance to copy into - * @param [in] data pointer to the byte array to be copied - * @param [in] size_bitss size of the data to be copied, in bits + * @warning The destination capacity must be no less than the source data + * size. + * + * @param[in,out] buf pointer to a BitBuffer instance to copy into + * @param[in] data pointer to the byte array to be copied + * @param[in] size_bits size of the data to be copied, in bits + * @note Parity bits are placed starting with the most significant bit + * of each byte and moving up. + * @note Example: DDDDDDDD PDDDDDDD DPDDDDDD DDP... */ void bit_buffer_copy_bytes_with_parity(BitBuffer* buf, const uint8_t* data, size_t size_bits); -/** - * Write a BitBuffer instance's entire contents to an arbitrary memory location. - * The destination memory must be allocated. Additionally, the destination - * capacity must be no less than the source data size. +/** Write a BitBuffer instance's entire contents to an arbitrary memory location. * - * @param [in] buf pointer to a BitBuffer instance to write from - * @param [out] dest pointer to the destination memory location - * @param [in] size_bytes maximum destination data size, in bytes + * @warning The destination memory must be allocated. Additionally, the + * destination capacity must be no less than the source data size. + * + * @param[in] buf pointer to a BitBuffer instance to write from + * @param[out] dest pointer to the destination memory location + * @param[in] size_bytes maximum destination data size, in bytes */ void bit_buffer_write_bytes(const BitBuffer* buf, void* dest, size_t size_bytes); -/** - * Write a BitBuffer instance's entire contents to an arbitrary memory location. +/** Write a BitBuffer instance's entire contents to an arbitrary memory location. + * * Additionally, place a parity bit after each byte. - * The destination memory must be allocated. Additionally, the destination - * capacity must be no less than the source data size plus parity. * - * @param [in] buf pointer to a BitBuffer instance to write from - * @param [out] dest pointer to the destination memory location - * @param [in] size_bytes maximum destination data size, in bytes - * @param [out] bits_written actual number of bits writen, in bits + * @warning The destination memory must be allocated. Additionally, the + * destination capacity must be no less than the source data size + * plus parity. + * + * @param[in] buf pointer to a BitBuffer instance to write from + * @param[out] dest pointer to the destination memory location + * @param[in] size_bytes maximum destination data size, in bytes + * @param[out] bits_written actual number of bits written, in bits + * @note Parity bits are placed starting with the most significant bit of + * each byte and moving up. + * @note Example: DDDDDDDD PDDDDDDD DPDDDDDD DDP... */ void bit_buffer_write_bytes_with_parity( const BitBuffer* buf, @@ -129,15 +145,17 @@ void bit_buffer_write_bytes_with_parity( size_t size_bytes, size_t* bits_written); -/** - * Write a slice of BitBuffer instance's contents to an arbitrary memory location. - * The destination memory must be allocated. Additionally, the destination - * capacity must be no less than the requested slice size. - * - * @param [in] buf pointer to a BitBuffer instance to write from - * @param [out] dest pointer to the destination memory location - * @param [in] start_index index to begin copying source data from - * @param [in] size_bytes data slice size, in bytes +/** Write a slice of BitBuffer instance's contents to an arbitrary memory + * location. + * + * @warning The destination memory must be allocated. Additionally, the + * destination capacity must be no less than the requested slice + * size. + * + * @param[in] buf pointer to a BitBuffer instance to write from + * @param[out] dest pointer to the destination memory location + * @param[in] start_index index to begin copying source data from + * @param[in] size_bytes data slice size, in bytes */ void bit_buffer_write_bytes_mid( const BitBuffer* buf, @@ -147,176 +165,196 @@ void bit_buffer_write_bytes_mid( // Checks -/** - * Check whether a BitBuffer instance contains a partial byte (i.e. the bit count - * is not divisible by 8). +/** Check whether a BitBuffer instance contains a partial byte (i.e.\ the bit + * count is not divisible by 8). * - * @param [in] buf pointer to a BitBuffer instance to be checked - * @return true if the instance contains a partial byte, false otherwise + * @param[in] buf pointer to a BitBuffer instance to be checked + * + * @return true if the instance contains a partial byte, false otherwise */ bool bit_buffer_has_partial_byte(const BitBuffer* buf); -/** - * Check whether a BitBuffer instance's contents start with the designated byte. +/** Check whether a BitBuffer instance's contents start with the designated byte. * - * @param [in] buf pointer to a BitBuffer instance to be checked - * @param [in] byte byte value to be checked against - * @return true if data starts with designated byte, false otherwise + * @param[in] buf pointer to a BitBuffer instance to be checked + * @param[in] byte byte value to be checked against + * + * @return true if data starts with designated byte, false otherwise */ bool bit_buffer_starts_with_byte(const BitBuffer* buf, uint8_t byte); // Getters -/** - * Get a BitBuffer instance's capacity (i.e. the maximum possible amount of data), in bytes. +/** Get a BitBuffer instance's capacity (i.e.\ the maximum possible amount of + * data), in bytes. * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @return capacity, in bytes + * @param[in] buf pointer to a BitBuffer instance to be queried + * + * @return capacity, in bytes */ size_t bit_buffer_get_capacity_bytes(const BitBuffer* buf); -/** - * Get a BitBuffer instance's data size (i.e. the amount of stored data), in bits. - * Might be not divisible by 8 (see bit_buffer_is_partial_byte). +/** Get a BitBuffer instance's data size (i.e.\ the amount of stored data), in + * bits. + * + * @warning Might be not divisible by 8 (see bit_buffer_is_partial_byte). * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @return data size, in bits. + * @param[in] buf pointer to a BitBuffer instance to be queried + * + * @return data size, in bits. */ size_t bit_buffer_get_size(const BitBuffer* buf); /** - * Get a BitBuffer instance's data size (i.e. the amount of stored data), in bytes. - * If a partial byte is present, it is also counted. + * Get a BitBuffer instance's data size (i.e.\ the amount of stored data), in + * bytes. + * + * @warning If a partial byte is present, it is also counted. * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @return data size, in bytes. + * @param[in] buf pointer to a BitBuffer instance to be queried + * + * @return data size, in bytes. */ size_t bit_buffer_get_size_bytes(const BitBuffer* buf); -/** - * Get a byte value at a specified index in a BitBuffer instance. - * The index must be valid (i.e. less than the instance's data size in bytes). +/** Get a byte value at a specified index in a BitBuffer instance. + * + * @warning The index must be valid (i.e.\ less than the instance's data size + * in bytes). + * + * @param[in] buf pointer to a BitBuffer instance to be queried + * @param[in] index index of the byte in question * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @param [in] index index of the byte in question + * @return byte value */ uint8_t bit_buffer_get_byte(const BitBuffer* buf, size_t index); -/** - * Get a byte value starting from the specified bit index in a BitBuffer instance. - * The resulting byte might correspond to a single byte (if the index is a multiple - * of 8), or two overlapping bytes combined. - * The index must be valid (i.e. less than the instance's data size in bits). +/** Get a byte value starting from the specified bit index in a BitBuffer + * instance. + * + * @warning The resulting byte might correspond to a single byte (if the + * index is a multiple of 8), or two overlapping bytes combined. The + * index must be valid (i.e.\ less than the instance's data size in + * bits). * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @param [in] index bit index of the byte in question + * @param[in] buf pointer to a BitBuffer instance to be queried + * @param[in] index_bits bit index of the byte in question + * + * @return byte value */ uint8_t bit_buffer_get_byte_from_bit(const BitBuffer* buf, size_t index_bits); -/** - * Get the pointer to a BitBuffer instance's underlying data. +/** Get the pointer to a BitBuffer instance's underlying data. * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @return pointer to the underlying data + * @param[in] buf pointer to a BitBuffer instance to be queried + * + * @return pointer to the underlying data */ const uint8_t* bit_buffer_get_data(const BitBuffer* buf); -/** - * Get the pointer to a BitBuffer instance's underlying data. +/** Get the pointer to the parity data of a BitBuffer instance. * - * @param [in] buf pointer to a BitBuffer instance to be queried - * @return pointer to the underlying data + * @param[in] buf pointer to a BitBuffer instance to be queried + * + * @return pointer to the parity data */ const uint8_t* bit_buffer_get_parity(const BitBuffer* buf); // Setters -/** - * Set byte value at a specified index in a BitBuffer instance. - * The index must be valid (i.e. less than the instance's data size in bytes). +/** Set byte value at a specified index in a BitBuffer instance. * - * @param [in,out] buf pointer to a BitBuffer instance to be modified - * @param [in] index index of the byte in question - * @param [in] byte byte value to be set at index + * @warning The index must be valid (i.e.\ less than the instance's data + * size in bytes). + * + * @param[in,out] buf pointer to a BitBuffer instance to be modified + * @param[in] index index of the byte in question + * @param[in] byte byte value to be set at index */ void bit_buffer_set_byte(BitBuffer* buf, size_t index, uint8_t byte); -/** - * Set byte and parity bit value at a specified index in a BitBuffer instance. - * The index must be valid (i.e. less than the instance's data size in bytes). +/** Set byte and parity bit value at a specified index in a BitBuffer instance. + * + * @warning The index must be valid (i.e.\ less than the instance's data + * size in bytes). * - * @param [in,out] buf pointer to a BitBuffer instance to be modified - * @param [in] index index of the byte in question - * @param [in] byte byte value to be set at index - * @param [in] parity parity bit value to be set at index + * @param[in,out] buff pointer to a BitBuffer instance to be modified + * @param[in] index index of the byte in question + * @param[in] byte byte value to be set at index + * @param[in] parity parity bit value to be set at index */ void bit_buffer_set_byte_with_parity(BitBuffer* buff, size_t index, uint8_t byte, bool parity); -/** - * Resize a BitBuffer instance to a new size, in bits. - * @warning May cause bugs. Use only if absolutely necessary. +/** Resize a BitBuffer instance to a new size, in bits. + * + * @warning May cause bugs. Use only if absolutely necessary. * - * @param [in,out] buf pointer to a BitBuffer instance to be resized - * @param [in] new_size the new size of the buffer, in bits + * @param[in,out] buf pointer to a BitBuffer instance to be resized + * @param[in] new_size the new size of the buffer, in bits */ void bit_buffer_set_size(BitBuffer* buf, size_t new_size); -/** - * Resize a BitBuffer instance to a new size, in bytes. - * @warning May cause bugs. Use only if absolutely necessary. +/** Resize a BitBuffer instance to a new size, in bytes. + * + * @warning May cause bugs. Use only if absolutely necessary. * - * @param [in,out] buf pointer to a BitBuffer instance to be resized - * @param [in] new_size_bytes the new size of the buffer, in bytes + * @param[in,out] buf pointer to a BitBuffer instance to be resized + * @param[in] new_size_bytes the new size of the buffer, in bytes */ void bit_buffer_set_size_bytes(BitBuffer* buf, size_t new_size_bytes); // Modification -/** - * Append all BitBuffer's instance contents to this one. The destination capacity - * must be no less than its original data size plus source data size. +/** Append all BitBuffer's instance contents to this one. + * + * @warning The destination capacity must be no less than its original + * data size plus source data size. * - * @param [in,out] buf pointer to a BitBuffer instance to be appended to - * @param [in] other pointer to a BitBuffer instance to be appended + * @param[in,out] buf pointer to a BitBuffer instance to be appended to + * @param[in] other pointer to a BitBuffer instance to be appended */ void bit_buffer_append(BitBuffer* buf, const BitBuffer* other); -/** - * Append a BitBuffer's instance contents to this one, starting from start_index. - * The destination capacity must be no less than the source data size - * counting from start_index. +/** Append a BitBuffer's instance contents to this one, starting from + * start_index. + * + * @warning The destination capacity must be no less than the source data + * size counting from start_index. * - * @param [in,out] buf pointer to a BitBuffer instance to be appended to - * @param [in] other pointer to a BitBuffer instance to be appended - * @param [in] start_index index to begin copying source data from + * @param[in,out] buf pointer to a BitBuffer instance to be appended to + * @param[in] other pointer to a BitBuffer instance to be appended + * @param[in] start_index index to begin copying source data from */ void bit_buffer_append_right(BitBuffer* buf, const BitBuffer* other, size_t start_index); -/** - * Append a byte to a BitBuffer instance. - * The destination capacity must be no less its original data size plus one. +/** Append a byte to a BitBuffer instance. + * + * @warning The destination capacity must be no less its original data + * size plus one. * - * @param [in,out] buf pointer to a BitBuffer instance to be appended to - * @param [in] byte byte value to be appended + * @param[in,out] buf pointer to a BitBuffer instance to be appended to + * @param[in] byte byte value to be appended */ void bit_buffer_append_byte(BitBuffer* buf, uint8_t byte); -/** - * Append a byte array to a BitBuffer instance. - * The destination capacity must be no less its original data size plus source data size. +/** Append a byte array to a BitBuffer instance. + * + * @warning The destination capacity must be no less its original data + * size plus source data size. * - * @param [in,out] buf pointer to a BitBuffer instance to be appended to - * @param [in] data pointer to the byte array to be appended - * @param [in] size_bytes size of the data to be appended, in bytes + * @param[in,out] buf pointer to a BitBuffer instance to be appended to + * @param[in] data pointer to the byte array to be appended + * @param[in] size_bytes size of the data to be appended, in bytes */ void bit_buffer_append_bytes(BitBuffer* buf, const uint8_t* data, size_t size_bytes); -/** - * Append a bit to a BitBuffer instance. - * The destination capacity must be sufficient to accomodate the additional bit. +/** Append a bit to a BitBuffer instance. + * + * @warning The destination capacity must be sufficient to accommodate the + * additional bit. * - * @param [in,out] buf pointer to a BitBuffer instance to be appended to - * @param [in] bit bit value to be appended + * @param[in,out] buf pointer to a BitBuffer instance to be appended to + * @param[in] bit bit value to be appended */ void bit_buffer_append_bit(BitBuffer* buf, bool bit); From cfb9c991cb025445091bfc0bb9ef23d15cc0a4b2 Mon Sep 17 00:00:00 2001 From: Nikolay Marchuk Date: Sun, 6 Oct 2024 22:56:35 +0700 Subject: [PATCH 10/36] furi_hal_random: Wait for ready state and no errors before sampling (#3933) When random output is not ready, but error state flags are not set, sampling of random generator samples zero until next value is ready. --- targets/f7/furi_hal/furi_hal_random.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/f7/furi_hal/furi_hal_random.c b/targets/f7/furi_hal/furi_hal_random.c index 8b75a05c5b4..6269a90e1b8 100644 --- a/targets/f7/furi_hal/furi_hal_random.c +++ b/targets/f7/furi_hal/furi_hal_random.c @@ -11,7 +11,7 @@ #define TAG "FuriHalRandom" static uint32_t furi_hal_random_read_rng(void) { - while(LL_RNG_IsActiveFlag_CECS(RNG) && LL_RNG_IsActiveFlag_SECS(RNG) && + while(LL_RNG_IsActiveFlag_CECS(RNG) || LL_RNG_IsActiveFlag_SECS(RNG) || !LL_RNG_IsActiveFlag_DRDY(RNG)) { /* Error handling as described in RM0434, pg. 582-583 */ if(LL_RNG_IsActiveFlag_CECS(RNG)) { From 6ead328bb7e703dc0d0db40cc05ed45ce3bcb5bf Mon Sep 17 00:00:00 2001 From: assasinfil Date: Sun, 6 Oct 2024 19:33:07 +0300 Subject: [PATCH 11/36] Moscow social card parser (#3464) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated troyka layout (full version) * Changed to furi func * Small refactor * Bitlib refactor * Moved to API * Rollback troyka parser * Fix functions * Added MSK Social card parser * Parser func refactor start * Layout E3 refactored * Layout E4 refactored * Layout 6 refactored * Layout E5 refactored * Layout 2 refactored * Layout E5 fix * Layout E6 refactored, valid_date need fix * Layout E6 fix * Layout FCB refactored * Layout F0B refactored * Layout 8 refactored * Layout A refactored * Layout C refactored * Layout D refactored * Layout E1 refactored * Layout E2 refactored * Old code cleanup * Memory cleanup * Unused imports cleanup * Keys struct refactor * Keys struct refactor * Layout E1 fix * Added debug info for layout and department * Fix social card parse validation * Added card number validation * Added transport data ui improvements from Astrrra's troyka render func. Co-authored-by: gornekich Co-authored-by: あく --- .../nfc/api/mosgortrans/mosgortrans_util.c | 14 + .../nfc/api/mosgortrans/mosgortrans_util.h | 5 + .../main/nfc/api/nfc_app_api_table_i.h | 9 +- applications/main/nfc/application.fam | 9 + .../plugins/supported_cards/social_moscow.c | 301 ++++++++++++++++++ .../main/nfc/plugins/supported_cards/troika.c | 36 +-- 6 files changed, 349 insertions(+), 25 deletions(-) create mode 100644 applications/main/nfc/plugins/supported_cards/social_moscow.c diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c index 3138d790b3e..261f24ce00c 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c @@ -2,6 +2,20 @@ #define TAG "Mosgortrans" +void render_section_header( + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + for(uint8_t i = 0; i < prefix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } + furi_string_cat_printf(str, "[ %s ]", name); + for(uint8_t i = 0; i < suffix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } +} + void from_days_to_datetime(uint32_t days, DateTime* datetime, uint16_t start_year) { uint32_t timestamp = days * 24 * 60 * 60; DateTime start_datetime = {0}; diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.h b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h index 2dc469c45cc..e8cbd7a37d3 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.h +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h @@ -10,6 +10,11 @@ extern "C" { #endif +void render_section_header( + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt); bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result); #ifdef __cplusplus diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h index bf0e926ee67..790fa576644 100644 --- a/applications/main/nfc/api/nfc_app_api_table_i.h +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -15,4 +15,11 @@ static constexpr auto nfc_app_api_table = sort(create_array_t( API_METHOD( mosgortrans_parse_transport_block, bool, - (const MfClassicBlock* block, FuriString* result)))); + (const MfClassicBlock* block, FuriString* result)), + API_METHOD( + render_section_header, + void, + (FuriString * str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt)))); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 898434bb78d..180be622430 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -92,6 +92,15 @@ App( sources=["plugins/supported_cards/troika.c"], ) +App( + appid="social_moscow_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="social_moscow_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/social_moscow.c"], +) + App( appid="washcity_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/social_moscow.c b/applications/main/nfc/plugins/supported_cards/social_moscow.c new file mode 100644 index 00000000000..ed2ee6c1d4e --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/social_moscow.c @@ -0,0 +1,301 @@ +#include "nfc_supported_card_plugin.h" +#include + +#include + +#include +#include +#include +#include "../../api/mosgortrans/mosgortrans_util.h" +#include "furi_hal_rtc.h" + +#define TAG "Social_Moscow" + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + const MfClassicKeyPair* keys; + uint32_t data_sector; +} SocialMoscowCardConfig; + +static const MfClassicKeyPair social_moscow_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, + {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, + {.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, + {.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, + {.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, + {.a = 0x40ead80721ce, .b = 0x100533b89331}, + {.a = 0x0db5e6523f7c, .b = 0x653a87594079}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}}; + +static const MfClassicKeyPair social_moscow_4k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //1 + {.a = 0x2735fc181807, .b = 0xbf23a53c1f63}, //2 + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //3 + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //4 + {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, //5 + {.a = 0x186d8c4b93f9, .b = 0x9f131d8c2057}, //6 + {.a = 0x3a4bba8adaf0, .b = 0x67362d90f973}, //7 + {.a = 0x8765b17968a2, .b = 0x6202a38f69e2}, //8 + {.a = 0x40ead80721ce, .b = 0x100533b89331}, //9 + {.a = 0x0db5e6523f7c, .b = 0x653a87594079}, //10 + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //11 + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //12 + {.a = 0x51119dae5216, .b = 0xd8a274b2e026}, //13 + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //14 + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //15 + {.a = 0xa0a1a2a3a4a5, .b = 0x7de02a7f6025}, //16 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //17 + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //18 + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //19 + {.a = 0x2aba9519f574, .b = 0xcb9a1f2d7368}, //20 + {.a = 0x84fd7f7a12b6, .b = 0xc7c0adb3284f}, //21 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //22 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //23 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //24 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //25 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //26 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //27 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //28 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //29 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //30 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //31 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //32 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //33 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //34 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //35 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //36 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //37 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //38 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //39 + {.a = 0xa229e68ad9e5, .b = 0x49c2b5296ef4}, //40 +}; + +static bool social_moscow_get_card_config(SocialMoscowCardConfig* config, MfClassicType type) { + bool success = true; + if(type == MfClassicType1k) { + config->data_sector = 15; + config->keys = social_moscow_1k_keys; + } else if(type == MfClassicType4k) { + config->data_sector = 15; + config->keys = social_moscow_4k_keys; + } else { + success = false; + } + + return success; +} + +static bool social_moscow_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector); + + MfClassicKey key = {0}; + bit_lib_num_to_bytes_be(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + break; + } + FURI_LOG_D(TAG, "Verify success!"); + verified = true; + } while(false); + + return verified; +} + +static bool social_moscow_verify(Nfc* nfc) { + return social_moscow_verify_type(nfc, MfClassicType1k) || + social_moscow_verify_type(nfc, MfClassicType4k); +} + +static bool social_moscow_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType4k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, data->type)) break; + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + bit_lib_num_to_bytes_be(cfg.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"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = (error == MfClassicErrorNone); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static uint8_t calculate_luhn(uint64_t number) { + // https://en.wikipedia.org/wiki/Luhn_algorithm + // Drop existing check digit to form payload + uint64_t payload = number / 10; + int sum = 0; + int position = 0; + + while(payload > 0) { + int digit = payload % 10; + if(position % 2 == 0) { + digit *= 2; + } + if(digit > 9) { + digit = (digit / 10) + (digit % 10); + } + sum += digit; + payload /= 10; + position++; + } + + return (10 - (sum % 10)) % 10; +} + +static uint64_t hex_num(uint64_t hex) { + uint64_t result = 0; + for(uint8_t i = 0; i < 8; ++i) { + uint8_t half_byte = hex & 0x0F; + uint64_t num = 0; + for(uint8_t j = 0; j < 4; ++j) { + num += (half_byte & 0x1) * (1 << j); + half_byte = half_byte >> 1; + } + result += num * pow(10, i); + hex = hex >> 4; + } + return result; +} + +static bool social_moscow_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + SocialMoscowCardConfig cfg = {}; + if(!social_moscow_get_card_config(&cfg, data->type)) break; + + // Verify key + const MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); + + const uint64_t key_a = + bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + const uint64_t key_b = + bit_lib_bytes_to_num_be(sec_tr->key_b.data, COUNT_OF(sec_tr->key_b.data)); + if((key_a != cfg.keys[cfg.data_sector].a) || (key_b != cfg.keys[cfg.data_sector].b)) break; + + uint32_t card_code = bit_lib_get_bits_32(data->block[60].data, 8, 24); + uint8_t card_region = bit_lib_get_bits(data->block[60].data, 32, 8); + uint64_t card_number = bit_lib_get_bits_64(data->block[60].data, 40, 40); + uint8_t card_control = bit_lib_get_bits(data->block[60].data, 80, 4); + uint64_t omc_number = bit_lib_get_bits_64(data->block[21].data, 8, 64); + uint8_t year = data->block[60].data[11]; + uint8_t month = data->block[60].data[12]; + + uint64_t number = hex_num(card_control) + hex_num(card_number) * 10 + + hex_num(card_region) * 10 * 10000000000 + + hex_num(card_code) * 10 * 10000000000 * 100; + + uint8_t luhn = calculate_luhn(number); + if(luhn != card_control) break; + + FuriString* metro_result = furi_string_alloc(); + FuriString* ground_result = furi_string_alloc(); + bool is_metro_data_present = + mosgortrans_parse_transport_block(&data->block[4], metro_result); + bool is_ground_data_present = + mosgortrans_parse_transport_block(&data->block[16], ground_result); + furi_string_cat_printf( + parsed_data, + "\e#Social \ecard\nNumber: %lx %x %llx %x\nOMC: %llx\nValid for: %02x/%02x %02x%02x\n", + card_code, + card_region, + card_number, + card_control, + omc_number, + month, + year, + data->block[60].data[13], + data->block[60].data[14]); + if(is_metro_data_present && !furi_string_empty(metro_result)) { + render_section_header(parsed_data, "Metro", 22, 21); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result)); + } + if(is_ground_data_present && !furi_string_empty(ground_result)) { + render_section_header(parsed_data, "Ground", 21, 20); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result)); + } + furi_string_free(ground_result); + furi_string_free(metro_result); + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin social_moscow_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = social_moscow_verify, + .read = social_moscow_read, + .parse = social_moscow_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor social_moscow_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &social_moscow_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* social_moscow_plugin_ep() { + return &social_moscow_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 0c93fa59ae0..bd36d40e5bd 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -82,20 +82,6 @@ static const MfClassicKeyPair troika_4k_keys[] = { {.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40 }; -static void troika_render_section_header( - FuriString* str, - const char* name, - uint8_t prefix_separator_cnt, - uint8_t suffix_separator_cnt) { - for(uint8_t i = 0; i < prefix_separator_cnt; i++) { - furi_string_cat_printf(str, ":"); - } - furi_string_cat_printf(str, "[ %s ]", name); - for(uint8_t i = 0; i < suffix_separator_cnt; i++) { - furi_string_cat_printf(str, ":"); - } -} - static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { bool success = true; @@ -212,23 +198,25 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { FuriString* ground_result = furi_string_alloc(); FuriString* tat_result = furi_string_alloc(); - bool result1 = mosgortrans_parse_transport_block(&data->block[32], metro_result); - bool result2 = mosgortrans_parse_transport_block(&data->block[28], ground_result); - bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result); + bool is_metro_data_present = + mosgortrans_parse_transport_block(&data->block[32], metro_result); + bool is_ground_data_present = + mosgortrans_parse_transport_block(&data->block[28], ground_result); + bool is_tat_data_present = mosgortrans_parse_transport_block(&data->block[16], tat_result); furi_string_cat_printf(parsed_data, "\e#Troyka card\n"); - if(result1 && !furi_string_empty(metro_result)) { - troika_render_section_header(parsed_data, "Metro", 22, 21); + if(is_metro_data_present && !furi_string_empty(metro_result)) { + render_section_header(parsed_data, "Metro", 22, 21); furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result)); } - if(result2 && !furi_string_empty(ground_result)) { - troika_render_section_header(parsed_data, "Ediny", 22, 22); + if(is_ground_data_present && !furi_string_empty(ground_result)) { + render_section_header(parsed_data, "Ediny", 22, 22); furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result)); } - if(result3 && !furi_string_empty(tat_result)) { - troika_render_section_header(parsed_data, "TAT", 24, 23); + if(is_tat_data_present && !furi_string_empty(tat_result)) { + render_section_header(parsed_data, "TAT", 24, 23); furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tat_result)); } @@ -236,7 +224,7 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_free(ground_result); furi_string_free(metro_result); - parsed = result1 || result2 || result3; + parsed = is_metro_data_present || is_ground_data_present || is_tat_data_present; } while(false); return parsed; From c3dc0ae6b985d4420b94376bebdd34a6dd90fbc8 Mon Sep 17 00:00:00 2001 From: assasinfil Date: Sun, 6 Oct 2024 19:48:12 +0300 Subject: [PATCH 12/36] Plantain parser improvements (#3469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactored card nubmer and balance * Podorozhnik refactor * Balance fix and BSK card support added Co-authored-by: あく --- .../nfc/plugins/supported_cards/plantain.c | 112 +++++++++++++++--- 1 file changed, 93 insertions(+), 19 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index bed96455467..59253194eed 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -4,10 +4,21 @@ #include #include +#include #include #define TAG "Plantain" +void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t start_year) { + uint32_t timestamp = minutes * 60; + DateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += datetime_datetime_to_timestamp(&start_datetime); + datetime_timestamp_to_datetime(timestamp, datetime); +} + typedef struct { uint64_t a; uint64_t b; @@ -208,29 +219,92 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; - // Point to block 0 of sector 4, value 0 - const uint8_t* temp_ptr = data->block[16].data; - // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t - // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal - uint32_t balance = - ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; - // Read card number - // Point to block 0 of sector 0, value 0 - temp_ptr = data->block[0].data; - // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t - // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal - uint8_t card_number_arr[7]; - for(size_t i = 0; i < 7; i++) { - card_number_arr[i] = temp_ptr[6 - i]; - } - // Copy card number to uint64_t + furi_string_printf(parsed_data, "\e#Plantain card\n"); uint64_t card_number = 0; for(size_t i = 0; i < 7; i++) { - card_number = (card_number << 8) | card_number_arr[i]; + card_number = (card_number << 8) | data->block[0].data[6 - i]; } - furi_string_printf( - parsed_data, "\e#Plantain\nNo.: %lluX\nBalance: %lu\n", card_number, balance); + // Print card number with 4-digit groups + furi_string_cat_printf(parsed_data, "Number: "); + FuriString* card_number_s = furi_string_alloc(); + furi_string_cat_printf(card_number_s, "%lld", card_number); + FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 "); + for(uint8_t i = 0; i < 24; i += 4) { + for(uint8_t j = 0; j < 4; j++) { + furi_string_push_back(tmp_s, furi_string_get_char(card_number_s, i + j)); + } + furi_string_push_back(tmp_s, ' '); + } + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s)); + if(data->type == MfClassicType1k) { + //balance + uint32_t balance = 0; + for(uint8_t i = 0; i < 4; i++) { + balance = (balance << 8) | data->block[16].data[3 - i]; + } + furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100); + + //trips + uint8_t trips_metro = data->block[21].data[0]; + uint8_t trips_ground = data->block[21].data[1]; + furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground); + //trip time + uint32_t last_trip_timestamp = 0; + for(uint8_t i = 0; i < 3; i++) { + last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i]; + } + DateTime last_trip = {0}; + from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010); + furi_string_cat_printf( + parsed_data, + "Trip start: %02d.%02d.%04d %02d:%02d\n", + last_trip.day, + last_trip.month, + last_trip.year, + last_trip.hour, + last_trip.minute); + //validator + uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4]; + furi_string_cat_printf(parsed_data, "Validator: %d\n", validator); + //tariff + uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6]; + furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100); + //trips in metro + furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro); + //trips on ground + furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground); + //last payment + uint32_t last_payment_timestamp = 0; + for(uint8_t i = 0; i < 3; i++) { + last_payment_timestamp = (last_payment_timestamp << 8) | + data->block[18].data[4 - i]; + } + DateTime last_payment_date = {0}; + from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010); + furi_string_cat_printf( + parsed_data, + "Last pay: %02d.%02d.%04d %02d:%02d\n", + last_payment_date.day, + last_payment_date.month, + last_payment_date.year, + last_payment_date.hour, + last_payment_date.minute); + //payment summ + uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8]; + furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100); + furi_string_free(card_number_s); + furi_string_free(tmp_s); + } else if(data->type == MfClassicType4k) { + //trips + uint8_t trips_metro = data->block[36].data[0]; + uint8_t trips_ground = data->block[36].data[1]; + furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground); + //trips in metro + furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro); + //trips on ground + furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground); + } parsed = true; } while(false); From 8c14361e6ae213f8f827a44acf69d6e1d52d5e70 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:55:13 +0300 Subject: [PATCH 13/36] [FL-3830] Emulation freeze (#3930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../nfc/helpers/protocol_support/nfc_protocol_support.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 7a07404fdc1..0d63dc56bb2 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -559,6 +559,7 @@ static void nfc_protocol_support_scene_save_name_on_exit(NfcApp* instance) { */ enum { NfcSceneEmulateStateWidget, /**< Widget view is displayed. */ + NfcSceneEmulateStateWidgetLog, /**< Widget view with Log button is displayed */ NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */ }; @@ -633,12 +634,14 @@ static bool "Log", nfc_protocol_support_common_widget_callback, instance); + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog); } // Update TextBox data text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); consumed = true; } else if(event.event == GuiButtonTypeCenter) { - if(state == NfcSceneEmulateStateWidget) { + if(state == NfcSceneEmulateStateWidgetLog) { view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); scene_manager_set_scene_state( instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox); @@ -649,7 +652,7 @@ static bool if(state == NfcSceneEmulateStateTextBox) { view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); scene_manager_set_scene_state( - instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget); + instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidgetLog); consumed = true; } } From 0469ef0e55295bd19be82b9be32dd45c11ffd542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Sun, 6 Oct 2024 19:36:05 +0100 Subject: [PATCH 14/36] FuriHal, drivers: rework gauge initialization routine (#3912) * FuriHal, drivers: rework gauge initialization, ensure that we can recover from any kind of internal/external issue * Make PVS happy * Format sources * bq27220: add gaps injection into write operations * Drivers: bq27220 cleanup and various fixes * Drivers: bq27220 verbose logging and full access routine fix * Drivers: better cfg mode exit handling in bq27220 driver * Drivers: rewrite bq27220 based on bqstudio+ev2400, experiments and guessing. Fixes all known issues. * PVS: hello license check * Drivers: minimize reset count in bq27220 init sequence * Drivers: bq27220 hide debug logging, reorganize routine to ensure predictable result and minimum amount of interaction with gauge, add documentation and notes. * Drivers: more reliable bq27220_full_access routine * Drivers: replace some warning with error in bq27220 * Drivers: move static asserts to headers in bq27220 * Fix PVS warnings * Drivers: simplify logic in bq27220 --------- Co-authored-by: hedger --- .../nfc/plugins/supported_cards/plantain.c | 2 +- lib/drivers/bq27220.c | 468 +++++++++++++++--- lib/drivers/bq27220.h | 267 ++++++++-- lib/drivers/bq27220_data_memory.h | 2 + lib/drivers/bq27220_reg.h | 136 ++--- scripts/fbt_tools/pvsstudio.py | 2 +- targets/f7/furi_hal/furi_hal_power.c | 24 +- 7 files changed, 698 insertions(+), 203 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 59253194eed..c38140de272 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -228,7 +228,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { // Print card number with 4-digit groups furi_string_cat_printf(parsed_data, "Number: "); FuriString* card_number_s = furi_string_alloc(); - furi_string_cat_printf(card_number_s, "%lld", card_number); + furi_string_cat_printf(card_number_s, "%llu", card_number); FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 "); for(uint8_t i = 0; i < 24; i += 4) { for(uint8_t j = 0; j < 4; j++) { diff --git a/lib/drivers/bq27220.c b/lib/drivers/bq27220.c index a3a88603d72..d60e287da47 100644 --- a/lib/drivers/bq27220.c +++ b/lib/drivers/bq27220.c @@ -1,29 +1,77 @@ - #include "bq27220.h" #include "bq27220_reg.h" #include "bq27220_data_memory.h" -_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size"); - #include #include #define TAG "Gauge" -static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { - uint16_t buf = 0; +#define BQ27220_ID (0x0220u) - furi_hal_i2c_read_mem( - handle, BQ27220_ADDRESS, address, (uint8_t*)&buf, 2, BQ27220_I2C_TIMEOUT); +/** Delay between 2 writes into Subclass/MAC area. Fails at ~120us. */ +#define BQ27220_MAC_WRITE_DELAY_US (250u) - return buf; +/** Delay between we ask chip to load data to MAC and it become valid. Fails at ~500us. */ +#define BQ27220_SELECT_DELAY_US (1000u) + +/** Delay between 2 control operations(like unseal or full access). Fails at ~2500us.*/ +#define BQ27220_MAGIC_DELAY_US (5000u) + +/** Delay before freshly written configuration can be read. Fails at ? */ +#define BQ27220_CONFIG_DELAY_US (10000u) + +/** Config apply delay. Must wait, or DM read returns garbage. */ +#define BQ27220_CONFIG_APPLY_US (2000000u) + +/** Timeout for common operations. */ +#define BQ27220_TIMEOUT_COMMON_US (2000000u) + +/** Timeout for reset operation. Normally reset takes ~2s. */ +#define BQ27220_TIMEOUT_RESET_US (4000000u) + +/** Timeout cycle interval */ +#define BQ27220_TIMEOUT_CYCLE_INTERVAL_US (1000u) + +/** Timeout cycles count helper */ +#define BQ27220_TIMEOUT(timeout_us) ((timeout_us) / (BQ27220_TIMEOUT_CYCLE_INTERVAL_US)) + +#ifdef BQ27220_DEBUG +#define BQ27220_DEBUG_LOG(...) FURI_LOG_D(TAG, ##__VA_ARGS__) +#else +#define BQ27220_DEBUG_LOG(...) +#endif + +static inline bool bq27220_read_reg( + FuriHalI2cBusHandle* handle, + uint8_t address, + uint8_t* buffer, + size_t buffer_size) { + return furi_hal_i2c_trx( + handle, BQ27220_ADDRESS, &address, 1, buffer, buffer_size, BQ27220_I2C_TIMEOUT); } -static bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) { - bool ret = furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandControl, (uint8_t*)&control, 2, BQ27220_I2C_TIMEOUT); +static inline bool bq27220_write( + FuriHalI2cBusHandle* handle, + uint8_t address, + const uint8_t* buffer, + size_t buffer_size) { + return furi_hal_i2c_write_mem( + handle, BQ27220_ADDRESS, address, buffer, buffer_size, BQ27220_I2C_TIMEOUT); +} - return ret; +static inline bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) { + return bq27220_write(handle, CommandControl, (uint8_t*)&control, 2); +} + +static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { + uint16_t buf = BQ27220_ERROR; + + if(!bq27220_read_reg(handle, address, (uint8_t*)&buf, 2)) { + FURI_LOG_E(TAG, "bq27220_read_word failed"); + } + + return buf; } static uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) { @@ -56,49 +104,49 @@ static bool bq27220_parameter_check( if(update) { // Datasheet contains incorrect procedure for memory update, more info: // https://e2e.ti.com/support/power-management-group/power-management/f/power-management-forum/719878/bq27220-technical-reference-manual-sluubd4-is-missing-extended-data-commands-chapter + // Also see note in the header - // 2. Write the address AND the parameter data to 0x3E+ (auto increment) - if(!furi_hal_i2c_write_mem( - handle, - BQ27220_ADDRESS, - CommandSelectSubclass, - buffer, - size + 2, - BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM write failed"); + // Write the address AND the parameter data to 0x3E+ (auto increment) + if(!bq27220_write(handle, CommandSelectSubclass, buffer, size + 2)) { + FURI_LOG_E(TAG, "DM write failed"); break; } - furi_delay_us(10000); + // We must wait, otherwise write will fail + furi_delay_us(BQ27220_MAC_WRITE_DELAY_US); - // 3. Calculate the check sum: 0xFF - (sum of address and data) OR 0xFF + // Calculate the check sum: 0xFF - (sum of address and data) OR 0xFF uint8_t checksum = bq27220_get_checksum(buffer, size + 2); - // 4. Write the check sum to 0x60 and the total length of (address + parameter data + check sum + length) to 0x61 + // Write the check sum to 0x60 and the total length of (address + parameter data + check sum + length) to 0x61 buffer[0] = checksum; // 2 bytes address, `size` bytes data, 1 byte check sum, 1 byte length buffer[1] = 2 + size + 1 + 1; - if(!furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandMACDataSum, buffer, 2, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "CRC write failed"); + if(!bq27220_write(handle, CommandMACDataSum, buffer, 2)) { + FURI_LOG_E(TAG, "CRC write failed"); break; } - - furi_delay_us(10000); + // Final wait as in gm.fs specification + furi_delay_us(BQ27220_CONFIG_DELAY_US); ret = true; } else { - if(!furi_hal_i2c_write_mem( - handle, BQ27220_ADDRESS, CommandSelectSubclass, buffer, 2, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM SelectSubclass for read failed"); + if(!bq27220_write(handle, CommandSelectSubclass, buffer, 2)) { + FURI_LOG_E(TAG, "DM SelectSubclass for read failed"); break; } - if(!furi_hal_i2c_rx(handle, BQ27220_ADDRESS, old_data, size, BQ27220_I2C_TIMEOUT)) { - FURI_LOG_I(TAG, "DM read failed"); + // bqstudio uses 15ms wait delay here + furi_delay_us(BQ27220_SELECT_DELAY_US); + + if(!bq27220_read_reg(handle, CommandMACData, old_data, size)) { + FURI_LOG_E(TAG, "DM read failed"); break; } + // bqstudio uses burst reads with continue(CommandSelectSubclass without argument) and ~5ms between burst + furi_delay_us(BQ27220_SELECT_DELAY_US); + if(*(uint32_t*)&(old_data[0]) != *(uint32_t*)&(buffer[2])) { - FURI_LOG_W( //-V641 + FURI_LOG_E( //-V641 TAG, "Data at 0x%04x(%zu): 0x%08lx!=0x%08lx", address, @@ -119,22 +167,34 @@ static bool bq27220_data_memory_check( const BQ27220DMData* data_memory, bool update) { if(update) { - if(!bq27220_control(handle, Control_ENTER_CFG_UPDATE)) { + const uint16_t cfg_request = Control_ENTER_CFG_UPDATE; + if(!bq27220_write( + handle, CommandSelectSubclass, (uint8_t*)&cfg_request, sizeof(cfg_request))) { FURI_LOG_E(TAG, "ENTER_CFG_UPDATE command failed"); return false; }; // Wait for enter CFG update mode - uint32_t timeout = 100; - OperationStatus status = {0}; - while((status.CFGUPDATE != true) && (timeout-- > 0)) { - bq27220_get_operation_status(handle, &status); + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else if(operation_status.CFGUPDATE) { + break; + }; + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); } if(timeout == 0) { - FURI_LOG_E(TAG, "CFGUPDATE mode failed"); + FURI_LOG_E( + TAG, + "Enter CFGUPDATE mode failed, CFG %u, SEC %u", + operation_status.CFGUPDATE, + operation_status.SEC); return false; } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); } // Process data memory records @@ -179,43 +239,283 @@ static bool bq27220_data_memory_check( } // Finalize configuration update - if(update) { + if(update && result) { bq27220_control(handle, Control_EXIT_CFG_UPDATE_REINIT); - furi_delay_us(10000); + + // Wait for gauge to apply new configuration + furi_delay_us(BQ27220_CONFIG_APPLY_US); + + // ensure that we exited config update mode + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else if(operation_status.CFGUPDATE != true) { + break; + } + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); + } + + // Check timeout + if(timeout == 0) { + FURI_LOG_E(TAG, "Exit CFGUPDATE mode failed"); + return false; + } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); } return result; } -bool bq27220_init(FuriHalI2cBusHandle* handle) { - // Request device number(chip PN) - if(!bq27220_control(handle, Control_DEVICE_NUMBER)) { - FURI_LOG_E(TAG, "Device is not present"); - return false; - }; - // Check control response - uint16_t data = 0; - data = bq27220_read_word(handle, CommandControl); - if(data != 0xFF00) { - FURI_LOG_E(TAG, "Invalid control response: %x", data); - return false; - }; +bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { + bool result = false; + bool reset_and_provisioning_required = false; + + do { + // Request device number(chip PN) + BQ27220_DEBUG_LOG("Checking device ID"); + if(!bq27220_control(handle, Control_DEVICE_NUMBER)) { + FURI_LOG_E(TAG, "ID: Device is not responding"); + break; + }; + // Enterprise wait(MAC read fails if less than 500us) + // bqstudio uses ~15ms + furi_delay_us(BQ27220_SELECT_DELAY_US); + // Read id data from MAC scratch space + uint16_t data = bq27220_read_word(handle, CommandMACData); + if(data != BQ27220_ID) { + FURI_LOG_E(TAG, "Invalid Device Number %04x != 0x0220", data); + break; + } + + // Unseal device since we are going to read protected configuration + BQ27220_DEBUG_LOG("Unsealing"); + if(!bq27220_unseal(handle)) { + break; + } + + // Try to recover gauge from forever init + BQ27220_DEBUG_LOG("Checking initialization status"); + Bq27220OperationStatus operation_status; + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Failed to get operation status"); + break; + } + if(!operation_status.INITCOMP || operation_status.CFGUPDATE) { + FURI_LOG_E(TAG, "Incorrect state, reset needed"); + reset_and_provisioning_required = true; + } + + // Ensure correct profile is selected + BQ27220_DEBUG_LOG("Checking chosen profile"); + Bq27220ControlStatus control_status; + if(!bq27220_get_control_status(handle, &control_status)) { + FURI_LOG_E(TAG, "Failed to get control status"); + break; + } + if(control_status.BATT_ID != 0) { + FURI_LOG_E(TAG, "Incorrect profile, reset needed"); + reset_and_provisioning_required = true; + } - data = bq27220_read_word(handle, CommandMACData); - FURI_LOG_I(TAG, "Device Number %04x", data); + // Ensure correct configuration loaded into gauge DataMemory + // Only if reset is not required, otherwise we don't + if(!reset_and_provisioning_required) { + BQ27220_DEBUG_LOG("Checking data memory"); + if(!bq27220_data_memory_check(handle, data_memory, false)) { + FURI_LOG_E(TAG, "Incorrect configuration data, reset needed"); + reset_and_provisioning_required = true; + } + } + + // Reset needed + if(reset_and_provisioning_required) { + FURI_LOG_W(TAG, "Resetting device"); + if(!bq27220_reset(handle)) { + FURI_LOG_E(TAG, "Failed to reset device"); + break; + } - return data == 0x0220; + // Get full access to read and modify parameters + // Also it looks like this step is totally unnecessary + BQ27220_DEBUG_LOG("Acquiring Full Access"); + if(!bq27220_full_access(handle)) { + break; + } + + // Update memory + FURI_LOG_W(TAG, "Updating data memory"); + bq27220_data_memory_check(handle, data_memory, true); + if(!bq27220_data_memory_check(handle, data_memory, false)) { + FURI_LOG_E(TAG, "Data memory update failed"); + break; + } + } + + BQ27220_DEBUG_LOG("Sealing"); + if(!bq27220_seal(handle)) { + FURI_LOG_E(TAG, "Seal failed"); + break; + } + + result = true; + } while(0); + + return result; } -bool bq27220_apply_data_memory(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { - FURI_LOG_I(TAG, "Verifying data memory"); - if(!bq27220_data_memory_check(handle, data_memory, false)) { - FURI_LOG_I(TAG, "Updating data memory"); - bq27220_data_memory_check(handle, data_memory, true); - } - FURI_LOG_I(TAG, "Data memory verification complete"); +bool bq27220_reset(FuriHalI2cBusHandle* handle) { + bool result = false; + do { + if(!bq27220_control(handle, Control_RESET)) { + FURI_LOG_E(TAG, "Reset request failed"); + break; + }; + + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_RESET_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else if(operation_status.INITCOMP == true) { + break; + }; + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); + } + + if(timeout == 0) { + FURI_LOG_E(TAG, "INITCOMP timeout after reset"); + break; + } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); + + result = true; + } while(0); + + return result; +} + +bool bq27220_seal(FuriHalI2cBusHandle* handle) { + Bq27220OperationStatus operation_status = {0}; + bool result = false; + do { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC == Bq27220OperationStatusSecSealed) { + result = true; + break; + } + + if(!bq27220_control(handle, Control_SEALED)) { + FURI_LOG_E(TAG, "Seal request failed"); + break; + } + + furi_delay_us(BQ27220_SELECT_DELAY_US); + + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecSealed) { + FURI_LOG_E(TAG, "Seal failed"); + break; + } + + result = true; + } while(0); + + return result; +} + +bool bq27220_unseal(FuriHalI2cBusHandle* handle) { + Bq27220OperationStatus operation_status = {0}; + bool result = false; + do { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecSealed) { + result = true; + break; + } + + // Hai, Kazuma desu + bq27220_control(handle, UnsealKey1); + furi_delay_us(BQ27220_MAGIC_DELAY_US); + bq27220_control(handle, UnsealKey2); + furi_delay_us(BQ27220_MAGIC_DELAY_US); - return true; + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecUnsealed) { + FURI_LOG_E(TAG, "Unseal failed %u", operation_status.SEC); + break; + } + + result = true; + } while(0); + + return result; +} + +bool bq27220_full_access(FuriHalI2cBusHandle* handle) { + bool result = false; + + do { + uint32_t timeout = BQ27220_TIMEOUT(BQ27220_TIMEOUT_COMMON_US); + Bq27220OperationStatus operation_status; + while(--timeout > 0) { + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_W(TAG, "Failed to get operation status, retries left %lu", timeout); + } else { + break; + }; + furi_delay_us(BQ27220_TIMEOUT_CYCLE_INTERVAL_US); + } + + if(timeout == 0) { + FURI_LOG_E(TAG, "Failed to get operation status"); + break; + } + BQ27220_DEBUG_LOG("Cycles left: %lu", timeout); + + // Already full access + if(operation_status.SEC == Bq27220OperationStatusSecFull) { + result = true; + break; + } + // Must be unsealed to get full access + if(operation_status.SEC != Bq27220OperationStatusSecUnsealed) { + FURI_LOG_E(TAG, "Not in unsealed state"); + break; + } + + // Explosion!!! + bq27220_control(handle, FullAccessKey); //-V760 + furi_delay_us(BQ27220_MAGIC_DELAY_US); + bq27220_control(handle, FullAccessKey); + furi_delay_us(BQ27220_MAGIC_DELAY_US); + + if(!bq27220_get_operation_status(handle, &operation_status)) { + FURI_LOG_E(TAG, "Status query failed"); + break; + } + if(operation_status.SEC != Bq27220OperationStatusSecFull) { + FURI_LOG_E(TAG, "Full access failed %u", operation_status.SEC); + break; + } + + result = true; + } while(0); + + return result; } uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle) { @@ -226,24 +526,30 @@ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandCurrent); } -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status) { - uint16_t data = bq27220_read_word(handle, CommandBatteryStatus); - if(data == BQ27220_ERROR) { - return false; - } else { - *(uint16_t*)battery_status = data; - return true; - } +bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status) { + return bq27220_read_reg(handle, CommandControl, (uint8_t*)control_status, 2); +} + +bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status) { + return bq27220_read_reg(handle, CommandBatteryStatus, (uint8_t*)battery_status, 2); +} + +bool bq27220_get_operation_status( + FuriHalI2cBusHandle* handle, + Bq27220OperationStatus* operation_status) { + return bq27220_read_reg(handle, CommandOperationStatus, (uint8_t*)operation_status, 2); } -bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status) { - uint16_t data = bq27220_read_word(handle, CommandOperationStatus); - if(data == BQ27220_ERROR) { +bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status) { + // Request gauging data to be loaded to MAC + if(!bq27220_control(handle, Control_GAUGING_STATUS)) { + FURI_LOG_E(TAG, "DM SelectSubclass for read failed"); return false; - } else { - *(uint16_t*)operation_status = data; - return true; } + // Wait for data being loaded to MAC + furi_delay_us(BQ27220_SELECT_DELAY_US); + // Read id data from MAC scratch space + return bq27220_read_reg(handle, CommandMACData, (uint8_t*)gauging_status, 2); } uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle) { diff --git a/lib/drivers/bq27220.h b/lib/drivers/bq27220.h index fc76e318f82..cdfcb20b1ac 100644 --- a/lib/drivers/bq27220.h +++ b/lib/drivers/bq27220.h @@ -1,3 +1,31 @@ +/** + * @file bq27220.h + * + * Quite problematic chip with quite bad documentation. + * + * Couple things to keep in mind: + * + * - Datasheet and technical reference manual are full of bullshit + * - bqstudio is ignoring them + * - bqstudio i2c exchange tracing gives some ideas on timings that works, but there is a catch + * - bqstudio timings contradicts to gm.fs file specification + * - it's impossible to reproduce all situations in bqstudio + * - experiments with blackbox can not cover all edge cases + * - final timings are kinda blend between all of those sources + * - device behavior differs depending on i2c clock speed + * - The Hero Himmel would not have used this gauge in the first place + * + * Couple advises if you'll need to modify this driver: + * - Reset and wait for INITCOMP if something is not right. + * - Do not do partial config update, it takes unpredictable amount of time to apply. + * - Don't forget to reset chip before writing new config. + * - If something fails at config update stage, wait for 4 seconds before doing next cycle. + * - If you can program and lock chip at factory stage - do it. It will save you a lot of time. + * - Keep sealed or strange things may happen. + * - There is a condition when it may stuck at INITCOMP state, just "press reset button". + * + */ + #pragma once #include @@ -9,26 +37,45 @@ typedef struct { // Low byte, Low bit first - bool DSG : 1; // The device is in DISCHARGE - bool SYSDWN : 1; // System down bit indicating the system should shut down - bool TDA : 1; // Terminate Discharge Alarm - bool BATTPRES : 1; // Battery Present detected - bool AUTH_GD : 1; // Detect inserted battery - bool OCVGD : 1; // Good OCV measurement taken - bool TCA : 1; // Terminate Charge Alarm - bool RSVD : 1; // Reserved + uint8_t BATT_ID : 3; /**< Battery Identification */ + bool SNOOZE : 1; /**< SNOOZE mode is enabled */ + bool BCA : 1; /**< fuel gauge board calibration routine is active */ + bool CCA : 1; /**< Coulomb Counter Calibration routine is active */ + uint8_t RSVD0 : 2; /**< Reserved */ // High byte, Low bit first - bool CHGINH : 1; // Charge inhibit - bool FC : 1; // Full-charged is detected - bool OTD : 1; // Overtemperature in discharge condition is detected - bool OTC : 1; // Overtemperature in charge condition is detected - bool SLEEP : 1; // Device is operating in SLEEP mode when set - bool OCVFAIL : 1; // Status bit indicating that the OCV reading failed due to current - bool OCVCOMP : 1; // An OCV measurement update is complete - bool FD : 1; // Full-discharge is detected -} BatteryStatus; - -_Static_assert(sizeof(BatteryStatus) == 2, "Incorrect structure size"); + uint8_t RSVD1; /**< Reserved */ +} Bq27220ControlStatus; + +_Static_assert(sizeof(Bq27220ControlStatus) == 2, "Incorrect Bq27220ControlStatus structure size"); + +typedef struct { + // Low byte, Low bit first + bool DSG : 1; /**< The device is in DISCHARGE */ + bool SYSDWN : 1; /**< System down bit indicating the system should shut down */ + bool TDA : 1; /**< Terminate Discharge Alarm */ + bool BATTPRES : 1; /**< Battery Present detected */ + bool AUTH_GD : 1; /**< Detect inserted battery */ + bool OCVGD : 1; /**< Good OCV measurement taken */ + bool TCA : 1; /**< Terminate Charge Alarm */ + bool RSVD : 1; /**< Reserved */ + // High byte, Low bit first + bool CHGINH : 1; /**< Charge inhibit */ + bool FC : 1; /**< Full-charged is detected */ + bool OTD : 1; /**< Overtemperature in discharge condition is detected */ + bool OTC : 1; /**< Overtemperature in charge condition is detected */ + bool SLEEP : 1; /**< Device is operating in SLEEP mode when set */ + bool OCVFAIL : 1; /**< Status bit indicating that the OCV reading failed due to current */ + bool OCVCOMP : 1; /**< An OCV measurement update is complete */ + bool FD : 1; /**< Full-discharge is detected */ +} Bq27220BatteryStatus; + +_Static_assert(sizeof(Bq27220BatteryStatus) == 2, "Incorrect Bq27220BatteryStatus structure size"); + +typedef enum { + Bq27220OperationStatusSecSealed = 0b11, + Bq27220OperationStatusSecUnsealed = 0b10, + Bq27220OperationStatusSecFull = 0b01, +} Bq27220OperationStatusSec; typedef struct { // Low byte, Low bit first @@ -40,53 +87,189 @@ typedef struct { bool SMTH : 1; /**< RemainingCapacity is scaled by smooth engine */ bool BTPINT : 1; /**< BTP threshold has been crossed */ // High byte, Low bit first - uint8_t RSVD1 : 2; + uint8_t RSVD1 : 2; /**< Reserved */ bool CFGUPDATE : 1; /**< Gauge is in CONFIG UPDATE mode */ - uint8_t RSVD0 : 5; -} OperationStatus; + uint8_t RSVD0 : 5; /**< Reserved */ +} Bq27220OperationStatus; -_Static_assert(sizeof(OperationStatus) == 2, "Incorrect structure size"); +_Static_assert( + sizeof(Bq27220OperationStatus) == 2, + "Incorrect Bq27220OperationStatus structure size"); + +typedef struct { + // Low byte, Low bit first + bool FD : 1; /**< Full Discharge */ + bool FC : 1; /**< Full Charge */ + bool TD : 1; /**< Terminate Discharge */ + bool TC : 1; /**< Terminate Charge */ + bool RSVD0 : 1; /**< Reserved */ + bool EDV : 1; /**< Cell voltage is above or below EDV0 threshold */ + bool DSG : 1; /**< DISCHARGE or RELAXATION */ + bool CF : 1; /**< Battery conditioning is needed */ + // High byte, Low bit first + uint8_t RSVD1 : 2; /**< Reserved */ + bool FCCX : 1; /**< fcc1hz clock going into CC: 0 = 1 Hz, 1 = 16 Hz*/ + uint8_t RSVD2 : 2; /**< Reserved */ + bool EDV1 : 1; /**< Cell voltage is above or below EDV1 threshold */ + bool EDV2 : 1; /**< Cell voltage is above or below EDV2 threshold */ + bool VDQ : 1; /**< Charge cycle FCC update qualification */ +} Bq27220GaugingStatus; + +_Static_assert(sizeof(Bq27220GaugingStatus) == 2, "Incorrect Bq27220GaugingStatus structure size"); typedef struct BQ27220DMData BQ27220DMData; /** Initialize Driver - * @return true on success, false otherwise + * + * This routine performs a lot of things under the hood: + * - Verifies that gauge is present on i2c bus and got correct ID(0220) + * - Unseals gauge + * - Checks various internal statuses + * - Checks that current profile is 0 + * - Checks configuration again provided data_memory + * - Reset gauge if something on previous stages was fishy + * - Updates configuration if needed + * - Sealing gauge to prevent configuration and state from accidental damage + * + * @param handle The I2C Bus handle + * @param[in] data_memory The data memory to be uploaded into gauge + * + * @return true on success, false otherwise */ -bool bq27220_init(FuriHalI2cBusHandle* handle); +bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); -/** Initialize Driver - * @return true on success, false otherwise +/** Reset gauge + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise + */ +bool bq27220_reset(FuriHalI2cBusHandle* handle); + +/** Seal gauge access + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise + */ +bool bq27220_seal(FuriHalI2cBusHandle* handle); + +/** Unseal gauge access + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise + */ +bool bq27220_unseal(FuriHalI2cBusHandle* handle); + +/** Get full access + * + * @warning must be done in unsealed state + * + * @param handle The I2C Bus handle + * + * @return true on success, false otherwise */ -bool bq27220_apply_data_memory(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); +bool bq27220_full_access(FuriHalI2cBusHandle* handle); -/** Get battery voltage in mV or error */ +/** Get battery voltage + * + * @param handle The I2C Bus handle + * + * @return voltage in mV or BQ27220_ERROR + */ uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle); -/** Get current in mA or error*/ +/** Get current + * + * @param handle The I2C Bus handle + * + * @return current in mA or BQ27220_ERROR + */ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle); -/** Get battery status */ -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, BatteryStatus* battery_status); +/** Get control status + * + * @param handle The handle + * @param control_status The control status + * + * @return true on success, false otherwise + */ +bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status); + +/** Get battery status + * + * @param handle The handle + * @param battery_status The battery status + * + * @return true on success, false otherwise + */ +bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status); + +/** Get operation status + * + * @param handle The handle + * @param operation_status The operation status + * + * @return true on success, false otherwise + */ +bool bq27220_get_operation_status( + FuriHalI2cBusHandle* handle, + Bq27220OperationStatus* operation_status); -/** Get operation status */ -bool bq27220_get_operation_status(FuriHalI2cBusHandle* handle, OperationStatus* operation_status); +/** Get gauging status + * + * @param handle The handle + * @param gauging_status The gauging status + * + * @return true on success, false otherwise + */ +bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status); -/** Get temperature in units of 0.1°K */ +/** Get temperature + * + * @param handle The I2C Bus handle + * + * @return temperature in units of 0.1°K + */ uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle); -/** Get compensated full charge capacity in in mAh */ +/** Get compensated full charge capacity + * + * @param handle The I2C Bus handle + * + * @return full charge capacity in mAh or BQ27220_ERROR + */ uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle); -/** Get design capacity in mAh */ +/** Get design capacity + * + * @param handle The I2C Bus handle + * + * @return design capacity in mAh or BQ27220_ERROR + */ uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle); -/** Get remaining capacity in in mAh */ +/** Get remaining capacity + * + * @param handle The I2C Bus handle + * + * @return remaining capacity in mAh or BQ27220_ERROR + */ uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle); -/** Get predicted remaining battery capacity in percents */ +/** Get predicted remaining battery capacity + * + * @param handle The I2C Bus handle + * + * @return state of charge in percents or BQ27220_ERROR + */ uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle); -/** Get ratio of full charge capacity over design capacity in percents */ +/** Get ratio of full charge capacity over design capacity + * + * @param handle The I2C Bus handle + * + * @return state of health in percents or BQ27220_ERROR + */ uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle); - -void bq27220_change_design_capacity(FuriHalI2cBusHandle* handle, uint16_t capacity); diff --git a/lib/drivers/bq27220_data_memory.h b/lib/drivers/bq27220_data_memory.h index 30f2dae1ec6..0bd9348d2bb 100644 --- a/lib/drivers/bq27220_data_memory.h +++ b/lib/drivers/bq27220_data_memory.h @@ -82,3 +82,5 @@ typedef struct { const bool SME0 : 1; const uint8_t RSVD3 : 3; } BQ27220DMGaugingConfig; + +_Static_assert(sizeof(BQ27220DMGaugingConfig) == 2, "Incorrect structure size"); diff --git a/lib/drivers/bq27220_reg.h b/lib/drivers/bq27220_reg.h index 1c1ec9d8f90..2d93e31d0e8 100644 --- a/lib/drivers/bq27220_reg.h +++ b/lib/drivers/bq27220_reg.h @@ -1,68 +1,76 @@ #pragma once -#define BQ27220_ADDRESS 0xAA -#define BQ27220_I2C_TIMEOUT 50 +#define BQ27220_ADDRESS (0xAAu) +#define BQ27220_I2C_TIMEOUT (50u) -#define CommandControl 0x00 -#define CommandAtRate 0x02 -#define CommandAtRateTimeToEmpty 0x04 -#define CommandTemperature 0x06 -#define CommandVoltage 0x08 -#define CommandBatteryStatus 0x0A -#define CommandCurrent 0x0C -#define CommandRemainingCapacity 0x10 -#define CommandFullChargeCapacity 0x12 -#define CommandAverageCurrent 0x14 -#define CommandTimeToEmpty 0x16 -#define CommandTimeToFull 0x18 -#define CommandStandbyCurrent 0x1A -#define CommandStandbyTimeToEmpty 0x1C -#define CommandMaxLoadCurrent 0x1E -#define CommandMaxLoadTimeToEmpty 0x20 -#define CommandRawCoulombCount 0x22 -#define CommandAveragePower 0x24 -#define CommandInternalTemperature 0x28 -#define CommandCycleCount 0x2A -#define CommandStateOfCharge 0x2C -#define CommandStateOfHealth 0x2E -#define CommandChargeVoltage 0x30 -#define CommandChargeCurrent 0x32 -#define CommandBTPDischargeSet 0x34 -#define CommandBTPChargeSet 0x36 -#define CommandOperationStatus 0x3A -#define CommandDesignCapacity 0x3C -#define CommandSelectSubclass 0x3E -#define CommandMACData 0x40 -#define CommandMACDataSum 0x60 -#define CommandMACDataLen 0x61 -#define CommandAnalogCount 0x79 -#define CommandRawCurrent 0x7A -#define CommandRawVoltage 0x7C -#define CommandRawIntTemp 0x7E +#define CommandControl (0x00u) +#define CommandAtRate (0x02u) +#define CommandAtRateTimeToEmpty (0x04u) +#define CommandTemperature (0x06u) +#define CommandVoltage (0x08u) +#define CommandBatteryStatus (0x0Au) +#define CommandCurrent (0x0Cu) +#define CommandRemainingCapacity (0x10u) +#define CommandFullChargeCapacity (0x12u) +#define CommandAverageCurrent (0x14u) +#define CommandTimeToEmpty (0x16u) +#define CommandTimeToFull (0x18u) +#define CommandStandbyCurrent (0x1Au) +#define CommandStandbyTimeToEmpty (0x1Cu) +#define CommandMaxLoadCurrent (0x1Eu) +#define CommandMaxLoadTimeToEmpty (0x20u) +#define CommandRawCoulombCount (0x22u) +#define CommandAveragePower (0x24u) +#define CommandInternalTemperature (0x28u) +#define CommandCycleCount (0x2Au) +#define CommandStateOfCharge (0x2Cu) +#define CommandStateOfHealth (0x2Eu) +#define CommandChargeVoltage (0x30u) +#define CommandChargeCurrent (0x32u) +#define CommandBTPDischargeSet (0x34u) +#define CommandBTPChargeSet (0x36u) +#define CommandOperationStatus (0x3Au) +#define CommandDesignCapacity (0x3Cu) +#define CommandSelectSubclass (0x3Eu) +#define CommandMACData (0x40u) +#define CommandMACDataSum (0x60u) +#define CommandMACDataLen (0x61u) +#define CommandAnalogCount (0x79u) +#define CommandRawCurrent (0x7Au) +#define CommandRawVoltage (0x7Cu) +#define CommandRawIntTemp (0x7Eu) -#define Control_CONTROL_STATUS 0x0000 -#define Control_DEVICE_NUMBER 0x0001 -#define Control_FW_VERSION 0x0002 -#define Control_BOARD_OFFSET 0x0009 -#define Control_CC_OFFSET 0x000A -#define Control_CC_OFFSET_SAVE 0x000B -#define Control_OCV_CMD 0x000C -#define Control_BAT_INSERT 0x000D -#define Control_BAT_REMOVE 0x000E -#define Control_SET_SNOOZE 0x0013 -#define Control_CLEAR_SNOOZE 0x0014 -#define Control_SET_PROFILE_1 0x0015 -#define Control_SET_PROFILE_2 0x0016 -#define Control_SET_PROFILE_3 0x0017 -#define Control_SET_PROFILE_4 0x0018 -#define Control_SET_PROFILE_5 0x0019 -#define Control_SET_PROFILE_6 0x001A -#define Control_CAL_TOGGLE 0x002D -#define Control_SEALED 0x0030 -#define Control_RESET 0x0041 -#define Control_EXIT_CAL 0x0080 -#define Control_ENTER_CAL 0x0081 -#define Control_ENTER_CFG_UPDATE 0x0090 -#define Control_EXIT_CFG_UPDATE_REINIT 0x0091 -#define Control_EXIT_CFG_UPDATE 0x0092 -#define Control_RETURN_TO_ROM 0x0F00 +#define Control_CONTROL_STATUS (0x0000u) +#define Control_DEVICE_NUMBER (0x0001u) +#define Control_FW_VERSION (0x0002u) +#define Control_HW_VERSION (0x0003u) +#define Control_BOARD_OFFSET (0x0009u) +#define Control_CC_OFFSET (0x000Au) +#define Control_CC_OFFSET_SAVE (0x000Bu) +#define Control_OCV_CMD (0x000Cu) +#define Control_BAT_INSERT (0x000Du) +#define Control_BAT_REMOVE (0x000Eu) +#define Control_SET_SNOOZE (0x0013u) +#define Control_CLEAR_SNOOZE (0x0014u) +#define Control_SET_PROFILE_1 (0x0015u) +#define Control_SET_PROFILE_2 (0x0016u) +#define Control_SET_PROFILE_3 (0x0017u) +#define Control_SET_PROFILE_4 (0x0018u) +#define Control_SET_PROFILE_5 (0x0019u) +#define Control_SET_PROFILE_6 (0x001Au) +#define Control_CAL_TOGGLE (0x002Du) +#define Control_SEALED (0x0030u) +#define Control_RESET (0x0041u) +#define Control_OERATION_STATUS (0x0054u) +#define Control_GAUGING_STATUS (0x0056u) +#define Control_EXIT_CAL (0x0080u) +#define Control_ENTER_CAL (0x0081u) +#define Control_ENTER_CFG_UPDATE (0x0090u) +#define Control_EXIT_CFG_UPDATE_REINIT (0x0091u) +#define Control_EXIT_CFG_UPDATE (0x0092u) +#define Control_RETURN_TO_ROM (0x0F00u) + +#define UnsealKey1 (0x0414u) +#define UnsealKey2 (0x3672u) + +#define FullAccessKey (0xffffu) diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 1a55278dcc2..6097a8dc9c1 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -47,7 +47,7 @@ def generate(env): PVSOPTIONS=[ "@.pvsoptions", "-j${PVSNCORES}", - "--disableLicenseExpirationCheck", + # "--disableLicenseExpirationCheck", # "--incremental", # kinda broken on PVS side ], PVSCONVOPTIONS=[ diff --git a/targets/f7/furi_hal/furi_hal_power.c b/targets/f7/furi_hal/furi_hal_power.c index ccbc521a696..37c6a8b1bba 100644 --- a/targets/f7/furi_hal/furi_hal_power.c +++ b/targets/f7/furi_hal/furi_hal_power.c @@ -73,18 +73,14 @@ void furi_hal_power_init(void) { // Find and init gauge size_t retry = 2; while(retry > 0) { - furi_hal_power.gauge_ok = bq27220_init(&furi_hal_i2c_handle_power); - if(furi_hal_power.gauge_ok) { - furi_hal_power.gauge_ok = bq27220_apply_data_memory( - &furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); - } + furi_hal_power.gauge_ok = + bq27220_init(&furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory); if(furi_hal_power.gauge_ok) { break; } else { - // Normal startup time is 250ms - // But if we try to access gauge at that stage it will become unresponsive - // 2 seconds timeout needed to restart communication - furi_delay_us(2020202); + // Gauge need some time to think about it's behavior + // We must wait, otherwise next init cycle will fail at unseal stage + furi_delay_us(4000000); } retry--; } @@ -110,8 +106,8 @@ void furi_hal_power_init(void) { bool furi_hal_power_gauge_is_ok(void) { bool ret = true; - BatteryStatus battery_status; - OperationStatus operation_status; + Bq27220BatteryStatus battery_status; + Bq27220OperationStatus operation_status; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -132,7 +128,7 @@ bool furi_hal_power_gauge_is_ok(void) { bool furi_hal_power_is_shutdown_requested(void) { bool ret = false; - BatteryStatus battery_status; + Bq27220BatteryStatus battery_status; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); @@ -593,8 +589,8 @@ void furi_hal_power_debug_get(PropertyValueCallback out, void* context) { PropertyValueContext property_context = { .key = key, .value = value, .out = out, .sep = '.', .last = false, .context = context}; - BatteryStatus battery_status; - OperationStatus operation_status; + Bq27220BatteryStatus battery_status; + Bq27220OperationStatus operation_status; furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); From 0eaad8bf64f01a6f932647a9cda5475dd9ea1524 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Mon, 7 Oct 2024 04:21:31 +0900 Subject: [PATCH 15/36] [FL-3896] Split BadUSB into BadUSB and BadBLE (#3931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove BLE from BadUSB * Add the BadBLE app * Format images to 1-bit B/W * BadUsb: remove dead bits and pieces Co-authored-by: あく --- applications/main/bad_usb/application.fam | 2 +- applications/main/bad_usb/bad_usb_app.c | 8 - applications/main/bad_usb/bad_usb_app_i.h | 1 - .../main/bad_usb/helpers/bad_usb_hid.c | 156 +--- .../main/bad_usb/helpers/bad_usb_hid.h | 7 +- .../main/bad_usb/helpers/ducky_script.c | 4 +- .../main/bad_usb/helpers/ducky_script.h | 2 +- .../bad_usb/scenes/bad_usb_scene_config.c | 88 --- .../bad_usb/scenes/bad_usb_scene_config.h | 1 - .../main/bad_usb/scenes/bad_usb_scene_work.c | 4 +- .../main/bad_usb/views/bad_usb_view.c | 2 +- applications/system/bad_ble/application.fam | 12 + .../system/bad_ble/assets/Bad_BLE_48x22.png | Bin 0 -> 145 bytes applications/system/bad_ble/bad_ble_app.c | 196 +++++ applications/system/bad_ble/bad_ble_app.h | 11 + applications/system/bad_ble/bad_ble_app_i.h | 53 ++ .../system/bad_ble/helpers/bad_ble_hid.c | 157 ++++ .../system/bad_ble/helpers/bad_ble_hid.h | 34 + .../system/bad_ble/helpers/ducky_script.c | 716 ++++++++++++++++++ .../system/bad_ble/helpers/ducky_script.h | 55 ++ .../bad_ble/helpers/ducky_script_commands.c | 241 ++++++ .../system/bad_ble/helpers/ducky_script_i.h | 76 ++ .../bad_ble/helpers/ducky_script_keycodes.c | 133 ++++ applications/system/bad_ble/icon.png | Bin 0 -> 96 bytes .../system/bad_ble/scenes/bad_ble_scene.c | 30 + .../system/bad_ble/scenes/bad_ble_scene.h | 29 + .../bad_ble/scenes/bad_ble_scene_config.c | 59 ++ .../bad_ble/scenes/bad_ble_scene_config.h | 7 + .../scenes/bad_ble_scene_config_layout.c | 49 ++ .../scenes/bad_ble_scene_confirm_unpair.c | 53 ++ .../bad_ble/scenes/bad_ble_scene_error.c | 65 ++ .../scenes/bad_ble_scene_file_select.c | 46 ++ .../scenes/bad_ble_scene_unpair_done.c | 37 + .../bad_ble/scenes/bad_ble_scene_work.c | 65 ++ .../system/bad_ble/views/bad_ble_view.c | 284 +++++++ .../system/bad_ble/views/bad_ble_view.h | 26 + 36 files changed, 2444 insertions(+), 265 deletions(-) delete mode 100644 applications/main/bad_usb/scenes/bad_usb_scene_config.c create mode 100644 applications/system/bad_ble/application.fam create mode 100644 applications/system/bad_ble/assets/Bad_BLE_48x22.png create mode 100644 applications/system/bad_ble/bad_ble_app.c create mode 100644 applications/system/bad_ble/bad_ble_app.h create mode 100644 applications/system/bad_ble/bad_ble_app_i.h create mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.c create mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.h create mode 100644 applications/system/bad_ble/helpers/ducky_script.c create mode 100644 applications/system/bad_ble/helpers/ducky_script.h create mode 100644 applications/system/bad_ble/helpers/ducky_script_commands.c create mode 100644 applications/system/bad_ble/helpers/ducky_script_i.h create mode 100644 applications/system/bad_ble/helpers/ducky_script_keycodes.c create mode 100644 applications/system/bad_ble/icon.png create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.h create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.h create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_error.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_file_select.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c create mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_work.c create mode 100644 applications/system/bad_ble/views/bad_ble_view.c create mode 100644 applications/system/bad_ble/views/bad_ble_view.h diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 8d3909fccc6..9844e248df9 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -7,7 +7,7 @@ App( icon="A_BadUsb_14", order=70, resources="resources", - fap_libs=["assets", "ble_profile"], + fap_libs=["assets"], fap_icon="icon.png", fap_category="USB", ) diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 2d2d4be86ce..1ee92bdf3f1 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -35,7 +35,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { FuriString* temp_str = furi_string_alloc(); uint32_t version = 0; - uint32_t interface = 0; if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) { do { @@ -45,8 +44,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { break; if(!flipper_format_read_string(fff, "layout", temp_str)) break; - if(!flipper_format_read_uint32(fff, "interface", &interface, 1)) break; - if(interface > BadUsbHidInterfaceBle) break; state = true; } while(0); @@ -56,7 +53,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { if(state) { furi_string_set(app->keyboard_layout, temp_str); - app->interface = interface; Storage* fs_api = furi_record_open(RECORD_STORAGE); FileInfo layout_file_info; @@ -68,7 +64,6 @@ static void bad_usb_load_settings(BadUsbApp* app) { } } else { furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); - app->interface = BadUsbHidInterfaceUsb; } furi_string_free(temp_str); @@ -84,9 +79,6 @@ static void bad_usb_save_settings(BadUsbApp* app) { fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION)) break; if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; - uint32_t interface_id = app->interface; - if(!flipper_format_write_uint32(fff, "interface", (const uint32_t*)&interface_id, 1)) - break; } while(0); } diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index a4dd57d8b95..e63d0044c00 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -41,7 +41,6 @@ struct BadUsbApp { BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; - BadUsbHidInterface interface; FuriHalUsbInterface* usb_if_prev; }; diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.c b/applications/main/bad_usb/helpers/bad_usb_hid.c index 5d7076314af..dcba7b5e932 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.c +++ b/applications/main/bad_usb/helpers/bad_usb_hid.c @@ -1,12 +1,9 @@ #include "bad_usb_hid.h" #include -#include #include #define TAG "BadUSB HID" -#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" - void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) { furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg)); return NULL; @@ -72,155 +69,6 @@ static const BadUsbHidApi hid_api_usb = { .release_all = hid_usb_release_all, .get_led_state = hid_usb_get_led_state, }; - -typedef struct { - Bt* bt; - FuriHalBleProfileBase* profile; - HidStateCallback state_callback; - void* callback_context; - bool is_connected; -} BleHidInstance; - -static const BleProfileHidParams ble_hid_params = { - .device_name_prefix = "BadUSB", - .mac_xor = 0x0002, -}; - -static void hid_ble_connection_status_callback(BtStatus status, void* context) { - furi_assert(context); - BleHidInstance* ble_hid = context; - ble_hid->is_connected = (status == BtStatusConnected); - if(ble_hid->state_callback) { - ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); - } -} - -void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { - UNUSED(hid_cfg); - BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); - ble_hid->bt = furi_record_open(RECORD_BT); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - - ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); - furi_check(ble_hid->profile); - - furi_hal_bt_start_advertising(); - - bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); - - return ble_hid; -} - -void hid_ble_deinit(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - - bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(ble_hid->bt); - - furi_check(bt_profile_restore_default(ble_hid->bt)); - furi_record_close(RECORD_BT); - free(ble_hid); -} - -void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - ble_hid->state_callback = cb; - ble_hid->callback_context = context; -} - -bool hid_ble_is_connected(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_hid->is_connected; -} - -bool hid_ble_kb_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_press(ble_hid->profile, button); -} - -bool hid_ble_kb_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_release(ble_hid->profile, button); -} - -bool hid_ble_consumer_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_press(ble_hid->profile, button); -} - -bool hid_ble_consumer_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_release(ble_hid->profile, button); -} - -bool hid_ble_release_all(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - bool state = ble_profile_hid_kb_release_all(ble_hid->profile); - state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); - return state; -} - -uint8_t hid_ble_get_led_state(void* inst) { - UNUSED(inst); - FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); - return 0; -} - -static const BadUsbHidApi hid_api_ble = { - .init = hid_ble_init, - .deinit = hid_ble_deinit, - .set_state_callback = hid_ble_set_state_callback, - .is_connected = hid_ble_is_connected, - - .kb_press = hid_ble_kb_press, - .kb_release = hid_ble_kb_release, - .consumer_press = hid_ble_consumer_press, - .consumer_release = hid_ble_consumer_release, - .release_all = hid_ble_release_all, - .get_led_state = hid_ble_get_led_state, -}; - -const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface) { - if(interface == BadUsbHidInterfaceUsb) { - return &hid_api_usb; - } else { - return &hid_api_ble; - } -} - -void bad_usb_hid_ble_remove_pairing(void) { - Bt* bt = furi_record_open(RECORD_BT); - bt_disconnect(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - furi_hal_bt_stop_advertising(); - - bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - bt_forget_bonded_devices(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(bt); - - furi_check(bt_profile_restore_default(bt)); - furi_record_close(RECORD_BT); +const BadUsbHidApi* bad_usb_hid_get_interface() { + return &hid_api_usb; } diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.h b/applications/main/bad_usb/helpers/bad_usb_hid.h index 71d3a58e793..feaaacd5415 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.h +++ b/applications/main/bad_usb/helpers/bad_usb_hid.h @@ -7,11 +7,6 @@ extern "C" { #include #include -typedef enum { - BadUsbHidInterfaceUsb, - BadUsbHidInterfaceBle, -} BadUsbHidInterface; - typedef struct { void* (*init)(FuriHalUsbHidConfig* hid_cfg); void (*deinit)(void* inst); @@ -26,7 +21,7 @@ typedef struct { uint8_t (*get_led_state)(void* inst); } BadUsbHidApi; -const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface); +const BadUsbHidApi* bad_usb_hid_get_interface(); void bad_usb_hid_ble_remove_pairing(void); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index ccc3caa811b..d730fdba4df 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -650,7 +650,7 @@ static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) { memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout))); } -BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) { +BadUsbScript* bad_usb_script_open(FuriString* file_path) { furi_assert(file_path); BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); @@ -660,7 +660,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface inte bad_usb->st.state = BadUsbStateInit; bad_usb->st.error[0] = '\0'; - bad_usb->hid = bad_usb_hid_get_interface(interface); + bad_usb->hid = bad_usb_hid_get_interface(); bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); furi_thread_start(bad_usb->thread); diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index 9519623f604..43969d7b67d 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -34,7 +34,7 @@ typedef struct { typedef struct BadUsbScript BadUsbScript; -BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface); +BadUsbScript* bad_usb_script_open(FuriString* file_path); void bad_usb_script_close(BadUsbScript* bad_usb); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c deleted file mode 100644 index 1acf3acb153..00000000000 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "../bad_usb_app_i.h" - -enum SubmenuIndex { - ConfigIndexKeyboardLayout, - ConfigIndexInterface, - ConfigIndexBleUnpair, -}; - -const char* const interface_mode_text[2] = { - "USB", - "BLE", -}; - -void bad_usb_scene_config_select_callback(void* context, uint32_t index) { - BadUsbApp* bad_usb = context; - if(index != ConfigIndexInterface) { - view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index); - } -} - -void bad_usb_scene_config_interface_callback(VariableItem* item) { - BadUsbApp* bad_usb = variable_item_get_context(item); - furi_assert(bad_usb); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, interface_mode_text[index]); - bad_usb->interface = index; - - view_dispatcher_send_custom_event(bad_usb->view_dispatcher, ConfigIndexInterface); -} - -static void draw_menu(BadUsbApp* bad_usb) { - VariableItemList* var_item_list = bad_usb->var_item_list; - - variable_item_list_reset(var_item_list); - - variable_item_list_add(var_item_list, "Keyboard Layout (global)", 0, NULL, NULL); - - VariableItem* item = variable_item_list_add( - var_item_list, "Interface", 2, bad_usb_scene_config_interface_callback, bad_usb); - if(bad_usb->interface == BadUsbHidInterfaceUsb) { - variable_item_set_current_value_index(item, 0); - variable_item_set_current_value_text(item, interface_mode_text[0]); - } else { - variable_item_set_current_value_index(item, 1); - variable_item_set_current_value_text(item, interface_mode_text[1]); - variable_item_list_add(var_item_list, "Remove Pairing", 0, NULL, NULL); - } -} - -void bad_usb_scene_config_on_enter(void* context) { - BadUsbApp* bad_usb = context; - VariableItemList* var_item_list = bad_usb->var_item_list; - - variable_item_list_set_enter_callback( - var_item_list, bad_usb_scene_config_select_callback, bad_usb); - draw_menu(bad_usb); - variable_item_list_set_selected_item(var_item_list, 0); - - view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig); -} - -bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) { - BadUsbApp* bad_usb = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == ConfigIndexKeyboardLayout) { - scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout); - } else if(event.event == ConfigIndexInterface) { - draw_menu(bad_usb); - } else if(event.event == ConfigIndexBleUnpair) { - bad_usb_hid_ble_remove_pairing(); - } else { - furi_crash("Unknown key type"); - } - } - - return consumed; -} - -void bad_usb_scene_config_on_exit(void* context) { - BadUsbApp* bad_usb = context; - VariableItemList* var_item_list = bad_usb->var_item_list; - - variable_item_list_reset(var_item_list); -} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.h b/applications/main/bad_usb/scenes/bad_usb_scene_config.h index 423aedc51bd..e640bb556b5 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.h +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.h @@ -1,5 +1,4 @@ ADD_SCENE(bad_usb, file_select, FileSelect) ADD_SCENE(bad_usb, work, Work) ADD_SCENE(bad_usb, error, Error) -ADD_SCENE(bad_usb, config, Config) ADD_SCENE(bad_usb, config_layout, ConfigLayout) diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 0a383f02958..0afc056b622 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -20,7 +20,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { bad_usb_script_close(app->bad_usb_script); app->bad_usb_script = NULL; - scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout); } consumed = true; } else if(event.event == InputKeyOk) { @@ -39,7 +39,7 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { void bad_usb_scene_work_on_enter(void* context) { BadUsbApp* app = context; - app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface); + app->bad_usb_script = bad_usb_script_open(app->file_path); bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); FuriString* file_name; diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 728f843487f..7fb0b1434e6 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -47,7 +47,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || (state == BadUsbStateNotConnected)) { elements_button_center(canvas, "Run"); - elements_button_left(canvas, "Config"); + elements_button_left(canvas, "Layout"); } else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); if(!model->pause_wait) { diff --git a/applications/system/bad_ble/application.fam b/applications/system/bad_ble/application.fam new file mode 100644 index 00000000000..e00e6eefccf --- /dev/null +++ b/applications/system/bad_ble/application.fam @@ -0,0 +1,12 @@ +App( + appid="bad_ble", + name="Bad BLE", + apptype=FlipperAppType.EXTERNAL, + entry_point="bad_ble_app", + stack_size=2 * 1024, + icon="A_BadUsb_14", + fap_libs=["assets", "ble_profile"], + fap_icon="icon.png", + fap_icon_assets="assets", + fap_category="Bluetooth", +) diff --git a/applications/system/bad_ble/assets/Bad_BLE_48x22.png b/applications/system/bad_ble/assets/Bad_BLE_48x22.png new file mode 100644 index 0000000000000000000000000000000000000000..5f6fa6f4694972b23d9d0a219f404f16c18f6403 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^20$#v2qYNR^(cYp2u~Nskcv6JXAQX;3mdKI;Vst091lAb^rhX literal 0 HcmV?d00001 diff --git a/applications/system/bad_ble/bad_ble_app.c b/applications/system/bad_ble/bad_ble_app.c new file mode 100644 index 00000000000..f243371986a --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app.c @@ -0,0 +1,196 @@ +#include "bad_ble_app_i.h" +#include +#include +#include +#include +#include + +#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/.badble.settings" +#define BAD_BLE_SETTINGS_FILE_TYPE "Flipper BadBLE Settings File" +#define BAD_BLE_SETTINGS_VERSION 1 +#define BAD_BLE_SETTINGS_DEFAULT_LAYOUT BAD_BLE_APP_PATH_LAYOUT_FOLDER "/en-US.kl" + +static bool bad_ble_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BadBleApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool bad_ble_app_back_event_callback(void* context) { + furi_assert(context); + BadBleApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void bad_ble_app_tick_event_callback(void* context) { + furi_assert(context); + BadBleApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void bad_ble_load_settings(BadBleApp* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + bool state = false; + + FuriString* temp_str = furi_string_alloc(); + uint32_t version = 0; + + if(flipper_format_file_open_existing(fff, BAD_BLE_SETTINGS_PATH)) { + do { + if(!flipper_format_read_header(fff, temp_str, &version)) break; + if((strcmp(furi_string_get_cstr(temp_str), BAD_BLE_SETTINGS_FILE_TYPE) != 0) || + (version != BAD_BLE_SETTINGS_VERSION)) + break; + + if(!flipper_format_read_string(fff, "layout", temp_str)) break; + + state = true; + } while(0); + } + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); + + if(state) { + furi_string_set(app->keyboard_layout, temp_str); + + Storage* fs_api = furi_record_open(RECORD_STORAGE); + FileInfo layout_file_info; + FS_Error file_check_err = storage_common_stat( + fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); + furi_record_close(RECORD_STORAGE); + if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) { + furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT); + } + } else { + furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT); + } + + furi_string_free(temp_str); +} + +static void bad_ble_save_settings(BadBleApp* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + + if(flipper_format_file_open_always(fff, BAD_BLE_SETTINGS_PATH)) { + do { + if(!flipper_format_write_header_cstr( + fff, BAD_BLE_SETTINGS_FILE_TYPE, BAD_BLE_SETTINGS_VERSION)) + break; + if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; + } while(0); + } + + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); +} + +BadBleApp* bad_ble_app_alloc(char* arg) { + BadBleApp* app = malloc(sizeof(BadBleApp)); + + app->bad_ble_script = NULL; + + app->file_path = furi_string_alloc(); + app->keyboard_layout = furi_string_alloc(); + if(arg && strlen(arg)) { + furi_string_set(app->file_path, arg); + } + + bad_ble_load_settings(app); + + app->gui = furi_record_open(RECORD_GUI); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + app->dialogs = furi_record_open(RECORD_DIALOGS); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&bad_ble_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, bad_ble_app_tick_event_callback, 500); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, bad_ble_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, bad_ble_app_back_event_callback); + + // Custom Widget + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, BadBleAppViewPopup, popup_get_view(app->popup)); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + BadBleAppViewConfig, + variable_item_list_get_view(app->var_item_list)); + + app->bad_ble_view = bad_ble_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BadBleAppViewWork, bad_ble_view_get_view(app->bad_ble_view)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + if(!furi_string_empty(app->file_path)) { + scene_manager_next_scene(app->scene_manager, BadBleSceneWork); + } else { + furi_string_set(app->file_path, BAD_BLE_APP_BASE_FOLDER); + scene_manager_next_scene(app->scene_manager, BadBleSceneFileSelect); + } + + return app; +} + +void bad_ble_app_free(BadBleApp* app) { + furi_assert(app); + + if(app->bad_ble_script) { + bad_ble_script_close(app->bad_ble_script); + app->bad_ble_script = NULL; + } + + // Views + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWork); + bad_ble_view_free(app->bad_ble_view); + + // Custom Widget + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWidget); + widget_free(app->widget); + + // Popup + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewPopup); + popup_free(app->popup); + + // Config menu + view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig); + variable_item_list_free(app->var_item_list); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + + bad_ble_save_settings(app); + + furi_string_free(app->file_path); + furi_string_free(app->keyboard_layout); + + free(app); +} + +int32_t bad_ble_app(void* p) { + BadBleApp* bad_ble_app = bad_ble_app_alloc((char*)p); + + view_dispatcher_run(bad_ble_app->view_dispatcher); + + bad_ble_app_free(bad_ble_app); + return 0; +} diff --git a/applications/system/bad_ble/bad_ble_app.h b/applications/system/bad_ble/bad_ble_app.h new file mode 100644 index 00000000000..11954836e56 --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BadBleApp BadBleApp; + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/bad_ble_app_i.h b/applications/system/bad_ble/bad_ble_app_i.h new file mode 100644 index 00000000000..d1f739bebbc --- /dev/null +++ b/applications/system/bad_ble/bad_ble_app_i.h @@ -0,0 +1,53 @@ +#pragma once + +#include "bad_ble_app.h" +#include "scenes/bad_ble_scene.h" +#include "helpers/ducky_script.h" +#include "helpers/bad_ble_hid.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "views/bad_ble_view.h" + +#define BAD_BLE_APP_BASE_FOLDER EXT_PATH("badusb") +#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/assets/layouts" +#define BAD_BLE_APP_SCRIPT_EXTENSION ".txt" +#define BAD_BLE_APP_LAYOUT_EXTENSION ".kl" + +typedef enum { + BadBleAppErrorNoFiles, + BadBleAppErrorCloseRpc, +} BadBleAppError; + +struct BadBleApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + DialogsApp* dialogs; + Widget* widget; + Popup* popup; + VariableItemList* var_item_list; + + BadBleAppError error; + FuriString* file_path; + FuriString* keyboard_layout; + BadBle* bad_ble_view; + BadBleScript* bad_ble_script; + + BadBleHidInterface interface; +}; + +typedef enum { + BadBleAppViewWidget, + BadBleAppViewPopup, + BadBleAppViewWork, + BadBleAppViewConfig, +} BadBleAppView; diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.c b/applications/system/bad_ble/helpers/bad_ble_hid.c new file mode 100644 index 00000000000..c34b3c64612 --- /dev/null +++ b/applications/system/bad_ble/helpers/bad_ble_hid.c @@ -0,0 +1,157 @@ +#include "bad_ble_hid.h" +#include +#include +#include + +#define TAG "BadBLE HID" + +#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" + +typedef struct { + Bt* bt; + FuriHalBleProfileBase* profile; + HidStateCallback state_callback; + void* callback_context; + bool is_connected; +} BleHidInstance; + +static const BleProfileHidParams ble_hid_params = { + .device_name_prefix = "BadBLE", + .mac_xor = 0x0002, +}; + +static void hid_ble_connection_status_callback(BtStatus status, void* context) { + furi_assert(context); + BleHidInstance* ble_hid = context; + ble_hid->is_connected = (status == BtStatusConnected); + if(ble_hid->state_callback) { + ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); + } +} + +void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { + UNUSED(hid_cfg); + BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); + ble_hid->bt = furi_record_open(RECORD_BT); + bt_disconnect(ble_hid->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); + furi_check(ble_hid->profile); + + furi_hal_bt_start_advertising(); + + bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); + + return ble_hid; +} + +void hid_ble_deinit(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + + bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); + bt_disconnect(ble_hid->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(ble_hid->bt); + + furi_check(bt_profile_restore_default(ble_hid->bt)); + furi_record_close(RECORD_BT); + free(ble_hid); +} + +void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + ble_hid->state_callback = cb; + ble_hid->callback_context = context; +} + +bool hid_ble_is_connected(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_hid->is_connected; +} + +bool hid_ble_kb_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_press(ble_hid->profile, button); +} + +bool hid_ble_kb_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_release(ble_hid->profile, button); +} + +bool hid_ble_consumer_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_press(ble_hid->profile, button); +} + +bool hid_ble_consumer_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_release(ble_hid->profile, button); +} + +bool hid_ble_release_all(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + bool state = ble_profile_hid_kb_release_all(ble_hid->profile); + state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); + return state; +} + +uint8_t hid_ble_get_led_state(void* inst) { + UNUSED(inst); + FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); + return 0; +} + +static const BadBleHidApi hid_api_ble = { + .init = hid_ble_init, + .deinit = hid_ble_deinit, + .set_state_callback = hid_ble_set_state_callback, + .is_connected = hid_ble_is_connected, + + .kb_press = hid_ble_kb_press, + .kb_release = hid_ble_kb_release, + .consumer_press = hid_ble_consumer_press, + .consumer_release = hid_ble_consumer_release, + .release_all = hid_ble_release_all, + .get_led_state = hid_ble_get_led_state, +}; + +const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface) { + UNUSED(interface); + return &hid_api_ble; +} + +void bad_ble_hid_ble_remove_pairing(void) { + Bt* bt = furi_record_open(RECORD_BT); + bt_disconnect(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + furi_hal_bt_stop_advertising(); + + bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + bt_forget_bonded_devices(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(bt); + + furi_check(bt_profile_restore_default(bt)); + furi_record_close(RECORD_BT); +} diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.h b/applications/system/bad_ble/helpers/bad_ble_hid.h new file mode 100644 index 00000000000..b06385d6dd4 --- /dev/null +++ b/applications/system/bad_ble/helpers/bad_ble_hid.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef enum { + BadBleHidInterfaceBle, +} BadBleHidInterface; + +typedef struct { + void* (*init)(FuriHalUsbHidConfig* hid_cfg); + void (*deinit)(void* inst); + void (*set_state_callback)(void* inst, HidStateCallback cb, void* context); + bool (*is_connected)(void* inst); + + bool (*kb_press)(void* inst, uint16_t button); + bool (*kb_release)(void* inst, uint16_t button); + bool (*consumer_press)(void* inst, uint16_t button); + bool (*consumer_release)(void* inst, uint16_t button); + bool (*release_all)(void* inst); + uint8_t (*get_led_state)(void* inst); +} BadBleHidApi; + +const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface); + +void bad_ble_hid_ble_remove_pairing(void); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/helpers/ducky_script.c b/applications/system/bad_ble/helpers/ducky_script.c new file mode 100644 index 00000000000..a903fbdc4d0 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script.c @@ -0,0 +1,716 @@ +#include +#include +#include +#include +#include +#include +#include +#include "ducky_script.h" +#include "ducky_script_i.h" +#include + +#define TAG "BadBle" + +#define WORKER_TAG TAG "Worker" + +#define BADUSB_ASCII_TO_KEY(script, x) \ + (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) + +typedef enum { + WorkerEvtStartStop = (1 << 0), + WorkerEvtPauseResume = (1 << 1), + WorkerEvtEnd = (1 << 2), + WorkerEvtConnect = (1 << 3), + WorkerEvtDisconnect = (1 << 4), +} WorkerEvtFlags; + +static const char ducky_cmd_id[] = {"ID"}; + +static const uint8_t numpad_keys[10] = { + HID_KEYPAD_0, + HID_KEYPAD_1, + HID_KEYPAD_2, + HID_KEYPAD_3, + HID_KEYPAD_4, + HID_KEYPAD_5, + HID_KEYPAD_6, + HID_KEYPAD_7, + HID_KEYPAD_8, + HID_KEYPAD_9, +}; + +uint32_t ducky_get_command_len(const char* line) { + uint32_t len = strlen(line); + for(uint32_t i = 0; i < len; i++) { + if(line[i] == ' ') return i; + } + return 0; +} + +bool ducky_is_line_end(const char chr) { + return (chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'); +} + +uint16_t ducky_get_keycode(BadBleScript* bad_ble, const char* param, bool accept_chars) { + uint16_t keycode = ducky_get_keycode_by_name(param); + if(keycode != HID_KEYBOARD_NONE) { + return keycode; + } + + if((accept_chars) && (strlen(param) > 0)) { + return BADUSB_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF; + } + return 0; +} + +bool ducky_get_number(const char* param, uint32_t* val) { + uint32_t value = 0; + if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) { + *val = value; + return true; + } + return false; +} + +void ducky_numlock_on(BadBleScript* bad_ble) { + if((bad_ble->hid->get_led_state(bad_ble->hid_inst) & HID_KB_LED_NUM) == 0) { + bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK); + bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK); + } +} + +bool ducky_numpad_press(BadBleScript* bad_ble, const char num) { + if((num < '0') || (num > '9')) return false; + + uint16_t key = numpad_keys[num - '0']; + bad_ble->hid->kb_press(bad_ble->hid_inst, key); + bad_ble->hid->kb_release(bad_ble->hid_inst, key); + + return true; +} + +bool ducky_altchar(BadBleScript* bad_ble, const char* charcode) { + uint8_t i = 0; + bool state = false; + + bad_ble->hid->kb_press(bad_ble->hid_inst, KEY_MOD_LEFT_ALT); + + while(!ducky_is_line_end(charcode[i])) { + state = ducky_numpad_press(bad_ble, charcode[i]); + if(state == false) break; + i++; + } + + bad_ble->hid->kb_release(bad_ble->hid_inst, KEY_MOD_LEFT_ALT); + return state; +} + +bool ducky_altstring(BadBleScript* bad_ble, const char* param) { + uint32_t i = 0; + bool state = false; + + while(param[i] != '\0') { + if((param[i] < ' ') || (param[i] > '~')) { + i++; + continue; // Skip non-printable chars + } + + char temp_str[4]; + snprintf(temp_str, 4, "%u", param[i]); + + state = ducky_altchar(bad_ble, temp_str); + if(state == false) break; + i++; + } + return state; +} + +int32_t ducky_error(BadBleScript* bad_ble, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(bad_ble->st.error, sizeof(bad_ble->st.error), text, args); + + va_end(args); + return SCRIPT_STATE_ERROR; +} + +bool ducky_string(BadBleScript* bad_ble, const char* param) { + uint32_t i = 0; + + while(param[i] != '\0') { + if(param[i] != '\n') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, param[i]); + if(keycode != HID_KEYBOARD_NONE) { + bad_ble->hid->kb_press(bad_ble->hid_inst, keycode); + bad_ble->hid->kb_release(bad_ble->hid_inst, keycode); + } + } else { + bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + } + i++; + } + bad_ble->stringdelay = 0; + return true; +} + +static bool ducky_string_next(BadBleScript* bad_ble) { + if(bad_ble->string_print_pos >= furi_string_size(bad_ble->string_print)) { + return true; + } + + char print_char = furi_string_get_char(bad_ble->string_print, bad_ble->string_print_pos); + + if(print_char != '\n') { + uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, print_char); + if(keycode != HID_KEYBOARD_NONE) { + bad_ble->hid->kb_press(bad_ble->hid_inst, keycode); + bad_ble->hid->kb_release(bad_ble->hid_inst, keycode); + } + } else { + bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN); + } + + bad_ble->string_print_pos++; + + return false; +} + +static int32_t ducky_parse_line(BadBleScript* bad_ble, FuriString* line) { + uint32_t line_len = furi_string_size(line); + const char* line_tmp = furi_string_get_cstr(line); + + if(line_len == 0) { + return SCRIPT_STATE_NEXT_LINE; // Skip empty lines + } + FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); + + // Ducky Lang Functions + int32_t cmd_result = ducky_execute_cmd(bad_ble, line_tmp); + if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { + return cmd_result; + } + + // Special keys + modifiers + uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_ble, "No keycode defined for %s", line_tmp); + } + if((key & 0xFF00) != 0) { + // It's a modifier key + line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; + key |= ducky_get_keycode(bad_ble, line_tmp, true); + } + bad_ble->hid->kb_press(bad_ble->hid_inst, key); + bad_ble->hid->kb_release(bad_ble->hid_inst, key); + return 0; +} + +static bool ducky_set_usb_id(BadBleScript* bad_ble, const char* line) { + if(sscanf(line, "%lX:%lX", &bad_ble->hid_cfg.vid, &bad_ble->hid_cfg.pid) == 2) { + bad_ble->hid_cfg.manuf[0] = '\0'; + bad_ble->hid_cfg.product[0] = '\0'; + + uint8_t id_len = ducky_get_command_len(line); + if(!ducky_is_line_end(line[id_len + 1])) { + sscanf( + &line[id_len + 1], + "%31[^\r\n:]:%31[^\r\n]", + bad_ble->hid_cfg.manuf, + bad_ble->hid_cfg.product); + } + FURI_LOG_D( + WORKER_TAG, + "set id: %04lX:%04lX mfr:%s product:%s", + bad_ble->hid_cfg.vid, + bad_ble->hid_cfg.pid, + bad_ble->hid_cfg.manuf, + bad_ble->hid_cfg.product); + return true; + } + return false; +} + +static void bad_ble_hid_state_callback(bool state, void* context) { + furi_assert(context); + BadBleScript* bad_ble = context; + + if(state == true) { + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtConnect); + } else { + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtDisconnect); + } +} + +static bool ducky_script_preload(BadBleScript* bad_ble, File* script_file) { + uint8_t ret = 0; + uint32_t line_len = 0; + + furi_string_reset(bad_ble->line); + + do { + ret = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); + for(uint16_t i = 0; i < ret; i++) { + if(bad_ble->file_buf[i] == '\n' && line_len > 0) { + bad_ble->st.line_nb++; + line_len = 0; + } else { + if(bad_ble->st.line_nb == 0) { // Save first line + furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); + } + line_len++; + } + } + if(storage_file_eof(script_file)) { + if(line_len > 0) { + bad_ble->st.line_nb++; + break; + } + } + } while(ret > 0); + + const char* line_tmp = furi_string_get_cstr(bad_ble->line); + bool id_set = false; // Looking for ID command at first line + if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { + id_set = ducky_set_usb_id(bad_ble, &line_tmp[strlen(ducky_cmd_id) + 1]); + } + + if(id_set) { + bad_ble->hid_inst = bad_ble->hid->init(&bad_ble->hid_cfg); + } else { + bad_ble->hid_inst = bad_ble->hid->init(NULL); + } + bad_ble->hid->set_state_callback(bad_ble->hid_inst, bad_ble_hid_state_callback, bad_ble); + + storage_file_seek(script_file, 0, true); + furi_string_reset(bad_ble->line); + + return true; +} + +static int32_t ducky_script_execute_next(BadBleScript* bad_ble, File* script_file) { + int32_t delay_val = 0; + + if(bad_ble->repeat_cnt > 0) { + bad_ble->repeat_cnt--; + delay_val = ducky_parse_line(bad_ble, bad_ble->line_prev); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; + } else if(delay_val < 0) { // Script error + bad_ble->st.error_line = bad_ble->st.line_cur - 1; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur - 1U); + return SCRIPT_STATE_ERROR; + } else { + return delay_val + bad_ble->defdelay; + } + } + + furi_string_set(bad_ble->line_prev, bad_ble->line); + furi_string_reset(bad_ble->line); + + while(1) { + if(bad_ble->buf_len == 0) { + bad_ble->buf_len = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); + if(storage_file_eof(script_file)) { + if((bad_ble->buf_len < FILE_BUFFER_LEN) && (bad_ble->file_end == false)) { + bad_ble->file_buf[bad_ble->buf_len] = '\n'; + bad_ble->buf_len++; + bad_ble->file_end = true; + } + } + + bad_ble->buf_start = 0; + if(bad_ble->buf_len == 0) return SCRIPT_STATE_END; + } + for(uint8_t i = bad_ble->buf_start; i < (bad_ble->buf_start + bad_ble->buf_len); i++) { + if(bad_ble->file_buf[i] == '\n' && furi_string_size(bad_ble->line) > 0) { + bad_ble->st.line_cur++; + bad_ble->buf_len = bad_ble->buf_len + bad_ble->buf_start - (i + 1); + bad_ble->buf_start = i + 1; + furi_string_trim(bad_ble->line); + delay_val = ducky_parse_line(bad_ble, bad_ble->line); + if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line + return 0; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays + return delay_val; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button + return delay_val; + } else if(delay_val < 0) { + bad_ble->st.error_line = bad_ble->st.line_cur; + FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur); + return SCRIPT_STATE_ERROR; + } else { + return delay_val + bad_ble->defdelay; + } + } else { + furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); + } + } + bad_ble->buf_len = 0; + if(bad_ble->file_end) return SCRIPT_STATE_END; + } + + return 0; +} + +static uint32_t bad_ble_flags_get(uint32_t flags_mask, uint32_t timeout) { + uint32_t flags = furi_thread_flags_get(); + furi_check((flags & FuriFlagError) == 0); + if(flags == 0) { + flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); + furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout)); + } else { + uint32_t state = furi_thread_flags_clear(flags); + furi_check((state & FuriFlagError) == 0); + } + return flags; +} + +static int32_t bad_ble_worker(void* context) { + BadBleScript* bad_ble = context; + + BadBleWorkerState worker_state = BadBleStateInit; + BadBleWorkerState pause_state = BadBleStateRunning; + int32_t delay_val = 0; + + FURI_LOG_I(WORKER_TAG, "Init"); + File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + bad_ble->line = furi_string_alloc(); + bad_ble->line_prev = furi_string_alloc(); + bad_ble->string_print = furi_string_alloc(); + + while(1) { + if(worker_state == BadBleStateInit) { // State: initialization + if(storage_file_open( + script_file, + furi_string_get_cstr(bad_ble->file_path), + FSAM_READ, + FSOM_OPEN_EXISTING)) { + if((ducky_script_preload(bad_ble, script_file)) && (bad_ble->st.line_nb > 0)) { + if(bad_ble->hid->is_connected(bad_ble->hid_inst)) { + worker_state = BadBleStateIdle; // Ready to run + } else { + worker_state = BadBleStateNotConnected; // USB not connected + } + } else { + worker_state = BadBleStateScriptError; // Script preload error + } + } else { + FURI_LOG_E(WORKER_TAG, "File open error"); + worker_state = BadBleStateFileError; // File open error + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateNotConnected) { // State: USB not connected + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { + worker_state = BadBleStateIdle; // Ready to run + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateWillRun; // Will run when USB is connected + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateIdle) { // State: ready to start + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { // Start executing script + dolphin_deed(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_ble->buf_len = 0; + bad_ble->st.line_cur = 0; + bad_ble->defdelay = 0; + bad_ble->stringdelay = 0; + bad_ble->defstringdelay = 0; + bad_ble->repeat_cnt = 0; + bad_ble->key_hold_nb = 0; + bad_ble->file_end = false; + storage_file_seek(script_file, 0, true); + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateWillRun) { // State: start on connection + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); + + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtConnect) { // Start executing script + dolphin_deed(DolphinDeedBadUsbPlayScript); + delay_val = 0; + bad_ble->buf_len = 0; + bad_ble->st.line_cur = 0; + bad_ble->defdelay = 0; + bad_ble->stringdelay = 0; + bad_ble->defstringdelay = 0; + bad_ble->repeat_cnt = 0; + bad_ble->file_end = false; + storage_file_seek(script_file, 0, true); + // extra time for PC to recognize Flipper as keyboard + flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop, + FuriFlagWaitAny | FuriFlagNoClear, + 1500); + if(flags == (unsigned)FuriFlagErrorTimeout) { + // If nothing happened - start script execution + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; + furi_thread_flags_clear(WorkerEvtStartStop); + } + } else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution + worker_state = BadBleStateNotConnected; + } + bad_ble->st.state = worker_state; + + } else if(worker_state == BadBleStateRunning) { // State: running + uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); + uint32_t flags = furi_thread_flags_wait( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriFlagWaitAny, + delay_cur); + + delay_val -= delay_cur; + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; // Stop executing script + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtPauseResume) { + pause_state = BadBleStateRunning; + worker_state = BadBleStatePaused; // Pause + } + bad_ble->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + if(delay_val > 0) { + bad_ble->st.delay_remain--; + continue; + } + bad_ble->st.state = BadBleStateRunning; + delay_val = ducky_script_execute_next(bad_ble, script_file); + if(delay_val == SCRIPT_STATE_ERROR) { // Script error + delay_val = 0; + worker_state = BadBleStateScriptError; + bad_ble->st.state = worker_state; + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(delay_val == SCRIPT_STATE_END) { // End of script + delay_val = 0; + worker_state = BadBleStateIdle; + bad_ble->st.state = BadBleStateDone; + bad_ble->hid->release_all(bad_ble->hid_inst); + continue; + } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays + delay_val = bad_ble->defdelay; + bad_ble->string_print_pos = 0; + worker_state = BadBleStateStringDelay; + } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input + worker_state = BadBleStateWaitForBtn; + bad_ble->st.state = BadBleStateWaitForBtn; // Show long delays + } else if(delay_val > 1000) { + bad_ble->st.state = BadBleStateDelay; // Show long delays + bad_ble->st.delay_remain = delay_val / 1000; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if(worker_state == BadBleStateWaitForBtn) { // State: Wait for button Press + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriWaitForever); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + delay_val = 0; + worker_state = BadBleStateRunning; + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->hid->release_all(bad_ble->hid_inst); + } + bad_ble->st.state = worker_state; + continue; + } + } else if(worker_state == BadBleStatePaused) { // State: Paused + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + FuriWaitForever); + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; // Stop executing script + bad_ble->st.state = worker_state; + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->st.state = worker_state; + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtPauseResume) { + if(pause_state == BadBleStateRunning) { + if(delay_val > 0) { + bad_ble->st.state = BadBleStateDelay; + bad_ble->st.delay_remain = delay_val / 1000; + } else { + bad_ble->st.state = BadBleStateRunning; + delay_val = 0; + } + worker_state = BadBleStateRunning; // Resume + } else if(pause_state == BadBleStateStringDelay) { + bad_ble->st.state = BadBleStateRunning; + worker_state = BadBleStateStringDelay; // Resume + } + } + continue; + } + } else if(worker_state == BadBleStateStringDelay) { // State: print string with delays + uint32_t delay = (bad_ble->stringdelay == 0) ? bad_ble->defstringdelay : + bad_ble->stringdelay; + uint32_t flags = bad_ble_flags_get( + WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, + delay); + + if(!(flags & FuriFlagError)) { + if(flags & WorkerEvtEnd) { + break; + } else if(flags & WorkerEvtStartStop) { + worker_state = BadBleStateIdle; // Stop executing script + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtDisconnect) { + worker_state = BadBleStateNotConnected; // USB disconnected + bad_ble->hid->release_all(bad_ble->hid_inst); + } else if(flags & WorkerEvtPauseResume) { + pause_state = BadBleStateStringDelay; + worker_state = BadBleStatePaused; // Pause + } + bad_ble->st.state = worker_state; + continue; + } else if( + (flags == (unsigned)FuriFlagErrorTimeout) || + (flags == (unsigned)FuriFlagErrorResource)) { + bool string_end = ducky_string_next(bad_ble); + if(string_end) { + bad_ble->stringdelay = 0; + worker_state = BadBleStateRunning; + } + } else { + furi_check((flags & FuriFlagError) == 0); + } + } else if( + (worker_state == BadBleStateFileError) || + (worker_state == BadBleStateScriptError)) { // State: error + uint32_t flags = + bad_ble_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command + + if(flags & WorkerEvtEnd) { + break; + } + } + } + + bad_ble->hid->set_state_callback(bad_ble->hid_inst, NULL, NULL); + bad_ble->hid->deinit(bad_ble->hid_inst); + + storage_file_close(script_file); + storage_file_free(script_file); + furi_string_free(bad_ble->line); + furi_string_free(bad_ble->line_prev); + furi_string_free(bad_ble->string_print); + + FURI_LOG_I(WORKER_TAG, "End"); + + return 0; +} + +static void bad_ble_script_set_default_keyboard_layout(BadBleScript* bad_ble) { + furi_assert(bad_ble); + memset(bad_ble->layout, HID_KEYBOARD_NONE, sizeof(bad_ble->layout)); + memcpy(bad_ble->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_ble->layout))); +} + +BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface) { + furi_assert(file_path); + + BadBleScript* bad_ble = malloc(sizeof(BadBleScript)); + bad_ble->file_path = furi_string_alloc(); + furi_string_set(bad_ble->file_path, file_path); + bad_ble_script_set_default_keyboard_layout(bad_ble); + + bad_ble->st.state = BadBleStateInit; + bad_ble->st.error[0] = '\0'; + bad_ble->hid = bad_ble_hid_get_interface(interface); + + bad_ble->thread = furi_thread_alloc_ex("BadBleWorker", 2048, bad_ble_worker, bad_ble); + furi_thread_start(bad_ble->thread); + return bad_ble; +} //-V773 + +void bad_ble_script_close(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtEnd); + furi_thread_join(bad_ble->thread); + furi_thread_free(bad_ble->thread); + furi_string_free(bad_ble->file_path); + free(bad_ble); +} + +void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path) { + furi_assert(bad_ble); + + if((bad_ble->st.state == BadBleStateRunning) || (bad_ble->st.state == BadBleStateDelay)) { + // do not update keyboard layout while a script is running + return; + } + + File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + if(!furi_string_empty(layout_path)) { //-V1051 + if(storage_file_open( + layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { + uint16_t layout[128]; + if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) { + memcpy(bad_ble->layout, layout, sizeof(layout)); + } + } + storage_file_close(layout_file); + } else { + bad_ble_script_set_default_keyboard_layout(bad_ble); + } + storage_file_free(layout_file); +} + +void bad_ble_script_start_stop(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtStartStop); +} + +void bad_ble_script_pause_resume(BadBleScript* bad_ble) { + furi_assert(bad_ble); + furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtPauseResume); +} + +BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) { + furi_assert(bad_ble); + return &(bad_ble->st); +} diff --git a/applications/system/bad_ble/helpers/ducky_script.h b/applications/system/bad_ble/helpers/ducky_script.h new file mode 100644 index 00000000000..044cae82561 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script.h @@ -0,0 +1,55 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "bad_ble_hid.h" + +typedef enum { + BadBleStateInit, + BadBleStateNotConnected, + BadBleStateIdle, + BadBleStateWillRun, + BadBleStateRunning, + BadBleStateDelay, + BadBleStateStringDelay, + BadBleStateWaitForBtn, + BadBleStatePaused, + BadBleStateDone, + BadBleStateScriptError, + BadBleStateFileError, +} BadBleWorkerState; + +typedef struct { + BadBleWorkerState state; + size_t line_cur; + size_t line_nb; + uint32_t delay_remain; + size_t error_line; + char error[64]; +} BadBleState; + +typedef struct BadBleScript BadBleScript; + +BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface); + +void bad_ble_script_close(BadBleScript* bad_ble); + +void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path); + +void bad_ble_script_start(BadBleScript* bad_ble); + +void bad_ble_script_stop(BadBleScript* bad_ble); + +void bad_ble_script_start_stop(BadBleScript* bad_ble); + +void bad_ble_script_pause_resume(BadBleScript* bad_ble); + +BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/helpers/ducky_script_commands.c b/applications/system/bad_ble/helpers/ducky_script_commands.c new file mode 100644 index 00000000000..f70c5eba400 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_commands.c @@ -0,0 +1,241 @@ +#include +#include "ducky_script.h" +#include "ducky_script_i.h" + +typedef int32_t (*DuckyCmdCallback)(BadBleScript* bad_usb, const char* line, int32_t param); + +typedef struct { + char* name; + DuckyCmdCallback callback; + int32_t param; +} DuckyCmd; + +static int32_t ducky_fnc_delay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint32_t delay_val = 0; + bool state = ducky_get_number(line, &delay_val); + if((state) && (delay_val > 0)) { + return (int32_t)delay_val; + } + + return ducky_error(bad_usb, "Invalid number %s", line); +} + +static int32_t ducky_fnc_defdelay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->defdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_strdelay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->stringdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_defstrdelay(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->defstringdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_string(BadBleScript* bad_usb, const char* line, int32_t param) { + line = &line[ducky_get_command_len(line) + 1]; + furi_string_set_str(bad_usb->string_print, line); + if(param == 1) { + furi_string_cat(bad_usb->string_print, "\n"); + } + + if(bad_usb->stringdelay == 0 && + bad_usb->defstringdelay == 0) { // stringdelay not set - run command immediately + bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print)); + if(!state) { + return ducky_error(bad_usb, "Invalid string %s", line); + } + } else { // stringdelay is set - run command in thread to keep handling external events + return SCRIPT_STATE_STRING_START; + } + + return 0; +} + +static int32_t ducky_fnc_repeat(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->repeat_cnt); + if((!state) || (bad_usb->repeat_cnt == 0)) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + +static int32_t ducky_fnc_sysrq(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); + bad_usb->hid->kb_press(bad_usb->hid_inst, key); + bad_usb->hid->release_all(bad_usb->hid_inst); + return 0; +} + +static int32_t ducky_fnc_altchar(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_usb); + bool state = ducky_altchar(bad_usb, line); + if(!state) { + return ducky_error(bad_usb, "Invalid altchar %s", line); + } + return 0; +} + +static int32_t ducky_fnc_altstring(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + ducky_numlock_on(bad_usb); + bool state = ducky_altstring(bad_usb, line); + if(!state) { + return ducky_error(bad_usb, "Invalid altstring %s", line); + } + return 0; +} + +static int32_t ducky_fnc_hold(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + bad_usb->key_hold_nb++; + if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { + return ducky_error(bad_usb, "Too many keys are hold"); + } + bad_usb->hid->kb_press(bad_usb->hid_inst, key); + return 0; +} + +static int32_t ducky_fnc_release(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + if(bad_usb->key_hold_nb == 0) { + return ducky_error(bad_usb, "No keys are hold"); + } + bad_usb->key_hold_nb--; + bad_usb->hid->kb_release(bad_usb->hid_inst, key); + return 0; +} + +static int32_t ducky_fnc_media(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_media_keycode_by_name(line); + if(key == HID_CONSUMER_UNASSIGNED) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + bad_usb->hid->consumer_press(bad_usb->hid_inst, key); + bad_usb->hid->consumer_release(bad_usb->hid_inst, key); + return 0; +} + +static int32_t ducky_fnc_globe(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + uint16_t key = ducky_get_keycode(bad_usb, line, true); + if(key == HID_KEYBOARD_NONE) { + return ducky_error(bad_usb, "No keycode defined for %s", line); + } + + bad_usb->hid->consumer_press(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE); + bad_usb->hid->kb_press(bad_usb->hid_inst, key); + bad_usb->hid->kb_release(bad_usb->hid_inst, key); + bad_usb->hid->consumer_release(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE); + return 0; +} + +static int32_t ducky_fnc_waitforbutton(BadBleScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + UNUSED(bad_usb); + UNUSED(line); + + return SCRIPT_STATE_WAIT_FOR_BTN; +} + +static const DuckyCmd ducky_commands[] = { + {"REM", NULL, -1}, + {"ID", NULL, -1}, + {"DELAY", ducky_fnc_delay, -1}, + {"STRING", ducky_fnc_string, 0}, + {"STRINGLN", ducky_fnc_string, 1}, + {"DEFAULT_DELAY", ducky_fnc_defdelay, -1}, + {"DEFAULTDELAY", ducky_fnc_defdelay, -1}, + {"STRINGDELAY", ducky_fnc_strdelay, -1}, + {"STRING_DELAY", ducky_fnc_strdelay, -1}, + {"DEFAULT_STRING_DELAY", ducky_fnc_defstrdelay, -1}, + {"DEFAULTSTRINGDELAY", ducky_fnc_defstrdelay, -1}, + {"REPEAT", ducky_fnc_repeat, -1}, + {"SYSRQ", ducky_fnc_sysrq, -1}, + {"ALTCHAR", ducky_fnc_altchar, -1}, + {"ALTSTRING", ducky_fnc_altstring, -1}, + {"ALTCODE", ducky_fnc_altstring, -1}, + {"HOLD", ducky_fnc_hold, -1}, + {"RELEASE", ducky_fnc_release, -1}, + {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, + {"MEDIA", ducky_fnc_media, -1}, + {"GLOBE", ducky_fnc_globe, -1}, +}; + +#define TAG "BadBle" + +#define WORKER_TAG TAG "Worker" + +int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line) { + size_t cmd_word_len = strcspn(line, " "); + for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { + size_t cmd_compare_len = strlen(ducky_commands[i].name); + + if(cmd_compare_len != cmd_word_len) { + continue; + } + + if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) { + if(ducky_commands[i].callback == NULL) { + return 0; + } else { + return (ducky_commands[i].callback)(bad_usb, line, ducky_commands[i].param); + } + } + } + + return SCRIPT_STATE_CMD_UNKNOWN; +} diff --git a/applications/system/bad_ble/helpers/ducky_script_i.h b/applications/system/bad_ble/helpers/ducky_script_i.h new file mode 100644 index 00000000000..a5581d20655 --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_i.h @@ -0,0 +1,76 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "ducky_script.h" +#include "bad_ble_hid.h" + +#define SCRIPT_STATE_ERROR (-1) +#define SCRIPT_STATE_END (-2) +#define SCRIPT_STATE_NEXT_LINE (-3) +#define SCRIPT_STATE_CMD_UNKNOWN (-4) +#define SCRIPT_STATE_STRING_START (-5) +#define SCRIPT_STATE_WAIT_FOR_BTN (-6) + +#define FILE_BUFFER_LEN 16 + +struct BadBleScript { + FuriHalUsbHidConfig hid_cfg; + const BadBleHidApi* hid; + void* hid_inst; + FuriThread* thread; + BadBleState st; + + FuriString* file_path; + uint8_t file_buf[FILE_BUFFER_LEN + 1]; + uint8_t buf_start; + uint8_t buf_len; + bool file_end; + + uint32_t defdelay; + uint32_t stringdelay; + uint32_t defstringdelay; + uint16_t layout[128]; + + FuriString* line; + FuriString* line_prev; + uint32_t repeat_cnt; + uint8_t key_hold_nb; + + FuriString* string_print; + size_t string_print_pos; +}; + +uint16_t ducky_get_keycode(BadBleScript* bad_usb, const char* param, bool accept_chars); + +uint32_t ducky_get_command_len(const char* line); + +bool ducky_is_line_end(const char chr); + +uint16_t ducky_get_keycode_by_name(const char* param); + +uint16_t ducky_get_media_keycode_by_name(const char* param); + +bool ducky_get_number(const char* param, uint32_t* val); + +void ducky_numlock_on(BadBleScript* bad_usb); + +bool ducky_numpad_press(BadBleScript* bad_usb, const char num); + +bool ducky_altchar(BadBleScript* bad_usb, const char* charcode); + +bool ducky_altstring(BadBleScript* bad_usb, const char* param); + +bool ducky_string(BadBleScript* bad_usb, const char* param); + +int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line); + +int32_t ducky_error(BadBleScript* bad_usb, const char* text, ...); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/bad_ble/helpers/ducky_script_keycodes.c b/applications/system/bad_ble/helpers/ducky_script_keycodes.c new file mode 100644 index 00000000000..290618c131b --- /dev/null +++ b/applications/system/bad_ble/helpers/ducky_script_keycodes.c @@ -0,0 +1,133 @@ +#include +#include "ducky_script_i.h" + +typedef struct { + char* name; + uint16_t keycode; +} DuckyKey; + +static const DuckyKey ducky_keys[] = { + {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, + {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, + {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, + {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, + {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, + {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, + + {"CTRL", KEY_MOD_LEFT_CTRL}, + {"CONTROL", KEY_MOD_LEFT_CTRL}, + {"SHIFT", KEY_MOD_LEFT_SHIFT}, + {"ALT", KEY_MOD_LEFT_ALT}, + {"GUI", KEY_MOD_LEFT_GUI}, + {"WINDOWS", KEY_MOD_LEFT_GUI}, + + {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, + {"DOWN", HID_KEYBOARD_DOWN_ARROW}, + {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, + {"LEFT", HID_KEYBOARD_LEFT_ARROW}, + {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, + {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, + {"UPARROW", HID_KEYBOARD_UP_ARROW}, + {"UP", HID_KEYBOARD_UP_ARROW}, + + {"ENTER", HID_KEYBOARD_RETURN}, + {"BREAK", HID_KEYBOARD_PAUSE}, + {"PAUSE", HID_KEYBOARD_PAUSE}, + {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, + {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, + {"BACKSPACE", HID_KEYBOARD_DELETE}, + {"END", HID_KEYBOARD_END}, + {"ESC", HID_KEYBOARD_ESCAPE}, + {"ESCAPE", HID_KEYBOARD_ESCAPE}, + {"HOME", HID_KEYBOARD_HOME}, + {"INSERT", HID_KEYBOARD_INSERT}, + {"NUMLOCK", HID_KEYPAD_NUMLOCK}, + {"PAGEUP", HID_KEYBOARD_PAGE_UP}, + {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, + {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, + {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SPACE", HID_KEYBOARD_SPACEBAR}, + {"TAB", HID_KEYBOARD_TAB}, + {"MENU", HID_KEYBOARD_APPLICATION}, + {"APP", HID_KEYBOARD_APPLICATION}, + + {"F1", HID_KEYBOARD_F1}, + {"F2", HID_KEYBOARD_F2}, + {"F3", HID_KEYBOARD_F3}, + {"F4", HID_KEYBOARD_F4}, + {"F5", HID_KEYBOARD_F5}, + {"F6", HID_KEYBOARD_F6}, + {"F7", HID_KEYBOARD_F7}, + {"F8", HID_KEYBOARD_F8}, + {"F9", HID_KEYBOARD_F9}, + {"F10", HID_KEYBOARD_F10}, + {"F11", HID_KEYBOARD_F11}, + {"F12", HID_KEYBOARD_F12}, + {"F13", HID_KEYBOARD_F13}, + {"F14", HID_KEYBOARD_F14}, + {"F15", HID_KEYBOARD_F15}, + {"F16", HID_KEYBOARD_F16}, + {"F17", HID_KEYBOARD_F17}, + {"F18", HID_KEYBOARD_F18}, + {"F19", HID_KEYBOARD_F19}, + {"F20", HID_KEYBOARD_F20}, + {"F21", HID_KEYBOARD_F21}, + {"F22", HID_KEYBOARD_F22}, + {"F23", HID_KEYBOARD_F23}, + {"F24", HID_KEYBOARD_F24}, +}; + +static const DuckyKey ducky_media_keys[] = { + {"POWER", HID_CONSUMER_POWER}, + {"REBOOT", HID_CONSUMER_RESET}, + {"SLEEP", HID_CONSUMER_SLEEP}, + {"LOGOFF", HID_CONSUMER_AL_LOGOFF}, + + {"EXIT", HID_CONSUMER_AC_EXIT}, + {"HOME", HID_CONSUMER_AC_HOME}, + {"BACK", HID_CONSUMER_AC_BACK}, + {"FORWARD", HID_CONSUMER_AC_FORWARD}, + {"REFRESH", HID_CONSUMER_AC_REFRESH}, + + {"SNAPSHOT", HID_CONSUMER_SNAPSHOT}, + + {"PLAY", HID_CONSUMER_PLAY}, + {"PAUSE", HID_CONSUMER_PAUSE}, + {"PLAY_PAUSE", HID_CONSUMER_PLAY_PAUSE}, + {"NEXT_TRACK", HID_CONSUMER_SCAN_NEXT_TRACK}, + {"PREV_TRACK", HID_CONSUMER_SCAN_PREVIOUS_TRACK}, + {"STOP", HID_CONSUMER_STOP}, + {"EJECT", HID_CONSUMER_EJECT}, + + {"MUTE", HID_CONSUMER_MUTE}, + {"VOLUME_UP", HID_CONSUMER_VOLUME_INCREMENT}, + {"VOLUME_DOWN", HID_CONSUMER_VOLUME_DECREMENT}, + + {"FN", HID_CONSUMER_FN_GLOBE}, + {"BRIGHT_UP", HID_CONSUMER_BRIGHTNESS_INCREMENT}, + {"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT}, +}; + +uint16_t ducky_get_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { + size_t key_cmd_len = strlen(ducky_keys[i].name); + if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_keys[i].keycode; + } + } + + return HID_KEYBOARD_NONE; +} + +uint16_t ducky_get_media_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_media_keys); i++) { + size_t key_cmd_len = strlen(ducky_media_keys[i].name); + if((strncmp(param, ducky_media_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_media_keys[i].keycode; + } + } + + return HID_CONSUMER_UNASSIGNED; +} diff --git a/applications/system/bad_ble/icon.png b/applications/system/bad_ble/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..27355f8dbab9f62f03c3114bd345117b72703df2 GIT binary patch literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsse8IOhE&W+{!>5Ur5*F)|L$Vj tulr2n@!{d|IW8GdR-3Ztdxz&lMuxeII5)+8P-F*b^>p=fS?83{1OR(_90~vc literal 0 HcmV?d00001 diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.c b/applications/system/bad_ble/scenes/bad_ble_scene.c new file mode 100644 index 00000000000..351bb1e7945 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene.c @@ -0,0 +1,30 @@ +#include "bad_ble_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const bad_ble_scene_on_enter_handlers[])(void*) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const bad_ble_scene_on_exit_handlers[])(void* context) = { +#include "bad_ble_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers bad_ble_scene_handlers = { + .on_enter_handlers = bad_ble_scene_on_enter_handlers, + .on_event_handlers = bad_ble_scene_on_event_handlers, + .on_exit_handlers = bad_ble_scene_on_exit_handlers, + .scene_num = BadBleSceneNum, +}; diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.h b/applications/system/bad_ble/scenes/bad_ble_scene.h new file mode 100644 index 00000000000..25b19fc4b55 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BadBleScene##id, +typedef enum { +#include "bad_ble_scene_config.h" + BadBleSceneNum, +} BadBleScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers bad_ble_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "bad_ble_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "bad_ble_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "bad_ble_scene_config.h" +#undef ADD_SCENE diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.c b/applications/system/bad_ble/scenes/bad_ble_scene_config.c new file mode 100644 index 00000000000..1f64f19039d --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config.c @@ -0,0 +1,59 @@ +#include "../bad_ble_app_i.h" + +enum SubmenuIndex { + ConfigIndexKeyboardLayout, + ConfigIndexBleUnpair, +}; + +void bad_ble_scene_config_select_callback(void* context, uint32_t index) { + BadBleApp* bad_ble = context; + + view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index); +} + +static void draw_menu(BadBleApp* bad_ble) { + VariableItemList* var_item_list = bad_ble->var_item_list; + + variable_item_list_reset(var_item_list); + + variable_item_list_add(var_item_list, "Keyboard Layout (Global)", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Unpair Device", 0, NULL, NULL); +} + +void bad_ble_scene_config_on_enter(void* context) { + BadBleApp* bad_ble = context; + VariableItemList* var_item_list = bad_ble->var_item_list; + + variable_item_list_set_enter_callback( + var_item_list, bad_ble_scene_config_select_callback, bad_ble); + draw_menu(bad_ble); + variable_item_list_set_selected_item(var_item_list, 0); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfig); +} + +bool bad_ble_scene_config_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == ConfigIndexKeyboardLayout) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout); + } else if(event.event == ConfigIndexBleUnpair) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfirmUnpair); + } else { + furi_crash("Unknown key type"); + } + } + + return consumed; +} + +void bad_ble_scene_config_on_exit(void* context) { + BadBleApp* bad_ble = context; + VariableItemList* var_item_list = bad_ble->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.h b/applications/system/bad_ble/scenes/bad_ble_scene_config.h new file mode 100644 index 00000000000..5675fca59b7 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config.h @@ -0,0 +1,7 @@ +ADD_SCENE(bad_ble, file_select, FileSelect) +ADD_SCENE(bad_ble, work, Work) +ADD_SCENE(bad_ble, error, Error) +ADD_SCENE(bad_ble, config, Config) +ADD_SCENE(bad_ble, config_layout, ConfigLayout) +ADD_SCENE(bad_ble, confirm_unpair, ConfirmUnpair) +ADD_SCENE(bad_ble, unpair_done, UnpairDone) diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c b/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c new file mode 100644 index 00000000000..594525dd7b7 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c @@ -0,0 +1,49 @@ +#include "../bad_ble_app_i.h" +#include + +static bool bad_ble_layout_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + FuriString* predefined_path; + predefined_path = furi_string_alloc(); + if(!furi_string_empty(bad_ble->keyboard_layout)) { + furi_string_set(predefined_path, bad_ble->keyboard_layout); + } else { + furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER); + } + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px); + browser_options.base_path = BAD_BLE_APP_PATH_LAYOUT_FOLDER; + browser_options.skip_assets = false; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->keyboard_layout, predefined_path, &browser_options); + + furi_string_free(predefined_path); + return res; +} + +void bad_ble_scene_config_layout_on_enter(void* context) { + BadBleApp* bad_ble = context; + + if(bad_ble_layout_select(bad_ble)) { + scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneWork); + } else { + scene_manager_previous_scene(bad_ble->scene_manager); + } +} + +bool bad_ble_scene_config_layout_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBleApp* bad_ble = context; + return false; +} + +void bad_ble_scene_config_layout_on_exit(void* context) { + UNUSED(context); + // BadBleApp* bad_ble = context; +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c b/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c new file mode 100644 index 00000000000..63f1e92cf25 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c @@ -0,0 +1,53 @@ +#include "../bad_ble_app_i.h" + +void bad_ble_scene_confirm_unpair_widget_callback( + GuiButtonType type, + InputType input_type, + void* context) { + UNUSED(input_type); + SceneManagerEvent event = {.type = SceneManagerEventTypeCustom, .event = type}; + bad_ble_scene_confirm_unpair_on_event(context, event); +} + +void bad_ble_scene_confirm_unpair_on_enter(void* context) { + BadBleApp* bad_ble = context; + Widget* widget = bad_ble->widget; + + widget_add_button_element( + widget, GuiButtonTypeLeft, "Cancel", bad_ble_scene_confirm_unpair_widget_callback, context); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "Unpair", + bad_ble_scene_confirm_unpair_widget_callback, + context); + + widget_add_text_box_element( + widget, 0, 0, 128, 64, AlignCenter, AlignTop, "\e#Unpair the Device?\e#\n", false); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewWidget); +} + +bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + SceneManager* scene_manager = bad_ble->scene_manager; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(scene_manager, BadBleSceneUnpairDone); + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(scene_manager); + } + } + + return consumed; +} + +void bad_ble_scene_confirm_unpair_on_exit(void* context) { + BadBleApp* bad_ble = context; + Widget* widget = bad_ble->widget; + + widget_reset(widget); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_error.c b/applications/system/bad_ble/scenes/bad_ble_scene_error.c new file mode 100644 index 00000000000..c9c2b12da2b --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_error.c @@ -0,0 +1,65 @@ +#include "../bad_ble_app_i.h" + +typedef enum { + BadBleCustomEventErrorBack, +} BadBleCustomEvent; + +static void + bad_ble_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); + BadBleApp* app = context; + + if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { + view_dispatcher_send_custom_event(app->view_dispatcher, BadBleCustomEventErrorBack); + } +} + +void bad_ble_scene_error_on_enter(void* context) { + BadBleApp* app = context; + + if(app->error == BadBleAppErrorNoFiles) { + widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43); + widget_add_string_multiline_element( + app->widget, + 81, + 4, + AlignCenter, + AlignTop, + FontSecondary, + "No SD card or\napp data found.\nThis app will not\nwork without\nrequired files."); + widget_add_button_element( + app->widget, GuiButtonTypeLeft, "Back", bad_ble_scene_error_event_callback, app); + } else if(app->error == BadBleAppErrorCloseRpc) { + widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); + widget_add_string_multiline_element( + app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nIs Active!"); + widget_add_string_multiline_element( + app->widget, + 3, + 30, + AlignLeft, + AlignTop, + FontSecondary, + "Disconnect from\nPC or phone to\nuse this function."); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWidget); +} + +bool bad_ble_scene_error_on_event(void* context, SceneManagerEvent event) { + BadBleApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BadBleCustomEventErrorBack) { + view_dispatcher_stop(app->view_dispatcher); + consumed = true; + } + } + return consumed; +} + +void bad_ble_scene_error_on_exit(void* context) { + BadBleApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c b/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c new file mode 100644 index 00000000000..2a182a874d2 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c @@ -0,0 +1,46 @@ +#include "../bad_ble_app_i.h" +#include +#include + +static bool bad_ble_file_select(BadBleApp* bad_ble) { + furi_assert(bad_ble); + + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options( + &browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px); + browser_options.base_path = BAD_BLE_APP_BASE_FOLDER; + browser_options.skip_assets = true; + + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options); + + return res; +} + +void bad_ble_scene_file_select_on_enter(void* context) { + BadBleApp* bad_ble = context; + + if(bad_ble->bad_ble_script) { + bad_ble_script_close(bad_ble->bad_ble_script); + bad_ble->bad_ble_script = NULL; + } + + if(bad_ble_file_select(bad_ble)) { + scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork); + } else { + view_dispatcher_stop(bad_ble->view_dispatcher); + } +} + +bool bad_ble_scene_file_select_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + // BadBleApp* bad_ble = context; + return false; +} + +void bad_ble_scene_file_select_on_exit(void* context) { + UNUSED(context); + // BadBleApp* bad_ble = context; +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c b/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c new file mode 100644 index 00000000000..4c1fe3366b1 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c @@ -0,0 +1,37 @@ +#include "../bad_ble_app_i.h" + +static void bad_ble_scene_unpair_done_popup_callback(void* context) { + BadBleApp* bad_ble = context; + scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneConfig); +} + +void bad_ble_scene_unpair_done_on_enter(void* context) { + BadBleApp* bad_ble = context; + Popup* popup = bad_ble->popup; + + popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58); + popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom); + popup_set_callback(popup, bad_ble_scene_unpair_done_popup_callback); + popup_set_context(popup, bad_ble); + popup_set_timeout(popup, 1000); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewPopup); +} + +bool bad_ble_scene_unpair_done_on_event(void* context, SceneManagerEvent event) { + BadBleApp* bad_ble = context; + UNUSED(bad_ble); + UNUSED(event); + + bool consumed = false; + + return consumed; +} + +void bad_ble_scene_unpair_done_on_exit(void* context) { + BadBleApp* bad_ble = context; + Popup* popup = bad_ble->popup; + + popup_reset(popup); +} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_work.c b/applications/system/bad_ble/scenes/bad_ble_scene_work.c new file mode 100644 index 00000000000..ff71edc3c21 --- /dev/null +++ b/applications/system/bad_ble/scenes/bad_ble_scene_work.c @@ -0,0 +1,65 @@ +#include "../helpers/ducky_script.h" +#include "../bad_ble_app_i.h" +#include "../views/bad_ble_view.h" +#include +#include "toolbox/path.h" + +void bad_ble_scene_work_button_callback(InputKey key, void* context) { + furi_assert(context); + BadBleApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, key); +} + +bool bad_ble_scene_work_on_event(void* context, SceneManagerEvent event) { + BadBleApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InputKeyLeft) { + if(bad_ble_view_is_idle_state(app->bad_ble_view)) { + bad_ble_script_close(app->bad_ble_script); + app->bad_ble_script = NULL; + + scene_manager_next_scene(app->scene_manager, BadBleSceneConfig); + } + consumed = true; + } else if(event.event == InputKeyOk) { + bad_ble_script_start_stop(app->bad_ble_script); + consumed = true; + } else if(event.event == InputKeyRight) { + bad_ble_script_pause_resume(app->bad_ble_script); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeTick) { + bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); + } + return consumed; +} + +void bad_ble_scene_work_on_enter(void* context) { + BadBleApp* app = context; + + app->bad_ble_script = bad_ble_script_open(app->file_path, app->interface); + bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout); + + FuriString* file_name; + file_name = furi_string_alloc(); + path_extract_filename(app->file_path, file_name, true); + bad_ble_view_set_file_name(app->bad_ble_view, furi_string_get_cstr(file_name)); + furi_string_free(file_name); + + FuriString* layout; + layout = furi_string_alloc(); + path_extract_filename(app->keyboard_layout, layout, true); + bad_ble_view_set_layout(app->bad_ble_view, furi_string_get_cstr(layout)); + furi_string_free(layout); + + bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); + + bad_ble_view_set_button_callback(app->bad_ble_view, bad_ble_scene_work_button_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWork); +} + +void bad_ble_scene_work_on_exit(void* context) { + UNUSED(context); +} diff --git a/applications/system/bad_ble/views/bad_ble_view.c b/applications/system/bad_ble/views/bad_ble_view.c new file mode 100644 index 00000000000..28f935733ef --- /dev/null +++ b/applications/system/bad_ble/views/bad_ble_view.c @@ -0,0 +1,284 @@ +#include "bad_ble_view.h" +#include "../helpers/ducky_script.h" +#include +#include +#include +#include "bad_ble_icons.h" + +#define MAX_NAME_LEN 64 + +struct BadBle { + View* view; + BadBleButtonCallback callback; + void* context; +}; + +typedef struct { + char file_name[MAX_NAME_LEN]; + char layout[MAX_NAME_LEN]; + BadBleState state; + bool pause_wait; + uint8_t anim_frame; +} BadBleModel; + +static void bad_ble_draw_callback(Canvas* canvas, void* _model) { + BadBleModel* model = _model; + + FuriString* disp_str; + disp_str = furi_string_alloc_set(model->file_name); + elements_string_fit_width(canvas, disp_str, 128 - 2); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); + + if(strlen(model->layout) == 0) { + furi_string_set(disp_str, "(default)"); + } else { + furi_string_printf(disp_str, "(%s)", model->layout); + } + elements_string_fit_width(canvas, disp_str, 128 - 2); + canvas_draw_str( + canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str)); + + furi_string_reset(disp_str); + + canvas_draw_icon(canvas, 22, 24, &I_Bad_BLE_48x22); + + BadBleWorkerState state = model->state.state; + + if((state == BadBleStateIdle) || (state == BadBleStateDone) || + (state == BadBleStateNotConnected)) { + elements_button_center(canvas, "Run"); + elements_button_left(canvas, "Config"); + } else if((state == BadBleStateRunning) || (state == BadBleStateDelay)) { + elements_button_center(canvas, "Stop"); + if(!model->pause_wait) { + elements_button_right(canvas, "Pause"); + } + } else if(state == BadBleStatePaused) { + elements_button_center(canvas, "End"); + elements_button_right(canvas, "Resume"); + } else if(state == BadBleStateWaitForBtn) { + elements_button_center(canvas, "Press to continue"); + } else if(state == BadBleStateWillRun) { + elements_button_center(canvas, "Cancel"); + } + + if(state == BadBleStateNotConnected) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to device"); + } else if(state == BadBleStateWillRun) { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); + } else if(state == BadBleStateFileError) { + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); + canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); + } else if(state == BadBleStateScriptError) { + canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); + canvas_set_font(canvas, FontSecondary); + furi_string_printf(disp_str, "line %zu", model->state.error_line); + canvas_draw_str_aligned( + canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + + furi_string_set_str(disp_str, model->state.error); + elements_string_fit_width(canvas, disp_str, canvas_width(canvas)); + canvas_draw_str_aligned( + canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + } else if(state == BadBleStateIdle) { + canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadBleStateRunning) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadBleStateDone) { + canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + } else if(state == BadBleStateDelay) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_set_font(canvas, FontSecondary); + furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); + canvas_draw_str_aligned( + canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + } else if((state == BadBleStatePaused) || (state == BadBleStateWaitForBtn)) { + if(model->anim_frame == 0) { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); + } else { + canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); + } + canvas_set_font(canvas, FontBigNumbers); + furi_string_printf( + disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); + canvas_draw_str_aligned( + canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); + furi_string_reset(disp_str); + canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused"); + furi_string_reset(disp_str); + } else { + canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); + } + + furi_string_free(disp_str); +} + +static bool bad_ble_input_callback(InputEvent* event, void* context) { + furi_assert(context); + BadBle* bad_ble = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft) { + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } else if(event->key == InputKeyOk) { + with_view_model( + bad_ble->view, BadBleModel * model, { model->pause_wait = false; }, true); + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } else if(event->key == InputKeyRight) { + with_view_model( + bad_ble->view, + BadBleModel * model, + { + if((model->state.state == BadBleStateRunning) || + (model->state.state == BadBleStateDelay)) { + model->pause_wait = true; + } + }, + true); + consumed = true; + furi_assert(bad_ble->callback); + bad_ble->callback(event->key, bad_ble->context); + } + } + + return consumed; +} + +BadBle* bad_ble_view_alloc(void) { + BadBle* bad_ble = malloc(sizeof(BadBle)); + + bad_ble->view = view_alloc(); + view_allocate_model(bad_ble->view, ViewModelTypeLocking, sizeof(BadBleModel)); + view_set_context(bad_ble->view, bad_ble); + view_set_draw_callback(bad_ble->view, bad_ble_draw_callback); + view_set_input_callback(bad_ble->view, bad_ble_input_callback); + + return bad_ble; +} + +void bad_ble_view_free(BadBle* bad_ble) { + furi_assert(bad_ble); + view_free(bad_ble->view); + free(bad_ble); +} + +View* bad_ble_view_get_view(BadBle* bad_ble) { + furi_assert(bad_ble); + return bad_ble->view; +} + +void bad_ble_view_set_button_callback( + BadBle* bad_ble, + BadBleButtonCallback callback, + void* context) { + furi_assert(bad_ble); + furi_assert(callback); + with_view_model( + bad_ble->view, + BadBleModel * model, + { + UNUSED(model); + bad_ble->callback = callback; + bad_ble->context = context; + }, + true); +} + +void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name) { + furi_assert(name); + with_view_model( + bad_ble->view, + BadBleModel * model, + { strlcpy(model->file_name, name, MAX_NAME_LEN); }, + true); +} + +void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout) { + furi_assert(layout); + with_view_model( + bad_ble->view, + BadBleModel * model, + { strlcpy(model->layout, layout, MAX_NAME_LEN); }, + true); +} + +void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st) { + furi_assert(st); + with_view_model( + bad_ble->view, + BadBleModel * model, + { + memcpy(&(model->state), st, sizeof(BadBleState)); + model->anim_frame ^= 1; + if(model->state.state == BadBleStatePaused) { + model->pause_wait = false; + } + }, + true); +} + +bool bad_ble_view_is_idle_state(BadBle* bad_ble) { + bool is_idle = false; + with_view_model( + bad_ble->view, + BadBleModel * model, + { + if((model->state.state == BadBleStateIdle) || + (model->state.state == BadBleStateDone) || + (model->state.state == BadBleStateNotConnected)) { + is_idle = true; + } + }, + false); + return is_idle; +} diff --git a/applications/system/bad_ble/views/bad_ble_view.h b/applications/system/bad_ble/views/bad_ble_view.h new file mode 100644 index 00000000000..e26488818e4 --- /dev/null +++ b/applications/system/bad_ble/views/bad_ble_view.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include "../helpers/ducky_script.h" + +typedef struct BadBle BadBle; +typedef void (*BadBleButtonCallback)(InputKey key, void* context); + +BadBle* bad_ble_view_alloc(void); + +void bad_ble_view_free(BadBle* bad_ble); + +View* bad_ble_view_get_view(BadBle* bad_ble); + +void bad_ble_view_set_button_callback( + BadBle* bad_ble, + BadBleButtonCallback callback, + void* context); + +void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name); + +void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout); + +void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st); + +bool bad_ble_view_is_idle_state(BadBle* bad_ble); From 41c35cd59ec64296b41a5422ae1170c7dc4edfdb Mon Sep 17 00:00:00 2001 From: Ruslan Nadyrshin <110516632+rnadyrshin@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:27:16 +0400 Subject: [PATCH 16/36] Documentation: update and cleanup (#3934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Developers Docs editing * Logo underline removed The underline has been removed when hovering over the logo. * proofread docs * application -> app in several files --------- Co-authored-by: knrn64 <25254561+knrn64@users.noreply.github.com> Co-authored-by: あく --- ReadMe.md | 6 +- .../examples/example_number_input/ReadMe.md | 14 ++- documentation/AppManifests.md | 78 ++++++++-------- documentation/AppsOnSDCard.md | 42 ++++----- documentation/ExpansionModules.md | 8 +- documentation/FuriCheck.md | 20 ++-- documentation/FuriHalBus.md | 10 +- documentation/FuriHalDebuging.md | 14 +-- documentation/HardwareTargets.md | 12 +-- documentation/KeyCombo.md | 4 +- documentation/OTA.md | 2 +- documentation/UnitTests.md | 14 +-- documentation/UniversalRemotes.md | 4 +- .../Reading logs via the Dev Board.md | 4 +- documentation/doxygen/Doxyfile.cfg | 2 +- documentation/doxygen/app_publishing.dox | 7 ++ documentation/doxygen/applications.dox | 14 +-- documentation/doxygen/dev_board.dox | 6 +- documentation/doxygen/dev_tools.dox | 7 +- documentation/doxygen/examples.dox | 15 +-- documentation/doxygen/expansion_modules.dox | 2 +- documentation/doxygen/file_formats.dox | 1 + documentation/doxygen/header.html | 4 +- documentation/doxygen/index.dox | 28 +++--- documentation/doxygen/js.dox | 21 +++-- documentation/doxygen/misc.dox | 6 +- documentation/doxygen/system.dox | 16 ++-- documentation/fbt.md | 92 +++++++++---------- documentation/file_formats/NfcFileFormats.md | 2 +- .../file_formats/TarHeatshrinkFormat.md | 4 +- documentation/js/js_badusb.md | 2 +- documentation/js/js_data_types.md | 12 +-- documentation/js/js_math.md | 2 +- 33 files changed, 247 insertions(+), 228 deletions(-) create mode 100644 documentation/doxygen/app_publishing.dox diff --git a/ReadMe.md b/ReadMe.md index 15235e6fcf6..f16adc43289 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -9,8 +9,8 @@ # Flipper Zero Firmware - [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what Flipper Zero can do. -- [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices. -- [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and anything you want to ask. +- [Flipper Zero Firmware Update](https://flipperzero.one/update). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices. +- [User Documentation](https://docs.flipper.net). Learn more about your dolphin: specs, usage guides, and anything you want to ask. - [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). Dive into the Flipper Zero Firmware source code: build system, firmware structure, and more. # Contributing @@ -19,7 +19,7 @@ Our main goal is to build a healthy and sustainable community around Flipper, so ## I need help -The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). If you want to contribute to the firmware development, or modify it for your own needs, you can also check our [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). +The best place to search for answers is our [User Documentation](https://docs.flipper.net). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). If you want to contribute to the firmware development or modify it for your own needs, you can also check our [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). ## I want to report an issue diff --git a/applications/examples/example_number_input/ReadMe.md b/applications/examples/example_number_input/ReadMe.md index 9d5a0a9e5eb..8a221ba0870 100644 --- a/applications/examples/example_number_input/ReadMe.md +++ b/applications/examples/example_number_input/ReadMe.md @@ -1,7 +1,13 @@ -# Number Input +# Number Input {#example_number_input} -Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need of intense validations after a user input. +Simple keyboard that limits user inputs to a full number (integer). Useful to enforce correct entries without the need for intense validations after a user input. -Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed withing min - max, an additional button is displayed to switch the sign between + and -. +## Source code -It is also possible to define a header text, shown in this example app with the 3 different input options. \ No newline at end of file +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_number_input). + +## General principle + +Definition of min/max values is required. Numbers are of type int32_t. If negative numbers are allowed within min - max, an additional button is displayed to switch the sign between + and -. + +It is also possible to define a header text, as shown in this example app with the 3 different input options. \ No newline at end of file diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 98a38ffd85f..f0f9d637991 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -1,65 +1,65 @@ -# Flipper Application Manifests (.fam) {#app_manifests} +# FAM (Flipper App Manifests) {#app_manifests} All components of Flipper Zero firmware — services, user applications, and system settings — are developed independently. Each component has a build system manifest file named `application.fam`, which defines the basic properties of that component and its relations to other parts of the system. -When building firmware, `fbt` collects all application manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](fbt.md) for details on build configurations. +When building firmware, `fbt` collects all app manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](fbt.md) for details on build configurations. -## Application definition +## App definition A firmware component's properties are declared in a Python code snippet, forming a call to the `App()` function with various parameters. -Only two parameters are mandatory: **appid** and **apptype**. Others are optional and may only be meaningful for certain application types. +Only two parameters are mandatory: **appid** and **apptype**. Others are optional and may only be meaningful for certain app types. ### Parameters -- **appid**: string, application ID within the build system. It is used to specify which applications to include in the build configuration and resolve dependencies and conflicts. +- **appid**: string, app ID within the build system. It is used to specify which app to include in the build configuration and resolve dependencies and conflicts. - **apptype**: member of FlipperAppType.\* enumeration. Valid values are: | Enum member | Firmware component type | | ----------- | ------------------------------------------------------------------------------------------- | | SERVICE | System service, created at early startup | -| SYSTEM | Application is not being shown in any menus. It can be started by other apps or from CLI | -| APP | Regular application for the main menu | -| PLUGIN | Application to be built as a part of the firmware and to be placed in the Plugins menu | -| DEBUG | Application only visible in Debug menu with debug mode enabled | +| SYSTEM | App is not being shown in any menus. It can be started by other apps or from CLI | +| APP | Regular app for the main menu | +| PLUGIN | App to be built as a part of the firmware and to be placed in the Plugins menu | +| DEBUG | App only visible in Debug menu with debug mode enabled | | ARCHIVE | One and only Archive app | -| SETTINGS | Application to be placed in the system settings menu | +| SETTINGS | App to be placed in the system settings menu | | STARTUP | Callback function to run at system startup. Does not define a separate app | -| EXTERNAL | Application to be built as `.fap` plugin | -| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | +| EXTERNAL | App to be built as `.fap` plugin | +| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and app bundles | - **name**: name displayed in menus. -- **entry_point**: C function to be used as the application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. +- **entry_point**: C function to be used as the app's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` to use them as entry points. - **flags**: internal flags for system apps. Do not use. -- **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. **For external applications**: specified definitions are used when building the application itself. -- **requires**: list of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. -- **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, `fbt` will abort the firmware build process. +- **cdefines**: C preprocessor definitions to declare globally for other apps when the current app is included in the active build configuration. **For external apps**: specified definitions are used when building the app itself. +- **requires**: list of app IDs to include in the build configuration when the current app is referenced in the list of apps to build. +- **conflicts**: list of app IDs with which the current app conflicts. If any of them is found in the constructed app list, `fbt` will abort the firmware build process. - **provides**: functionally identical to **_requires_** field. -- **stack_size**: stack size in bytes to allocate for an application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `top` and `free` CLI commands to profile your app's memory usage._ +- **stack_size**: stack size in bytes to allocate for an app on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `top` and `free` CLI commands to profile your app's memory usage._ - **icon**: animated icon name from built-in assets to be used when building the app as a part of the firmware. -- **order**: order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._ -- **sdk_headers**: list of C header files from this app's code to include in API definitions for external applications. -- **targets**: list of strings and target names with which this application is compatible. If not specified, the application is built for all targets. The default value is `["all"]`. -- **resources**: name of a folder within the application's source folder to be used for packacking SD card resources for this application. They will only be used if application is included in build configuration. The default value is `""`, meaning no resources are packaged. +- **order**: order of an app within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. _Used for ordering startup hooks and menu entries._ +- **sdk_headers**: list of C header files from this app's code to include in API definitions for external apps. +- **targets**: list of strings and target names with which this app is compatible. If not specified, the app is built for all targets. The default value is `["all"]`. +- **resources**: name of a folder within the app's source folder to be used for packacking SD card resources for this app. They will only be used if app is included in build configuration. The default value is `""`, meaning no resources are packaged. -#### Parameters for external applications +#### Parameters for external apps The following parameters are used only for [FAPs](./AppsOnSDCard.md): -- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Applications cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. Paths starting with `"!"` are excluded from the list of sources. They can also include wildcard characters and directory names. For example, a value of `["*.c*", "!plugins"]` will include all C and C++ source files in the app folder except those in the `plugins` (and `lib`) folders. Paths with no wildcards (`*, ?`) are treated as full literal paths for both inclusion and exclusion. -- **fap_version**: string, application version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap. +- **sources**: list of strings, file name masks used for gathering sources within the app folder. The default value of `["*.c*"]` includes C and C++ source files. Apps cannot use the `"lib"` folder for their own source code, as it is reserved for **fap_private_libs**. Paths starting with `"!"` are excluded from the list of sources. They can also include wildcard characters and directory names. For example, a value of `["*.c*", "!plugins"]` will include all C and C++ source files in the app folder except those in the `plugins` (and `lib`) folders. Paths with no wildcards (`*, ?`) are treated as full literal paths for both inclusion and exclusion. +- **fap_version**: string, app version. The default value is "0.1". You can also use a tuple of 2 numbers in the form of (x,y) to specify the version. It is also possible to add more dot-separated parts to the version, like patch number, but only major and minor version numbers are stored in the built .fap. - **fap_icon**: name of a `.png` file, 1-bit color depth, 10x10px, to be embedded within `.fap` file. -- **fap_libs**: list of extra libraries to link the application against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption. +- **fap_libs**: list of extra libraries to link the app against. Provides access to extra functions that are not exported as a part of main firmware at the expense of increased `.fap` file size and RAM consumption. - **fap_category**: string, may be empty. App subcategory, also determines the path of the FAP within the apps folder in the file system. -- **fap_description**: string, may be empty. Short application description. -- **fap_author**: string, may be empty. Application's author. -- **fap_weburl**: string, may be empty. Application's homepage. -- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](AppsOnSDCard.md) for details. -- **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. `fbt` will run the specified command for each file in the list. -- **fal_embedded**: boolean, default `False`. Applies only to PLUGIN type. If `True`, the plugin will be embedded into host application's .fap file as a resource and extracted to `apps_assets/APPID` folder on its start. This allows plugins to be distributed as a part of the host application. +- **fap_description**: string, may be empty. Short app description. +- **fap_author**: string, may be empty. App's author. +- **fap_weburl**: string, may be empty. App's homepage. +- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this app. These images will be preprocessed and built alongside the app. See [FAP assets](AppsOnSDCard.md) for details. +- **fap_extbuild**: provides support for parts of app sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. `fbt` will run the specified command for each file in the list. +- **fal_embedded**: boolean, default `False`. Applies only to PLUGIN type. If `True`, the plugin will be embedded into host app's .fap file as a resource and extracted to `apps_assets/APPID` folder on its start. This allows plugins to be distributed as a part of the host app. -Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by `fbt`: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by `fbt`. +Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an app's temporary build folder. For that, you can use pattern expansion by `fbt`: `${FAP_WORK_DIR}` will be replaced with the path to the app's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the app's source folder. You can also use other variables defined internally by `fbt`. Example for building an app from Rust sources: @@ -73,8 +73,8 @@ Example for building an app from Rust sources: ), ``` -- **fap_private_libs**: list of additional libraries distributed as sources alongside the application. These libraries will be built as a part of the application build process. - Library sources must be placed in a subfolder of the `lib` folder within the application's source folder. +- **fap_private_libs**: list of additional libraries distributed as sources alongside the app. These libraries will be built as a part of the app build process. + Library sources must be placed in a subfolder of the `lib` folder within the app's source folder. Each library is defined as a call to the `Lib()` function, accepting the following parameters: - **name**: name of the library's folder. Required. @@ -82,7 +82,7 @@ Example for building an app from Rust sources: - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. - - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. + - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the app's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. Example for building an app with a private library: @@ -105,14 +105,14 @@ Example for building an app with a private library: ], ``` -For that snippet, `fbt` will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, `fbt` will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in the `sources` list. Additionally, `fbt` will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. -For the `loclass` library, `fbt` will add `lib/loclass` to the list of the include paths for the application and build all sources in that folder. Also, `fbt` will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases. +For that snippet, `fbt` will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, `fbt` will add `lib/mbedtls/include` to the list of include paths for the app and compile only the files specified in the `sources` list. Additionally, `fbt` will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. +For the `loclass` library, `fbt` will add `lib/loclass` to the list of the included paths for the app and build all sources in that folder. Also, `fbt` will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases. -Both libraries will be linked with the application. +Both libraries will be linked with the app. ## .fam file contents -The `.fam` file contains one or more application definitions. For example, here's a part of `applications/service/bt/application.fam`: +The `.fam` file contains one or more app definitions. For example, here's a part of `applications/service/bt/application.fam`: ```python App( diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index cb8106fc6cf..8bc73b1ab40 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -1,29 +1,29 @@ -# FAP (Flipper Application Package) {#apps_on_sd_card} +# FAP (Flipper App Package) {#apps_on_sd_card} -[fbt](./fbt.md) supports building applications as FAP files. FAPs are essentially `.elf` executables with extra metadata and resources bundled in. +[fbt](./fbt.md) supports building apps as FAP files. FAPs are essentially `.elf` executables with extra metadata and resources bundled in. FAPs are built with the `faps` target. They can also be deployed to the `dist` folder with the `fap_dist` target. FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). -## How to set up an application to be built as a FAP {#fap-howto} +## How to set up an app to be built as a FAP {#fap-howto} -FAPs are created and developed the same way as internal applications that are part of the firmware. +FAPs are created and developed the same way as internal apps that are part of the firmware. -To build your application as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest, and set its _apptype_ to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details. +To build your app as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in app. Then configure its `application.fam` manifest, and set its `apptype` to `FlipperAppType.EXTERNAL`. See [Flipper App Manifests](AppManifests.md) for more details. -- To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. +- To build your app, run `./fbt fap_{APPID}`, where APPID is your app's ID in its manifest. - To build your app and upload it over USB to run on Flipper, use `./fbt launch APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu). - To build an app without uploading it to Flipper, use `./fbt build APPSRC=applications_user/path/to/app`. This command is also available in VSCode configuration as "Build App". - To build all FAPs, run `./fbt faps` or `./fbt fap_dist`. ## FAP assets -FAPs can include static and animated images as private assets. They will be automatically compiled alongside application sources and can be referenced the same way as assets from the main firmware. +FAPs can include static and animated images as private assets. They will be automatically compiled alongside app sources and can be referenced the same way as assets from the main firmware. -To use that feature, put your images in a subfolder inside your application's folder, then reference that folder in your application's manifest in the `fap_icon_assets` field. See [Application Manifests](AppManifests.md) for more details. +To use that feature, put your images in a subfolder inside your app's folder, then reference that folder in your app's manifest in the `fap_icon_assets` field. See [Flipper App Manifests](AppManifests.md) for more details. -To use these assets in your application, put `#include "{APPID}_icons.h"` in your application's source code, where `{APPID}` is the `appid` value field from your application's manifest. Then you can use all icons from your application's assets the same way as if they were a part of `assets_icons.h` of the main firmware. +To use these assets in your app, put `#include "{APPID}_icons.h"` in your app's source code, where `{APPID}` is the `appid` value field from your app's manifest. Then you can use all icons from your app's assets the same way as if they were a part of `assets_icons.h` of the main firmware. Images and animated icons should follow the same [naming convention](../assets/ReadMe.md) as those from the main firmware. @@ -33,11 +33,11 @@ Images and animated icons should follow the same [naming convention](../assets/R With it, you can debug FAPs as if they were a part of the main firmware — inspect variables, set breakpoints, step through the code, etc. -If debugging session is active, firmware will trigger a breakpoint after loading a FAP it into memory, but before running any code from it. This allows you to set breakpoints in the FAP's code. Note that any breakpoints set before the FAP is loaded may need re-setting after the FAP is actually loaded, since before loading it debugger cannot know the exact address of the FAP's code. +If debugging session is active, firmware will trigger a breakpoint after loading a FAP into memory, but before running any code from it. This allows you to set breakpoints in the FAP's code. Note that any breakpoints set before the FAP is loaded may need re-setting after the FAP is actually loaded, since the debugger cannot know the exact address of the FAP's code before loading the FAP. ### Setting up debugging environment -The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](./fbt.md#nb) for details. +The debugging support script looks up debugging information in the latest firmware build directory (`build/latest`). That directory is symlinked by `fbt` to the latest firmware configuration (Debug or Release) build directory when you run `./fbt` for the chosen configuration. See [fbt docs](fbt.md) for details. To debug FAPs, do the following: @@ -45,23 +45,23 @@ To debug FAPs, do the following: 2. Flash it with `./fbt flash` 3. [Build your FAP](#fap-howto) and run it on Flipper -After that, you can attach with `./fbt debug` or VS Code and use all debug features. +After that, you can attach the debugger to the target MCU with `./fbt debug` or VS Code and use all debug features. -It is **important** that firmware and application build type (debug/release) match and that the matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work. +It is **important** that firmware and app build type (debug/release) match and that the matching firmware folder is linked as `build/latest`. Otherwise, debugging will not work. -## How Flipper runs an application from an SD card +## How Flipper runs an app from an SD card -Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader application responsible for loading the FAP from the SD card, verifying its integrity and compatibility, copying it to RAM, and adjusting it for its new location. +Flipper's MCU cannot run code directly from external storage, so it needs to be copied to RAM first. That is done by the App Loader responsible for loading the FAP from the SD card, verifying its integrity and compatibility, copying it to RAM, and adjusting it for its new location. -Since FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of the firmware. Note that the amount of occupied RAM is less than the total FAP file size since only code and data sections are allocated, while the FAP file includes extra information only used at app load time. +Since the FAP has to be loaded to RAM to be executed, the amount of RAM available for allocations from heap is reduced compared to running the same app from flash, as a part of the firmware. Note that the amount of occupied RAM is less than the total FAP file size since only code and data sections are allocated, while the FAP file includes extra information only used at app load time. -Applications are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. The App Loader checks if the application's major API version matches the firmware's major API version. +Apps are built for a specific API version. It is a part of the hardware target's definition and contains a major and minor version number. The App Loader checks if the app's major API version matches the firmware's major API version. -The App Loader allocates memory for the application and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the application. +The App Loader allocates memory for the app and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the app. ## API versioning {#api-versioning} -Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `targets/` directory. +Not all parts of firmware are available for external apps. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `targets/` directory. `fbt` uses semantic versioning for the API. The major version is incremented when there are breaking changes in the API. The minor version is incremented when new features are added. @@ -78,6 +78,6 @@ API versioning is mostly automated by `fbt`. When rebuilding the firmware, `fbt` ### Symbol table {#symbol-table} -The symbol table is a list of symbols exported by firmware and available for external applications. It is generated by `fbt` from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` application. +The symbol table is a list of symbols exported by firmware and available for external apps. It is generated by `fbt` from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` app. -`fbt` also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The application won't be able to run on the device until all required symbols are provided in the symbol table. +`fbt` also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The app won't be able to run on the device until all required symbols are provided in the symbol table. diff --git a/documentation/ExpansionModules.md b/documentation/ExpansionModules.md index fd9703adcc4..2fbdb738f8b 100644 --- a/documentation/ExpansionModules.md +++ b/documentation/ExpansionModules.md @@ -29,7 +29,7 @@ Depending on the UART selected for communication, the following pins area availa ## Frame structure -Each frame consists of a header (1 byte), contents (size depends of frame type) and checksum (1 byte) fields: +Each frame consists of a header (1 byte), contents (size depends on frame type) and checksum (1 byte) fields: | Header (1 byte) | Contents (0 or more bytes) | Checksum (1 byte) | |-----------------|----------------------------|-------------------| @@ -79,7 +79,7 @@ CONTROL frames are used to control various aspects of the communication and enab |-----------------|-------------------|-------------------| | 0x04 | Command | XOR checksum | -The `Command` field SHALL have one of the followind values: +The `Command` field SHALL have one of the following values: | Command | Meaning | Note | |---------|--------------------------|:----:| @@ -96,7 +96,7 @@ Notes: ### Data frame -DATA frames are used to transmit arbitrary data in either direction. Each DATA frame can hold up to 64 bytes. If an RPC session is curretly open, all received bytes are forwarded to it. +DATA frames are used to transmit arbitrary data in either direction. Each DATA frame can hold up to 64 bytes. If an RPC session is currently open, all received bytes are forwarded to it. | Header (1 byte) | Contents (1 to 65 byte(s)) | Checksum (1 byte) | |-----------------|----------------------------|-------------------| @@ -110,7 +110,7 @@ The `Data` field SHALL have the following structure: ## Communication flow -In order for the host to be able to detect the module, the respective feature must be enabled first. This can be done via the GUI by going to `Settings -> Expansion Modules` and selecting the required `Listen UART` or programmatically by calling `expansion_enable()`. Likewise, disabling this feature via the same GUI or by calling `expansion_disable()` will result in ceasing all communications and not being able to detect any connected modules. +In order for the host to be able to detect the module, the respective feature must be enabled first. This can be done via the GUI by going to `Settings → Expansion Modules` and selecting the required `Listen UART` or programmatically by calling `expansion_enable()`. Likewise, disabling this feature via the same GUI or by calling `expansion_disable()` will result in ceasing all communications and not being able to detect any connected modules. The communication is always initiated by the module by the means of shortly pulling the RX pin down. The host SHALL respond with a HEARTBEAT frame indicating that it is ready to receive requests. The module then MUST issue a BAUDRATE request within Tto. Failure to do so will result in the host dropping the connection and returning to its initial state. diff --git a/documentation/FuriCheck.md b/documentation/FuriCheck.md index 77a44ca84bf..ead964af972 100644 --- a/documentation/FuriCheck.md +++ b/documentation/FuriCheck.md @@ -1,13 +1,13 @@ # Run time checks and forced system crash {#furi_check} The best way to protect system integrity is to reduce amount cases that we must handle and crash the system as early as possible. -For that purpose we have bunch of helpers located in Furi Core check.h. +For that purpose, we have a bunch of helpers located in Furi Core `check.h`. ## Couple notes before start -- Definition of Crash - log event, save crash information in RTC and reboot the system. -- Definition of Halt - log event, stall the system. -- Debug and production builds behaves differently: debug build will never reset system in order to preserve state for debugging. +- Definition of Crash — log event, save crash information in RTC and reboot the system. +- Definition of Halt — log event, stall the system. +- Debug and production builds behave differently: debug build will never reset system in order to preserve state for debugging. - If you have debugger connected we will stop before reboot automatically. - All helpers accept optional MESSAGE_CSTR: it can be in RAM or Flash memory, but only messages from Flash will be shown after system reboot. - MESSAGE_CSTR can be NULL, but macros magic already doing it for you, so just don't. @@ -16,10 +16,10 @@ For that purpose we have bunch of helpers located in Furi Core check.h. Assert condition in development environment and crash the system if CONDITION is false. -- Should be used at development stage in apps and services -- Keep in mind that release never contains this check -- Keep in mind that libraries never contains this check by default, use `LIB_DEBUG=1` if you need it -- Avoid putting function calls into CONDITION, since it may be omitted in some builds +- Should be used at development stage in apps and services. +- Keep in mind that release never contains this check. +- Keep in mind that libraries never contain this check by default, use `LIB_DEBUG=1` if you need it. +- Avoid putting function calls into CONDITION, since it may be omitted in some builds. ## `furi_check(CONDITION)` or `furi_check(CONDITION, MESSAGE_CSTR)` @@ -31,10 +31,10 @@ Always assert condition and crash the system if CONDITION is false. Crash the system. -- Use it to crash the system. For example: if abnormal condition detected. +- Use it to crash the system. For example, if an abnormal condition is detected. ## `furi_halt()` or `furi_halt(MESSAGE_CSTR)` Halt the system. -- We use it internally to shutdown flipper if poweroff is not possible. +- We use it internally to shutdown Flipper if poweroff is not possible. diff --git a/documentation/FuriHalBus.md b/documentation/FuriHalBus.md index 12c5a70ece1..5e7bb5f402f 100644 --- a/documentation/FuriHalBus.md +++ b/documentation/FuriHalBus.md @@ -5,7 +5,7 @@ On system startup, most of the peripheral devices are under reset and not clocked by default. This is done to reduce power consumption and to guarantee that the device will always be in the same state before use. Some crucial peripherals are enabled right away by the system, others must be explicitly enabled by the user code. -**NOTE:** Here and afterwards the word *"system"* refers to any code belonging to the operating system, hardware drivers or built-in applications. +**NOTE:** Here and afterwards, the word *"system"* refers to any code belonging to the operating system, hardware drivers or built-in apps. To **ENABLE** a peripheral, call `furi_hal_bus_enable()`. At the time of the call, the peripheral in question MUST be disabled, otherwise a crash will occur to indicate improper use. This means that any given peripheral cannot be enabled twice or more without disabling it first. @@ -24,7 +24,7 @@ Built-in peripherals are divided into three categories: Below is the list of peripherals that are enabled by the system. The user code must NEVER attempt to disable them. If a corresponding API is provided, the user code must employ it in order to access the peripheral. -*Table 1* - Peripherals enabled by the system +*Table 1* — Peripherals enabled by the system | Peripheral | Enabled at | | :-----------: | :-----------------------: | @@ -49,7 +49,7 @@ Below is the list of peripherals that are enabled and disabled by the system. Th When not using the API, these peripherals MUST be enabled by the user code and then disabled when not needed anymore. -*Table 2* - Peripherals enabled and disabled by the system +*Table 2* — Peripherals enabled and disabled by the system | Peripheral | API header file | | :-----------: | :-------------------: | @@ -69,7 +69,7 @@ Below is the list of peripherals that are not enabled by default and MUST be ena Note that some of these peripherals may also be used by the system to implement its certain features. The system will take over any given peripheral only when the respective feature is in use. -*Table 3* - Peripherals enabled and disabled by user +*Table 3* — Peripherals enabled and disabled by user | Peripheral | System | Purpose | | :-----------: | :-------: | ------------------------------------- | @@ -93,7 +93,7 @@ The DMA1,2 peripherals are a special case in that they have multiple independent Below is the list of DMA channels and their usage by the system. -*Table 4* - DMA channels +*Table 4* — DMA channels | DMA | Channel | System | Purpose | | :---: | :-------: | :-------: | ------------------------- | diff --git a/documentation/FuriHalDebuging.md b/documentation/FuriHalDebuging.md index 5104a999829..bc7d532d21b 100644 --- a/documentation/FuriHalDebuging.md +++ b/documentation/FuriHalDebuging.md @@ -1,7 +1,7 @@ # Furi HAL Debugging {#furi_hal_debugging} -Some Furi subsystems got additional debugging features that can be enabled by adding additional defines to firmware compilation. -Usually they are used for low level tracing and profiling or signal redirection/duplication. +Some Furi subsystems have additional debugging features that can be enabled by adding additional defines to firmware compilation. +Usually, they are used for low level tracing and profiling or signal redirection/duplication. ## FuriHalOs @@ -10,9 +10,9 @@ Usually they are used for low level tracing and profiling or signal redirection/ There are 3 signals that will be exposed to external GPIO pins: -- `AWAKE` - `PA7` - High when system is busy with computations, low when sleeping. Can be used to track transitions to sleep mode. -- `TICK` - `PA6` - Flipped on system tick, only flips when no tick suppression in progress. Can be used to track tick skew and abnormal task scheduling. -- `SECOND` - `PA4` - Flipped each second. Can be used for tracing RT issue: time flow disturbance means system doesn't conforms Hard RT. +- `AWAKE` — `PA7` — High when system is busy with computations, low when sleeping. Can be used to track transitions to sleep mode. +- `TICK` — `PA6` — Flipped on system tick, only flips when no tick suppression in progress. Can be used to track tick skew and abnormal task scheduling. +- `SECOND` — `PA4` — Flipped each second. Can be used for tracing RT issue: time flow disturbance means system doesn't conform Hard RT. @@ -22,8 +22,8 @@ There are 3 signals that will be exposed to external GPIO pins: There are 2 signals that will be exposed to external GPIO pins: -- `WFI` - `PB2` - Light sleep (wait for interrupt) used. Basically this is lightest and most non-breaking things power save mode. All function and debug should work correctly in this mode. -- `STOP` - `PC3` - STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible. +- `WFI` — `PB2` — Light sleep (wait for interrupt) used. Basically, this is the lightest and most non-breaking things power save mode. All functions and debug should work correctly in this mode. +- `STOP` — `PC3` — STOP mode used. Platform deep sleep mode. Extremely fragile mode where most of the silicon is disabled or in unusable state. Debugging MCU in this mode is nearly impossible. ## FuriHalSD diff --git a/documentation/HardwareTargets.md b/documentation/HardwareTargets.md index 9c36088eac8..e35ee7991b1 100644 --- a/documentation/HardwareTargets.md +++ b/documentation/HardwareTargets.md @@ -25,20 +25,20 @@ A target definition file, `target.json`, is a JSON file that can contain the fol * `excluded_modules`: list of strings specifying fbt library (module) names to exclude from being used to configure build environment. -## Applications & Hardware +## Apps & Hardware -Not all applications are available on different hardware targets. +Not all apps are available on different hardware targets. -* For applications built into the firmware, you have to specify a compatible application set using `FIRMWARE_APP_SET=...` fbt option. See [fbt docs](./fbt.md) for details on build configurations. +* For apps built into the firmware, you have to specify a compatible app set using `FIRMWARE_APP_SET=...` fbt option. See [fbt docs](./fbt.md) for details on build configurations. -* For applications built as external .faps, you have to explicitly specify compatible targets in application's manifest, `application.fam`. For example, to limit application to a single target, add `targets=["f7"],` to the manifest. It won't be built for other targets. +* For apps built as external FAPs, you have to explicitly specify compatible targets in the app's manifest, `application.fam`. For example, to limit the app to a single target, add `targets=["f7"],` to the manifest. It won't be built for other targets. -For details on application manifests, check out [their docs page](./AppManifests.md). +For details on app manifests, check out [their docs page](./AppManifests.md). ## Building Firmware for a Specific Target -You have to specify TARGET_HW (and, optionally, FIRMWARE_APP_SET) for `fbt` to build firmware for non-default target. For example, building and flashing debug firmware for f18 can be done with +You have to specify TARGET_HW (and, optionally, FIRMWARE_APP_SET) for `fbt` to build firmware for a non-default target. For example, building and flashing debug firmware for f18 can be done with ./fbt TARGET_HW=18 flash_usb_full diff --git a/documentation/KeyCombo.md b/documentation/KeyCombo.md index e3c5e000432..09bbb1ee0bd 100644 --- a/documentation/KeyCombo.md +++ b/documentation/KeyCombo.md @@ -10,7 +10,7 @@ There are times when your Flipper feels blue and doesn't respond to any of your - Release `LEFT` and `BACK` This combo performs a hardware reset by pulling the MCU reset line down. -Main components involved: Keys -> DD8(NC7SZ32M5X, OR-gate) -> DD1(STM32WB55, MCU). +Main components involved: Keys → DD8(NC7SZ32M5X, OR-gate) → DD1(STM32WB55, MCU). It won't work only in one case: @@ -26,7 +26,7 @@ It won't work only in one case: - Release the `BACK` key This combo performs a reset by switching SYS power line off and then on. -Main components involved: Keys -> DD6(bq25896, charger). +Main components involved: Keys → DD6(bq25896, charger). It won't work only in one case: diff --git a/documentation/OTA.md b/documentation/OTA.md index 9783a704770..1499ab74b6a 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -2,7 +2,7 @@ ## Executing code from RAM -In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. System image executing in RAM has full write access to Flipper's entire flash memory — something that's not possible when running main code from the same flash. +In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. The system image executing in RAM has full write access to Flipper's entire flash memory — something that's not possible when running main code from the same flash. We leverage that boot mode to perform OTA firmware updates, including operations on a radio stack running on the second MCU core. diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index b77cd56c6ec..5c80e763d4f 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -5,7 +5,7 @@ Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they are correct. They are crucial for writing robust, bug-free code. -Flipper Zero firmware includes a separate application called [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests). +Flipper Zero firmware includes a separate app called [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests). It is run directly on Flipper devices in order to employ their hardware features and rule out any platform-related differences. When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features. @@ -28,13 +28,13 @@ See [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/blob/d #### Entry point -The common entry point for all tests is the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/test_index.c) source file. +The common entry point for all tests is the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests) app. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/test_index.c) source file. #### Test assets Some unit tests require external data in order to function. These files (commonly called assets) reside in the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.). -### Application-specific +### App-specific #### Infrared @@ -50,9 +50,9 @@ To add unit tests for your protocol, follow these steps: Each unit test has three sections: -1. `decoder` - takes in a raw signal and outputs decoded messages. -2. `encoder` - takes in decoded messages and outputs a raw signal. -3. `encoder_decoder` - takes in decoded messages, turns them into a raw signal, and then decodes again. +1. `decoder` — takes in a raw signal and outputs decoded messages. +2. `encoder` — takes in decoded messages and outputs a raw signal. +3. `encoder_decoder` — takes in decoded messages, turns them into a raw signal, and then decodes again. Infrared test asset files have an `.irtest` extension and are regular `.ir` files with a few additions. Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder, these two are switched. @@ -61,4 +61,4 @@ Decoded data is represented in arrays (since a single raw signal may be decoded ##### Getting raw signals -Recording raw IR signals are possible using the Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format. +Recording raw IR signals is possible using Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards the Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format. diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 360d8a0abb2..65f64526fbc 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -41,11 +41,11 @@ When the user presses a button, a whole set of parameters is transmitted to the In order to add a particular air conditioner to the universal remote, 6 signals must be recorded: `Off`, `Dh`, `Cool_hi`, `Cool_lo`, `Heat_hi`, and `Heat_lo`. Each signal (except `Off`) is recorded using the following algorithm: -1. Get the remote and press the **Power Button** so that the display shows that A/C is ON. +1. Get the remote and press the **POWER** button so that the display shows that A/C is ON. 2. Set the A/C to the corresponding mode (see table below), leaving other parameters such as fan speed or vane on **AUTO** (if applicable). 3. Press the **POWER** button to switch the A/C off. 4. Start learning a new remote on Flipper if it's the first button or press `+` to add a new button otherwise. -5. Point the remote to Flipper's IR receiver as directed and press **POWER** button once again. +5. Point the remote to Flipper's IR receiver as directed and press the **POWER** button once again. 6. Save the resulting signal under the specified name. 7. Repeat steps 2-6 for each signal from the table below. diff --git a/documentation/devboard/Reading logs via the Dev Board.md b/documentation/devboard/Reading logs via the Dev Board.md index e9fc0e2ca8f..c2daf83aced 100644 --- a/documentation/devboard/Reading logs via the Dev Board.md +++ b/documentation/devboard/Reading logs via the Dev Board.md @@ -8,7 +8,7 @@ The Developer Board allows you to read Flipper Zero logs via UART. Unlike readin ## Setting the log level -Depending on your needs, you can set the log level by going to **Main Menu -> Settings -> Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipperzero.one/basics/settings#d5TAt). +Depending on your needs, you can set the log level by going to **Main Menu → Settings → Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipperzero.one/basics/settings#d5TAt). ![You can manually set the preferred log level](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/INzQMw8QUsG9PXi30WFS0_monosnap-miro-2023-07-11-13-29-47.jpg) @@ -145,7 +145,7 @@ On Windows, do the following: 3. Connect the developer board to your computer using a USB Type-C cable. ![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) -4. Find the serial port that the developer board is connected to by going to **Device Manager -> Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board. +4. Find the serial port that the developer board is connected to by going to **Device Manager → Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board. ![Find the serial port in your Device Manager](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KKLQJK1lvqmI5iab3d__C_image.png) 5. Run the PuTTY application and select **Serial** as the connection type. diff --git a/documentation/doxygen/Doxyfile.cfg b/documentation/doxygen/Doxyfile.cfg index a7838163b0d..90f36415f15 100644 --- a/documentation/doxygen/Doxyfile.cfg +++ b/documentation/doxygen/Doxyfile.cfg @@ -42,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "Flipper Zero Firmware" +PROJECT_NAME = "Flipper Developer Docs" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version diff --git a/documentation/doxygen/app_publishing.dox b/documentation/doxygen/app_publishing.dox new file mode 100644 index 00000000000..747891221ee --- /dev/null +++ b/documentation/doxygen/app_publishing.dox @@ -0,0 +1,7 @@ +/** +@page app_publishing Publishing to the Apps Catalog + +You can publish your app in the Flipper Apps Catalog. Users will be able to download your app and install it on their Flipper Zero via [mobile apps](https://flpr.app/) and [Flipper Lab](https://lab.flipper.net/apps). Check out the documentation below: + +- [Apps Catalog: Contribution Guide](https://github.com/flipperdevices/flipper-application-catalog/blob/main/documentation/Contributing.md) — How to publish and update your app in the Apps Catalog +*/ diff --git a/documentation/doxygen/applications.dox b/documentation/doxygen/applications.dox index ad0dfba8d80..7c21a6e8d95 100644 --- a/documentation/doxygen/applications.dox +++ b/documentation/doxygen/applications.dox @@ -1,12 +1,12 @@ /** -@page applications Application Programming +@page applications App Development -Flipper Zero features full support for custom applications which (usually) do not require any changes to the firmware. +Flipper Zero features full support for custom apps which (usually) do not require any changes to the firmware. -For easy application development, a software tool called [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) is available. +For easy app development, a software tool called [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) is available. -- @subpage vscode - Flipper Zero integration for VS Code -- @subpage apps_on_sd_card - Creating apps that can be dynamically loaded from the SD card -- @subpage app_manifests - How applications announce themselves to the system -- @subpage app_examples - Various application examples, complete with the source code +- @subpage apps_on_sd_card — Creating apps that can be dynamically loaded from the SD card +- @subpage app_manifests — How apps announce themselves to the system +- @subpage app_examples — Various app examples, complete with the source code +- @subpage app_publishing — Learn how to publish and update your app in the Apps Catalog */ diff --git a/documentation/doxygen/dev_board.dox b/documentation/doxygen/dev_board.dox index f9363ed0690..6caa44c7050 100644 --- a/documentation/doxygen/dev_board.dox +++ b/documentation/doxygen/dev_board.dox @@ -3,8 +3,8 @@ [ESP32-based development board](https://shop.flipperzero.one/collections/flipper-zero-accessories/products/wifi-devboard). -- @subpage dev_board_get_started - Quick start for new users -- @subpage dev_board_reading_logs - Find out what is currently happening on the system -- @subpage dev_board_fw_update - Keep the developer board up to date +- @subpage dev_board_get_started — Quick start for new users +- @subpage dev_board_reading_logs — Find out what is currently happening on the system +- @subpage dev_board_fw_update — Keep the developer board up to date */ diff --git a/documentation/doxygen/dev_tools.dox b/documentation/doxygen/dev_tools.dox index bd7a5c704fc..e3c589faced 100644 --- a/documentation/doxygen/dev_tools.dox +++ b/documentation/doxygen/dev_tools.dox @@ -3,7 +3,8 @@ Hardware and software tools for all kinds of programming. -- @subpage fbt - Official build and deployment tool for Flipper Zero -- @subpage dev_board - ESP32-based development board -- @subpage ota_updates - Standalone firmware self-update mechanism +- @subpage fbt — Official build and deployment tool for Flipper Zero +- @subpage vscode — Flipper Zero integration for VS Code +- @subpage dev_board — ESP32-based development board +- @subpage ota_updates — Standalone firmware self-update mechanism */ diff --git a/documentation/doxygen/examples.dox b/documentation/doxygen/examples.dox index 9743549a2df..43039d2f700 100644 --- a/documentation/doxygen/examples.dox +++ b/documentation/doxygen/examples.dox @@ -1,10 +1,13 @@ /** -@page app_examples Application Examples +@page app_examples App Examples -A collection of examples covering various aspects of application programming for Flipper Zero. +A collection of examples covering various aspects of app development for Flipper Zero. -- @subpage example_app_images - Using images and icons in an application -- @subpage example_app_assets - Using application-specific asset folders -- @subpage example_app_data - Using application-specific data folders -- @subpage example_thermo - Reading data from a 1-Wire thermometer +- @subpage example_number_input — Using a simple keyboard that limits user inputs to a full number (integer) +- @subpage example_app_images — Using images and icons in an app +- @subpage example_app_assets — Using app-specific asset folders +- @subpage example_app_data — Using app-specific data folders +- @subpage example_thermo — Reading data from a 1-Wire thermometer + +You can find more app examples in the [repository on GitHub](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples). */ diff --git a/documentation/doxygen/expansion_modules.dox b/documentation/doxygen/expansion_modules.dox index c38bb2923f6..5e9731b0212 100644 --- a/documentation/doxygen/expansion_modules.dox +++ b/documentation/doxygen/expansion_modules.dox @@ -3,6 +3,6 @@ Expansion modules are special pieces of hardware designed to interface with Flipper's GPIO connector, such as the [Video Game Module](https://shop.flipperzero.one/collections/flipper-zero-accessories/products/video-game-module-for-flipper-zero). -- @subpage expansion_protocol - Transport protocol for smart expansion modules +- @subpage expansion_protocol — Transport protocol for smart expansion modules */ diff --git a/documentation/doxygen/file_formats.dox b/documentation/doxygen/file_formats.dox index 47c2362cf75..2ec780d010d 100644 --- a/documentation/doxygen/file_formats.dox +++ b/documentation/doxygen/file_formats.dox @@ -9,5 +9,6 @@ Descriptions of various file formats used in Flipper Zero, grouped by applicatio - @subpage lfrfid_file_format - @subpage nfc_file_format - @subpage subghz_file_format +- @subpage heatshrink_file_format */ diff --git a/documentation/doxygen/header.html b/documentation/doxygen/header.html index cd3ea49e75b..5cc0aba38f3 100644 --- a/documentation/doxygen/header.html +++ b/documentation/doxygen/header.html @@ -48,11 +48,11 @@ - Logo + Logo -
$projectname $projectnumber +
$projectname $projectnumber
$projectbrief
diff --git a/documentation/doxygen/index.dox b/documentation/doxygen/index.dox index 78055caad04..7bd9024a116 100644 --- a/documentation/doxygen/index.dox +++ b/documentation/doxygen/index.dox @@ -1,26 +1,26 @@ /** @mainpage Overview -Welcome to the Flipper Zero Firmware Developer Documentation! +Welcome to the Flipper Developer Documentation! -This documentation is intended for developers who want to modify the firmware of the Flipper Zero. +This documentation is intended for developers interested in modifying the Flipper Zero firmware, creating Apps and JavaScript programs, or working on external hardware modules for the device. -If you are looking for the user manual, please visit the [User Documentation](https://docs.flipperzero.one/) instead. +If you are looking for the user manual, please visit the [User Documentation](https://docs.flipper.net/) instead. -The documentation is divided into several sections, with all of them accessible from the sidebar on the left: +The documentation is divided into several sections. All of them are accessible from the sidebar on the left: -- @ref applications - Writing applications for Flipper Zero -- @ref system - Understanding the firmware's internals -- @ref file_formats - Saving and loading data to and from files -- @ref dev_tools - Hardware and software tools for all kinds of programming -- @ref expansion - Additional modules to expand Flipper's consciousness -- @ref misc - Various useful pieces of information -- @ref js - JS-based scripting engine documentation +- @ref dev_tools — Hardware and software tools for all kinds of programming +- @ref system — Understanding the firmware's internals +- @ref applications — Developing apps for Flipper Zero +- @ref js — JS-based scripting engine +- @ref expansion — Additional modules to expand Flipper's consciousness +- @ref file_formats — Saving and loading data to and from files +- @ref misc — Various useful pieces of information -Aside from the manually-written documentation files, there's also a few automatically-generated ones at the bottom of the sidebar: +These sections are all manually written. There are also a few automatically generated ones at the bottom of the sidebar: -- [Data Structures](annotated.html) - Every data structure in a list -- [Files](files.html) - Source file tree with easy navigation +- [Data Structures](annotated.html) — Every data structure in a list +- [Files](files.html) — Source file tree with easy navigation These are generated from the source code and are useful for quickly finding the source code or API documentation for a particular function or data structure. */ diff --git a/documentation/doxygen/js.dox b/documentation/doxygen/js.dox index f4faf668fea..33ac078d923 100644 --- a/documentation/doxygen/js.dox +++ b/documentation/doxygen/js.dox @@ -1,21 +1,22 @@ /** @page js JavaScript -This page contains some information on the Flipper Zero scripting engine, which is based on a modified mJS library +This page contains some information on the Flipper Zero scripting engine, which is based on a modified mJS library. - [Brief mJS description](https://github.com/cesanta/mjs/blob/master/README.md) - @subpage js_data_types - @subpage js_builtin -JavaScript Modules -JS modules use the Flipper app plugin system. Each module is compiled into a .fal library file and is located on a microSD card. Here is a list of implemented modules: +## JavaScript modules -- @subpage js_badusb - BadUSB module -- @subpage js_serial - Serial module -- @subpage js_math - Math module -- @subpage js_dialog - Dialog module -- @subpage js_submenu - Submenu module -- @subpage js_textbox - Textbox module -- @subpage js_notification - Notifications module +JS modules use the Flipper app plugin system. Each module is compiled into a `.fal` library file and is located on a microSD card. Here is a list of implemented modules: + +- @subpage js_badusb — BadUSB module +- @subpage js_serial — Serial module +- @subpage js_math — Math module +- @subpage js_dialog — Dialog module +- @subpage js_submenu — Submenu module +- @subpage js_textbox — Textbox module +- @subpage js_notification — Notifications module */ diff --git a/documentation/doxygen/misc.dox b/documentation/doxygen/misc.dox index 0ef232ba249..f49a9524ab3 100644 --- a/documentation/doxygen/misc.dox +++ b/documentation/doxygen/misc.dox @@ -3,7 +3,7 @@ Various pieces of information that do not fall into other categories. -- @subpage lfrfid_raw - Collecting raw data from LFRFID tags -- @subpage key_combos - Different key combination shortcuts for Flipper Zero -- @subpage universal_remotes - Creating and improving IR universal remote libraries +- @subpage lfrfid_raw — Collecting raw data from LF RFID tags +- @subpage key_combos — Different key combination shortcuts for Flipper Zero +- @subpage universal_remotes — Creating and improving IR universal remote libraries */ diff --git a/documentation/doxygen/system.dox b/documentation/doxygen/system.dox index 328717ea21e..d7e42ed4fd1 100644 --- a/documentation/doxygen/system.dox +++ b/documentation/doxygen/system.dox @@ -1,13 +1,13 @@ /** @page system System Programming -Lower level aspects of software development for Flipper Zero. +Lower-level aspects of software development for Flipper Zero. -- @subpage unit_tests - Automated testing, a crucial part of the development process -- @subpage furi_check - Hard checks for exceptional situations -- @subpage furi_hal_bus - Access the on-chip peripherals in a safe way -- @subpage furi_hal_debugging - Low level debugging features -- @subpage hardware_targets - Support for different hardware platforms -- @subpage firmware_assets - Various files required for building the firmware -- @subpage dolphin_assets - Animations for the Dolphin game +- @subpage unit_tests — Automated testing, a crucial part of the development process +- @subpage furi_check — Hard checks for exceptional situations +- @subpage furi_hal_bus — Access the on-chip peripherals in a safe way +- @subpage furi_hal_debugging — Low-level debugging features +- @subpage hardware_targets — Support for different hardware platforms +- @subpage firmware_assets — Various files required for building the firmware +- @subpage dolphin_assets — Animations for the Dolphin game */ diff --git a/documentation/fbt.md b/documentation/fbt.md index fee003abb09..59f6aa154eb 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -3,7 +3,7 @@ FBT is the entry point for firmware-related commands and utilities. It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. -If you don't need all features of `fbt` - like building the whole firmware - and only want to build and debug a single application, you can use [ufbt](https://pypi.org/project/ufbt/). +If you don't need all features of `fbt` — like building the whole firmware — and only want to build and debug a single app, you can use [ufbt](https://pypi.org/project/ufbt/). ## Environment @@ -40,7 +40,7 @@ To run cleanup (think of `make clean`) for specified targets, add the `-c` optio `fbt` builds updater & firmware in separate subdirectories in `build`, and their names depend on optimization settings (`COMPACT` & `DEBUG` options). However, for ease of integration with IDEs, the latest built variant's directory is always linked as `built/latest`. Additionally, `compile_commands.json` is generated in that folder (it is used for code completion support in IDEs). -`build/latest` symlink & compilation database are only updated upon *firmware build targets* - that is, when you're re-building the firmware itself. Running other tasks, like firmware flashing or building update bundles *for a different debug/release configuration or hardware target*, does not update `built/latest` dir to point to that configuration. +`build/latest` symlink & compilation database are only updated upon *firmware build targets* — that is, when you're re-building the firmware itself. Running other tasks, like firmware flashing or building update bundles *for a different debug/release configuration or hardware target*, does not update `built/latest` dir to point to that configuration. ## VSCode integration @@ -51,7 +51,7 @@ To use language servers other than the default VS Code C/C++ language server, us - On the first start, you'll be prompted to install recommended plugins. We highly recommend installing them for the best development experience. _You can find a list of them in `.vscode/extensions.json`._ - Basic build tasks are invoked in the Ctrl+Shift+B menu. - Debugging requires a supported probe. That includes: - - Wi-Fi devboard with stock firmware (blackmagic). + - Wi-Fi Devboard with stock firmware (blackmagic). - ST-Link and compatible devices. - J-Link for flashing and debugging (in VSCode only). _Note that J-Link tools are not included with our toolchain and you have to [download](https://www.segger.com/downloads/jlink/) them yourself and put them on your system's PATH._ - Without a supported probe, you can install firmware on Flipper using the USB installation method. @@ -62,70 +62,70 @@ To use language servers other than the default VS Code C/C++ language server, us ### High-level (what you most likely need) -- `fw_dist` - build & publish firmware to the `dist` folder. This is a default target when no others are specified. -- `fap_dist` - build external plugins & publish to the `dist` folder. -- `updater_package`, `updater_minpackage` - build a self-update package. The minimal version only includes the firmware's DFU file; the full version also includes a radio stack & resources for the SD card. -- `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper. -- `flash` - flash the attached device over SWD interface with supported probes. Probe is detected automatically; you can override it with `SWD_TRANSPORT=...` variable. If multiple probes are attached, you can specify the serial number of the probe to use with `SWD_TRANSPORT_SERIAL=...`. -- `flash_usb`, `flash_usb_full` - build, upload and install the update package to the device over USB. See details on `updater_package` and `updater_minpackage`. -- `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded. -- `debug_other`, `debug_other_blackmagic` - attach GDB without loading any `.elf`. It will allow you to manually add external `.elf` files with `add-symbol-file` in GDB. -- `updater_debug` - attach GDB with the updater's `.elf` loaded. -- `devboard_flash` - Update WiFi dev board. Supports `ARGS="..."` to pass extra arguments to the update script, e.g. `ARGS="-c dev"`. -- `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board). -- `openocd` - just start OpenOCD. You can pass extra arguments with `ARGS="..."`. -- `get_blackmagic` - output the blackmagic address in the GDB remote format. Useful for IDE integration. -- `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `SWD_TRANSPORT_SERIAL=...`. -- `lint`, `format` - run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. Supports `ARGS="..."` to pass extra arguments to clang-format. -- `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. Supports `ARGS="..."` to pass extra arguments to black. -- `lint_img`, `format_img` - check the image assets for errors and format them. Enforces color depth and strips metadata. -- `lint_all`, `format_all` - run all linters and formatters. -- `firmware_pvs` - generate a PVS Studio report for the firmware. Requires PVS Studio to be available on your system's `PATH`. -- `doxygen` - generate Doxygen documentation for the firmware. `doxy` target also opens web browser to view the generated documentation. -- `cli` - start a Flipper CLI session over USB. +- `fw_dist` — build & publish firmware to the `dist` folder. This is a default target when no others are specified. +- `fap_dist` — build external plugins & publish to the `dist` folder. +- `updater_package`, `updater_minpackage` — build a self-update package. The minimal version only includes the firmware's DFU file; the full version also includes a radio stack & resources for the SD card. +- `copro_dist` — bundle Core2 FUS+stack binaries for qFlipper. +- `flash` — flash the attached device over SWD interface with supported probes. Probe is detected automatically; you can override it with `SWD_TRANSPORT=...` variable. If multiple probes are attached, you can specify the serial number of the probe to use with `SWD_TRANSPORT_SERIAL=...`. +- `flash_usb`, `flash_usb_full` — build, upload and install the update package to the device over USB. See details on `updater_package` and `updater_minpackage`. +- `debug` — build and flash firmware, then attach with gdb with firmware's .elf loaded. +- `debug_other`, `debug_other_blackmagic` — attach GDB without loading any `.elf`. It will allow you to manually add external `.elf` files with `add-symbol-file` in GDB. +- `updater_debug` — attach GDB with the updater's `.elf` loaded. +- `devboard_flash` — Update WiFi dev board. Supports `ARGS="..."` to pass extra arguments to the update script, e.g. `ARGS="-c dev"`. +- `blackmagic` — debug firmware with Blackmagic probe (WiFi dev board). +- `openocd` — just start OpenOCD. You can pass extra arguments with `ARGS="..."`. +- `get_blackmagic` — output the blackmagic address in the GDB remote format. Useful for IDE integration. +- `get_stlink` — output serial numbers for attached STLink probes. Used for specifying an adapter with `SWD_TRANSPORT_SERIAL=...`. +- `lint`, `format` — run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. Supports `ARGS="..."` to pass extra arguments to clang-format. +- `lint_py`, `format_py` — run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & app manifests. Supports `ARGS="..."` to pass extra arguments to black. +- `lint_img`, `format_img` — check the image assets for errors and format them. Enforces color depth and strips metadata. +- `lint_all`, `format_all` — run all linters and formatters. +- `firmware_pvs` — generate a PVS Studio report for the firmware. Requires PVS Studio to be available on your system's `PATH`. +- `doxygen` — generate Doxygen documentation for the firmware. `doxy` target also opens web browser to view the generated documentation. +- `cli` — start a Flipper CLI session over USB. ### Firmware targets -- `faps` - build all external & plugin apps as [`.faps`](AppsOnSDCard.md). +- `faps` — build all external & plugin apps as [`.faps`](AppsOnSDCard.md). - `fbt` also defines per-app targets. For example, for an app with `appid=snake_game` target names are: - - `fap_snake_game`, etc. - build single app as `.fap` by its application ID. - - Check out [`--extra-ext-apps`](#command-line-parameters) for force adding extra apps to external build. - - `fap_snake_game_list`, etc - generate source + assembler listing for app's `.fap`. -- `flash`, `firmware_flash` - flash the current version to the attached device over SWD. -- `jflash` - flash the current version to the attached device with JFlash using a J-Link probe. The JFlash executable must be on your `$PATH`. -- `firmware_all`, `updater_all` - build a basic set of binaries. -- `firmware_list`, `updater_list` - generate source + assembler listing. -- `firmware_cdb`, `updater_cdb` - generate a `compilation_database.json` file for external tools and IDEs. It can be created without actually building the firmware. + - `fap_snake_game`, etc. — build single app as `.fap` by its app ID. + - Check out [--extra-ext-apps](#command-line-parameters) for force adding extra apps to external build. + - `fap_snake_game_list`, etc — generate source + assembler listing for app's `.fap`. +- `flash`, `firmware_flash` — flash the current version to the attached device over SWD. +- `jflash` — flash the current version to the attached device with JFlash using a J-Link probe. The JFlash executable must be on your `$PATH`. +- `firmware_all`, `updater_all` — build a basic set of binaries. +- `firmware_list`, `updater_list` — generate source + assembler listing. +- `firmware_cdb`, `updater_cdb` — generate a `compilation_database.json` file for external tools and IDEs. It can be created without actually building the firmware. ### Assets -- `resources` - build resources and their manifest files - - `dolphin_ext` - process dolphin animations for the SD card -- `icons` - generate `.c+.h` for icons from PNG assets -- `proto` - generate `.pb.c+.pb.h` for `.proto` sources -- `proto_ver` - generate `.h` with a protobuf version -- `dolphin_internal`, `dolphin_blocking` - generate `.c+.h` for corresponding dolphin assets +- `resources` — build resources and their manifest files + - `dolphin_ext` — process dolphin animations for the SD card +- `icons` — generate `.c+.h` for icons from PNG assets +- `proto` — generate `.pb.c+.pb.h` for `.proto` sources +- `proto_ver` — generate `.h` with a protobuf version +- `dolphin_internal`, `dolphin_blocking` — generate `.c+.h` for corresponding dolphin assets ## Command-line parameters {#command-line-parameters} -- `--options optionfile.py` (default value `fbt_options.py`) - load a file with multiple configuration values -- `--extra-int-apps=app1,app2,appN` - force listed apps to be built as internal with the `firmware` target -- `--extra-ext-apps=app1,app2,appN` - force listed apps to be built as external with the `firmware_extapps` target -- `--extra-define=A --extra-define=B=C ` - extra global defines that will be passed to the C/C++ compiler, can be specified multiple times -- `--proxy-env=VAR1,VAR2` - additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes the execution environment and doesn't forward all inherited environment variables. You can find the list of variables that are always forwarded in the `environ.scons` file. +- `--options optionfile.py` (default value `fbt_options.py`) — load a file with multiple configuration values +- `--extra-int-apps=app1,app2,appN` — force listed apps to be built as internal with the `firmware` target +- `--extra-ext-apps=app1,app2,appN` — force listed apps to be built as external with the `firmware_extapps` target +- `--extra-define=A --extra-define=B=C ` — extra global defines that will be passed to the C/C++ compiler, can be specified multiple times +- `--proxy-env=VAR1,VAR2` — additional environment variables to expose to subprocesses spawned by `fbt`. By default, `fbt` sanitizes the execution environment and doesn't forward all inherited environment variables. You can find the list of variables that are always forwarded in the `environ.scons` file. ## Configuration Default configuration variables are set in the configuration file: `fbt_options.py`. Values set in the command line have higher precedence over the configuration file. -You can also create a file called `fbt_options_local.py` that will be evaluated when loading default options file, enabling persisent overriding of default options without modifying default configuration. +You can also create a file called `fbt_options_local.py` that will be evaluated when loading default options file, enabling persistent overriding of default options without modifying default configuration. You can find out available options with `./fbt -h`. ### Firmware application set -You can create customized firmware builds by modifying the list of applications to be included in the build. Application presets are configured with the `FIRMWARE_APPS` option, which is a `map(configuration_name:str -> application_list:tuple(str))`. To specify an application set to use in the build, set `FIRMWARE_APP_SET` to its name. +You can create customized firmware builds by modifying the list of apps to be included in the build. App presets are configured with the `FIRMWARE_APPS` option, which is a `map(configuration_name:str → application_list:tuple(str))`. To specify an app set to use in the build, set `FIRMWARE_APP_SET` to its name. For example, to build a firmware image with unit tests, run `./fbt FIRMWARE_APP_SET=unit_tests`. Check out `fbt_options.py` for details. diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index 5b08c3471fd..da0b0a19d6e 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -38,7 +38,7 @@ Version differences: ### Description -This file format is used to store the UID, SAK and ATQA of a ISO14443-3A device. +This file format is used to store the UID, SAK and ATQA of an ISO14443-3A device. UID must be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. Version differences: diff --git a/documentation/file_formats/TarHeatshrinkFormat.md b/documentation/file_formats/TarHeatshrinkFormat.md index 86c27a698eb..79b75416b9b 100644 --- a/documentation/file_formats/TarHeatshrinkFormat.md +++ b/documentation/file_formats/TarHeatshrinkFormat.md @@ -1,6 +1,6 @@ -# Heatshrink-compressed Tarball Format +# Heatshrink-compressed Tarball Format {#heatshrink_file_format} -Flipper supports the use of Heatshrink compression library for .tar archives. This allows for smaller file sizes and faster OTA updates. +Flipper supports the use of Heatshrink compression library for `.tar` archives. This allows for smaller file sizes and faster OTA updates. Heatshrink specification does not define a container format for storing compression parameters. This document describes the format used by Flipper to store Heatshrink-compressed data streams. diff --git a/documentation/js/js_badusb.md b/documentation/js/js_badusb.md index 78c49104ca5..b21126dfcbd 100644 --- a/documentation/js/js_badusb.md +++ b/documentation/js/js_badusb.md @@ -67,7 +67,7 @@ badusb.hold("CTRL", "v"); // Press and hold CTRL + "v" combo ``` ## release -Release a previously hold key. +Release a previously held key. ### Parameters Same as `press` diff --git a/documentation/js/js_data_types.md b/documentation/js/js_data_types.md index de1c896dce3..bd3bb1f426f 100644 --- a/documentation/js/js_data_types.md +++ b/documentation/js/js_data_types.md @@ -1,13 +1,13 @@ # Data types {#js_data_types} Here is a list of common data types used by mJS. -- string - sequence of single byte characters, no UTF8 support +- string — sequence of single byte characters, no UTF8 support - number - boolean -- foreign - C function or data pointer +- foreign — C function or data pointer - undefined - null -- object - a data structure with named fields -- array - special type of object, all items have indexes and equal types -- ArrayBuffer - raw data buffer -- DataView - provides interface for accessing ArrayBuffer contents +- object — a data structure with named fields +- array — special type of object, all items have indexes and equal types +- ArrayBuffer — raw data buffer +- DataView — provides interface for accessing ArrayBuffer contents diff --git a/documentation/js/js_math.md b/documentation/js/js_math.md index 296f01c62db..12dae8fb377 100644 --- a/documentation/js/js_math.md +++ b/documentation/js/js_math.md @@ -290,7 +290,7 @@ math.pow(2, 10); // 1024 ``` ## random -Return a floating-point, pseudo-random number that's greater than or equal to 0 and less than 1, with approximately uniform distribution over that range - which you can then scale to your desired range. +Return a floating-point, pseudo-random number that's greater than or equal to 0 and less than 1, with approximately uniform distribution over that range — which you can then scale to your desired range. ### Returns A floating-point, pseudo-random number between 0 (inclusive) and 1 (exclusive). From 369e19def3e400c438f899735179ece4f0402c87 Mon Sep 17 00:00:00 2001 From: jay candel Date: Tue, 8 Oct 2024 21:07:05 +0800 Subject: [PATCH 17/36] [IR] Heavily Expand Universal Remotes (#3929) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * expand all universal remotes AC: 26.04% increase Audio: 277.05% increase Projector: 43.36% increase TV: 35.54% increase * formatted with flipper infrared script * Delete applications/main/infrared/tv.ir * Delete applications/main/infrared/ac.ir * Delete applications/main/infrared/audio.ir * Delete applications/main/infrared/projector.ir * more ac additions * updated universal files: all buttons have same capitalization now, ran through infrared script again. * improved raw signal checks on ac file * updated ac.ir * update ac.ir * Infrared: remove duplicate data from projector.ir Co-authored-by: あく --- .../infrared/resources/infrared/assets/ac.ir | 954 ++++ .../resources/infrared/assets/audio.ir | 4083 +++++++++++++++++ .../resources/infrared/assets/projector.ir | 350 ++ .../infrared/resources/infrared/assets/tv.ir | 1679 +++++++ 4 files changed, 7066 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 0a182c5d94e..f57fe3aa134 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -942,3 +942,957 @@ type: parsed protocol: NEC address: 20 00 00 00 command: 02 00 00 00 +# +# Model: Airmax +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4453 4313 586 1554 585 483 586 1554 587 1552 587 483 587 484 586 1552 587 482 588 483 586 1553 586 483 587 483 586 1554 586 1553 585 484 587 1553 586 484 587 1553 586 1554 586 1553 586 1554 586 483 586 1553 586 1553 586 1555 585 483 586 482 588 483 585 484 585 1554 586 483 587 484 585 1553 587 1554 584 1554 585 484 586 484 586 483 586 483 586 484 586 482 588 483 584 484 586 1553 585 1555 584 1555 586 1553 586 1554 585 5129 4428 4312 585 1553 586 483 586 1555 584 1553 586 484 583 486 584 1555 585 484 585 483 586 1554 585 483 586 484 585 1554 585 1554 585 484 585 1554 585 484 584 1554 585 1554 584 1554 585 1554 586 483 585 1554 585 1555 584 1553 584 484 586 483 586 483 585 484 586 1554 583 484 586 483 585 1553 585 1553 585 1553 585 484 584 485 585 485 584 484 585 484 585 485 584 484 585 483 586 1554 585 1553 585 1554 584 1553 585 1553 585 +# +# Model: Airmet ac +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8971 4489 568 584 535 564 565 561 537 588 541 558 561 565 534 592 537 562 567 1659 559 566 563 563 536 1664 584 567 542 584 535 1664 564 587 542 558 561 564 535 591 538 562 567 558 541 585 534 566 563 562 537 589 540 559 560 566 533 593 536 563 566 560 539 587 532 567 562 564 535 591 538 1661 567 1658 590 1662 566 1659 559 1667 592 1660 558 567 562 563 536 590 539 561 558 567 542 584 535 1664 564 1662 586 1665 563 1662 566 1659 589 1663 565 560 559 566 533 593 536 564 565 560 538 587 542 557 562 564 535 591 538 1661 567 585 534 1666 562 1663 585 566 533 1667 592 560 538 +# +# Model: Airwell +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3089 3684 1991 910 1011 1854 983 847 1992 911 982 965 956 874 984 938 982 939 973 1866 1889 948 973 950 972 950 972 859 971 950 972 950 972 954 968 887 943 979 943 979 943 979 943 887 944 979 943 979 943 979 943 887 943 979 943 950 972 979 943 1803 1952 977 3051 3724 1951 978 943 1894 943 888 1951 978 943 979 943 888 942 980 942 979 943 1895 1859 978 943 979 943 979 943 888 943 979 943 979 943 980 942 888 943 980 942 980 942 980 942 888 942 980 942 980 942 980 942 888 943 979 943 980 942 980 942 1804 1950 978 3050 3725 1950 978 942 1896 941 889 1950 979 941 980 942 888 942 980 942 980 942 1896 1858 979 942 980 942 980 942 889 941 980 942 981 941 980 942 889 942 980 942 981 941 981 941 889 941 981 941 981 941 981 941 889 941 981 941 981 941 981 941 1805 1949 980 3964 +# +# Model: Airwell AWSI-PNXA012-N11 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3031 3980 1912 1945 1969 1950 1912 973 994 981 965 951 975 1027 909 1950 975 965 971 974 1941 1006 940 977 969 1003 964 951 975 1000 936 1006 940 973 973 1974 941 973 1942 1006 941 974 993 950 975 999 937 1036 910 975 992 952 974 998 938 1950 975 967 3947 3987 1916 2002 1881 1975 1939 947 999 943 972 971 996 948 967 1949 997 949 976 997 1907 980 966 1006 941 975 971 1001 945 999 968 945 970 976 970 1974 972 942 1942 975 992 952 994 949 977 970 997 948 967 979 967 1005 942 999 968 1923 971 1000 3914 3958 1965 1919 1995 1893 1969 984 962 954 972 996 971 973 942 1949 976 992 965 947 1947 1002 965 950 976 1001 945 997 939 1007 939 1006 940 998 969 1915 1000 944 2002 946 969 944 992 956 1001 943 972 1000 967 949 966 977 969 1001 966 1926 968 1003 4889 +# +# Model: Easy Home_Portable_Air_Cooler +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1281 401 1275 431 406 1248 1276 431 1244 409 439 1242 433 1247 439 1242 433 1248 438 1243 443 1239 1285 7120 1280 407 1279 406 442 1239 1285 400 1275 434 414 1241 434 1247 439 1243 443 1239 436 1246 440 1242 1282 8228 1282 405 1281 404 433 1248 1286 399 1276 406 442 1240 435 1245 441 1241 434 1247 439 1243 443 1239 1285 7121 1279 407 1279 432 416 1239 1285 400 1275 434 414 1241 434 1246 440 1242 444 1238 437 1245 441 1241 1283 8225 1285 402 1284 401 436 1245 1279 406 1280 403 434 1246 440 1241 434 1246 440 1242 444 1237 438 1244 1280 7124 1286 399 1276 408 440 1241 1283 401 1274 434 414 1240 435 1245 441 1241 434 1246 440 1242 444 1237 1287 8220 1279 408 1278 407 441 1240 1284 401 1274 409 439 1242 433 1247 439 1242 444 1238 437 1244 442 1240 1284 7121 1278 408 1278 433 415 1240 1284 399 1276 406 442 1239 436 1244 442 1239 436 1244 442 1239 436 1245 1279 8227 1283 403 1283 400 437 1244 1280 403 1283 399 438 1242 433 1247 439 1242 433 1247 439 1242 444 1238 1275 7127 1283 402 1284 426 412 1243 1281 402 1274 408 440 1240 435 1245 441 1239 436 1245 441 1240 435 1245 1279 8226 1284 402 1284 399 438 1242 1282 400 1275 407 441 1239 436 1243 443 1237 438 1242 444 1237 438 1242 1282 7120 1280 405 1281 428 409 1244 1280 403 1272 409 439 1241 435 1245 441 1239 436 1244 442 1239 436 1244 1280 8225 1285 400 1275 408 440 1241 1283 399 1276 406 432 1248 438 1242 433 1247 439 1242 444 1237 438 1242 1282 7120 1280 406 1280 403 434 1246 1278 405 1281 400 437 1243 432 1247 439 1241 434 1247 439 1242 444 1237 1276 8228 1282 405 1281 402 435 1244 1280 404 1282 400 437 1241 434 1246 440 1241 434 1246 440 1242 433 1247 1277 7126 1284 403 1283 400 437 1241 1283 402 1273 409 439 1239 436 1244 442 1239 436 1244 442 1239 436 1245 1279 8227 1283 403 1283 401 436 1242 1282 403 1283 399 438 1241 434 1246 440 1240 435 1246 440 1241 434 1246 1278 7126 1284 402 1284 399 438 1241 1283 401 1306 376 440 1239 436 1244 442 1238 437 1244 442 1239 436 1245 1279 8225 1285 402 1305 379 437 1242 1313 371 1304 378 438 1240 435 1245 441 1240 435 1245 441 1240 435 1246 1278 7125 1285 401 1306 378 470 1209 1304 380 1306 377 460 1218 436 1244 442 1238 437 1244 442 1239 436 1245 1310 8195 1315 371 1304 379 469 1211 1313 371 1304 378 470 1209 435 1245 441 1240 435 1245 441 1240 435 1245 1310 7092 1308 378 1308 375 462 1216 1308 376 1299 382 466 1213 462 1218 436 1244 442 1238 437 1244 442 1239 1306 +# +# Model: Amcor AC +# +name: Off +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 9C 00 00 00 +# +# Model: Arctic King_RG15B1 +# +name: Off +type: parsed +protocol: NECext +address: 01 FF 00 00 +command: 12 ED 00 00 +# +# Model: Argo ac +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3455 1584 486 347 483 348 482 347 482 348 480 350 430 401 429 1229 432 401 429 400 430 400 430 399 431 399 431 398 432 398 432 399 431 399 456 375 455 377 453 1207 453 378 452 1209 452 379 451 379 451 379 451 379 451 379 451 379 452 379 451 379 451 379 451 403 427 1233 427 1234 427 380 450 1234 426 1234 427 403 427 1233 427 403 427 1234 427 403 427 1234 426 404 426 403 427 403 427 403 427 403 427 1234 427 403 427 403 427 403 427 403 427 403 427 1234 427 403 427 403 427 403 427 403 427 403 427 1234 427 403 427 1234 426 1234 427 1234 426 1234 427 1234 427 403 427 403 427 403 427 1234 427 404 426 403 427 403 427 403 427 403 427 403 427 403 428 403 427 403 427 403 427 403 427 403 427 403 427 403 427 1234 427 1234 427 404 426 404 426 404 426 404 426 1234 426 404 427 404 426 404 426 1234 426 404 426 404 426 1234 427 404 426 404 426 404 426 404 426 404 426 404 426 1235 426 1235 426 1235 426 404 426 1235 425 405 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 427 404 426 404 426 404 426 404 427 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 404 426 1235 426 1235 426 1235 426 404 426 1235 426 404 426 +# +# Model: Ariston AC_A-MW09-IGX +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4481 4414 595 1596 594 527 562 1602 588 1604 586 535 565 530 570 1594 596 526 563 532 568 1596 594 528 561 534 566 1598 592 1600 590 531 569 1596 594 527 562 1603 587 1604 586 1605 648 1543 594 527 562 1603 587 1604 586 1605 595 525 564 531 569 526 563 532 568 1596 594 528 561 534 566 1598 592 1600 590 1601 589 532 568 527 562 533 567 528 561 534 566 529 560 535 565 530 570 1594 596 1596 594 1597 593 1598 592 1599 591 5252 4503 4418 590 1601 589 532 568 1597 593 1598 592 529 560 535 565 1600 590 531 569 526 563 1602 588 533 567 529 560 1604 596 1595 595 526 563 1602 588 533 567 1598 592 1599 591 1600 590 1601 589 532 568 1598 592 1599 591 1600 590 531 569 527 562 532 568 528 561 1603 587 534 566 530 559 1605 595 1596 594 1597 593 528 561 534 566 529 560 535 565 530 570 525 564 531 569 527 562 1602 588 1603 587 1604 596 1595 595 1596 594 +# +# Model: Ballu R05-BGE +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4467 4296 626 1511 627 469 626 1512 626 1538 652 442 625 469 601 1538 600 495 599 469 599 1565 598 471 597 498 596 1542 596 1569 595 474 595 1570 594 474 595 1570 594 1544 594 1570 594 1570 594 475 594 1570 594 1544 594 1570 594 475 594 501 594 475 594 500 595 1544 594 501 594 475 594 1570 594 1544 594 1570 594 475 594 501 594 475 594 501 594 475 594 501 594 475 594 475 620 1544 594 1570 594 1544 594 1570 594 1545 594 5144 4435 4328 593 1545 593 501 594 1545 593 1571 593 475 594 501 594 1545 593 501 593 475 594 1571 593 475 594 501 594 1545 593 1571 593 475 594 1571 593 476 619 1545 593 1571 593 1545 593 1571 593 476 593 1571 593 1545 593 1571 594 502 593 476 593 476 593 502 593 1545 593 502 593 476 593 1572 592 1572 592 1546 592 502 593 476 593 476 593 502 593 477 592 503 592 477 592 503 592 1546 592 1572 592 1546 592 1572 592 1572 592 +# +# Model: Portable +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3120 1593 488 1180 489 1177 492 342 492 342 492 367 467 1174 485 349 485 349 485 1181 488 1179 490 343 491 1177 492 341 493 366 458 1183 486 1181 488 346 488 1179 490 1177 492 342 492 341 493 1174 485 349 485 347 487 1180 489 345 489 344 490 370 464 368 466 341 493 341 483 376 458 375 459 348 486 374 460 372 462 345 489 370 464 369 465 368 466 341 493 367 457 348 486 374 460 348 486 1179 490 343 491 343 491 1176 493 1174 485 349 485 349 485 374 460 373 461 373 461 346 488 371 463 371 463 369 465 1175 484 350 484 350 484 348 486 374 460 346 488 372 462 345 489 371 463 370 464 368 466 340 484 376 458 376 458 375 459 348 486 347 487 345 489 370 464 370 464 369 465 342 492 368 466 367 457 375 459 348 486 374 460 347 487 372 462 345 489 371 463 343 491 368 466 341 493 367 457 376 458 375 459 1181 488 346 488 345 489 1178 491 342 492 342 492 1175 494 1173 486 1182 487 346 488 346 488 1179 490 343 491 342 492 368 466 367 457 +# +# Model: Bonaire DurangoAC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1305 435 1280 432 415 1255 1307 432 1272 439 418 1252 442 1255 1307 431 416 1255 439 1258 447 1251 443 8174 1302 437 1278 433 414 1255 1307 432 1273 438 419 1250 444 1254 1298 440 417 1253 441 1256 438 1259 445 8170 1306 433 1271 439 418 1251 1301 438 1277 434 413 1256 449 1249 1303 435 412 1258 446 1251 443 1254 440 8176 1300 438 1277 434 413 1283 1279 433 1271 440 417 1278 416 1255 1307 431 416 1253 441 1257 447 1250 444 8171 1305 433 1272 439 418 1278 1274 438 1277 433 414 1282 412 1259 1303 434 413 1256 448 1249 445 1252 442 8173 1303 435 1270 440 417 1279 1273 438 1277 433 414 1282 412 1258 1304 433 414 1282 412 1258 446 1250 444 8171 1305 433 1272 438 419 1276 1276 435 1270 441 416 1252 442 1255 1297 440 417 1279 415 1255 439 1257 447 8168 1297 439 1276 434 413 1256 1306 431 1273 436 411 1258 446 1250 1302 409 438 1284 421 1249 445 1252 442 +# +# Model: Boston Bay_MSAB_09CR +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4468 4412 543 1593 541 502 539 1596 548 1589 545 496 545 498 543 1593 541 501 540 502 539 1598 546 495 546 497 544 1591 543 1594 540 501 539 1597 547 494 547 1590 544 1591 543 1594 540 1595 539 504 547 1588 546 1590 544 1593 540 501 539 503 538 503 538 505 546 1588 546 497 544 496 545 1592 542 1592 542 1594 540 501 539 503 538 503 538 505 546 494 547 496 545 496 545 497 544 1590 544 1593 541 1594 540 1597 547 1589 545 5188 4430 4421 544 1592 541 499 541 1595 539 1596 538 505 546 494 547 1589 545 497 544 496 545 1592 542 500 540 499 542 1594 540 1596 538 503 538 1599 545 497 544 1591 543 1593 541 1595 539 1596 538 504 547 1588 546 1588 546 1590 544 498 542 498 543 499 541 500 541 1593 541 501 540 502 539 1597 547 1586 548 1587 547 496 545 495 546 497 544 498 543 497 544 498 543 499 542 498 542 1592 542 1594 540 1595 539 1597 547 1589 545 +# +# Model: Botti BL-168DLR +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9577 4536 622 576 621 577 620 577 620 578 619 578 619 578 619 579 618 1667 625 1660 622 1664 618 1668 624 1662 620 1666 616 1670 622 1664 618 580 617 581 616 1669 623 575 622 575 622 1664 618 580 617 580 617 581 616 1669 623 575 622 1664 618 1668 624 573 624 1662 620 1666 616 1670 622 39798 9556 2250 626 96841 9564 2245 620 96843 9560 2253 622 96836 9569 2244 621 96844 9571 2244 621 96843 9573 2247 619 96844 9572 2248 617 96847 9570 2248 617 96848 9568 2250 626 96833 9564 2251 625 96834 9564 2252 624 96843 9595 2223 653 96809 9597 2221 644 96822 9604 2215 650 96814 9591 2226 650 96813 9591 2225 650 96811 9600 2218 647 96816 9597 2221 654 96812 9601 2219 646 96815 9598 2220 645 96813 9601 2216 649 96815 9599 2219 657 96809 9565 2252 624 96842 9564 2254 622 96841 9565 2254 622 96838 9568 2249 616 96844 9571 2247 619 96842 9574 2245 620 96840 9566 2251 625 96838 9567 2251 625 96835 9570 2250 615 96848 9566 2254 621 96843 9572 2247 618 96847 9567 2253 623 96842 9572 2249 616 96843 9572 2247 618 96843 9572 2249 616 96849 9576 2245 621 96842 9574 2246 619 96842 9575 2246 619 96843 9573 2248 617 96843 9572 2249 616 96847 9571 2249 617 96839 9569 2251 625 96837 9569 2251 624 96837 9568 2250 626 96835 9572 2248 618 96844 9572 2246 619 96843 9564 2253 623 96834 9572 2245 620 96835 9569 2246 619 +# +# Model: Boulanger AC +# +name: Off +type: parsed +protocol: NEC +address: 81 00 00 00 +command: 6B 00 00 00 +# +# Model: Chigo CS-21H3A-B155AF +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6133 7359 575 561 550 560 577 532 576 559 550 534 602 507 549 536 573 536 573 564 546 539 571 538 545 540 570 538 570 566 545 539 571 589 545 590 521 540 570 562 521 563 547 562 545 592 521 563 548 588 522 615 521 563 547 589 521 589 548 589 547 562 521 589 548 588 548 589 521 563 547 563 546 590 521 589 547 562 546 590 521 589 547 589 547 563 521 563 548 589 521 589 548 588 549 589 545 539 548 589 548 563 521 562 549 589 521 563 548 563 547 590 521 589 548 589 548 563 521 589 547 1667 521 1641 521 563 547 589 548 589 521 589 548 562 548 590 520 589 548 589 548 589 521 590 547 588 548 1667 520 1668 545 1642 547 563 520 563 547 1667 520 589 547 589 547 1641 546 563 547 1641 546 563 546 538 546 1668 544 539 547 1668 520 590 546 590 546 1668 520 564 546 590 520 1668 547 1642 546 1668 542 7378 545 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6107 7355 578 556 579 530 551 532 605 529 604 505 550 559 602 507 574 561 522 563 573 561 546 537 548 561 548 561 522 561 549 561 522 538 572 587 549 587 522 561 548 561 547 563 522 561 548 561 522 561 549 588 546 563 521 561 549 561 522 561 549 561 522 561 549 561 547 590 521 561 549 562 521 562 548 562 545 538 548 562 547 562 522 588 548 588 548 588 522 562 548 588 548 589 521 562 548 588 547 589 521 562 548 562 521 562 548 563 546 590 521 562 548 589 546 591 520 1641 544 566 547 1667 544 1616 548 1613 547 1666 520 588 548 588 548 589 545 538 547 562 547 564 519 562 548 563 520 563 547 1667 520 589 547 564 545 1641 547 590 546 590 520 1666 548 590 543 1642 548 563 520 1666 548 590 544 538 547 1667 544 592 520 1668 543 592 520 562 548 564 519 1667 547 590 544 1642 547 1614 546 588 548 7396 543 +# +# Model: Chigo KRF-51G_79F +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6060 7357 592 1633 593 1633 593 1633 593 1633 593 1633 593 1633 592 1634 592 1634 592 515 591 515 591 515 591 516 590 517 590 516 590 517 590 517 589 1636 590 1636 590 1636 590 1636 590 1636 590 1636 590 1636 590 1636 590 517 590 517 590 517 590 517 590 517 590 517 589 517 590 517 590 1636 590 1637 589 1637 589 1637 589 1636 615 1612 589 1637 589 1636 590 517 590 517 589 517 590 517 590 517 590 517 589 517 590 517 590 1637 589 1637 589 1637 589 517 589 517 589 517 590 1637 589 1637 589 517 590 517 590 517 589 1637 589 1637 589 1637 590 517 589 517 589 517 589 518 589 517 589 1637 589 1637 589 517 590 1637 589 1637 589 1637 589 1637 589 1637 590 517 589 517 589 1637 589 517 589 517 590 517 589 1638 589 517 590 1637 589 518 589 1637 589 518 588 518 589 1637 589 518 588 1637 589 518 588 1637 589 518 589 1637 589 1637 589 7359 589 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6059 7355 592 1633 592 1633 592 1633 592 1633 592 1633 592 1633 592 1633 618 1608 617 489 617 490 616 490 616 491 590 517 589 517 614 492 590 517 590 1636 589 1636 590 1636 590 1636 589 1636 590 1636 590 1636 590 1636 590 517 589 517 589 517 589 517 590 517 589 517 589 517 589 517 589 517 589 1636 614 492 590 1636 590 1636 590 1636 589 1636 590 1636 589 1637 589 517 589 1636 590 517 589 517 589 517 614 492 590 517 589 1636 590 517 589 1636 590 517 589 517 589 517 589 517 590 1636 590 517 589 1636 590 517 589 1636 589 1636 590 1636 590 1637 589 517 589 517 589 1637 588 1637 589 517 589 1637 589 1636 590 517 589 1636 590 1636 590 517 589 517 589 1637 589 517 589 517 589 1637 589 517 589 518 588 1637 589 518 588 1637 589 517 590 1637 588 518 588 518 588 1637 589 518 588 1637 589 518 588 1637 589 518 588 1637 589 1637 589 7357 589 +# +# Model: Comfort Aire_RG57A6 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4381 4345 579 1558 579 488 580 1559 578 488 580 489 579 488 580 489 579 1558 579 489 579 488 580 488 580 489 579 488 580 489 579 1557 579 488 580 488 580 1559 577 1557 580 488 580 1557 579 1557 580 1557 580 488 580 1558 579 1558 579 1558 579 1557 580 1558 579 1559 578 1557 580 1557 580 1556 580 1558 579 1557 579 1559 578 1557 579 1560 577 1561 549 1584 579 1557 553 1584 552 1584 552 515 553 516 551 516 551 1585 551 1583 553 5156 4384 4343 552 515 552 1584 552 515 552 1584 552 1584 552 1584 552 1585 551 515 553 1585 551 1583 553 1584 552 1583 553 1584 552 1583 553 516 551 1584 552 1585 551 515 552 517 550 1583 553 515 552 515 552 516 551 1584 552 515 552 516 551 516 551 515 552 515 552 515 552 516 551 516 551 515 552 515 553 515 552 515 552 515 552 516 551 515 552 516 551 515 552 515 552 516 551 1583 553 1584 552 1585 551 515 552 515 552 +# +# Model: Cortlitec portable_ac +# +name: Off +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 00 FF 00 00 +# +name: Dh +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 0C F3 00 00 +# +# Model: COTech MPPH-08CRN7-QB6_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4412 4354 570 1575 568 504 572 1574 569 503 563 510 567 505 572 527 539 1580 563 509 568 505 572 1574 569 503 563 509 568 505 572 527 539 507 569 503 563 1582 571 500 566 507 569 503 563 535 542 505 561 511 566 1579 564 1582 571 1574 569 1576 567 1578 565 1581 562 1583 570 1575 568 1577 566 1580 563 1582 571 1574 569 1576 567 1579 564 1581 562 1583 570 1575 568 1577 566 1579 564 509 568 1577 566 1580 563 1582 571 501 565 5165 4412 4352 572 500 566 1579 564 509 568 1577 566 1579 564 1581 562 1583 570 502 564 1581 562 1582 571 501 565 1580 563 1582 571 1574 569 1576 567 1578 565 1579 564 509 567 1577 566 1579 564 1581 562 1583 570 1575 568 1577 566 506 571 501 565 534 542 529 537 535 542 531 535 537 539 532 545 527 539 507 569 529 537 509 568 504 562 510 566 532 544 527 539 507 569 528 538 534 542 1576 567 504 562 510 566 506 571 1574 569 +# +# Model: Daikin AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9830 9789 9825 9795 4618 2487 381 348 384 929 381 936 384 353 379 934 386 350 382 350 382 362 380 349 383 929 381 355 387 346 386 926 384 353 379 354 388 355 387 918 382 354 388 348 384 349 383 353 379 357 385 924 386 358 384 921 389 347 385 351 381 929 381 932 388 348 384 349 383 361 381 347 385 351 381 355 387 345 387 925 385 352 380 353 379 365 388 340 382 354 388 348 384 349 383 929 381 355 387 346 386 358 384 921 379 357 385 351 381 352 380 356 386 930 380 357 385 358 385 344 388 348 384 933 387 349 383 353 379 938 382 354 388 352 380 20353 4625 +# +# Model: Daikin AC_industrial_TB +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 5055 2191 335 1799 360 752 338 714 365 716 364 1800 359 723 367 715 365 718 361 720 359 1805 365 747 332 1802 357 1806 364 719 360 1803 367 1798 361 1803 367 1797 362 1802 368 744 335 1799 360 721 369 714 365 716 363 719 360 721 369 713 366 1798 361 1802 368 715 364 717 362 720 359 723 367 715 364 1799 360 722 368 714 365 717 363 719 361 722 368 714 365 716 363 719 360 721 369 713 366 716 363 718 361 721 359 723 367 1797 362 1802 368 1796 363 1801 358 754 336 716 363 719 361 29583 5057 2160 366 1798 361 750 329 723 367 715 364 1800 359 753 337 715 364 717 363 720 359 1804 366 747 332 1801 358 1806 364 718 361 1803 356 1798 1802 368 1797 362 1802 368 714 365 1799 360 722 368 714 365 717 362 719 360 722 368 714 365 1798 361 1803 367 715 364 718 362 721 359 723 367 715 364 718 361 720 359 723 367 715 364 717 362 720 360 1804 366 1799 360 721 369 714 365 1798 361 1803 367 1798 361 720 359 723 367 715 364 718 361 720 360 723 367 715 364 717 362 720 359 722 368 714 365 717 362 719 360 722 368 1796 363 719 360 721 369 714 365 716 363 719 361 721 369 713 366 716 363 718 361 721 358 723 367 716 363 718 362 720 359 723 367 715 364 718 361 720 359 723 367 715 364 1799 360 1804 366 1799 360 721 369 714 365 716 363 1801 358 754 336 1798 361 720 360 1805 365 748 331 720 359 723 367 715 364 717 363 720 360 722 368 714 365 717 362 720 359 722 368 714 365 717 363 719 361 722 368 714 365 1798 361 751 339 713 366 716 363 1801 359 1805 365 1800 359 1805 365 1800 359 1805 365 1799 360 +# +# Model: Daikin ARC480A53 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 445 455 421 449 416 453 422 447 418 451 424 25400 3494 1753 416 1322 419 450 415 456 420 449 416 1325 416 451 425 444 421 448 417 453 423 1318 423 444 421 1320 421 1316 414 457 419 1320 421 1318 423 1314 417 1322 419 1320 421 449 416 453 422 1317 424 445 420 450 415 456 419 447 418 451 414 456 420 449 416 452 424 448 417 453 422 445 420 450 415 456 419 448 417 452 423 447 418 449 416 453 423 448 417 453 423 446 419 449 416 453 423 447 418 1321 420 451 414 454 422 449 416 1322 419 1319 422 449 416 1322 419 451 424 445 420 449 416 455 421 445 420 449 416 455 420 448 417 452 424 446 419 1321 420 1317 424 1315 416 1324 417 1322 419 1320 421 1320 421 446 419 1319 422 1319 422 1318 423 1313 417 453 423 445 420 450 415 454 421 449 416 454 422 446 419 449 416 456 420 448 417 453 423 444 421 450 415 454 422 447 418 451 424 445 420 450 415 455 420 447 418 452 424 447 418 451 424 445 420 448 417 453 423 446 419 451 424 444 421 448 417 452 423 446 419 451 424 445 420 448 417 451 414 456 420 452 424 443 422 449 416 451 424 448 417 449 416 455 420 1318 423 446 419 1319 422 448 417 451 424 447 418 1320 421 1316 415 455 421 451 414 455 421 449 416 451 424 446 419 1319 422 448 417 454 422 446 419 451 414 1325 416 452 423 447 418 451 424 447 418 1318 423 446 419 449 416 1327 424 1312 419 449 416 455 421 449 416 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 444 456 419 450 415 455 420 448 417 455 420 25402 3491 1755 424 1315 415 455 420 449 416 458 417 1313 417 453 422 447 418 452 423 444 421 1320 421 447 418 1321 420 1319 422 448 417 1322 419 1320 421 1318 423 1315 415 1332 419 442 423 448 417 1320 421 448 417 454 422 445 420 450 415 461 414 450 415 455 420 446 419 452 423 446 419 451 424 444 421 447 418 454 421 445 420 449 416 454 422 447 418 1322 419 449 416 454 421 447 418 451 414 1325 416 453 422 447 418 453 422 446 419 453 422 445 420 448 417 453 422 1315 415 1324 417 453 422 447 418 450 415 454 422 448 417 454 421 446 419 451 424 1314 416 1326 415 1321 420 1318 423 446 419 1321 420 449 416 1322 419 1323 418 1320 421 1317 424 1318 423 445 420 449 416 452 423 449 416 452 423 444 421 449 416 459 416 446 419 458 417 448 417 456 420 443 422 447 418 452 423 445 420 450 415 452 424 447 418 452 423 446 419 449 416 460 415 448 417 452 423 445 420 450 415 455 420 449 416 452 423 445 420 451 414 454 421 449 416 455 420 446 419 451 414 454 422 449 416 452 424 445 420 451 424 443 422 448 417 1323 418 451 424 1315 415 453 422 446 419 450 415 1325 416 1324 417 451 414 456 419 450 415 454 421 447 418 452 423 446 419 450 415 453 423 450 415 451 424 1315 416 453 422 447 418 452 424 447 418 450 415 1323 418 1321 420 1319 422 1318 423 1317 424 1316 414 453 422 +# +# Model: Daikin FTE35KV1 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 5041 2133 360 1770 356 693 364 685 362 689 358 1772 364 686 361 689 358 692 365 684 363 1768 358 692 365 1767 359 1771 355 694 363 1769 357 1773 363 1768 358 1772 364 1767 359 689 358 692 365 1765 361 689 358 692 365 685 362 688 359 691 366 684 363 1767 359 1773 363 1768 358 1772 364 1767 359 689 358 1774 362 1769 357 691 366 684 363 687 360 690 357 693 364 685 362 689 358 691 366 684 363 687 360 690 357 693 364 1766 360 1773 363 1767 359 1771 355 694 363 687 360 690 357 693 364 29443 5038 2134 359 1774 362 686 361 689 358 692 365 1765 361 689 358 692 365 685 362 688 359 1771 365 685 362 1769 357 1775 361 687 360 1772 364 1767 359 1771 355 1776 360 1770 356 693 364 686 361 1799 327 693 364 686 361 689 358 692 365 685 362 688 359 691 356 694 363 686 361 689 358 1773 363 1769 357 691 366 684 363 1798 328 692 365 1767 359 1771 355 694 363 686 361 689 358 692 365 1765 361 1772 364 684 363 687 360 1801 335 684 363 687 360 690 357 693 364 686 361 1769 357 694 363 686 361 689 358 692 365 685 362 688 359 691 356 694 363 687 360 690 357 693 365 685 362 688 359 691 367 684 363 687 360 690 357 693 365 685 362 688 359 1772 364 1768 358 690 357 693 364 686 361 689 358 692 365 1765 361 689 358 692 365 685 362 688 359 691 356 694 363 687 360 689 358 1773 363 687 360 690 357 693 364 685 362 688 359 1772 364 686 361 1769 357 1776 360 1770 356 1775 361 687 360 +# +# Model: Daikin FTX50GV1B +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 190 26844 503 368 475 395 475 395 475 367 476 395 475 25347 3527 1695 451 1290 451 420 476 394 476 394 475 1264 475 370 471 399 470 400 470 400 443 1298 443 400 470 1270 471 1270 471 400 470 1270 471 1271 470 1270 471 1270 471 1271 470 400 470 373 470 1271 470 400 470 400 470 400 443 400 470 400 470 400 443 400 470 400 470 400 470 374 469 1271 470 400 470 1271 470 400 470 401 442 401 469 1272 469 1272 469 401 469 401 469 374 469 401 469 401 469 401 442 401 469 401 469 401 469 374 469 401 469 401 469 374 469 401 469 401 469 401 442 1300 441 1299 442 1300 441 402 468 1272 469 402 468 1272 469 1273 468 34903 3522 1703 471 1270 471 400 470 373 470 400 470 1270 471 400 470 400 443 400 470 400 470 1270 471 400 443 1298 443 1297 444 400 470 1270 471 1270 471 1270 471 1270 471 1270 471 400 470 400 470 1270 471 373 470 400 470 400 470 400 443 400 470 400 470 400 470 373 470 400 470 400 470 400 443 1298 443 400 470 400 470 400 443 400 470 1271 470 400 470 1271 470 1271 470 400 470 1271 470 1271 470 1271 470 373 470 400 470 1271 470 1271 470 400 470 1271 470 1271 470 400 443 400 470 400 470 400 470 1271 470 373 470 1271 470 401 469 1271 470 400 470 1271 470 34903 3522 1702 471 1269 472 373 470 399 471 399 471 1270 471 399 444 399 471 399 471 399 471 1270 471 372 471 1270 471 1270 471 399 471 1270 471 1270 471 1270 471 1270 471 1270 471 399 471 399 444 1297 444 399 471 399 471 399 471 372 471 399 471 399 471 373 470 399 471 399 471 399 444 400 470 399 471 399 471 373 470 400 470 400 470 399 444 400 470 1270 471 400 470 400 470 1270 471 1271 470 1271 470 372 471 400 470 400 470 400 443 1298 443 400 470 1271 470 1271 470 400 470 400 443 401 469 400 470 401 469 373 470 401 469 401 469 401 442 401 469 401 469 401 469 375 468 402 468 401 469 1273 468 1296 445 426 416 426 445 426 444 426 417 426 444 426 444 426 444 399 444 426 444 426 444 426 417 426 444 426 444 426 444 399 444 426 444 426 444 426 417 1324 417 1325 416 426 444 426 444 427 416 427 444 426 444 426 444 399 444 427 443 427 443 426 417 1325 416 1325 416 427 443 427 443 426 444 399 444 427 443 427 444 427 416 427 444 427 443 427 443 400 443 427 443 427 443 400 443 427 443 427 443 427 416 1325 416 427 443 427 443 427 443 400 443 427 443 1298 443 1298 443 427 443 427 416 427 443 427 443 427 443 400 443 427 443 1298 443 427 443 400 443 428 442 427 443 427 416 428 443 427 443 427 443 400 443 1298 443 1298 443 428 442 428 442 428 415 428 442 1299 442 +# +# Model: Daikin FTXM95PVMA +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 536 358 508 358 508 358 507 359 506 335 530 25024 3539 1656 534 1198 534 360 534 331 477 389 477 1228 533 360 507 358 508 358 508 358 507 1224 508 359 506 1225 506 1226 505 361 504 1229 502 1231 474 1258 474 1258 474 1258 474 392 474 392 474 1258 474 392 474 392 474 392 474 392 474 392 474 392 474 392 474 392 474 392 474 392 474 1259 473 392 474 1259 473 392 474 392 474 392 474 1259 473 1259 473 393 473 392 474 393 473 392 473 1259 473 393 473 393 473 393 473 392 474 393 473 393 473 393 472 393 473 393 473 393 473 393 473 1259 473 1260 472 1259 473 393 473 393 473 1259 473 1260 472 1260 472 35478 3510 1688 474 1259 473 392 474 392 474 392 474 1259 473 392 474 393 473 392 474 392 474 1259 473 393 473 1259 473 1259 473 392 474 1259 473 1259 473 1259 473 1259 473 1260 472 393 473 393 473 1259 473 393 473 393 473 393 473 393 473 393 473 393 473 393 473 393 473 394 472 394 472 394 472 1260 472 394 472 394 472 394 472 394 472 1261 471 395 471 395 471 395 471 418 448 395 471 418 448 418 448 419 447 418 448 419 447 418 448 419 447 418 448 1285 447 419 447 418 448 419 447 418 448 419 446 1285 447 419 447 419 447 1285 447 1285 447 419 447 35479 3510 1689 473 1259 473 393 473 393 473 392 474 1259 473 393 473 393 473 393 473 393 473 1259 473 393 473 1259 473 1259 473 393 473 1259 473 1260 472 1259 473 1259 473 1260 472 393 473 393 473 1260 472 393 473 394 472 394 472 393 473 393 472 393 473 393 473 393 473 393 473 393 473 394 472 394 472 394 472 394 472 394 472 394 472 394 472 394 472 1285 447 418 448 418 448 1285 447 1285 447 1285 447 419 447 419 447 419 447 1285 447 419 447 419 447 1285 447 1285 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 419 447 1286 446 1285 447 419 447 1286 446 1286 446 1286 446 1286 446 1286 446 419 447 420 446 420 446 419 447 420 446 419 447 420 446 419 447 420 446 420 446 420 446 420 445 420 446 1286 446 1286 446 420 446 420 446 420 446 420 446 420 446 420 446 420 446 420 446 420 446 420 446 1287 445 1287 445 420 446 420 446 420 446 421 445 420 446 421 445 420 446 420 446 420 446 420 445 421 445 421 445 421 445 421 445 421 445 421 445 421 445 1288 444 421 445 421 445 421 445 421 445 421 445 1288 444 1288 444 422 444 1288 444 422 444 421 445 422 443 422 444 422 444 1288 444 422 444 422 444 422 444 422 444 422 444 422 444 422 444 422 444 1288 444 422 444 1289 443 423 443 423 443 1289 443 1289 443 1290 442 +# +# Model: Daikin FTXS25KVMA +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 427 419 454 418 454 417 455 391 454 418 454 25460 3513 1723 428 1317 428 418 481 391 454 418 427 1317 428 418 454 418 454 418 454 391 454 1290 455 418 454 1291 479 1266 478 395 477 1268 476 1270 474 1272 473 1297 448 1297 448 423 422 424 449 1297 448 423 449 424 448 396 449 423 449 424 449 424 421 424 448 424 449 423 422 424 449 1297 448 424 449 1297 448 424 448 397 448 424 448 1297 448 1297 448 424 448 424 421 424 448 424 448 424 448 397 448 424 448 424 448 424 421 424 448 424 448 424 421 1325 420 425 448 424 448 424 448 1298 447 1298 447 1298 447 397 448 425 447 1298 447 1298 447 1298 447 34988 3533 1730 448 1297 448 424 448 424 421 424 448 1297 448 424 448 424 448 396 449 424 448 1297 448 424 448 1297 448 1297 448 397 448 1297 448 1297 448 1297 448 1297 448 1297 448 424 448 424 448 1297 448 424 421 424 448 424 448 424 448 397 448 424 448 424 448 425 420 424 449 424 448 424 449 1297 448 397 448 424 448 424 448 424 421 1325 420 425 448 424 448 1297 448 424 421 425 447 1297 448 424 448 1297 448 425 448 397 448 1298 447 425 448 424 448 1298 447 425 420 425 447 425 447 425 447 397 448 425 447 1298 447 1298 447 1298 447 425 447 1298 447 35014 3507 1730 448 1297 448 397 448 424 448 424 448 1297 448 424 421 424 448 424 448 424 448 1297 448 397 448 1298 447 1298 447 425 447 1299 445 1300 445 1299 446 1299 446 1299 446 426 446 426 419 1326 419 426 447 426 447 425 447 398 447 426 446 426 447 399 446 426 446 426 446 426 419 427 446 426 447 427 445 399 446 427 445 451 421 428 417 452 420 1325 420 453 420 452 420 1325 420 425 420 1325 420 453 420 452 420 425 420 452 420 452 420 452 393 453 420 452 420 1325 420 1326 419 453 419 426 420 452 420 452 420 453 392 453 419 453 419 452 421 425 420 452 420 452 420 453 392 453 419 1326 419 453 420 1326 419 1326 419 1327 418 1326 419 1327 418 453 392 454 419 453 420 453 420 425 420 453 419 453 419 453 392 453 419 453 420 453 419 426 419 453 419 1326 418 1327 418 454 419 454 391 454 419 453 419 453 392 454 419 453 419 454 419 426 419 454 418 1327 418 1327 418 454 418 455 391 454 419 454 418 454 418 426 419 454 418 454 419 454 391 454 418 454 419 454 391 454 419 454 418 454 418 427 418 454 418 1327 418 455 418 454 391 454 419 454 418 454 418 1328 417 1328 417 428 417 454 419 454 418 454 391 455 418 454 418 455 417 427 418 454 418 454 418 428 417 454 418 455 417 455 390 455 418 455 417 1328 417 455 418 428 417 455 417 1328 417 455 418 1328 417 1328 417 +# +# Model: Delonghi portable_Pinguino-Air-to-Air-PAC-N81_ac +# +name: Off +type: parsed +protocol: NECext +address: 48 12 00 00 +command: 88 08 00 00 +# +# Model: Pinguino PAC_EL275HGRKC +# +name: Off +type: parsed +protocol: NEC +address: 82 00 00 00 +command: 6B 00 00 00 +# +# Model: electriQ P15C-V2 +# +name: Off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +# Model: Electrol ESV09CRO_B2I +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3086 3063 3089 4438 571 1657 575 529 571 1656 576 529 571 534 576 1650 571 1655 577 527 573 1654 578 526 574 1653 569 1657 575 530 570 536 574 530 570 534 576 1648 573 1653 569 1658 574 530 570 535 575 530 570 534 576 527 573 531 569 535 575 529 571 533 577 526 574 530 570 534 576 528 572 532 568 536 574 529 571 533 578 526 574 530 570 534 576 528 572 1652 569 534 577 1649 573 532 579 526 574 530 570 534 576 527 573 531 569 535 575 528 572 532 568 536 574 529 571 533 577 526 574 530 570 534 577 1647 574 530 570 535 575 529 571 532 568 536 575 529 571 533 577 526 574 530 570 533 577 527 573 530 570 534 576 527 573 531 569 535 575 528 572 532 568 535 575 529 571 532 568 536 574 529 571 533 577 526 574 530 570 533 577 527 573 530 570 534 576 527 573 531 569 534 576 527 573 531 569 535 575 528 572 531 569 535 575 528 572 532 568 535 576 1648 573 531 569 1655 577 1648 573 1651 570 1654 578 1647 574 1651 570 533 577 1648 573 1651 571 +# +# Model: Emerson EARC8RE1_ac +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3110 1566 525 1084 497 356 471 1085 497 356 472 356 471 1085 497 356 470 357 474 1084 498 356 471 356 471 356 471 356 471 356 496 357 470 357 473 1061 520 357 470 1063 519 357 470 1064 518 1064 518 357 470 357 471 +# +# Model: Eurom_PAC_9.2 +# +name: Off +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 04 00 00 00 +# +# Model: Firstline AAS2500 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3405 1710 463 387 464 388 464 387 464 388 464 389 462 386 465 1224 464 388 463 388 463 389 463 387 464 388 464 387 465 387 464 389 462 389 463 388 464 388 463 1226 462 388 463 1225 463 388 463 387 464 389 462 389 462 388 463 387 464 387 464 389 463 388 463 388 464 1226 462 1224 464 1225 462 387 464 388 464 388 463 388 463 1226 462 387 464 1225 463 388 463 1225 463 388 463 389 463 387 464 1226 462 1224 464 388 463 388 463 387 465 1224 464 388 463 1224 463 388 464 387 464 1224 464 1226 461 387 464 1224 464 388 463 1225 463 1224 464 1224 463 1224 464 1223 464 388 464 387 464 387 464 387 464 389 463 389 462 388 464 388 463 388 463 387 464 387 465 387 464 388 463 387 464 388 463 388 464 388 463 1225 463 388 463 1225 463 1224 463 388 463 388 463 388 464 388 463 387 464 388 464 389 462 389 463 387 464 388 464 388 464 388 463 388 464 387 464 387 465 388 463 387 464 1225 463 1225 463 1225 462 387 464 389 462 388 463 387 465 389 462 388 463 388 464 389 463 388 463 388 464 387 464 389 462 389 463 387 464 388 463 388 463 388 464 389 462 389 463 389 462 389 463 389 462 388 463 1224 464 1225 463 1224 464 389 462 1224 464 394 437 +# +# Model: Frico PA2510E08 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 272 76189 1130 2004 1129 2008 2141 2008 1127 966 1125 967 2138 992 1094 999 2101 1004 1085 +# +# Model: Friedrich +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 5624 5582 567 553 568 550 571 550 561 557 564 584 537 554 567 1672 571 1671 562 554 567 553 568 1696 537 1676 567 1673 570 1668 565 554 567 556 565 1671 562 557 564 1676 567 1671 562 584 537 555 566 1673 570 552 569 1666 567 1671 562 1676 567 1671 572 548 563 556 565 1672 571 554 567 547 564 556 565 1674 569 549 562 558 563 1676 567 551 570 554 568 548 563 557 564 555 566 554 567 1672 571 1667 566 1674 569 1673 570 545 566 554 567 1671 572 1667 566 554 567 1672 561 557 564 1677 566 +# +# Model: Friedrich 4235h +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3232 1451 581 1008 580 1007 581 326 494 326 494 326 494 1009 579 325 495 326 494 1034 579 1010 578 325 494 1012 576 326 494 326 494 1013 575 1013 576 326 494 1013 575 1013 575 326 494 326 494 1013 575 326 494 326 494 326 494 1013 575 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 1013 575 326 494 326 494 326 494 326 494 326 494 326 494 326 494 1013 575 1014 574 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 1014 574 1014 574 326 494 326 494 326 494 326 494 1014 574 1014 574 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 1015 573 326 494 1015 573 1015 573 1015 573 70108 3201 1510 574 1014 574 1015 574 326 494 326 494 326 494 1014 574 326 494 326 494 1014 574 1014 574 326 494 1014 574 326 494 326 494 1014 574 1015 573 326 495 1015 573 1015 573 326 494 326 494 1015 573 326 494 326 494 1015 573 326 494 326 494 326 494 327 493 326 494 326 494 326 494 325 495 326 494 325 495 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 1015 573 326 494 326 494 1015 573 1015 573 326 494 326 494 326 494 326 494 1015 573 326 494 1016 572 1016 572 1016 572 1016 572 326 494 326 494 326 494 326 494 1016 572 326 494 1016 572 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 494 326 495 326 494 327 493 326 494 1018 570 326 494 326 494 1018 570 1017 571 326 495 326 494 326 493 326 496 +# +# Model: Friedrich AKB73756214 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3267 9589 679 1380 626 416 546 470 546 469 547 1512 547 469 571 445 571 445 572 1462 569 1490 570 447 544 473 566 450 567 450 568 449 594 448 568 448 568 448 568 448 569 448 569 448 567 1468 566 450 567 1493 568 449 567 449 568 448 568 1466 567 +# +# Model: Frigidaire RG15D_AC +# +name: Off +type: parsed +protocol: NECext +address: 08 F5 00 00 +command: 11 EE 00 00 +# +# Model: Fujidenzo FEA5001_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9464 4442 637 583 601 584 601 584 601 584 600 582 603 580 604 580 630 555 629 1637 627 1665 598 1668 571 1696 569 1696 594 1672 594 1672 592 1674 593 1673 593 591 594 1673 593 1672 593 591 591 593 569 616 568 616 593 593 591 1674 593 592 593 593 592 1675 592 1675 592 1700 568 1700 568 39559 9409 2272 567 96244 9461 2246 594 +# +# Model: Fujitsu AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3302 1618 435 393 436 391 438 1207 431 396 433 1213 435 392 437 391 438 388 441 1206 432 1214 434 392 437 389 440 388 431 1215 433 1213 435 392 437 390 439 388 431 396 433 395 434 392 437 390 439 388 431 396 433 394 435 392 437 390 439 388 441 1205 433 395 434 392 437 389 440 388 431 396 433 394 435 392 437 1209 439 388 431 397 432 394 435 393 436 1210 438 387 432 396 433 394 435 392 437 390 439 388 431 1215 433 394 435 1211 437 1208 440 1205 433 1213 435 1211 437 1209 439 +# +# Model: Fujitsu AC_ASU18RLF +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3306 1624 406 411 413 404 410 1224 414 403 411 1223 405 412 412 405 409 408 406 1228 410 1224 414 402 412 406 408 409 405 1229 409 1224 414 403 411 407 407 410 414 402 412 406 408 409 405 412 412 404 410 408 406 411 413 403 411 406 408 409 405 1230 408 408 406 411 413 404 410 407 407 410 414 403 411 406 408 1226 412 405 409 408 406 411 413 404 410 1224 414 403 411 406 408 409 405 412 412 405 409 408 406 1228 410 407 407 1227 411 1222 406 1229 409 1198 440 1220 408 1227 411 +# +# Model: GE AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8521 4240 539 1600 542 1572 539 1600 542 567 514 1572 539 570 511 1602 519 1593 600 4181 515 567 514 1599 543 539 511 571 520 563 508 575 485 570 511 572 592 19298 8523 4263 536 1577 544 1595 537 1577 544 564 517 1597 535 548 512 1627 515 1599 512 4245 544 565 485 1627 515 569 512 571 489 567 514 569 512 571 490 567 596 19304 8527 4262 537 1576 545 1595 537 1577 544 565 516 1623 509 548 512 1601 541 1626 485 4245 544 565 485 1601 541 569 512 571 490 567 514 569 512 570 491 566 597 19297 8524 4265 534 1580 541 1598 544 1569 542 567 514 1626 516 541 519 1620 512 1629 492 4238 541 568 492 1594 538 572 509 574 486 571 510 572 509 574 487 570 594 +# +# Model: GE Window_AC +# +name: Off +type: parsed +protocol: NECext +address: 98 6F 00 00 +command: 19 E6 00 00 +# +# Model: Gree airco +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9018 4484 651 553 653 555 650 1657 650 554 652 554 652 1656 652 1656 651 553 653 1656 651 1656 652 1657 651 554 652 553 653 554 652 553 652 557 650 554 652 554 652 554 652 554 652 554 652 1655 652 555 651 555 652 555 651 553 652 556 650 554 652 1656 652 553 653 1655 653 552 654 555 651 1656 652 555 651 19990 652 1657 650 1658 650 553 652 1655 652 554 652 554 652 554 652 554 652 554 652 555 651 554 652 553 654 556 650 553 653 1655 652 554 652 554 652 554 652 555 651 556 651 554 651 553 653 555 651 554 652 554 651 554 652 554 652 556 651 1655 652 553 652 554 651 1656 652 +# +# Model: Gree airco_lightoff +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9075 4405 725 1580 728 478 728 480 726 481 725 478 728 479 728 479 727 478 728 478 727 1583 724 479 727 1581 727 480 727 480 726 478 727 480 726 479 727 481 725 479 728 478 728 478 728 478 727 479 727 480 726 477 729 478 728 478 728 480 726 1581 726 480 726 1580 727 479 727 478 727 1582 725 480 726 19911 650 1657 650 1657 650 556 650 558 647 556 650 556 649 557 649 555 650 556 649 555 651 556 650 556 650 558 649 555 651 1658 650 555 650 555 650 556 650 555 651 556 650 557 649 556 650 557 649 553 653 555 651 554 651 583 649 555 651 1657 650 555 651 556 650 1656 651 +# +# Model: Gree KFR-70G-A1 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9081 4414 685 1618 682 509 680 536 654 537 653 537 654 537 653 1675 627 564 626 1676 653 1649 653 1649 653 537 653 538 653 537 653 537 654 537 653 537 654 537 653 537 653 538 653 538 653 537 653 538 653 538 653 537 653 537 654 538 652 538 653 1649 653 538 652 1676 626 538 652 538 653 1676 653 538 652 +# +# Model: GREE YAPOF +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8995 4545 627 1658 629 1660 627 581 628 554 629 1660 627 1659 628 1685 628 555 628 1659 628 581 628 554 629 1660 627 554 629 581 628 555 628 554 629 581 628 555 628 555 628 581 628 555 628 1660 627 581 628 554 629 555 627 581 628 554 628 556 627 1686 627 555 627 1660 627 557 626 581 628 1658 629 555 628 20037 627 555 628 581 628 555 628 556 627 1687 626 556 627 555 628 582 627 556 627 556 626 582 627 555 628 557 626 581 628 1659 628 555 628 555 628 581 628 554 628 556 627 584 625 556 626 556 627 582 627 557 625 555 627 582 627 555 627 1661 625 1686 627 556 653 1632 655 39992 8996 4518 650 1660 653 1635 627 556 627 583 625 1660 628 1661 626 1660 627 581 628 1658 629 557 625 557 626 1686 627 556 627 555 628 582 627 556 627 556 627 582 627 555 628 557 626 582 627 1661 626 557 626 583 626 556 627 556 627 581 628 556 627 1661 626 1660 627 1687 626 555 628 556 627 1688 625 557 626 20012 626 581 628 556 627 556 626 583 626 556 626 556 627 583 626 556 627 557 626 582 627 555 628 556 627 582 627 556 627 556 627 583 626 556 626 556 627 584 625 556 627 556 627 581 628 1661 626 1661 626 582 627 556 627 555 628 583 626 557 625 1660 627 557 626 583 626 +# +# Model: Haier AC_HWE08XCR-L +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8714 4338 579 538 551 540 549 542 557 1613 555 1615 553 538 551 540 549 1619 549 1620 558 1612 556 1614 554 1617 551 540 559 1611 557 1612 556 531 558 1613 555 536 553 538 551 1620 558 1612 556 535 554 537 552 535 554 537 552 1618 550 1620 558 533 556 536 553 1617 551 1617 551 1608 549 +# +# Model: Hisense DG11J1-99_celsius +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8987 4555 560 1694 561 1698 561 574 562 578 562 584 560 586 561 590 560 1705 561 567 563 1698 561 1703 559 577 563 579 564 586 561 588 563 578 562 568 562 572 561 574 563 580 561 582 562 587 561 591 560 579 561 568 562 1699 561 576 561 578 562 581 562 586 561 589 561 580 560 567 563 571 562 576 561 580 561 582 562 587 560 590 561 579 561 567 562 571 562 575 562 578 562 581 563 586 561 588 563 559 563 8017 563 568 561 571 562 574 562 578 562 582 561 585 561 590 561 1702 564 567 562 572 561 574 562 580 560 581 562 584 563 589 562 578 562 569 561 573 561 575 561 577 563 582 562 586 561 589 561 577 563 569 561 571 562 574 562 578 562 582 561 586 561 590 561 579 561 567 563 571 562 575 562 577 563 582 561 585 562 589 562 577 563 568 562 571 562 575 562 578 563 582 562 585 562 589 562 578 562 569 561 571 562 575 562 579 561 581 562 585 562 588 563 578 562 568 562 1697 563 576 561 578 562 580 564 585 562 588 562 1687 562 8017 561 568 561 571 562 576 561 578 562 581 562 585 562 588 563 579 562 569 561 572 562 575 562 579 562 582 562 585 563 589 562 578 562 568 562 571 562 574 563 579 562 582 562 585 563 589 562 580 561 569 561 572 562 574 563 578 562 582 562 584 564 589 562 578 562 567 563 571 563 575 562 1705 562 583 561 584 563 589 562 579 562 568 562 571 562 574 563 578 563 581 563 586 562 588 563 578 563 568 562 572 562 574 563 1706 561 581 563 585 562 588 563 561 561 +# +# Model: Hisense room_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9115 4510 641 1653 641 1632 614 531 611 539 608 543 608 546 608 550 608 1673 608 529 608 1666 608 1670 607 540 607 544 607 570 584 574 584 564 584 553 584 557 584 1694 584 563 584 567 584 1704 584 1708 583 564 583 1686 584 557 584 560 584 564 583 1701 583 1704 584 1708 584 564 584 553 583 557 583 561 583 564 583 567 584 571 584 574 584 564 583 553 584 557 583 561 583 564 584 568 583 572 583 575 583 546 583 8052 583 554 583 557 583 561 583 565 583 568 583 572 582 575 583 1699 582 1688 583 558 583 561 583 565 582 1702 583 1706 582 575 583 565 583 555 582 558 582 562 582 565 582 569 582 572 582 576 582 566 582 555 582 558 582 562 581 565 582 569 581 573 581 576 581 566 581 555 582 559 581 562 582 566 581 570 581 573 581 577 581 566 581 556 581 560 581 563 581 567 580 570 581 574 580 578 580 1701 580 1690 581 560 580 564 580 1701 580 1704 580 575 580 579 579 568 579 1691 579 561 580 1698 579 1702 578 1705 579 1709 579 580 578 575 554 8080 554 582 555 586 554 589 555 593 554 596 555 600 554 604 554 593 555 1717 554 586 554 590 554 593 554 597 554 601 554 604 554 593 555 583 554 586 554 590 554 594 554 597 554 601 553 604 554 594 553 584 553 587 554 591 553 594 554 598 553 602 553 605 553 595 553 584 553 588 552 592 552 1729 553 599 552 602 553 606 552 595 552 585 552 588 552 592 552 620 527 599 552 628 526 631 527 621 526 1744 526 614 526 618 526 1754 526 624 527 628 526 631 527 604 526 +# +# Model: Hisense window_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8963 4401 561 543 558 545 566 537 564 540 561 542 559 1652 561 1650 563 540 561 543 558 545 567 537 564 1647 566 1645 558 1654 559 544 567 536 565 1646 567 1644 559 545 567 537 564 1647 566 537 564 540 561 542 559 545 567 537 564 539 562 541 560 544 567 536 565 538 563 541 560 543 558 545 567 538 563 540 561 542 559 545 567 537 564 539 562 542 559 544 568 536 565 539 562 541 560 543 569 536 565 538 563 541 560 543 558 545 566 538 563 540 561 542 559 545 566 537 564 539 562 542 559 544 567 536 565 539 562 541 560 544 568 536 565 538 563 541 560 543 558 545 567 538 563 540 561 543 558 545 567 537 564 540 561 542 559 544 568 536 565 539 562 541 560 570 542 536 565 538 563 541 560 543 558 546 565 538 563 540 561 543 558 571 541 537 564 540 561 542 559 545 567 537 564 539 562 542 559 544 568 536 565 539 562 541 560 544 568 536 565 538 563 541 560 543 569 535 566 538 563 541 560 543 569 535 566 538 563 541 560 543 569 536 565 538 563 541 560 544 567 536 565 539 562 542 559 544 567 537 564 539 562 542 559 544 568 537 564 539 562 542 559 544 568 537 564 539 562 542 559 545 567 537 564 540 561 542 559 545 566 537 564 540 561 543 558 545 567 538 563 541 560 543 569 536 565 538 563 541 560 544 568 536 565 539 562 542 559 544 568 1645 558 546 566 1646 567 537 564 540 561 1651 562 541 560 544 568 563 538 539 562 541 560 544 567 536 565 539 562 541 560 544 567 537 564 539 562 542 559 544 568 537 564 540 561 542 559 545 566 1645 558 546 566 1646 567 537 564 540 561 1651 562 541 560 544 588 +# +# Model: Hitachi RAK35 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 479 397 30131 50087 3427 1674 447 1253 447 483 446 483 446 483 420 483 447 483 447 483 447 483 447 483 420 510 419 483 447 483 446 1254 446 483 420 510 419 484 446 483 447 484 445 483 446 484 419 1255 445 1254 446 484 445 484 445 484 419 484 446 484 446 484 445 484 446 484 445 1255 418 484 446 1255 445 1255 418 1255 445 1255 445 1254 446 1255 419 484 446 1255 445 1255 418 511 418 485 445 484 445 484 446 484 445 485 418 511 418 484 446 1255 445 1255 418 1255 445 1255 444 1256 444 1256 417 1256 445 1255 443 487 418 511 419 485 444 1255 445 485 445 485 420 509 418 485 445 1255 445 485 444 485 445 1256 417 512 418 485 444 485 445 1256 417 1256 444 485 444 485 420 509 444 486 418 511 418 485 445 485 444 1256 444 486 418 512 417 485 420 509 445 485 445 485 445 485 417 1256 444 486 444 485 445 1256 417 1256 443 486 444 485 444 486 444 485 444 485 418 485 444 485 444 485 444 486 443 486 417 1257 420 509 420 510 444 485 444 486 444 485 418 485 445 485 443 487 444 1256 444 1256 417 485 442 487 420 510 444 485 444 486 417 1256 420 1280 443 1257 417 512 393 510 444 486 442 487 443 486 419 510 417 513 419 484 419 510 444 486 419 510 419 510 393 537 415 488 420 510 418 510 419 510 419 510 392 537 392 511 445 484 419 511 418 511 418 511 392 538 391 511 419 511 419 511 418 511 418 511 391 511 419 512 418 511 418 511 419 511 418 512 391 512 418 512 417 512 417 512 418 512 417 512 391 512 418 512 417 512 417 512 417 512 391 539 390 513 416 513 417 512 417 513 417 537 366 564 365 514 416 537 392 537 392 538 392 538 365 564 365 538 392 538 392 538 391 538 391 538 365 564 365 538 392 538 391 1309 391 1308 365 538 391 538 392 538 391 538 392 538 365 565 364 538 392 538 391 539 391 1309 364 1309 391 538 391 539 391 538 391 538 391 539 364 538 392 539 390 539 391 539 390 539 364 565 364 539 391 539 390 1309 391 539 390 1309 364 539 391 539 390 539 391 539 498 +# +# Model: Hitach RAK-18QH8 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3295 1683 467 1196 440 407 467 404 470 407 467 405 469 406 467 405 468 409 468 407 467 406 468 405 469 405 469 1168 468 403 470 406 468 411 467 404 470 406 468 404 469 406 468 405 469 406 467 404 470 409 469 405 469 404 470 405 468 406 468 407 467 405 468 1167 469 410 467 1168 468 1167 469 1167 469 1167 468 1168 468 1167 469 407 467 1173 467 1168 468 1169 467 1166 470 1168 468 1168 468 1168 468 1167 469 1171 469 405 469 409 464 404 470 432 441 405 469 404 470 407 467 410 468 404 470 405 468 1169 467 1168 468 404 469 405 469 1167 469 1169 471 1167 469 1167 469 405 469 403 470 1167 469 1168 468 406 468 409 469 404 469 1168 468 404 469 404 469 1168 468 406 467 406 468 1171 469 1169 467 404 469 1167 469 1167 469 404 469 1169 467 1195 441 409 469 1169 467 1168 468 405 469 404 470 1168 468 404 470 405 469 409 469 405 469 404 470 1167 469 1169 467 403 470 1169 467 1168 468 1171 469 404 469 405 469 1168 468 1168 468 1168 468 404 470 1169 467 408 469 1169 467 1167 469 404 470 405 469 405 468 1168 468 405 468 1173 467 406 467 406 468 407 466 406 468 406 468 406 468 406 468 410 468 1168 468 1168 468 1168 468 1169 467 1169 467 1168 468 1169 467 1172 468 405 468 406 468 407 467 407 467 406 468 405 469 406 468 411 466 1168 468 1167 469 1168 468 1169 467 1195 440 1169 467 1168 468 1171 469 405 469 407 467 407 467 406 467 407 467 406 468 406 467 410 468 1168 468 1169 467 1169 467 1169 467 1168 468 1168 468 1167 469 1172 468 407 467 404 469 405 469 405 468 406 468 406 468 406 467 410 468 1169 467 1169 467 1168 468 1168 468 1167 469 1168 468 1167 469 1172 468 405 468 405 469 407 466 406 468 407 466 406 468 406 468 410 468 1168 468 1168 468 1170 466 1168 468 1168 468 1168 468 1168 468 1173 467 1168 468 405 468 1169 467 406 467 406 468 1168 468 406 468 410 467 408 466 1168 468 407 467 1168 468 1170 466 406 468 1169 467 1173 467 1169 467 405 468 406 467 407 467 1170 466 1170 466 1170 466 1173 467 406 468 1169 467 1171 465 1169 467 406 468 406 467 406 467 411 467 406 468 406 467 407 467 407 466 407 467 407 467 408 466 412 465 1170 466 1170 466 1169 467 1169 467 1170 466 1170 466 1169 467 1174 466 407 466 408 466 408 466 407 466 407 467 407 466 406 468 411 467 1170 466 1170 466 1171 465 1168 468 1170 466 1169 467 1170 466 1174 466 407 467 408 466 407 466 407 467 407 467 408 466 407 466 412 466 1171 465 1169 467 1170 465 1170 466 1171 465 1170 465 1171 465 1174 466 1170 466 1169 467 408 465 408 465 408 465 408 466 408 465 414 464 408 466 407 467 1170 466 1170 466 1170 466 1170 466 1171 465 1174 465 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3376 1605 463 1172 464 411 463 408 466 407 466 412 462 409 465 409 465 415 463 409 464 411 463 408 465 409 464 1171 465 410 463 410 464 413 464 437 437 411 462 410 464 409 464 410 463 410 464 410 463 413 465 409 465 413 460 410 464 409 465 410 464 409 465 1173 463 415 462 1171 465 1174 462 1174 462 1173 463 1173 463 1174 462 412 462 1177 463 1175 461 1173 462 1172 464 1173 463 1174 462 1172 464 1174 462 1177 463 411 463 409 464 410 464 411 463 410 464 410 463 411 463 414 463 410 463 412 462 1174 462 1174 462 410 464 412 462 1173 463 1177 463 1174 462 1173 463 411 462 411 463 1174 462 1174 462 413 460 417 461 414 459 1174 462 411 463 413 460 1174 462 412 461 413 461 1179 461 1175 461 412 462 1172 464 1174 462 414 460 1174 462 1172 464 414 464 1172 464 1172 464 409 465 409 465 1172 464 409 464 408 466 415 463 408 465 410 464 1171 465 1171 465 408 466 1172 464 1172 464 1176 464 409 464 409 465 409 465 409 465 409 465 409 465 1172 464 412 466 1172 464 1171 465 1174 462 1171 465 1172 464 1172 464 409 465 1176 464 411 463 408 465 408 466 408 466 409 465 409 465 409 464 414 464 1172 464 1172 464 1172 464 1172 464 1173 463 1171 465 1174 462 1177 462 409 465 409 465 411 462 410 464 410 464 409 465 409 464 414 464 1172 464 1172 464 1172 464 1171 465 1173 463 1173 463 1172 464 1176 464 409 464 410 464 410 463 410 463 410 464 410 464 409 464 414 464 1172 464 1173 463 1173 463 1172 464 1172 464 1173 463 1173 463 1176 464 410 463 411 463 411 463 409 465 409 464 411 462 411 463 414 463 1173 463 1172 464 1173 463 1172 464 1172 464 1173 463 1174 461 1178 462 410 463 410 463 412 462 411 463 410 463 409 464 411 463 415 463 1173 463 1174 462 1173 463 1173 462 1173 463 1174 462 1172 464 1178 462 1171 465 1174 462 411 462 411 463 1174 462 410 463 1173 463 414 463 412 462 410 463 1174 462 1173 463 411 463 1173 463 410 464 1177 463 1174 462 411 463 411 462 411 463 1174 462 1174 462 1175 461 1178 462 412 461 1173 463 1172 464 1175 461 412 462 411 462 411 463 415 462 411 463 412 462 412 461 412 462 412 462 411 463 412 462 416 461 1174 462 1173 463 1174 462 1174 461 1174 462 1175 461 1174 462 1181 459 412 462 411 463 412 462 412 461 411 462 411 463 413 460 415 463 1174 462 1175 461 1176 460 1176 460 1174 462 1174 461 1174 462 1178 461 413 461 413 460 413 460 414 460 412 461 412 462 412 462 417 460 1175 461 1175 461 1175 461 1175 460 1176 460 1175 461 1174 462 1179 461 1175 460 1177 435 436 461 413 460 413 437 437 460 412 438 440 461 414 436 437 437 1199 460 1177 459 1176 460 1176 437 1199 437 1202 437 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3376 1603 465 1198 438 408 489 384 490 382 468 407 467 408 466 407 467 439 438 408 490 384 489 384 466 435 439 1169 467 408 466 408 489 388 467 411 463 406 467 409 465 408 490 385 465 409 488 383 467 412 489 383 467 407 466 409 465 408 466 408 466 408 466 1173 463 412 466 1171 465 1170 466 1171 465 1170 466 1170 466 1170 466 408 466 1175 465 1169 467 1171 465 1171 465 1173 463 1172 464 1170 466 1170 466 1176 463 409 465 409 465 410 463 407 467 409 465 411 462 407 467 413 464 407 466 410 464 1172 464 1173 463 408 466 411 463 1171 465 1175 465 1171 465 1171 465 408 465 410 464 1171 465 1172 463 408 465 413 465 408 466 1171 465 409 465 408 465 1173 463 410 464 408 465 1175 465 1169 467 409 464 1172 464 1172 463 408 466 1172 464 1172 464 412 466 1171 465 1172 464 409 464 409 464 1171 465 411 462 410 464 414 463 409 464 411 463 1172 463 1174 462 411 463 1173 463 1173 463 1177 463 410 463 411 463 1172 464 1173 463 1178 458 411 462 1172 464 416 462 1174 462 1173 463 412 461 411 463 411 463 1174 462 411 462 1178 462 411 462 410 464 413 461 411 463 410 464 412 462 411 463 417 461 1173 463 1175 461 1173 463 1173 463 1172 464 1173 463 1175 461 1177 463 411 463 410 464 410 463 410 464 410 464 411 463 410 463 414 464 1173 463 1173 463 1174 462 1172 464 1175 461 1173 463 1175 461 1176 464 411 463 410 463 412 461 411 462 411 463 411 462 414 460 415 463 1174 462 1174 462 1173 463 1173 463 1174 462 1173 463 1173 463 1178 462 410 463 411 463 411 463 411 463 412 462 410 463 411 462 416 462 1173 463 1174 462 1174 462 1174 462 1173 463 1174 462 1173 463 1177 463 410 463 438 436 410 463 412 462 410 463 411 463 412 461 416 462 1174 462 1174 462 1175 461 1173 463 1174 462 1173 463 1174 462 1179 461 1175 461 1174 462 411 463 412 462 1176 459 412 462 1174 461 415 463 411 462 411 463 1175 461 1174 462 411 463 1175 460 412 462 1178 461 1176 460 411 463 412 461 412 462 1174 462 1174 462 1174 462 1179 461 412 461 1174 462 1174 462 1175 461 413 460 412 462 412 462 417 461 411 463 412 462 412 461 413 460 412 462 412 462 413 461 416 461 1176 461 1175 461 1174 462 1175 461 1174 462 1176 460 1174 461 1180 460 412 461 414 460 412 462 414 459 413 461 413 461 413 460 417 461 1176 459 1176 460 1177 459 1176 460 1175 460 1175 461 1176 437 1203 460 414 459 413 461 413 460 413 437 436 461 413 438 437 437 440 461 1176 437 1198 438 1198 438 1198 461 1175 461 1177 459 1177 436 1203 437 1201 458 1175 438 436 438 436 438 437 436 437 437 436 437 441 437 438 436 436 438 1198 438 1200 436 1199 437 1199 437 1199 437 1203 436 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3297 1684 411 1224 412 461 413 462 412 460 413 461 413 461 413 463 411 466 412 461 412 461 413 461 413 461 413 1223 413 461 412 465 409 466 412 461 412 462 467 406 412 461 413 462 467 407 466 406 412 465 467 406 468 406 468 406 468 405 468 408 466 406 467 1168 412 493 440 1167 469 1168 468 1168 468 1167 468 1168 468 1168 468 407 466 1171 469 1167 469 1170 466 1168 468 1168 468 1168 468 1168 468 1168 468 1173 467 407 466 407 467 408 465 406 468 406 468 404 470 405 469 409 469 405 468 406 468 1168 468 1167 469 406 468 405 468 1166 470 1172 468 1166 470 1171 465 406 467 406 468 1168 468 1168 468 405 469 410 467 405 469 1167 469 406 468 405 469 1168 468 405 468 406 468 1173 467 1169 467 406 467 1169 467 1169 467 407 467 1169 467 1169 467 412 466 1167 469 1169 467 408 466 407 467 1169 467 408 465 408 466 411 466 407 467 409 465 1171 465 1169 467 406 467 1197 439 1171 465 1174 466 434 440 405 468 406 468 405 469 407 467 407 466 406 468 1172 468 1170 466 1168 468 1168 468 1167 469 1169 467 1167 469 1171 465 411 467 405 469 405 469 408 466 405 469 407 466 405 469 405 469 410 468 1168 468 1168 468 1168 468 1169 467 1169 467 1168 468 1169 467 1173 467 406 468 405 468 406 467 407 467 405 469 406 468 406 467 411 467 1169 467 1168 468 1168 468 1167 469 1168 468 1168 468 1169 467 1173 467 406 468 405 468 404 470 405 469 406 468 405 468 406 468 409 468 1168 468 1168 468 1169 467 1169 467 1168 468 1170 465 1169 467 1172 468 405 468 407 467 406 467 406 467 407 467 406 467 406 468 410 467 1168 468 1170 466 1168 468 1168 468 1169 467 1169 467 1168 468 1173 467 406 468 406 468 407 467 406 468 408 466 406 468 406 468 411 466 1170 466 1170 466 1168 468 1169 467 1169 467 1170 466 1169 467 1174 466 406 468 1169 467 1168 468 406 468 1168 468 406 468 1169 467 410 467 1170 466 407 467 406 468 1169 467 406 467 1169 467 407 466 1172 468 1170 466 409 465 407 467 407 466 1169 467 1170 466 1169 467 1173 467 407 466 1172 464 1171 465 1169 467 407 466 407 467 406 467 411 467 406 468 408 465 407 467 407 467 407 467 407 467 407 466 412 466 1169 467 1170 466 1170 466 1169 467 1169 467 1169 467 1170 466 1174 466 408 465 408 466 408 466 407 466 409 465 406 467 409 465 411 466 1170 466 1170 466 1170 466 1171 465 1170 466 1171 465 1170 466 1174 466 407 466 408 466 408 466 408 465 407 467 408 466 407 467 411 466 1169 467 1170 466 1170 466 1170 466 1171 465 1170 466 1170 466 1174 466 1170 466 1170 466 408 466 409 465 407 467 407 466 409 465 412 466 409 465 409 465 1171 465 1170 466 1171 465 1171 465 1171 465 1175 464 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3376 1602 466 1169 467 410 463 406 468 405 469 406 468 408 466 407 466 412 466 407 467 407 467 407 466 408 466 1171 465 406 468 406 467 411 467 407 467 407 467 407 467 407 467 409 465 406 467 406 467 412 466 406 468 406 468 407 467 406 467 407 467 407 466 1170 466 410 467 1171 465 1170 466 1169 467 1169 467 1171 465 1171 465 408 466 1174 466 1170 466 1169 467 1171 465 1169 467 1171 465 1169 467 1172 464 1173 467 408 466 408 466 410 464 407 467 408 465 409 465 407 466 412 466 408 465 409 465 1172 464 1171 465 408 466 411 463 1170 466 1175 464 1173 463 1171 465 408 465 408 466 1171 465 1172 464 411 463 412 466 409 465 1170 466 408 466 408 466 1171 465 408 465 409 465 1175 464 1171 465 410 463 1171 465 1171 465 408 466 1171 465 1171 465 412 465 1169 467 1170 466 408 466 410 464 1170 466 409 464 408 466 412 466 408 466 407 467 1171 465 1170 466 407 467 1171 465 1171 465 1175 465 436 438 408 466 1170 466 1170 466 1171 465 408 466 1170 466 413 464 1172 464 1173 463 409 464 408 466 411 462 1172 464 409 465 1177 463 409 465 410 464 409 464 409 465 409 465 410 464 409 465 414 464 1172 464 1172 464 1173 463 1172 464 1171 465 1173 463 1170 466 1177 463 409 464 410 464 408 465 437 437 410 464 410 464 410 464 413 464 1171 465 1172 464 1171 465 1173 463 1172 464 1175 461 1173 463 1176 464 410 464 410 464 411 462 408 466 410 464 409 464 410 464 414 464 1173 463 1172 463 1173 463 1175 461 1172 464 1172 464 1173 463 1177 463 411 463 410 464 410 463 410 464 409 465 410 464 410 464 414 463 1172 464 1173 463 1174 462 1173 463 1173 463 1173 463 1174 462 1178 462 409 464 410 464 411 462 412 462 411 462 412 462 411 462 414 463 1173 463 1172 464 1173 463 1173 463 1174 462 1172 464 1173 462 1177 463 412 461 1174 462 1174 462 411 463 1173 462 411 463 1175 461 415 463 1173 463 411 462 412 462 1174 462 411 463 1173 463 411 463 1178 462 1173 463 412 462 410 463 411 463 1174 462 1173 463 1174 462 1178 462 411 462 1174 462 1174 462 1174 461 412 462 412 462 411 463 416 462 412 462 411 462 412 462 412 461 411 463 412 462 412 461 416 461 1174 462 1174 462 1175 461 1175 461 1175 461 1174 462 1174 462 1179 461 412 461 413 461 412 461 412 462 412 461 414 460 412 461 416 462 1176 460 1174 462 1174 462 1175 460 1174 462 1175 460 1177 459 1179 461 412 461 413 461 413 460 413 461 413 461 412 461 412 462 416 461 1176 460 1174 462 1174 462 1175 461 1176 459 1177 459 1175 437 1203 460 1175 461 1176 436 438 459 414 460 414 460 413 460 414 459 418 437 437 437 437 437 1199 437 1200 436 1200 436 1200 436 1200 436 1202 437 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3378 1603 466 1170 466 408 465 406 468 407 467 407 467 408 466 407 467 412 466 407 467 408 465 406 468 406 468 1170 466 407 467 406 468 413 465 410 464 407 466 409 465 407 467 406 468 409 464 408 466 411 467 407 466 405 469 407 467 406 467 408 466 406 468 1170 466 412 466 1169 466 1171 465 1171 465 1170 489 1146 466 1172 488 383 467 1177 487 1149 487 1145 466 1172 487 1147 465 1169 466 1174 486 1147 465 1174 466 409 465 409 465 409 464 409 464 408 465 409 465 409 464 415 463 409 464 409 465 1170 466 1171 465 409 465 410 463 1172 463 1177 463 1172 464 1199 436 413 461 410 464 1172 463 1172 464 411 463 415 463 411 463 1173 463 411 462 410 464 1174 461 409 465 411 463 1176 464 1173 463 410 464 1172 464 1172 464 437 436 1174 462 1173 462 415 463 1174 462 1173 463 411 463 413 461 1172 464 411 462 410 463 415 463 409 465 411 463 1172 464 1172 464 409 465 1173 463 1174 462 1176 464 409 465 409 465 1171 465 1170 466 1173 463 408 466 1172 464 412 465 1171 465 1172 464 407 467 408 465 408 466 1171 465 408 466 1175 465 408 465 407 466 408 466 409 465 408 465 408 466 407 467 412 466 1170 466 1173 463 1170 466 1170 466 1170 466 1170 466 1171 465 1174 466 408 465 407 467 409 465 408 465 409 464 408 466 408 466 414 464 1171 465 1170 466 1171 465 1170 466 1172 464 1171 465 1170 466 1176 464 409 465 410 463 408 466 411 463 409 464 409 465 408 466 413 464 1170 466 1172 464 1171 465 1170 466 1171 465 1172 464 1170 466 1178 462 408 465 410 464 409 465 408 466 409 465 409 465 409 465 413 465 1171 465 1172 464 1172 464 1173 463 1171 465 1172 464 1171 465 1176 464 407 467 409 465 409 465 410 463 409 465 409 465 411 463 413 464 1173 463 1174 462 1172 464 1172 464 1171 465 1171 465 1172 464 1175 465 410 464 1172 464 1174 462 409 465 1172 464 410 463 1170 466 414 463 1172 464 410 464 409 465 1172 464 411 462 1172 464 409 465 1175 465 1172 464 410 463 410 464 410 464 409 464 1173 463 1173 463 1176 464 410 464 1173 463 1171 465 1171 465 1172 464 409 464 410 464 414 464 410 464 410 464 410 464 409 465 410 463 410 464 411 463 414 464 1173 463 1172 464 1173 463 1172 464 1172 464 1173 463 1172 464 1177 463 410 464 410 463 410 463 411 463 410 464 411 463 409 464 414 464 1173 463 1173 462 1174 462 1173 463 1173 463 1173 463 1174 462 1177 463 411 463 412 462 410 463 411 463 411 462 412 462 410 463 414 464 1173 463 1174 462 1173 463 1172 464 1174 462 1173 463 1173 463 1178 462 1173 463 1174 462 411 462 411 463 411 462 410 464 411 463 415 463 411 463 412 462 1174 462 1173 463 1174 462 1175 461 1173 463 1178 461 +# +# Model: Inventum AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4419 4378 562 1618 568 552 541 1619 567 1640 536 533 560 535 558 1649 537 559 534 537 566 1619 567 554 539 530 563 1618 568 1613 563 532 561 1626 560 537 566 554 539 530 563 557 536 536 1622 564 1616 560 1624 562 1625 561 1620 566 1615 561 1621 565 1616 560 534 559 536 567 529 564 559 534 1625 561 533 560 1625 561 1620 566 528 565 530 563 1620 566 530 563 532 561 1624 562 532 561 1650 536 1645 541 1614 562 1620 566 5226 4418 4372 558 1622 564 557 536 1623 563 1618 568 527 566 529 564 1617 559 564 539 531 562 1623 563 558 535 533 560 1621 565 1616 560 535 568 1619 567 529 564 557 536 532 561 560 533 561 542 1614 562 1619 567 1642 534 1628 569 1613 563 1619 567 1614 562 1619 567 528 565 530 563 533 560 563 540 1619 567 527 566 1619 567 1614 562 533 560 535 558 1625 561 536 567 527 566 1619 567 528 565 1620 566 1615 561 1620 566 1618 558 +# +# Model: Kelon AS-24HR4SQJUL +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9159 4545 562 1674 562 1675 561 596 560 595 535 621 535 621 535 623 533 1676 560 597 535 1702 534 1703 533 622 535 622 535 622 509 648 508 597 560 596 560 596 558 1679 534 623 534 622 535 622 534 622 509 597 560 597 560 1678 558 598 558 599 532 1728 508 649 508 1729 507 597 560 597 558 598 534 623 534 623 533 623 531 625 508 649 507 597 560 597 534 623 534 623 534 623 534 623 508 649 508 649 508 571 508 8132 558 1703 533 1703 534 623 534 623 533 623 508 625 532 649 508 1677 560 597 558 599 533 1703 534 623 533 1703 533 1705 532 625 532 597 534 623 534 623 533 599 558 599 532 625 532 624 532 624 533 574 557 599 558 599 558 598 557 599 533 624 532 624 533 624 531 575 558 599 558 598 558 1703 534 1703 533 624 507 649 508 649 507 598 559 598 533 624 532 624 533 624 532 624 507 650 507 650 507 1678 559 1678 559 598 557 599 533 1705 532 1729 507 625 532 626 531 598 556 601 533 624 533 1704 532 624 532 1704 533 1704 533 1705 531 573 506 8133 558 598 558 598 533 624 532 624 533 624 532 624 507 650 507 599 558 1678 558 599 557 599 532 624 533 624 533 624 530 627 506 599 505 575 557 599 557 599 558 599 532 625 532 625 531 625 531 574 505 599 558 599 532 625 531 625 532 624 532 625 506 651 506 599 480 600 557 599 558 599 557 600 532 625 532 625 532 626 505 600 505 600 531 625 532 625 531 625 532 624 506 650 507 650 507 600 479 1681 556 600 557 600 556 600 531 626 530 650 507 626 530 574 506 +# +# Model: Unknown Model_1 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3150 2991 3125 4400 619 503 617 520 591 525 591 525 591 1621 616 1613 615 1619 640 517 589 1640 588 1645 587 1643 585 1649 584 533 583 533 583 533 583 1650 583 533 583 533 583 533 583 533 583 1650 583 1646 583 1650 583 1655 583 533 583 533 584 1654 584 1646 583 1650 583 1650 583 533 584 533 583 533 583 538 583 529 583 533 583 1651 582 1651 582 1651 582 1655 583 1646 583 534 582 1655 583 1651 582 534 582 1655 583 530 582 534 582 534 582 1655 583 529 583 1651 582 1655 582 530 582 1651 582 534 582 40182 3091 3040 3116 4436 583 533 583 538 583 529 583 533 583 1650 583 1651 583 1650 583 533 583 1650 583 1650 583 1655 583 1646 583 533 583 534 583 533 583 1651 582 533 583 534 582 534 582 533 583 1651 582 1655 583 1646 583 1651 583 534 582 534 582 1651 582 1656 581 1647 582 1656 582 530 582 534 582 534 583 534 582 539 582 534 582 1647 582 1656 582 1647 582 1651 582 1652 581 535 581 1652 581 1647 582 535 582 1652 581 535 581 535 581 535 581 1652 581 535 581 1652 581 1652 581 535 581 1653 580 535 581 +# +# Model: Legion LE-F30RH-IN +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6172 7369 602 1569 602 1569 602 1569 601 1570 573 1598 574 1598 573 1597 574 1598 574 526 573 526 573 527 572 528 571 529 570 529 570 530 569 530 568 1603 569 1603 569 1603 569 1603 569 1603 568 1603 568 1603 569 1604 568 531 568 531 568 531 568 532 567 531 568 555 544 532 567 555 543 1627 544 1628 543 1628 543 1628 543 1628 544 1628 543 1628 544 1629 543 556 543 556 543 556 543 556 543 556 543 556 543 556 543 555 543 1627 544 1628 543 1629 543 556 543 555 543 1628 543 1628 543 1629 543 556 543 556 544 555 544 1628 544 1629 543 556 543 556 543 556 543 556 543 556 543 555 543 1628 543 1629 543 555 543 1628 543 1628 543 1628 543 1628 543 1629 543 557 542 556 543 1629 543 556 543 556 543 555 543 1630 542 556 542 1629 543 557 543 1630 543 557 543 1630 543 1631 542 557 542 1631 542 557 543 1630 543 557 542 1630 543 558 542 7398 543 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6173 7370 600 1571 656 1515 603 1569 602 1570 600 1571 573 1598 574 1598 573 1598 574 525 574 526 573 526 573 527 572 528 571 529 570 530 569 530 569 1603 569 1603 569 1603 569 1603 569 1603 568 1603 569 1603 569 1604 568 531 569 531 568 531 568 531 568 532 567 555 544 532 567 555 544 1603 568 1604 567 1604 567 1605 567 1628 544 1605 567 1628 543 1629 543 556 544 555 544 556 543 556 543 556 543 556 544 556 543 555 544 1629 543 555 544 1629 543 556 543 556 543 556 543 555 543 1629 543 556 544 1630 543 556 544 1629 544 1629 544 1629 544 1630 543 557 543 556 544 1629 543 1630 543 556 544 1629 543 1630 543 556 544 1629 543 1630 543 557 544 556 543 1630 543 557 543 557 543 1630 543 557 543 557 543 1630 543 557 543 1630 543 557 543 1630 543 557 543 1629 543 1630 543 557 543 1630 543 557 543 1630 543 557 543 1631 543 557 544 7399 543 +# +# Model: Lennox AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4477 4354 606 1548 606 472 605 1551 603 474 578 501 576 524 553 524 553 1602 552 1602 553 525 552 1603 552 526 551 526 551 526 551 526 551 526 551 526 551 1604 551 1604 551 526 551 526 551 1604 551 1604 551 526 551 1604 551 526 551 526 551 526 551 526 551 526 551 526 551 1604 551 1604 551 526 551 526 551 526 551 526 551 1604 576 501 577 1579 576 501 551 1604 575 1579 576 1579 551 526 551 1604 551 1604 551 1604 551 5205 4446 4385 575 501 576 1579 576 501 576 1579 576 1579 576 1578 576 1579 576 501 576 501 576 1578 576 501 576 1579 576 1578 577 1578 577 1578 576 1578 577 1578 577 501 576 501 576 1578 577 1578 577 501 576 501 576 1578 576 501 576 1578 577 1579 576 1578 577 1579 576 1578 577 1578 576 501 576 501 576 1579 576 1579 576 1579 576 1579 576 501 576 1578 576 501 576 1579 576 501 576 501 576 501 576 1579 576 501 576 501 576 502 575 +# +# Model: LG AC +# +name: Off +type: parsed +protocol: NECext +address: 81 66 00 00 +command: 81 7E 00 00 +# +# Model: LG AC_2 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8455 4196 542 1566 541 539 519 535 513 541 517 1565 541 538 520 534 514 540 518 1563 544 1565 542 538 520 533 515 539 519 535 513 541 517 536 522 532 516 538 520 533 515 539 519 535 513 1569 538 542 516 1566 541 539 519 534 514 540 518 1564 542 +# +name: Dh +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 14 EB 00 00 +# +# Model: LG LP1417GSR_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8971 4413 598 1634 595 1641 599 526 599 529 596 533 592 541 594 541 594 1646 594 525 600 1633 596 1645 595 533 592 540 595 538 597 539 596 528 597 1629 600 520 594 526 599 526 599 530 595 537 598 538 597 530 595 523 591 1639 601 524 601 527 598 1643 597 537 598 539 596 531 594 522 592 528 597 527 598 530 595 536 599 534 591 546 600 526 599 517 597 521 593 528 597 528 597 532 593 541 594 544 591 519 595 7883 598 520 594 525 600 524 590 535 600 529 596 539 596 1651 599 1639 601 515 599 522 592 531 594 532 593 538 597 536 599 538 597 528 597 518 596 525 600 523 591 534 591 537 598 533 592 546 600 527 598 518 596 523 591 531 594 531 594 536 599 534 591 545 601 526 599 517 597 521 593 529 596 530 595 536 599 532 593 542 593 532 593 522 592 526 599 522 592 535 600 532 593 540 595 541 594 533 592 524 601 519 595 528 597 529 596 535 600 534 601 537 598 527 598 1627 592 1637 592 531 594 533 592 1652 598 537 598 1652 598 1623 596 7882 599 519 595 525 600 522 592 534 591 538 597 535 590 547 599 528 597 518 596 1634 595 1639 601 525 600 530 595 539 596 541 594 532 593 523 591 529 596 527 598 528 597 533 592 541 594 542 593 532 593 525 600 522 592 531 594 533 592 538 597 535 600 537 598 527 598 518 596 522 592 530 595 1646 594 1652 598 1650 600 538 597 530 595 521 593 527 598 526 599 529 596 534 591 543 592 546 600 527 598 519 595 1636 593 1640 600 1639 601 1644 596 1651 599 539 596 514 590 +# +# Model: LG LP1419IVSM +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3196 9607 615 1444 589 428 588 428 588 430 613 1444 589 428 588 428 587 430 588 1446 614 1445 589 429 588 429 588 429 584 434 611 406 582 483 561 455 561 456 560 456 558 460 557 459 558 1503 559 457 533 1527 559 457 559 457 586 457 560 1475 584 +# +# Model: LG R12AWN-NB11 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8838 3941 624 1427 514 500 515 497 541 473 540 1487 540 474 539 474 538 476 512 1538 514 1516 544 466 514 497 514 501 514 499 514 500 513 499 513 501 487 525 514 499 512 502 487 526 512 1517 542 470 487 1539 513 499 512 503 513 499 512 1513 512 +# +# Model: LG SX122CL +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3160 1550 576 1092 576 1091 577 329 555 338 573 329 558 1065 601 329 557 329 556 1065 601 1066 575 338 547 1093 574 339 546 339 546 1095 573 1095 572 339 546 1096 572 1097 571 341 544 341 544 1124 544 342 543 342 543 1125 542 344 542 344 541 344 542 344 542 343 541 344 542 344 542 344 542 344 542 344 542 344 542 343 541 344 542 344 542 344 542 344 542 344 542 344 542 343 541 345 542 1126 542 343 541 345 541 1126 542 1126 542 344 542 344 542 344 542 343 541 344 542 345 541 1126 541 1126 541 1127 542 1127 541 343 542 345 541 344 542 345 542 1125 541 345 542 1127 541 343 542 345 541 345 541 345 541 345 541 345 541 345 541 343 541 345 541 345 541 345 541 345 541 345 541 345 541 344 541 345 541 345 541 345 541 345 541 345 541 345 541 344 540 345 541 345 541 345 541 345 541 345 541 344 541 345 541 345 541 345 541 346 540 345 541 344 540 345 541 346 540 345 541 345 541 345 540 1128 541 1126 541 345 541 346 541 1126 541 345 541 +# +# Model: Lifetime Air +# +name: Off +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 02 00 00 00 +# +name: Dh +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 57 00 00 00 +# +# Model: Logik HLF-20R +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1286 428 1251 408 442 1241 1289 399 1280 431 408 1247 1283 431 408 1248 442 1242 437 1246 443 1240 439 8135 1279 434 1256 430 409 1246 1284 430 1260 426 413 1242 1287 426 413 1243 436 1247 443 1241 438 1245 445 8130 1284 428 1262 424 415 1240 1279 435 1255 430 409 1247 1283 430 409 1247 443 1241 438 1245 434 1249 441 8133 1281 432 1258 428 411 1244 1286 428 1251 407 443 1240 1279 434 416 1240 439 1244 435 1249 441 1243 436 8137 1287 400 1279 433 417 1239 1280 433 1257 430 409 1246 1284 429 410 1246 444 1240 439 1245 445 1239 440 8133 1281 405 1285 428 411 1245 1285 429 1261 425 414 1241 1289 425 414 1242 437 1246 444 1240 439 1245 434 8139 1285 428 1251 435 415 1240 1279 434 1256 430 409 1246 1283 430 409 1247 443 1241 438 1245 445 1239 440 8133 1281 432 1258 428 411 1244 1286 428 1251 407 443 1240 1279 434 416 1240 439 1244 435 1249 441 1243 436 8137 1287 426 1253 432 407 1248 1281 432 1258 401 438 1244 1285 427 412 1244 435 1248 442 1242 437 1247 442 8131 1283 403 1287 426 413 1242 1287 426 1253 433 417 1239 1280 432 418 1238 441 1243 436 1247 442 1241 438 8136 1288 398 1281 404 435 1248 1281 404 1286 400 439 1244 1285 400 439 1244 435 1249 441 1243 436 1247 442 8131 1283 404 1285 400 439 1243 1286 401 1278 407 443 1240 1289 397 442 1240 439 1245 434 1249 441 1243 436 8138 1286 401 1278 407 443 1240 1279 407 1283 403 436 1246 1283 403 436 1246 444 1240 439 1245 434 1249 441 8134 1280 406 1284 402 437 1246 1283 402 1288 398 441 1242 1288 399 440 1242 437 1247 443 1241 438 1245 434 8140 1284 402 1288 398 441 1242 1287 425 1254 432 407 1249 1281 432 407 1249 441 1243 436 1247 443 1241 438 8136 1288 425 1254 405 434 1248 1281 406 1284 401 438 1245 1285 402 437 1246 433 1250 440 1244 435 1248 442 +# +# Model: Midea AC_MAW05R1WBL +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4459 4372 589 1565 597 480 595 1559 592 485 590 487 588 488 598 479 596 1558 593 1562 589 1565 597 1558 593 483 593 485 590 486 589 488 598 479 596 480 595 1559 592 485 590 486 589 1565 597 481 594 1559 592 485 590 1564 598 479 596 481 594 483 592 484 591 1563 588 489 597 1558 593 1561 590 486 589 488 598 479 596 481 594 483 592 484 591 1563 599 478 597 480 595 482 593 483 592 485 590 486 589 488 588 489 597 5158 4462 4367 594 483 592 1563 588 488 598 1557 594 1560 591 1563 599 1556 595 482 593 484 591 485 590 487 588 1566 596 1559 592 1562 589 1565 597 1558 593 1562 589 488 598 1556 595 1559 592 485 590 1564 598 479 596 1558 593 484 591 1563 588 1566 596 1559 592 1562 589 488 598 1557 594 482 593 484 591 1563 588 1566 596 1559 592 1563 588 1565 597 1558 593 484 591 1563 588 1566 596 1559 592 1562 589 1565 597 1558 593 1561 590 1564 598 +# +# Model: Midea silent_cool_26_pro +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4528 4233 682 1461 684 389 683 1461 684 390 737 335 681 391 681 390 682 1461 683 1461 684 1461 683 1461 683 392 626 468 603 469 603 468 603 469 628 443 629 1515 629 443 628 444 627 445 626 1518 626 1519 625 1519 626 1519 626 446 626 446 625 446 625 446 626 446 625 1519 625 1519 626 1519 626 446 626 446 625 446 626 446 625 446 626 446 626 1519 625 446 625 446 626 446 625 446 625 1519 625 447 625 1519 626 446 626 5105 4471 4290 625 447 625 1520 625 447 625 1519 625 1520 624 1520 625 1520 625 447 625 447 625 447 624 447 624 1519 625 1520 625 1520 624 1520 625 1520 624 1520 625 447 625 1520 624 1520 624 1520 625 447 625 447 625 447 624 447 625 1520 624 1520 624 1520 624 1520 624 1520 624 447 625 447 625 447 625 1520 624 1520 625 1520 624 1521 624 1520 624 1521 624 447 624 1520 624 1521 624 1521 623 1520 624 448 624 1520 624 448 624 1521 624 +# +# Model: mitsubishi-MSY-GE10VA +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3439 1755 439 1262 435 1286 432 438 441 423 456 418 441 1282 436 433 436 431 438 1285 433 1314 435 408 461 1293 435 408 440 426 433 1289 460 1296 412 455 414 1312 406 1319 409 429 440 429 461 1267 441 426 464 406 442 1281 437 430 439 427 432 436 464 405 433 432 437 458 411 456 434 422 437 428 462 412 436 428 441 422 457 411 437 456 413 454 405 436 433 434 435 427 432 435 465 404 434 435 434 431 459 413 435 429 440 422 437 433 457 1270 438 1288 461 410 438 426 464 404 434 1293 435 1315 413 423 436 1287 462 407 431 434 435 435 434 433 467 415 433 1318 410 1286 463 409 439 1284 455 1301 438 410 438 426 464 1262 456 1269 439 429 461 410 469 407 462 1268 440 1285 433 461 408 456 413 1284 465 1261 457 414 455 1271 468 1277 431 1321 438 405 433 460 409 430 439 426 464 407 462 407 441 450 409 434 435 430 439 452 438 431 438 405 464 405 464 405 464 409 460 409 439 430 439 427 442 423 436 460 430 415 433 431 438 425 434 444 435 456 413 432 437 427 442 425 434 436 464 429 430 439 430 417 442 428 441 428 462 411 437 452 438 405 433 434 435 456 413 427 442 425 434 455 414 427 432 435 434 433 436 427 463 406 432 437 442 449 430 1268 460 1265 463 1262 466 405 464 406 442 422 437 1289 439 428 441 17052 3577 1746 407 1321 407 1294 434 460 409 431 459 439 409 1288 440 429 440 431 438 1309 430 1272 456 415 464 1289 408 433 457 412 436 1289 439 1284 465 404 434 1292 436 1289 439 428 441 450 409 1315 413 427 442 451 408 1294 434 432 437 430 439 428 462 406 432 432 458 414 455 414 434 430 439 428 441 426 433 436 433 432 437 454 405 438 441 428 431 434 456 418 441 448 431 411 437 429 440 425 434 433 436 458 411 425 434 433 436 457 412 1287 462 1263 455 419 461 408 440 430 439 1310 429 1272 435 460 409 1288 440 429 461 408 441 425 465 405 464 407 441 1286 432 1291 437 432 437 1315 413 1314 414 450 440 406 442 1283 456 1270 458 413 456 413 456 413 435 1297 431 1294 465 404 434 433 436 1314 414 1285 433 433 436 1288 440 1285 464 1264 433 433 436 453 416 425 434 460 409 434 435 432 437 454 415 423 436 431 459 424 435 430 460 413 435 430 439 423 456 413 435 431 438 455 414 427 432 435 434 455 414 426 464 406 432 437 442 422 457 414 434 431 438 424 435 435 455 414 455 416 432 433 436 457 412 426 464 410 438 453 437 412 436 437 463 409 439 425 434 455 414 426 433 437 432 433 436 433 436 457 433 412 436 453 437 406 432 435 465 1266 462 1263 434 1318 410 426 464 410 438 426 433 1293 435 434 435 +# +# Model: Mitsubishi MSH-30RV +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3520 1642 515 1199 514 1199 514 383 486 383 486 383 486 1199 514 383 486 383 486 1199 513 1199 514 382 487 1199 513 383 486 383 486 1226 486 1226 486 383 486 1226 486 1226 486 383 486 383 486 1226 486 384 510 359 510 1202 510 360 509 360 509 360 509 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 361 508 1204 508 361 508 361 508 361 508 361 508 361 508 1205 508 361 508 361 508 361 508 361 508 1205 507 1205 508 1205 507 361 508 362 507 362 507 361 508 362 507 361 508 1205 507 362 507 362 507 1205 508 362 507 362 507 362 507 362 507 362 507 361 508 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 362 507 1205 507 362 507 362 507 362 507 362 507 362 507 362 507 1205 507 362 507 1206 506 1206 506 362 507 1206 507 362 507 +# +# Model: Mitsubishi MSH_GA71VB +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3480 1701 468 1248 467 1250 465 407 460 411 467 405 462 1255 460 412 466 405 462 1255 460 1257 468 404 463 1253 462 410 468 404 463 1253 462 1255 470 402 465 1252 463 1254 461 411 467 405 462 1254 461 411 467 405 462 1254 461 411 467 405 462 409 469 403 464 407 460 411 467 405 462 409 469 403 464 407 461 411 467 405 462 409 469 403 464 407 460 411 467 404 463 1254 461 410 468 404 463 1254 461 410 468 404 463 408 460 412 466 406 462 1255 460 412 466 406 461 410 468 404 463 1253 462 1255 470 1247 468 404 463 409 469 402 465 406 461 411 467 1250 465 407 460 1256 469 1248 467 1250 465 1252 463 410 468 403 464 408 460 412 466 406 461 410 468 404 463 408 460 412 466 406 461 410 468 404 463 408 459 412 466 405 462 409 469 403 464 407 460 411 467 405 462 409 469 403 464 407 460 411 467 405 462 409 469 402 465 407 460 411 467 404 463 1253 462 1255 470 402 465 406 461 1256 469 402 465 1252 463 408 470 1248 467 1250 465 407 460 1256 469 +# +# Model: Mitsubishi SRK20ZJ-S +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3195 1551 429 365 428 1158 429 365 429 365 428 1158 429 365 428 1158 429 366 428 365 428 1159 428 1158 429 1159 428 364 429 1158 429 366 428 1158 429 1158 429 1158 429 365 428 365 428 365 429 365 428 1158 429 1158 429 365 428 1158 429 1158 458 354 440 353 441 1128 459 353 494 353 386 1128 486 353 441 353 440 1101 459 1128 459 353 440 1128 459 1127 460 1128 459 1128 459 353 440 1129 457 1129 458 1130 457 1131 456 353 440 354 439 354 439 1132 455 354 439 353 440 354 439 354 439 1132 455 1132 455 1132 455 1132 455 354 440 354 439 1133 454 1133 454 354 439 353 440 354 439 353 440 1133 454 1133 454 353 440 353 440 1133 454 353 440 1133 454 1133 454 1133 454 353 440 353 440 353 440 1133 454 1133 454 353 440 353 440 354 439 1133 454 1133 454 1133 454 354 439 +# +# Model: Mitsubishi SRK35ZS-W +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3199 1594 380 414 380 1208 379 415 389 405 389 1201 386 407 386 1202 385 409 384 408 385 1203 384 1203 384 1204 383 410 383 1206 381 412 382 1208 379 1207 380 1209 389 404 390 406 387 405 389 406 387 1201 386 1202 385 408 385 1204 383 409 384 1204 383 1205 382 412 382 411 382 413 380 1206 382 414 380 1208 379 414 379 416 388 1201 386 1201 386 1201 386 408 385 1202 385 1204 383 1204 383 1206 381 1205 382 1207 380 1207 380 1208 390 405 388 405 389 406 387 406 387 406 387 407 386 408 385 1202 385 1204 383 1203 384 411 382 1205 382 1205 382 1207 380 1207 381 414 390 404 379 414 380 1210 388 406 387 408 385 406 387 408 385 1202 385 1203 384 409 384 1205 382 1205 382 1206 381 1206 381 1208 379 415 389 404 389 1199 388 407 386 406 387 407 386 408 385 409 384 1202 385 1204 383 1204 383 1204 383 1205 382 412 381 1208 379 1208 379 415 389 404 390 406 387 406 387 407 386 1200 387 409 384 408 385 1202 385 1205 383 410 383 1205 382 1205 382 412 381 1207 380 1207 380 415 389 404 379 1209 389 406 387 405 388 1200 387 408 385 408 385 1202 385 1204 383 1204 383 1204 383 1208 379 1207 380 1207 380 1208 379 416 388 405 388 407 386 407 386 407 386 407 386 407 386 408 385 408 385 1204 383 1204 383 1205 382 1205 382 1207 380 1208 379 414 379 1210 388 406 387 406 387 406 387 407 386 407 386 409 384 1203 384 +# +# Model: Moretti Air_Cooler +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1299 412 1270 412 426 1225 1299 412 1244 438 399 1279 400 1254 451 1253 426 1253 426 1253 426 1254 1268 7130 1266 416 1266 416 421 1258 1267 416 1267 416 422 1258 421 1258 422 1258 421 1259 421 1258 422 1258 1266 7132 1266 417 1266 417 421 1259 1266 417 1266 417 421 1259 421 1259 421 1259 421 1259 421 1259 421 1259 1266 7133 1265 417 1266 417 421 1259 1266 418 1266 417 422 1259 421 1260 420 1259 421 1259 421 1259 420 1259 1266 7133 1265 418 1266 418 421 1260 1265 418 1266 418 421 1260 420 1260 420 1260 420 1260 420 1260 420 1260 1266 7135 1265 418 1266 419 420 1260 1266 419 1266 419 420 1261 420 1261 420 1261 420 1261 420 1261 420 1261 1265 7137 1264 420 1265 420 419 1262 1266 420 1265 420 420 1262 420 1262 419 1262 420 1262 419 1262 419 1262 1265 7139 1264 421 1265 421 419 1263 1265 421 1265 421 418 1264 418 1264 418 1263 419 1264 417 1264 418 1264 1264 7142 1263 423 1263 422 418 1288 1241 446 1240 446 394 1288 394 1289 393 1288 394 1288 394 1288 394 1288 1241 7168 1240 446 1241 446 394 1289 1241 447 1241 447 394 1289 394 1289 394 1289 394 1289 394 1289 394 1289 1241 7171 1239 447 1240 447 393 1290 1240 447 1241 447 393 1290 393 1290 393 1290 393 1290 393 1290 393 1290 1241 7174 1239 447 1241 448 392 1291 1240 448 1241 448 393 1291 393 1292 392 1292 392 1291 393 1292 392 1291 1240 7177 1239 449 1240 449 392 1292 1240 449 1240 449 392 1293 392 1293 392 1292 392 1293 391 1293 392 1293 1239 7179 1239 450 1239 450 391 1294 1239 450 1240 451 391 1294 391 1294 391 1295 390 1294 391 1295 390 1294 1239 7184 1237 452 1238 475 367 1319 1215 476 1215 476 366 1320 366 1320 366 1320 365 1320 365 1320 366 1320 1215 7211 1213 476 1190 501 366 1321 1215 477 1214 477 365 1322 364 1321 365 1322 364 1321 365 1321 365 1322 1213 7214 1213 478 1213 478 364 1322 1189 503 1189 503 364 1323 339 1348 338 1348 364 1323 363 1324 362 1323 1213 7218 1187 528 1164 529 313 1374 1163 529 1164 529 313 1375 313 1375 312 1375 312 1374 313 1374 313 1375 1163 7271 1162 530 1163 530 312 1376 1163 531 1162 530 312 1377 311 1378 310 1402 285 1378 310 1402 285 1403 1136 7300 1136 557 1136 557 285 1404 1136 558 1136 559 283 1404 285 1431 257 1405 284 1431 257 1431 257 1431 1109 7330 1108 585 1109 586 256 1432 1109 586 1109 638 177 1486 222 1468 221 1468 221 1468 221 1467 221 1495 1055 7413 1028 666 1029 2500 885 +# +# Model: Ok AC_OAC_7020 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4371 4390 527 1618 527 545 526 1618 527 520 552 545 527 545 527 521 551 1593 552 545 527 545 527 545 527 546 526 545 527 545 526 1618 527 545 527 545 527 1618 527 545 527 545 527 520 552 1618 527 1593 551 1618 527 1618 527 1618 527 1618 526 1618 526 1594 579 1591 527 1618 526 1619 527 1618 526 1593 552 1618 527 1618 527 1593 552 1618 527 1618 527 1618 527 1618 527 1618 527 545 527 1618 527 1618 527 545 527 1619 554 518 527 5203 4373 4391 526 545 527 1618 527 545 527 1593 552 1618 527 1595 550 1618 528 544 528 1618 527 1618 526 1594 552 1617 528 1619 526 1618 527 545 527 1618 527 1619 527 519 553 1618 527 1618 527 1618 527 522 550 545 527 546 526 545 555 517 528 545 527 545 528 544 527 521 551 522 550 545 527 545 526 521 552 521 551 545 527 544 528 545 527 545 527 545 527 545 527 545 528 1618 527 545 527 545 527 1619 527 545 527 1619 526 +# +# Model: Osaka CH_09_DSBP +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9034 4455 670 1627 695 507 693 508 692 510 690 514 688 514 688 514 688 514 687 514 688 1609 688 514 688 514 688 514 688 514 688 515 687 515 687 514 688 515 687 515 687 515 687 515 686 1611 686 515 687 515 687 516 686 517 685 517 685 516 686 1612 685 540 662 1635 662 540 662 540 662 1635 662 540 662 19937 688 514 688 514 688 514 688 514 688 514 688 514 688 514 688 514 687 515 687 515 687 515 687 515 687 515 686 1611 686 515 687 516 686 516 686 539 662 517 685 516 686 539 663 539 663 540 662 540 662 540 662 540 661 540 662 540 662 1635 662 1635 662 1636 661 1635 662 +# +# Model: Panasonic A75C4187 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3498 1744 439 431 442 1305 442 431 442 432 441 431 442 431 442 432 441 432 441 433 440 431 442 432 441 431 442 432 441 1306 441 431 442 432 441 432 441 432 441 432 441 432 441 432 441 1308 439 1305 442 1305 442 431 442 433 440 1305 442 434 439 433 440 431 442 432 441 432 441 432 441 433 440 431 442 432 441 433 440 432 441 433 440 432 441 432 441 432 441 432 441 432 441 434 439 433 440 432 441 433 440 433 440 432 441 432 441 432 441 432 441 432 441 431 442 432 441 433 440 1308 439 1306 441 432 441 432 441 431 442 432 441 432 441 10333 3499 1741 442 432 441 1306 441 432 441 432 441 431 442 432 441 431 442 433 440 432 441 433 440 432 441 432 441 432 441 1308 439 431 442 432 441 432 441 432 441 433 440 432 441 432 441 1306 441 1307 440 1306 441 432 441 432 441 1307 440 433 440 432 441 433 440 432 441 431 442 432 441 432 441 432 441 432 441 432 441 432 441 433 440 1308 439 432 441 1305 442 1305 442 432 441 432 441 433 440 432 441 1307 440 1307 440 431 442 1306 441 432 441 1306 441 1307 440 432 441 433 440 1306 441 432 441 432 441 433 440 431 442 432 441 1308 439 432 441 +# +# Model: Panasonic Climate_A75C2600 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3545 3410 937 803 936 2542 937 803 936 832 907 832 933 2545 933 2546 931 809 928 813 925 2580 899 839 900 840 900 839 900 2580 899 2580 899 840 899 840 900 2580 899 840 900 840 900 839 900 840 899 840 900 840 899 840 900 2580 899 840 899 840 899 840 900 840 900 840 899 840 3511 3449 898 841 898 2581 898 840 900 840 899 841 898 2581 898 2581 899 840 899 841 898 2581 898 841 898 841 898 841 898 2581 898 2581 898 841 898 841 898 2581 899 841 898 841 898 841 899 841 898 841 898 842 898 841 898 2581 898 841 898 842 897 842 897 842 897 842 898 842 3509 3450 897 13896 3509 3451 896 842 897 843 897 843 896 843 896 2583 896 843 897 843 896 2583 896 844 895 844 895 868 871 869 871 2609 870 869 871 869 870 2609 870 869 871 2609 870 2609 870 869 871 2609 870 2609 870 869 870 869 870 869 870 2609 870 2609 870 869 871 2609 870 2610 869 870 869 870 3482 3478 869 870 869 870 869 870 870 870 869 2610 869 870 869 870 870 2610 869 871 869 871 869 871 868 871 869 2611 868 871 868 871 869 2611 868 872 867 2611 869 2611 868 871 868 2612 867 2612 868 872 867 897 842 897 843 2637 843 2637 842 897 843 2637 843 2637 842 898 842 898 3454 3506 841 +# +# Model: Panasonic CWA75C4179 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3472 1744 416 457 415 1306 418 463 419 466 416 473 419 474 418 478 414 457 415 458 414 463 419 462 420 465 417 472 420 1316 418 479 413 458 414 459 413 464 418 463 419 466 416 473 419 1318 416 1324 420 1295 418 454 418 459 413 1312 422 463 419 470 422 471 421 475 417 454 418 455 417 460 422 459 413 472 420 469 413 480 412 485 417 453 419 454 418 459 413 468 414 471 421 468 414 479 413 484 418 452 420 453 419 458 414 468 414 471 421 467 415 478 414 483 419 451 421 453 419 1301 412 1312 422 463 419 470 422 471 421 476 416 433 418 10535 3469 1746 414 459 413 1308 415 465 417 468 414 475 417 476 416 481 421 449 413 461 421 455 417 465 417 467 415 474 418 1319 415 482 420 450 422 451 421 457 415 466 416 469 413 476 416 1320 414 1327 417 1297 416 457 415 462 420 1305 419 466 416 473 419 474 418 478 414 457 415 458 414 463 419 462 420 465 417 472 420 473 419 477 415 456 416 1301 412 464 418 463 419 1310 413 1319 415 1321 413 485 417 453 419 454 418 459 413 1312 422 1307 416 472 420 1317 416 480 412 459 413 460 412 465 417 464 418 467 415 474 418 475 417 479 413 1302 422 1295 418 1302 421 1303 421 1308 415 473 419 1318 416 481 421 1293 420 1296 417 460 412 1313 421 1307 416 473 419 473 419 478 414 457 415 458 414 463 419 462 420 465 417 472 420 473 419 477 415 456 416 457 415 1306 418 1307 416 1312 422 467 415 478 414 483 419 451 421 452 420 457 415 467 415 469 413 476 416 1321 413 1328 416 1298 415 458 414 463 419 462 420 465 417 472 420 472 420 477 415 456 416 457 415 462 420 461 421 464 418 470 422 471 421 476 416 454 418 1299 414 463 419 461 421 464 418 471 421 472 420 477 415 1299 414 459 413 464 418 463 419 466 416 473 419 473 419 478 414 457 415 458 414 463 419 462 420 465 417 472 420 473 419 477 415 456 416 457 415 1306 417 1307 416 468 414 1319 415 478 414 483 419 430 421 +# +# Model: Remko RKL +# +name: Off +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 12 ED 00 00 +# +# Model: Rinnai AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9026 4519 590 1654 616 1657 623 574 561 582 563 582 563 583 562 1688 592 1656 675 1627 593 1650 620 1654 677 521 563 1660 620 1656 614 1664 616 581 564 578 567 575 560 582 563 581 564 579 566 1684 596 1655 615 1661 619 577 568 574 561 581 564 580 565 579 566 580 565 582 563 581 564 577 568 574 561 582 563 580 565 579 566 1657 613 1664 616 582 563 578 567 575 560 583 562 581 564 580 565 582 563 584 561 582 563 578 567 575 560 583 562 581 564 580 565 1659 621 579 566 578 567 574 561 581 564 579 566 577 568 576 569 577 568 579 566 578 567 574 561 580 565 578 567 576 569 575 560 587 568 579 566 577 568 573 562 580 565 578 567 576 569 575 560 586 569 552 593 577 568 573 562 580 565 577 568 576 559 585 560 586 569 552 593 576 569 1703 567 575 560 1713 567 577 568 576 569 577 568 553 592 578 567 1704 566 1707 563 1656 614 1662 618 1659 621 577 568 553 592 1706 564 +# +# Model: Royal Clima_RC-TWN55HN +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4412 4413 548 1606 548 530 601 1552 602 1552 601 475 601 475 548 1606 548 528 548 528 548 1605 548 528 548 529 547 1607 546 1608 546 531 545 1610 544 534 542 1635 519 1635 519 1635 519 1635 518 558 519 1635 542 1612 518 1636 518 558 519 558 519 558 519 558 542 1612 518 558 519 558 519 1636 517 1636 518 1636 518 558 519 558 518 558 542 535 518 559 518 559 541 535 518 559 518 1635 518 1635 518 1636 518 1635 519 1636 518 5235 4384 4443 517 1636 541 535 518 1636 518 1636 518 559 518 559 518 1635 518 559 518 559 517 1636 518 559 518 558 518 1636 541 1612 518 559 518 1636 517 559 518 1636 518 1636 518 1636 518 1636 517 559 518 1636 518 1636 518 1636 541 535 518 559 518 559 541 536 517 1636 518 559 518 559 518 1636 517 1636 518 1636 518 559 517 559 517 559 517 559 517 559 518 559 518 559 517 559 518 1636 518 1636 518 1636 517 1636 517 1636 517 +# +# Model: Samsung AC_AR12K +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 270 18152 3021 8955 523 499 495 1497 492 504 500 468 526 496 498 498 496 499 495 501 493 502 492 1499 500 496 498 524 470 1521 498 497 497 499 495 1496 492 1499 500 1491 497 1494 494 1497 502 494 500 495 499 497 497 498 496 500 494 528 466 530 464 531 473 522 493 503 491 504 500 495 499 497 497 498 496 500 494 501 493 503 491 504 500 495 499 496 498 498 496 499 495 501 493 529 465 531 473 522 472 523 492 504 490 505 499 496 498 498 496 499 495 1496 492 1499 500 1492 496 1494 525 2947 2999 8953 525 1519 469 499 516 507 497 498 496 500 494 501 493 503 491 504 500 495 499 1492 496 499 495 501 493 1498 501 495 499 1492 496 1522 466 1524 496 1496 492 1499 500 1492 496 499 495 500 494 502 492 504 500 495 499 496 498 498 496 499 495 500 494 502 492 530 474 521 473 523 523 472 522 474 489 506 498 497 497 499 495 500 494 502 492 503 501 494 500 496 498 497 497 499 495 500 494 502 492 503 501 521 473 523 471 524 522 474 520 475 498 497 497 499 495 500 494 2978 2999 8952 525 1492 496 499 495 501 493 503 491 504 500 495 499 497 497 525 469 526 468 1524 516 479 494 501 493 503 491 504 500 495 499 497 497 1494 494 1497 492 1500 499 1492 496 499 495 1497 491 531 473 1518 522 1469 499 496 498 498 496 500 494 1497 491 1500 499 1492 496 499 495 501 493 502 492 504 500 495 499 523 471 525 469 526 520 1472 516 1475 493 502 492 504 500 495 499 1492 496 499 495 501 493 502 492 504 500 495 499 496 498 498 496 1522 466 1525 525 1466 491 1500 499 +# +# Model: Samsung AR13TYHZCWKN +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 645 17766 3059 8884 533 461 557 1427 560 435 584 433 534 458 534 458 534 458 588 404 587 405 586 1399 586 407 584 408 558 1429 555 1457 502 491 525 1460 528 1458 554 1431 554 1431 555 1431 554 438 555 438 554 439 553 440 553 464 528 464 528 464 529 465 528 465 526 467 526 467 527 465 553 439 554 439 554 438 554 439 554 439 553 439 554 439 553 439 553 439 554 440 552 464 528 464 528 465 528 466 527 466 501 492 525 468 526 466 527 466 553 439 554 439 553 440 552 1433 553 1433 553 2936 3024 8893 552 1458 527 465 528 465 527 467 526 467 500 493 524 468 526 467 526 467 552 1434 552 441 552 441 552 1435 550 465 528 1458 527 1458 528 1459 527 1485 501 1485 500 1486 501 491 527 466 528 465 528 465 527 465 528 465 527 465 528 465 528 465 527 465 528 465 527 466 527 466 527 492 500 492 501 492 500 493 500 493 500 492 526 466 527 465 527 465 527 465 527 466 527 466 527 466 527 465 527 466 526 466 527 466 526 467 526 493 499 493 474 518 499 494 500 493 526 2936 3025 8918 526 1459 527 466 527 466 527 466 526 466 527 466 526 467 526 467 525 467 526 1460 525 493 499 493 498 495 498 495 499 493 500 493 525 1460 527 1460 525 1460 526 1460 525 1460 526 1460 526 468 525 1487 499 1487 497 495 498 495 499 493 500 1486 525 1460 525 1460 525 467 525 467 526 467 525 467 526 468 524 1462 524 468 524 494 498 1488 497 1488 499 494 499 493 525 468 525 1461 524 468 525 468 524 468 525 468 525 468 524 468 525 469 524 494 498 494 499 1488 473 1514 524 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 619 17878 3008 8908 535 458 535 1451 534 459 534 459 533 459 534 485 507 485 587 404 532 460 533 1451 536 456 537 456 535 1450 561 432 560 433 533 1454 531 1455 530 1456 529 1457 529 1508 501 491 502 491 502 490 503 490 503 489 504 488 504 489 503 489 504 489 504 489 504 488 504 489 504 464 529 464 529 464 529 464 528 516 476 516 477 516 502 491 502 490 503 490 503 489 504 489 503 489 504 489 504 489 504 489 503 489 504 489 503 489 504 464 529 1458 528 1509 476 1509 501 1484 504 2934 3028 8939 504 1482 504 489 503 489 504 489 504 489 503 489 504 489 503 489 504 465 528 1459 527 516 476 516 501 1485 502 1484 503 1483 503 1482 503 489 504 1482 504 1483 503 1482 503 1459 527 1459 527 516 476 1509 501 1485 501 491 502 490 503 489 504 1482 503 1482 503 1483 503 489 504 489 503 489 504 489 503 489 503 490 503 516 477 516 501 1485 502 1484 502 490 503 490 503 490 503 490 503 1483 503 490 503 490 503 489 503 490 503 490 502 490 503 1510 475 1510 500 1485 502 1484 503 +# +# Model: Samsung Wind-Free +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 606 17832 2994 8936 520 501 496 1491 494 501 496 498 489 532 465 529 488 505 492 502 495 499 488 1499 496 499 498 495 492 1496 499 1463 522 499 498 1463 522 1467 517 1469 526 1491 493 1495 520 500 487 507 490 504 493 500 497 497 490 504 493 501 496 498 489 505 492 501 496 498 499 495 492 502 495 499 498 522 465 529 468 526 492 502 495 499 488 506 491 503 494 499 498 496 491 503 494 500 497 497 490 504 493 500 497 497 490 504 493 500 497 497 490 531 466 528 469 1518 487 1475 520 2947 3018 8939 517 1471 524 496 491 503 494 500 497 497 490 504 493 500 497 497 490 504 493 1521 464 530 467 527 491 1471 514 507 490 1471 524 1465 519 1468 517 1472 523 1465 520 1468 517 477 520 501 496 497 490 531 466 528 469 525 493 501 496 498 489 505 492 502 495 498 499 495 492 502 495 499 498 495 492 502 495 499 498 496 491 502 495 499 498 496 491 529 468 526 471 523 495 499 488 506 491 503 494 499 498 496 491 503 494 500 497 496 491 503 494 500 497 497 490 503 494 2973 2992 8939 517 1469 526 522 465 529 488 506 491 503 494 499 488 506 491 503 494 500 497 1490 495 499 498 496 491 1497 498 1464 520 1468 517 1470 525 524 463 1498 517 1471 524 1465 520 1468 517 1471 524 1465 520 1468 517 1472 523 497 490 504 493 501 496 1491 494 1496 519 1469 516 504 493 501 496 498 489 505 492 501 496 498 499 495 492 502 495 1492 493 1469 526 495 492 529 468 1492 513 1476 519 502 495 498 489 505 492 502 495 499 498 496 491 503 494 499 498 496 491 1497 498 1464 541 +# +# Model: Sharp AH-A9UCD +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9111 4374 765 1548 712 496 713 497 713 496 710 1598 711 1602 712 1602 712 496 712 497 712 498 710 495 709 501 729 496 711 496 707 498 707 498 707 498 708 498 707 499 707 499 707 498 707 1600 706 499 708 497 709 498 706 500 706 499 707 499 707 1597 705 499 710 1596 705 497 707 500 708 1601 708 500 709 19935 707 1604 706 498 707 500 709 500 708 499 709 500 710 500 709 500 708 1600 708 500 709 500 710 498 705 499 708 1597 704 498 709 500 707 498 709 498 705 499 709 498 709 498 708 498 708 499 707 499 707 498 707 498 708 499 707 498 708 1600 706 497 707 1597 705 1600 709 +# +# Model: Sharp AH_X9VEW_AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3821 1901 456 507 456 1424 430 490 484 1425 429 475 488 1420 434 471 482 1427 437 467 486 1420 434 486 457 1416 459 1432 432 487 456 1436 439 480 483 1425 439 1425 460 1420 455 1419 435 485 457 472 491 1417 458 1407 437 483 459 487 456 474 458 487 466 1432 432 471 461 485 457 472 491 1415 460 1378 466 480 483 1424 430 474 458 487 466 1424 461 1419 456 1426 438 465 457 489 464 466 456 489 464 1426 438 481 461 484 437 493 460 1429 435 482 460 485 458 1440 435 484 458 1438 437 467 465 480 462 467 465 481 461 468 464 481 461 469 463 481 461 468 485 1424 430 1434 462 1418 457 1425 439 465 457 489 464 465 457 489 464 466 456 490 463 467 465 480 463 467 465 480 463 467 486 1422 432 471 461 485 457 472 460 485 457 472 460 485 457 472 460 486 456 488 434 497 456 473 459 486 456 488 465 1414 461 1393 482 1423 462 1419 435 469 463 482 460 469 463 482 460 1436 439 1425 439 480 483 +# +# Model: Sharp CVP10MX +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3827 1854 505 433 507 1382 506 434 506 1383 505 434 506 1410 478 433 507 1381 507 433 507 1383 505 435 505 1382 505 1383 505 435 505 1382 506 435 505 1383 505 1382 506 1384 504 1381 507 434 506 434 506 1382 506 1382 506 432 508 434 506 436 504 433 507 1382 506 435 505 434 506 436 504 1381 507 1383 505 434 506 1382 506 1381 507 434 506 1381 507 1381 507 1383 505 435 505 434 506 435 505 433 507 1383 505 435 505 435 505 434 506 1381 507 433 507 435 505 434 506 1382 506 433 507 434 506 433 507 438 502 434 506 433 507 434 506 434 506 432 508 435 505 434 506 433 507 433 507 1387 501 435 505 436 504 433 507 433 507 435 505 433 507 434 506 434 506 435 505 435 505 432 508 1410 478 461 479 432 508 435 505 434 506 433 507 436 504 434 506 434 506 433 507 435 505 1383 505 435 505 1381 507 1381 507 1382 506 1382 506 1381 507 435 505 433 507 433 507 461 478 1383 505 433 507 434 506 +# +# Model: Shivaki SSA18002 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3197 1545 581 1033 553 1006 606 338 463 342 485 339 488 1033 553 342 485 342 485 1034 551 1035 550 342 485 1037 548 342 485 340 487 1040 546 1040 546 340 487 1040 546 1040 546 340 487 340 488 1040 546 340 487 340 488 1040 546 340 487 340 487 340 487 342 485 340 487 340 487 340 487 342 485 342 485 342 485 340 487 342 485 340 487 340 487 342 485 342 485 342 485 342 485 340 488 342 485 1041 545 340 487 340 487 1041 545 1041 545 340 488 340 487 340 487 340 487 340 488 342 485 342 485 340 487 340 488 1041 545 340 487 340 487 342 486 342 485 340 487 340 487 340 487 340 487 342 485 341 486 1041 545 340 487 340 487 340 487 340 487 340 488 342 485 340 487 342 485 342 485 340 487 342 485 340 487 340 488 342 485 343 484 340 488 340 487 340 487 340 487 340 487 342 485 340 487 340 487 341 486 342 485 340 488 342 485 342 485 342 485 340 487 340 487 340 488 1042 544 340 487 340 487 340 487 340 487 340 487 340 487 340 488 342 485 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3196 1545 581 1005 580 1008 604 337 464 338 488 338 489 1033 553 339 488 338 489 1033 552 1034 551 339 487 1036 549 339 488 339 488 1039 547 1039 547 340 487 1040 546 1040 546 339 488 339 488 1040 546 340 487 339 488 1040 546 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 1040 546 339 488 339 488 1040 546 339 488 339 488 339 488 1040 546 339 488 339 488 339 488 339 488 339 488 339 488 1040 546 1041 545 1041 545 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 339 488 340 487 339 488 339 488 340 487 339 488 1042 544 339 488 1042 544 339 488 339 488 339 488 339 488 1042 544 1042 544 +# +# Model: SINCLAIR ASH13BIF2 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9048 4430 705 500 706 1602 705 1602 729 476 730 1579 728 478 728 1580 727 480 726 480 726 480 726 480 726 1606 702 505 701 481 725 481 724 505 701 481 725 504 702 505 701 504 701 505 701 1606 702 504 702 504 701 504 701 504 702 505 701 505 701 1606 701 504 702 1607 701 504 701 504 701 1607 701 505 701 19915 726 1582 726 481 725 504 701 505 701 1606 701 505 701 505 701 505 701 505 700 1606 701 505 701 505 701 505 701 504 702 1606 701 505 701 504 701 505 701 504 702 505 701 505 701 505 701 505 701 505 701 505 701 505 701 505 701 505 701 1606 701 505 701 1607 700 1606 701 39924 9075 4406 728 478 728 1581 727 1581 726 481 725 1582 725 480 725 1606 701 504 702 481 725 505 701 505 701 1606 701 504 702 505 701 505 701 505 701 505 701 505 701 505 701 505 701 505 701 1606 701 505 701 505 701 505 701 505 701 505 701 505 701 1606 701 1607 701 1607 701 505 701 505 701 1607 701 506 701 19913 726 480 726 480 726 480 726 480 726 479 727 479 727 480 725 479 727 479 726 480 726 480 726 480 726 480 726 480 726 480 726 504 701 480 726 480 725 505 702 481 725 1606 701 505 701 481 725 481 725 505 700 505 701 504 701 505 701 1607 701 504 701 505 700 1607 701 +# +# Model: SoleusAir +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 6149 7347 601 535 573 560 602 505 602 480 597 509 600 534 599 487 593 541 591 568 569 539 568 490 594 539 570 513 568 540 570 566 570 540 568 543 594 540 569 566 543 515 594 540 569 514 567 542 570 514 568 543 594 540 569 490 592 542 570 514 568 541 570 541 542 541 569 567 570 515 567 542 570 515 567 542 570 568 568 516 570 568 570 542 568 542 570 515 569 542 570 568 568 517 569 541 569 517 569 568 570 515 568 570 569 569 543 542 569 569 569 516 569 542 570 543 568 1640 570 516 568 543 570 569 543 1613 569 516 568 1613 570 569 543 570 569 542 543 543 569 543 542 544 569 516 569 544 570 543 570 570 570 543 543 544 569 1615 569 571 569 570 568 519 569 1615 569 544 569 1615 568 572 569 571 542 1642 569 571 568 1617 569 1642 542 1642 569 1589 568 545 569 1590 569 518 568 1617 569 545 568 7372 568 +# +# Model: Subtropic in-07HN1 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9019 4453 576 1665 603 1639 602 504 602 505 601 507 599 508 598 1644 573 1669 573 1669 572 1670 572 1670 572 1670 572 1670 572 1670 572 1670 572 535 572 535 572 535 572 535 572 535 572 535 572 1670 572 1670 572 1670 571 535 572 535 572 535 572 535 572 535 572 535 572 535 572 535 572 535 572 535 572 535 572 535 572 536 571 1670 572 535 572 1670 572 535 572 535 572 536 571 536 571 536 571 536 571 535 572 536 571 536 571 536 571 536 571 536 571 536 571 1670 572 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 536 571 537 570 536 571 537 570 536 571 537 570 536 571 537 570 537 570 537 570 537 570 537 570 537 570 538 569 538 569 538 569 561 546 562 545 562 545 562 545 1696 546 562 545 1697 545 562 545 562 545 562 545 562 545 562 545 1696 546 1697 545 1697 545 562 545 562 545 1697 545 1697 545 1697 545 +# +# Model: Tcl +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3087 1607 488 1064 517 335 492 1087 484 315 512 340 487 1091 490 336 491 338 489 1089 492 333 494 332 485 341 486 340 487 338 489 336 491 339 488 1090 491 334 493 1085 486 340 487 1091 490 1088 493 333 484 343 484 44227 178 +# +# Model: Timberk RG05D4-BGE +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4414 4312 565 1591 565 517 565 1596 564 1592 565 517 565 517 565 1591 566 519 565 519 565 1595 565 517 565 517 565 1592 564 1593 563 519 563 1599 563 521 563 1594 562 1594 563 1594 563 1595 562 520 562 1598 563 1597 562 1600 562 520 562 520 562 520 562 520 562 1595 562 520 562 522 562 1601 561 1595 562 1595 562 521 561 521 561 521 561 521 561 523 561 523 561 521 561 521 561 1600 561 1596 561 1596 561 1596 561 1597 561 5165 4384 4310 560 1596 561 521 561 1600 561 1596 561 521 561 521 561 1595 562 523 561 523 561 1600 561 521 562 521 561 1595 562 1595 562 520 562 1601 562 523 561 1595 562 1595 562 1595 562 1595 562 521 561 1599 562 1597 562 1601 562 521 561 521 561 521 561 521 561 1595 562 521 561 522 562 1601 562 1595 562 1595 562 521 561 521 561 521 561 521 561 523 561 523 561 521 561 521 561 1599 562 1596 561 1596 561 1595 562 1598 562 +# +# Model: Tora TS_16-Classic +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3121 1585 524 1061 524 1036 549 346 473 346 471 346 499 1036 549 346 473 346 472 1061 524 1061 524 346 472 1064 545 347 473 346 472 1092 493 1092 493 346 473 1092 493 1092 493 346 498 346 473 1067 518 346 473 346 497 1068 518 346 474 346 473 346 498 346 474 346 473 346 497 346 475 346 473 346 499 347 472 346 473 346 499 346 473 346 472 353 492 346 473 346 498 346 474 346 473 346 473 1093 492 346 499 346 473 1094 491 1094 491 346 473 346 499 346 473 346 473 346 499 346 473 346 497 346 474 346 473 1094 491 346 473 346 499 346 473 346 498 347 473 346 473 346 498 346 474 346 473 346 497 346 475 346 472 347 498 346 473 346 473 347 498 346 473 346 473 347 498 346 473 346 498 347 474 346 473 346 497 346 475 346 473 346 473 354 491 346 473 346 499 346 473 347 472 346 498 347 472 346 473 353 492 347 472 346 473 347 498 353 465 346 473 347 498 353 465 346 499 353 466 353 466 347 497 354 465 354 465 347 472 1095 490 354 491 +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3112 1543 540 1041 544 1039 546 346 500 346 473 346 473 1063 522 346 494 347 475 1064 546 1039 543 346 448 1068 517 346 472 347 497 1046 514 1096 516 346 473 1069 516 1069 516 346 473 346 473 1069 516 347 498 346 473 1069 516 346 472 347 498 346 473 346 473 346 499 346 472 346 498 346 473 346 473 346 498 346 474 346 473 346 498 347 473 346 473 347 498 346 473 1070 515 346 472 347 471 1071 538 346 500 346 448 347 496 1070 515 346 474 346 472 346 497 347 474 346 497 346 474 346 473 346 473 346 499 346 473 346 473 346 499 346 473 346 498 347 473 347 472 346 497 346 474 346 473 346 496 347 474 346 473 347 498 346 473 346 473 346 499 346 473 346 473 347 498 346 473 346 497 346 475 346 473 347 495 346 476 346 473 346 472 355 490 346 473 346 498 346 473 347 472 346 498 347 473 346 473 355 490 346 473 347 472 346 499 347 471 346 473 346 499 346 473 346 496 1072 490 1095 490 347 497 1070 490 1096 489 1095 490 346 499 346 473 +# +# Model: Toshiba RAS13SKV2E +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4387 4349 553 1609 553 1607 555 1605 546 1613 549 531 550 530 551 1610 552 527 554 526 555 526 555 525 556 524 557 1602 549 1611 551 529 552 1608 554 526 555 525 556 525 556 524 546 533 548 532 549 1611 551 1608 554 1606 556 1604 558 1603 548 1611 551 1609 553 1607 555 525 556 525 556 524 557 523 547 533 548 533 548 532 549 531 550 530 551 1607 555 526 555 1605 557 1603 548 531 550 531 550 530 551 529 552 528 553 527 554 526 555 526 555 525 556 524 546 1612 550 1610 552 1608 554 527 554 526 555 526 555 525 556 524 557 523 547 533 548 532 549 531 550 1609 553 1607 555 525 556 525 556 1603 548 1612 550 530 551 7454 4385 4352 549 1611 551 1609 553 1608 554 1606 556 525 556 524 557 1602 549 531 550 531 550 530 551 530 551 529 552 1633 529 1604 558 523 558 1601 550 531 550 530 551 530 551 529 552 528 553 527 554 1604 558 1603 548 1611 551 1609 553 1608 554 1606 556 1604 558 1602 549 532 549 531 550 530 551 530 551 529 552 528 553 527 554 526 555 525 556 1603 548 532 549 1610 552 1608 554 527 554 527 554 526 555 525 556 524 557 523 547 533 548 532 549 531 550 530 551 1608 554 1606 556 1604 558 523 547 533 548 532 549 531 550 530 551 529 552 528 553 527 554 526 555 1605 557 1602 549 531 550 531 550 1609 553 1607 555 526 555 +# +# Model: Toshiba RG57H4 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4415 4348 568 1578 569 503 570 1575 572 500 563 510 563 508 565 533 540 1578 569 1576 571 527 546 500 563 509 564 533 540 532 541 1603 544 502 571 500 563 1582 565 1580 567 1578 569 504 569 502 571 1574 563 509 564 1581 566 506 567 504 569 529 544 501 572 500 563 509 564 1580 567 1578 569 529 544 501 572 500 563 509 564 507 566 1578 569 1576 571 501 572 1573 564 508 565 1606 541 504 569 1576 571 501 572 1573 564 5169 4410 4351 565 507 566 1579 568 504 569 1575 572 1573 564 1582 565 1580 567 505 568 503 570 1575 572 1573 564 1581 566 1580 567 1578 569 503 570 1575 572 1573 564 534 539 507 566 505 568 1577 570 1575 572 500 563 1582 565 507 566 1579 568 1577 570 1575 572 1573 564 1582 565 1580 567 505 568 504 569 1576 571 1574 563 1582 565 1580 567 1579 568 504 569 503 570 1574 563 509 564 1581 566 506 567 1578 569 503 570 1574 563 510 563 +# +# Model: Tosot T24H-ILF +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9072 4445 602 1586 603 477 601 478 600 479 599 480 598 481 598 482 597 482 572 1617 597 1593 572 1617 597 483 572 507 572 507 572 507 572 507 572 507 597 482 597 482 572 507 597 482 572 1618 571 507 572 507 572 507 572 507 572 507 572 507 572 1618 571 507 572 1618 572 507 572 507 572 1618 572 507 652 20153 572 507 597 482 572 507 572 507 597 482 597 482 572 507 596 483 572 507 572 507 572 507 572 507 572 507 572 1618 596 483 572 507 596 483 595 484 572 507 597 482 595 484 597 482 597 482 572 507 572 507 597 482 572 507 597 482 572 507 597 483 571 1618 597 482 675 40391 9179 4421 599 1590 599 481 597 482 597 482 597 481 598 482 597 482 597 482 597 1592 597 1592 598 1592 597 482 597 482 597 482 597 482 597 482 597 482 597 482 597 482 597 482 572 507 597 1593 597 482 572 507 572 507 572 507 572 507 572 508 571 1618 597 1593 596 1593 572 507 572 507 572 1618 597 482 678 20152 597 483 596 482 597 482 597 482 597 482 597 482 572 507 597 482 597 482 597 482 572 507 597 482 597 482 597 482 597 482 597 482 572 507 597 482 597 482 572 507 597 482 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 507 572 1618 596 482 572 507 572 +# +# Model: Tropic AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1373 348 1310 376 463 1190 1318 400 1286 401 439 1244 442 1244 1288 400 465 1218 468 1218 468 1219 467 7970 1307 404 1281 405 435 1252 1281 406 1280 406 434 1252 434 1252 1281 406 434 1253 434 1252 434 1252 434 8000 1280 406 1281 406 434 1252 1281 406 1280 406 434 1252 434 1252 1281 406 434 1253 433 1253 433 1253 434 8000 1280 406 1280 406 434 1253 1280 406 1280 406 434 1253 433 1253 1280 406 434 1253 433 1253 433 1253 433 8001 1279 406 1280 406 434 1253 1280 407 1279 407 433 1253 434 1253 1280 407 433 1253 433 1253 433 1253 433 8001 1279 407 1279 407 433 1253 1280 407 1280 407 433 1253 433 1253 1280 407 433 1253 433 1253 434 1253 433 +# +# Model: Trotec PAC2600X +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4329 4399 534 1607 511 561 534 1607 535 535 536 537 533 537 533 563 507 1606 535 1610 507 559 535 563 506 536 534 563 507 535 511 557 536 1605 535 537 533 1607 533 563 507 536 534 535 535 1605 535 1606 534 564 507 1634 506 1605 535 1635 506 1634 483 1629 512 1630 534 1607 511 1631 533 1635 506 1634 506 1608 533 1606 535 1607 511 1630 534 1609 532 1608 511 557 513 1658 483 560 510 1631 510 1631 532 565 484 562 508 1631 510 5210 4353 4396 510 559 511 1631 510 561 509 1628 513 1630 511 1658 483 1631 511 561 509 560 510 1634 508 1631 511 1658 484 1631 511 1633 509 1658 484 563 508 1658 484 559 512 1658 484 1633 509 1632 510 559 512 559 511 1631 511 559 512 562 508 560 510 561 509 558 512 559 511 559 511 560 510 560 510 560 510 560 510 560 510 560 510 560 510 587 483 587 483 1630 511 558 512 1631 510 559 511 562 508 1658 483 1630 512 560 510 +# +# Model: Vornado +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1347 405 1322 422 410 1332 1300 448 1289 480 383 1303 445 1298 439 1331 417 1299 438 1332 416 1302 1320 6834 1292 456 1292 450 413 1304 1328 445 1292 452 411 1302 446 1297 440 1303 445 1298 439 1303 445 1301 1321 6806 1320 453 1295 422 441 1301 1321 454 1294 422 441 1301 436 1305 443 1300 448 1295 442 1301 447 1298 1324 6802 1324 451 1297 420 443 1299 1323 424 1324 419 444 1298 439 1304 444 1298 439 1304 444 1299 438 1306 1326 6815 1321 453 1295 421 442 1302 1320 426 1322 421 442 1301 436 1306 442 1301 447 1296 441 1301 447 1299 1323 6802 1324 424 1324 418 445 1300 1322 424 1324 419 444 1298 439 1304 444 1299 438 1304 444 1299 438 1306 1326 6809 1328 420 1317 424 439 1306 1326 419 1318 424 439 1304 444 1299 438 1305 443 1299 438 1305 443 1302 1320 +# +# Model: Whynter AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 781 710 2930 2876 775 716 781 735 752 2175 779 711 776 2177 746 745 773 2232 753 2173 749 768 750 2177 745 745 773 743 754 737 750 741 746 744 754 737 771 746 751 739 748 2178 776 742 755 2172 771 719 778 2227 747 744 754 2200 754 737 750 2203 751 741 746 2181 773 744 753 737 750 741 746 +# +# Model: Windfree Actest2 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 548 17943 2990 9008 467 560 469 1533 469 560 468 559 468 561 467 560 468 562 467 561 466 559 468 1536 467 559 468 560 467 1535 468 1533 469 560 467 1536 467 1534 468 1533 468 1534 468 1534 467 560 468 559 468 559 469 560 469 559 469 559 468 559 468 560 468 560 467 562 467 559 469 558 469 559 468 559 469 561 468 559 468 559 468 559 468 559 469 560 469 559 468 559 468 560 468 559 468 561 467 560 469 559 468 559 468 558 469 559 469 561 468 560 467 559 469 559 468 1535 468 1533 491 2976 3014 8997 492 1509 493 536 493 535 492 534 494 535 492 535 493 537 491 536 493 533 494 1509 493 536 493 536 491 1508 494 536 493 1509 492 1510 493 1510 491 1509 494 1508 493 1511 492 534 494 534 494 534 494 535 492 536 493 535 493 533 495 534 493 536 491 535 493 537 492 534 493 535 493 534 494 534 494 536 493 535 493 535 493 534 493 534 493 536 493 534 493 533 495 534 494 534 494 535 493 536 493 534 493 535 493 534 494 534 493 536 493 534 494 534 493 534 494 535 492 2973 3017 8996 493 1508 494 536 493 535 493 535 493 533 495 535 493 536 492 536 493 534 493 1508 494 535 494 535 492 1509 493 535 494 1507 494 1510 493 533 494 1509 494 1505 496 1509 494 1508 494 1512 491 1507 494 1509 494 1508 494 533 494 535 493 534 495 1507 494 1509 494 1509 493 533 494 535 493 536 493 534 494 535 492 1508 519 509 495 1508 493 533 495 1508 493 1509 493 534 494 1508 518 1484 494 535 492 533 519 508 495 534 493 535 494 533 495 534 494 534 494 534 495 1508 493 1507 494 76867 4382 4447 533 1656 532 509 533 1655 533 1655 533 510 533 509 533 1655 533 509 532 509 533 1656 533 509 532 510 532 1657 531 1657 531 509 533 1657 532 509 532 1656 532 1656 532 1655 533 1658 531 511 530 1656 533 1657 531 1656 532 510 533 510 531 510 532 510 532 1656 532 508 534 511 532 1656 532 1655 533 1655 533 509 532 512 531 509 532 511 531 508 533 508 534 511 532 509 533 1656 532 1655 533 1657 531 1656 532 1657 532 5202 4355 4447 534 1656 533 510 531 1655 533 1655 533 508 534 508 533 1657 532 508 533 509 533 1657 531 508 533 511 532 1656 532 1656 532 509 532 1656 531 511 532 1655 533 1656 531 1655 532 1656 531 509 532 1658 531 1658 530 1656 531 510 532 510 531 509 534 510 532 1657 530 511 530 510 531 1658 531 1656 532 1656 531 509 533 509 532 510 532 510 533 510 531 509 533 508 533 509 532 1659 531 1656 532 1656 532 1655 533 1655 533 +# +# Model: Windfree Ac_test +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 549 17944 2988 9004 491 537 492 1510 491 537 490 537 490 537 492 536 491 537 491 536 491 537 491 1510 491 536 491 537 491 1509 492 1511 491 537 491 1509 492 1510 492 1509 492 1512 491 1510 491 536 491 535 492 537 492 535 492 535 492 535 493 535 493 537 492 535 493 536 491 534 494 534 493 537 492 536 492 535 493 535 493 536 492 536 492 536 493 536 491 535 492 535 493 535 493 537 492 536 491 535 492 535 493 535 492 536 493 535 492 536 492 534 493 535 493 537 492 1508 493 1510 493 2970 3017 8996 493 1508 494 533 494 534 494 534 493 534 493 535 494 533 494 533 494 537 491 1509 492 535 493 534 493 1510 493 533 494 1509 493 1508 492 1510 492 1507 494 1509 493 1507 494 534 493 536 491 533 494 535 493 534 493 534 494 535 492 535 492 537 492 535 492 535 493 534 493 534 493 535 494 533 494 534 494 534 494 536 491 537 490 535 494 534 493 535 493 534 493 534 493 535 494 535 493 535 492 534 493 534 493 536 493 534 493 535 492 534 494 534 493 535 493 536 493 2972 3015 8994 493 1508 493 535 492 534 493 535 493 535 492 536 493 536 492 534 494 537 491 1509 493 534 493 535 492 1510 493 535 492 1511 491 1509 492 534 493 1510 492 1509 492 1510 492 1509 492 1510 492 1509 492 1510 492 1509 492 535 492 536 493 533 494 1512 491 1509 492 1509 492 535 494 535 492 537 491 535 492 536 493 1510 491 535 492 1509 492 536 492 1510 492 1510 491 536 491 1510 492 1509 492 534 493 536 491 536 493 535 493 535 492 535 492 537 490 535 492 536 492 1508 493 1511 492 76863 4380 4448 507 1680 508 534 507 1682 507 1680 508 535 506 534 508 1680 507 536 507 533 508 1679 508 534 507 534 507 1680 508 1682 507 534 507 1678 509 534 507 1680 508 1682 507 1680 507 1679 508 533 508 1679 509 1681 508 1681 507 532 510 534 507 535 507 532 510 1682 507 534 508 533 508 1679 509 1680 508 1681 508 534 507 534 507 533 509 534 507 534 509 535 506 535 507 533 509 1680 508 1679 509 1682 507 1679 509 1678 509 5226 4354 4451 507 1679 509 533 508 1678 510 1682 508 534 507 532 509 1680 507 534 507 534 509 1680 508 533 509 534 507 1680 508 1680 508 534 509 1681 506 533 508 1680 508 1679 509 1681 508 1679 509 534 507 1679 509 1679 509 1681 508 533 508 533 509 533 509 534 508 1680 507 535 508 533 508 1680 508 1679 509 1680 508 535 508 534 507 534 507 534 507 533 508 535 508 534 508 533 509 1682 506 1681 507 1680 508 1682 507 1680 508 +# +# Model: Windfree Remote3 +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 602 17896 3013 8976 522 505 522 1481 521 507 522 507 521 505 522 507 520 505 523 506 522 504 523 1481 521 505 522 508 521 1481 520 506 521 505 523 1478 522 1482 520 1479 522 1480 522 1480 521 506 521 507 520 509 520 506 521 505 523 506 521 506 521 507 522 506 521 506 521 506 522 503 524 508 521 505 523 506 521 504 523 506 521 506 521 507 522 505 522 504 523 505 522 506 521 507 522 505 522 506 522 506 521 505 522 508 521 506 522 507 520 506 521 1483 519 1479 522 1482 520 1480 521 2944 3041 8969 520 1482 521 506 521 507 520 506 521 507 520 506 521 507 522 506 521 505 522 1483 519 506 521 506 521 1483 518 506 522 1481 519 1482 520 506 521 1482 520 1481 519 1482 520 1479 521 1481 521 1482 518 1482 520 1482 519 508 519 508 519 508 520 1481 519 1483 494 1508 493 533 494 533 496 534 493 533 494 532 495 535 493 1506 494 1506 495 534 494 1506 495 534 493 533 494 534 494 1507 519 508 519 509 493 534 495 534 493 532 495 533 494 534 494 1507 493 1507 495 1506 495 1509 493 76841 4380 4447 508 1680 507 533 508 1678 510 1681 508 533 508 533 508 1679 508 533 508 534 508 1679 508 533 508 533 508 1679 508 1678 509 535 508 1680 507 533 508 1679 508 1679 508 536 507 1679 508 533 508 1679 508 1679 508 1680 507 535 508 534 507 1680 507 532 509 1680 507 535 506 535 507 1681 506 1679 508 1680 508 533 508 534 509 533 508 532 509 533 508 533 508 535 508 533 508 1680 508 1679 508 1679 509 1679 509 1680 509 5225 4354 4450 507 1679 508 533 508 1679 508 1679 509 532 509 535 508 1677 510 531 510 534 507 1679 508 535 508 534 507 1679 508 1679 508 533 508 1680 508 532 509 1678 509 1678 509 533 508 1679 508 534 509 1679 508 1679 508 1679 508 534 507 534 509 1679 509 532 509 1678 509 532 509 534 508 1679 509 1679 508 1680 507 532 509 534 507 534 509 532 509 532 509 533 508 532 509 534 509 1679 508 1680 507 1678 509 1679 508 1680 509 +# +# Model: Xiaomi AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 988 606 586 2210 587 1472 586 882 587 2211 586 375 584 2210 587 374 584 1472 586 374 585 376 582 375 583 400 559 400 558 883 587 375 584 375 583 2210 587 374 584 884 586 882 587 374 584 376 583 2211 586 884 585 375 584 375 583 374 584 374 584 375 584 1472 585 882 587 376 583 2211 586 884 585 2211 586 375 583 375 583 400 558 884 586 374 584 375 583 376 583 375 583 375 583 2211 586 2211 585 375 583 883 586 +# +# Model: York AC +# +name: Off +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9024 4481 655 551 655 1653 654 550 656 1652 655 1652 656 550 656 550 656 550 656 550 656 551 655 1652 655 551 655 1652 655 550 656 551 655 1654 654 550 656 550 656 551 655 1652 655 550 656 551 654 550 656 1652 655 1651 657 550 655 551 655 550 656 1654 654 550 656 1653 654 551 654 551 655 1651 656 551 655 19984 655 550 656 551 655 550 656 551 655 550 655 551 655 551 655 550 656 1654 653 1653 655 1653 655 550 655 551 655 550 656 551 655 551 655 550 656 551 655 551 655 551 655 551 655 551 655 550 656 550 656 550 656 551 655 551 655 551 655 1652 656 550 656 551 655 550 656 39996 8999 4479 656 551 655 1652 656 550 656 1653 655 1653 655 550 656 551 655 550 656 551 655 551 655 1652 655 551 655 1652 655 550 656 551 655 1653 655 551 655 550 656 550 656 1652 655 551 654 551 655 551 655 1652 655 1652 656 551 655 551 655 552 654 551 655 1653 655 1653 655 551 655 549 656 1653 655 552 654 19984 655 1652 655 551 655 550 656 1652 656 551 655 551 655 551 655 1652 655 1652 655 551 656 1652 656 1653 655 1653 655 551 655 1652 655 551 655 551 655 551 654 551 654 551 655 551 655 1653 655 550 656 551 655 1652 656 1653 654 551 655 551 655 551 655 550 655 550 656 551 655 diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index f3ff853333c..20070bbe06e 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -567,3 +567,4086 @@ type: parsed protocol: NECext address: 00 7F 00 00 command: 1E E1 00 00 +# # Model: Adastra WA215 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 44 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 07 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 49 00 00 00 +# +# Model: Adcom GTP500II +# +name: Power +type: parsed +protocol: NEC42 +address: 51 00 00 00 +command: 00 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC42 +address: 51 00 00 00 +command: 1F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC42 +address: 51 00 00 00 +command: 1E 00 00 00 +# +# Model: Aiwa RC-T506 +# +name: Power +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 00 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 4D 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 4E 00 00 00 +# +# Model: AIWA XR-EM300 +# +name: Mute +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 4C 00 00 00 +# +# Model: ASR EMITTER1HD +# +name: Power +type: parsed +protocol: RC5 +address: 10 00 00 00 +command: 0C 00 00 00 +# +# Model: Auna AV2_CD508 +# +name: Power +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 16 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 12 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 4B 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 04 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 13 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 5E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 06 00 00 00 +# +# Model: CambridgeAudio 650A +# +name: Next +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 2B 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 30 00 00 00 +# +# Model: AEG +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 02 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 01 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0E 00 00 00 +# +# Model: AIWA NSXR71 +# +name: Play +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 40 00 00 00 +# +name: Pause +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 44 00 00 00 +# +name: Pause +type: parsed +protocol: NEC42 +address: 6E 00 00 00 +command: 41 00 00 00 +# +# Model: Denon RC_1199 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 03 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 04 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 05 00 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 06 00 00 00 +# +name: Prev +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 47 00 00 00 +# +name: Next +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 46 00 00 00 +# +name: Prev +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 0F 00 00 00 +# +name: Next +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 0E 00 00 00 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: 51 54 32 01 +command: 3F 00 00 00 +# +# Model: Fisher SLIM-1500 +# +name: Power +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 06 00 00 00 +# +name: Pause +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 13 00 00 00 +# +name: Play +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 13 00 00 00 +# +name: Pause +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 1B 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: A0 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 20 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: A2 00 00 00 +command: 60 00 00 00 +# +# Model: GPX cd_radio +# +name: Pause +type: parsed +protocol: Samsung32 +address: 81 00 00 00 +command: 09 00 00 00 +# +name: Next +type: parsed +protocol: Samsung32 +address: 81 00 00 00 +command: 11 00 00 00 +# +# Model: GPX HC221B +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 04 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 02 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 00 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 01 00 00 00 +# +# Model: LG +# +name: Power +type: parsed +protocol: Samsung32 +address: 10 00 00 00 +command: 1E 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 10 00 00 00 +command: 1F 00 00 00 +# +name: Pause +type: parsed +protocol: Samsung32 +address: 10 00 00 00 +command: 05 00 00 00 +# +# Model: Marantz RC2100DR_CD +# +name: Prev +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 21 00 00 00 +# +name: Next +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 20 00 00 00 +# +name: Next +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 34 00 00 00 +# +# Model: Marantz RC2100DR_CDR +# +name: Power +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 0C 00 00 00 +# +name: Prev +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 21 00 00 00 +# +name: Next +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 20 00 00 00 +# +name: Next +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 34 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 30 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 36 00 00 00 +# +name: Play +type: parsed +protocol: RC5 +address: 1A 00 00 00 +command: 35 00 00 00 +# +# Model: Onkyo DX-7333 +# +name: Pause +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1F E0 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1C E3 00 00 +# +name: Play +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1B E4 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 01 FE 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 00 FF 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1E E1 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 2C 00 00 +command: 1D E2 00 00 +# +# Model: Panasonic SC-HC58 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: AC 02 20 00 +command: 01 00 00 00 +# +# Model: Philips CD_720 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 11 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 10 00 00 00 +# +# Model: Philips CD_Player_723 +# +name: Prev +type: parsed +protocol: RC5 +address: 14 00 00 00 +command: 32 00 00 00 +# +# Model: Rockola juke +# +name: Power +type: parsed +protocol: NEC +address: 4D 00 00 00 +command: 00 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 4D 00 00 00 +command: 0F 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 4D 00 00 00 +command: 10 00 00 00 +# +# Model: Sony CFD-S35CP +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 44 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 44 00 00 00 +command: 13 00 00 00 +# +name: Play +type: parsed +protocol: SIRC15 +address: 64 00 00 00 +command: 32 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC15 +address: 64 00 00 00 +command: 39 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC15 +address: 64 00 00 00 +command: 38 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC15 +address: 64 00 00 00 +command: 3A 00 00 00 +# +name: Next +type: parsed +protocol: SIRC15 +address: 64 00 00 00 +command: 3B 00 00 00 +# +# Model: SONY minidisk-deck +# +name: Power +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 15 00 00 00 +# +name: Play +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 2A 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 29 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 28 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 20 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC +address: 0F 00 00 00 +command: 2B 00 00 00 +# +# Model: Sony RM-R52_RCD-W500C_Deck-A +# +name: Play +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 32 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 39 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 38 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 30 00 00 00 +# +name: Next +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 31 00 00 00 +# +name: Next +type: parsed +protocol: SIRC +address: 11 00 00 00 +command: 34 00 00 00 +# +# Model: Sony RM-R52_RCD-W500C_Deck-B +# +name: Play +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 2A 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 29 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 28 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 20 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 21 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 5A 19 00 00 +command: 2C 00 00 00 +# +# Model: TEAC RC-505_Remote_Control_Unit +# +name: Play +type: parsed +protocol: NECext +address: 86 61 00 00 +command: 08 F7 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 86 61 00 00 +command: 05 FA 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 86 61 00 00 +command: 09 F6 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 86 61 00 00 +command: 0D F2 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 86 61 00 00 +command: 0C F3 00 00 +# +# Model: Unkown KC-806 +# +name: Power +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1C E3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 00 FF 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 04 FB 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 17 E8 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 02 FD 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 01 FE 00 00 +# +# Model: Winnes KC809 +# +name: Pause +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 16 E9 00 00 +# +# Model: Denon RC1253 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 05 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 70 01 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 71 01 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 72 01 00 00 +# +name: Prev +type: parsed +protocol: Kaseikyo +address: 41 54 32 00 +command: 22 00 00 00 +# +# Model: Douk ST-01_Pro +# +name: Power +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 45 FF 00 00 +# +name: Play +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 44 FF 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 40 FF 00 00 +# +name: Next +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 43 FF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 09 FF 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 15 FF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: AA 33 00 00 +command: 47 FF 00 00 +# +# Model: Edifier r1280t +# +name: Vol_up +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 3C C3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 4D B2 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 2B D4 00 00 +# +# Model: Edifier r1700bt +# +name: Power +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 01 FE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 09 F6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 0C F3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 00 FF 00 00 +# +# Model: Edifier r1855db +# +name: Vol_up +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 06 F9 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 47 B8 00 00 +# +# Model: Edifier r2000db +# +name: Power +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 00 FF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 01 FE 00 00 +# +# Model: Emotiva PT100_TA100 +# +name: Power +type: parsed +protocol: NECext +address: 00 79 00 00 +command: 80 7F 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 79 00 00 +command: 99 66 00 00 +# +# Model: Grundig CMS_5000 +# +name: Pause +type: parsed +protocol: NECext +address: 30 FC 00 00 +command: 05 FA 00 00 +# +# Model: Harman Kardon AVI200 +# +name: Power +type: parsed +protocol: NECext +address: 80 70 00 00 +command: C0 3F 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 80 70 00 00 +command: C1 3E 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 80 70 00 00 +command: C7 38 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 80 70 00 00 +command: C8 37 00 00 +# +# Model: DTR-7 +# +name: Power +type: parsed +protocol: NECext +address: D2 6D 00 00 +command: 04 FB 00 00 +# +name: Power +type: parsed +protocol: NECext +address: D2 6C 00 00 +command: 47 B8 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: D2 6D 00 00 +command: 02 FD 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: D2 6D 00 00 +command: 03 FC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: D2 6D 00 00 +command: 05 FA 00 00 +# +# Model: Logitech Z906 +# +name: Power +type: parsed +protocol: NECext +address: 02 A0 00 00 +command: 80 7F 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 02 A0 00 00 +command: EA 15 00 00 +# +# Model: NAD712 +# +name: Power +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 80 7F 00 00 +# +# Model: NAD Amp_1 +# +name: Power +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: C8 37 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 02 FD 00 00 +# +# Model: Onkyo +# +name: Power +type: parsed +protocol: NECext +address: D2 6C 00 00 +command: CB 34 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 6C 00 00 +command: 54 AB 00 00 +# +# Model: Onkyo RC627S +# +name: Power +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 04 FB 00 00 +# +name: Power +type: parsed +protocol: NECext +address: D2 04 00 00 +command: 47 B8 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 02 FD 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 03 FC 00 00 +# +name: Play +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 1B E4 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 1F E0 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 1C E3 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 8B 74 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 8A 75 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 1E E1 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 03 00 00 +command: 1D E2 00 00 +# +# Model: Onkyo RC866M_Receiver +# +name: Prev +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 90 6F 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 80 7F 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 8F 70 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 91 6E 00 00 +# +name: Play +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 8D 72 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 02 00 00 +command: 8E 71 00 00 +# +# Model: Onkyo RC898M +# +name: Mute +type: parsed +protocol: NECext +address: D2 11 00 00 +command: 05 FA 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: D2 19 00 00 +command: 82 7D 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: D2 19 00 00 +command: 83 7C 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 90 6F 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 81 7E 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 80 7F 00 00 +# +name: Next +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 8F 70 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 91 6E 00 00 +# +name: Play +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 8D 72 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: D2 14 00 00 +command: 8E 71 00 00 +# +# Model: Panasonic N2QAYB000210 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: A2 02 20 00 +command: 02 00 00 00 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: A2 02 20 00 +command: 62 00 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: A2 02 20 00 +command: A2 00 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: AC 02 20 02 +command: B1 02 00 00 +# +# Model: Panasonic SA_PM602 +# +name: Play +type: parsed +protocol: Kaseikyo +address: AC 02 20 00 +command: 61 00 00 00 +# +# Model: Panasonic SC-PMX92-94 +# +name: Next +type: parsed +protocol: Kaseikyo +address: AC 02 20 00 +command: 31 00 00 00 +# +name: Prev +type: parsed +protocol: Kaseikyo +address: AC 02 20 00 +command: 21 00 00 00 +# +# Model: Philips BTD2180 +# +name: Power +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 0C 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 10 00 00 00 +# +name: Mute +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 2C 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 11 00 00 00 +# +name: Prev +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 21 00 00 00 +# +name: Pause +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 31 00 00 00 +# +name: Next +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 20 00 00 00 +# +name: Prev +type: parsed +protocol: RC6 +address: 04 00 00 00 +command: 83 00 00 00 +# +# Model: Pioneer AXD7741 +# +name: Power +type: parsed +protocol: Pioneer +address: A5 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: Pioneer +address: A5 00 00 00 +command: 0A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Pioneer +address: A5 00 00 00 +command: 0B 00 00 00 +# +# Model: Pioneer VSX-D1-S +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 06 F9 00 00 +# +# Model: Pioneer XXD3105 +# +name: Play +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 0C 00 00 00 +# +name: Pause +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 5B 00 00 00 +# +# Model: Portta Toslink_Audio_Switcher +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 17 00 00 00 +# +# Model: Pyle P2203ABTU +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 05 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0B 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 4D 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 54 00 00 00 +# +# Model: Rega IO +# +name: Prev +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 08 00 00 00 +# +# Model: Revo Superconnect_BZAWDFB0315H2 +# +name: Power +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 02 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 01 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 12 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 59 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 18 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 58 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 5B 00 00 00 +# +# Model: Rio Sonic_Blue_059PXC +# +name: Power +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 84 7B 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 85 7A 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 90 6F 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 83 7C 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 87 78 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 80 7F 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 8A 75 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 82 13 00 00 +command: 81 7E 00 00 +# +# Model: SiriusXM Onyx_EZR +# +name: Power +type: parsed +protocol: RC5X +address: 1B 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: RC5X +address: 1B 00 00 00 +command: 0D 00 00 00 +# +# Model: SMSL RC-8A +# +name: Power +type: parsed +protocol: NECext +address: 12 34 00 00 +command: 01 FE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 12 34 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 12 34 00 00 +command: 0B F4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 12 34 00 00 +command: 09 F6 00 00 +# +# Model: SMSL RC-8C +# +name: Power +type: parsed +protocol: NECext +address: 12 36 00 00 +command: 01 FE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 12 36 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 12 36 00 00 +command: 0B F4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 12 36 00 00 +command: 09 F6 00 00 +# +# Model: Sony Amp +# +name: Power +type: parsed +protocol: SIRC15 +address: 30 00 00 00 +command: 15 00 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 30 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 30 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC15 +address: 30 00 00 00 +command: 14 00 00 00 +# +# Model: Sony I-WXH-80 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 02 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 01 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 0B 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 06 00 00 00 +# +# Model: Sony MHC-GS300AV +# +name: Power +type: parsed +protocol: SIRC15 +address: 10 00 00 00 +command: 15 00 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 10 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 10 00 00 00 +command: 13 00 00 00 +# +# Model: Sony RMT-AA320U_AV +# +name: Pause +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 38 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 31 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 30 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 7D 00 00 00 +# +# Model: Sony RM_AMU009_Sony_Audio_System_CMT +# +name: Prev +type: parsed +protocol: SIRC20 +address: 3A 07 00 00 +command: 30 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 3A 07 00 00 +command: 31 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 3A 07 00 00 +command: 34 00 00 00 +# +# Model: Sony STR-DH590 +# +name: Prev +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 33 00 00 00 +# +name: Next +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 34 00 00 00 +# +# Model: Firetv EVG487 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 67 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 38 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 3C 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 3D 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 8C 00 00 00 +# +# Model: Audac IMEO2 +# +name: Power +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 1C E3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 21 DE 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 2B D4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 1D E2 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 2A D5 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 13 EC 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 14 EB 00 00 +# +# Model: Bestisan7020 +# +name: Power +type: parsed +protocol: NECext +address: 29 A1 00 00 +command: 7F 80 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 29 A1 00 00 +command: 9B 64 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 29 A1 00 00 +command: 9E 61 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 29 A1 00 00 +command: 9F 60 00 00 +# +# Model: Bose CINEMATE_1_SR +# +name: Power +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 4C B3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 03 FC 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 02 FD 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 01 FE 00 00 +# +# Model: Bose Solo_5 +# +name: Next +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 5A A5 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 59 A6 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 56 A9 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 1A E5 00 00 +# +# Model: Bose Solo_Soundbar +# +name: Mute +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 01 FD 00 00 +# +# Model: Bose Soundbar_300 +# +name: Power +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 4C B3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 03 FC 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 02 FD 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 01 FE 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: BA 4B 00 00 +command: 57 A8 00 00 +# +# Model: Amz snd_bar +# +name: Power +type: parsed +protocol: NEC +address: 35 00 00 00 +command: 09 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 35 00 00 00 +command: 45 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 35 00 00 00 +command: 1B 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 35 00 00 00 +command: 51 00 00 00 +# +# Model: Soundblasterx +# +name: Power +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 08 F7 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 0C F3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 01 FE 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 07 F8 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 06 F9 00 00 +# +# Model: Canton Smart10 +# +name: Mute +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 1E E1 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 1D E2 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 18 E7 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 0A F5 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 0D F2 00 00 +# +# Model: YARRA 3DX +# +name: Power +type: parsed +protocol: NECext +address: 0A 1D 00 00 +command: 01 FE 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 0A 1D 00 00 +command: 03 FC 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 0A 1D 00 00 +command: 08 F7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 0A 1D 00 00 +command: 0A F5 00 00 +# +# Model: Craig CHT912 +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 0C 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 02 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 05 00 00 00 +# +# Model: Craig CHT921 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 12 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 03 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 01 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1E 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1F 00 00 00 +# +# Model: Craig CHT939 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +# Model: Soundbar creative_stage +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 09 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 06 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 07 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 1E 00 00 00 +# +# Model: Denon RC-1230 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 52 54 32 00 +command: 83 00 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: 52 54 32 00 +command: 86 00 00 00 +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 52 54 32 00 +command: 84 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 52 54 32 00 +command: 85 00 00 00 +# +# Model: Hisense HS215 +# +name: Power +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 20 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 34 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 34 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 21 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 22 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 23 00 00 00 +# +# Model: Hitachi HSB40B16 +# +name: Power +type: parsed +protocol: NEC +address: 5B 00 00 00 +command: 80 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 5B 00 00 00 +command: 88 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 5B 00 00 00 +command: 82 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 5B 00 00 00 +command: 83 00 00 00 +# +# Model: iLive ITP280B_Remote +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1E 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 42 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 41 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +# Model: iLive Soundbar +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 14 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 10 00 00 00 +# +# Model: JBL 5_1_Soundbar +# +name: Power +type: parsed +protocol: NECext +address: 84 74 00 00 +command: FF 00 00 00 +# +# Model: JBL CINEMA_SB120 +# +name: Power +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 53 AC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: AD 52 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 26 D9 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 28 D7 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 27 D8 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 25 DA 00 00 +# +# Model: JVC THBY370A +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 00 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 08 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 09 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 01 00 00 00 +# +# Model: Klipsch Soundbar +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 01 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 03 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 06 00 00 00 +# +# Model: Larksound L200 +# +name: Vol_up +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 19 E6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 16 E9 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 09 F6 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 5E A1 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 0C F3 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 18 E7 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 30 0F 00 00 +command: 45 BA 00 00 +# +# Model: LG SJ4 +# +name: Power +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 1E 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 17 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 16 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 1F 00 00 00 +# +name: Play +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 4F 00 00 00 +# +name: Prev +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 06 00 00 00 +# +# Model: LG AKB74815321 +# +name: Next +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 07 00 00 00 +# +# Model: LG NB5541 +# +name: Pause +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 4F 00 00 00 +# +name: Pause +type: parsed +protocol: Samsung32 +address: 2C 00 00 00 +command: 05 00 00 00 +# +# Model: Majority K2_SoundBar +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 41 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 42 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 43 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 1A 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 4F 00 00 00 +# +# Model: Onn sound_bar_and_subwoofer +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 16 E9 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 0F F0 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 15 EA 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 83 22 00 00 +command: 0C F3 00 00 +# +# Model: Panasonic SCH-TB8 +# +name: Power +type: parsed +protocol: Kaseikyo +address: A0 02 20 00 +command: D0 03 00 00 +# +# Model: Pheanoo P27 +# +name: Power +type: parsed +protocol: NECext +address: 83 B6 00 00 +command: 4D B2 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 83 B6 00 00 +command: 43 BC 00 00 +# +# Model: Philips HTL2161B_Soundbar +# +name: Power +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 0C 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 11 00 00 00 +# +# Model: Philips HTL2163_Soundbar +# +name: Mute +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 0D 00 00 00 +# +# Model: Philips TAB5105_79 +# +name: Power +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: C7 00 00 00 +# +name: Next +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 20 00 00 00 +# +name: Prev +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 21 00 00 00 +# +name: Play +type: parsed +protocol: RC6 +address: 10 00 00 00 +command: 2C 00 00 00 +# +# Model: Polk RE9114_1 +# +name: Power +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 00 FF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 20 DF 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 26 D9 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 21 DE 00 00 +# +name: Next +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 24 DB 00 00 +# +# Model: POLK RE9641_1 +# +name: Vol_up +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 1E E1 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: C8 91 00 00 +command: 1F E0 00 00 +# +# Model: Promethean ActivSoundBar_ASB-40 +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 05 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 02 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 08 00 00 00 +# +# Model: Quantis LSW-1 +# +name: Power +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 12 ED 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1D E2 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 1A E5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 03 FC 00 00 +# +# Model: RCA RTS7110B2 +# +name: Power +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 5C 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 45 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 45 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 4E 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 55 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 8A 00 00 00 +command: 59 00 00 00 +# +# Model: Sennheiser Ambeo_Soundbar +# +name: Power +type: parsed +protocol: RC5 +address: 13 00 00 00 +command: 0B 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 13 00 00 00 +command: 0D 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 13 00 00 00 +command: 0E 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 13 00 00 00 +command: 0C 00 00 00 +# +# Model: Sonos ARC_Beam_Playbar_Playbase +# +name: Vol_up +type: parsed +protocol: NECext +address: 80 D9 00 00 +command: 8A 75 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 80 D9 00 00 +command: 88 77 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 80 D9 00 00 +command: 8C 73 00 00 +# +# Model: Sony Old_XBR +# +name: Power +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 15 00 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 14 00 00 00 +# +# Model: Sony Soundbar_HT-XT3 +# +name: Play +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 32 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 39 00 00 00 +# +# Model: TaoTronics TT-SK023_Sound_Bar +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 12 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 0D 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1E 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 0A 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1F 00 00 00 +# +# Model: Taotronics TT-SK026 +# +name: Power +type: parsed +protocol: NECext +address: D9 14 00 00 +command: 6D 92 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: D9 14 00 00 +command: 6E 91 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: D9 14 00 00 +command: 4F B0 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: D9 14 00 00 +command: 50 AF 00 00 +# +# Model: TCL TS5010 +# +name: Pause +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 2A D5 00 00 +# +# Model: Teufel Cinebase_Soundbar +# +name: Power +type: parsed +protocol: NECext +address: EF 01 00 00 +command: 25 DA 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: EF 01 00 00 +command: 14 EB 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: EF 01 00 00 +command: 13 EC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: EF 01 00 00 +command: 28 D7 00 00 +# +# Model: Toshiba SBX4250 +# +name: Power +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: 12 ED 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: 1F E0 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: 1F E0 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: BD 42 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: 60 9F 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 45 BD 00 00 +command: 61 9E 00 00 +# +# Model: Vizio SB3651_E6 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 41 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 48 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8B 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8A 00 00 00 +# +# Model: Vizio V51-H6 +# +name: Play +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8E 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 8E 00 00 00 +# +# Model: Binnifa Live1T +# +name: Mute +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 50 00 00 00 +# +# Model: Yamaha BAR400 +# +name: Power +type: parsed +protocol: NEC +address: 78 00 00 00 +command: CC 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 1E 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 1F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 9C 00 00 00 +# +# Model: Audioengine a5 +# +name: Mute +type: parsed +protocol: NECext +address: 00 FD 00 00 +command: 03 FC 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 FD 00 00 +command: 09 F6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 FD 00 00 +command: 07 F8 00 00 +# +# Model: Biseoamz DY29S +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1C 00 00 00 +# +# Model: Bumpboxx +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 46 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 45 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 09 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 19 00 00 00 +# +# Model: Como audio +# +name: Power +type: parsed +protocol: NEC +address: 77 00 00 00 +command: F1 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 77 00 00 00 +command: F3 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 77 00 00 00 +command: F7 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 77 00 00 00 +command: FB 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 77 00 00 00 +command: FC 00 00 00 +# +# Model: Craig CHT729 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 47 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 15 00 00 00 +# +# Model: Craig CHT904 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 0E 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 1A 00 00 00 +# +# Model: Creative z5400 +# +name: Power +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 10 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 1A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 0E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 16 00 00 00 +# +# Model: DollarTec BT5AmpBoard +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 15 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 43 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 40 00 00 00 +# +# Model: EASTERN DA_9000 +# +name: Power +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 1D 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 18 00 00 00 +# +# Model: Edifier AirPulse_A80 +# +name: Power +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 00 FF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 19 E6 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 1C E3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 18 E7 00 00 +# +# Model: Edifier R1280DB +# +name: Prev +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 06 F9 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 08 F7 00 00 +# +# Model: Edifier R1700BT +# +name: Vol_up +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 2B D4 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 3C C3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 1A E5 00 00 +# +# Model: Edifier R1700BTs +# +name: Prev +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 44 BB 00 00 +# +# Model: Edifier R2800 +# +name: Mute +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 09 F6 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 01 FE 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 02 FD 00 00 +# +# Model: Edifier RC80B +# +name: Next +type: parsed +protocol: NECext +address: 10 E7 00 00 +command: 5D A2 00 00 +# +# Model: Edifier S360DB +# +name: Power +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 1C E3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 0C F3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 0F F0 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 03 FC 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 78 0E 00 00 +command: 40 BF 00 00 +# +# Model: Fluance AI40 +# +name: Power +type: parsed +protocol: NECext +address: 3F 5C 00 00 +command: 18 E7 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 3F 5C 00 00 +command: 55 AA 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 3F 5C 00 00 +command: 59 A6 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 3F 5C 00 00 +command: 15 EA 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 3F 5C 00 00 +command: 16 E9 00 00 +# +# Model: Dream Aurora_AC6923 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 57 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 03 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 10 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 13 00 00 00 +# +# Model: Quacker LED_Speaker +# +name: Prev +type: parsed +protocol: NECext +address: 00 EF 00 00 +command: 0D F2 00 00 +# +# Model: Geneva Sound_System_Model_L +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 00 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 06 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0C 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0B 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 11 00 00 00 +# +# Model: IBIZA PORT_15_UHF +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 48 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1D 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 11 00 00 00 +# +# Model: JBL Cinema_CB150 +# +name: Power +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 1B E4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 2A D5 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 14 EB 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 86 FF 00 00 +command: 13 EC 00 00 +# +# Model: JBL LSR4326P +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 14 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 08 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 00 00 00 00 +# +# Model: JBL On_Stage_IIIP_Speaker +# +name: Mute +type: parsed +protocol: NECext +address: 40 AF 00 00 +command: 19 E6 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 40 AF 00 00 +command: 12 ED 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 40 AF 00 00 +command: 05 FA 00 00 +# +# Model: JVC D62BM +# +name: Play +type: parsed +protocol: NECext +address: 02 BD 00 00 +command: 0D F2 00 00 +# +# Model: KEF LSX_Speakers +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 02 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 04 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 4B 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 4A 00 00 00 +# +# Model: Klipsch fives +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1A 00 00 00 +# +# Model: Klipsch r15pm +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 0A 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 0D 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1C 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 07 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 02 00 00 00 +# +# Model: Logi WD216XM +# +name: Power +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 02 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 08 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 00 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 01 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 05 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 04 00 00 00 +# +# Model: Z906 +# +name: Vol_up +type: parsed +protocol: NECext +address: 02 A0 00 00 +command: AA 55 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 02 A0 00 00 +command: 6A 95 00 00 +# +# Model: Microlab RC071 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 11 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 02 00 00 00 +# +# Model: Microlab SOLO11 +# +name: Prev +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 5A 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 5B 00 00 00 +# +# Model: Naim Muso +# +name: Power +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 0C 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 0D 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 11 00 00 00 +# +name: Next +type: parsed +protocol: RC5 +address: 15 00 00 00 +command: 20 00 00 00 +# +# Model: Sangean RC30 +# +name: Vol_up +type: parsed +protocol: NEC42 +address: 01 00 00 00 +command: 0C 00 00 00 +# +# Model: Sony Amp_STR-DE875 +# +name: Power +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 2E 00 00 00 +# +name: Power +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 2F 00 00 00 +# +name: Play +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 0C 00 00 00 +# +name: Pause +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 0C 00 00 00 +# +# Model: Sony SRS-GU10iP +# +name: Power +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 15 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 14 00 00 00 +# +name: Play +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 28 00 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 13 00 00 00 +# +name: Next +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 29 00 00 00 +# +name: Prev +type: parsed +protocol: SIRC15 +address: 99 00 00 00 +command: 2A 00 00 00 +# +# Model: Steljes Desktop_Speakers +# +name: Power +type: parsed +protocol: NECext +address: 85 23 00 00 +command: 99 66 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 85 23 00 00 +command: 57 A8 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 85 23 00 00 +command: 47 B8 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 85 23 00 00 +command: 97 68 00 00 +# +# Model: Teufel 3SIXTY +# +name: Power +type: parsed +protocol: NEC +address: FD 00 00 00 +command: E2 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: FD 00 00 00 +command: E1 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: FD 00 00 00 +command: E7 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: FD 00 00 00 +command: B9 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: FD 00 00 00 +command: EA 00 00 00 +# +# Model: Teufel CEM500RC +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 13 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 17 00 00 00 +# +# Model: Toshiba RM-V329 +# +name: Power +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 12 ED 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 11 EE 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 10 EF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 13 EC 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 03 FC 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 07 F8 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 2D D3 00 00 +command: 06 F9 00 00 +# +# Model: Technics EUR646496 +# +name: Power +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: D0 03 00 00 +# +name: Play +type: parsed +protocol: Kaseikyo +address: 90 02 20 00 +command: A0 00 00 00 +# +name: Next +type: parsed +protocol: Kaseikyo +address: AA 02 20 01 +command: A0 00 00 00 +# +name: Pause +type: parsed +protocol: Kaseikyo +address: AA 02 20 00 +command: 00 00 00 00 +# +# Model: terratec m3po +# +name: Mute +type: parsed +protocol: RC5X +address: 0A 00 00 00 +command: 2F 00 00 00 +# +name: Play +type: parsed +protocol: RC5X +address: 0A 00 00 00 +command: 31 00 00 00 +# +name: Pause +type: parsed +protocol: RC5X +address: 0A 00 00 00 +command: 0F 00 00 00 +# +name: Pause +type: parsed +protocol: RC5X +address: 0A 00 00 00 +command: 2C 00 00 00 +# +# Model: X4 TECH_TU-1200 +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 04 00 00 00 +# +# Model: YAMAHA AX-380 +# +name: Power +type: parsed +protocol: NEC +address: 7A 00 00 00 +command: 1F 00 00 00 +# +# Model: Yamaha RAS5 +# +name: Power +type: parsed +protocol: NECext +address: 7E 81 00 00 +command: 2A D4 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 7A 85 00 00 +command: 1A E4 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 7A 85 00 00 +command: 1B E5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 7A 85 00 00 +command: 1C E2 00 00 +# +# Model: Yamaha RAV203_V473170_US +# +name: Mute +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 0D 00 00 00 +# +name: Pause +type: parsed +protocol: RC5 +address: 05 00 00 00 +command: 29 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 7A 00 00 00 +command: 59 00 00 00 +# +name: Play +type: parsed +protocol: RC5 +address: 05 00 00 00 +command: 35 00 00 00 +# +# Model: Yamaha WS19340 +# +name: Power +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 0F 00 00 00 +# +name: Play +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 02 00 00 00 +# +name: Prev +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 04 00 00 00 +# +name: Next +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 03 00 00 00 +# +name: Pause +type: parsed +protocol: NEC +address: 78 00 00 00 +command: 01 00 00 00 +# +# Model: Yamaha ZP45780 +# +name: Prev +type: parsed +protocol: NECext +address: 7F 01 00 00 +command: 6A 95 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 7F 01 00 00 +command: 6B 94 00 00 +# +name: Play +type: parsed +protocol: NECext +address: 7F 01 00 00 +command: 68 97 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 7F 01 00 00 +command: 67 98 00 00 +# +name: Pause +type: parsed +protocol: NECext +address: 7F 01 00 00 +command: 69 96 00 00 diff --git a/applications/main/infrared/resources/infrared/assets/projector.ir b/applications/main/infrared/resources/infrared/assets/projector.ir index 5a161d153cd..c63d5a31284 100644 --- a/applications/main/infrared/resources/infrared/assets/projector.ir +++ b/applications/main/infrared/resources/infrared/assets/projector.ir @@ -798,6 +798,8 @@ protocol: NECext address: 83 F4 00 00 command: 17 E8 00 00 # +# Model: ViewSonic X1_Projector +# name: Vol_up type: raw frequency: 38000 @@ -839,3 +841,351 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 9010 4253 564 566 566 1671 566 1699 565 565 539 568 564 566 565 539 566 1699 565 1672 565 566 566 1672 564 567 565 1672 565 567 565 567 564 541 564 1698 566 539 565 567 565 567 562 542 565 1699 564 539 567 1699 565 540 564 1698 566 1672 565 1698 566 1672 565 567 565 1671 565 566 566 +# +# Model: Apeman LC650_ +# +name: Power +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 01 FE 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 6A 95 00 00 +# +# Model: BenQ MH856UST +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 30 00 00 +command: 97 68 00 00 +# +# Model: BenQ TRY01 +# +name: Power +type: parsed +protocol: NECext +address: 04 B1 00 00 +command: 58 A7 00 00 +# +# Model: Generic Universal_Remote +# +name: Power +type: parsed +protocol: NECext +address: 48 50 00 00 +command: 02 FD 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 48 50 00 00 +command: 27 D8 00 00 +# +# Model: Coolux X3S +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 00 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 1A 00 00 00 +# +# Model: Dell projector +# +name: Power +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 02 FD 00 00 +# +# Model: Dell tsfm_ir01 +# +name: Mute +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 0F F0 00 00 +# +# Model: Epson 4650 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2288 611 571 587 1153 587 572 587 572 1167 572 1168 571 587 573 587 572 587 572 589 570 587 572 587 572 587 572 588 571 77346 2287 611 571 588 1152 588 571 588 571 1168 571 1168 571 588 571 588 571 588 571 589 570 587 572 588 572 587 572 587 572 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2288 610 572 588 1151 587 573 588 571 1169 570 586 573 587 572 587 1152 587 573 586 1153 588 572 587 573 586 573 587 572 76771 2289 610 572 588 1151 587 573 587 572 1166 573 587 572 587 572 587 1152 588 571 588 1151 587 573 587 572 587 573 588 571 +# +# Model: Epson EHTW5650 +# +name: Mute +type: parsed +protocol: NECext +address: 83 55 00 00 +command: AD 52 00 00 +# +# Model: Epson EMP822H +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 55 00 00 +command: B1 4E 00 00 +# +# Model: Epson projector_Power_Only +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8811 4222 530 1580 531 1579 531 507 531 507 531 507 531 508 531 508 530 1582 528 1583 527 535 503 1608 502 536 501 1609 501 537 501 1610 500 538 500 1611 499 538 500 539 500 538 500 1611 500 539 499 538 500 1611 499 539 499 1611 499 1611 500 1611 499 539 499 1611 500 1611 500 539 499 35437 8784 4252 500 1611 500 1612 500 539 500 539 500 539 500 539 500 539 500 1611 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 1612 500 539 500 1612 499 539 500 539 500 539 499 1612 499 540 499 539 500 1612 499 539 500 1612 499 1613 499 1612 499 539 500 1612 500 1612 500 539 500 +# +# Model: Gateway 210_projextor +# +name: Power +type: parsed +protocol: NEC +address: 30 00 00 00 +command: 0B 00 00 00 +# +# Model: Groview +# +name: Power +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 0A F5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 4A B5 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 6B 00 00 +command: 0E F1 00 00 +# +# Model: Infocus Navigator_3 +# +name: Power +type: parsed +protocol: NECext +address: 87 4E 00 00 +command: 17 E8 00 00 +# +# Model: JVC LX-UH1B +# +name: Power +type: parsed +protocol: NECext +address: 00 6A 00 00 +command: 40 BF 00 00 +# +# Model: LG PH300-NA +# +name: Power +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: AD 52 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: 02 FD 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: 03 FC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 04 0F 00 00 +command: 09 F6 00 00 +# +# Model: Maxell MC-EU5001 +# +name: Mute +type: parsed +protocol: NECext +address: 87 45 00 00 +command: 52 AD 00 00 +# +# Model: NexiGo-PJ20 +# +name: Mute +type: parsed +protocol: NEC +address: 03 00 00 00 +command: 02 00 00 00 +# +# Model: Optoma projector +# +name: Vol_up +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 07 F8 00 00 +# +# Model: Optoma Remote_HOF04K276D6 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 4F 50 00 00 +command: 0A F5 00 00 +# +# Model: Optoma UHZ45 +# +name: Mute +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 03 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 09 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 32 00 00 00 +command: 0C 00 00 00 +# +# Model: Philips PicoPix_Max_PPX620_Projector +# +name: Power +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 12 00 00 00 +# +# Model: PVO YG300Pro +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 40 00 00 00 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9107 4376 681 1573 681 472 655 472 654 474 652 475 652 476 651 476 652 476 651 476 651 1604 651 1604 651 1604 651 1603 652 1604 651 1604 651 1604 651 1604 650 476 651 477 650 477 650 476 651 477 651 1604 650 476 651 477 650 1604 651 1604 650 1604 651 1604 650 1604 651 477 650 1604 650 39498 9079 2178 651 +# +# Model: RIF6-cube-projector-raw +# +name: Power +type: raw +frequency: 36045 +duty_cycle: 0.330000 +data: 9024 4506 570 582 542 582 518 606 518 606 518 606 518 606 518 606 518 610 518 1698 548 1698 546 1700 546 1700 546 1698 546 1700 546 1696 548 1702 570 1674 546 610 516 1698 546 606 542 582 542 582 544 1674 546 610 520 606 542 1674 570 582 542 1676 546 1698 570 1674 572 582 520 1698 546 +# +name: Mute +type: raw +frequency: 36045 +duty_cycle: 0.330000 +data: 9044 4484 572 580 544 580 544 580 544 580 542 582 544 580 520 604 542 586 544 1674 570 1674 570 1676 570 1674 572 1672 570 1674 546 1700 568 1678 570 582 542 1672 572 580 542 580 542 1674 566 582 542 1672 570 584 538 1674 564 580 534 1674 556 1674 556 580 530 1672 558 582 524 1676 552 +# +# Model: Samsung Freestyle_Gen2 +# +name: Power +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 02 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: D1 00 00 00 +# +# Model: Samsung VG-TM2360E +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1171 433 566 433 881 433 2381 433 1486 434 565 434 1486 433 1776 433 2380 433 565 434 2381 433 1170 434 87358 1220 1171 433 566 433 883 431 2381 433 1486 433 567 432 1487 432 1775 434 2381 432 566 433 2380 434 1171 433 86252 1221 1172 432 565 434 880 435 2381 433 1486 433 565 434 1487 432 1776 433 2380 434 566 433 2379 434 1170 434 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1173 431 566 433 882 432 2382 432 1487 432 566 433 565 434 566 433 2671 432 2670 433 2381 433 566 433 87411 324 937 325 358 325 647 326 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1172 432 566 433 882 433 2381 432 1486 434 566 433 565 434 882 433 1486 434 2669 434 2065 433 566 433 87779 324 936 326 358 325 647 326 +# +# Model: Sharp RRMCGA664WJSA_Notevision XR-32S-L +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 293 1801 296 753 295 1801 296 1801 296 752 296 754 294 1801 296 1800 297 752 296 1802 295 752 296 1801 296 753 295 1800 297 752 296 42709 296 1800 297 753 295 1800 297 1800 297 753 295 1802 295 753 295 753 295 1801 296 753 295 1801 296 754 294 1802 295 753 295 1801 296 42694 295 1800 297 752 296 1803 294 1803 294 753 295 753 295 1801 296 1802 295 752 296 1802 295 752 296 1801 296 753 295 1802 295 753 295 42709 295 1802 295 753 295 1803 294 1801 296 753 295 1802 295 752 296 752 296 1801 296 752 296 1803 294 754 294 1803 294 754 294 1804 293 42694 294 1802 294 755 293 1803 294 1804 268 779 269 779 269 1828 269 1828 269 780 268 1829 268 778 270 1829 323 725 268 1829 268 781 324 +# +# Model: SMART Projectors +# +name: Power +type: parsed +protocol: NECext +address: 8B CA 00 00 +command: 12 ED 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 8B CA 00 00 +command: 11 EE 00 00 +# +# Model: Sony RM_PJ27 +# +name: Vol_up +type: parsed +protocol: SIRC15 +address: 54 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC15 +address: 54 00 00 00 +command: 13 00 00 00 +# +# Model: TopVision +# +name: Vol_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 11 00 00 00 diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 724cb90cec7..ae3c4d4b44e 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1978,3 +1978,1682 @@ type: parsed protocol: NECext address: 00 7F 00 00 command: 18 E7 00 00 +# # Model: FireTV Omni_Series_4K +# +name: Power +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 46 B9 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 0C F3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 19 E6 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 4C B3 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 0F F0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 5A A5 00 00 +# +# Model: Android TV_MXQ +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 40 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 18 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 41 00 00 00 +# +# Model: APEX LE4643T +# +name: Power +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 0A F5 00 00 +# +# Model: BAIRD T15011DLEDDS_RC-6 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 0C 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 0D 00 00 00 +# +# Model: BBK TV_LEM-1071 +# +name: Power +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 1C E3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 4B B4 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 4F B0 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 08 F7 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 09 F6 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 DF 00 00 +command: 05 FA 00 00 +# +# Model: BGH BLE2814D +# +name: Power +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 03 FC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 15 EA 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 16 E9 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 1A E5 00 00 +# +# Model: Blaupunkt +# +name: Mute +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 50 AF 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1E E1 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5F A0 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1F E0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5C A3 00 00 +# +# Model: Blitzwolf BWPCM2 +# +name: Power +type: parsed +protocol: NECext +address: A0 B7 00 00 +command: E9 16 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: A0 B7 00 00 +command: AF 50 00 00 +# +# Model: Bolva TV +# +name: Ch_prev +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 41 BE 00 00 +# +# Model: Bose TV +# +name: Power +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 4C B3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: BA A0 00 00 +command: 01 FE 00 00 +# +# Model: BUSH TV_VL32HDLED +# +name: Power +type: parsed +protocol: NEC +address: 08 00 00 00 +command: D7 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 80 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 8E 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 83 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 08 00 00 00 +command: 86 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 08 00 00 00 +command: DF 00 00 00 +# +# Model: CCE RC512_Remote +# +name: Power +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 40 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 12 00 00 00 +# +# Model: ContinentalEdison +# +name: Mute +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5C A3 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 48 B7 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 44 BB 00 00 +# +# Model: ContinentalEdison CELED32JBL7 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 12 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 14 00 00 00 +# +# Model: Crown 22111 +# +name: Power +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 52 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 53 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 02 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 09 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 03 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 41 00 00 00 +# +# Model: Daewood Parsed +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 82 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 9F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 8B 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 9B 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 8F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: D0 00 00 00 +# +# Model: Dual DL-32HD-002 +# +name: Vol_up +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 0F F0 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 5A A5 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 0C F3 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 02 7D 00 00 +command: 19 E6 00 00 +# +# Model: Dynex DX-RC01A-12 +# +name: Power +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0F F0 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0C F3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0D F2 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0A F5 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0B F4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 86 05 00 00 +command: 0E F1 00 00 +# +# Model: DYON Movie_Smart_32_XT +# +name: Power +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 0A F5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 0F F0 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 15 EA 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 1C E3 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 1F E0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 40 40 00 00 +command: 1E E1 00 00 +# +# Model: EdenWood TV +# +name: Ch_next +type: parsed +protocol: RC5 +address: 01 00 00 00 +command: 14 00 00 00 +# +# Model: Elitelux L32HD1000 +# +name: Power +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 15 EA 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1B E4 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 1A E5 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 19 E6 00 00 +# +# Model: Enseo +# +name: Power +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 02 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 06 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 0C 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 08 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 0E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 6E 00 00 00 +command: 04 00 00 00 +# +# Model: Fetch TV_Box_AUS +# +name: Power +type: parsed +protocol: NECext +address: 64 46 00 00 +command: 5D A2 00 00 +# +name: Vol_up +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 10 00 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 64 46 00 00 +command: DE 21 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 64 46 00 00 +command: DB 24 00 00 +# +# Model: Furrion +# +name: Ch_next +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 02 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 09 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 03 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 20 00 00 00 +command: 41 00 00 00 +# +# Model: AORUS Monitor +# +name: Power +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 1A 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 11 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 00 00 00 00 +command: 33 00 00 00 +# +# Model: Grandin +# +name: Power +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 12 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 1E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 80 00 00 00 +command: 10 00 00 00 +# +# Model: Grandin Unknown_Model +# +name: Vol_up +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 1A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 1E 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 1B 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 40 00 00 00 +command: 1F 00 00 00 +# +# Model: GRUNDIG +# +name: Ch_next +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 20 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 21 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 11 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 0D 00 00 00 +# +# Model: Hitachi 43140 +# +name: Vol_up +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 11 00 00 00 +# +name: Mute +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 0D 00 00 00 +# +name: Ch_next +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 20 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC5 +address: 03 00 00 00 +command: 21 00 00 00 +# +# Model: Hitachi LE46H508 +# +name: Mute +type: parsed +protocol: NEC +address: 50 00 00 00 +command: 0B 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 50 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 50 00 00 00 +command: 15 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 50 00 00 00 +command: 19 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 50 00 00 00 +command: 18 00 00 00 +# +# Model: KRAFT KTV +# +name: Power +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 0A F5 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 0B F4 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 1E E1 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 5F A0 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 1F E0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 01 3E 00 00 +command: 5C A3 00 00 +# +# Model: LG 27GR95QE_TV +# +name: Power +type: parsed +protocol: NECext +address: 04 F4 00 00 +command: 08 F7 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 04 F4 00 00 +command: 02 FD 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 04 F4 00 00 +command: 03 FC 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 04 F4 00 00 +command: 09 F6 00 00 +# +# Model: LG Hotel_TV_Home2 +# +name: Power +type: parsed +protocol: NECext +address: 69 69 00 00 +command: 01 FE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 69 69 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 69 69 00 00 +command: 0B F4 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 69 69 00 00 +command: 0E F1 00 00 +# +# Model: LG MR21GC_Magic_Remote +# +name: Ch_next +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 34 00 00 00 +# +name: Ch_prev +type: parsed +protocol: SIRC20 +address: 10 01 00 00 +command: 33 00 00 00 +# +# Model: lodgenet lrc3220 +# +name: Power +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 80 7F 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 8F 70 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 93 6C 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 8D 72 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 91 6E 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 85 7C 00 00 +command: 97 68 00 00 +# +# Model: LOEWE TV +# +name: Ch_next +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 18 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC5 +address: 00 00 00 00 +command: 17 00 00 00 +# +# Model: Manta +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 48 B7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 49 B6 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 01 FE 00 00 +# +# Model: Manta TV +# +name: Power +type: parsed +protocol: NECext +address: 00 BF 00 00 +command: 00 FF 00 00 +# +# Model: Manta TV_2 +# +name: Power +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 5F 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 40 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 5D 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 03 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 1F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 1C 00 00 00 +# +# Model: Matsui 1435b +# +name: Power +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 14 00 00 00 +# +name: Ch_prev +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 11 00 00 00 +# +name: Ch_next +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 10 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 13 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 12 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 17 00 00 00 +command: 15 00 00 00 +# +# Model: Medion MD21302 +# +name: Power +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 18 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 56 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 4F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 0D 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 4C 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 19 00 00 00 +command: 0F 00 00 00 +# +# Model: Mivar LCD_TV +# +name: Power +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 0A F5 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 58 A7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 4B B4 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 1F E0 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 1E E1 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 FB 00 00 +command: 0F F0 00 00 +# +# Model: NEC E425 +# +name: Power +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 01 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0C 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 10 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 18 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 1C 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 04 00 00 00 +# +# Model: Neo TV +# +name: Mute +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 16 E9 00 00 +# +# Model: Panasonic 58JX800_Series +# +name: Vol_up +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: 00 02 00 00 +# +name: Vol_dn +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: 10 02 00 00 +# +# Model: Panasonic N2QAYA_152 +# +name: Ch_next +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: 40 03 00 00 +# +name: Mute +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: 20 03 00 00 +# +# Model: Panasonic N2QAYB000705 +# +name: Ch_prev +type: parsed +protocol: Kaseikyo +address: 80 02 20 00 +command: 50 03 00 00 +# +# Model: Panasonic N2QAYB000752_Full +# +name: Ch_prev +type: parsed +protocol: Kaseikyo +address: B0 02 20 00 +command: 50 03 00 00 +# +name: Ch_next +type: parsed +protocol: Kaseikyo +address: B0 02 20 00 +command: 40 03 00 00 +# +# Model: Panasonic TH-43HS550K +# +name: Power +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 0C 00 00 00 +# +name: Vol_up +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 14 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 15 00 00 00 +# +name: Ch_next +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 12 00 00 00 +# +name: Ch_prev +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 3E 00 00 00 +command: 0D 00 00 00 +# +# Model: Philips 22IT_TV_Monitor +# +name: Power +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 01 FE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 0C F3 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 10 EF 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 18 E7 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 1C E3 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 BD 00 00 +command: 04 FB 00 00 +# +# Model: Philips 32PFL4208T +# +name: Ch_next +type: parsed +protocol: RC6 +address: 00 00 00 00 +command: 4C 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC6 +address: 00 00 00 00 +command: 4D 00 00 00 +# +# Model: Philips TV_14PV172_08 +# +name: Ch_next +type: parsed +protocol: RC5X +address: 00 00 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: RC5X +address: 00 00 00 00 +command: 11 00 00 00 +# +name: Vol_up +type: parsed +protocol: RC5X +address: 00 00 00 00 +command: 16 00 00 00 +# +name: Vol_dn +type: parsed +protocol: RC5X +address: 00 00 00 00 +command: 15 00 00 00 +# +# Model: Pioneer Kuro_PDP_LX508A +# +name: Power +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 0A 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 0B 00 00 00 +# +name: Mute +type: parsed +protocol: Pioneer +address: AA 00 00 00 +command: 49 00 00 00 +# +# Model: Samsung +# +name: Ch_next +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 10 00 00 00 +# +# Model: Samsung AA59-00741A +# +name: Ch_prev +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 12 00 00 00 +# +# Model: Samsung BN59-01180A +# +name: Power +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 98 00 00 00 +# +# Model: Samsung Broadband_Hospitality +# +name: Ch_next +type: parsed +protocol: SIRC20 +address: 5A 0E 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: SIRC20 +address: 5A 0E 00 00 +command: 11 00 00 00 +# +# Model: Sanyo DP26640 +# +name: Ch_next +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0A 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0B 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0E 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 0F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 38 00 00 00 +command: 18 00 00 00 +# +# Model: Sceptre 8142026670003C +# +name: Ch_next +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 11 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 14 00 00 00 +# +# Model: Sharp g0684cesa_NES_TV +# +name: Ch_next +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 0C 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 0D 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 0E 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 0F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 28 00 00 00 +command: 10 00 00 00 +# +# Model: Silver LE410004 +# +name: Power +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 0C F3 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 10 EF 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 0E F1 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 00 F7 00 00 +command: 11 EE 00 00 +# +# Model: Soniq QSP500TV6 +# +name: Power +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 10 EF 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 11 EE 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 58 A7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 5B A4 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 5E A1 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 72 DD 00 00 +command: 56 A9 00 00 +# +# Model: Sony RM-V310 +# +name: Vol_up +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 13 00 00 00 +# +name: Mute +type: parsed +protocol: SIRC +address: 10 00 00 00 +command: 14 00 00 00 +# +name: Ch_next +type: parsed +protocol: SIRC +address: 0D 00 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: SIRC +address: 0D 00 00 00 +command: 11 00 00 00 +# +# Model: Sony XBR +# +name: Power +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 2E 00 00 00 +# +# Model: Strong RCU-Z400N +# +name: Power +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 1C 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: A0 00 00 00 +command: 5F 00 00 00 +# +# Model: Sunbrite +# +name: Vol_up +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1F 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1E 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 02 00 00 00 +command: 1C 00 00 00 +# +# Model: SWEEX Generic_Monitor +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 11 00 00 00 +# +# Model: TCL Roku_TV +# +name: Power +type: parsed +protocol: NECext +address: EA C7 00 00 +command: 97 68 00 00 +# +# Model: Vitec Exterity_IPTV +# +name: Power +type: parsed +protocol: NECext +address: AD ED 00 00 +command: B5 4A 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: AD ED 00 00 +command: BA 45 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: AD ED 00 00 +command: BB 44 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: AD ED 00 00 +command: B0 4F 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: AD ED 00 00 +command: B1 4E 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: AD ED 00 00 +command: C5 3A 00 00 From 5b36c5465d0b63f5dda428948345162ae328a9b3 Mon Sep 17 00:00:00 2001 From: Silent Date: Tue, 8 Oct 2024 21:50:31 +0200 Subject: [PATCH 18/36] FuriThread: Improve state callbacks (#3881) State callbacks assumed they were invoked from the thread that changed its state, but this wasn't true for FuriThreadStateStarting in the past, and now it's not true for FuriThreadStateStopped either. Now it is safe to release the thread memory form the state callback once it switches to FuriThreadStateStopped. Therefore, pending deletion calls can be removed. Co-authored-by: Aleksandr Kutuzov --- applications/services/loader/loader.c | 8 ++- applications/services/region/region.c | 15 ++--- applications/services/rpc/rpc.c | 67 +++++++++---------- .../system/updater/util/update_task.c | 8 +-- furi/core/thread.c | 16 ++--- furi/core/thread.h | 6 +- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 2 +- 8 files changed, 58 insertions(+), 66 deletions(-) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index b76b38c25c9..72cac4b626d 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -308,12 +308,14 @@ static void loader_applications_closed_callback(void* context) { furi_message_queue_put(loader->queue, &message, FuriWaitForever); } -static void loader_thread_state_callback(FuriThreadState thread_state, void* context) { +static void + loader_thread_state_callback(FuriThread* thread, FuriThreadState thread_state, void* context) { + UNUSED(thread); furi_assert(context); - Loader* loader = context; - if(thread_state == FuriThreadStateStopped) { + Loader* loader = context; + LoaderMessage message; message.type = LoaderMessageTypeAppClosed; furi_message_queue_put(loader->queue, &message, FuriWaitForever); diff --git a/applications/services/region/region.c b/applications/services/region/region.c index dffcc6b2d5e..bed676f9bd1 100644 --- a/applications/services/region/region.c +++ b/applications/services/region/region.c @@ -104,19 +104,12 @@ static int32_t region_load_file(void* context) { return 0; } -static void region_loader_pending_callback(void* context, uint32_t arg) { - UNUSED(arg); - - FuriThread* loader = context; - furi_thread_join(loader); - furi_thread_free(loader); -} - -static void region_loader_state_callback(FuriThreadState state, void* context) { +static void + region_loader_release_callback(FuriThread* thread, FuriThreadState state, void* context) { UNUSED(context); if(state == FuriThreadStateStopped) { - furi_timer_pending_callback(region_loader_pending_callback, furi_thread_get_current(), 0); + furi_thread_free(thread); } } @@ -126,7 +119,7 @@ static void region_storage_callback(const void* message, void* context) { if(event->type == StorageEventTypeCardMount) { FuriThread* loader = furi_thread_alloc_ex(NULL, 2048, region_load_file, NULL); - furi_thread_set_state_callback(loader, region_loader_state_callback); + furi_thread_set_state_callback(loader, region_loader_release_callback); furi_thread_start(loader); } } diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 00ec2259c75..08a2c3f6ded 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -67,7 +67,7 @@ static RpcSystemCallbacks rpc_systems[] = { struct RpcSession { Rpc* rpc; - FuriThread* thread; + FuriThreadId thread_id; RpcHandlerDict_t handlers; FuriStreamBuffer* stream; @@ -172,7 +172,7 @@ size_t rpc_session_feed( size_t bytes_sent = furi_stream_buffer_send(session->stream, encoded_bytes, size, timeout); - furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtNewData); + furi_thread_flags_set(session->thread_id, RpcEvtNewData); return bytes_sent; } @@ -220,7 +220,7 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { break; } else { /* Save disconnect flag and continue reading buffer */ - furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect); + furi_thread_flags_set(session->thread_id, RpcEvtDisconnect); } } else if(flags & RpcEvtNewData) { // Just wake thread up @@ -347,35 +347,32 @@ static int32_t rpc_session_worker(void* context) { return 0; } -static void rpc_session_thread_pending_callback(void* context, uint32_t arg) { - UNUSED(arg); - RpcSession* session = (RpcSession*)context; +static void rpc_session_thread_release_callback( + FuriThread* thread, + FuriThreadState thread_state, + void* context) { + if(thread_state == FuriThreadStateStopped) { + RpcSession* session = (RpcSession*)context; - for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) { - if(rpc_systems[i].free) { - (rpc_systems[i].free)(session->system_contexts[i]); + for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) { + if(rpc_systems[i].free) { + (rpc_systems[i].free)(session->system_contexts[i]); + } } - } - free(session->system_contexts); - free(session->decoded_message); - RpcHandlerDict_clear(session->handlers); - furi_stream_buffer_free(session->stream); - - furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); - if(session->terminated_callback) { - session->terminated_callback(session->context); - } - furi_mutex_release(session->callbacks_mutex); - - furi_mutex_free(session->callbacks_mutex); - furi_thread_join(session->thread); - furi_thread_free(session->thread); - free(session); -} + free(session->system_contexts); + free(session->decoded_message); + RpcHandlerDict_clear(session->handlers); + furi_stream_buffer_free(session->stream); + + furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); + if(session->terminated_callback) { + session->terminated_callback(session->context); + } + furi_mutex_release(session->callbacks_mutex); -static void rpc_session_thread_state_callback(FuriThreadState thread_state, void* context) { - if(thread_state == FuriThreadStateStopped) { - furi_timer_pending_callback(rpc_session_thread_pending_callback, context, 0); + furi_mutex_free(session->callbacks_mutex); + furi_thread_free(thread); + free(session); } } @@ -407,12 +404,14 @@ RpcSession* rpc_session_open(Rpc* rpc, RpcOwner owner) { }; rpc_add_handler(session, PB_Main_stop_session_tag, &rpc_handler); - session->thread = furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session); + FuriThread* thread = + furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session); + session->thread_id = furi_thread_get_id(thread); - furi_thread_set_state_context(session->thread, session); - furi_thread_set_state_callback(session->thread, rpc_session_thread_state_callback); + furi_thread_set_state_context(thread, session); + furi_thread_set_state_callback(thread, rpc_session_thread_release_callback); - furi_thread_start(session->thread); + furi_thread_start(thread); return session; } @@ -424,7 +423,7 @@ void rpc_session_close(RpcSession* session) { rpc_session_set_send_bytes_callback(session, NULL); rpc_session_set_close_callback(session, NULL); rpc_session_set_buffer_is_empty_callback(session, NULL); - furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect); + furi_thread_flags_set(session->thread_id, RpcEvtDisconnect); } void rpc_on_system_start(void* p) { diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index cca488475ea..9db8339efc1 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -395,14 +395,15 @@ bool update_task_open_file(UpdateTask* update_task, FuriString* filename) { return open_success; } -static void update_task_worker_thread_cb(FuriThreadState state, void* context) { - UpdateTask* update_task = context; +static void + update_task_worker_thread_cb(FuriThread* thread, FuriThreadState state, void* context) { + UNUSED(context); if(state != FuriThreadStateStopped) { return; } - if(furi_thread_get_return_code(update_task->thread) == UPDATE_TASK_NOERR) { + if(furi_thread_get_return_code(thread) == UPDATE_TASK_NOERR) { furi_delay_ms(UPDATE_DELAY_OPERATION_OK); furi_hal_power_reset(); } @@ -427,7 +428,6 @@ UpdateTask* update_task_alloc(void) { furi_thread_alloc_ex("UpdateWorker", 5120, NULL, update_task); furi_thread_set_state_callback(thread, update_task_worker_thread_cb); - furi_thread_set_state_context(thread, update_task); #ifdef FURI_RAM_EXEC UNUSED(update_task_worker_backup_restore); furi_thread_set_callback(thread, update_task_worker_flash_writer); diff --git a/furi/core/thread.c b/furi/core/thread.c index 65787c0e0f7..3990dd63d25 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -33,7 +33,7 @@ struct FuriThread { StaticTask_t container; StackType_t* stack_buffer; - FuriThreadState state; + volatile FuriThreadState state; int32_t ret; FuriThreadCallback callback; @@ -59,7 +59,6 @@ struct FuriThread { // this ensures that the size of this structure is minimal bool is_service; bool heap_trace_enabled; - volatile bool is_active; }; // IMPORTANT: container MUST be the FIRST struct member @@ -84,7 +83,7 @@ static void furi_thread_set_state(FuriThread* thread, FuriThreadState state) { furi_assert(thread); thread->state = state; if(thread->state_callback) { - thread->state_callback(state, thread->state_context); + thread->state_callback(thread, state, thread->state_context); } } @@ -124,7 +123,7 @@ static void furi_thread_body(void* context) { // flush stdout __furi_thread_stdout_flush(thread); - furi_thread_set_state(thread, FuriThreadStateStopped); + furi_thread_set_state(thread, FuriThreadStateStopping); vTaskDelete(NULL); furi_thread_catch(); @@ -207,7 +206,6 @@ void furi_thread_free(FuriThread* thread) { furi_check(thread->is_service == false); // Cannot free a non-joined thread furi_check(thread->state == FuriThreadStateStopped); - furi_check(!thread->is_active); furi_thread_set_name(thread, NULL); furi_thread_set_appid(thread, NULL); @@ -349,8 +347,6 @@ void furi_thread_start(FuriThread* thread) { uint32_t stack_depth = thread->stack_size / sizeof(StackType_t); - thread->is_active = true; - furi_check( xTaskCreateStatic( furi_thread_body, @@ -368,7 +364,7 @@ void furi_thread_cleanup_tcb_event(TaskHandle_t task) { // clear thread local storage vTaskSetThreadLocalStoragePointer(task, 0, NULL); furi_check(thread == (FuriThread*)task); - thread->is_active = false; + furi_thread_set_state(thread, FuriThreadStateStopped); } } @@ -383,8 +379,8 @@ bool furi_thread_join(FuriThread* thread) { // // If your thread exited, but your app stuck here: some other thread uses // all cpu time, which delays kernel from releasing task handle - while(thread->is_active) { - furi_delay_ms(10); + while(thread->state != FuriThreadStateStopped) { + furi_delay_tick(2); } return true; diff --git a/furi/core/thread.h b/furi/core/thread.h index d90ece85d6e..c320fdbc10d 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -21,7 +21,8 @@ extern "C" { * Many of the FuriThread functions MUST ONLY be called when the thread is STOPPED. */ typedef enum { - FuriThreadStateStopped, /**< Thread is stopped */ + FuriThreadStateStopped, /**< Thread is stopped and is safe to release */ + FuriThreadStateStopping, /**< Thread is stopping */ FuriThreadStateStarting, /**< Thread is starting */ FuriThreadStateRunning, /**< Thread is running */ } FuriThreadState; @@ -80,10 +81,11 @@ typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); * * The function to be used as a state callback MUST follow this signature. * + * @param[in] pointer to the FuriThread instance that changed the state * @param[in] state identifier of the state the thread has transitioned to * @param[in,out] context pointer to a user-specified object */ -typedef void (*FuriThreadStateCallback)(FuriThreadState state, void* context); +typedef void (*FuriThreadStateCallback)(FuriThread* thread, FuriThreadState state, void* context); /** * @brief Signal handler callback function pointer type. diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index e808f0748d5..7e612de8620 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,75.0,, +Version,+,76.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7317f7f0471..02e184a8a20 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,75.0,, +Version,+,76.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, From 344118c3463ecb0019812f75f2129c7d77ef9762 Mon Sep 17 00:00:00 2001 From: ted-logan Date: Wed, 9 Oct 2024 02:47:19 -0700 Subject: [PATCH 19/36] nfc/clipper: Update BART station codes (#3937) In the NFC Clipper card plugin, update the BART station codes for two newer East Bay stations (Milpitas, and Berryessa/North San Jose), and correct the station code for Castro Valley. These station ids come from visiting the stations and checking what id they presented as in the Clipper card data. --- applications/main/nfc/plugins/supported_cards/clipper.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/plugins/supported_cards/clipper.c b/applications/main/nfc/plugins/supported_cards/clipper.c index 04a2afcda30..35d0c70390d 100644 --- a/applications/main/nfc/plugins/supported_cards/clipper.c +++ b/applications/main/nfc/plugins/supported_cards/clipper.c @@ -106,7 +106,7 @@ static const IdMapping bart_zones[] = { {.id = 0x0023, .name = "South Hayward"}, {.id = 0x0024, .name = "Union City"}, {.id = 0x0025, .name = "Fremont"}, - {.id = 0x0026, .name = "Daly City(2)?"}, + {.id = 0x0026, .name = "Castro Valley"}, {.id = 0x0027, .name = "Dublin/Pleasanton"}, {.id = 0x0028, .name = "South San Francisco"}, {.id = 0x0029, .name = "San Bruno"}, @@ -115,6 +115,8 @@ static const IdMapping bart_zones[] = { {.id = 0x002c, .name = "West Dublin/Pleasanton"}, {.id = 0x002d, .name = "OAK Airport"}, {.id = 0x002e, .name = "Warm Springs/South Fremont"}, + {.id = 0x002f, .name = "Milpitas"}, + {.id = 0x0030, .name = "Berryessa/North San Jose"}, }; static const size_t kNumBARTZones = COUNT(bart_zones); From 5190aace88bba125383281f7f51816d911ad4ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 14 Oct 2024 14:39:09 +0100 Subject: [PATCH 20/36] Furi: A Lot of Fixes (#3942) - BT Service: cleanup code - Dialog: correct release order in file browser - Rpc: rollback to pre #3881 state - Kernel: fix inverted behavior in furi_kernel_is_running - Log: properly take mutex when kernel is not running - Thread: rework tread control block scrubbing procedure, ensure that we don't do stupid things in idle task, add new priority for init task - Timer: add control queue flush method, force flush on stop - Furi: system init task now performs thread scrubbing - BleGlue: add some extra checks - FreeRTOSConfig: fix bunch of issues that were preventing configuration from being properly applied and cleanup --- applications/services/bt/bt_service/bt.c | 20 +++--- .../dialogs/dialogs_module_file_browser.c | 5 +- applications/services/rpc/rpc.c | 69 ++++++++++--------- furi/core/kernel.c | 5 +- furi/core/log.c | 17 +++-- furi/core/thread.c | 44 ++++++++---- furi/core/thread.h | 21 +++--- furi/core/thread_i.h | 7 ++ furi/core/timer.c | 39 +++++++---- furi/core/timer.h | 9 ++- furi/furi.c | 7 ++ furi/furi.h | 2 + targets/f18/api_symbols.csv | 4 +- targets/f7/api_symbols.csv | 4 +- targets/f7/ble_glue/ble_glue.c | 11 ++- targets/f7/ble_glue/gap.c | 28 ++++---- targets/f7/furi_hal/furi_hal_spi.c | 2 +- targets/f7/inc/FreeRTOSConfig.h | 29 ++++---- targets/f7/src/main.c | 5 +- 19 files changed, 205 insertions(+), 123 deletions(-) create mode 100644 furi/core/thread_i.h diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index d72e745ee32..2eeeab2868b 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -430,13 +430,11 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { *message->profile_instance = NULL; } } - if(message->lock) api_lock_unlock(message->lock); } -static void bt_close_connection(Bt* bt, BtMessage* message) { +static void bt_close_connection(Bt* bt) { bt_close_rpc_connection(bt); furi_hal_bt_stop_advertising(); - if(message->lock) api_lock_unlock(message->lock); } static void bt_apply_settings(Bt* bt) { @@ -484,19 +482,13 @@ static void bt_load_settings(Bt* bt) { } static void bt_handle_get_settings(Bt* bt, BtMessage* message) { - furi_assert(message->lock); *message->data.settings = bt->bt_settings; - api_lock_unlock(message->lock); } static void bt_handle_set_settings(Bt* bt, BtMessage* message) { - furi_assert(message->lock); bt->bt_settings = *message->data.csettings; - bt_apply_settings(bt); bt_settings_save(&bt->bt_settings); - - api_lock_unlock(message->lock); } static void bt_handle_reload_keys_settings(Bt* bt) { @@ -548,6 +540,12 @@ int32_t bt_srv(void* p) { while(1) { furi_check( furi_message_queue_get(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); + FURI_LOG_D( + TAG, + "call %d, lock 0x%p, result 0x%p", + message.type, + (void*)message.lock, + (void*)message.result); if(message.type == BtMessageTypeUpdateStatus) { // Update view ports bt_statusbar_update(bt); @@ -571,7 +569,7 @@ int32_t bt_srv(void* p) { } else if(message.type == BtMessageTypeSetProfile) { bt_change_profile(bt, &message); } else if(message.type == BtMessageTypeDisconnect) { - bt_close_connection(bt, &message); + bt_close_connection(bt); } else if(message.type == BtMessageTypeForgetBondedDevices) { bt_keys_storage_delete(bt->keys_storage); } else if(message.type == BtMessageTypeGetSettings) { @@ -581,6 +579,8 @@ int32_t bt_srv(void* p) { } else if(message.type == BtMessageTypeReloadKeysSettings) { bt_handle_reload_keys_settings(bt); } + + if(message.lock) api_lock_unlock(message.lock); } return 0; diff --git a/applications/services/dialogs/dialogs_module_file_browser.c b/applications/services/dialogs/dialogs_module_file_browser.c index 12a7439e609..603c27cff82 100644 --- a/applications/services/dialogs/dialogs_module_file_browser.c +++ b/applications/services/dialogs/dialogs_module_file_browser.c @@ -54,11 +54,14 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow ret = file_browser_context->result; view_holder_set_view(view_holder, NULL); - view_holder_free(view_holder); file_browser_stop(file_browser); + file_browser_free(file_browser); + view_holder_free(view_holder); + api_lock_free(file_browser_context->lock); free(file_browser_context); + furi_record_close(RECORD_GUI); return ret; diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 08a2c3f6ded..41d55841ef0 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -67,7 +67,7 @@ static RpcSystemCallbacks rpc_systems[] = { struct RpcSession { Rpc* rpc; - FuriThreadId thread_id; + FuriThread* thread; RpcHandlerDict_t handlers; FuriStreamBuffer* stream; @@ -172,7 +172,7 @@ size_t rpc_session_feed( size_t bytes_sent = furi_stream_buffer_send(session->stream, encoded_bytes, size, timeout); - furi_thread_flags_set(session->thread_id, RpcEvtNewData); + furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtNewData); return bytes_sent; } @@ -220,7 +220,7 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { break; } else { /* Save disconnect flag and continue reading buffer */ - furi_thread_flags_set(session->thread_id, RpcEvtDisconnect); + furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect); } } else if(flags & RpcEvtNewData) { // Just wake thread up @@ -347,32 +347,37 @@ static int32_t rpc_session_worker(void* context) { return 0; } -static void rpc_session_thread_release_callback( - FuriThread* thread, - FuriThreadState thread_state, - void* context) { - if(thread_state == FuriThreadStateStopped) { - RpcSession* session = (RpcSession*)context; +static void rpc_session_thread_pending_callback(void* context, uint32_t arg) { + UNUSED(arg); + RpcSession* session = (RpcSession*)context; - for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) { - if(rpc_systems[i].free) { - (rpc_systems[i].free)(session->system_contexts[i]); - } - } - free(session->system_contexts); - free(session->decoded_message); - RpcHandlerDict_clear(session->handlers); - furi_stream_buffer_free(session->stream); - - furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); - if(session->terminated_callback) { - session->terminated_callback(session->context); + for(size_t i = 0; i < COUNT_OF(rpc_systems); ++i) { + if(rpc_systems[i].free) { + (rpc_systems[i].free)(session->system_contexts[i]); } - furi_mutex_release(session->callbacks_mutex); + } + free(session->system_contexts); + free(session->decoded_message); + RpcHandlerDict_clear(session->handlers); + furi_stream_buffer_free(session->stream); + + furi_mutex_acquire(session->callbacks_mutex, FuriWaitForever); + if(session->terminated_callback) { + session->terminated_callback(session->context); + } + furi_mutex_release(session->callbacks_mutex); + + furi_mutex_free(session->callbacks_mutex); + furi_thread_join(session->thread); + furi_thread_free(session->thread); + free(session); +} - furi_mutex_free(session->callbacks_mutex); - furi_thread_free(thread); - free(session); +static void + rpc_session_thread_state_callback(FuriThread* thread, FuriThreadState state, void* context) { + UNUSED(thread); + if(state == FuriThreadStateStopped) { + furi_timer_pending_callback(rpc_session_thread_pending_callback, context, 0); } } @@ -404,14 +409,12 @@ RpcSession* rpc_session_open(Rpc* rpc, RpcOwner owner) { }; rpc_add_handler(session, PB_Main_stop_session_tag, &rpc_handler); - FuriThread* thread = - furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session); - session->thread_id = furi_thread_get_id(thread); + session->thread = furi_thread_alloc_ex("RpcSessionWorker", 3072, rpc_session_worker, session); - furi_thread_set_state_context(thread, session); - furi_thread_set_state_callback(thread, rpc_session_thread_release_callback); + furi_thread_set_state_context(session->thread, session); + furi_thread_set_state_callback(session->thread, rpc_session_thread_state_callback); - furi_thread_start(thread); + furi_thread_start(session->thread); return session; } @@ -423,7 +426,7 @@ void rpc_session_close(RpcSession* session) { rpc_session_set_send_bytes_callback(session, NULL); rpc_session_set_close_callback(session, NULL); rpc_session_set_buffer_is_empty_callback(session, NULL); - furi_thread_flags_set(session->thread_id, RpcEvtDisconnect); + furi_thread_flags_set(furi_thread_get_id(session->thread), RpcEvtDisconnect); } void rpc_on_system_start(void* p) { diff --git a/furi/core/kernel.c b/furi/core/kernel.c index f3f84e692e4..34c562bb34e 100644 --- a/furi/core/kernel.c +++ b/furi/core/kernel.c @@ -33,7 +33,7 @@ bool furi_kernel_is_irq_or_masked(void) { } bool furi_kernel_is_running(void) { - return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING; + return xTaskGetSchedulerState() == taskSCHEDULER_RUNNING; } int32_t furi_kernel_lock(void) { @@ -129,6 +129,8 @@ uint32_t furi_kernel_get_tick_frequency(void) { void furi_delay_tick(uint32_t ticks) { furi_check(!furi_kernel_is_irq_or_masked()); + furi_check(furi_thread_get_current_id() != xTaskGetIdleTaskHandle()); + if(ticks == 0U) { taskYIELD(); } else { @@ -138,6 +140,7 @@ void furi_delay_tick(uint32_t ticks) { FuriStatus furi_delay_until_tick(uint32_t tick) { furi_check(!furi_kernel_is_irq_or_masked()); + furi_check(furi_thread_get_current_id() != xTaskGetIdleTaskHandle()); TickType_t tcnt, delay; FuriStatus stat; diff --git a/furi/core/log.c b/furi/core/log.c index f8110b46ac7..fb0c9671136 100644 --- a/furi/core/log.c +++ b/furi/core/log.c @@ -108,10 +108,17 @@ void furi_log_puts(const char* data) { } void furi_log_print_format(FuriLogLevel level, const char* tag, const char* format, ...) { - if(level <= furi_log.log_level && - furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk) { - FuriString* string; - string = furi_string_alloc(); + do { + if(level > furi_log.log_level) { + break; + } + + if(furi_mutex_acquire(furi_log.mutex, furi_kernel_is_running() ? FuriWaitForever : 0) != + FuriStatusOk) { + break; + } + + FuriString* string = furi_string_alloc(); const char* color = _FURI_LOG_CLR_RESET; const char* log_letter = " "; @@ -157,7 +164,7 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form furi_log_puts("\r\n"); furi_mutex_release(furi_log.mutex); - } + } while(0); } void furi_log_print_raw_format(FuriLogLevel level, const char* format, ...) { diff --git a/furi/core/thread.c b/furi/core/thread.c index 3990dd63d25..fd576ea72bb 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -1,6 +1,7 @@ -#include "thread.h" +#include "thread_i.h" #include "thread_list_i.h" #include "kernel.h" +#include "message_queue.h" #include "memmgr.h" #include "memmgr_heap.h" #include "check.h" @@ -67,6 +68,8 @@ static_assert(offsetof(FuriThread, container) == 0); // Our idle priority should be equal to the one from FreeRTOS static_assert(FuriThreadPriorityIdle == tskIDLE_PRIORITY); +static FuriMessageQueue* furi_thread_scrub_message_queue = NULL; + static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size); static int32_t __furi_thread_stdout_flush(FuriThread* thread); @@ -125,7 +128,9 @@ static void furi_thread_body(void* context) { furi_thread_set_state(thread, FuriThreadStateStopping); - vTaskDelete(NULL); + furi_message_queue_put(furi_thread_scrub_message_queue, &thread, FuriWaitForever); + + vTaskSuspend(NULL); furi_thread_catch(); } @@ -159,6 +164,31 @@ static void furi_thread_init_common(FuriThread* thread) { } } +void furi_thread_init(void) { + furi_thread_scrub_message_queue = furi_message_queue_alloc(8, sizeof(FuriThread*)); +} + +void furi_thread_scrub(void) { + FuriThread* thread_to_scrub = NULL; + while(true) { + furi_check( + furi_message_queue_get( + furi_thread_scrub_message_queue, &thread_to_scrub, FuriWaitForever) == + FuriStatusOk); + + TaskHandle_t task = (TaskHandle_t)thread_to_scrub; + + // Delete task: FreeRTOS will remove task from all lists where it may be + vTaskDelete(task); + // Sanity check: ensure that local storage is ours and clear it + furi_check(pvTaskGetThreadLocalStoragePointer(task, 0) == thread_to_scrub); + vTaskSetThreadLocalStoragePointer(task, 0, NULL); + + // Deliver thread stopped callback + furi_thread_set_state(thread_to_scrub, FuriThreadStateStopped); + } +} + FuriThread* furi_thread_alloc(void) { FuriThread* thread = malloc(sizeof(FuriThread)); @@ -358,16 +388,6 @@ void furi_thread_start(FuriThread* thread) { &thread->container) == (TaskHandle_t)thread); } -void furi_thread_cleanup_tcb_event(TaskHandle_t task) { - FuriThread* thread = pvTaskGetThreadLocalStoragePointer(task, 0); - if(thread) { - // clear thread local storage - vTaskSetThreadLocalStoragePointer(task, 0, NULL); - furi_check(thread == (FuriThread*)task); - furi_thread_set_state(thread, FuriThreadStateStopped); - } -} - bool furi_thread_join(FuriThread* thread) { furi_check(thread); // Cannot join a service thread diff --git a/furi/core/thread.h b/furi/core/thread.h index c320fdbc10d..ed7aa4553b0 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -21,10 +21,10 @@ extern "C" { * Many of the FuriThread functions MUST ONLY be called when the thread is STOPPED. */ typedef enum { - FuriThreadStateStopped, /**< Thread is stopped and is safe to release */ - FuriThreadStateStopping, /**< Thread is stopping */ - FuriThreadStateStarting, /**< Thread is starting */ - FuriThreadStateRunning, /**< Thread is running */ + FuriThreadStateStopped, /**< Thread is stopped and is safe to release. Event delivered from system init thread(TCB cleanup routine). It is safe to release thread instance. */ + FuriThreadStateStopping, /**< Thread is stopping. Event delivered from child thread. */ + FuriThreadStateStarting, /**< Thread is starting. Event delivered from parent(self) thread. */ + FuriThreadStateRunning, /**< Thread is running. Event delivered from child thread. */ } FuriThreadState; /** @@ -32,6 +32,7 @@ typedef enum { */ typedef enum { FuriThreadPriorityIdle = 0, /**< Idle priority */ + FuriThreadPriorityInit = 4, /**< Init System Thread Priority */ FuriThreadPriorityLowest = 14, /**< Lowest */ FuriThreadPriorityLow = 15, /**< Low */ FuriThreadPriorityNormal = 16, /**< Normal, system default */ @@ -77,13 +78,15 @@ typedef int32_t (*FuriThreadCallback)(void* context); typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); /** - * @brief State change callback function pointer type. + * @brief State change callback function pointer type. * - * The function to be used as a state callback MUST follow this signature. + * The function to be used as a state callback MUST follow this + * signature. * - * @param[in] pointer to the FuriThread instance that changed the state - * @param[in] state identifier of the state the thread has transitioned to - * @param[in,out] context pointer to a user-specified object + * @param[in] thread to the FuriThread instance that changed the state + * @param[in] state identifier of the state the thread has transitioned + * to + * @param[in,out] context pointer to a user-specified object */ typedef void (*FuriThreadStateCallback)(FuriThread* thread, FuriThreadState state, void* context); diff --git a/furi/core/thread_i.h b/furi/core/thread_i.h new file mode 100644 index 00000000000..c6b12a78027 --- /dev/null +++ b/furi/core/thread_i.h @@ -0,0 +1,7 @@ +#pragma once + +#include "thread.h" + +void furi_thread_init(void); + +void furi_thread_scrub(void); diff --git a/furi/core/timer.c b/furi/core/timer.c index 01adbeb89e8..ddd82e33194 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -17,12 +17,24 @@ static_assert(offsetof(FuriTimer, container) == 0); #define TIMER_DELETED_EVENT (1U << 0) -static void TimerCallback(TimerHandle_t hTimer) { +static void furi_timer_callback(TimerHandle_t hTimer) { FuriTimer* instance = pvTimerGetTimerID(hTimer); furi_check(instance); instance->cb_func(instance->cb_context); } +static void furi_timer_flush_epilogue(void* context, uint32_t arg) { + furi_assert(context); + UNUSED(arg); + + EventGroupHandle_t hEvent = context; + + // See https://github.com/FreeRTOS/FreeRTOS-Kernel/issues/1142 + vTaskSuspendAll(); + xEventGroupSetBits(hEvent, TIMER_DELETED_EVENT); + (void)xTaskResumeAll(); +} + FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* context) { furi_check((furi_kernel_is_irq_or_masked() == 0U) && (func != NULL)); @@ -33,23 +45,13 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co const UBaseType_t reload = (type == FuriTimerTypeOnce ? pdFALSE : pdTRUE); const TimerHandle_t hTimer = xTimerCreateStatic( - NULL, portMAX_DELAY, reload, instance, TimerCallback, &instance->container); + NULL, portMAX_DELAY, reload, instance, furi_timer_callback, &instance->container); furi_check(hTimer == (TimerHandle_t)instance); return instance; } -static void furi_timer_epilogue(void* context, uint32_t arg) { - furi_assert(context); - UNUSED(arg); - - EventGroupHandle_t hEvent = context; - vTaskSuspendAll(); - xEventGroupSetBits(hEvent, TIMER_DELETED_EVENT); - (void)xTaskResumeAll(); -} - void furi_timer_free(FuriTimer* instance) { furi_check(!furi_kernel_is_irq_or_masked()); furi_check(instance); @@ -57,16 +59,21 @@ void furi_timer_free(FuriTimer* instance) { TimerHandle_t hTimer = (TimerHandle_t)instance; furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); + furi_timer_flush(); + + free(instance); +} + +void furi_timer_flush(void) { StaticEventGroup_t event_container = {}; EventGroupHandle_t hEvent = xEventGroupCreateStatic(&event_container); - furi_check(xTimerPendFunctionCall(furi_timer_epilogue, hEvent, 0, portMAX_DELAY) == pdPASS); + furi_check( + xTimerPendFunctionCall(furi_timer_flush_epilogue, hEvent, 0, portMAX_DELAY) == pdPASS); furi_check( xEventGroupWaitBits(hEvent, TIMER_DELETED_EVENT, pdFALSE, pdTRUE, portMAX_DELAY) == TIMER_DELETED_EVENT); vEventGroupDelete(hEvent); - - free(instance); } FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { @@ -112,6 +119,8 @@ FuriStatus furi_timer_stop(FuriTimer* instance) { furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); + furi_timer_flush(); + return FuriStatusOk; } diff --git a/furi/core/timer.h b/furi/core/timer.h index 00056db18eb..9d8df3dc620 100644 --- a/furi/core/timer.h +++ b/furi/core/timer.h @@ -35,6 +35,12 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co */ void furi_timer_free(FuriTimer* instance); +/** Flush timer task control message queue + * + * Ensures that all commands before this point was processed. + */ +void furi_timer_flush(void); + /** Start timer * * @warning This is asynchronous call, real operation will happen as soon as @@ -61,8 +67,7 @@ FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks); /** Stop timer * - * @warning This is asynchronous call, real operation will happen as soon as - * timer service process this request. + * @warning This is synchronous call that will be blocked till timer queue processed. * * @param instance The pointer to FuriTimer instance * diff --git a/furi/furi.c b/furi/furi.c index f4e64ee099b..bc7452f130e 100644 --- a/furi/furi.c +++ b/furi/furi.c @@ -1,5 +1,7 @@ #include "furi.h" +#include "core/thread_i.h" + #include #include @@ -7,6 +9,7 @@ void furi_init(void) { furi_check(!furi_kernel_is_irq_or_masked()); furi_check(xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED); + furi_thread_init(); furi_log_init(); furi_record_init(); } @@ -18,3 +21,7 @@ void furi_run(void) { /* Start the kernel scheduler */ vTaskStartScheduler(); } + +void furi_background(void) { + furi_thread_scrub(); +} diff --git a/furi/furi.h b/furi/furi.h index d75debe9876..6ddf2857757 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -35,6 +35,8 @@ void furi_init(void); void furi_run(void); +void furi_background(void); + #ifdef __cplusplus } #endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7e612de8620..2b04e3e4039 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,76.0,, +Version,+,77.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1102,6 +1102,7 @@ Function,-,ftello,off_t,FILE* Function,-,ftrylockfile,int,FILE* Function,-,funlockfile,void,FILE* Function,-,funopen,FILE*,"const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" +Function,-,furi_background,void, Function,+,furi_delay_ms,void,uint32_t Function,+,furi_delay_tick,void,uint32_t Function,+,furi_delay_until_tick,FuriStatus,uint32_t @@ -1672,6 +1673,7 @@ Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" +Function,+,furi_timer_flush,void, Function,+,furi_timer_free,void,FuriTimer* Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 02e184a8a20..71d9213d85d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,76.0,, +Version,+,77.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1207,6 +1207,7 @@ Function,-,ftello,off_t,FILE* Function,-,ftrylockfile,int,FILE* Function,-,funlockfile,void,FILE* Function,-,funopen,FILE*,"const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" +Function,-,furi_background,void, Function,+,furi_delay_ms,void,uint32_t Function,+,furi_delay_tick,void,uint32_t Function,+,furi_delay_until_tick,FuriStatus,uint32_t @@ -1886,6 +1887,7 @@ Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId Function,+,furi_thread_yield,void, Function,+,furi_timer_alloc,FuriTimer*,"FuriTimerCallback, FuriTimerType, void*" +Function,+,furi_timer_flush,void, Function,+,furi_timer_free,void,FuriTimer* Function,+,furi_timer_get_expire_time,uint32_t,FuriTimer* Function,+,furi_timer_is_running,uint32_t,FuriTimer* diff --git a/targets/f7/ble_glue/ble_glue.c b/targets/f7/ble_glue/ble_glue.c index 73bb41badc8..fe101b2c931 100644 --- a/targets/f7/ble_glue/ble_glue.c +++ b/targets/f7/ble_glue/ble_glue.c @@ -87,6 +87,8 @@ void ble_glue_init(void) { TL_Init(); ble_glue->shci_mtx = furi_mutex_alloc(FuriMutexTypeNormal); + // Take mutex, SHCI will release it in most unusual way later + furi_check(furi_mutex_acquire(ble_glue->shci_mtx, FuriWaitForever) == FuriStatusOk); // FreeRTOS system task creation ble_event_thread_start(); @@ -248,7 +250,9 @@ void ble_glue_stop(void) { ble_event_thread_stop(); // Free resources furi_mutex_free(ble_glue->shci_mtx); + ble_glue->shci_mtx = NULL; furi_timer_free(ble_glue->hardfault_check_timer); + ble_glue->hardfault_check_timer = NULL; ble_glue_clear_shared_memory(); free(ble_glue); @@ -309,10 +313,13 @@ BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode desired_mode) { static void ble_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { switch(status) { case SHCI_TL_CmdBusy: - furi_mutex_acquire(ble_glue->shci_mtx, FuriWaitForever); + furi_check( + furi_mutex_acquire( + ble_glue->shci_mtx, furi_kernel_is_running() ? FuriWaitForever : 0) == + FuriStatusOk); break; case SHCI_TL_CmdAvailable: - furi_mutex_release(ble_glue->shci_mtx); + furi_check(furi_mutex_release(ble_glue->shci_mtx) == FuriStatusOk); break; default: break; diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 9cd33456b41..732440ccf90 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -129,7 +129,7 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data; furi_check(gap); - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); switch(event_pckt->evt) { case HCI_DISCONNECTION_COMPLETE_EVT_CODE: { @@ -304,7 +304,7 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { break; } - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); return BleEventFlowEnable; } @@ -490,7 +490,7 @@ static void gap_advertise_stop(void) { } void gap_start_advertising(void) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); if(gap->state == GapStateIdle) { gap->state = GapStateStartingAdv; FURI_LOG_I(TAG, "Start advertising"); @@ -498,18 +498,18 @@ void gap_start_advertising(void) { GapCommand command = GapCommandAdvFast; furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); } - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); } void gap_stop_advertising(void) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); if(gap->state > GapStateIdle) { FURI_LOG_I(TAG, "Stop advertising"); gap->enable_adv = false; GapCommand command = GapCommandAdvStop; furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk); } - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); } static void gap_advetise_timer_callback(void* context) { @@ -566,9 +566,9 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { GapState gap_get_state(void) { GapState state; if(gap) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); state = gap->state; - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); } else { state = GapStateUninitialized; } @@ -577,17 +577,21 @@ GapState gap_get_state(void) { void gap_thread_stop(void) { if(gap) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); gap->enable_adv = false; GapCommand command = GapCommandKillThread; furi_message_queue_put(gap->command_queue, &command, FuriWaitForever); - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); furi_thread_join(gap->thread); furi_thread_free(gap->thread); + gap->thread = NULL; // Free resources furi_mutex_free(gap->state_mutex); + gap->state_mutex = NULL; furi_message_queue_free(gap->command_queue); + gap->command_queue = NULL; furi_timer_free(gap->advertise_timer); + gap->advertise_timer = NULL; ble_event_dispatcher_reset(); free(gap); @@ -604,7 +608,7 @@ static int32_t gap_app(void* context) { FURI_LOG_E(TAG, "Message queue get error: %d", status); continue; } - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + furi_check(furi_mutex_acquire(gap->state_mutex, FuriWaitForever) == FuriStatusOk); if(command == GapCommandKillThread) { break; } @@ -615,7 +619,7 @@ static int32_t gap_app(void* context) { } else if(command == GapCommandAdvStop) { gap_advertise_stop(); } - furi_mutex_release(gap->state_mutex); + furi_check(furi_mutex_release(gap->state_mutex) == FuriStatusOk); } return 0; diff --git a/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c index 49bcd48a1ef..2a7cb7c25f4 100644 --- a/targets/f7/furi_hal/furi_hal_spi.c +++ b/targets/f7/furi_hal/furi_hal_spi.c @@ -202,7 +202,7 @@ bool furi_hal_spi_bus_trx_dma( furi_check(size > 0); // If scheduler is not running, use blocking mode - if(furi_kernel_is_running()) { + if(!furi_kernel_is_running()) { return furi_hal_spi_bus_trx(handle, tx_buffer, rx_buffer, size, timeout_ms); } diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 82cda2c6da7..357019ea233 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -84,6 +84,7 @@ to exclude the API function. */ #define INCLUDE_xTaskGetCurrentTaskHandle 1 #define INCLUDE_xTaskGetSchedulerState 1 #define INCLUDE_xTimerPendFunctionCall 1 +#define INCLUDE_xTaskGetIdleTaskHandle 1 /* Workaround for various notification issues: * - First one used by system primitives @@ -129,25 +130,11 @@ See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY \ (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) -/* Normal assert() semantics without relying on the provision of an assert.h -header file. */ -#ifdef DEBUG -#include -#define configASSERT(x) \ - if((x) == 0) { \ - furi_crash("FreeRTOS Assert"); \ - } -#endif - /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS standard names. */ #define vPortSVCHandler SVC_Handler #define xPortPendSVHandler PendSV_Handler -#define USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION 1 -#define configOVERRIDE_DEFAULT_TICK_CONFIGURATION \ - 1 /* required only for Keil but does not hurt otherwise */ - #define traceTASK_SWITCHED_IN() \ extern void furi_hal_mpu_set_stack_protection(uint32_t* stack); \ furi_hal_mpu_set_stack_protection((uint32_t*)pxCurrentTCB->pxStack); \ @@ -157,6 +144,14 @@ standard names. */ // referencing `FreeRTOS_errno' here vvvvv because FreeRTOS calls our hook _before_ copying the value into the TCB, hence a manual write to the TCB would get overwritten #define traceTASK_SWITCHED_OUT() FreeRTOS_errno = errno -#define portCLEAN_UP_TCB(pxTCB) \ - extern void furi_thread_cleanup_tcb_event(TaskHandle_t task); \ - furi_thread_cleanup_tcb_event(pxTCB) +/* Normal assert() semantics without relying on the provision of an assert.h +header file. */ +#ifdef DEBUG +#define configASSERT(x) \ + if((x) == 0) { \ + furi_crash("FreeRTOS Assert"); \ + } +#endif + +// Must be last line of config because of recursion +#include diff --git a/targets/f7/src/main.c b/targets/f7/src/main.c index 77961f0eff8..21775f19a24 100644 --- a/targets/f7/src/main.c +++ b/targets/f7/src/main.c @@ -15,6 +15,8 @@ int32_t init_task(void* context) { // Init flipper flipper_init(); + furi_background(); + return 0; } @@ -25,7 +27,8 @@ int main(void) { // Flipper critical FURI HAL furi_hal_init_early(); - FuriThread* main_thread = furi_thread_alloc_ex("Init", 4096, init_task, NULL); + FuriThread* main_thread = furi_thread_alloc_ex("InitSrv", 1024, init_task, NULL); + furi_thread_set_priority(main_thread, FuriThreadPriorityInit); #ifdef FURI_RAM_EXEC // Prevent entering sleep mode when executed from RAM From 421bd3e1f9484ce1100fcba6a50f12cc8fbe74fc Mon Sep 17 00:00:00 2001 From: Ruslan Nadyrshin <110516632+rnadyrshin@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:47:12 +0400 Subject: [PATCH 21/36] Wi-Fi Devboard documentation rework (#3944) * New step-by-step documentation structure for Wi-Fi Devboard * Added a description of working under Windows * Added a description of switching Devboard operation mode (Black Magic, DAPLink) * The images for the documentation are uploaded to the CDN * The text in the sidebar, near the dolphin logo, changed from blue to black/white Co-authored-by: knrn64 <25254561+knrn64@users.noreply.github.com> Co-authored-by: Aleksandr Kutuzov --- .../devboard/Debugging via the Devboard.md | 88 ++++++++++ .../devboard/Devboard debug modes.md | 33 ++++ .../Firmware update on Developer Board.md | 114 ++++++------ .../Get started with the Dev Board.md | 162 ++++-------------- .../Reading logs via the Dev Board.md | 18 +- .../USB connection to the Devboard.md | 22 +++ .../Wi-Fi connection to the Devboard.md | 60 +++++++ documentation/doxygen/dev_board.dox | 33 +++- documentation/doxygen/header.html | 2 +- documentation/fbt.md | 2 +- 10 files changed, 328 insertions(+), 206 deletions(-) create mode 100644 documentation/devboard/Debugging via the Devboard.md create mode 100644 documentation/devboard/Devboard debug modes.md create mode 100644 documentation/devboard/USB connection to the Devboard.md create mode 100644 documentation/devboard/Wi-Fi connection to the Devboard.md diff --git a/documentation/devboard/Debugging via the Devboard.md b/documentation/devboard/Debugging via the Devboard.md new file mode 100644 index 00000000000..49888cdbcb4 --- /dev/null +++ b/documentation/devboard/Debugging via the Devboard.md @@ -0,0 +1,88 @@ +# Debugging via the Devboard {#dev_board_debugging_guide} + +On this page, you'll learn about how debugging via the Wi-Fi Developer Board works. To illustrate this process, we'll start a debug session for Flipper Zero's firmware in VS Code using the native Flipper Build Tool. + +*** + +## Overview + +The Developer Board acts as the debug probe, which provides a bridge between the IDE (integrated development environment) with a debugger running on a host computer and the target microcontroller (in your Flipper Zero). The user controls the debugging process on the computer connected to the Developer Board via [Wi-Fi](#dev_board_wifi_connection) or [USB cable](#dev_board_usb_connection). + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_hardware_CDN.jpg width=700 + +Data exchange between the Wi-Fi Developer Board and your Flipper Zero is conducted via the Serial Wire Debug interface. The following GPIO pins serve this purpose: + +- **Pin 10:** Serial Wire Clock (SWCLK) + +- **Pin 12:** Serial Wire Debug Data I/O (SWDIO) + +To learn more about Flipper Zero pinout, visit [GPIO & modules in Flipper Docs](https://docs.flipper.net/gpio-and-modules). + +*** + +## Prerequisites + +### Step 1. Installing Git + +You'll need Git installed on your computer to clone the firmware repository. If you don't have Git, install it following the [official installation guide](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). + +### Step 2. Building the firmware + +Before starting debugging, you need to clone and build Flipper Zero firmware: + +1. Open the **Terminal** (on Linux & macOS) or **PowerShell** (on Windows) in the directory where you want to store the firmware source code. + +2. Clone the firmware repository: + + ``` + git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git + cd flipperzero-firmware + ``` + +3. Run the **Flipper Build Tool (FBT)** to build the firmware: + + ``` + ./fbt + ``` + +*** + +## Debugging the firmware + +From the **flipperzero-firmware** directory that you cloned earlier, run the following command: + +``` +./fbt flash +``` + +This will upload the firmware you've just built to your Flipper Zero via the Developer Board. After that, you can start debugging the firmware. We recommend using **VS Code** with the recommended extensions (as described below), and we have pre-made configurations for it. + +To debug in **VS Code**, do the following: + +1. In VS Code, open the **flipperzero-firmware** directory. + +2. You should see a notification about recommended extensions. Install them. + + If there were no notifications, open the **Extensions** tab, enter `@recommended` in the search bar, and install the workspace recommendations. + +3. Run the `./fbt vscode_dist` command. This will generate the VS Code configuration files needed for debugging. + +4. In VS Code, open the **Run and Debug** tab and select a debugger from the dropdown menu: + + - **Attach FW (blackmagic):** Can be used via **Wi-Fi** or **USB** + - **Attach FW (DAP):** Can be used via **USB** only + + Note that when debugging via USB, you need to make sure the selected debugger matches the debug mode on your Devboard. To check the debug mode on your Devboard, access the Devboard's web interface as described [here](#dev_board_wifi_connection) and check the **USB mode** field. If you want to use a different debug mode, enable this mode by following the steps in [Devboard debug modes](#dev_board_debug_modes). + +5. If needed, flash your Flipper Zero with the `./fbt flash` command, then click the ▷ **Start Debugging** button in the debug sidebar to start the debugging session. + +6. Note that starting a debug session halts the execution of the firmware, so you'll need to click the I▷ **Continue** button on the toolbar at the top of your VS Code window to continue execution. + +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_VS_Code.jpg width=900 + +> [!note] +> If you want to use a different debug mode on your Developer Board, visit [Devboard debug modes](#dev_board_debug_modes). +> +> If you want to read logs via the Developer Board, see [Reading logs via the Devboard](#dev_board_reading_logs). +> +> To learn about debugging in VS Code, see [VS Code official guide](https://code.visualstudio.com/docs/editor/debugging). diff --git a/documentation/devboard/Devboard debug modes.md b/documentation/devboard/Devboard debug modes.md new file mode 100644 index 00000000000..a6550cbbbe7 --- /dev/null +++ b/documentation/devboard/Devboard debug modes.md @@ -0,0 +1,33 @@ +# Devboard debug modes {#dev_board_debug_modes} + +The Wi-Fi Devboard for Flipper Zero supports **Black Magic** and **DAPLink** debug modes, and you can switch between them depending on your needs. Note that available modes depend on connection: + +- **Wi-Fi:** Only **Black Magic** mode is available. +- **USB:** Switch between **Black Magic** (default) and **DAPLink**. Learn more about switching debug modes for USB connection below. + +> [!note] +> Black Magic mode doesn't support RTOS threads, but you can still perform other debugging operations. + +*** + +## Switching debug modes for USB connection + +Switching debug modes for working via USB has to be done wirelessly (yes, you read that correctly). Additionally, depending on how the Devboard wireless connection is configured, you may need to follow different steps for **Wi-Fi access point mode** or **Wi-Fi client mode**: + +1. If the Devboard isn't connected to your Flipper Zero, turn off your Flipper Zero and connect the Developer Board, then turn the device back on. + +2. Access the Devboard's web interface: + + - [Wi-Fi access point mode](#wifi-access-point) + + - [Wi-Fi client mode](#wifi-client-mode) + +3. In the **WiFi** tab, click the **USB mode** option and select **BlackMagicProbe** or **DapLink**. + +4. Click **SAVE**, then click **REBOOT** to apply the changes. + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_switching_modes_CDN.jpg width=700 + +> [!note] +> After switching debug modes on your Devboard, remember to select the same debugger in **VS Code** in the **Run and Debug** tab, and click the ▷ **Start Debugging** button. + diff --git a/documentation/devboard/Firmware update on Developer Board.md b/documentation/devboard/Firmware update on Developer Board.md index f6a81d97b6f..0b88e52be5a 100644 --- a/documentation/devboard/Firmware update on Developer Board.md +++ b/documentation/devboard/Firmware update on Developer Board.md @@ -1,122 +1,112 @@ # Firmware update on Developer Board {#dev_board_fw_update} -It's important to regularly update your Developer Board to ensure that you have access to the latest features and bug fixes. This tutorial will guide you through the necessary steps to update the firmware of your Developer Board. +It's important to regularly update your Developer Board to ensure that you have access to the latest features and bug fixes. This page will guide you through the necessary steps to update the firmware of your Developer Board. -This tutorial assumes that you're familiar with the basics of the command line. If you’re not, please refer to the [Windows](https://www.digitalcitizen.life/command-prompt-how-use-basic-commands/) or [MacOS/Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials. +> [!note] +> This guide assumes that you're familiar with the basics of the command line. If you're new to it, we recommend checking out these [Windows](https://learn.microsoft.com/en-us/powershell/scripting/learn/ps101/01-getting-started?view=powershell-7.4) or [macOS/Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials. *** -## Installing the micro Flipper Build Tool +## Step 1. Install the micro Flipper Build Tool -Micro Flipper Build Tool (uFBT) is a cross-platform tool that enables basic development tasks for Flipper Zero, such as building and debugging applications, flashing firmware, and creating VS Code development configurations. + is a cross-platform tool developed and supported by our team that enables basic development tasks for Flipper Zero, such as building and debugging applications, flashing firmware, creating VS Code development configurations, and flashing firmware to the Wi-Fi Developer Board. -Install uFBT on your computer by running the following command in the Terminal: +**On Linux & macOS:** -**For Linux & macOS:** +Run the following command in the Terminal: -```text +``` python3 -m pip install --upgrade ufbt ``` -**For Windows:** +**On Windows:** -```text -py -m pip install --upgrade ufbt -``` +1. Download the latest version of Python on +2. Run the following command in the PowerShell -If you want to learn more about uFBT, visit [the project's page](https://pypi.org/project/ufbt/). + ``` + py -m pip install --upgrade ufbt + ``` *** -## Connecting the Developer Board to your computer - -1. List all of the serial devices on your computer. - - **Windows** - - On Windows, go to Device Manager and expand the Ports (COM & LPT) section. +## Step 2. Connect the Devboard to PC - **macOS** +To update the firmware, you need to switch your Developer Board to Bootloader mode, connect to a PC via a USB cable, and make sure that the PC detects the Developer Board: - On macOS, you can run the following command in the Terminal: - - ```text - ls /dev/cu.* - ``` +1. List all of the serial devices on your computer. - **Linux** + - **macOS:** Run the `ls /dev/cu.*` command in the Terminal. - On Linux, you can run the following command in the Terminal: + - **Linux:** Run the `ls /dev/tty*` command in the Terminal. - ```text - ls /dev/tty* - ``` - - View the devices in the list. + - **Windows:** Go to **Device Manager** and expand the **Ports (COM & LPT)** section. 2. Connect the Developer Board to your computer using a USB-C cable. -![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/Aq7gfMI-m_5H6sGGjwb4I_monosnap-miro-2023-07-19-19-47-39.jpg) - -3. Switch your Developer Board to Bootloader mode: - 3.1. Press and hold the **BOOT** button. + \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_update_wired_connection.jpg width=700 - 3.2. Press the **RESET** button while holding the **BOOT** button. - - 3.3. Release the **BOOT** button.\ -![You can easily switch the Dev Board to Bootloader mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KynP9iT6sJ3mXLaLyI82__image.png) +3. Switch your Developer Board to Bootloader mode: -4. Repeat Step 1 and view the name of your Developer Board that appeared in the list. + 1. Press and hold the **BOOT** button. + 2. Press the **RESET** button while holding the **BOOT** button. + 3. Release the **BOOT** button. - For example, on macOS: + \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_to_bootloader.png width=700 - ```text - /dev/cu.usbmodem01 - ``` +4. Repeat **Step 1** and view the name of your Developer Board that appeared in the list. *** -## Flashing the firmware +## Step 3. Flash the firmware -To flash the firmware onto your Developer Board, run the following command in the terminal: +**On Linux & macOS:** -```text +``` python3 -m ufbt devboard_flash ``` +**On Windows:** Run the following command in the PowerShell: + +``` +py -m ufbt devboard_flash +``` + You should see the following message: `WiFi board flashed successfully`. -## If flashing failed +### If flashing failed -If you get an error message during the flashing process, such as this: +Occasionally, you might get an error message during the flashing process, such as: -```text +``` A fatal error occurred: Serial data stream stopped: Possible serial noise or corruption. ``` -Or this: +*or* -```text +``` FileNotFoundError: [Errno 2] No such file or directory: '/dev/cu.usbmodem01' ``` -Try doing the following: +To fix it, try doing the following: -* Disconnect the Developer Board from your computer, then reconnect it. +- Disconnect the Developer Board from your computer, then reconnect it. After that, switch your Developer Board to Bootloader mode once again, as described in -* Use a different USB port on your computer. +- Use a different USB port on your computer. -* Use a different USB-C cable. +- Use a different USB-C cable. *** -## Finishing the installation - -After flashing the firmware: +## Step 4. Finish the installation 1. Reboot the Developer Board by pressing the **RESET** button. -![Reset the Developer Board](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/rcQeKARgrVwa51tLoo-qY_monosnap-miro-2023-07-20-18-29-33.jpg) + + \image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot_after_flashing.jpg width=700 2. Disconnect and reconnect the USB-C cable. -The Developer Board should appear as a serial device on your computer. Now, you can use it with the Black Magic Debug client of your choice. + You've successfully updated the firmware of your Developer Board! + +If you followed the **Get started with the Devboard** guide, you're ready for the next step: [Step 3. Plug the Devboard into Flipper Zero](#dev_board_get_started_step-3). + diff --git a/documentation/devboard/Get started with the Dev Board.md b/documentation/devboard/Get started with the Dev Board.md index 04fa9d3589e..141bf641186 100644 --- a/documentation/devboard/Get started with the Dev Board.md +++ b/documentation/devboard/Get started with the Dev Board.md @@ -1,178 +1,80 @@ # Get started with the Dev Board {#dev_board_get_started} -The Wi-Fi Developer Board serves as a tool to debug the Flipper Zero firmware. To debug the firmware, the initial step involves compiling the firmware from its source code. This process enables the debugging functionality within the firmware and generates all the necessary files required for debugging purposes. +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_box_CDN.jpg width=700 -> **NOTE:** Building and debugging the Flipper Zero firmware is fully supported on MacOS and Linux. Support for Windows is in beta test. +Before you start using your Devboard, you need to prepare your Flipper Zero and Devboard for debugging. In this guide, we'll walk you through all the necessary steps and provide links to explore the Devboard's capabilities further. *** -## Updating the firmware of your Developer Board +## Step 1. Enable Debug Mode on your Flipper Zero -Update the firmware of your Developer Board before using it. For more information, visit [Firmware update on Developer Board](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/update). +Since the main purpose of the Developer board is to debug applications on Flipper Zero, you first need to enable Debug Mode. To do so, go to **Settings → System** and set **Debug** to **ON**. -*** - -## Installing Git - -You'll need Git installed on your computer to clone the firmware repository. If you don't have Git, install it by doing the following: - -* **MacOS** - - On MacOS, install the **Xcode Command Line Tools** package, which includes Git as one of the pre-installed command-line utilities, by running in the Terminal the following command: +\image html https://cdn.flipperzero.one/Flipper_Zero_enamble_debug_CDN.jpg width=700 - ```text - xcode-select --install - ``` +> [!note] +> Debug Mode needs to be re-enabled after each update of Flipper Zero's firmware. -* **Linux** +Debug Mode allows you to debug your apps for Flipper Zero, as well as access debugging options in apps via the user interface and CLI. To learn more about Flipper Zero CLI, visit [Command-line interface in Flipper Docs](https://docs.flipper.net/development/cli). - On Linux, you can install Git using your package manager. For example, on Ubuntu, run in the Terminal the following command: - - ```text - sudo apt install git - ``` - -For other distributions, refer to your package manager documentation. +\image html https://cdn.flipperzero.one/Flipper_Zero_Command_Line_Interface_CDN.jpg width=700 *** -## Building the firmware - -First, clone the firmware repository: - -```text -git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git -cd flipperzero-firmware -``` - -Then, run the **Flipper Build Tool** (FBT) to build the firmware: +## Step 2. Update firmware on the Developer Board -```text -./fbt -``` +The Developer Board comes with stock firmware that may not include all the latest features and bug fixes. To ensure optimal performance, please update your board's firmware using the instructions in [Firmware update on Devboard](#dev_board_fw_update). *** -## Connecting the Developer Board - -The Developer Board can work in the **Wired** mode and two **Wireless** modes: **Wi-Fi access point (AP)** mode and **Wi-Fi client (STA)** mode. The Wired mode is the simplest to set up, but requires a USB Type-C cable. The Wireless modes are more complex to set up, but they allow you to debug your Flipper Zero wirelessly. - - > **NOTE:** Use the following credentials when connecting to the Developer Board in **Wi-Fi access point** mode:\n - Name: **blackmagic**\n - Password: **iamwitcher** - -## Wired - -![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/jZdVlRTPVdSQVegzCyXp7_monosnap-miro-2023-06-22-16-28-06.jpg) - -To connect the Developer Board in **Wired** mode, do the following: - -1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. - -2. On your computer, open the **Terminal** and run the following: - - * **MacOS** - - ```text - ls /dev/cu.* - ``` - - * **Linux** +## Step 3. Plug the Devboard into Flipper Zero {#dev_board_get_started_step-3} - ```text - ls /dev/tty* - ``` +Once your Developer Board firmware is up to date, you can proceed to plug it into your Flipper Zero. Two important things to keep in mind: - Note the list of devices. +1. **Power off your Flipper Zero before plugging in the Developer Board.** -3. Connect the Developer Board to your computer via a USB-C cable. + If you skip this step, you may corrupt the data stored on the microSD card. Connecting external modules with a large capacitive load may affect the microSD card's power supply since both the microSD card and external module are powered from the same 3.3 V power source inside Flipper Zero. -4. Rerun the command. Two new devices have to appear: this is the Developer Board. +2. **Make sure the Developer Board is inserted all the way in.** - > **NOTE:** If the Developer Board doesn't appear in the list of devices, try using a different cable, USB port, or computer. - > - > **NOTE:** Flipper Zero logs can only be viewed when the Developer Board is connected via USB. The option to view logs over Wi-Fi will be added in future updates. For more information, visit [Reading logs via the Dev Board](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/reading-logs). + If your Flipper Zero isn't in a silicone case, insert the module all the way in so there is no gap between your Flipper Zero and the Devboard. You may need to apply more force to insert it completely. After that, press and hold the **BACK** button to power on your Flipper Zero. -## Wireless + \image html https://cdn.flipperzero.one/Flipper_Zero_external_module_without_case_CDN.jpg width=700 -### Wi-Fi access point (AP) mode + If your Flipper Zero is in a silicone case, insert the module all the way in so there is no gap in the middle between the silicone case and the module. After that, press and hold the **BACK** button to power on your Flipper Zero. -![The Developer Board in Wi-Fi access point mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/tKRTMHAuruiLSEce2a8Ve_monosnap-miro-2023-06-22-16-39-17.jpg) + \image html https://cdn.flipperzero.one/Flipper_Zero_external_module_with_case_CDN.jpg width=700 -Out of the box, the Developer Board is configured to work as a **Wi-Fi access point**. This means it'll create its own Wi-Fi network to which you can connect. If your Developer Board doesn't create a Wi-Fi network, it is probably configured to work in **Wi-Fi client** mode. To reset your Developer Board back to **Wi-Fi access point** mode, press and hold the **BOOT** button for 10 seconds, then wait for the module to reboot. - -![You can reconfigure the Developer Board mode by pressing and holding the BOOT button](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/57eELJsAwMxeZCEA1NMJw_monosnap-miro-2023-06-22-20-33-27.jpg) - -To connect the Developer Board in **Wi-Fi access point** mode, do the following: - -1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. - -2. Open Wi-Fi settings on your client device (phone, laptop, or other). - -3. Connect to the network: - - * Name: **blackmagic** - - * Password: **iamwitcher** - -4. To configure the Developer Board, open a browser and go to `http://192.168.4.1`. - -### Wi-Fi client (STA) mode - -![The Developer Board in Wi-Fi client mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/xLQpFyYPfUS5Cx0uQhrNd_monosnap-miro-2023-06-23-12-34-36.jpg) - -To connect the Developer Board in **Wi-Fi client** mode, you need to configure it to connect to your Wi-Fi network by doing the following: - -1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. - -2. Connect to the Developer Board in **Wi-Fi access point** mode. - -3. In a browser, go to the configuration page on `http://192.168.4.1`. - -4. Select the **STA** mode and enter your network's **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby networks. - -5. Save the configuration and reboot the Developer Board. - -![In the Wi-Fi tab, you can set the Developer Board mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/klbLVj8lz2bEvm7j4wRaj_monosnap-miro-2023-06-23-13-06-32.jpg) +*** -After rebooting, the Developer Board connects to your Wi-Fi network. You can connect to the device using the mDNS name **blackmagic.local** or the IP address it got from your router (you'll have to figure this out yourself, every router is different). +## Step 4. Connect to a computer -After connecting to your debugger via , you can find its IP address in the **SYS** tab. You can also change the debugger's mode to **AP** or **STA** there. +Now, you can connect the Developer Board to your computer via USB or Wi-Fi, depending on your needs. We described both methods in separate documents: -![In the SYS tab, you can view the IP address of your Developer Board](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/5XbUptlfqzlV0p6hRUqiG_monosnap-miro-2023-06-22-18-11-30.jpg) +- **[Via USB cable](#dev_board_usb_connection)** for debugging in DAP Link or Black Magic mode, and reading logs. +- [Via Wi-Fi](#dev_board_wifi_connection) for debugging in Black Magic mode. *** -## Debugging the firmware +## Next steps + +You are ready to debug now! To further explore what you can do with the Devboard, check out these pages: -Open the **Terminal** in the **flipperzero-firmware** directory that you cloned earlier and run the following command: +- [Debugging via the Devboard](#dev_board_debugging_guide) +- [Devboard debug modes](#dev_board_debug_modes) +- [Reading logs via the Devboard](#dev_board_reading_logs) -```text -./fbt flash -``` +These guides should help you get started with your Devboard. If you have any questions or you want to share your experience, don't hesitate to join our community on [Reddit](https://www.reddit.com/r/flipperzero/) and [Discord](https://discord.com/invite/flipper), where we have a dedicated #wifi-devboard channel. -This will upload the firmware you've just built to your Flipper Zero via the Developer Board. After that, you can start debugging the firmware using the [GDB](https://www.gnu.org/software/gdb/) debugger. We recommend using **VSCode** with the recommended extensions, and we have pre-made configurations for it. -To debug in **VSCode**, do the following: -1. In VSCode, open the **flipperzero-firmware** directory. -2. You should see a notification about recommended extensions. Install them. - If there were no notifications, open the **Extensions** tab, enter `@recommended` in the search bar, and install the workspace recommendations. -3. In the **Terminal**, run the `./fbt vscode_dist` command. This will generate the VSCode configuration files needed for debugging. -4. In VSCode, open the **Run and Debug** tab and select **Attach FW (blackmagic)** from the dropdown menu. -5. If needed, flash your Flipper Zero with the `./fbt flash` command, then click the **Play** button in the debug sidebar to start the debugging session. -6. Note that starting a debug session halts the execution of the firmware, so you'll need to click the **Continue** button on the toolbar at the top of your VSCode window to continue execution. -![Click Continue in the toolbar to continue execution of the firmware](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/lp8ygGaZ3DvWD3OSI9yGO_monosnap-miro-2023-06-23-17-58-09.jpg) -To learn about debugging, visit the following pages: -* [Debugging with GDB](https://sourceware.org/gdb/current/onlinedocs/gdb.pdf) -* [Debugging in VS Code](https://code.visualstudio.com/docs/editor/debugging) diff --git a/documentation/devboard/Reading logs via the Dev Board.md b/documentation/devboard/Reading logs via the Dev Board.md index c2daf83aced..56d0c87ef88 100644 --- a/documentation/devboard/Reading logs via the Dev Board.md +++ b/documentation/devboard/Reading logs via the Dev Board.md @@ -8,9 +8,9 @@ The Developer Board allows you to read Flipper Zero logs via UART. Unlike readin ## Setting the log level -Depending on your needs, you can set the log level by going to **Main Menu → Settings → Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipperzero.one/basics/settings#d5TAt). +Depending on your needs, you can set the log level by going to **Main Menu → Settings → Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipper.net/basics/settings#d5TAt). -![You can manually set the preferred log level](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/INzQMw8QUsG9PXi30WFS0_monosnap-miro-2023-07-11-13-29-47.jpg) +\image html https://cdn.flipperzero.one/Flipper_Zero_log_level.jpg "You can manually set the preferred log level" width=700 *** @@ -18,9 +18,9 @@ Depending on your needs, you can set the log level by going to **Main Menu → S Depending on your operating system, you need to install an additional application on your computer to read logs via the Developer Board: -### MacOS +### macOS -On MacOS, you need to install the **minicom** communication program by doing the following: +On macOS, you need to install the **minicom** communication program by doing the following: 1. [Install Homebrew](https://brew.sh/) by running the following command in the Terminal: @@ -47,7 +47,7 @@ After installation of minicom on your macOS computer, you can connect to the Dev Note the list of devices. 3. Connect the developer board to your computer using a USB Type-C cable. -![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700 4. Rerun the command. Two new devices have to appear: this is the Developer Board. @@ -100,7 +100,7 @@ After installation of minicom on your Linux computer, you can connect to the Dev Note the list of devices. 3. Connect the developer board to your computer using a USB Type-C cable. -![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700 4. Rerun the command. Two new devices have to appear: this is the Developer Board. @@ -143,17 +143,17 @@ On Windows, do the following: 2. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on. 3. Connect the developer board to your computer using a USB Type-C cable. -![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_developer_board_wired.png width=700 4. Find the serial port that the developer board is connected to by going to **Device Manager → Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board. -![Find the serial port in your Device Manager](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KKLQJK1lvqmI5iab3d__C_image.png) +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_Device_Manager.png width=700 5. Run the PuTTY application and select **Serial** as the connection type. 6. Enter the port number you found in the previous step into the **Serial line** field. 7. Set the **Speed** parameter to **230400** and click **Open**. -![Set speed to 230400](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/ROBSJyfQ_CXiy4GUZcPbs_monosnap-miro-2023-07-12-13-56-47.jpg) +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_PuTTy.jpg width=700 8. View logs of your Flipper Zero in the PuTTY terminal window. diff --git a/documentation/devboard/USB connection to the Devboard.md b/documentation/devboard/USB connection to the Devboard.md new file mode 100644 index 00000000000..af0d9113d77 --- /dev/null +++ b/documentation/devboard/USB connection to the Devboard.md @@ -0,0 +1,22 @@ +# USB connection to the Devboard {#dev_board_usb_connection} + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_USB_connection_CDN.jpg width=700 + +To connect to the Developer Board via USB, do the following: + +1. If the Devboard isn't connected to your Flipper Zero, turn off your Flipper Zero and connect the Developer Board to it. Then, turn your Flipper Zero back on. + +2. On your computer, check the list of serial devices. + + - **macOS:** On your computer, run `ls /dev/cu.*` in the Terminal. + + - **Linux:** On your computer, run `ls /dev/tty*` in the Terminal. + + - **Windows:** Go to **Device Manager** and expand the **Ports (COM & LPT)** section. + +3. Connect the Devboard to your computer via a USB-C cable. + +4. Repeat **Step 2**. Two new devices will appear — this is the Developer Board. + +> [!warning] +> If the Developer Board doesn't appear in the list of devices, try using a different cable, USB port, or computer. \ No newline at end of file diff --git a/documentation/devboard/Wi-Fi connection to the Devboard.md b/documentation/devboard/Wi-Fi connection to the Devboard.md new file mode 100644 index 00000000000..fd3bc324966 --- /dev/null +++ b/documentation/devboard/Wi-Fi connection to the Devboard.md @@ -0,0 +1,60 @@ +# Wi-Fi connection to the Devboard {#dev_board_wifi_connection} + +You can connect to the Developer Board wirelessly in two ways: + +- **Wi-Fi access point mode (default):** The Devboard creates its own Wi-Fi network, which you can connect to in order to access its web interface and debug via Wi-Fi. The downside is that you will need to disconnect from your current Wi-Fi network, resulting in a loss of internet connection. + +- **Wi-Fi client mode:** You can connect to the Devboard through an existing Wi-Fi network, allowing you to access the Devboard web interface and debug via Wi-Fi without losing your internet connection. + +Let's go over both of these modes below. + +*** + +## Wi-Fi access point (AP) mode {#wifi-access-point} + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_Access_Point_CDN.jpg width=700 + +Out of the box, the Developer Board is configured to work as a Wi-Fi access point. To connect the Developer Board in this mode, do the following: + +1. Plug the Wi-Fi Devboard into your Flipper Zero by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. + +2. Open Wi-Fi settings on your client device (phone, laptop, or other). + +3. Connect to the network: + + Name: `blackmagic` + Password: `iamwitcher` + + If your computer fails to find the **blackmagic** network, read the [troubleshooting section](#wifi-access-point_troubleshooting) below. + +4. To access the Devboard's web interface, open a browser and go to or . + +### If your computer fails to find the black magic network {#wifi-access-point_troubleshooting} + +- Reset Wi-Fi connection on your computer. + +- The Developer Board is probably configured to work in Wi-Fi client mode. → Reset your Developer Board settings to default by pressing and holding the **BOOT** button for **10 seconds**, then wait for the Devboard to reboot. After the reset, the Devboard will work in Wi-Fi access point mode. + +\image html https://cdn.flipperzero.one/Flipper_Zero_Wi-Fi_devboard_reboot.jpg width=700 + +*** + +## Wi-Fi client (STA) mode {#wifi-client-mode} + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_STA_CDN.jpg width=700 + +To connect the Developer Board in **Wi-Fi client** mode, you need to configure it to connect to your Wi-Fi network by doing the following: + +1. Plug the Wi-Fi Devboard into your Flipper Zero by turning off your Flipper Zero and connecting the Developer Board, and then turning the device back on. + +2. Connect to the Developer Board in [Wi-Fi access point](#wifi-access-point) mode. + +3. In a browser, go to the Devboard's web interface at or . + +4. Select the **STA** mode and enter your network's **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby 2.4 GHz networks (5 GHz networks aren't supported). + +5. Save the configuration and reboot the Developer Board. + + \image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_connect_to_WiFi_CDN.jpg width=700 + +6. Now, you can access the Devboard's web interface at [http://blackmagic.local](https://blackmagic.local) via the existing Wi-Fi network without losing connection to the internet. diff --git a/documentation/doxygen/dev_board.dox b/documentation/doxygen/dev_board.dox index 6caa44c7050..8d5a2bf35d7 100644 --- a/documentation/doxygen/dev_board.dox +++ b/documentation/doxygen/dev_board.dox @@ -1,10 +1,37 @@ /** -@page dev_board Developer Board +@page dev_board Wi-Fi Developer Board -[ESP32-based development board](https://shop.flipperzero.one/collections/flipper-zero-accessories/products/wifi-devboard). +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_devboard_laptop_CDN.jpg width=700 + +Wi-Fi-enabled Developer Board brings debugging and firmware update capabilities to your Flipper Zero. The Developer Board is based on the ESP32-S2 MCU with custom firmware incorporating Black Magic Debug and CMSIS-DAP, and is built with ESP-IDF. It can flash and debug various microprocessors and microcontrollers (including the one used in your Flipper Zero) via Wi-Fi or USB cable. + +The Developer Board provides a debug interface, allowing developers to halt program execution, set breakpoints, inspect variables and memory, and step through code execution. + +
+Get your Wi-Fi Developer Board +
+
+ +Check out these guides to get started with the Devboard: - @subpage dev_board_get_started — Quick start for new users +- @subpage dev_board_fw_update — Keep the Developer Board up to date +- @subpage dev_board_usb_connection — Instructions for Windows, macOS and Linux +- @subpage dev_board_wifi_connection — Instructions for Windows, macOS and Linux +- @subpage dev_board_debugging_guide — Learn how it works +- @subpage dev_board_debug_modes — Available modes and how to switch between them - @subpage dev_board_reading_logs — Find out what is currently happening on the system -- @subpage dev_board_fw_update — Keep the developer board up to date + +## Hardware + +The Developer Board is equipped with an [ESP32-S2-WROVER](https://www.espressif.com/en/products/socs/esp32-s2) module, which includes built-in Wi-Fi capabilities. It also offers GPIO pins for easy connectivity to various targets. Additionally, the Developer Board features a USB Type-C connector for data transfer and power supply. For user interaction, the Developer Board has tactile switches. + +\image html https://cdn.flipperzero.one/Flipper_Zero_WiFi_developer_board_hardware_CDN.jpg width=700 + +## Additional resources + +To learn more about the Wi-Fi Developer Board hardware, visit [Schematics in Flipper Docs](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/schematics). + +For additional information about Flipper Zero GPIO pins, visit [GPIO & modules in Flipper Docs](https://docs.flipperzero.one/gpio-and-modules). */ diff --git a/documentation/doxygen/header.html b/documentation/doxygen/header.html index 5cc0aba38f3..e987e39aae6 100644 --- a/documentation/doxygen/header.html +++ b/documentation/doxygen/header.html @@ -52,7 +52,7 @@ -
$projectname $projectnumber +
$projectname $projectnumber
$projectbrief
diff --git a/documentation/fbt.md b/documentation/fbt.md index 59f6aa154eb..2635a43fc6e 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -44,7 +44,7 @@ To run cleanup (think of `make clean`) for specified targets, add the `-c` optio ## VSCode integration -`fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu. +`fbt` includes basic development environment configuration for VS Code. Run `./fbt vscode_dist` to deploy it. That will copy the initial environment configuration to the `.vscode` folder. After that, you can use that configuration by starting VS Code and choosing the firmware root folder in the "File > Open Folder" menu. To use language servers other than the default VS Code C/C++ language server, use `./fbt vscode_dist LANG_SERVER=` instead. Currently `fbt` supports the default language server (`cpptools`) and `clangd`. From d9d3867ce19fbfba92647187ef2820eb234020de Mon Sep 17 00:00:00 2001 From: SUMUKH <130692934+sumukhj1219@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:03:51 +0530 Subject: [PATCH 22/36] Fixes Mouse Clicker Should have a "0" value setting for "as fast as possible" #3876 (#3894) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixes mouse clicking rate * Hid: limit max clicks to 100/s, rewrite code to make it more robust Co-authored-by: あく --- .../system/hid_app/views/hid_mouse_clicker.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index 0bb815249df..e289b717964 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -7,7 +7,7 @@ #define TAG "HidMouseClicker" #define DEFAULT_CLICK_RATE 1 -#define MAXIMUM_CLICK_RATE 60 +#define MAXIMUM_CLICK_RATE 100 struct HidMouseClicker { View* view; @@ -34,7 +34,9 @@ static void hid_mouse_clicker_start_or_restart_timer(void* context) { HidMouseClickerModel * model, { furi_timer_start( - hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate); + hid_mouse_clicker->timer, + furi_kernel_get_tick_frequency() / + ((model->rate) ? model->rate : MAXIMUM_CLICK_RATE)); }, true); } @@ -75,7 +77,11 @@ static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) { // Clicks/s char label[20]; - snprintf(label, sizeof(label), "%d clicks/s", model->rate); + if(model->rate) { + snprintf(label, sizeof(label), "%d clicks/s", model->rate); + } else { + snprintf(label, sizeof(label), "max clicks/s"); + } elements_multiline_text_aligned(canvas, 28, 37, AlignCenter, AlignBottom, label); canvas_draw_icon(canvas, 25, 20, &I_ButtonUp_7x4); @@ -139,7 +145,7 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { consumed = true; break; case InputKeyDown: - if(model->rate > 1) { + if(model->rate > 0) { model->rate--; } rate_changed = true; From 0f831412fa617ae51afc5375b801c3b5c7543834 Mon Sep 17 00:00:00 2001 From: porta Date: Mon, 14 Oct 2024 17:50:18 +0300 Subject: [PATCH 23/36] [FL-3909] CLI improvements, part I (#3928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: cli top blinking * feat: clear prompt on down key * feat: proper-er ansi escape sequence handling * ci: fix compact build error * Make PVS happy * style: remove magic numbers * style: review suggestions Co-authored-by: あく --- applications/main/subghz/subghz_cli.c | 9 +- applications/main/subghz/subghz_cli.h | 1 + applications/services/cli/cli.c | 194 ++++++++++++----- applications/services/cli/cli.h | 16 +- applications/services/cli/cli_ansi.c | 76 +++++++ applications/services/cli/cli_ansi.h | 94 ++++++++ applications/services/cli/cli_commands.c | 227 +++++++++++++++++--- applications/services/crypto/crypto_cli.c | 9 +- applications/services/storage/storage_cli.c | 3 +- 9 files changed, 528 insertions(+), 101 deletions(-) create mode 100644 applications/services/cli/cli_ansi.c create mode 100644 applications/services/cli/cli_ansi.h diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 6375f2eee4d..bce88b7a354 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -999,13 +999,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { chat_event = subghz_chat_worker_get_event_chat(subghz_chat); switch(chat_event.event) { case SubGhzChatEventInputData: - if(chat_event.c == CliSymbolAsciiETX) { + if(chat_event.c == CliKeyETX) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); break; - } else if( - (chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) { + } else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) { size_t len = furi_string_utf8_length(input); if(len > furi_string_utf8_length(name)) { printf("%s", "\e[D\e[1P"); @@ -1027,7 +1026,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { } furi_string_set(input, sysmsg); } - } else if(chat_event.c == CliSymbolAsciiCR) { + } else if(chat_event.c == CliKeyCR) { printf("\r\n"); furi_string_push_back(input, '\r'); furi_string_push_back(input, '\n'); @@ -1041,7 +1040,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { furi_string_printf(input, "%s", furi_string_get_cstr(name)); printf("%s", furi_string_get_cstr(input)); fflush(stdout); - } else if(chat_event.c == CliSymbolAsciiLF) { + } else if(chat_event.c == CliKeyLF) { //cut out the symbol \n } else { putc(chat_event.c, stdout); diff --git a/applications/main/subghz/subghz_cli.h b/applications/main/subghz/subghz_cli.h index f6388218f46..18c84c3e0f4 100644 --- a/applications/main/subghz/subghz_cli.h +++ b/applications/main/subghz/subghz_cli.h @@ -1,5 +1,6 @@ #pragma once #include +#include void subghz_on_system_start(void); diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 0d8f52c04ec..6f18ee97316 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -1,12 +1,15 @@ #include "cli_i.h" #include "cli_commands.h" #include "cli_vcp.h" +#include "cli_ansi.h" #include #include #define TAG "CliSrv" #define CLI_INPUT_LEN_LIMIT 256 +#define CLI_PROMPT ">: " // qFlipper does not recognize us if we use escape sequences :( +#define CLI_PROMPT_LENGTH 3 // printable characters Cli* cli_alloc(void) { Cli* cli = malloc(sizeof(Cli)); @@ -85,7 +88,7 @@ bool cli_cmd_interrupt_received(Cli* cli) { char c = '\0'; if(cli_is_connected(cli)) { if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) { - return c == CliSymbolAsciiETX; + return c == CliKeyETX; } } else { return true; @@ -102,7 +105,8 @@ void cli_print_usage(const char* cmd, const char* usage, const char* arg) { } void cli_motd(void) { - printf("\r\n" + printf(ANSI_FLIPPER_BRAND_ORANGE + "\r\n" " _.-------.._ -,\r\n" " .-\"```\"--..,,_/ /`-, -, \\ \r\n" " .:\" /:/ /'\\ \\ ,_..., `. | |\r\n" @@ -116,12 +120,11 @@ void cli_motd(void) { " _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n" "| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n" "| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n" - "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" - "\r\n" - "Welcome to Flipper Zero Command Line Interface!\r\n" + "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" ANSI_RESET + "\r\n" ANSI_FG_BR_WHITE "Welcome to " ANSI_FLIPPER_BRAND_ORANGE + "Flipper Zero" ANSI_FG_BR_WHITE " Command Line Interface!\r\n" "Read the manual: https://docs.flipper.net/development/cli\r\n" - "Run `help` or `?` to list available commands\r\n" - "\r\n"); + "Run `help` or `?` to list available commands\r\n" ANSI_RESET "\r\n"); const Version* firmware_version = furi_hal_version_get_firmware_version(); if(firmware_version) { @@ -142,7 +145,7 @@ void cli_nl(Cli* cli) { void cli_prompt(Cli* cli) { UNUSED(cli); - printf("\r\n>: %s", furi_string_get_cstr(cli->line)); + printf("\r\n" CLI_PROMPT "%s", furi_string_get_cstr(cli->line)); fflush(stdout); } @@ -165,7 +168,7 @@ static void cli_handle_backspace(Cli* cli) { cli->cursor_position--; } else { - cli_putc(cli, CliSymbolAsciiBell); + cli_putc(cli, CliKeyBell); } } @@ -241,7 +244,7 @@ static void cli_handle_enter(Cli* cli) { printf( "`%s` command not found, use `help` or `?` to list all available commands", furi_string_get_cstr(command)); - cli_putc(cli, CliSymbolAsciiBell); + cli_putc(cli, CliKeyBell); } cli_reset(cli); @@ -305,8 +308,85 @@ static void cli_handle_autocomplete(Cli* cli) { cli_prompt(cli); } -static void cli_handle_escape(Cli* cli, char c) { - if(c == 'A') { +typedef enum { + CliCharClassWord, + CliCharClassSpace, + CliCharClassOther, +} CliCharClass; + +/** + * @brief Determines the class that a character belongs to + * + * The return value of this function should not be used on its own; it should + * only be used for comparing it with other values returned by this function. + * This function is used internally in `cli_skip_run`. + */ +static CliCharClass cli_char_class(char c) { + if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { + return CliCharClassWord; + } else if(c == ' ') { + return CliCharClassSpace; + } else { + return CliCharClassOther; + } +} + +typedef enum { + CliSkipDirectionLeft, + CliSkipDirectionRight, +} CliSkipDirection; + +/** + * @brief Skips a run of a class of characters + * + * @param string Input string + * @param original_pos Position to start the search at + * @param direction Direction in which to perform the search + * @returns The position at which the run ends + */ +static size_t cli_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) { + if(furi_string_size(string) == 0) return original_pos; + if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos; + if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string)) + return original_pos; + + int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0; + int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1; + int32_t position = original_pos; + CliCharClass start_class = + cli_char_class(furi_string_get_char(string, position + look_offset)); + + while(true) { + position += increment; + if(position < 0) break; + if(position >= (int32_t)furi_string_size(string)) break; + if(cli_char_class(furi_string_get_char(string, position + look_offset)) != start_class) + break; + } + + return MAX(0, position); +} + +void cli_process_input(Cli* cli) { + CliKeyCombo combo = cli_read_ansi_key_combo(cli); + FURI_LOG_T(TAG, "code=0x%02x, mod=0x%x\r\n", combo.key, combo.modifiers); + + if(combo.key == CliKeyTab) { + cli_handle_autocomplete(cli); + + } else if(combo.key == CliKeySOH) { + furi_delay_ms(33); // We are too fast, Minicom is not ready yet + cli_motd(); + cli_prompt(cli); + + } else if(combo.key == CliKeyETX) { + cli_reset(cli); + cli_prompt(cli); + + } else if(combo.key == CliKeyEOT) { + cli_reset(cli); + + } else if(combo.key == CliKeyUp && combo.modifiers == CliModKeyNo) { // Use previous command if line buffer is empty if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) { // Set line buffer and cursor position @@ -315,67 +395,85 @@ static void cli_handle_escape(Cli* cli, char c) { // Show new line to user printf("%s", furi_string_get_cstr(cli->line)); } - } else if(c == 'B') { - } else if(c == 'C') { + + } else if(combo.key == CliKeyDown && combo.modifiers == CliModKeyNo) { + // Clear input buffer + furi_string_reset(cli->line); + cli->cursor_position = 0; + printf("\r" CLI_PROMPT "\e[0K"); + + } else if(combo.key == CliKeyRight && combo.modifiers == CliModKeyNo) { + // Move right if(cli->cursor_position < furi_string_size(cli->line)) { cli->cursor_position++; printf("\e[C"); } - } else if(c == 'D') { + + } else if(combo.key == CliKeyLeft && combo.modifiers == CliModKeyNo) { + // Move left if(cli->cursor_position > 0) { cli->cursor_position--; printf("\e[D"); } - } - fflush(stdout); -} -void cli_process_input(Cli* cli) { - char in_chr = cli_getc(cli); - size_t rx_len; + } else if(combo.key == CliKeyHome && combo.modifiers == CliModKeyNo) { + // Move to beginning of line + cli->cursor_position = 0; + printf("\e[%uG", CLI_PROMPT_LENGTH + 1); // columns start at 1 \(-_-)/ - if(in_chr == CliSymbolAsciiTab) { - cli_handle_autocomplete(cli); - } else if(in_chr == CliSymbolAsciiSOH) { - furi_delay_ms(33); // We are too fast, Minicom is not ready yet - cli_motd(); - cli_prompt(cli); - } else if(in_chr == CliSymbolAsciiETX) { - cli_reset(cli); - cli_prompt(cli); - } else if(in_chr == CliSymbolAsciiEOT) { - cli_reset(cli); - } else if(in_chr == CliSymbolAsciiEsc) { - rx_len = cli_read(cli, (uint8_t*)&in_chr, 1); - if((rx_len > 0) && (in_chr == '[')) { - cli_read(cli, (uint8_t*)&in_chr, 1); - cli_handle_escape(cli, in_chr); - } else { - cli_putc(cli, CliSymbolAsciiBell); - } - } else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) { + } else if(combo.key == CliKeyEnd && combo.modifiers == CliModKeyNo) { + // Move to end of line + cli->cursor_position = furi_string_size(cli->line); + printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1); + + } else if( + combo.modifiers == CliModKeyCtrl && + (combo.key == CliKeyLeft || combo.key == CliKeyRight)) { + // Skip run of similar chars to the left or right + CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft : + CliSkipDirectionRight; + cli->cursor_position = cli_skip_run(cli->line, cli->cursor_position, direction); + printf("\e[%zuG", CLI_PROMPT_LENGTH + cli->cursor_position + 1); + + } else if(combo.key == CliKeyBackspace || combo.key == CliKeyDEL) { cli_handle_backspace(cli); - } else if(in_chr == CliSymbolAsciiCR) { + + } else if(combo.key == CliKeyETB) { // Ctrl + Backspace + // Delete run of similar chars to the left + size_t run_start = cli_skip_run(cli->line, cli->cursor_position, CliSkipDirectionLeft); + furi_string_replace_at(cli->line, run_start, cli->cursor_position - run_start, ""); + cli->cursor_position = run_start; + printf( + "\e[%zuG%s\e[0K\e[%zuG", // move cursor, print second half of line, erase remains, move cursor again + CLI_PROMPT_LENGTH + cli->cursor_position + 1, + furi_string_get_cstr(cli->line) + run_start, + CLI_PROMPT_LENGTH + run_start + 1); + + } else if(combo.key == CliKeyCR) { cli_handle_enter(cli); + } else if( - (in_chr >= 0x20 && in_chr < 0x7F) && //-V560 + (combo.key >= 0x20 && combo.key < 0x7F) && //-V560 (furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) { if(cli->cursor_position == furi_string_size(cli->line)) { - furi_string_push_back(cli->line, in_chr); - cli_putc(cli, in_chr); + furi_string_push_back(cli->line, combo.key); + cli_putc(cli, combo.key); } else { // Insert character to line buffer - const char in_str[2] = {in_chr, 0}; + const char in_str[2] = {combo.key, 0}; furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str); // Print character in replace mode - printf("\e[4h%c\e[4l", in_chr); + printf("\e[4h%c\e[4l", combo.key); fflush(stdout); } cli->cursor_position++; + } else { - cli_putc(cli, CliSymbolAsciiBell); + cli_putc(cli, CliKeyBell); } + + fflush(stdout); } void cli_add_command( diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index bb84670a739..c91f71c4474 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -10,26 +10,12 @@ extern "C" { #endif -typedef enum { - CliSymbolAsciiSOH = 0x01, - CliSymbolAsciiETX = 0x03, - CliSymbolAsciiEOT = 0x04, - CliSymbolAsciiBell = 0x07, - CliSymbolAsciiBackspace = 0x08, - CliSymbolAsciiTab = 0x09, - CliSymbolAsciiLF = 0x0A, - CliSymbolAsciiCR = 0x0D, - CliSymbolAsciiEsc = 0x1B, - CliSymbolAsciiUS = 0x1F, - CliSymbolAsciiSpace = 0x20, - CliSymbolAsciiDel = 0x7F, -} CliSymbols; - typedef enum { CliCommandFlagDefault = 0, /**< Default, loader lock is used */ CliCommandFlagParallelSafe = (1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */ CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */ + CliCommandFlagHidden = (1 << 2), /**< Not shown in `help` */ } CliCommandFlag; #define RECORD_CLI "cli" diff --git a/applications/services/cli/cli_ansi.c b/applications/services/cli/cli_ansi.c new file mode 100644 index 00000000000..d27c20bad09 --- /dev/null +++ b/applications/services/cli/cli_ansi.c @@ -0,0 +1,76 @@ +#include "cli_ansi.h" + +/** + * @brief Converts a single character representing a special key into the enum + * representation + */ +static CliKey cli_ansi_key_from_mnemonic(char c) { + switch(c) { + case 'A': + return CliKeyUp; + case 'B': + return CliKeyDown; + case 'C': + return CliKeyRight; + case 'D': + return CliKeyLeft; + case 'F': + return CliKeyEnd; + case 'H': + return CliKeyHome; + default: + return CliKeyUnrecognized; + } +} + +CliKeyCombo cli_read_ansi_key_combo(Cli* cli) { + char ch = cli_getc(cli); + + if(ch != CliKeyEsc) + return (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = ch, + }; + + ch = cli_getc(cli); + + // ESC ESC -> ESC + if(ch == '\e') + return (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = '\e', + }; + + // ESC -> Alt + + if(ch != '[') + return (CliKeyCombo){ + .modifiers = CliModKeyAlt, + .key = cli_getc(cli), + }; + + ch = cli_getc(cli); + + // ESC [ 1 + if(ch == '1') { + // ESC [ 1 ; + if(cli_getc(cli) == ';') { + CliModKey modifiers = (cli_getc(cli) - '0'); // convert following digit to a number + modifiers &= ~1; + return (CliKeyCombo){ + .modifiers = modifiers, + .key = cli_ansi_key_from_mnemonic(cli_getc(cli)), + }; + } + + return (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = CliKeyUnrecognized, + }; + } + + // ESC [ + return (CliKeyCombo){ + .modifiers = CliModKeyNo, + .key = cli_ansi_key_from_mnemonic(ch), + }; +} diff --git a/applications/services/cli/cli_ansi.h b/applications/services/cli/cli_ansi.h new file mode 100644 index 00000000000..110d8a5fcdb --- /dev/null +++ b/applications/services/cli/cli_ansi.h @@ -0,0 +1,94 @@ +#pragma once + +#include "cli.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ANSI_RESET "\e[0m" +#define ANSI_BOLD "\e[1m" +#define ANSI_FAINT "\e[2m" + +#define ANSI_FG_BLACK "\e[30m" +#define ANSI_FG_RED "\e[31m" +#define ANSI_FG_GREEN "\e[32m" +#define ANSI_FG_YELLOW "\e[33m" +#define ANSI_FG_BLUE "\e[34m" +#define ANSI_FG_MAGENTA "\e[35m" +#define ANSI_FG_CYAN "\e[36m" +#define ANSI_FG_WHITE "\e[37m" +#define ANSI_FG_BR_BLACK "\e[90m" +#define ANSI_FG_BR_RED "\e[91m" +#define ANSI_FG_BR_GREEN "\e[92m" +#define ANSI_FG_BR_YELLOW "\e[93m" +#define ANSI_FG_BR_BLUE "\e[94m" +#define ANSI_FG_BR_MAGENTA "\e[95m" +#define ANSI_FG_BR_CYAN "\e[96m" +#define ANSI_FG_BR_WHITE "\e[97m" + +#define ANSI_BG_BLACK "\e[40m" +#define ANSI_BG_RED "\e[41m" +#define ANSI_BG_GREEN "\e[42m" +#define ANSI_BG_YELLOW "\e[43m" +#define ANSI_BG_BLUE "\e[44m" +#define ANSI_BG_MAGENTA "\e[45m" +#define ANSI_BG_CYAN "\e[46m" +#define ANSI_BG_WHITE "\e[47m" +#define ANSI_BG_BR_BLACK "\e[100m" +#define ANSI_BG_BR_RED "\e[101m" +#define ANSI_BG_BR_GREEN "\e[102m" +#define ANSI_BG_BR_YELLOW "\e[103m" +#define ANSI_BG_BR_BLUE "\e[104m" +#define ANSI_BG_BR_MAGENTA "\e[105m" +#define ANSI_BG_BR_CYAN "\e[106m" +#define ANSI_BG_BR_WHITE "\e[107m" + +#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m" + +typedef enum { + CliKeyUnrecognized = 0, + + CliKeySOH = 0x01, + CliKeyETX = 0x03, + CliKeyEOT = 0x04, + CliKeyBell = 0x07, + CliKeyBackspace = 0x08, + CliKeyTab = 0x09, + CliKeyLF = 0x0A, + CliKeyCR = 0x0D, + CliKeyETB = 0x17, + CliKeyEsc = 0x1B, + CliKeyUS = 0x1F, + CliKeySpace = 0x20, + CliKeyDEL = 0x7F, + + CliKeySpecial = 0x80, + CliKeyLeft, + CliKeyRight, + CliKeyUp, + CliKeyDown, + CliKeyHome, + CliKeyEnd, +} CliKey; + +typedef enum { + CliModKeyNo = 0, + CliModKeyAlt = 2, + CliModKeyCtrl = 4, + CliModKeyMeta = 8, +} CliModKey; + +typedef struct { + CliModKey modifiers; + CliKey key; +} CliKeyCombo; + +/** + * @brief Reads a key or key combination + */ +CliKeyCombo cli_read_ansi_key_combo(Cli* cli); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index c3539813b1c..cb813840a47 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -1,5 +1,6 @@ #include "cli_commands.h" #include "cli_command_gpio.h" +#include "cli_ansi.h" #include #include @@ -10,6 +11,7 @@ #include #include #include +#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" @@ -52,37 +54,196 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) { } } -void cli_command_help(Cli* cli, FuriString* args, void* context) { +// Lil Easter egg :> +void cli_command_neofetch(Cli* cli, FuriString* args, void* context) { + UNUSED(cli); UNUSED(args); UNUSED(context); + + static const char* const neofetch_logo[] = { + " _.-------.._ -,", + " .-\"```\"--..,,_/ /`-, -, \\ ", + " .:\" /:/ /'\\ \\ ,_..., `. | |", + " / ,----/:/ /`\\ _\\~`_-\"` _;", + " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ", + " | | | 0 | | .-' ,/` /", + " | ,..\\ \\ ,.-\"` ,/` /", + "; : `/`\"\"\\` ,/--==,/-----,", + "| `-...| -.___-Z:_______J...---;", + ": ` _-'", + }; +#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE + + // Determine logo parameters + size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0; + for(size_t i = 0; i < logo_height; i++) + logo_width = MAX(logo_width, strlen(neofetch_logo[i])); + logo_width += 4; // space between logo and info + + // Format hostname delimiter + const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr()); + char delimiter[64]; + memset(delimiter, '-', size_of_hostname); + delimiter[size_of_hostname] = '\0'; + + // Get heap info + size_t heap_total = memmgr_get_total_heap(); + size_t heap_used = heap_total - memmgr_get_free_heap(); + uint16_t heap_percent = (100 * heap_used) / heap_total; + + // Get storage info + Storage* storage = furi_record_open(RECORD_STORAGE); + uint64_t ext_total, ext_free, ext_used, ext_percent; + storage_common_fs_info(storage, "/ext", &ext_total, &ext_free); + ext_used = ext_total - ext_free; + ext_percent = (100 * ext_used) / ext_total; + ext_used /= 1024 * 1024; + ext_total /= 1024 * 1024; + furi_record_close(RECORD_STORAGE); + + // Get battery info + uint16_t charge_percent = furi_hal_power_get_pct(); + const char* charge_state; + if(furi_hal_power_is_charging()) { + if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) { + charge_state = "charging"; + } else { + charge_state = "charged"; + } + } else { + charge_state = "discharging"; + } + + // Get misc info + uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); + const Version* version = version_get(); + uint16_t major, minor; + furi_hal_info_get_api_version(&major, &minor); + + // Print ASCII art with info + const size_t info_height = 16; + for(size_t i = 0; i < MAX(logo_height, info_height); i++) { + printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : ""); + switch(i) { + case 0: // you@ + printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr()); + break; + case 1: // delimiter + printf(ANSI_RESET "%s", delimiter); + break; + case 2: // OS: FURI (SDK .) + printf( + "OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)", + version_get_version(version), + version_get_gitbranch(version), + version_get_version(version), + version_get_githash(version), + major, + minor); + break; + case 3: // Host: + printf( + "Host" ANSI_RESET ": %s %s", + furi_hal_version_get_model_code(), + furi_hal_version_get_device_name_ptr()); + break; + case 4: // Kernel: FreeRTOS .. + printf( + "Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d", + tskKERNEL_VERSION_MAJOR, + tskKERNEL_VERSION_MINOR, + tskKERNEL_VERSION_BUILD); + break; + case 5: // Uptime: ?h?m?s + printf( + "Uptime" ANSI_RESET ": %luh%lum%lus", + uptime / 60 / 60, + uptime / 60 % 60, + uptime % 60); + break; + case 6: // ST7567 128x64 @ 1 bpp in 1.4" + printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\""); + break; + case 7: // DE: GuiSrv + printf("DE" ANSI_RESET ": GuiSrv"); + break; + case 8: // Shell: CliSrv + printf("Shell" ANSI_RESET ": CliSrv"); + break; + case 9: // CPU: STM32WB55RG @ 64 MHz + printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz"); + break; + case 10: // Memory: / B (??%) + printf( + "Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent); + break; + case 11: // Disk (/ext): / MiB (??%) + printf( + "Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)", + ext_used, + ext_total, + ext_percent); + break; + case 12: // Battery: ??% () + printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state); + break; + case 13: // empty space + break; + case 14: // Colors (line 1) + for(size_t j = 30; j <= 37; j++) + printf("\e[%dm███", j); + break; + case 15: // Colors (line 2) + for(size_t j = 90; j <= 97; j++) + printf("\e[%dm███", j); + break; + default: + break; + } + printf("\r\n"); + } + printf(ANSI_RESET); +#undef NEOFETCH_COLOR +} + +void cli_command_help(Cli* cli, FuriString* args, void* context) { + UNUSED(context); printf("Commands available:"); - // Command count - const size_t commands_count = CliCommandTree_size(cli->commands); - const size_t commands_count_mid = commands_count / 2 + commands_count % 2; + // Count non-hidden commands + CliCommandTree_it_t it_count; + CliCommandTree_it(it_count, cli->commands); + size_t commands_count = 0; + while(!CliCommandTree_end_p(it_count)) { + if(!(CliCommandTree_cref(it_count)->value_ptr->flags & CliCommandFlagHidden)) + commands_count++; + CliCommandTree_next(it_count); + } - // Use 2 iterators from start and middle to show 2 columns - CliCommandTree_it_t it_left; - CliCommandTree_it(it_left, cli->commands); - CliCommandTree_it_t it_right; - CliCommandTree_it(it_right, cli->commands); - for(size_t i = 0; i < commands_count_mid; i++) - CliCommandTree_next(it_right); + // Create iterators starting at different positions + const size_t columns = 3; + const size_t commands_per_column = (commands_count / columns) + (commands_count % columns); + CliCommandTree_it_t iterators[columns]; + for(size_t c = 0; c < columns; c++) { + CliCommandTree_it(iterators[c], cli->commands); + for(size_t i = 0; i < c * commands_per_column; i++) + CliCommandTree_next(iterators[c]); + } - // Iterate throw tree - for(size_t i = 0; i < commands_count_mid; i++) { + // Print commands + for(size_t r = 0; r < commands_per_column; r++) { printf("\r\n"); - // Left Column - if(!CliCommandTree_end_p(it_left)) { - printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr)); - CliCommandTree_next(it_left); - } - // Right Column - if(!CliCommandTree_end_p(it_right)) { - printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr)); - CliCommandTree_next(it_right); + + for(size_t c = 0; c < columns; c++) { + if(!CliCommandTree_end_p(iterators[c])) { + const CliCommandTree_itref_t* item = CliCommandTree_cref(iterators[c]); + if(!(item->value_ptr->flags & CliCommandFlagHidden)) { + printf("%-30s", furi_string_get_cstr(*item->key_ptr)); + } + CliCommandTree_next(iterators[c]); + } } - }; + } if(furi_string_size(args) > 0) { cli_nl(cli); @@ -391,16 +552,18 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { int interval = 1000; args_read_int_and_trim(args, &interval); + if(interval) printf("\e[2J\e[?25l"); // Clear display, hide cursor + FuriThreadList* thread_list = furi_thread_list_alloc(); while(!cli_cmd_interrupt_received(cli)) { uint32_t tick = furi_get_tick(); furi_thread_enumerate(thread_list); - if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0 + if(interval) printf("\e[0;0f"); // Return to 0,0 uint32_t uptime = tick / furi_kernel_get_tick_frequency(); printf( - "Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n", + "\rThreads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\e[0K\r\n", furi_thread_list_size(thread_list), (double)furi_thread_list_get_isr_time(thread_list), uptime / 60 / 60, @@ -408,14 +571,14 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { uptime % 60); printf( - "Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n", + "\rHeap: total %zu, free %zu, minimum %zu, max block %zu\e[0K\r\n\r\n", memmgr_get_total_heap(), memmgr_get_free_heap(), memmgr_get_minimum_free_heap(), memmgr_heap_get_max_free_block()); printf( - "%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n", + "\r%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\e[0K\r\n", "AppID", "Name", "State", @@ -429,7 +592,7 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) { const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i); printf( - "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n", + "\r%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\e[0K\r\n", item->app_id, item->name, item->state, @@ -448,6 +611,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { } } furi_thread_list_free(thread_list); + + if(interval) printf("\e[?25h"); // Show cursor } void cli_command_free(Cli* cli, FuriString* args, void* context) { @@ -499,6 +664,12 @@ void cli_commands_init(Cli* cli) { cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); + cli_add_command( + cli, + "neofetch", + CliCommandFlagParallelSafe | CliCommandFlagHidden, + cli_command_neofetch, + NULL); cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index 744fa7151d1..90746801aae 100644 --- a/applications/services/crypto/crypto_cli.c +++ b/applications/services/crypto/crypto_cli.c @@ -3,6 +3,7 @@ #include #include +#include void crypto_cli_print_usage(void) { printf("Usage:\r\n"); @@ -45,14 +46,14 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) { input = furi_string_alloc(); char c; while(cli_read(cli, (uint8_t*)&c, 1) == 1) { - if(c == CliSymbolAsciiETX) { + if(c == CliKeyETX) { printf("\r\n"); break; } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); furi_string_push_back(input, c); - } else if(c == CliSymbolAsciiCR) { + } else if(c == CliKeyCR) { printf("\r\n"); furi_string_cat(input, "\r\n"); } @@ -120,14 +121,14 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { hex_input = furi_string_alloc(); char c; while(cli_read(cli, (uint8_t*)&c, 1) == 1) { - if(c == CliSymbolAsciiETX) { + if(c == CliKeyETX) { printf("\r\n"); break; } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); furi_string_push_back(hex_input, c); - } else if(c == CliSymbolAsciiCR) { + } else if(c == CliKeyCR) { printf("\r\n"); } } diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 441b58da66b..17bbc02a5b4 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -224,7 +225,7 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) { while(true) { uint8_t symbol = cli_getc(cli); - if(symbol == CliSymbolAsciiETX) { + if(symbol == CliKeyETX) { size_t write_size = read_index % buffer_size; if(write_size > 0) { From 0902fd49e1cb7bd0c49bb2685985593f08861449 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:22:47 +0300 Subject: [PATCH 24/36] NFC: iso14443_4a improvements. Canvas: extended icon draw. (#3918) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Now 4a listener invokes upper level callback on Halt and FieldOff * Added new method for drawing mirrored XBM bitmaps * iso14443_4a poller logic enhanced * Function renamed accroding to review suggestions * Rename #2 * Api adjustements * Correct API bump Co-authored-by: あく --- applications/services/gui/canvas.c | 14 ++- applications/services/gui/canvas.h | 20 +++++ lib/nfc/helpers/iso14443_4_layer.c | 90 +++++++++++++++++-- lib/nfc/helpers/iso14443_4_layer.h | 4 + .../iso14443_4a/iso14443_4a_listener.c | 9 ++ .../iso14443_4a/iso14443_4a_listener.h | 1 + .../iso14443_4a/iso14443_4a_poller.h | 63 +++++++++++++ .../iso14443_4a/iso14443_4a_poller_i.c | 31 +++++++ targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 6 +- 10 files changed, 231 insertions(+), 10 deletions(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index de09305aaba..47e4c7d3da9 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -512,9 +512,21 @@ void canvas_draw_xbm( size_t height, const uint8_t* bitmap) { furi_check(canvas); + canvas_draw_xbm_ex(canvas, x, y, width, height, IconRotation0, bitmap); +} + +void canvas_draw_xbm_ex( + Canvas* canvas, + int32_t x, + int32_t y, + size_t width, + size_t height, + IconRotation rotation, + const uint8_t* bitmap_data) { + furi_check(canvas); x += canvas->offset_x; y += canvas->offset_y; - canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap, IconRotation0); + canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap_data, rotation); } void canvas_draw_glyph(Canvas* canvas, int32_t x, int32_t y, uint16_t ch) { diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 9554a200e3e..308d17fc31e 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -287,6 +287,26 @@ void canvas_draw_xbm( size_t height, const uint8_t* bitmap); +/** Draw rotated XBM bitmap + * + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param[in] width bitmap width + * @param[in] height bitmap height + * @param[in] rotation bitmap rotation + * @param bitmap pointer to XBM bitmap data + */ + +void canvas_draw_xbm_ex( + Canvas* canvas, + int32_t x, + int32_t y, + size_t width, + size_t height, + IconRotation rotation, + const uint8_t* bitmap_data); + /** Draw dot at x,y * * @param canvas Canvas instance diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 3e8c6e83a34..20954993806 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -2,10 +2,42 @@ #include -#define ISO14443_4_BLOCK_PCB (1U << 1) -#define ISO14443_4_BLOCK_PCB_I (0U) -#define ISO14443_4_BLOCK_PCB_R (5U << 5) -#define ISO14443_4_BLOCK_PCB_S (3U << 6) +#define ISO14443_4_BLOCK_PCB (1U << 1) +#define ISO14443_4_BLOCK_PCB_MASK (0x03) + +#define ISO14443_4_BLOCK_PCB_I (0U) +#define ISO14443_4_BLOCK_PCB_I_NAD_OFFSET (2) +#define ISO14443_4_BLOCK_PCB_I_CID_OFFSET (3) +#define ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET (4) +#define ISO14443_4_BLOCK_PCB_I_NAD_MASK (1U << ISO14443_4_BLOCK_PCB_I_NAD_OFFSET) +#define ISO14443_4_BLOCK_PCB_I_CID_MASK (1U << ISO14443_4_BLOCK_PCB_I_CID_OFFSET) +#define ISO14443_4_BLOCK_PCB_I_CHAIN_MASK (1U << ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET) + +#define ISO14443_4_BLOCK_PCB_R_MASK (5U << 5) +#define ISO14443_4_BLOCK_PCB_R_NACK_OFFSET (4) +#define ISO14443_4_BLOCK_PCB_R_CID_OFFSET (3) +#define ISO14443_4_BLOCK_PCB_R_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) +#define ISO14443_4_BLOCK_PCB_R_NACK_MASK (1U << ISO14443_4_BLOCK_PCB_R_NACK_OFFSET) + +#define ISO14443_4_BLOCK_PCB_S_MASK (3U << 6) +#define ISO14443_4_BLOCK_PCB_S_CID_OFFSET (3) +#define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET (4) +#define ISO14443_4_BLOCK_PCB_S_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) +#define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK (3U << ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET) + +#define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & mask) == mask) + +#define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK) + +#define ISO14443_4_BLOCK_PCB_IS_S_BLOCK(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_S_MASK) + +#define ISO14443_4_BLOCK_PCB_IS_CHAIN_ACTIVE(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_I_CHAIN_MASK) + +#define ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(pcb) \ + ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_NACK_MASK) struct Iso14443_4Layer { uint8_t pcb; @@ -31,9 +63,31 @@ void iso14443_4_layer_free(Iso14443_4Layer* instance) { void iso14443_4_layer_reset(Iso14443_4Layer* instance) { furi_assert(instance); + instance->pcb_prev = 0; instance->pcb = ISO14443_4_BLOCK_PCB_I | ISO14443_4_BLOCK_PCB; } +void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool CID_present) { + uint8_t block_pcb = instance->pcb & ISO14443_4_BLOCK_PCB_MASK; + instance->pcb = ISO14443_4_BLOCK_PCB_I | (chaining << ISO14443_4_BLOCK_PCB_I_CHAIN_OFFSET) | + (CID_present << ISO14443_4_BLOCK_PCB_I_CID_OFFSET) | block_pcb; +} + +void iso14443_4_layer_set_r_block(Iso14443_4Layer* instance, bool acknowledged, bool CID_present) { + furi_assert(instance); + uint8_t block_pcb = instance->pcb & ISO14443_4_BLOCK_PCB_MASK; + instance->pcb = ISO14443_4_BLOCK_PCB_R_MASK | + (!acknowledged << ISO14443_4_BLOCK_PCB_R_NACK_OFFSET) | + (CID_present << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) | block_pcb; +} + +void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool CID_present) { + furi_assert(instance); + uint8_t des_wtx = !deselect ? (ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK) : 0; + instance->pcb = ISO14443_4_BLOCK_PCB_S_MASK | des_wtx | + (CID_present << ISO14443_4_BLOCK_PCB_S_CID_OFFSET) | ISO14443_4_BLOCK_PCB; +} + void iso14443_4_layer_encode_block( Iso14443_4Layer* instance, const BitBuffer* input_data, @@ -46,6 +100,11 @@ void iso14443_4_layer_encode_block( iso14443_4_layer_update_pcb(instance); } +static inline uint8_t iso14443_4_layer_get_response_pcb(const BitBuffer* block_data) { + const uint8_t* data = bit_buffer_get_data(block_data); + return data[0]; +} + bool iso14443_4_layer_decode_block( Iso14443_4Layer* instance, BitBuffer* output_data, @@ -55,9 +114,26 @@ bool iso14443_4_layer_decode_block( bool ret = false; do { - if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; - bit_buffer_copy_right(output_data, block_data, 1); - ret = true; + if(ISO14443_4_BLOCK_PCB_IS_R_BLOCK(instance->pcb_prev)) { + const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data); + ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && + (!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb)); + instance->pcb &= ISO14443_4_BLOCK_PCB_MASK; + iso14443_4_layer_update_pcb(instance); + } else if(ISO14443_4_BLOCK_PCB_IS_CHAIN_ACTIVE(instance->pcb_prev)) { + const uint8_t response_pcb = iso14443_4_layer_get_response_pcb(block_data); + ret = (ISO14443_4_BLOCK_PCB_IS_R_BLOCK(response_pcb)) && + (!ISO14443_4_BLOCK_PCB_R_NACK_ACTIVE(response_pcb)); + instance->pcb &= ~(ISO14443_4_BLOCK_PCB_I_CHAIN_MASK); + } else if(ISO14443_4_BLOCK_PCB_IS_S_BLOCK(instance->pcb_prev)) { + ret = bit_buffer_starts_with_byte(block_data, instance->pcb_prev); + if(bit_buffer_get_size_bytes(block_data) > 1) + bit_buffer_copy_right(output_data, block_data, 1); + } else { + if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; + bit_buffer_copy_right(output_data, block_data, 1); + ret = true; + } } while(false); return ret; diff --git a/lib/nfc/helpers/iso14443_4_layer.h b/lib/nfc/helpers/iso14443_4_layer.h index 437c2e8a651..67a7f37fe24 100644 --- a/lib/nfc/helpers/iso14443_4_layer.h +++ b/lib/nfc/helpers/iso14443_4_layer.h @@ -14,6 +14,10 @@ void iso14443_4_layer_free(Iso14443_4Layer* instance); void iso14443_4_layer_reset(Iso14443_4Layer* instance); +void iso14443_4_layer_set_i_block(Iso14443_4Layer* instance, bool chaining, bool CID_present); +void iso14443_4_layer_set_r_block(Iso14443_4Layer* instance, bool acknowledged, bool CID_present); +void iso14443_4_layer_set_s_block(Iso14443_4Layer* instance, bool deselect, bool CID_present); + void iso14443_4_layer_encode_block( Iso14443_4Layer* instance, const BitBuffer* input_data, diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c index 95612bf54d0..32cc8f19870 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -84,6 +84,15 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) iso14443_3a_event->type == Iso14443_3aListenerEventTypeHalted || iso14443_3a_event->type == Iso14443_3aListenerEventTypeFieldOff) { instance->state = Iso14443_4aListenerStateIdle; + + instance->iso14443_4a_event.type = iso14443_3a_event->type == + Iso14443_3aListenerEventTypeHalted ? + Iso14443_4aListenerEventTypeHalted : + Iso14443_4aListenerEventTypeFieldOff; + + if(instance->callback) { + command = instance->callback(instance->generic_event, instance->context); + } command = NfcCommandContinue; } diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h index ba649847b27..04f0b197af5 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.h @@ -12,6 +12,7 @@ typedef struct Iso14443_4aListener Iso14443_4aListener; typedef enum { Iso14443_4aListenerEventTypeHalted, + Iso14443_4aListenerEventTypeFieldOff, Iso14443_4aListenerEventTypeReceivedData, } Iso14443_4aListenerEventType; diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h index fef565e5149..80a4c154001 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller.h @@ -56,6 +56,69 @@ Iso14443_4aError iso14443_4a_poller_send_block( const BitBuffer* tx_buffer, BitBuffer* rx_buffer); +/** + * @brief Transmit and receive Iso14443_4a chained block in poller mode. Also it + * automatically modifies PCB packet byte with appropriate bits then resets them back + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer. The fwt parameter is calculated during activation procedure. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_chain_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Transmit Iso14443_4a R-block in poller mode. This block never contains + * data, but can contain CID and NAD, therefore in tx_buffer only two bytes can be added. + * The first one will represent CID, the second one will represent NAD. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with R-block repsonse + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] acknowledged Sets appropriate bit in PCB byte. True - ACK, false - NAK + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_receive_ready_block( + Iso14443_4aPoller* instance, + bool acknowledged, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + +/** + * @brief Transmit Iso14443_4a S-block in poller mode. S-block used to exchange control + * information between the card and the reader. Two different types of S-blocks + * are defined: + * - Waiting time extension containing a 1 byte long INF field and (deselect = false) + * - DESELECT containing no INF field (deselect = true) + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with R-block repsonse + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] deselect Sets appropriate bit in PCB byte. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @return Iso14443_4aErrorNone on success, an error code on failure. + */ +Iso14443_4aError iso14443_4a_poller_send_supervisory_block( + Iso14443_4aPoller* instance, + bool deselect, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer); + /** * @brief Send HALT command to the card. * diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index a9453b03907..427973f4af9 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -114,3 +114,34 @@ Iso14443_4aError iso14443_4a_poller_send_block( return error; } + +Iso14443_4aError iso14443_4a_poller_send_chain_block( + Iso14443_4aPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + iso14443_4_layer_set_i_block(instance->iso14443_4_layer, true, false); + Iso14443_4aError error = iso14443_4a_poller_send_block(instance, tx_buffer, rx_buffer); + return error; +} + +Iso14443_4aError iso14443_4a_poller_send_receive_ready_block( + Iso14443_4aPoller* instance, + bool acknowledged, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + bool CID_present = bit_buffer_get_size_bytes(tx_buffer) != 0; + iso14443_4_layer_set_r_block(instance->iso14443_4_layer, acknowledged, CID_present); + Iso14443_4aError error = iso14443_4a_poller_send_block(instance, tx_buffer, rx_buffer); + return error; +} + +Iso14443_4aError iso14443_4a_poller_send_supervisory_block( + Iso14443_4aPoller* instance, + bool deselect, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { + bool CID_present = bit_buffer_get_size_bytes(tx_buffer) != 0; + iso14443_4_layer_set_s_block(instance->iso14443_4_layer, deselect, CID_present); + Iso14443_4aError error = iso14443_4a_poller_send_block(instance, tx_buffer, rx_buffer); + return error; +} diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 2b04e3e4039..9b2160d53df 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.0,, +Version,+,77.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -746,6 +746,7 @@ Function,+,canvas_draw_str,void,"Canvas*, int32_t, int32_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, int32_t, int32_t, size_t, size_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" +Function,+,canvas_draw_xbm_ex,void,"Canvas*, int32_t, int32_t, size_t, size_t, IconRotation, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,size_t,"Canvas*, uint16_t" Function,+,canvas_height,size_t,const Canvas* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 71d9213d85d..b5dab0f281e 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.0,, +Version,+,77.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -823,6 +823,7 @@ Function,+,canvas_draw_str,void,"Canvas*, int32_t, int32_t, const char*" Function,+,canvas_draw_str_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" Function,+,canvas_draw_triangle,void,"Canvas*, int32_t, int32_t, size_t, size_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" +Function,+,canvas_draw_xbm_ex,void,"Canvas*, int32_t, int32_t, size_t, size_t, IconRotation, const uint8_t*" Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,size_t,"Canvas*, uint16_t" Function,+,canvas_height,size_t,const Canvas* @@ -2125,6 +2126,9 @@ Function,+,iso14443_4a_load,_Bool,"Iso14443_4aData*, FlipperFormat*, uint32_t" Function,+,iso14443_4a_poller_halt,Iso14443_4aError,Iso14443_4aPoller* Function,+,iso14443_4a_poller_read_ats,Iso14443_4aError,"Iso14443_4aPoller*, Iso14443_4aAtsData*" Function,+,iso14443_4a_poller_send_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*" +Function,+,iso14443_4a_poller_send_chain_block,Iso14443_4aError,"Iso14443_4aPoller*, const BitBuffer*, BitBuffer*" +Function,+,iso14443_4a_poller_send_receive_ready_block,Iso14443_4aError,"Iso14443_4aPoller*, _Bool, const BitBuffer*, BitBuffer*" +Function,+,iso14443_4a_poller_send_supervisory_block,Iso14443_4aError,"Iso14443_4aPoller*, _Bool, const BitBuffer*, BitBuffer*" Function,+,iso14443_4a_reset,void,Iso14443_4aData* Function,+,iso14443_4a_save,_Bool,"const Iso14443_4aData*, FlipperFormat*" Function,+,iso14443_4a_set_uid,_Bool,"Iso14443_4aData*, const uint8_t*, size_t" From 57c438d91a87d53512fc0925187526e72d047889 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 14 Oct 2024 19:26:17 +0100 Subject: [PATCH 25/36] heap: increased size (#3924) * reduced reserved memory size for system stack; added temporary markup to monitor usage * fbt: relink elf file on linker script change; removed debug memory fill * Make PVS Happy * Make doxygen happy Co-authored-by: Aleksandr Kutuzov --- applications/services/gui/canvas.h | 15 +++++++-------- firmware.scons | 1 + lib/nfc/helpers/iso14443_4_layer.c | 2 +- .../protocols/iso14443_4a/iso14443_4a_listener.c | 5 +---- targets/f7/stm32wb55xx_flash.ld | 2 +- targets/f7/stm32wb55xx_ram_fw.ld | 2 +- 6 files changed, 12 insertions(+), 15 deletions(-) diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index 308d17fc31e..561bc2ab820 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -289,15 +289,14 @@ void canvas_draw_xbm( /** Draw rotated XBM bitmap * - * @param canvas Canvas instance - * @param x x coordinate - * @param y y coordinate - * @param[in] width bitmap width - * @param[in] height bitmap height - * @param[in] rotation bitmap rotation - * @param bitmap pointer to XBM bitmap data + * @param canvas Canvas instance + * @param x x coordinate + * @param y y coordinate + * @param[in] width bitmap width + * @param[in] height bitmap height + * @param[in] rotation bitmap rotation + * @param bitmap_data pointer to XBM bitmap data */ - void canvas_draw_xbm_ex( Canvas* canvas, int32_t x, diff --git a/firmware.scons b/firmware.scons index 62b1184ebde..4c5e058739d 100644 --- a/firmware.scons +++ b/firmware.scons @@ -216,6 +216,7 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( sources, LIBS=fwenv["TARGET_CFG"].linker_dependencies, ) +Depends(fwelf, fwenv["LINKER_SCRIPT_PATH"]) # Firmware depends on everything child builders returned # Depends(fwelf, lib_targets) diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 20954993806..4c5dcd6a41c 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -25,7 +25,7 @@ #define ISO14443_4_BLOCK_PCB_S_CID_MASK (1U << ISO14443_4_BLOCK_PCB_R_CID_OFFSET) #define ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_MASK (3U << ISO14443_4_BLOCK_PCB_S_WTX_DESELECT_OFFSET) -#define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & mask) == mask) +#define ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, mask) (((pcb) & (mask)) == (mask)) #define ISO14443_4_BLOCK_PCB_IS_R_BLOCK(pcb) \ ISO14443_4_BLOCK_PCB_BITS_ACTIVE(pcb, ISO14443_4_BLOCK_PCB_R_MASK) diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c index 32cc8f19870..2519fb90c25 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_listener.c @@ -65,10 +65,8 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) if(instance->state == Iso14443_4aListenerStateIdle) { if(bit_buffer_get_size_bytes(rx_buffer) == 2 && bit_buffer_get_byte(rx_buffer, 0) == ISO14443_4A_CMD_READ_ATS) { - if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) != + if(iso14443_4a_listener_send_ats(instance, &instance->data->ats_data) == Iso14443_4aErrorNone) { - command = NfcCommandContinue; - } else { instance->state = Iso14443_4aListenerStateActive; } } @@ -93,7 +91,6 @@ static NfcCommand iso14443_4a_listener_run(NfcGenericEvent event, void* context) if(instance->callback) { command = instance->callback(instance->generic_event, instance->context); } - command = NfcCommandContinue; } return command; diff --git a/targets/f7/stm32wb55xx_flash.ld b/targets/f7/stm32wb55xx_flash.ld index 3fb78964591..524da6fc327 100644 --- a/targets/f7/stm32wb55xx_flash.ld +++ b/targets/f7/stm32wb55xx_flash.ld @@ -3,7 +3,7 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _stack_end = 0x20030000; /* end of RAM */ /* Generate a link error if heap and stack don't fit into RAM */ -_stack_size = 0x1000; /* required amount of stack */ +_stack_size = 0x200; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K diff --git a/targets/f7/stm32wb55xx_ram_fw.ld b/targets/f7/stm32wb55xx_ram_fw.ld index cae30b6e99a..f0e8ad678cd 100644 --- a/targets/f7/stm32wb55xx_ram_fw.ld +++ b/targets/f7/stm32wb55xx_ram_fw.ld @@ -3,7 +3,7 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _stack_end = 0x20030000; /* end of RAM */ /* Generate a link error if heap and stack don't fit into RAM */ -_stack_size = 0x1000; /* required amount of stack */ +_stack_size = 0x200; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K From 8a95cb8d6b84c7ddc7e68e7e4aac831e54b5ab61 Mon Sep 17 00:00:00 2001 From: porta Date: Mon, 14 Oct 2024 21:42:11 +0300 Subject: [PATCH 26/36] [FL-3893] JS modules (#3841) * feat: backport js_gpio from unleashed * feat: backport js_keyboard, TextInputModel::minimum_length from unleashed * fix: api version inconsistency * style: js_gpio * build: fix submodule ._ . * refactor: js_gpio * docs: type declarations for gpio * feat: gpio interrupts * fix: js_gpio freeing, resetting and minor stylistic changes * style: js_gpio * style: mlib array, fixme's * feat: js_gpio adc * feat: js_event_loop * docs: js_event_loop * feat: js_event_loop subscription cancellation * feat: js_event_loop + js_gpio integration * fix: js_event_loop memory leak * feat: stop event loop on back button * test: js: basic, math, event_loop * feat: js_event_loop queue * feat: js linkage to previously loaded plugins * build: fix ci errors * feat: js module ordered teardown * feat: js_gui_defer_free * feat: basic hourglass view * style: JS ASS (Argument Schema for Scripts) * fix: js_event_loop mem leaks and lifetime problems * fix: crashing test and pvs false positives * feat: mjs custom obj destructors, gui submenu view * refactor: yank js_gui_defer_free (yuck) * refactor: maybe_unsubscribe * empty_screen, docs, typing fix-ups * docs: navigation event & demo * feat: submenu setHeader * feat: text_input * feat: text_box * docs: text_box availability * ci: silence irrelevant pvs low priority warning * style: use furistring * style: _get_at -> _safe_get * fix: built-in module name assignment * feat: js_dialog; refactor, optimize: js_gui * docs: js_gui * ci: silence pvs warning: Memory allocation is infallible * style: fix storage spelling * feat: foreign pointer signature checks * feat: js_storage * docs: js_storage * fix: my unit test was breaking other tests ;_; * ci: fix ci? * Make doxygen happy * docs: flipper, math, notification, global * style: review suggestions * style: review fixups * fix: badusb demo script * docs: badusb * ci: add nofl * ci: make linter happy * Bump api version Co-authored-by: Aleksandr Kutuzov --- applications/debug/unit_tests/application.fam | 8 + .../resources/unit_tests/js/basic.js | 4 + .../resources/unit_tests/js/event_loop.js | 30 ++ .../resources/unit_tests/js/math.js | 34 ++ .../resources/unit_tests/js/storage.js | 136 ++++++ .../debug/unit_tests/tests/js/js_test.c | 88 ++++ applications/debug/unit_tests/tests/minunit.h | 5 +- .../debug/unit_tests/unit_test_api_table_i.h | 14 +- .../services/gui/modules/text_input.c | 12 +- .../services/gui/modules/text_input.h | 7 + applications/services/gui/view_dispatcher.c | 22 +- applications/services/gui/view_dispatcher.h | 13 + applications/services/gui/view_dispatcher_i.h | 1 + applications/services/storage/storage.h | 4 +- applications/system/js_app/application.fam | 79 ++- .../examples/apps/Scripts/badusb_demo.js | 71 ++- .../js_app/examples/apps/Scripts/delay.js | 2 +- .../js_app/examples/apps/Scripts/dialog.js | 19 - .../examples/apps/Scripts/event_loop.js | 25 + .../js_app/examples/apps/Scripts/gpio.js | 57 +++ .../js_app/examples/apps/Scripts/gui.js | 77 +++ .../js_app/examples/apps/Scripts/load.js | 2 +- .../js_app/examples/apps/Scripts/load_api.js | 2 +- .../js_app/examples/apps/Scripts/math.js | 45 -- .../js_app/examples/apps/Scripts/notify.js | 2 +- .../js_app/examples/apps/Scripts/submenu.js | 11 - .../js_app/examples/apps/Scripts/textbox.js | 30 -- .../js_app/examples/apps/Scripts/uart_echo.js | 2 +- applications/system/js_app/js_app.c | 2 +- applications/system/js_app/js_modules.c | 114 +++-- applications/system/js_app/js_modules.h | 264 +++++++++- applications/system/js_app/js_thread.c | 17 +- applications/system/js_app/js_thread.h | 8 + .../system/js_app/modules/js_badusb.c | 8 +- .../system/js_app/modules/js_dialog.c | 154 ------ .../modules/js_event_loop/js_event_loop.c | 451 ++++++++++++++++++ .../modules/js_event_loop/js_event_loop.h | 104 ++++ .../js_event_loop/js_event_loop_api_table.cpp | 16 + .../js_event_loop/js_event_loop_api_table_i.h | 4 + .../system/js_app/modules/js_flipper.c | 3 +- .../system/js_app/modules/js_flipper.h | 2 +- applications/system/js_app/modules/js_gpio.c | 345 ++++++++++++++ .../system/js_app/modules/js_gui/dialog.c | 129 +++++ .../js_app/modules/js_gui/empty_screen.c | 12 + .../system/js_app/modules/js_gui/js_gui.c | 348 ++++++++++++++ .../system/js_app/modules/js_gui/js_gui.h | 116 +++++ .../modules/js_gui/js_gui_api_table.cpp | 16 + .../modules/js_gui/js_gui_api_table_i.h | 4 + .../system/js_app/modules/js_gui/loading.c | 12 + .../system/js_app/modules/js_gui/submenu.c | 87 ++++ .../system/js_app/modules/js_gui/text_box.c | 78 +++ .../system/js_app/modules/js_gui/text_input.c | 120 +++++ applications/system/js_app/modules/js_math.c | 4 +- .../system/js_app/modules/js_notification.c | 4 +- .../system/js_app/modules/js_serial.c | 4 +- .../system/js_app/modules/js_storage.c | 383 +++++++++++++++ .../system/js_app/modules/js_submenu.c | 147 ------ applications/system/js_app/modules/js_tests.c | 104 ++++ applications/system/js_app/modules/js_tests.h | 5 + .../system/js_app/modules/js_textbox.c | 219 --------- .../js_app/plugin_api/app_api_table_i.h | 3 +- .../system/js_app/plugin_api/js_plugin_api.h | 4 + .../system/js_app/types/badusb/index.d.ts | 81 ++++ .../system/js_app/types/event_loop/index.d.ts | 70 +++ .../system/js_app/types/flipper/index.d.ts | 14 + applications/system/js_app/types/global.d.ts | 178 +++++++ .../system/js_app/types/gpio/index.d.ts | 45 ++ .../system/js_app/types/gui/dialog.d.ts | 16 + .../system/js_app/types/gui/empty_screen.d.ts | 7 + .../system/js_app/types/gui/index.d.ts | 41 ++ .../system/js_app/types/gui/loading.d.ts | 7 + .../system/js_app/types/gui/submenu.d.ts | 13 + .../system/js_app/types/gui/text_box.d.ts | 14 + .../system/js_app/types/gui/text_input.d.ts | 14 + .../system/js_app/types/math/index.d.ts | 24 + .../js_app/types/notification/index.d.ts | 20 + .../system/js_app/types/serial/index.d.ts | 77 +++ .../system/js_app/types/storage/index.d.ts | 237 +++++++++ .../system/js_app/types/tests/index.d.ts | 8 + documentation/doxygen/Doxyfile.cfg | 2 +- documentation/doxygen/js.dox | 22 +- documentation/images/dialog.png | Bin 0 -> 1377 bytes documentation/images/empty.png | Bin 0 -> 1005 bytes documentation/images/loading.png | Bin 0 -> 1173 bytes documentation/images/submenu.png | Bin 0 -> 1774 bytes documentation/images/text_box.png | Bin 0 -> 2350 bytes documentation/images/text_input.png | Bin 0 -> 2044 bytes documentation/js/js_builtin.md | 12 +- documentation/js/js_dialog.md | 49 -- documentation/js/js_event_loop.md | 144 ++++++ documentation/js/js_gpio.md | 77 +++ documentation/js/js_gui.md | 161 +++++++ documentation/js/js_gui__dialog.md | 53 ++ documentation/js/js_gui__empty_screen.md | 22 + documentation/js/js_gui__loading.md | 23 + documentation/js/js_gui__submenu.md | 37 ++ documentation/js/js_gui__text_box.md | 25 + documentation/js/js_gui__text_input.md | 44 ++ documentation/js/js_submenu.md | 48 -- documentation/js/js_textbox.md | 69 --- fbt_options.py | 1 + furi/core/event_loop.c | 12 + furi/core/event_loop.h | 17 + lib/mjs/mjs_core.c | 1 + lib/mjs/mjs_object.c | 17 +- lib/mjs/mjs_object.h | 5 + lib/mjs/mjs_object_public.h | 8 + targets/f18/api_symbols.csv | 7 +- targets/f18/furi_hal/furi_hal_resources.c | 16 + targets/f18/furi_hal/furi_hal_resources.h | 20 + targets/f7/api_symbols.csv | 7 +- targets/f7/furi_hal/furi_hal_resources.c | 16 + targets/f7/furi_hal/furi_hal_resources.h | 20 + tsconfig.json | 15 + 114 files changed, 4978 insertions(+), 931 deletions(-) create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/basic.js create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/event_loop.js create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/math.js create mode 100644 applications/debug/unit_tests/resources/unit_tests/js/storage.js create mode 100644 applications/debug/unit_tests/tests/js/js_test.c delete mode 100644 applications/system/js_app/examples/apps/Scripts/dialog.js create mode 100644 applications/system/js_app/examples/apps/Scripts/event_loop.js create mode 100644 applications/system/js_app/examples/apps/Scripts/gpio.js create mode 100644 applications/system/js_app/examples/apps/Scripts/gui.js delete mode 100644 applications/system/js_app/examples/apps/Scripts/submenu.js delete mode 100644 applications/system/js_app/examples/apps/Scripts/textbox.js delete mode 100644 applications/system/js_app/modules/js_dialog.c create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop.c create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop.h create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop_api_table.cpp create mode 100644 applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h create mode 100644 applications/system/js_app/modules/js_gpio.c create mode 100644 applications/system/js_app/modules/js_gui/dialog.c create mode 100644 applications/system/js_app/modules/js_gui/empty_screen.c create mode 100644 applications/system/js_app/modules/js_gui/js_gui.c create mode 100644 applications/system/js_app/modules/js_gui/js_gui.h create mode 100644 applications/system/js_app/modules/js_gui/js_gui_api_table.cpp create mode 100644 applications/system/js_app/modules/js_gui/js_gui_api_table_i.h create mode 100644 applications/system/js_app/modules/js_gui/loading.c create mode 100644 applications/system/js_app/modules/js_gui/submenu.c create mode 100644 applications/system/js_app/modules/js_gui/text_box.c create mode 100644 applications/system/js_app/modules/js_gui/text_input.c create mode 100644 applications/system/js_app/modules/js_storage.c delete mode 100644 applications/system/js_app/modules/js_submenu.c create mode 100644 applications/system/js_app/modules/js_tests.c create mode 100644 applications/system/js_app/modules/js_tests.h delete mode 100644 applications/system/js_app/modules/js_textbox.c create mode 100644 applications/system/js_app/types/badusb/index.d.ts create mode 100644 applications/system/js_app/types/event_loop/index.d.ts create mode 100644 applications/system/js_app/types/flipper/index.d.ts create mode 100644 applications/system/js_app/types/global.d.ts create mode 100644 applications/system/js_app/types/gpio/index.d.ts create mode 100644 applications/system/js_app/types/gui/dialog.d.ts create mode 100644 applications/system/js_app/types/gui/empty_screen.d.ts create mode 100644 applications/system/js_app/types/gui/index.d.ts create mode 100644 applications/system/js_app/types/gui/loading.d.ts create mode 100644 applications/system/js_app/types/gui/submenu.d.ts create mode 100644 applications/system/js_app/types/gui/text_box.d.ts create mode 100644 applications/system/js_app/types/gui/text_input.d.ts create mode 100644 applications/system/js_app/types/math/index.d.ts create mode 100644 applications/system/js_app/types/notification/index.d.ts create mode 100644 applications/system/js_app/types/serial/index.d.ts create mode 100644 applications/system/js_app/types/storage/index.d.ts create mode 100644 applications/system/js_app/types/tests/index.d.ts create mode 100644 documentation/images/dialog.png create mode 100644 documentation/images/empty.png create mode 100644 documentation/images/loading.png create mode 100644 documentation/images/submenu.png create mode 100644 documentation/images/text_box.png create mode 100644 documentation/images/text_input.png delete mode 100644 documentation/js/js_dialog.md create mode 100644 documentation/js/js_event_loop.md create mode 100644 documentation/js/js_gpio.md create mode 100644 documentation/js/js_gui.md create mode 100644 documentation/js/js_gui__dialog.md create mode 100644 documentation/js/js_gui__empty_screen.md create mode 100644 documentation/js/js_gui__loading.md create mode 100644 documentation/js/js_gui__submenu.md create mode 100644 documentation/js/js_gui__text_box.md create mode 100644 documentation/js/js_gui__text_input.md delete mode 100644 documentation/js/js_submenu.md delete mode 100644 documentation/js/js_textbox.md create mode 100644 tsconfig.json diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index c87305847af..dec3283e4e7 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -221,6 +221,14 @@ App( requires=["unit_tests"], ) +App( + appid="test_js", + sources=["tests/common/*.c", "tests/js/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests", "js_app"], +) + App( appid="test_strint", sources=["tests/common/*.c", "tests/strint/*.c"], diff --git a/applications/debug/unit_tests/resources/unit_tests/js/basic.js b/applications/debug/unit_tests/resources/unit_tests/js/basic.js new file mode 100644 index 00000000000..0927595a2c2 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/basic.js @@ -0,0 +1,4 @@ +let tests = require("tests"); + +tests.assert_eq(1337, 1337); +tests.assert_eq("hello", "hello"); diff --git a/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js b/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js new file mode 100644 index 00000000000..0437b829325 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/event_loop.js @@ -0,0 +1,30 @@ +let tests = require("tests"); +let event_loop = require("event_loop"); + +let ext = { + i: 0, + received: false, +}; + +let queue = event_loop.queue(16); + +event_loop.subscribe(queue.input, function (_, item, tests, ext) { + tests.assert_eq(123, item); + ext.received = true; +}, tests, ext); + +event_loop.subscribe(event_loop.timer("periodic", 1), function (_, _item, queue, counter, ext) { + ext.i++; + queue.send(123); + if (counter === 10) + event_loop.stop(); + return [queue, counter + 1, ext]; +}, queue, 1, ext); + +event_loop.subscribe(event_loop.timer("oneshot", 1000), function (_, _item, tests) { + tests.fail("event loop was not stopped"); +}, tests); + +event_loop.run(); +tests.assert_eq(10, ext.i); +tests.assert_eq(true, ext.received); diff --git a/applications/debug/unit_tests/resources/unit_tests/js/math.js b/applications/debug/unit_tests/resources/unit_tests/js/math.js new file mode 100644 index 00000000000..ea8d80f9140 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/math.js @@ -0,0 +1,34 @@ +let tests = require("tests"); +let math = require("math"); + +// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 + +// basics +tests.assert_float_close(5, math.abs(-5), math.EPSILON); +tests.assert_float_close(0.5, math.abs(-0.5), math.EPSILON); +tests.assert_float_close(5, math.abs(5), math.EPSILON); +tests.assert_float_close(0.5, math.abs(0.5), math.EPSILON); +tests.assert_float_close(3, math.cbrt(27), math.EPSILON); +tests.assert_float_close(6, math.ceil(5.3), math.EPSILON); +tests.assert_float_close(31, math.clz32(1), math.EPSILON); +tests.assert_float_close(5, math.floor(5.7), math.EPSILON); +tests.assert_float_close(5, math.max(3, 5), math.EPSILON); +tests.assert_float_close(3, math.min(3, 5), math.EPSILON); +tests.assert_float_close(-1, math.sign(-5), math.EPSILON); +tests.assert_float_close(5, math.trunc(5.7), math.EPSILON); + +// trig +tests.assert_float_close(1.0471975511965976, math.acos(0.5), math.EPSILON); +tests.assert_float_close(1.3169578969248166, math.acosh(2), math.EPSILON); +tests.assert_float_close(0.5235987755982988, math.asin(0.5), math.EPSILON); +tests.assert_float_close(1.4436354751788103, math.asinh(2), math.EPSILON); +tests.assert_float_close(0.7853981633974483, math.atan(1), math.EPSILON); +tests.assert_float_close(0.7853981633974483, math.atan2(1, 1), math.EPSILON); +tests.assert_float_close(0.5493061443340549, math.atanh(0.5), math.EPSILON); +tests.assert_float_close(-1, math.cos(math.PI), math.EPSILON * 18); // Error 3.77475828372553223744e-15 +tests.assert_float_close(1, math.sin(math.PI / 2), math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 + +// powers +tests.assert_float_close(5, math.sqrt(25), math.EPSILON); +tests.assert_float_close(8, math.pow(2, 3), math.EPSILON); +tests.assert_float_close(2.718281828459045, math.exp(1), math.EPSILON * 2); // Error 4.44089209850062616169e-16 diff --git a/applications/debug/unit_tests/resources/unit_tests/js/storage.js b/applications/debug/unit_tests/resources/unit_tests/js/storage.js new file mode 100644 index 00000000000..872b29cfbc4 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/js/storage.js @@ -0,0 +1,136 @@ +let storage = require("storage"); +let tests = require("tests"); + +let baseDir = "/ext/.tmp/unit_tests"; + +tests.assert_eq(true, storage.rmrf(baseDir)); +tests.assert_eq(true, storage.makeDirectory(baseDir)); + +// write +let file = storage.openFile(baseDir + "/helloworld", "w", "create_always"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.write("Hello, World!")); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// read +file = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.size()); +tests.assert_eq("Hello, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// seek +file = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.isOpen()); +tests.assert_eq(13, file.size()); +tests.assert_eq("Hello, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.seekAbsolute(1)); +tests.assert_eq(true, file.seekRelative(2)); +tests.assert_eq(3, file.tell()); +tests.assert_eq(false, file.eof()); +tests.assert_eq("lo, World!", file.read("ascii", 128)); +tests.assert_eq(true, file.eof()); +tests.assert_eq(true, file.close()); +tests.assert_eq(false, file.isOpen()); + +// byte-level copy +let src = storage.openFile(baseDir + "/helloworld", "r", "open_existing"); +let dst = storage.openFile(baseDir + "/helloworld2", "rw", "create_always"); +tests.assert_eq(true, !!src); +tests.assert_eq(true, src.isOpen()); +tests.assert_eq(true, !!dst); +tests.assert_eq(true, dst.isOpen()); +tests.assert_eq(true, src.copyTo(dst, 10)); +tests.assert_eq(true, dst.seekAbsolute(0)); +tests.assert_eq("Hello, Wor", dst.read("ascii", 128)); +tests.assert_eq(true, src.copyTo(dst, 3)); +tests.assert_eq(true, dst.seekAbsolute(0)); +tests.assert_eq("Hello, World!", dst.read("ascii", 128)); +tests.assert_eq(true, src.eof()); +tests.assert_eq(true, src.close()); +tests.assert_eq(false, src.isOpen()); +tests.assert_eq(true, dst.eof()); +tests.assert_eq(true, dst.close()); +tests.assert_eq(false, dst.isOpen()); + +// truncate +tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld2")); +file = storage.openFile(baseDir + "/helloworld2", "w", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq(true, file.seekAbsolute(5)); +tests.assert_eq(true, file.truncate()); +tests.assert_eq(true, file.close()); +file = storage.openFile(baseDir + "/helloworld2", "r", "open_existing"); +tests.assert_eq(true, !!file); +tests.assert_eq("Hello", file.read("ascii", 128)); +tests.assert_eq(true, file.close()); + +// existence +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld2")); +tests.assert_eq(false, storage.fileExists(baseDir + "/sus_amogus_123")); +tests.assert_eq(false, storage.directoryExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir)); +tests.assert_eq(true, storage.directoryExists(baseDir)); +tests.assert_eq(true, storage.fileOrDirExists(baseDir)); +tests.assert_eq(true, storage.remove(baseDir + "/helloworld2")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld2")); + +// stat +let stat = storage.stat(baseDir + "/helloworld"); +tests.assert_eq(true, !!stat); +tests.assert_eq(baseDir + "/helloworld", stat.path); +tests.assert_eq(false, stat.isDirectory); +tests.assert_eq(13, stat.size); + +// rename +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.rename(baseDir + "/helloworld", baseDir + "/helloworld123")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.rename(baseDir + "/helloworld123", baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); + +// copy +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(false, storage.fileExists(baseDir + "/helloworld123")); +tests.assert_eq(true, storage.copy(baseDir + "/helloworld", baseDir + "/helloworld123")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld")); +tests.assert_eq(true, storage.fileExists(baseDir + "/helloworld123")); + +// next avail +tests.assert_eq("helloworld1", storage.nextAvailableFilename(baseDir, "helloworld", "", 20)); + +// fs info +let fsInfo = storage.fsInfo("/ext"); +tests.assert_eq(true, !!fsInfo); +tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace); // idk \(-_-)/ +fsInfo = storage.fsInfo("/int"); +tests.assert_eq(true, !!fsInfo); +tests.assert_eq(true, fsInfo.freeSpace < fsInfo.totalSpace); + +// path operations +tests.assert_eq(true, storage.arePathsEqual("/ext/test", "/ext/Test")); +tests.assert_eq(false, storage.arePathsEqual("/ext/test", "/ext/Testttt")); +tests.assert_eq(true, storage.isSubpathOf("/ext/test", "/ext/test/sub")); +tests.assert_eq(false, storage.isSubpathOf("/ext/test/sub", "/ext/test")); + +// dir +let entries = storage.readDirectory(baseDir); +tests.assert_eq(true, !!entries); +// FIXME: (-nofl) this test suite assumes that files are listed by +// `readDirectory` in the exact order that they were created, which is not +// something that is actually guaranteed. +// Possible solution: sort and compare the array. +tests.assert_eq("helloworld", entries[0].path); +tests.assert_eq("helloworld123", entries[1].path); + +tests.assert_eq(true, storage.rmrf(baseDir)); +tests.assert_eq(true, storage.makeDirectory(baseDir)); diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c new file mode 100644 index 00000000000..af590e89956 --- /dev/null +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -0,0 +1,88 @@ +#include "../test.h" // IWYU pragma: keep + +#include +#include +#include + +#include +#include + +#include + +#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js") + +typedef enum { + JsTestsFinished = 1, + JsTestsError = 2, +} JsTestFlag; + +typedef struct { + FuriEventFlag* event_flags; + FuriString* error_string; +} JsTestCallbackContext; + +static void js_test_callback(JsThreadEvent event, const char* msg, void* param) { + JsTestCallbackContext* context = param; + if(event == JsThreadEventPrint) { + FURI_LOG_I("js_test", "%s", msg); + } else if(event == JsThreadEventError || event == JsThreadEventErrorTrace) { + context->error_string = furi_string_alloc_set_str(msg); + furi_event_flag_set(context->event_flags, JsTestsFinished | JsTestsError); + } else if(event == JsThreadEventDone) { + furi_event_flag_set(context->event_flags, JsTestsFinished); + } +} + +static void js_test_run(const char* script_path) { + JsTestCallbackContext* context = malloc(sizeof(JsTestCallbackContext)); + context->event_flags = furi_event_flag_alloc(); + + JsThread* thread = js_thread_run(script_path, js_test_callback, context); + uint32_t flags = furi_event_flag_wait( + context->event_flags, JsTestsFinished, FuriFlagWaitAny, FuriWaitForever); + if(flags & FuriFlagError) { + // getting the flags themselves should not fail + furi_crash(); + } + + FuriString* error_string = context->error_string; + + js_thread_stop(thread); + furi_event_flag_free(context->event_flags); + free(context); + + if(flags & JsTestsError) { + // memory leak: not freeing the FuriString if the tests fail, + // because mu_fail executes a return + // + // who cares tho? + mu_fail(furi_string_get_cstr(error_string)); + } +} + +MU_TEST(js_test_basic) { + js_test_run(JS_SCRIPT_PATH("basic")); +} +MU_TEST(js_test_math) { + js_test_run(JS_SCRIPT_PATH("math")); +} +MU_TEST(js_test_event_loop) { + js_test_run(JS_SCRIPT_PATH("event_loop")); +} +MU_TEST(js_test_storage) { + js_test_run(JS_SCRIPT_PATH("storage")); +} + +MU_TEST_SUITE(test_js) { + MU_RUN_TEST(js_test_basic); + MU_RUN_TEST(js_test_math); + MU_RUN_TEST(js_test_event_loop); + MU_RUN_TEST(js_test_storage); +} + +int run_minunit_test_js(void) { + MU_RUN_SUITE(test_js); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_js) diff --git a/applications/debug/unit_tests/tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h index 9310cfc9c9c..9ca3bb403db 100644 --- a/applications/debug/unit_tests/tests/minunit.h +++ b/applications/debug/unit_tests/tests/minunit.h @@ -31,7 +31,7 @@ extern "C" { #include #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf _snprintf -#define __func__ __FUNCTION__ +#define __func__ __FUNCTION__ //-V1059 #endif #elif defined(__unix__) || defined(__unix) || defined(unix) || \ @@ -56,7 +56,7 @@ extern "C" { #endif #if __GNUC__ >= 5 && !defined(__STDC_VERSION__) -#define __func__ __extension__ __FUNCTION__ +#define __func__ __extension__ __FUNCTION__ //-V1059 #endif #else @@ -102,6 +102,7 @@ void minunit_printf_warning(const char* format, ...); MU__SAFE_BLOCK(minunit_setup = setup_fun; minunit_teardown = teardown_fun;) /* Test runner */ +//-V:MU_RUN_TEST:550 #define MU_RUN_TEST(test) \ MU__SAFE_BLOCK( \ if(minunit_real_timer == 0 && minunit_proc_timer == 0) { \ diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 50524e5b7d0..10b08902256 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -7,7 +7,7 @@ #include #include -#include +#include static constexpr auto unit_tests_api_table = sort(create_array_t( API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)), @@ -33,13 +33,9 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( xQueueGenericSend, BaseType_t, (QueueHandle_t, const void* const, TickType_t, const BaseType_t)), - API_METHOD(furi_event_loop_alloc, FuriEventLoop*, (void)), - API_METHOD(furi_event_loop_free, void, (FuriEventLoop*)), API_METHOD( - furi_event_loop_subscribe_message_queue, - void, - (FuriEventLoop*, FuriMessageQueue*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*)), - API_METHOD(furi_event_loop_unsubscribe, void, (FuriEventLoop*, FuriEventLoopObject*)), - API_METHOD(furi_event_loop_run, void, (FuriEventLoop*)), - API_METHOD(furi_event_loop_stop, void, (FuriEventLoop*)), + js_thread_run, + JsThread*, + (const char* script_path, JsThreadCallback callback, void* context)), + API_METHOD(js_thread_stop, void, (JsThread * worker)), API_VARIABLE(PB_Main_msg, PB_Main_msg_t))); diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c index dc1c9c8c1c7..753f0b3b8af 100644 --- a/applications/services/gui/modules/text_input.c +++ b/applications/services/gui/modules/text_input.c @@ -18,6 +18,7 @@ typedef struct { const char* header; char* text_buffer; size_t text_buffer_size; + size_t minimum_length; bool clear_default_text; TextInputCallback callback; @@ -321,7 +322,7 @@ static void text_input_handle_ok(TextInput* text_input, TextInputModel* model, b model->text_buffer, model->validator_text, model->validator_callback_context))) { model->validator_message_visible = true; furi_timer_start(text_input->timer, furi_kernel_get_tick_frequency() * 4); - } else if(model->callback != 0 && text_length > 0) { + } else if(model->callback != 0 && text_length >= model->minimum_length) { model->callback(model->callback_context); } } else if(selected == BACKSPACE_KEY) { @@ -487,6 +488,7 @@ void text_input_reset(TextInput* text_input) { model->header = ""; model->selected_row = 0; model->selected_column = 0; + model->minimum_length = 1; model->clear_default_text = false; model->text_buffer = NULL; model->text_buffer_size = 0; @@ -531,6 +533,14 @@ void text_input_set_result_callback( true); } +void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length) { + with_view_model( + text_input->view, + TextInputModel * model, + { model->minimum_length = minimum_length; }, + true); +} + void text_input_set_validator( TextInput* text_input, TextInputValidatorCallback callback, diff --git a/applications/services/gui/modules/text_input.h b/applications/services/gui/modules/text_input.h index b6ca6b54f22..b6198f96952 100644 --- a/applications/services/gui/modules/text_input.h +++ b/applications/services/gui/modules/text_input.h @@ -65,6 +65,13 @@ void text_input_set_result_callback( size_t text_buffer_size, bool clear_default_text); +/** + * @brief Sets the minimum length of a TextInput + * @param [in] text_input TextInput + * @param [in] minimum_length Minimum input length + */ +void text_input_set_minimum_length(TextInput* text_input, size_t minimum_length); + void text_input_set_validator( TextInput* text_input, TextInputValidatorCallback callback, diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 63878fc1909..6db4d824120 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -5,6 +5,12 @@ #define VIEW_DISPATCHER_QUEUE_LEN (16U) ViewDispatcher* view_dispatcher_alloc(void) { + ViewDispatcher* dispatcher = view_dispatcher_alloc_ex(furi_event_loop_alloc()); + dispatcher->is_event_loop_owned = true; + return dispatcher; +} + +ViewDispatcher* view_dispatcher_alloc_ex(FuriEventLoop* loop) { ViewDispatcher* view_dispatcher = malloc(sizeof(ViewDispatcher)); view_dispatcher->view_port = view_port_alloc(); @@ -16,7 +22,7 @@ ViewDispatcher* view_dispatcher_alloc(void) { ViewDict_init(view_dispatcher->views); - view_dispatcher->event_loop = furi_event_loop_alloc(); + view_dispatcher->event_loop = loop; view_dispatcher->input_queue = furi_message_queue_alloc(VIEW_DISPATCHER_QUEUE_LEN, sizeof(InputEvent)); @@ -57,7 +63,7 @@ void view_dispatcher_free(ViewDispatcher* view_dispatcher) { furi_message_queue_free(view_dispatcher->input_queue); furi_message_queue_free(view_dispatcher->event_queue); - furi_event_loop_free(view_dispatcher->event_loop); + if(view_dispatcher->is_event_loop_owned) furi_event_loop_free(view_dispatcher->event_loop); // Free dispatcher free(view_dispatcher); } @@ -85,6 +91,7 @@ void view_dispatcher_set_tick_event_callback( ViewDispatcherTickEventCallback callback, uint32_t tick_period) { furi_check(view_dispatcher); + furi_check(view_dispatcher->is_event_loop_owned); view_dispatcher->tick_event_callback = callback; view_dispatcher->tick_period = tick_period; } @@ -106,11 +113,12 @@ void view_dispatcher_run(ViewDispatcher* view_dispatcher) { uint32_t tick_period = view_dispatcher->tick_period == 0 ? FuriWaitForever : view_dispatcher->tick_period; - furi_event_loop_tick_set( - view_dispatcher->event_loop, - tick_period, - view_dispatcher_handle_tick_event, - view_dispatcher); + if(view_dispatcher->is_event_loop_owned) + furi_event_loop_tick_set( + view_dispatcher->event_loop, + tick_period, + view_dispatcher_handle_tick_event, + view_dispatcher); furi_event_loop_run(view_dispatcher->event_loop); diff --git a/applications/services/gui/view_dispatcher.h b/applications/services/gui/view_dispatcher.h index 9fbf897918c..5820bcad3bc 100644 --- a/applications/services/gui/view_dispatcher.h +++ b/applications/services/gui/view_dispatcher.h @@ -47,6 +47,15 @@ typedef void (*ViewDispatcherTickEventCallback)(void* context); */ ViewDispatcher* view_dispatcher_alloc(void); +/** Allocate ViewDispatcher instance with an externally owned event loop. If + * this constructor is used instead of `view_dispatcher_alloc`, the burden of + * freeing the event loop is placed on the caller. + * + * @param loop pointer to FuriEventLoop instance + * @return pointer to ViewDispatcher instance + */ +ViewDispatcher* view_dispatcher_alloc_ex(FuriEventLoop* loop); + /** Free ViewDispatcher instance * * @warning All added views MUST be removed using view_dispatcher_remove_view() @@ -97,6 +106,10 @@ void view_dispatcher_set_navigation_event_callback( /** Set tick event handler * + * @warning Requires the event loop to be owned by the view dispatcher, i.e. + * it should have been instantiated with `view_dispatcher_alloc`, not + * `view_dispatcher_alloc_ex`. + * * @param view_dispatcher ViewDispatcher instance * @param callback ViewDispatcherTickEventCallback * @param tick_period callback call period diff --git a/applications/services/gui/view_dispatcher_i.h b/applications/services/gui/view_dispatcher_i.h index c6c8dc665c1..3d84b549955 100644 --- a/applications/services/gui/view_dispatcher_i.h +++ b/applications/services/gui/view_dispatcher_i.h @@ -14,6 +14,7 @@ DICT_DEF2(ViewDict, uint32_t, M_DEFAULT_OPLIST, View*, M_PTR_OPLIST) // NOLINT struct ViewDispatcher { + bool is_event_loop_owned; FuriEventLoop* event_loop; FuriMessageQueue* input_queue; FuriMessageQueue* event_queue; diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index ea0ff24ade8..c28a5e10f5e 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -377,7 +377,7 @@ void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, Furi * @param storage pointer to a storage API instance. * @param source pointer to a zero-terminated string containing the source path. * @param dest pointer to a zero-terminated string containing the destination path. - * @return FSE_OK if the migration was successfull completed, any other error code on failure. + * @return FSE_OK if the migration was successfully completed, any other error code on failure. */ FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest); @@ -425,7 +425,7 @@ bool storage_common_is_subdir(Storage* storage, const char* parent, const char* /******************* Error Functions *******************/ /** - * @brief Get the textual description of a numeric error identifer. + * @brief Get the textual description of a numeric error identifier. * * @param error_id numeric identifier of the error in question. * @return pointer to a statically allocated zero-terminated string containing the respective error text. diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index a7ae5c7c751..36fd7b16c4a 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -16,11 +16,70 @@ App( ) App( - appid="js_dialog", + appid="js_event_loop", apptype=FlipperAppType.PLUGIN, - entry_point="js_dialog_ep", + entry_point="js_event_loop_ep", requires=["js_app"], - sources=["modules/js_dialog.c"], + sources=[ + "modules/js_event_loop/js_event_loop.c", + "modules/js_event_loop/js_event_loop_api_table.cpp", + ], +) + +App( + appid="js_gui", + apptype=FlipperAppType.PLUGIN, + entry_point="js_gui_ep", + requires=["js_app", "js_event_loop"], + sources=["modules/js_gui/js_gui.c", "modules/js_gui/js_gui_api_table.cpp"], +) + +App( + appid="js_gui__loading", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_loading_ep", + requires=["js_app", "js_gui", "js_event_loop"], + sources=["modules/js_gui/loading.c"], +) + +App( + appid="js_gui__empty_screen", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_empty_screen_ep", + requires=["js_app", "js_gui", "js_event_loop"], + sources=["modules/js_gui/empty_screen.c"], +) + +App( + appid="js_gui__submenu", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_submenu_ep", + requires=["js_app", "js_gui"], + sources=["modules/js_gui/submenu.c"], +) + +App( + appid="js_gui__text_input", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_text_input_ep", + requires=["js_app", "js_gui", "js_event_loop"], + sources=["modules/js_gui/text_input.c"], +) + +App( + appid="js_gui__text_box", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_text_box_ep", + requires=["js_app"], + sources=["modules/js_gui/text_box.c"], +) + +App( + appid="js_gui__dialog", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_dialog_ep", + requires=["js_app"], + sources=["modules/js_gui/dialog.c"], ) App( @@ -48,11 +107,11 @@ App( ) App( - appid="js_submenu", + appid="js_gpio", apptype=FlipperAppType.PLUGIN, - entry_point="js_submenu_ep", - requires=["js_app"], - sources=["modules/js_submenu.c"], + entry_point="js_gpio_ep", + requires=["js_app", "js_event_loop"], + sources=["modules/js_gpio.c"], ) App( @@ -64,9 +123,9 @@ App( ) App( - appid="js_textbox", + appid="js_storage", apptype=FlipperAppType.PLUGIN, - entry_point="js_textbox_ep", + entry_point="js_storage_ep", requires=["js_app"], - sources=["modules/js_textbox.c"], + sources=["modules/js_storage.c"], ) diff --git a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js index 21090f6034b..7284d86b741 100644 --- a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js +++ b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js @@ -1,33 +1,58 @@ let badusb = require("badusb"); let notify = require("notification"); let flipper = require("flipper"); -let dialog = require("dialog"); +let eventLoop = require("event_loop"); +let gui = require("gui"); +let dialog = require("gui/dialog"); -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper", prod_name: "Zero" }); -dialog.message("BadUSB demo", "Press OK to start"); +let views = { + dialog: dialog.makeWith({ + header: "BadUSB demo", + text: "Press OK to start", + center: "Start", + }), +}; -if (badusb.isConnected()) { - notify.blink("green", "short"); - print("USB is connected"); +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfrName: "Flipper", prodName: "Zero" }); - badusb.println("Hello, world!"); +eventLoop.subscribe(views.dialog.input, function (_sub, button, eventLoop, gui) { + if (button !== "center") + return; - badusb.press("CTRL", "a"); - badusb.press("CTRL", "c"); - badusb.press("DOWN"); - delay(1000); - badusb.press("CTRL", "v"); - delay(1000); - badusb.press("CTRL", "v"); + gui.viewDispatcher.sendTo("back"); - badusb.println("1234", 200); + if (badusb.isConnected()) { + notify.blink("green", "short"); + print("USB is connected"); - badusb.println("Flipper Model: " + flipper.getModel()); - badusb.println("Flipper Name: " + flipper.getName()); - badusb.println("Battery level: " + to_string(flipper.getBatteryCharge()) + "%"); + badusb.println("Hello, world!"); - notify.success(); -} else { - print("USB not connected"); - notify.error(); -} + badusb.press("CTRL", "a"); + badusb.press("CTRL", "c"); + badusb.press("DOWN"); + delay(1000); + badusb.press("CTRL", "v"); + delay(1000); + badusb.press("CTRL", "v"); + + badusb.println("1234", 200); + + badusb.println("Flipper Model: " + flipper.getModel()); + badusb.println("Flipper Name: " + flipper.getName()); + badusb.println("Battery level: " + toString(flipper.getBatteryCharge()) + "%"); + + notify.success(); + } else { + print("USB not connected"); + notify.error(); + } + + eventLoop.stop(); +}, eventLoop, gui); + +eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _item, eventLoop) { + eventLoop.stop(); +}, eventLoop); + +gui.viewDispatcher.switchTo(views.dialog); +eventLoop.run(); diff --git a/applications/system/js_app/examples/apps/Scripts/delay.js b/applications/system/js_app/examples/apps/Scripts/delay.js index 9f64abee800..5d8fbe42247 100644 --- a/applications/system/js_app/examples/apps/Scripts/delay.js +++ b/applications/system/js_app/examples/apps/Scripts/delay.js @@ -6,4 +6,4 @@ print("2"); delay(1000) print("3"); delay(1000) -print("end"); \ No newline at end of file +print("end"); diff --git a/applications/system/js_app/examples/apps/Scripts/dialog.js b/applications/system/js_app/examples/apps/Scripts/dialog.js deleted file mode 100644 index 9fc44f8b9e2..00000000000 --- a/applications/system/js_app/examples/apps/Scripts/dialog.js +++ /dev/null @@ -1,19 +0,0 @@ -let dialog = require("dialog"); - -let result1 = dialog.message("Dialog demo", "Press OK to start"); -print(result1); - -let dialog_params = ({ - header: "Test_header", - text: "Test_text", - button_left: "Left", - button_right: "Right", - button_center: "OK" -}); - -let result2 = dialog.custom(dialog_params); -if (result2 === "") { - print("Back is pressed"); -} else { - print(result2, "is pressed"); -} diff --git a/applications/system/js_app/examples/apps/Scripts/event_loop.js b/applications/system/js_app/examples/apps/Scripts/event_loop.js new file mode 100644 index 00000000000..ad2f8a7dc7a --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/event_loop.js @@ -0,0 +1,25 @@ +let eventLoop = require("event_loop"); + +// print a string after 1337 milliseconds +eventLoop.subscribe(eventLoop.timer("oneshot", 1337), function (_subscription, _item) { + print("Hi after 1337 ms"); +}); + +// count up to 5 with a delay of 100ms between increments +eventLoop.subscribe(eventLoop.timer("periodic", 100), function (subscription, _item, counter) { + print("Counter two:", counter); + if (counter === 5) + subscription.cancel(); + return [counter + 1]; +}, 0); + +// count up to 15 with a delay of 100ms between increments +// and stop the program when the count reaches 15 +eventLoop.subscribe(eventLoop.timer("periodic", 100), function (subscription, _item, event_loop, counter) { + print("Counter one:", counter); + if (counter === 15) + event_loop.stop(); + return [event_loop, counter + 1]; +}, eventLoop, 0); + +eventLoop.run(); diff --git a/applications/system/js_app/examples/apps/Scripts/gpio.js b/applications/system/js_app/examples/apps/Scripts/gpio.js new file mode 100644 index 00000000000..f3b4bc121bb --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/gpio.js @@ -0,0 +1,57 @@ +let eventLoop = require("event_loop"); +let gpio = require("gpio"); + +// initialize pins +let led = gpio.get("pc3"); // same as `gpio.get(7)` +let pot = gpio.get("pc0"); // same as `gpio.get(16)` +let button = gpio.get("pc1"); // same as `gpio.get(15)` +led.init({ direction: "out", outMode: "push_pull" }); +pot.init({ direction: "in", inMode: "analog" }); +button.init({ direction: "in", pull: "up", inMode: "interrupt", edge: "falling" }); + +// blink led +print("Commencing blinking (PC3)"); +eventLoop.subscribe(eventLoop.timer("periodic", 1000), function (_, _item, led, state) { + led.write(state); + return [led, !state]; +}, led, true); + +// read potentiometer when button is pressed +print("Press the button (PC1)"); +eventLoop.subscribe(button.interrupt(), function (_, _item, pot) { + print("PC0 is at", pot.read_analog(), "mV"); +}, pot); + +// the program will just exit unless this is here +eventLoop.run(); + +// possible pins https://docs.flipper.net/gpio-and-modules#miFsS +// "PA7" aka 2 +// "PA6" aka 3 +// "PA4" aka 4 +// "PB3" aka 5 +// "PB2" aka 6 +// "PC3" aka 7 +// "PA14" aka 10 +// "PA13" aka 12 +// "PB6" aka 13 +// "PB7" aka 14 +// "PC1" aka 15 +// "PC0" aka 16 +// "PB14" aka 17 + +// possible modes +// { direction: "out", outMode: "push_pull" } +// { direction: "out", outMode: "open_drain" } +// { direction: "out", outMode: "push_pull", altFn: true } +// { direction: "out", outMode: "open_drain", altFn: true } +// { direction: "in", inMode: "analog" } +// { direction: "in", inMode: "plain_digital" } +// { direction: "in", inMode: "interrupt", edge: "rising" } +// { direction: "in", inMode: "interrupt", edge: "falling" } +// { direction: "in", inMode: "interrupt", edge: "both" } +// { direction: "in", inMode: "event", edge: "rising" } +// { direction: "in", inMode: "event", edge: "falling" } +// { direction: "in", inMode: "event", edge: "both" } +// all variants support an optional `pull` field which can either be undefined, +// "up" or "down" diff --git a/applications/system/js_app/examples/apps/Scripts/gui.js b/applications/system/js_app/examples/apps/Scripts/gui.js new file mode 100644 index 00000000000..dd80b5bc4b5 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/gui.js @@ -0,0 +1,77 @@ +// import modules +let eventLoop = require("event_loop"); +let gui = require("gui"); +let loadingView = require("gui/loading"); +let submenuView = require("gui/submenu"); +let emptyView = require("gui/empty_screen"); +let textInputView = require("gui/text_input"); +let textBoxView = require("gui/text_box"); +let dialogView = require("gui/dialog"); + +// declare view instances +let views = { + loading: loadingView.make(), + empty: emptyView.make(), + keyboard: textInputView.makeWith({ + header: "Enter your name", + minLength: 0, + maxLength: 32, + }), + helloDialog: dialogView.makeWith({ + center: "Hi Flipper! :)", + }), + longText: textBoxView.makeWith({ + text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.", + }), + demos: submenuView.makeWith({ + header: "Choose a demo", + items: [ + "Hourglass screen", + "Empty screen", + "Text input & Dialog", + "Text box", + "Exit app", + ], + }), +}; + +// demo selector +eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) { + if (index === 0) { + gui.viewDispatcher.switchTo(views.loading); + // the loading view captures all back events, preventing our navigation callback from firing + // switch to the demo chooser after a second + eventLoop.subscribe(eventLoop.timer("oneshot", 1000), function (_sub, _, gui, views) { + gui.viewDispatcher.switchTo(views.demos); + }, gui, views); + } else if (index === 1) { + gui.viewDispatcher.switchTo(views.empty); + } else if (index === 2) { + gui.viewDispatcher.switchTo(views.keyboard); + } else if (index === 3) { + gui.viewDispatcher.switchTo(views.longText); + } else if (index === 4) { + eventLoop.stop(); + } +}, gui, eventLoop, views); + +// say hi after keyboard input +eventLoop.subscribe(views.keyboard.input, function (_sub, name, gui, views) { + views.helloDialog.set("text", "Hi " + name + "! :)"); + gui.viewDispatcher.switchTo(views.helloDialog); +}, gui, views); + +// go back after the greeting dialog +eventLoop.subscribe(views.helloDialog.input, function (_sub, button, gui, views) { + if (button === "center") + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// go to the demo chooser screen when the back key is pressed +eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views) { + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// run UI +gui.viewDispatcher.switchTo(views.demos); +eventLoop.run(); diff --git a/applications/system/js_app/examples/apps/Scripts/load.js b/applications/system/js_app/examples/apps/Scripts/load.js index dfb110ca59a..813619741af 100644 --- a/applications/system/js_app/examples/apps/Scripts/load.js +++ b/applications/system/js_app/examples/apps/Scripts/load.js @@ -1,3 +1,3 @@ let math = load("/ext/apps/Scripts/load_api.js"); let result = math.add(5, 10); -print(result); \ No newline at end of file +print(result); diff --git a/applications/system/js_app/examples/apps/Scripts/load_api.js b/applications/system/js_app/examples/apps/Scripts/load_api.js index ad3b26e1563..80712c40b0b 100644 --- a/applications/system/js_app/examples/apps/Scripts/load_api.js +++ b/applications/system/js_app/examples/apps/Scripts/load_api.js @@ -1,3 +1,3 @@ ({ add: function (a, b) { return a + b; }, -}) \ No newline at end of file +}) diff --git a/applications/system/js_app/examples/apps/Scripts/math.js b/applications/system/js_app/examples/apps/Scripts/math.js index c5a0bf18d01..63527ea671b 100644 --- a/applications/system/js_app/examples/apps/Scripts/math.js +++ b/applications/system/js_app/examples/apps/Scripts/math.js @@ -22,48 +22,3 @@ print("math.sign(-5):", math.sign(-5)); print("math.sin(math.PI/2):", math.sin(math.PI / 2)); print("math.sqrt(25):", math.sqrt(25)); print("math.trunc(5.7):", math.trunc(5.7)); - -// Unit tests. Please add more if you have time and knowledge. -// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 - -let succeeded = 0; -let failed = 0; - -function test(text, result, expected, epsilon) { - let is_equal = math.is_equal(result, expected, epsilon); - if (is_equal) { - succeeded += 1; - } else { - failed += 1; - print(text, "expected", expected, "got", result); - } -} - -test("math.abs(5)", math.abs(-5), 5, math.EPSILON); -test("math.abs(0.5)", math.abs(-0.5), 0.5, math.EPSILON); -test("math.abs(5)", math.abs(5), 5, math.EPSILON); -test("math.abs(-0.5)", math.abs(0.5), 0.5, math.EPSILON); -test("math.acos(0.5)", math.acos(0.5), 1.0471975511965976, math.EPSILON); -test("math.acosh(2)", math.acosh(2), 1.3169578969248166, math.EPSILON); -test("math.asin(0.5)", math.asin(0.5), 0.5235987755982988, math.EPSILON); -test("math.asinh(2)", math.asinh(2), 1.4436354751788103, math.EPSILON); -test("math.atan(1)", math.atan(1), 0.7853981633974483, math.EPSILON); -test("math.atan2(1, 1)", math.atan2(1, 1), 0.7853981633974483, math.EPSILON); -test("math.atanh(0.5)", math.atanh(0.5), 0.5493061443340549, math.EPSILON); -test("math.cbrt(27)", math.cbrt(27), 3, math.EPSILON); -test("math.ceil(5.3)", math.ceil(5.3), 6, math.EPSILON); -test("math.clz32(1)", math.clz32(1), 31, math.EPSILON); -test("math.floor(5.7)", math.floor(5.7), 5, math.EPSILON); -test("math.max(3, 5)", math.max(3, 5), 5, math.EPSILON); -test("math.min(3, 5)", math.min(3, 5), 3, math.EPSILON); -test("math.pow(2, 3)", math.pow(2, 3), 8, math.EPSILON); -test("math.sign(-5)", math.sign(-5), -1, math.EPSILON); -test("math.sqrt(25)", math.sqrt(25), 5, math.EPSILON); -test("math.trunc(5.7)", math.trunc(5.7), 5, math.EPSILON); -test("math.cos(math.PI)", math.cos(math.PI), -1, math.EPSILON * 18); // Error 3.77475828372553223744e-15 -test("math.exp(1)", math.exp(1), 2.718281828459045, math.EPSILON * 2); // Error 4.44089209850062616169e-16 -test("math.sin(math.PI / 2)", math.sin(math.PI / 2), 1, math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 - -if (failed > 0) { - print("!!!", failed, "Unit tests failed !!!"); -} \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/notify.js b/applications/system/js_app/examples/apps/Scripts/notify.js index 20f60c732e9..dd471650c93 100644 --- a/applications/system/js_app/examples/apps/Scripts/notify.js +++ b/applications/system/js_app/examples/apps/Scripts/notify.js @@ -6,4 +6,4 @@ delay(1000); for (let i = 0; i < 10; i++) { notify.blink("red", "short"); delay(500); -} \ No newline at end of file +} diff --git a/applications/system/js_app/examples/apps/Scripts/submenu.js b/applications/system/js_app/examples/apps/Scripts/submenu.js deleted file mode 100644 index 2455513093f..00000000000 --- a/applications/system/js_app/examples/apps/Scripts/submenu.js +++ /dev/null @@ -1,11 +0,0 @@ -let submenu = require("submenu"); - -submenu.addItem("Item 1", 0); -submenu.addItem("Item 2", 1); -submenu.addItem("Item 3", 2); - -submenu.setHeader("Select an option:"); - -let result = submenu.show(); -// Returns undefined when pressing back -print("Result:", result); diff --git a/applications/system/js_app/examples/apps/Scripts/textbox.js b/applications/system/js_app/examples/apps/Scripts/textbox.js deleted file mode 100644 index 6caf3723478..00000000000 --- a/applications/system/js_app/examples/apps/Scripts/textbox.js +++ /dev/null @@ -1,30 +0,0 @@ -let textbox = require("textbox"); - -// You should set config before adding text -// Focus (start / end), Font (text / hex) -textbox.setConfig("end", "text"); - -// Can make sure it's cleared before showing, in case of reusing in same script -// (Closing textbox already clears the text, but maybe you added more in a loop for example) -textbox.clearText(); - -// Add default text -textbox.addText("Example dynamic updating textbox\n"); - -// Non-blocking, can keep updating text after, can close in JS or in GUI -textbox.show(); - -let i = 0; -while (textbox.isOpen() && i < 20) { - print("console", i++); - - // Add text to textbox buffer - textbox.addText("textbox " + to_string(i) + "\n"); - - delay(500); -} - -// If not closed by user (instead i < 20 is false above), close forcefully -if (textbox.isOpen()) { - textbox.close(); -} diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/uart_echo.js index 60d44d078d7..2a0159b4690 100644 --- a/applications/system/js_app/examples/apps/Scripts/uart_echo.js +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo.js @@ -6,6 +6,6 @@ while (1) { if (rx_data !== undefined) { serial.write(rx_data); let data_view = Uint8Array(rx_data); - print("0x" + to_hex_string(data_view[0])); + print("0x" + toString(data_view[0], 16)); } } \ No newline at end of file diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index d36f3c8dbfe..5de720b4379 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -114,7 +114,7 @@ int32_t js_app(void* arg) { FuriString* start_text = furi_string_alloc_printf("Running %s", furi_string_get_cstr(name)); console_view_print(app->console_view, furi_string_get_cstr(start_text)); - console_view_print(app->console_view, "------------"); + console_view_print(app->console_view, "-------------"); furi_string_free(name); furi_string_free(start_text); diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index 9ab6cb14074..38ff46f752b 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -1,7 +1,11 @@ #include #include "js_modules.h" -#include +#include + #include "modules/js_flipper.h" +#ifdef FW_CFG_unit_tests +#include "modules/js_tests.h" +#endif #define TAG "JS modules" @@ -9,54 +13,72 @@ #define MODULES_PATH "/ext/apps_data/js_app/plugins" typedef struct { - JsModeConstructor create; - JsModeDestructor destroy; + FuriString* name; + const JsModuleConstructor create; + const JsModuleDestructor destroy; void* context; } JsModuleData; -DICT_DEF2(JsModuleDict, FuriString*, FURI_STRING_OPLIST, JsModuleData, M_POD_OPLIST); +// not using: +// - a dict because ordering is required +// - a bptree because it forces a sorted ordering +// - an rbtree because i deemed it more tedious to implement, and with the +// amount of modules in use (under 10 in the overwhelming majority of cases) +// i bet it's going to be slower than a plain array +ARRAY_DEF(JsModuleArray, JsModuleData, M_POD_OPLIST); +#define M_OPL_JsModuleArray_t() ARRAY_OPLIST(JsModuleArray) static const JsModuleDescriptor modules_builtin[] = { - {"flipper", js_flipper_create, NULL}, + {"flipper", js_flipper_create, NULL, NULL}, +#ifdef FW_CFG_unit_tests + {"tests", js_tests_create, NULL, NULL}, +#endif }; struct JsModules { struct mjs* mjs; - JsModuleDict_t module_dict; + JsModuleArray_t modules; PluginManager* plugin_manager; + CompositeApiResolver* resolver; }; JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver) { JsModules* modules = malloc(sizeof(JsModules)); modules->mjs = mjs; - JsModuleDict_init(modules->module_dict); + JsModuleArray_init(modules->modules); modules->plugin_manager = plugin_manager_alloc( PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); + modules->resolver = resolver; + return modules; } -void js_modules_destroy(JsModules* modules) { - JsModuleDict_it_t it; - for(JsModuleDict_it(it, modules->module_dict); !JsModuleDict_end_p(it); - JsModuleDict_next(it)) { - const JsModuleDict_itref_t* module_itref = JsModuleDict_cref(it); - if(module_itref->value.destroy) { - module_itref->value.destroy(module_itref->value.context); +void js_modules_destroy(JsModules* instance) { + for + M_EACH(module, instance->modules, JsModuleArray_t) { + FURI_LOG_T(TAG, "Tearing down %s", furi_string_get_cstr(module->name)); + if(module->destroy) module->destroy(module->context); + furi_string_free(module->name); } - } - plugin_manager_free(modules->plugin_manager); - JsModuleDict_clear(modules->module_dict); - free(modules); + plugin_manager_free(instance->plugin_manager); + JsModuleArray_clear(instance->modules); + free(instance); +} + +JsModuleData* js_find_loaded_module(JsModules* instance, const char* name) { + for + M_EACH(module, instance->modules, JsModuleArray_t) { + if(furi_string_cmp_str(module->name, name) == 0) return module; + } + return NULL; } mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) { - FuriString* module_name = furi_string_alloc_set_str(name); // Check if module is already installed - JsModuleData* module_inst = JsModuleDict_get(modules->module_dict, module_name); + JsModuleData* module_inst = js_find_loaded_module(modules, name); if(module_inst) { //-V547 - furi_string_free(module_name); mjs_prepend_errorf( modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module is already installed", name); return MJS_UNDEFINED; @@ -73,8 +95,11 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le if(strncmp(name, modules_builtin[i].name, name_compare_len) == 0) { JsModuleData module = { - .create = modules_builtin[i].create, .destroy = modules_builtin[i].destroy}; - JsModuleDict_set_at(modules->module_dict, module_name, module); + .create = modules_builtin[i].create, + .destroy = modules_builtin[i].destroy, + .name = furi_string_alloc_set_str(name), + }; + JsModuleArray_push_at(modules->modules, 0, module); module_found = true; FURI_LOG_I(TAG, "Using built-in module %s", name); break; @@ -83,39 +108,57 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le // External module load if(!module_found) { + FuriString* deslashed_name = furi_string_alloc_set_str(name); + furi_string_replace_all_str(deslashed_name, "/", "__"); FuriString* module_path = furi_string_alloc(); - furi_string_printf(module_path, "%s/js_%s.fal", MODULES_PATH, name); - FURI_LOG_I(TAG, "Loading external module %s", furi_string_get_cstr(module_path)); + furi_string_printf( + module_path, "%s/js_%s.fal", MODULES_PATH, furi_string_get_cstr(deslashed_name)); + FURI_LOG_I( + TAG, "Loading external module %s from %s", name, furi_string_get_cstr(module_path)); do { uint32_t plugin_cnt_last = plugin_manager_get_count(modules->plugin_manager); PluginManagerError load_error = plugin_manager_load_single( modules->plugin_manager, furi_string_get_cstr(module_path)); if(load_error != PluginManagerErrorNone) { + FURI_LOG_E( + TAG, + "Module %s load error. It may depend on other modules that are not yet loaded.", + name); break; } const JsModuleDescriptor* plugin = plugin_manager_get_ep(modules->plugin_manager, plugin_cnt_last); furi_assert(plugin); - if(strncmp(name, plugin->name, name_len) != 0) { - FURI_LOG_E(TAG, "Module name missmatch %s", plugin->name); + if(furi_string_cmp_str(deslashed_name, plugin->name) != 0) { + FURI_LOG_E(TAG, "Module name mismatch %s", plugin->name); break; } - JsModuleData module = {.create = plugin->create, .destroy = plugin->destroy}; - JsModuleDict_set_at(modules->module_dict, module_name, module); + JsModuleData module = { + .create = plugin->create, + .destroy = plugin->destroy, + .name = furi_string_alloc_set_str(name), + }; + JsModuleArray_push_at(modules->modules, 0, module); + + if(plugin->api_interface) { + FURI_LOG_I(TAG, "Added module API to composite resolver: %s", plugin->name); + composite_api_resolver_add(modules->resolver, plugin->api_interface); + } module_found = true; } while(0); furi_string_free(module_path); + furi_string_free(deslashed_name); } // Run module constructor mjs_val_t module_object = MJS_UNDEFINED; if(module_found) { - module_inst = JsModuleDict_get(modules->module_dict, module_name); + module_inst = js_find_loaded_module(modules, name); furi_assert(module_inst); if(module_inst->create) { //-V779 - module_inst->context = module_inst->create(modules->mjs, &module_object); + module_inst->context = module_inst->create(modules->mjs, &module_object, modules); } } @@ -123,7 +166,12 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le mjs_prepend_errorf(modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module load fail", name); } - furi_string_free(module_name); - return module_object; } + +void* js_module_get(JsModules* modules, const char* name) { + FuriString* module_name = furi_string_alloc_set_str(name); + JsModuleData* module_inst = js_find_loaded_module(modules, name); + furi_string_free(module_name); + return module_inst ? module_inst->context : NULL; +} diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index 77e50786f47..788715872e6 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -1,4 +1,6 @@ #pragma once + +#include #include "js_thread_i.h" #include #include @@ -7,19 +9,269 @@ #define PLUGIN_APP_ID "js" #define PLUGIN_API_VERSION 1 -typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object); -typedef void (*JsModeDestructor)(void* inst); +/** + * @brief Returns the foreign pointer in `obj["_"]` + */ +#define JS_GET_INST(mjs, obj) mjs_get_ptr(mjs, mjs_get(mjs, obj, INST_PROP_NAME, ~0)) +/** + * @brief Returns the foreign pointer in `this["_"]` + */ +#define JS_GET_CONTEXT(mjs) JS_GET_INST(mjs, mjs_get_this(mjs)) + +/** + * @brief Syntax sugar for constructing an object + * + * @example + * ```c + * mjs_val_t my_obj = mjs_mk_object(mjs); + * JS_ASSIGN_MULTI(mjs, my_obj) { + * JS_FIELD("method1", MJS_MK_FN(js_storage_file_is_open)); + * JS_FIELD("method2", MJS_MK_FN(js_storage_file_is_open)); + * } + * ``` + */ +#define JS_ASSIGN_MULTI(mjs, object) \ + for(struct { \ + struct mjs* mjs; \ + mjs_val_t val; \ + int i; \ + } _ass_multi = {mjs, object, 0}; \ + _ass_multi.i == 0; \ + _ass_multi.i++) +#define JS_FIELD(name, value) mjs_set(_ass_multi.mjs, _ass_multi.val, name, ~0, value) + +/** + * @brief The first word of structures that foreign pointer JS values point to + * + * This is used to detect situations where JS code mistakenly passes an opaque + * foreign pointer of one type as an argument to a native function which expects + * a struct of another type. + * + * It is recommended to use this functionality in conjunction with the following + * convenience verification macros: + * - `JS_ARG_STRUCT()` + * - `JS_ARG_OBJ_WITH_STRUCT()` + * + * @warning In order for the mechanism to work properly, your struct must store + * the magic value in the first word. + */ +typedef enum { + JsForeignMagicStart = 0x15BAD000, + JsForeignMagic_JsEventLoopContract, +} JsForeignMagic; + +// Are you tired of your silly little JS+C glue code functions being 75% +// argument validation code and 25% actual logic? Introducing: ASS (Argument +// Schema for Scripts)! ASS is a set of macros that reduce the typical +// boilerplate code of "check argument count, get arguments, validate arguments, +// extract C values from arguments" down to just one line! + +/** + * When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies + * that the function requires exactly as many arguments as were specified. + */ +#define JS_EXACTLY == +/** + * When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies + * that the function requires at least as many arguments as were specified. + */ +#define JS_AT_LEAST >= + +#define JS_ENUM_MAP(var_name, ...) \ + static const JsEnumMapping var_name##_mapping[] = { \ + {NULL, sizeof(var_name)}, \ + __VA_ARGS__, \ + {NULL, 0}, \ + }; typedef struct { - char* name; - JsModeConstructor create; - JsModeDestructor destroy; -} JsModuleDescriptor; + const char* name; + size_t value; +} JsEnumMapping; + +typedef struct { + void* out; + int (*validator)(mjs_val_t); + void (*converter)(struct mjs*, mjs_val_t*, void* out, const void* extra); + const char* expected_type; + bool (*extended_validator)(struct mjs*, mjs_val_t, const void* extra); + const void* extra_data; +} _js_arg_decl; + +static inline void _js_to_int32(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(int32_t*)out = mjs_get_int32(mjs, *in); +} +#define JS_ARG_INT32(out) ((_js_arg_decl){out, mjs_is_number, _js_to_int32, "number", NULL, NULL}) + +static inline void _js_to_ptr(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(void**)out = mjs_get_ptr(mjs, *in); +} +#define JS_ARG_PTR(out) \ + ((_js_arg_decl){out, mjs_is_foreign, _js_to_ptr, "opaque pointer", NULL, NULL}) + +static inline void _js_to_string(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(const char**)out = mjs_get_string(mjs, in, NULL); +} +#define JS_ARG_STR(out) ((_js_arg_decl){out, mjs_is_string, _js_to_string, "string", NULL, NULL}) + +static inline void _js_to_bool(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + *(bool*)out = !!mjs_get_bool(mjs, *in); +} +#define JS_ARG_BOOL(out) ((_js_arg_decl){out, mjs_is_boolean, _js_to_bool, "boolean", NULL, NULL}) + +static inline void _js_passthrough(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { + UNUSED(extra); + UNUSED(mjs); + *(mjs_val_t*)out = *in; +} +#define JS_ARG_ANY(out) ((_js_arg_decl){out, NULL, _js_passthrough, "any", NULL, NULL}) +#define JS_ARG_OBJ(out) ((_js_arg_decl){out, mjs_is_object, _js_passthrough, "any", NULL, NULL}) +#define JS_ARG_FN(out) \ + ((_js_arg_decl){out, mjs_is_function, _js_passthrough, "function", NULL, NULL}) +#define JS_ARG_ARR(out) ((_js_arg_decl){out, mjs_is_array, _js_passthrough, "array", NULL, NULL}) + +static inline bool _js_validate_struct(struct mjs* mjs, mjs_val_t val, const void* extra) { + JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra; + JsForeignMagic struct_magic = *(JsForeignMagic*)mjs_get_ptr(mjs, val); + return struct_magic == expected_magic; +} +#define JS_ARG_STRUCT(type, out) \ + ((_js_arg_decl){ \ + out, \ + mjs_is_foreign, \ + _js_to_ptr, \ + #type, \ + _js_validate_struct, \ + (void*)JsForeignMagic##_##type}) + +static inline bool _js_validate_obj_w_struct(struct mjs* mjs, mjs_val_t val, const void* extra) { + JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra; + JsForeignMagic struct_magic = *(JsForeignMagic*)JS_GET_INST(mjs, val); + return struct_magic == expected_magic; +} +#define JS_ARG_OBJ_WITH_STRUCT(type, out) \ + ((_js_arg_decl){ \ + out, \ + mjs_is_object, \ + _js_passthrough, \ + #type, \ + _js_validate_obj_w_struct, \ + (void*)JsForeignMagic##_##type}) + +static inline bool _js_validate_enum(struct mjs* mjs, mjs_val_t val, const void* extra) { + for(const JsEnumMapping* mapping = (JsEnumMapping*)extra + 1; mapping->name; mapping++) + if(strcmp(mapping->name, mjs_get_string(mjs, &val, NULL)) == 0) return true; + return false; +} +static inline void + _js_convert_enum(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) { + const JsEnumMapping* mapping = (JsEnumMapping*)extra; + size_t size = mapping->value; // get enum size from first entry + for(mapping++; mapping->name; mapping++) { + if(strcmp(mapping->name, mjs_get_string(mjs, val, NULL)) == 0) { + if(size == 1) + *(uint8_t*)out = mapping->value; + else if(size == 2) + *(uint16_t*)out = mapping->value; + else if(size == 4) + *(uint32_t*)out = mapping->value; + else if(size == 8) + *(uint64_t*)out = mapping->value; + return; + } + } + // unreachable, thanks to _js_validate_enum +} +#define JS_ARG_ENUM(var_name, name) \ + ((_js_arg_decl){ \ + &var_name, \ + mjs_is_string, \ + _js_convert_enum, \ + name " enum", \ + _js_validate_enum, \ + var_name##_mapping}) + +//-V:JS_FETCH_ARGS_OR_RETURN:1008 +/** + * @brief Fetches and validates the arguments passed to a JS function + * + * Example: `int32_t my_arg; JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&my_arg));` + * + * @warning This macro executes `return;` by design in case of an argument count + * mismatch or a validation failure + */ +#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \ + _js_arg_decl _js_args[] = {__VA_ARGS__}; \ + int _js_arg_cnt = COUNT_OF(_js_args); \ + mjs_val_t _js_arg_vals[_js_arg_cnt]; \ + if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + "expected %s%d arguments, got %d", \ + #arg_operator, \ + _js_arg_cnt, \ + mjs_nargs(mjs)); \ + for(int _i = 0; _i < _js_arg_cnt; _i++) { \ + _js_arg_vals[_i] = mjs_arg(mjs, _i); \ + if(_js_args[_i].validator) \ + if(!_js_args[_i].validator(_js_arg_vals[_i])) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + "argument %d: expected %s", \ + _i, \ + _js_args[_i].expected_type); \ + if(_js_args[_i].extended_validator) \ + if(!_js_args[_i].extended_validator(mjs, _js_arg_vals[_i], _js_args[_i].extra_data)) \ + JS_ERROR_AND_RETURN( \ + mjs, \ + MJS_BAD_ARGS_ERROR, \ + "argument %d: expected %s", \ + _i, \ + _js_args[_i].expected_type); \ + _js_args[_i].converter( \ + mjs, &_js_arg_vals[_i], _js_args[_i].out, _js_args[_i].extra_data); \ + } + +/** + * @brief Prepends an error, sets the JS return value to `undefined` and returns + * from the C function + * @warning This macro executes `return;` by design + */ +#define JS_ERROR_AND_RETURN(mjs, error_code, ...) \ + do { \ + mjs_prepend_errorf(mjs, error_code, __VA_ARGS__); \ + mjs_return(mjs, MJS_UNDEFINED); \ + return; \ + } while(0) typedef struct JsModules JsModules; +typedef void* (*JsModuleConstructor)(struct mjs* mjs, mjs_val_t* object, JsModules* modules); +typedef void (*JsModuleDestructor)(void* inst); + +typedef struct { + char* name; + JsModuleConstructor create; + JsModuleDestructor destroy; + const ElfApiInterface* api_interface; +} JsModuleDescriptor; + JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver); void js_modules_destroy(JsModules* modules); mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len); + +/** + * @brief Gets a module instance by its name + * This is useful when a module wants to access a stateful API of another + * module. + * @returns Pointer to module context, NULL if the module is not instantiated + */ +void* js_module_get(JsModules* modules, const char* name); diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 78b6f6ff47d..7e7280e9cb1 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -195,17 +195,11 @@ static void js_require(struct mjs* mjs) { } static void js_global_to_string(struct mjs* mjs) { + int base = 10; + if(mjs_nargs(mjs) > 1) base = mjs_get_int(mjs, mjs_arg(mjs, 1)); double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); char tmp_str[] = "-2147483648"; - itoa(num, tmp_str, 10); - mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); - mjs_return(mjs, ret); -} - -static void js_global_to_hex_string(struct mjs* mjs) { - double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); - char tmp_str[] = "-FFFFFFFF"; - itoa(num, tmp_str, 16); + itoa(num, tmp_str, base); mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); mjs_return(mjs, ret); } @@ -239,8 +233,7 @@ static int32_t js_thread(void* arg) { mjs_val_t global = mjs_get_global(mjs); mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print)); mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay)); - mjs_set(mjs, global, "to_string", ~0, MJS_MK_FN(js_global_to_string)); - mjs_set(mjs, global, "to_hex_string", ~0, MJS_MK_FN(js_global_to_hex_string)); + mjs_set(mjs, global, "toString", ~0, MJS_MK_FN(js_global_to_string)); mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address)); mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require)); @@ -296,8 +289,8 @@ static int32_t js_thread(void* arg) { } } - js_modules_destroy(worker->modules); mjs_destroy(mjs); + js_modules_destroy(worker->modules); composite_api_resolver_free(worker->resolver); diff --git a/applications/system/js_app/js_thread.h b/applications/system/js_app/js_thread.h index 969715ec1a4..581a4491921 100644 --- a/applications/system/js_app/js_thread.h +++ b/applications/system/js_app/js_thread.h @@ -1,5 +1,9 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + typedef struct JsThread JsThread; typedef enum { @@ -14,3 +18,7 @@ typedef void (*JsThreadCallback)(JsThreadEvent event, const char* msg, void* con JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context); void js_thread_stop(JsThread* worker); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 99f8958f761..891bfa2cdfc 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -72,8 +72,8 @@ static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConf } mjs_val_t vid_obj = mjs_get(mjs, arg, "vid", ~0); mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0); - mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfr_name", ~0); - mjs_val_t prod_obj = mjs_get(mjs, arg, "prod_name", ~0); + mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfrName", ~0); + mjs_val_t prod_obj = mjs_get(mjs, arg, "prodName", ~0); if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) { hid_cfg->vid = mjs_get_int32(mjs, vid_obj); @@ -378,7 +378,8 @@ static void js_badusb_println(struct mjs* mjs) { badusb_print(mjs, true); } -static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsBadusbInst* badusb = malloc(sizeof(JsBadusbInst)); mjs_val_t badusb_obj = mjs_mk_object(mjs); mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb)); @@ -409,6 +410,7 @@ static const JsModuleDescriptor js_badusb_desc = { "badusb", js_badusb_create, js_badusb_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_dialog.c b/applications/system/js_app/modules/js_dialog.c deleted file mode 100644 index 34de6d64160..00000000000 --- a/applications/system/js_app/modules/js_dialog.c +++ /dev/null @@ -1,154 +0,0 @@ -#include -#include "../js_modules.h" -#include - -static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { - size_t num_args = mjs_nargs(mjs); - if(num_args != 2) { - return false; - } - mjs_val_t header_obj = mjs_arg(mjs, 0); - mjs_val_t msg_obj = mjs_arg(mjs, 1); - if((!mjs_is_string(header_obj)) || (!mjs_is_string(msg_obj))) { - return false; - } - - size_t arg_len = 0; - *hdr = mjs_get_string(mjs, &header_obj, &arg_len); - if(arg_len == 0) { - *hdr = NULL; - } - - *msg = mjs_get_string(mjs, &msg_obj, &arg_len); - if(arg_len == 0) { - *msg = NULL; - } - - return true; -} - -static void js_dialog_message(struct mjs* mjs) { - const char* dialog_header = NULL; - const char* dialog_msg = NULL; - if(!js_dialog_msg_parse_params(mjs, &dialog_header, &dialog_msg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_buttons(message, NULL, "OK", NULL); - if(dialog_header) { - dialog_message_set_header(message, dialog_header, 64, 3, AlignCenter, AlignTop); - } - if(dialog_msg) { - dialog_message_set_text(message, dialog_msg, 64, 26, AlignCenter, AlignTop); - } - DialogMessageButton result = dialog_message_show(dialogs, message); - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - mjs_return(mjs, mjs_mk_boolean(mjs, result == DialogMessageButtonCenter)); -} - -static void js_dialog_custom(struct mjs* mjs) { - DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); - DialogMessage* message = dialog_message_alloc(); - - bool params_correct = false; - - do { - if(mjs_nargs(mjs) != 1) { - break; - } - mjs_val_t params_obj = mjs_arg(mjs, 0); - if(!mjs_is_object(params_obj)) { - break; - } - - mjs_val_t text_obj = mjs_get(mjs, params_obj, "header", ~0); - size_t arg_len = 0; - const char* text_str = mjs_get_string(mjs, &text_obj, &arg_len); - if(arg_len == 0) { - text_str = NULL; - } - if(text_str) { - dialog_message_set_header(message, text_str, 64, 3, AlignCenter, AlignTop); - } - - text_obj = mjs_get(mjs, params_obj, "text", ~0); - text_str = mjs_get_string(mjs, &text_obj, &arg_len); - if(arg_len == 0) { - text_str = NULL; - } - if(text_str) { - dialog_message_set_text(message, text_str, 64, 26, AlignCenter, AlignTop); - } - - mjs_val_t btn_obj[3] = { - mjs_get(mjs, params_obj, "button_left", ~0), - mjs_get(mjs, params_obj, "button_center", ~0), - mjs_get(mjs, params_obj, "button_right", ~0), - }; - const char* btn_text[3] = {NULL, NULL, NULL}; - - for(uint8_t i = 0; i < 3; i++) { - if(!mjs_is_string(btn_obj[i])) { - continue; - } - btn_text[i] = mjs_get_string(mjs, &btn_obj[i], &arg_len); - if(arg_len == 0) { - btn_text[i] = NULL; - } - } - - dialog_message_set_buttons(message, btn_text[0], btn_text[1], btn_text[2]); - - DialogMessageButton result = dialog_message_show(dialogs, message); - mjs_val_t return_obj = MJS_UNDEFINED; - if(result == DialogMessageButtonLeft) { - return_obj = mjs_mk_string(mjs, btn_text[0], ~0, true); - } else if(result == DialogMessageButtonCenter) { - return_obj = mjs_mk_string(mjs, btn_text[1], ~0, true); - } else if(result == DialogMessageButtonRight) { - return_obj = mjs_mk_string(mjs, btn_text[2], ~0, true); - } else { - return_obj = mjs_mk_string(mjs, "", ~0, true); - } - - mjs_return(mjs, return_obj); - params_correct = true; - } while(0); - - dialog_message_free(message); - furi_record_close(RECORD_DIALOGS); - - if(!params_correct) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - } -} - -static void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { - mjs_val_t dialog_obj = mjs_mk_object(mjs); - mjs_set(mjs, dialog_obj, "message", ~0, MJS_MK_FN(js_dialog_message)); - mjs_set(mjs, dialog_obj, "custom", ~0, MJS_MK_FN(js_dialog_custom)); - *object = dialog_obj; - - return (void*)1; -} - -static const JsModuleDescriptor js_dialog_desc = { - "dialog", - js_dialog_create, - NULL, -}; - -static const FlipperAppPluginDescriptor plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_dialog_desc, -}; - -const FlipperAppPluginDescriptor* js_dialog_ep(void) { - return &plugin_descriptor; -} diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop.c b/applications/system/js_app/modules/js_event_loop/js_event_loop.c new file mode 100644 index 00000000000..c4f0d1beec9 --- /dev/null +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop.c @@ -0,0 +1,451 @@ +#include "js_event_loop.h" +#include "../../js_modules.h" // IWYU pragma: keep +#include +#include + +/** + * @brief Number of arguments that callbacks receive from this module that they can't modify + */ +#define SYSTEM_ARGS 2 + +/** + * @brief Context passed to the generic event callback + */ +typedef struct { + JsEventLoopObjectType object_type; + + struct mjs* mjs; + mjs_val_t callback; + // NOTE: not using an mlib array because resizing is not needed. + mjs_val_t* arguments; + size_t arity; + + JsEventLoopTransformer transformer; + void* transformer_context; +} JsEventLoopCallbackContext; + +/** + * @brief Contains data needed to cancel a subscription + */ +typedef struct { + FuriEventLoop* loop; + JsEventLoopObjectType object_type; + FuriEventLoopObject* object; + JsEventLoopCallbackContext* context; + JsEventLoopContract* contract; + void* subscriptions; // SubscriptionArray_t, which we can't reference in this definition +} JsEventLoopSubscription; + +typedef struct { + FuriEventLoop* loop; + struct mjs* mjs; +} JsEventLoopTickContext; + +ARRAY_DEF(SubscriptionArray, JsEventLoopSubscription*, M_PTR_OPLIST); //-V575 +ARRAY_DEF(ContractArray, JsEventLoopContract*, M_PTR_OPLIST); //-V575 + +/** + * @brief Per-module instance control structure + */ +struct JsEventLoop { + FuriEventLoop* loop; + SubscriptionArray_t subscriptions; + ContractArray_t owned_contracts; //mjs, + &result, + context->callback, + MJS_UNDEFINED, + context->arity, + context->arguments); + + // save returned args for next call + if(mjs_array_length(context->mjs, result) != context->arity - SYSTEM_ARGS) return; + for(size_t i = 0; i < context->arity - SYSTEM_ARGS; i++) { + mjs_disown(context->mjs, &context->arguments[i + SYSTEM_ARGS]); + context->arguments[i + SYSTEM_ARGS] = mjs_array_get(context->mjs, result, i); + mjs_own(context->mjs, &context->arguments[i + SYSTEM_ARGS]); + } +} + +/** + * @brief Handles non-timer events + */ +static bool js_event_loop_callback(void* object, void* param) { + JsEventLoopCallbackContext* context = param; + + if(context->transformer) { + mjs_disown(context->mjs, &context->arguments[1]); + context->arguments[1] = + context->transformer(context->mjs, object, context->transformer_context); + mjs_own(context->mjs, &context->arguments[1]); + } else { + // default behavior: take semaphores and mutexes + switch(context->object_type) { + case JsEventLoopObjectTypeSemaphore: { + FuriSemaphore* semaphore = object; + furi_check(furi_semaphore_acquire(semaphore, 0) == FuriStatusOk); + } break; + default: + // the corresponding check has been performed when we were given the contract + furi_crash(); + } + } + + js_event_loop_callback_generic(param); + + return true; +} + +/** + * @brief Cancels an event subscription + */ +static void js_event_loop_subscription_cancel(struct mjs* mjs) { + JsEventLoopSubscription* subscription = JS_GET_CONTEXT(mjs); + + if(subscription->object_type == JsEventLoopObjectTypeTimer) { + furi_event_loop_timer_stop(subscription->object); + } else { + furi_event_loop_unsubscribe(subscription->loop, subscription->object); + } + + free(subscription->context->arguments); + free(subscription->context); + + // find and remove ourselves from the array + SubscriptionArray_it_t iterator; + for(SubscriptionArray_it(iterator, subscription->subscriptions); + !SubscriptionArray_end_p(iterator); + SubscriptionArray_next(iterator)) { + JsEventLoopSubscription* item = *SubscriptionArray_cref(iterator); + if(item == subscription) break; + } + SubscriptionArray_remove(subscription->subscriptions, iterator); + free(subscription); + + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief Subscribes a JavaScript function to an event + */ +static void js_event_loop_subscribe(struct mjs* mjs) { + JsEventLoop* module = JS_GET_CONTEXT(mjs); + + // get arguments + JsEventLoopContract* contract; + mjs_val_t callback; + JS_FETCH_ARGS_OR_RETURN( + mjs, JS_AT_LEAST, JS_ARG_STRUCT(JsEventLoopContract, &contract), JS_ARG_FN(&callback)); + + // create subscription object + JsEventLoopSubscription* subscription = malloc(sizeof(JsEventLoopSubscription)); + JsEventLoopCallbackContext* context = malloc(sizeof(JsEventLoopCallbackContext)); + subscription->loop = module->loop; + subscription->object_type = contract->object_type; + subscription->context = context; + subscription->subscriptions = module->subscriptions; + if(contract->object_type == JsEventLoopObjectTypeTimer) subscription->contract = contract; + mjs_val_t subscription_obj = mjs_mk_object(mjs); + mjs_set(mjs, subscription_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, subscription)); + mjs_set(mjs, subscription_obj, "cancel", ~0, MJS_MK_FN(js_event_loop_subscription_cancel)); + + // create callback context + context->object_type = contract->object_type; + context->arity = mjs_nargs(mjs) - SYSTEM_ARGS + 2; + context->arguments = calloc(context->arity, sizeof(mjs_val_t)); + context->arguments[0] = subscription_obj; + context->arguments[1] = MJS_UNDEFINED; + for(size_t i = SYSTEM_ARGS; i < context->arity; i++) { + mjs_val_t arg = mjs_arg(mjs, i - SYSTEM_ARGS + 2); + context->arguments[i] = arg; + mjs_own(mjs, &context->arguments[i]); + } + context->mjs = mjs; + context->callback = callback; + mjs_own(mjs, &context->callback); + mjs_own(mjs, &context->arguments[0]); + mjs_own(mjs, &context->arguments[1]); + + // queue and stream contracts must have a transform callback, others are allowed to delegate + // the obvious default behavior to this module + if(contract->object_type == JsEventLoopObjectTypeQueue || + contract->object_type == JsEventLoopObjectTypeStream) { + furi_check(contract->non_timer.transformer); + } + context->transformer = contract->non_timer.transformer; + context->transformer_context = contract->non_timer.transformer_context; + + // subscribe + switch(contract->object_type) { + case JsEventLoopObjectTypeTimer: { + FuriEventLoopTimer* timer = furi_event_loop_timer_alloc( + module->loop, js_event_loop_callback_generic, contract->timer.type, context); + furi_event_loop_timer_start(timer, contract->timer.interval_ticks); + contract->object = timer; + } break; + case JsEventLoopObjectTypeSemaphore: + furi_event_loop_subscribe_semaphore( + module->loop, + contract->object, + contract->non_timer.event, + js_event_loop_callback, + context); + break; + case JsEventLoopObjectTypeQueue: + furi_event_loop_subscribe_message_queue( + module->loop, + contract->object, + contract->non_timer.event, + js_event_loop_callback, + context); + break; + default: + furi_crash("unimplemented"); + } + + subscription->object = contract->object; + SubscriptionArray_push_back(module->subscriptions, subscription); + mjs_return(mjs, subscription_obj); +} + +/** + * @brief Runs the event loop until it is stopped + */ +static void js_event_loop_run(struct mjs* mjs) { + JsEventLoop* module = JS_GET_CONTEXT(mjs); + furi_event_loop_run(module->loop); +} + +/** + * @brief Stops a running event loop + */ +static void js_event_loop_stop(struct mjs* mjs) { + JsEventLoop* module = JS_GET_CONTEXT(mjs); + furi_event_loop_stop(module->loop); +} + +/** + * @brief Creates a timer event that can be subscribed to just like any other + * event + */ +static void js_event_loop_timer(struct mjs* mjs) { + // get arguments + const char* mode_str; + int32_t interval; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&mode_str), JS_ARG_INT32(&interval)); + JsEventLoop* module = JS_GET_CONTEXT(mjs); + + FuriEventLoopTimerType mode; + if(strcasecmp(mode_str, "periodic") == 0) { + mode = FuriEventLoopTimerTypePeriodic; + } else if(strcasecmp(mode_str, "oneshot") == 0) { + mode = FuriEventLoopTimerTypeOnce; + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 0: unknown mode"); + } + + // make timer contract + JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); + *contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeTimer, + .object = NULL, + .timer = + { + .interval_ticks = furi_ms_to_ticks((uint32_t)interval), + .type = mode, + }, + }; + ContractArray_push_back(module->owned_contracts, contract); + mjs_return(mjs, mjs_mk_foreign(mjs, contract)); +} + +/** + * @brief Queue transformer. Takes `mjs_val_t` pointers out of a queue and + * returns their dereferenced value + */ +static mjs_val_t + js_event_loop_queue_transformer(struct mjs* mjs, FuriEventLoopObject* object, void* context) { + UNUSED(context); + mjs_val_t* message_ptr; + furi_check(furi_message_queue_get(object, &message_ptr, 0) == FuriStatusOk); + mjs_val_t message = *message_ptr; + mjs_disown(mjs, message_ptr); + free(message_ptr); + return message; +} + +/** + * @brief Sends a message to a queue + */ +static void js_event_loop_queue_send(struct mjs* mjs) { + // get arguments + mjs_val_t message; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&message)); + JsEventLoopContract* contract = JS_GET_CONTEXT(mjs); + + // send message + mjs_val_t* message_ptr = malloc(sizeof(mjs_val_t)); + *message_ptr = message; + mjs_own(mjs, message_ptr); + furi_message_queue_put(contract->object, &message_ptr, 0); + + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief Creates a queue + */ +static void js_event_loop_queue(struct mjs* mjs) { + // get arguments + int32_t length; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&length)); + JsEventLoop* module = JS_GET_CONTEXT(mjs); + + // make queue contract + JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); + *contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + // we could store `mjs_val_t`s in the queue directly if not for mJS' requirement to have consistent pointers to owned values + .object = furi_message_queue_alloc((size_t)length, sizeof(mjs_val_t*)), + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = js_event_loop_queue_transformer, + }, + }; + ContractArray_push_back(module->owned_contracts, contract); + + // return object with control methods + mjs_val_t queue = mjs_mk_object(mjs); + mjs_set(mjs, queue, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, contract)); + mjs_set(mjs, queue, "input", ~0, mjs_mk_foreign(mjs, contract)); + mjs_set(mjs, queue, "send", ~0, MJS_MK_FN(js_event_loop_queue_send)); + mjs_return(mjs, queue); +} + +static void js_event_loop_tick(void* param) { + JsEventLoopTickContext* context = param; + uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0); + if(flags & FuriFlagError) { + return; + } + if(flags & ThreadEventStop) { + furi_event_loop_stop(context->loop); + mjs_exit(context->mjs); + } +} + +static void* js_event_loop_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + mjs_val_t event_loop_obj = mjs_mk_object(mjs); + JsEventLoop* module = malloc(sizeof(JsEventLoop)); + JsEventLoopTickContext* tick_ctx = malloc(sizeof(JsEventLoopTickContext)); + module->loop = furi_event_loop_alloc(); + tick_ctx->loop = module->loop; + tick_ctx->mjs = mjs; + module->tick_context = tick_ctx; + furi_event_loop_tick_set(module->loop, 10, js_event_loop_tick, tick_ctx); + SubscriptionArray_init(module->subscriptions); + ContractArray_init(module->owned_contracts); + + mjs_set(mjs, event_loop_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module)); + mjs_set(mjs, event_loop_obj, "subscribe", ~0, MJS_MK_FN(js_event_loop_subscribe)); + mjs_set(mjs, event_loop_obj, "run", ~0, MJS_MK_FN(js_event_loop_run)); + mjs_set(mjs, event_loop_obj, "stop", ~0, MJS_MK_FN(js_event_loop_stop)); + mjs_set(mjs, event_loop_obj, "timer", ~0, MJS_MK_FN(js_event_loop_timer)); + mjs_set(mjs, event_loop_obj, "queue", ~0, MJS_MK_FN(js_event_loop_queue)); + + *object = event_loop_obj; + return module; +} + +static void js_event_loop_destroy(void* inst) { + if(inst) { + JsEventLoop* module = inst; + furi_event_loop_stop(module->loop); + + // free subscriptions + SubscriptionArray_it_t sub_iterator; + for(SubscriptionArray_it(sub_iterator, module->subscriptions); + !SubscriptionArray_end_p(sub_iterator); + SubscriptionArray_next(sub_iterator)) { + JsEventLoopSubscription* const* sub = SubscriptionArray_cref(sub_iterator); + free((*sub)->context->arguments); + free((*sub)->context); + free(*sub); + } + SubscriptionArray_clear(module->subscriptions); + + // free owned contracts + ContractArray_it_t iterator; + for(ContractArray_it(iterator, module->owned_contracts); !ContractArray_end_p(iterator); + ContractArray_next(iterator)) { + // unsubscribe object + JsEventLoopContract* contract = *ContractArray_cref(iterator); + if(contract->object_type == JsEventLoopObjectTypeTimer) { + furi_event_loop_timer_stop(contract->object); + } else { + furi_event_loop_unsubscribe(module->loop, contract->object); + } + + // free object + switch(contract->object_type) { + case JsEventLoopObjectTypeTimer: + furi_event_loop_timer_free(contract->object); + break; + case JsEventLoopObjectTypeSemaphore: + furi_semaphore_free(contract->object); + break; + case JsEventLoopObjectTypeQueue: + furi_message_queue_free(contract->object); + break; + default: + furi_crash("unimplemented"); + } + + free(contract); + } + ContractArray_clear(module->owned_contracts); + + furi_event_loop_free(module->loop); + free(module->tick_context); + free(module); + } +} + +extern const ElfApiInterface js_event_loop_hashtable_api_interface; + +static const JsModuleDescriptor js_event_loop_desc = { + "event_loop", + js_event_loop_create, + js_event_loop_destroy, + &js_event_loop_hashtable_api_interface, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_event_loop_desc, +}; + +const FlipperAppPluginDescriptor* js_event_loop_ep(void) { + return &plugin_descriptor; +} + +FuriEventLoop* js_event_loop_get_loop(JsEventLoop* loop) { + // porta: not the proudest function that i ever wrote + furi_check(loop); + return loop->loop; +} diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop.h b/applications/system/js_app/modules/js_event_loop/js_event_loop.h new file mode 100644 index 00000000000..7ae608e349a --- /dev/null +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop.h @@ -0,0 +1,104 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include +#include + +/** + * @file js_event_loop.h + * + * In JS interpreter code, `js_event_loop` always creates and maintains the + * event loop. There are two ways in which other modules can integrate with this + * loop: + * - Via contracts: The user of your module would have to acquire an opaque + * JS value from you and pass it to `js_event_loop`. This is useful for + * events that they user may be interested in. For more info, look at + * `JsEventLoopContract`. Also look at `js_event_loop_get_loop`, which + * you will need to unsubscribe the event loop from your object. + * - Directly: When your module is created, you can acquire an instance of + * `JsEventLoop` which you can use to acquire an instance of + * `FuriEventLoop` that you can manipulate directly, without the JS + * programmer having to pass contracts around. This is useful for + * "behind-the-scenes" events that the user does not need to know about. For + * more info, look at `js_event_loop_get_loop`. + * + * In both cases, your module is responsible for both instantiating, + * unsubscribing and freeing the object that the event loop subscribes to. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct JsEventLoop JsEventLoop; + +typedef enum { + JsEventLoopObjectTypeTimer, + JsEventLoopObjectTypeQueue, + JsEventLoopObjectTypeMutex, + JsEventLoopObjectTypeSemaphore, + JsEventLoopObjectTypeStream, +} JsEventLoopObjectType; + +typedef mjs_val_t ( + *JsEventLoopTransformer)(struct mjs* mjs, FuriEventLoopObject* object, void* context); + +typedef struct { + FuriEventLoopEvent event; + JsEventLoopTransformer transformer; + void* transformer_context; +} JsEventLoopNonTimerContract; + +typedef struct { + FuriEventLoopTimerType type; + uint32_t interval_ticks; +} JsEventLoopTimerContract; + +/** + * @brief Adapter for other JS modules that wish to integrate with the event + * loop JS module + * + * If another module wishes to integrate with `js_event_loop`, it needs to + * implement a function callable from JS that returns an mJS foreign pointer to + * an instance of this structure. This value is then read by `event_loop`'s + * `subscribe` function. + * + * There are two fundamental variants of this structure: + * - `object_type` is `JsEventLoopObjectTypeTimer`: the `timer` field is + * valid, and the `non_timer` field is invalid. + * - `object_type` is something else: the `timer` field is invalid, and the + * `non_timer` field is valid. `non_timer.event` will be passed to + * `furi_event_loop_subscribe`. `non_timer.transformer` will be called to + * transform an object into a JS value (called an item) that's passed to the + * JS callback. This is useful for example to take an item out of a message + * queue and pass it to JS code in a convenient format. If + * `non_timer.transformer` is NULL, the event loop will take semaphores and + * mutexes on its own. + * + * The producer of the contract is responsible for freeing both the contract and + * the object that it points to when the interpreter is torn down. + */ +typedef struct { + JsForeignMagic magic; // +#include + +#include "js_event_loop_api_table_i.h" + +static_assert(!has_hash_collisions(js_event_loop_api_table), "Detected API method hash collision!"); + +extern "C" constexpr HashtableApiInterface js_event_loop_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + .resolver_callback = &elf_resolve_from_hashtable, + }, + js_event_loop_api_table.cbegin(), + js_event_loop_api_table.cend(), +}; diff --git a/applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h b/applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h new file mode 100644 index 00000000000..49090caebc4 --- /dev/null +++ b/applications/system/js_app/modules/js_event_loop/js_event_loop_api_table_i.h @@ -0,0 +1,4 @@ +#include "js_event_loop.h" + +static constexpr auto js_event_loop_api_table = sort( + create_array_t(API_METHOD(js_event_loop_get_loop, FuriEventLoop*, (JsEventLoop*)))); diff --git a/applications/system/js_app/modules/js_flipper.c b/applications/system/js_app/modules/js_flipper.c index 4619a1593c6..43c675e107c 100644 --- a/applications/system/js_app/modules/js_flipper.c +++ b/applications/system/js_app/modules/js_flipper.c @@ -25,7 +25,8 @@ static void js_flipper_get_battery(struct mjs* mjs) { mjs_return(mjs, mjs_mk_number(mjs, info.charge)); } -void* js_flipper_create(struct mjs* mjs, mjs_val_t* object) { +void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); mjs_val_t flipper_obj = mjs_mk_object(mjs); mjs_set(mjs, flipper_obj, "getModel", ~0, MJS_MK_FN(js_flipper_get_model)); mjs_set(mjs, flipper_obj, "getName", ~0, MJS_MK_FN(js_flipper_get_name)); diff --git a/applications/system/js_app/modules/js_flipper.h b/applications/system/js_app/modules/js_flipper.h index 3b05389cc77..98979ce5826 100644 --- a/applications/system/js_app/modules/js_flipper.h +++ b/applications/system/js_app/modules/js_flipper.h @@ -1,4 +1,4 @@ #pragma once #include "../js_thread_i.h" -void* js_flipper_create(struct mjs* mjs, mjs_val_t* object); +void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules); diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c new file mode 100644 index 00000000000..70021968fa8 --- /dev/null +++ b/applications/system/js_app/modules/js_gpio.c @@ -0,0 +1,345 @@ +#include "../js_modules.h" // IWYU pragma: keep +#include "./js_event_loop/js_event_loop.h" +#include +#include +#include +#include +#include + +#define INTERRUPT_QUEUE_LEN 16 + +/** + * Per-pin control structure + */ +typedef struct { + const GpioPin* pin; + bool had_interrupt; + FuriSemaphore* interrupt_semaphore; + JsEventLoopContract* interrupt_contract; + FuriHalAdcChannel adc_channel; + FuriHalAdcHandle* adc_handle; +} JsGpioPinInst; + +ARRAY_DEF(ManagedPinsArray, JsGpioPinInst*, M_PTR_OPLIST); //-V575 + +/** + * Per-module instance control structure + */ +typedef struct { + FuriEventLoop* loop; + ManagedPinsArray_t managed_pins; + FuriHalAdcHandle* adc_handle; +} JsGpioInst; + +/** + * @brief Interrupt callback + */ +static void js_gpio_int_cb(void* arg) { + furi_assert(arg); + FuriSemaphore* semaphore = arg; + furi_semaphore_release(semaphore); +} + +/** + * @brief Initializes a GPIO pin according to the provided mode object + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let led = gpio.get("pc3"); + * led.init({ direction: "out", outMode: "push_pull" }); + * ``` + */ +static void js_gpio_init(struct mjs* mjs) { + // deconstruct mode object + mjs_val_t mode_arg; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&mode_arg)); + mjs_val_t direction_arg = mjs_get(mjs, mode_arg, "direction", ~0); + mjs_val_t out_mode_arg = mjs_get(mjs, mode_arg, "outMode", ~0); + mjs_val_t in_mode_arg = mjs_get(mjs, mode_arg, "inMode", ~0); + mjs_val_t edge_arg = mjs_get(mjs, mode_arg, "edge", ~0); + mjs_val_t pull_arg = mjs_get(mjs, mode_arg, "pull", ~0); + + // get strings + const char* direction = mjs_get_string(mjs, &direction_arg, NULL); + const char* out_mode = mjs_get_string(mjs, &out_mode_arg, NULL); + const char* in_mode = mjs_get_string(mjs, &in_mode_arg, NULL); + const char* edge = mjs_get_string(mjs, &edge_arg, NULL); + const char* pull = mjs_get_string(mjs, &pull_arg, NULL); + if(!direction) + JS_ERROR_AND_RETURN( + mjs, MJS_BAD_ARGS_ERROR, "Expected string in \"direction\" field of mode object"); + if(!out_mode) out_mode = "open_drain"; + if(!in_mode) in_mode = "plain_digital"; + if(!edge) edge = "rising"; + + // convert strings to mode + GpioMode mode; + if(strcmp(direction, "out") == 0) { + if(strcmp(out_mode, "push_pull") == 0) + mode = GpioModeOutputPushPull; + else if(strcmp(out_mode, "open_drain") == 0) + mode = GpioModeOutputOpenDrain; + else + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid outMode"); + } else if(strcmp(direction, "in") == 0) { + if(strcmp(in_mode, "analog") == 0) { + mode = GpioModeAnalog; + } else if(strcmp(in_mode, "plain_digital") == 0) { + mode = GpioModeInput; + } else if(strcmp(in_mode, "interrupt") == 0) { + if(strcmp(edge, "rising") == 0) + mode = GpioModeInterruptRise; + else if(strcmp(edge, "falling") == 0) + mode = GpioModeInterruptFall; + else if(strcmp(edge, "both") == 0) + mode = GpioModeInterruptRiseFall; + else + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); + } else if(strcmp(in_mode, "event") == 0) { + if(strcmp(edge, "rising") == 0) + mode = GpioModeEventRise; + else if(strcmp(edge, "falling") == 0) + mode = GpioModeEventFall; + else if(strcmp(edge, "both") == 0) + mode = GpioModeEventRiseFall; + else + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid inMode"); + } + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid direction"); + } + + // convert pull + GpioPull pull_mode; + if(!pull) { + pull_mode = GpioPullNo; + } else if(strcmp(pull, "up") == 0) { + pull_mode = GpioPullUp; + } else if(strcmp(pull, "down") == 0) { + pull_mode = GpioPullDown; + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid pull"); + } + + // init GPIO + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + furi_hal_gpio_init(manager_data->pin, mode, pull_mode, GpioSpeedVeryHigh); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief Writes a logic value to a GPIO pin + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let led = gpio.get("pc3"); + * led.init({ direction: "out", outMode: "push_pull" }); + * led.write(true); + * ``` + */ +static void js_gpio_write(struct mjs* mjs) { + bool level; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_BOOL(&level)); + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + furi_hal_gpio_write(manager_data->pin, level); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief Reads a logic value from a GPIO pin + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let button = gpio.get("pc1"); + * button.init({ direction: "in" }); + * if(button.read()) + * print("hi button!!!!!"); + * ``` + */ +static void js_gpio_read(struct mjs* mjs) { + // get level + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + bool value = furi_hal_gpio_read(manager_data->pin); + mjs_return(mjs, mjs_mk_boolean(mjs, value)); +} + +/** + * @brief Returns a event loop contract that can be used to listen to interrupts + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let button = gpio.get("pc1"); + * let event_loop = require("event_loop"); + * button.init({ direction: "in", pull: "up", inMode: "interrupt", edge: "falling" }); + * event_loop.subscribe(button.interrupt(), function (_) { print("Hi!"); }); + * event_loop.run(); + * ``` + */ +static void js_gpio_interrupt(struct mjs* mjs) { + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + + // interrupt handling + if(!manager_data->had_interrupt) { + furi_hal_gpio_add_int_callback( + manager_data->pin, js_gpio_int_cb, manager_data->interrupt_semaphore); + furi_hal_gpio_enable_int_callback(manager_data->pin); + manager_data->had_interrupt = true; + } + + // make contract + JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); + *contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeSemaphore, + .object = manager_data->interrupt_semaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + }, + }; + manager_data->interrupt_contract = contract; + mjs_return(mjs, mjs_mk_foreign(mjs, contract)); +} + +/** + * @brief Reads a voltage from a GPIO pin in analog mode + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let pot = gpio.get("pc0"); + * pot.init({ direction: "in", inMode: "analog" }); + * print("voltage:" pot.read_analog(), "mV"); + * ``` + */ +static void js_gpio_read_analog(struct mjs* mjs) { + // get mV (ADC is configured for 12 bits and 2048 mV max) + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + uint16_t millivolts = + furi_hal_adc_read(manager_data->adc_handle, manager_data->adc_channel) / 2; + mjs_return(mjs, mjs_mk_number(mjs, (double)millivolts)); +} + +/** + * @brief Returns an object that manages a specified pin. + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let led = gpio.get("pc3"); + * ``` + */ +static void js_gpio_get(struct mjs* mjs) { + mjs_val_t name_arg; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&name_arg)); + const char* name_string = mjs_get_string(mjs, &name_arg, NULL); + const GpioPinRecord* pin_record = NULL; + + // parse input argument to a pin pointer + if(name_string) { + pin_record = furi_hal_resources_pin_by_name(name_string); + } else if(mjs_is_number(name_arg)) { + int name_int = mjs_get_int(mjs, name_arg); + pin_record = furi_hal_resources_pin_by_number(name_int); + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "Must be either a string or a number"); + } + + if(!pin_record) JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "Pin not found on device"); + if(pin_record->debug) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "Pin is used for debugging"); + + // return pin manager object + JsGpioInst* module = JS_GET_CONTEXT(mjs); + mjs_val_t manager = mjs_mk_object(mjs); + JsGpioPinInst* manager_data = malloc(sizeof(JsGpioPinInst)); + manager_data->pin = pin_record->pin; + manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0); + manager_data->adc_handle = module->adc_handle; + manager_data->adc_channel = pin_record->channel; + mjs_own(mjs, &manager); + mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data)); + mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init)); + mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write)); + mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read)); + mjs_set(mjs, manager, "read_analog", ~0, MJS_MK_FN(js_gpio_read_analog)); + mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt)); + mjs_return(mjs, manager); + + // remember pin + ManagedPinsArray_push_back(module->managed_pins, manager_data); +} + +static void* js_gpio_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + JsEventLoop* js_loop = js_module_get(modules, "event_loop"); + if(M_UNLIKELY(!js_loop)) return NULL; + FuriEventLoop* loop = js_event_loop_get_loop(js_loop); + + JsGpioInst* module = malloc(sizeof(JsGpioInst)); + ManagedPinsArray_init(module->managed_pins); + module->adc_handle = furi_hal_adc_acquire(); + module->loop = loop; + furi_hal_adc_configure(module->adc_handle); + + mjs_val_t gpio_obj = mjs_mk_object(mjs); + mjs_set(mjs, gpio_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module)); + mjs_set(mjs, gpio_obj, "get", ~0, MJS_MK_FN(js_gpio_get)); + *object = gpio_obj; + + return (void*)module; +} + +static void js_gpio_destroy(void* inst) { + furi_assert(inst); + JsGpioInst* module = (JsGpioInst*)inst; + + // reset pins + ManagedPinsArray_it_t iterator; + for(ManagedPinsArray_it(iterator, module->managed_pins); !ManagedPinsArray_end_p(iterator); + ManagedPinsArray_next(iterator)) { + JsGpioPinInst* manager_data = *ManagedPinsArray_cref(iterator); + if(manager_data->had_interrupt) { + furi_hal_gpio_disable_int_callback(manager_data->pin); + furi_hal_gpio_remove_int_callback(manager_data->pin); + } + furi_hal_gpio_init(manager_data->pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_event_loop_maybe_unsubscribe(module->loop, manager_data->interrupt_semaphore); + furi_semaphore_free(manager_data->interrupt_semaphore); + free(manager_data->interrupt_contract); + free(manager_data); + } + + // free buffers + furi_hal_adc_release(module->adc_handle); + ManagedPinsArray_clear(module->managed_pins); + free(module); +} + +static const JsModuleDescriptor js_gpio_desc = { + "gpio", + js_gpio_create, + js_gpio_destroy, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_gpio_desc, +}; + +const FlipperAppPluginDescriptor* js_gpio_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_gui/dialog.c b/applications/system/js_app/modules/js_gui/dialog.c new file mode 100644 index 00000000000..31eee237f6b --- /dev/null +++ b/applications/system/js_app/modules/js_gui/dialog.c @@ -0,0 +1,129 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +#define QUEUE_LEN 2 + +typedef struct { + FuriMessageQueue* queue; + JsEventLoopContract contract; +} JsDialogCtx; + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriMessageQueue* queue, JsDialogCtx* context) { + UNUSED(context); + DialogExResult result; + furi_check(furi_message_queue_get(queue, &result, 0) == FuriStatusOk); + const char* string; + if(result == DialogExResultLeft) { + string = "left"; + } else if(result == DialogExResultCenter) { + string = "center"; + } else if(result == DialogExResultRight) { + string = "right"; + } else { + furi_crash(); + } + return mjs_mk_string(mjs, string, ~0, false); +} + +static void input_callback(DialogExResult result, JsDialogCtx* context) { + furi_check(furi_message_queue_put(context->queue, &result, 0) == FuriStatusOk); +} + +static bool + header_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_header(dialog, value.string, 64, 0, AlignCenter, AlignTop); + return true; +} + +static bool + text_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_text(dialog, value.string, 64, 32, AlignCenter, AlignCenter); + return true; +} + +static bool + left_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_left_button_text(dialog, value.string); + return true; +} +static bool + center_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_center_button_text(dialog, value.string); + return true; +} +static bool + right_assign(struct mjs* mjs, DialogEx* dialog, JsViewPropValue value, JsDialogCtx* context) { + UNUSED(mjs); + UNUSED(context); + dialog_ex_set_right_button_text(dialog, value.string); + return true; +} + +static JsDialogCtx* ctx_make(struct mjs* mjs, DialogEx* dialog, mjs_val_t view_obj) { + JsDialogCtx* context = malloc(sizeof(JsDialogCtx)); + context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(DialogExResult)); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + }, + }; + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + dialog_ex_set_result_callback(dialog, (DialogExResultCallback)input_callback); + dialog_ex_set_context(dialog, context); + return context; +} + +static void ctx_destroy(DialogEx* input, JsDialogCtx* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->queue); + furi_message_queue_free(context->queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)dialog_ex_alloc, + .free = (JsViewFree)dialog_ex_free, + .get_view = (JsViewGetView)dialog_ex_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 5, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "text", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)text_assign}, + (JsViewPropDescriptor){ + .name = "left", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)left_assign}, + (JsViewPropDescriptor){ + .name = "center", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)center_assign}, + (JsViewPropDescriptor){ + .name = "right", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)right_assign}, + }}; + +JS_GUI_VIEW_DEF(dialog, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/empty_screen.c b/applications/system/js_app/modules/js_gui/empty_screen.c new file mode 100644 index 00000000000..9684eabdc1a --- /dev/null +++ b/applications/system/js_app/modules/js_gui/empty_screen.c @@ -0,0 +1,12 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)empty_screen_alloc, + .free = (JsViewFree)empty_screen_free, + .get_view = (JsViewGetView)empty_screen_get_view, + .prop_cnt = 0, + .props = {}, +}; +JS_GUI_VIEW_DEF(empty_screen, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c new file mode 100644 index 00000000000..8ac3055d5dc --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -0,0 +1,348 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "./js_gui.h" +#include +#include +#include +#include "../js_event_loop/js_event_loop.h" +#include + +#define EVENT_QUEUE_SIZE 16 + +typedef struct { + uint32_t next_view_id; + FuriEventLoop* loop; + Gui* gui; + ViewDispatcher* dispatcher; + // event stuff + JsEventLoopContract custom_contract; + FuriMessageQueue* custom; + JsEventLoopContract navigation_contract; + FuriSemaphore* + navigation; // FIXME: (-nofl) convert into callback once FuriEventLoop starts supporting this +} JsGui; + +// Useful for factories +static JsGui* js_gui; + +typedef struct { + uint32_t id; + const JsViewDescriptor* descriptor; + void* specific_view; + void* custom_data; +} JsGuiViewData; + +/** + * @brief Transformer for custom events + */ +static mjs_val_t + js_gui_vd_custom_transformer(struct mjs* mjs, FuriEventLoopObject* object, void* context) { + UNUSED(context); + furi_check(object); + FuriMessageQueue* queue = object; + uint32_t event; + furi_check(furi_message_queue_get(queue, &event, 0) == FuriStatusOk); + return mjs_mk_number(mjs, (double)event); +} + +/** + * @brief ViewDispatcher custom event callback + */ +static bool js_gui_vd_custom_callback(void* context, uint32_t event) { + furi_check(context); + JsGui* module = context; + furi_check(furi_message_queue_put(module->custom, &event, 0) == FuriStatusOk); + return true; +} + +/** + * @brief ViewDispatcher navigation event callback + */ +static bool js_gui_vd_nav_callback(void* context) { + furi_check(context); + JsGui* module = context; + furi_semaphore_release(module->navigation); + return true; +} + +/** + * @brief `viewDispatcher.sendCustom` + */ +static void js_gui_vd_send_custom(struct mjs* mjs) { + int32_t event; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&event)); + + JsGui* module = JS_GET_CONTEXT(mjs); + view_dispatcher_send_custom_event(module->dispatcher, (uint32_t)event); +} + +/** + * @brief `viewDispatcher.sendTo` + */ +static void js_gui_vd_send_to(struct mjs* mjs) { + enum { + SendDirToFront, + SendDirToBack, + } send_direction; + JS_ENUM_MAP(send_direction, {"front", SendDirToFront}, {"back", SendDirToBack}); + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ENUM(send_direction, "SendDirection")); + + JsGui* module = JS_GET_CONTEXT(mjs); + if(send_direction == SendDirToBack) { + view_dispatcher_send_to_back(module->dispatcher); + } else { + view_dispatcher_send_to_front(module->dispatcher); + } +} + +/** + * @brief `viewDispatcher.switchTo` + */ +static void js_gui_vd_switch_to(struct mjs* mjs) { + mjs_val_t view; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&view)); + JsGuiViewData* view_data = JS_GET_INST(mjs, view); + JsGui* module = JS_GET_CONTEXT(mjs); + view_dispatcher_switch_to_view(module->dispatcher, (uint32_t)view_data->id); +} + +static void* js_gui_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + // get event loop + JsEventLoop* js_loop = js_module_get(modules, "event_loop"); + if(M_UNLIKELY(!js_loop)) return NULL; + FuriEventLoop* loop = js_event_loop_get_loop(js_loop); + + // create C object + JsGui* module = malloc(sizeof(JsGui)); + module->loop = loop; + module->gui = furi_record_open(RECORD_GUI); + module->dispatcher = view_dispatcher_alloc_ex(loop); + module->custom = furi_message_queue_alloc(EVENT_QUEUE_SIZE, sizeof(uint32_t)); + module->navigation = furi_semaphore_alloc(EVENT_QUEUE_SIZE, 0); + view_dispatcher_attach_to_gui(module->dispatcher, module->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_send_to_front(module->dispatcher); + + // subscribe to events and create contracts + view_dispatcher_set_event_callback_context(module->dispatcher, module); + view_dispatcher_set_custom_event_callback(module->dispatcher, js_gui_vd_custom_callback); + view_dispatcher_set_navigation_event_callback(module->dispatcher, js_gui_vd_nav_callback); + module->custom_contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object = module->custom, + .object_type = JsEventLoopObjectTypeQueue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = js_gui_vd_custom_transformer, + }, + }; + module->navigation_contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object = module->navigation, + .object_type = JsEventLoopObjectTypeSemaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + }, + }; + + // create viewDispatcher object + mjs_val_t view_dispatcher = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, view_dispatcher) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, module)); + JS_FIELD("sendCustom", MJS_MK_FN(js_gui_vd_send_custom)); + JS_FIELD("sendTo", MJS_MK_FN(js_gui_vd_send_to)); + JS_FIELD("switchTo", MJS_MK_FN(js_gui_vd_switch_to)); + JS_FIELD("custom", mjs_mk_foreign(mjs, &module->custom_contract)); + JS_FIELD("navigation", mjs_mk_foreign(mjs, &module->navigation_contract)); + } + + // create API object + mjs_val_t api = mjs_mk_object(mjs); + mjs_set(mjs, api, "viewDispatcher", ~0, view_dispatcher); + + *object = api; + js_gui = module; + return module; +} + +static void js_gui_destroy(void* inst) { + furi_assert(inst); + JsGui* module = inst; + + view_dispatcher_free(module->dispatcher); + furi_event_loop_maybe_unsubscribe(module->loop, module->custom); + furi_event_loop_maybe_unsubscribe(module->loop, module->navigation); + furi_message_queue_free(module->custom); + furi_semaphore_free(module->navigation); + + furi_record_close(RECORD_GUI); + free(module); + js_gui = NULL; +} + +/** + * @brief Assigns a `View` property. Not available from JS. + */ +static bool + js_gui_view_assign(struct mjs* mjs, const char* name, mjs_val_t value, JsGuiViewData* data) { + const JsViewDescriptor* descriptor = data->descriptor; + for(size_t i = 0; i < descriptor->prop_cnt; i++) { + JsViewPropDescriptor prop = descriptor->props[i]; + if(strcmp(prop.name, name) != 0) continue; + + // convert JS value to C + JsViewPropValue c_value; + const char* expected_type = NULL; + switch(prop.type) { + case JsViewPropTypeNumber: { + if(!mjs_is_number(value)) { + expected_type = "number"; + break; + } + c_value = (JsViewPropValue){.number = mjs_get_int32(mjs, value)}; + } break; + case JsViewPropTypeString: { + if(!mjs_is_string(value)) { + expected_type = "string"; + break; + } + c_value = (JsViewPropValue){.string = mjs_get_string(mjs, &value, NULL)}; + } break; + case JsViewPropTypeArr: { + if(!mjs_is_array(value)) { + expected_type = "array"; + break; + } + c_value = (JsViewPropValue){.array = value}; + } break; + } + + if(expected_type) { + mjs_prepend_errorf( + mjs, MJS_BAD_ARGS_ERROR, "view prop \"%s\" requires %s value", name, expected_type); + return false; + } else { + return prop.assign(mjs, data->specific_view, c_value, data->custom_data); + } + } + + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "view has no prop named \"%s\"", name); + return false; +} + +/** + * @brief `View.set` + */ +static void js_gui_view_set(struct mjs* mjs) { + const char* name; + mjs_val_t value; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&name), JS_ARG_ANY(&value)); + JsGuiViewData* data = JS_GET_CONTEXT(mjs); + bool success = js_gui_view_assign(mjs, name, value, data); + UNUSED(success); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief `View` destructor + */ +static void js_gui_view_destructor(struct mjs* mjs, mjs_val_t obj) { + JsGuiViewData* data = JS_GET_INST(mjs, obj); + view_dispatcher_remove_view(js_gui->dispatcher, data->id); + if(data->descriptor->custom_destroy) + data->descriptor->custom_destroy(data->specific_view, data->custom_data, js_gui->loop); + data->descriptor->free(data->specific_view); + free(data); +} + +/** + * @brief Creates a `View` object from a descriptor. Not available from JS. + */ +static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descriptor) { + void* specific_view = descriptor->alloc(); + View* view = descriptor->get_view(specific_view); + uint32_t view_id = js_gui->next_view_id++; + view_dispatcher_add_view(js_gui->dispatcher, view_id, view); + + // generic view API + mjs_val_t view_obj = mjs_mk_object(mjs); + mjs_set(mjs, view_obj, "set", ~0, MJS_MK_FN(js_gui_view_set)); + + // object data + JsGuiViewData* data = malloc(sizeof(JsGuiViewData)); + *data = (JsGuiViewData){ + .descriptor = descriptor, + .id = view_id, + .specific_view = specific_view, + .custom_data = + descriptor->custom_make ? descriptor->custom_make(mjs, specific_view, view_obj) : NULL, + }; + mjs_set(mjs, view_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, data)); + mjs_set(mjs, view_obj, MJS_DESTRUCTOR_PROP_NAME, ~0, MJS_MK_FN(js_gui_view_destructor)); + + return view_obj; +} + +/** + * @brief `ViewFactory.make` + */ +static void js_gui_vf_make(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); + mjs_return(mjs, js_gui_make_view(mjs, descriptor)); +} + +/** + * @brief `ViewFactory.makeWith` + */ +static void js_gui_vf_make_with(struct mjs* mjs) { + mjs_val_t props; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&props)); + const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); + + // make the object like normal + mjs_val_t view_obj = js_gui_make_view(mjs, descriptor); + JsGuiViewData* data = JS_GET_INST(mjs, view_obj); + + // assign properties one by one + mjs_val_t key, iter = MJS_UNDEFINED; + while((key = mjs_next(mjs, props, &iter)) != MJS_UNDEFINED) { + furi_check(mjs_is_string(key)); + const char* name = mjs_get_string(mjs, &key, NULL); + mjs_val_t value = mjs_get(mjs, props, name, ~0); + + if(!js_gui_view_assign(mjs, name, value, data)) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + } + + mjs_return(mjs, view_obj); +} + +mjs_val_t js_gui_make_view_factory(struct mjs* mjs, const JsViewDescriptor* view_descriptor) { + mjs_val_t factory = mjs_mk_object(mjs); + mjs_set(mjs, factory, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, (void*)view_descriptor)); + mjs_set(mjs, factory, "make", ~0, MJS_MK_FN(js_gui_vf_make)); + mjs_set(mjs, factory, "makeWith", ~0, MJS_MK_FN(js_gui_vf_make_with)); + return factory; +} + +extern const ElfApiInterface js_gui_hashtable_api_interface; + +static const JsModuleDescriptor js_gui_desc = { + "gui", + js_gui_create, + js_gui_destroy, + &js_gui_hashtable_api_interface, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_gui_desc, +}; + +const FlipperAppPluginDescriptor* js_gui_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_gui/js_gui.h b/applications/system/js_app/modules/js_gui/js_gui.h new file mode 100644 index 00000000000..02198ca4f35 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui.h @@ -0,0 +1,116 @@ +#include "../../js_modules.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + JsViewPropTypeString, + JsViewPropTypeNumber, + JsViewPropTypeArr, +} JsViewPropType; + +typedef union { + const char* string; + int32_t number; + mjs_val_t array; +} JsViewPropValue; + +/** + * @brief Assigns a value to a view property + * + * The name and the type are implicit and defined in the property descriptor + */ +typedef bool ( + *JsViewPropAssign)(struct mjs* mjs, void* specific_view, JsViewPropValue value, void* context); + +/** @brief Property descriptor */ +typedef struct { + const char* name; // get_view -> [custom_make (if set)] -> props[i].assign -> [custom_destroy (if_set)] -> free +// \_______________ creation ________________/ \___ usage ___/ \_________ destruction _________/ + +/** + * @brief Creates a JS `ViewFactory` object + * + * This function is intended to be used by individual view adapter modules that + * wish to create a unified JS API interface in a declarative way. Usually this + * is done via the `JS_GUI_VIEW_DEF` macro which hides all the boilerplate. + * + * The `ViewFactory` object exposes two methods, `make` and `makeWith`, each + * returning a `View` object. These objects fully comply with the expectations + * of the `ViewDispatcher`, TS type definitions and the proposed Flipper JS + * coding style. + */ +mjs_val_t js_gui_make_view_factory(struct mjs* mjs, const JsViewDescriptor* view_descriptor); + +/** + * @brief Defines a module implementing `View` glue code + */ +#define JS_GUI_VIEW_DEF(name, descriptor) \ + static void* view_mod_ctor(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { \ + UNUSED(modules); \ + *object = js_gui_make_view_factory(mjs, descriptor); \ + return NULL; \ + } \ + static const JsModuleDescriptor js_mod_desc = { \ + "gui__" #name, \ + view_mod_ctor, \ + NULL, \ + NULL, \ + }; \ + static const FlipperAppPluginDescriptor plugin_descriptor = { \ + .appid = PLUGIN_APP_ID, \ + .ep_api_version = PLUGIN_API_VERSION, \ + .entry_point = &js_mod_desc, \ + }; \ + const FlipperAppPluginDescriptor* js_view_##name##_ep(void) { \ + return &plugin_descriptor; \ + } + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/modules/js_gui/js_gui_api_table.cpp b/applications/system/js_app/modules/js_gui/js_gui_api_table.cpp new file mode 100644 index 00000000000..2be9cb3b212 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui_api_table.cpp @@ -0,0 +1,16 @@ +#include +#include + +#include "js_gui_api_table_i.h" + +static_assert(!has_hash_collisions(js_gui_api_table), "Detected API method hash collision!"); + +extern "C" constexpr HashtableApiInterface js_gui_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + .resolver_callback = &elf_resolve_from_hashtable, + }, + js_gui_api_table.cbegin(), + js_gui_api_table.cend(), +}; diff --git a/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h b/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h new file mode 100644 index 00000000000..852b3d1071f --- /dev/null +++ b/applications/system/js_app/modules/js_gui/js_gui_api_table_i.h @@ -0,0 +1,4 @@ +#include "js_gui.h" + +static constexpr auto js_gui_api_table = sort(create_array_t( + API_METHOD(js_gui_make_view_factory, mjs_val_t, (struct mjs*, const JsViewDescriptor*)))); diff --git a/applications/system/js_app/modules/js_gui/loading.c b/applications/system/js_app/modules/js_gui/loading.c new file mode 100644 index 00000000000..e291824a078 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/loading.c @@ -0,0 +1,12 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)loading_alloc, + .free = (JsViewFree)loading_free, + .get_view = (JsViewGetView)loading_get_view, + .prop_cnt = 0, + .props = {}, +}; +JS_GUI_VIEW_DEF(loading, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/submenu.c b/applications/system/js_app/modules/js_gui/submenu.c new file mode 100644 index 00000000000..aecd413be4d --- /dev/null +++ b/applications/system/js_app/modules/js_gui/submenu.c @@ -0,0 +1,87 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +#define QUEUE_LEN 2 + +typedef struct { + FuriMessageQueue* queue; + JsEventLoopContract contract; +} JsSubmenuCtx; + +static mjs_val_t choose_transformer(struct mjs* mjs, FuriMessageQueue* queue, void* context) { + UNUSED(context); + uint32_t index; + furi_check(furi_message_queue_get(queue, &index, 0) == FuriStatusOk); + return mjs_mk_number(mjs, (double)index); +} + +void choose_callback(void* context, uint32_t index) { + JsSubmenuCtx* ctx = context; + furi_check(furi_message_queue_put(ctx->queue, &index, 0) == FuriStatusOk); +} + +static bool + header_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) { + UNUSED(mjs); + UNUSED(context); + submenu_set_header(submenu, value.string); + return true; +} + +static bool items_assign(struct mjs* mjs, Submenu* submenu, JsViewPropValue value, void* context) { + UNUSED(mjs); + submenu_reset(submenu); + size_t len = mjs_array_length(mjs, value.array); + for(size_t i = 0; i < len; i++) { + mjs_val_t item = mjs_array_get(mjs, value.array, i); + if(!mjs_is_string(item)) return false; + submenu_add_item(submenu, mjs_get_string(mjs, &item, NULL), i, choose_callback, context); + } + return true; +} + +static JsSubmenuCtx* ctx_make(struct mjs* mjs, Submenu* input, mjs_val_t view_obj) { + UNUSED(input); + JsSubmenuCtx* context = malloc(sizeof(JsSubmenuCtx)); + context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(uint32_t)); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)choose_transformer, + }, + }; + mjs_set(mjs, view_obj, "chosen", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(Submenu* input, JsSubmenuCtx* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->queue); + furi_message_queue_free(context->queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)submenu_alloc, + .free = (JsViewFree)submenu_free, + .get_view = (JsViewGetView)submenu_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 2, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "items", + .type = JsViewPropTypeArr, + .assign = (JsViewPropAssign)items_assign}, + }}; +JS_GUI_VIEW_DEF(submenu, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/text_box.c b/applications/system/js_app/modules/js_gui/text_box.c new file mode 100644 index 00000000000..4e6c8247ca7 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/text_box.c @@ -0,0 +1,78 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include + +static bool + text_assign(struct mjs* mjs, TextBox* text_box, JsViewPropValue value, FuriString* context) { + UNUSED(mjs); + furi_string_set(context, value.string); + text_box_set_text(text_box, furi_string_get_cstr(context)); + return true; +} + +static bool font_assign(struct mjs* mjs, TextBox* text_box, JsViewPropValue value, void* context) { + UNUSED(context); + TextBoxFont font; + if(strcasecmp(value.string, "hex") == 0) { + font = TextBoxFontHex; + } else if(strcasecmp(value.string, "text") == 0) { + font = TextBoxFontText; + } else { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "must be one of: \"text\", \"hex\""); + return false; + } + text_box_set_font(text_box, font); + return true; +} + +static bool + focus_assign(struct mjs* mjs, TextBox* text_box, JsViewPropValue value, void* context) { + UNUSED(context); + TextBoxFocus focus; + if(strcasecmp(value.string, "start") == 0) { + focus = TextBoxFocusStart; + } else if(strcasecmp(value.string, "end") == 0) { + focus = TextBoxFocusEnd; + } else { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "must be one of: \"start\", \"end\""); + return false; + } + text_box_set_focus(text_box, focus); + return true; +} + +FuriString* ctx_make(struct mjs* mjs, TextBox* specific_view, mjs_val_t view_obj) { + UNUSED(mjs); + UNUSED(specific_view); + UNUSED(view_obj); + return furi_string_alloc(); +} + +void ctx_destroy(TextBox* specific_view, FuriString* context, FuriEventLoop* loop) { + UNUSED(specific_view); + UNUSED(loop); + furi_string_free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)text_box_alloc, + .free = (JsViewFree)text_box_free, + .get_view = (JsViewGetView)text_box_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 3, + .props = { + (JsViewPropDescriptor){ + .name = "text", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)text_assign}, + (JsViewPropDescriptor){ + .name = "font", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)font_assign}, + (JsViewPropDescriptor){ + .name = "focus", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)focus_assign}, + }}; +JS_GUI_VIEW_DEF(text_box, &view_descriptor); diff --git a/applications/system/js_app/modules/js_gui/text_input.c b/applications/system/js_app/modules/js_gui/text_input.c new file mode 100644 index 00000000000..575029f8e69 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/text_input.c @@ -0,0 +1,120 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +#define DEFAULT_BUF_SZ 33 + +typedef struct { + char* buffer; + size_t buffer_size; + FuriString* header; + FuriSemaphore* input_semaphore; + JsEventLoopContract contract; +} JsKbdContext; + +static mjs_val_t + input_transformer(struct mjs* mjs, FuriSemaphore* semaphore, JsKbdContext* context) { + furi_check(furi_semaphore_acquire(semaphore, 0) == FuriStatusOk); + return mjs_mk_string(mjs, context->buffer, ~0, true); +} + +static void input_callback(JsKbdContext* context) { + furi_semaphore_release(context->input_semaphore); +} + +static bool + header_assign(struct mjs* mjs, TextInput* input, JsViewPropValue value, JsKbdContext* context) { + UNUSED(mjs); + furi_string_set(context->header, value.string); + text_input_set_header_text(input, furi_string_get_cstr(context->header)); + return true; +} + +static bool min_len_assign( + struct mjs* mjs, + TextInput* input, + JsViewPropValue value, + JsKbdContext* context) { + UNUSED(mjs); + UNUSED(context); + text_input_set_minimum_length(input, (size_t)value.number); + return true; +} + +static bool max_len_assign( + struct mjs* mjs, + TextInput* input, + JsViewPropValue value, + JsKbdContext* context) { + UNUSED(mjs); + context->buffer_size = (size_t)(value.number + 1); + context->buffer = realloc(context->buffer, context->buffer_size); //-V701 + text_input_set_result_callback( + input, + (TextInputCallback)input_callback, + context, + context->buffer, + context->buffer_size, + true); + return true; +} + +static JsKbdContext* ctx_make(struct mjs* mjs, TextInput* input, mjs_val_t view_obj) { + UNUSED(input); + JsKbdContext* context = malloc(sizeof(JsKbdContext)); + *context = (JsKbdContext){ + .buffer_size = DEFAULT_BUF_SZ, + .buffer = malloc(DEFAULT_BUF_SZ), + .header = furi_string_alloc(), + .input_semaphore = furi_semaphore_alloc(1, 0), + }; + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeSemaphore, + .object = context->input_semaphore, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)input_transformer, + .transformer_context = context, + }, + }; + UNUSED(mjs); + UNUSED(view_obj); + mjs_set(mjs, view_obj, "input", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void ctx_destroy(TextInput* input, JsKbdContext* context, FuriEventLoop* loop) { + UNUSED(input); + furi_event_loop_maybe_unsubscribe(loop, context->input_semaphore); + furi_semaphore_free(context->input_semaphore); + furi_string_free(context->header); + free(context->buffer); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)text_input_alloc, + .free = (JsViewFree)text_input_free, + .get_view = (JsViewGetView)text_input_get_view, + .custom_make = (JsViewCustomMake)ctx_make, + .custom_destroy = (JsViewCustomDestroy)ctx_destroy, + .prop_cnt = 3, + .props = { + (JsViewPropDescriptor){ + .name = "header", + .type = JsViewPropTypeString, + .assign = (JsViewPropAssign)header_assign}, + (JsViewPropDescriptor){ + .name = "minLength", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)min_len_assign}, + (JsViewPropDescriptor){ + .name = "maxLength", + .type = JsViewPropTypeNumber, + .assign = (JsViewPropAssign)max_len_assign}, + }}; + +JS_GUI_VIEW_DEF(text_input, &view_descriptor); diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c index d8812e61bbb..7d54cf9b9fa 100644 --- a/applications/system/js_app/modules/js_math.c +++ b/applications/system/js_app/modules/js_math.c @@ -305,7 +305,8 @@ void js_math_trunc(struct mjs* mjs) { mjs_return(mjs, mjs_mk_number(mjs, x < (double)0. ? ceil(x) : floor(x))); } -static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_math_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); mjs_val_t math_obj = mjs_mk_object(mjs); mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal)); mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs)); @@ -342,6 +343,7 @@ static const JsModuleDescriptor js_math_desc = { "math", js_math_create, NULL, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_notification.c b/applications/system/js_app/modules/js_notification.c index 2f57c45d1b2..994283a0984 100644 --- a/applications/system/js_app/modules/js_notification.c +++ b/applications/system/js_app/modules/js_notification.c @@ -75,7 +75,8 @@ static void js_notify_blink(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void* js_notification_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_notification_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); mjs_val_t notify_obj = mjs_mk_object(mjs); mjs_set(mjs, notify_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, notification)); @@ -96,6 +97,7 @@ static const JsModuleDescriptor js_notification_desc = { "notification", js_notification_create, js_notification_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_serial.c b/applications/system/js_app/modules/js_serial.c index 234eefb4324..7aea927837c 100644 --- a/applications/system/js_app/modules/js_serial.c +++ b/applications/system/js_app/modules/js_serial.c @@ -573,7 +573,8 @@ static void js_serial_expect(struct mjs* mjs) { } } -static void* js_serial_create(struct mjs* mjs, mjs_val_t* object) { +static void* js_serial_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); JsSerialInst* js_serial = malloc(sizeof(JsSerialInst)); js_serial->mjs = mjs; mjs_val_t serial_obj = mjs_mk_object(mjs); @@ -606,6 +607,7 @@ static const JsModuleDescriptor js_serial_desc = { "serial", js_serial_create, js_serial_destroy, + NULL, }; static const FlipperAppPluginDescriptor plugin_descriptor = { diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c new file mode 100644 index 00000000000..1d4053a5f79 --- /dev/null +++ b/applications/system/js_app/modules/js_storage.c @@ -0,0 +1,383 @@ +#include "../js_modules.h" // IWYU pragma: keep +#include + +// ---=== file ops ===--- + +static void js_storage_file_close(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_close(file))); +} + +static void js_storage_file_is_open(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_is_open(file))); +} + +static void js_storage_file_read(struct mjs* mjs) { + enum { + ReadModeAscii, + ReadModeBinary, + } read_mode; + JS_ENUM_MAP(read_mode, {"ascii", ReadModeAscii}, {"binary", ReadModeBinary}); + int32_t length; + JS_FETCH_ARGS_OR_RETURN( + mjs, JS_EXACTLY, JS_ARG_ENUM(read_mode, "ReadMode"), JS_ARG_INT32(&length)); + File* file = JS_GET_CONTEXT(mjs); + char buffer[length]; + size_t actually_read = storage_file_read(file, buffer, length); + if(read_mode == ReadModeAscii) { + mjs_return(mjs, mjs_mk_string(mjs, buffer, actually_read, true)); + } else if(read_mode == ReadModeBinary) { + mjs_return(mjs, mjs_mk_array_buf(mjs, buffer, actually_read)); + } +} + +static void js_storage_file_write(struct mjs* mjs) { + mjs_val_t data; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&data)); + const void* buf; + size_t len; + if(mjs_is_string(data)) { + buf = mjs_get_string(mjs, &data, &len); + } else if(mjs_is_array_buf(data)) { + buf = mjs_array_buf_get_ptr(mjs, data, &len); + } else { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 0: expected string or ArrayBuffer"); + } + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_number(mjs, storage_file_write(file, buf, len))); +} + +static void js_storage_file_seek_relative(struct mjs* mjs) { + int32_t offset; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, false))); +} + +static void js_storage_file_seek_absolute(struct mjs* mjs) { + int32_t offset; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, true))); +} + +static void js_storage_file_tell(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_number(mjs, storage_file_tell(file))); +} + +static void js_storage_file_truncate(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_truncate(file))); +} + +static void js_storage_file_size(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_number(mjs, storage_file_size(file))); +} + +static void js_storage_file_eof(struct mjs* mjs) { + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args + File* file = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_eof(file))); +} + +static void js_storage_file_copy_to(struct mjs* mjs) { + File* source = JS_GET_CONTEXT(mjs); + mjs_val_t dest_obj; + int32_t bytes; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&dest_obj), JS_ARG_INT32(&bytes)); + File* destination = JS_GET_INST(mjs, dest_obj); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_copy_to_file(source, destination, bytes))); +} + +// ---=== top-level file ops ===--- + +// common destructor for file and dir objects +static void js_storage_file_destructor(struct mjs* mjs, mjs_val_t obj) { + File* file = JS_GET_INST(mjs, obj); + storage_file_free(file); +} + +static void js_storage_open_file(struct mjs* mjs) { + const char* path; + FS_AccessMode access_mode; + FS_OpenMode open_mode; + JS_ENUM_MAP(access_mode, {"r", FSAM_READ}, {"w", FSAM_WRITE}, {"rw", FSAM_READ_WRITE}); + JS_ENUM_MAP( + open_mode, + {"open_existing", FSOM_OPEN_EXISTING}, + {"open_always", FSOM_OPEN_ALWAYS}, + {"open_append", FSOM_OPEN_APPEND}, + {"create_new", FSOM_CREATE_NEW}, + {"create_always", FSOM_CREATE_ALWAYS}); + JS_FETCH_ARGS_OR_RETURN( + mjs, + JS_EXACTLY, + JS_ARG_STR(&path), + JS_ARG_ENUM(access_mode, "AccessMode"), + JS_ARG_ENUM(open_mode, "OpenMode")); + + Storage* storage = JS_GET_CONTEXT(mjs); + File* file = storage_file_alloc(storage); + if(!storage_file_open(file, path, access_mode, open_mode)) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + mjs_val_t file_obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, file_obj) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, file)); + JS_FIELD(MJS_DESTRUCTOR_PROP_NAME, MJS_MK_FN(js_storage_file_destructor)); + JS_FIELD("close", MJS_MK_FN(js_storage_file_close)); + JS_FIELD("isOpen", MJS_MK_FN(js_storage_file_is_open)); + JS_FIELD("read", MJS_MK_FN(js_storage_file_read)); + JS_FIELD("write", MJS_MK_FN(js_storage_file_write)); + JS_FIELD("seekRelative", MJS_MK_FN(js_storage_file_seek_relative)); + JS_FIELD("seekAbsolute", MJS_MK_FN(js_storage_file_seek_absolute)); + JS_FIELD("tell", MJS_MK_FN(js_storage_file_tell)); + JS_FIELD("truncate", MJS_MK_FN(js_storage_file_truncate)); + JS_FIELD("size", MJS_MK_FN(js_storage_file_size)); + JS_FIELD("eof", MJS_MK_FN(js_storage_file_eof)); + JS_FIELD("copyTo", MJS_MK_FN(js_storage_file_copy_to)); + } + mjs_return(mjs, file_obj); +} + +static void js_storage_file_exists(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_exists(storage, path))); +} + +// ---=== dir ops ===--- + +static void js_storage_read_directory(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + + Storage* storage = JS_GET_CONTEXT(mjs); + File* dir = storage_file_alloc(storage); + if(!storage_dir_open(dir, path)) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + FileInfo file_info; + char name[128]; + FuriString* file_path = furi_string_alloc_set_str(path); + size_t path_size = furi_string_size(file_path); + uint32_t timestamp; + + mjs_val_t ret = mjs_mk_array(mjs); + while(storage_dir_read(dir, &file_info, name, sizeof(name))) { + furi_string_left(file_path, path_size); + path_append(file_path, name); + furi_check( + storage_common_timestamp(storage, furi_string_get_cstr(file_path), ×tamp) == + FSE_OK); + mjs_val_t obj = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, obj) { + JS_FIELD("path", mjs_mk_string(mjs, name, ~0, true)); + JS_FIELD("isDirectory", mjs_mk_boolean(mjs, file_info_is_dir(&file_info))); + JS_FIELD("size", mjs_mk_number(mjs, file_info.size)); + JS_FIELD("timestamp", mjs_mk_number(mjs, timestamp)); + } + mjs_array_push(mjs, ret, obj); + } + + storage_file_free(dir); + furi_string_free(file_path); + mjs_return(mjs, ret); +} + +static void js_storage_directory_exists(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_dir_exists(storage, path))); +} + +static void js_storage_make_directory(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_mkdir(storage, path))); +} + +// ---=== common ops ===--- + +static void js_storage_file_or_dir_exists(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_exists(storage, path))); +} + +static void js_storage_stat(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + FileInfo file_info; + uint32_t timestamp; + if((storage_common_stat(storage, path, &file_info) | + storage_common_timestamp(storage, path, ×tamp)) != FSE_OK) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + mjs_val_t ret = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, ret) { + JS_FIELD("path", mjs_mk_string(mjs, path, ~0, 1)); + JS_FIELD("isDirectory", mjs_mk_boolean(mjs, file_info_is_dir(&file_info))); + JS_FIELD("size", mjs_mk_number(mjs, file_info.size)); + JS_FIELD("accessTime", mjs_mk_number(mjs, timestamp)); + } + mjs_return(mjs, ret); +} + +static void js_storage_remove(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove(storage, path))); +} + +static void js_storage_rmrf(struct mjs* mjs) { + const char* path; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove_recursive(storage, path))); +} + +static void js_storage_rename(struct mjs* mjs) { + const char *old, *new; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&old), JS_ARG_STR(&new)); + Storage* storage = JS_GET_CONTEXT(mjs); + FS_Error status = storage_common_rename(storage, old, new); + mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK)); +} + +static void js_storage_copy(struct mjs* mjs) { + const char *source, *dest; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&source), JS_ARG_STR(&dest)); + Storage* storage = JS_GET_CONTEXT(mjs); + FS_Error status = storage_common_copy(storage, source, dest); + mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK || status == FSE_EXIST)); +} + +static void js_storage_fs_info(struct mjs* mjs) { + const char* fs; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fs)); + Storage* storage = JS_GET_CONTEXT(mjs); + uint64_t total_space, free_space; + if(storage_common_fs_info(storage, fs, &total_space, &free_space) != FSE_OK) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + mjs_val_t ret = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, ret) { + JS_FIELD("totalSpace", mjs_mk_number(mjs, total_space)); + JS_FIELD("freeSpace", mjs_mk_number(mjs, free_space)); + } + mjs_return(mjs, ret); +} + +static void js_storage_next_available_filename(struct mjs* mjs) { + const char *dir_path, *file_name, *file_ext; + int32_t max_len; + JS_FETCH_ARGS_OR_RETURN( + mjs, + JS_EXACTLY, + JS_ARG_STR(&dir_path), + JS_ARG_STR(&file_name), + JS_ARG_STR(&file_ext), + JS_ARG_INT32(&max_len)); + Storage* storage = JS_GET_CONTEXT(mjs); + FuriString* next_name = furi_string_alloc(); + storage_get_next_filename(storage, dir_path, file_name, file_ext, next_name, max_len); + mjs_return(mjs, mjs_mk_string(mjs, furi_string_get_cstr(next_name), ~0, true)); + furi_string_free(next_name); +} + +// ---=== path ops ===--- + +static void js_storage_are_paths_equal(struct mjs* mjs) { + const char *path1, *path2; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path1), JS_ARG_STR(&path2)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_equivalent_path(storage, path1, path2))); +} + +static void js_storage_is_subpath_of(struct mjs* mjs) { + const char *parent, *child; + JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&parent), JS_ARG_STR(&child)); + Storage* storage = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_is_subdir(storage, parent, child))); +} + +// ---=== module ctor & dtor ===--- + +static void* js_storage_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + Storage* storage = furi_record_open(RECORD_STORAGE); + UNUSED(storage); + *object = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, *object) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, storage)); + + // top-level file ops + JS_FIELD("openFile", MJS_MK_FN(js_storage_open_file)); + JS_FIELD("fileExists", MJS_MK_FN(js_storage_file_exists)); + + // dir ops + JS_FIELD("readDirectory", MJS_MK_FN(js_storage_read_directory)); + JS_FIELD("directoryExists", MJS_MK_FN(js_storage_directory_exists)); + JS_FIELD("makeDirectory", MJS_MK_FN(js_storage_make_directory)); + + // common ops + JS_FIELD("fileOrDirExists", MJS_MK_FN(js_storage_file_or_dir_exists)); + JS_FIELD("stat", MJS_MK_FN(js_storage_stat)); + JS_FIELD("remove", MJS_MK_FN(js_storage_remove)); + JS_FIELD("rmrf", MJS_MK_FN(js_storage_rmrf)); + JS_FIELD("rename", MJS_MK_FN(js_storage_rename)); + JS_FIELD("copy", MJS_MK_FN(js_storage_copy)); + JS_FIELD("fsInfo", MJS_MK_FN(js_storage_fs_info)); + JS_FIELD("nextAvailableFilename", MJS_MK_FN(js_storage_next_available_filename)); + + // path ops + JS_FIELD("arePathsEqual", MJS_MK_FN(js_storage_are_paths_equal)); + JS_FIELD("isSubpathOf", MJS_MK_FN(js_storage_is_subpath_of)); + } + return NULL; +} + +static void js_storage_destroy(void* data) { + UNUSED(data); + furi_record_close(RECORD_STORAGE); +} + +// ---=== boilerplate ===--- + +static const JsModuleDescriptor js_storage_desc = { + "storage", + js_storage_create, + js_storage_destroy, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_storage_desc, +}; + +const FlipperAppPluginDescriptor* js_storage_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_submenu.c b/applications/system/js_app/modules/js_submenu.c deleted file mode 100644 index 5ab9bef77cc..00000000000 --- a/applications/system/js_app/modules/js_submenu.c +++ /dev/null @@ -1,147 +0,0 @@ -#include -#include -#include -#include -#include "../js_modules.h" - -typedef struct { - Submenu* submenu; - ViewHolder* view_holder; - FuriApiLock lock; - uint32_t result; - bool accepted; -} JsSubmenuInst; - -static JsSubmenuInst* get_this_ctx(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSubmenuInst* submenu = mjs_get_ptr(mjs, obj_inst); - furi_assert(submenu); - return submenu; -} - -static void ret_bad_args(struct mjs* mjs, const char* error) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, MJS_UNDEFINED); -} - -static bool check_arg_count(struct mjs* mjs, size_t count) { - size_t num_args = mjs_nargs(mjs); - if(num_args != count) { - ret_bad_args(mjs, "Wrong argument count"); - return false; - } - return true; -} - -static void submenu_callback(void* context, uint32_t id) { - JsSubmenuInst* submenu = context; - submenu->result = id; - submenu->accepted = true; - api_lock_unlock(submenu->lock); -} - -static void submenu_exit(void* context) { - JsSubmenuInst* submenu = context; - submenu->result = 0; - submenu->accepted = false; - api_lock_unlock(submenu->lock); -} - -static void js_submenu_add_item(struct mjs* mjs) { - JsSubmenuInst* submenu = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; - - mjs_val_t label_arg = mjs_arg(mjs, 0); - const char* label = mjs_get_string(mjs, &label_arg, NULL); - if(!label) { - ret_bad_args(mjs, "Label must be a string"); - return; - } - - mjs_val_t id_arg = mjs_arg(mjs, 1); - if(!mjs_is_number(id_arg)) { - ret_bad_args(mjs, "Id must be a number"); - return; - } - int32_t id = mjs_get_int32(mjs, id_arg); - - submenu_add_item(submenu->submenu, label, id, submenu_callback, submenu); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_submenu_set_header(struct mjs* mjs) { - JsSubmenuInst* submenu = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; - - mjs_val_t header_arg = mjs_arg(mjs, 0); - const char* header = mjs_get_string(mjs, &header_arg, NULL); - if(!header) { - ret_bad_args(mjs, "Header must be a string"); - return; - } - - submenu_set_header(submenu->submenu, header); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_submenu_show(struct mjs* mjs) { - JsSubmenuInst* submenu = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - submenu->lock = api_lock_alloc_locked(); - Gui* gui = furi_record_open(RECORD_GUI); - submenu->view_holder = view_holder_alloc(); - view_holder_attach_to_gui(submenu->view_holder, gui); - view_holder_set_back_callback(submenu->view_holder, submenu_exit, submenu); - - view_holder_set_view(submenu->view_holder, submenu_get_view(submenu->submenu)); - api_lock_wait_unlock(submenu->lock); - - view_holder_set_view(submenu->view_holder, NULL); - view_holder_free(submenu->view_holder); - furi_record_close(RECORD_GUI); - api_lock_free(submenu->lock); - - submenu_reset(submenu->submenu); - if(submenu->accepted) { - mjs_return(mjs, mjs_mk_number(mjs, submenu->result)); - } else { - mjs_return(mjs, MJS_UNDEFINED); - } -} - -static void* js_submenu_create(struct mjs* mjs, mjs_val_t* object) { - JsSubmenuInst* submenu = malloc(sizeof(JsSubmenuInst)); - mjs_val_t submenu_obj = mjs_mk_object(mjs); - mjs_set(mjs, submenu_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, submenu)); - mjs_set(mjs, submenu_obj, "addItem", ~0, MJS_MK_FN(js_submenu_add_item)); - mjs_set(mjs, submenu_obj, "setHeader", ~0, MJS_MK_FN(js_submenu_set_header)); - mjs_set(mjs, submenu_obj, "show", ~0, MJS_MK_FN(js_submenu_show)); - submenu->submenu = submenu_alloc(); - *object = submenu_obj; - return submenu; -} - -static void js_submenu_destroy(void* inst) { - JsSubmenuInst* submenu = inst; - submenu_free(submenu->submenu); - free(submenu); -} - -static const JsModuleDescriptor js_submenu_desc = { - "submenu", - js_submenu_create, - js_submenu_destroy, -}; - -static const FlipperAppPluginDescriptor submenu_plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_submenu_desc, -}; - -const FlipperAppPluginDescriptor* js_submenu_ep(void) { - return &submenu_plugin_descriptor; -} diff --git a/applications/system/js_app/modules/js_tests.c b/applications/system/js_app/modules/js_tests.c new file mode 100644 index 00000000000..f2756400006 --- /dev/null +++ b/applications/system/js_app/modules/js_tests.c @@ -0,0 +1,104 @@ +#include "../js_modules.h" // IWYU pragma: keep +#include +#include +#include + +#define TAG "JsTests" + +static void js_tests_fail(struct mjs* mjs) { + furi_check(mjs_nargs(mjs) == 1); + mjs_val_t message_arg = mjs_arg(mjs, 0); + const char* message = mjs_get_string(mjs, &message_arg, NULL); + furi_check(message); + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "%s", message); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_tests_assert_eq(struct mjs* mjs) { + furi_check(mjs_nargs(mjs) == 2); + + mjs_val_t expected_arg = mjs_arg(mjs, 0); + mjs_val_t result_arg = mjs_arg(mjs, 1); + + if(mjs_is_number(expected_arg) && mjs_is_number(result_arg)) { + int32_t expected = mjs_get_int32(mjs, expected_arg); + int32_t result = mjs_get_int32(mjs, result_arg); + if(expected == result) { + FURI_LOG_T(TAG, "eq passed (exp=%ld res=%ld)", expected, result); + } else { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "expected %d, found %d", expected, result); + } + } else if(mjs_is_string(expected_arg) && mjs_is_string(result_arg)) { + const char* expected = mjs_get_string(mjs, &expected_arg, NULL); + const char* result = mjs_get_string(mjs, &result_arg, NULL); + if(strcmp(expected, result) == 0) { + FURI_LOG_T(TAG, "eq passed (exp=\"%s\" res=\"%s\")", expected, result); + } else { + mjs_prepend_errorf( + mjs, MJS_INTERNAL_ERROR, "expected \"%s\", found \"%s\"", expected, result); + } + } else if(mjs_is_boolean(expected_arg) && mjs_is_boolean(result_arg)) { + bool expected = mjs_get_bool(mjs, expected_arg); + bool result = mjs_get_bool(mjs, result_arg); + if(expected == result) { + FURI_LOG_T( + TAG, + "eq passed (exp=%s res=%s)", + expected ? "true" : "false", + result ? "true" : "false"); + } else { + mjs_prepend_errorf( + mjs, + MJS_INTERNAL_ERROR, + "expected %s, found %s", + expected ? "true" : "false", + result ? "true" : "false"); + } + } else { + JS_ERROR_AND_RETURN( + mjs, + MJS_INTERNAL_ERROR, + "type mismatch (expected %s, result %s)", + mjs_typeof(expected_arg), + mjs_typeof(result_arg)); + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_tests_assert_float_close(struct mjs* mjs) { + furi_check(mjs_nargs(mjs) == 3); + + mjs_val_t expected_arg = mjs_arg(mjs, 0); + mjs_val_t result_arg = mjs_arg(mjs, 1); + mjs_val_t epsilon_arg = mjs_arg(mjs, 2); + furi_check(mjs_is_number(expected_arg)); + furi_check(mjs_is_number(result_arg)); + furi_check(mjs_is_number(epsilon_arg)); + double expected = mjs_get_double(mjs, expected_arg); + double result = mjs_get_double(mjs, result_arg); + double epsilon = mjs_get_double(mjs, epsilon_arg); + + if(ABS(expected - result) > epsilon) { + mjs_prepend_errorf( + mjs, + MJS_INTERNAL_ERROR, + "expected %f found %f (tolerance=%f)", + expected, + result, + epsilon); + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +void* js_tests_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + mjs_val_t tests_obj = mjs_mk_object(mjs); + mjs_set(mjs, tests_obj, "fail", ~0, MJS_MK_FN(js_tests_fail)); + mjs_set(mjs, tests_obj, "assert_eq", ~0, MJS_MK_FN(js_tests_assert_eq)); + mjs_set(mjs, tests_obj, "assert_float_close", ~0, MJS_MK_FN(js_tests_assert_float_close)); + *object = tests_obj; + + return (void*)1; +} diff --git a/applications/system/js_app/modules/js_tests.h b/applications/system/js_app/modules/js_tests.h new file mode 100644 index 00000000000..49f752c2b39 --- /dev/null +++ b/applications/system/js_app/modules/js_tests.h @@ -0,0 +1,5 @@ +#pragma once +#include "../js_thread_i.h" +#include "../js_modules.h" + +void* js_tests_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules); diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c deleted file mode 100644 index b90dbc153a7..00000000000 --- a/applications/system/js_app/modules/js_textbox.c +++ /dev/null @@ -1,219 +0,0 @@ -#include -#include -#include "../js_modules.h" - -typedef struct { - TextBox* text_box; - ViewHolder* view_holder; - FuriString* text; - bool is_shown; -} JsTextboxInst; - -static JsTextboxInst* get_this_ctx(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsTextboxInst* textbox = mjs_get_ptr(mjs, obj_inst); - furi_assert(textbox); - return textbox; -} - -static void ret_bad_args(struct mjs* mjs, const char* error) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, MJS_UNDEFINED); -} - -static bool check_arg_count(struct mjs* mjs, size_t count) { - size_t num_args = mjs_nargs(mjs); - if(num_args != count) { - ret_bad_args(mjs, "Wrong argument count"); - return false; - } - return true; -} - -static void js_textbox_set_config(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 2)) return; - - TextBoxFocus set_focus = TextBoxFocusStart; - mjs_val_t focus_arg = mjs_arg(mjs, 0); - const char* focus = mjs_get_string(mjs, &focus_arg, NULL); - if(!focus) { - ret_bad_args(mjs, "Focus must be a string"); - return; - } else { - if(!strncmp(focus, "start", strlen("start"))) { - set_focus = TextBoxFocusStart; - } else if(!strncmp(focus, "end", strlen("end"))) { - set_focus = TextBoxFocusEnd; - } else { - ret_bad_args(mjs, "Bad focus value"); - return; - } - } - - TextBoxFont set_font = TextBoxFontText; - mjs_val_t font_arg = mjs_arg(mjs, 1); - const char* font = mjs_get_string(mjs, &font_arg, NULL); - if(!font) { - ret_bad_args(mjs, "Font must be a string"); - return; - } else { - if(!strncmp(font, "text", strlen("text"))) { - set_font = TextBoxFontText; - } else if(!strncmp(font, "hex", strlen("hex"))) { - set_font = TextBoxFontHex; - } else { - ret_bad_args(mjs, "Bad font value"); - return; - } - } - - text_box_set_focus(textbox->text_box, set_focus); - text_box_set_font(textbox->text_box, set_font); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_add_text(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 1)) return; - - mjs_val_t text_arg = mjs_arg(mjs, 0); - size_t text_len = 0; - const char* text = mjs_get_string(mjs, &text_arg, &text_len); - if(!text) { - ret_bad_args(mjs, "Text must be a string"); - return; - } - - // Avoid condition race between GUI and JS thread - text_box_set_text(textbox->text_box, ""); - - size_t new_len = furi_string_size(textbox->text) + text_len; - if(new_len >= 4096) { - furi_string_right(textbox->text, new_len / 2); - } - - furi_string_cat(textbox->text, text); - - text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_clear_text(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - // Avoid condition race between GUI and JS thread - text_box_set_text(textbox->text_box, ""); - - furi_string_reset(textbox->text); - - text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_is_open(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown)); -} - -static void textbox_callback(void* context, uint32_t arg) { - UNUSED(arg); - JsTextboxInst* textbox = context; - view_holder_set_view(textbox->view_holder, NULL); - textbox->is_shown = false; -} - -static void textbox_exit(void* context) { - JsTextboxInst* textbox = context; - // Using timer to schedule view_holder stop, will not work under high CPU load - furi_timer_pending_callback(textbox_callback, textbox, 0); -} - -static void js_textbox_show(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - if(textbox->is_shown) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Textbox is already shown"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } - - view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box)); - textbox->is_shown = true; - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void js_textbox_close(struct mjs* mjs) { - JsTextboxInst* textbox = get_this_ctx(mjs); - if(!check_arg_count(mjs, 0)) return; - - view_holder_set_view(textbox->view_holder, NULL); - textbox->is_shown = false; - - mjs_return(mjs, MJS_UNDEFINED); -} - -static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) { - JsTextboxInst* textbox = malloc(sizeof(JsTextboxInst)); - - mjs_val_t textbox_obj = mjs_mk_object(mjs); - mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox)); - mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config)); - mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text)); - mjs_set(mjs, textbox_obj, "clearText", ~0, MJS_MK_FN(js_textbox_clear_text)); - mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open)); - mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show)); - mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close)); - - textbox->text = furi_string_alloc(); - textbox->text_box = text_box_alloc(); - - Gui* gui = furi_record_open(RECORD_GUI); - textbox->view_holder = view_holder_alloc(); - view_holder_attach_to_gui(textbox->view_holder, gui); - view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox); - - *object = textbox_obj; - return textbox; -} - -static void js_textbox_destroy(void* inst) { - JsTextboxInst* textbox = inst; - - view_holder_set_view(textbox->view_holder, NULL); - view_holder_free(textbox->view_holder); - textbox->view_holder = NULL; - - furi_record_close(RECORD_GUI); - - text_box_reset(textbox->text_box); - furi_string_reset(textbox->text); - - text_box_free(textbox->text_box); - furi_string_free(textbox->text); - free(textbox); -} - -static const JsModuleDescriptor js_textbox_desc = { - "textbox", - js_textbox_create, - js_textbox_destroy, -}; - -static const FlipperAppPluginDescriptor textbox_plugin_descriptor = { - .appid = PLUGIN_APP_ID, - .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_textbox_desc, -}; - -const FlipperAppPluginDescriptor* js_textbox_ep(void) { - return &textbox_plugin_descriptor; -} diff --git a/applications/system/js_app/plugin_api/app_api_table_i.h b/applications/system/js_app/plugin_api/app_api_table_i.h index b48221343fe..b2debbde877 100644 --- a/applications/system/js_app/plugin_api/app_api_table_i.h +++ b/applications/system/js_app/plugin_api/app_api_table_i.h @@ -7,4 +7,5 @@ static constexpr auto app_api_table = sort(create_array_t( API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)), API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)), - API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)))); + API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)), + API_METHOD(js_module_get, void*, (JsModules*, const char*)))); diff --git a/applications/system/js_app/plugin_api/js_plugin_api.h b/applications/system/js_app/plugin_api/js_plugin_api.h index a817d34a90f..421b6857624 100644 --- a/applications/system/js_app/plugin_api/js_plugin_api.h +++ b/applications/system/js_app/plugin_api/js_plugin_api.h @@ -7,12 +7,16 @@ extern "C" { #endif +typedef void JsModules; + bool js_delay_with_flags(struct mjs* mjs, uint32_t time); void js_flags_set(struct mjs* mjs, uint32_t flags); uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); +void* js_module_get(JsModules* modules, const char* name); + #ifdef __cplusplus } #endif diff --git a/applications/system/js_app/types/badusb/index.d.ts b/applications/system/js_app/types/badusb/index.d.ts new file mode 100644 index 00000000000..2107909673c --- /dev/null +++ b/applications/system/js_app/types/badusb/index.d.ts @@ -0,0 +1,81 @@ +/** + * @brief Special key codes that this module recognizes + */ +export type ModifierKey = "CTRL" | "SHIFT" | "ALT" | "GUI"; + +export type MainKey = + "DOWN" | "LEFT" | "RIGHT" | "UP" | + + "ENTER" | "PAUSE" | "CAPSLOCK" | "DELETE" | "BACKSPACE" | "END" | "ESC" | + "HOME" | "INSERT" | "NUMLOCK" | "PAGEUP" | "PAGEDOWN" | "PRINTSCREEN" | + "SCROLLLOCK" | "SPACE" | "TAB" | "MENU" | + + "F1" | "F2" | "F3" | "F4" | "F5" | "F6" | "F7" | "F8" | "F9" | "F10" | + "F11" | "F12" | "F13" | "F14" | "F15" | "F16" | "F17" | "F18" | "F19" | + "F20" | "F21" | "F22" | "F23" | "F24" | + + "\n" | " " | "!" | "\"" | "#" | "$" | "%" | "&" | "'" | "(" | ")" | "*" | + "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | ">" | "=" | "?" | "@" | "[" | + "]" | "\\" | "^" | "_" | "`" | "{" | "}" | "|" | "~" | + + "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | + + "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | + "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | + "Y" | "Z" | + + "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | + "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | + "y" | "z"; + +export type KeyCode = MainKey | ModifierKey | number; + +/** + * @brief Initializes the module + * @param settings USB device settings. Omit to select default parameters + */ +export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string }): void; + +/** + * @brief Tells whether the virtual USB HID device has successfully connected + */ +export declare function isConnected(): boolean; + +/** + * @brief Presses one or multiple keys at once, then releases them + * @param keys The arguments represent a set of keys to. Out of that set, only + * one of the keys may represent a "main key" (see `MainKey`), with + * the rest being modifier keys (see `ModifierKey`). + */ +export declare function press(...keys: KeyCode[]): void; + +/** + * @brief Presses one or multiple keys at once without releasing them + * @param keys The arguments represent a set of keys to. Out of that set, only + * one of the keys may represent a "main key" (see `MainKey`), with + * the rest being modifier keys (see `ModifierKey`). + */ +export declare function hold(...keys: KeyCode[]): void; + +/** + * @brief Releases one or multiple keys at once + * @param keys The arguments represent a set of keys to. Out of that set, only + * one of the keys may represent a "main key" (see `MainKey`), with + * the rest being modifier keys (see `ModifierKey`). + */ +export declare function release(...keys: KeyCode[]): void; + +/** + * @brief Prints a string by repeatedly pressing and releasing keys + * @param string The string to print + * @param delay How many milliseconds to wait between key presses + */ +export declare function print(string: string, delay?: number): void; + +/** + * @brief Prints a string by repeatedly pressing and releasing keys. Presses + * "Enter" after printing the string + * @param string The string to print + * @param delay How many milliseconds to wait between key presses + */ +export declare function println(): void; diff --git a/applications/system/js_app/types/event_loop/index.d.ts b/applications/system/js_app/types/event_loop/index.d.ts new file mode 100644 index 00000000000..49237782c51 --- /dev/null +++ b/applications/system/js_app/types/event_loop/index.d.ts @@ -0,0 +1,70 @@ +type Lit = undefined | null | {}; + +/** + * Subscription control interface + */ +export interface Subscription { + /** + * Cancels the subscription, preventing any future events managed by the + * subscription from firing + */ + cancel(): void; +} + +/** + * Opaque event source identifier + */ +export type Contract = symbol; + +/** + * A callback can be assigned to an event loop to listen to an event. It may + * return an array with values that will be passed to it as arguments the next + * time that it is called. The first argument is always the subscription + * manager, and the second argument is always the item that trigged the event. + * The type of the item is defined by the event source. + */ +export type Callback = (subscription: Subscription, item: Item, ...args: Args) => Args | undefined | void; + +/** + * Subscribes a callback to an event + * @param contract Event identifier + * @param callback Function to call when the event is triggered + * @param args Initial arguments passed to the callback + */ +export function subscribe(contract: Contract, callback: Callback, ...args: Args): Subscription; +/** + * Runs the event loop until it is stopped (potentially never) + */ +export function run(): void | never; +/** + * Stops the event loop + */ +export function stop(): void; + +/** + * Creates a timer event that can be subscribed to just like any other event + * @param mode Either `"oneshot"` or `"periodic"` + * @param interval Timer interval in milliseconds + */ +export function timer(mode: "oneshot" | "periodic", interval: number): Contract; + +/** + * Message queue + */ +export interface Queue { + /** + * Message event + */ + input: Contract; + /** + * Sends a message to the queue + * @param message message to send + */ + send(message: T): void; +} + +/** + * Creates a message queue + * @param length maximum queue capacity + */ +export function queue(length: number): Queue; diff --git a/applications/system/js_app/types/flipper/index.d.ts b/applications/system/js_app/types/flipper/index.d.ts new file mode 100644 index 00000000000..b1b1d474bc5 --- /dev/null +++ b/applications/system/js_app/types/flipper/index.d.ts @@ -0,0 +1,14 @@ +/** + * @brief Returns the device model + */ +export declare function getModel(): string; + +/** + * @brief Returns the name of the virtual dolphin + */ +export declare function getName(): string; + +/** + * @brief Returns the battery charge percentage + */ +export declare function getBatteryCharge(): number; diff --git a/applications/system/js_app/types/global.d.ts b/applications/system/js_app/types/global.d.ts new file mode 100644 index 00000000000..ab1660cf66d --- /dev/null +++ b/applications/system/js_app/types/global.d.ts @@ -0,0 +1,178 @@ +/** + * @brief Pauses JavaScript execution for a while + * @param ms How many milliseconds to pause the execution for + */ +declare function delay(ms: number): void; + +/** + * @brief Prints to the GUI console view + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the console view + */ +declare function print(...args: any[]): void; + +/** + * @brief Converts a number to a string + * @param value The number to convert to a string + * @param base Integer base (`2`...`16`), default: 16 + */ +declare function toString(value: number, base?: number): string; + +/** + * @brief Reads a JS value from a file + * + * Reads a file at the specified path, interprets it as a JS value and returns + * said value. + * + * @param path The path to the file + */ +declare function load(path: string): any; + +/** + * @brief mJS Foreign Pointer type + * + * JavaScript code cannot do anything with values of `RawPointer` type except + * acquire them from native code and pass them right back to other parts of + * native code. These values cannot be turned into something meaningful, nor can + * be they modified. + */ +declare type RawPointer = symbol & { "__tag__": "raw_ptr" }; +// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist. + +/** + * @brief Holds raw bytes + */ +declare class ArrayBuffer { + /** + * @brief The pointer to the byte buffer + * @note Like other `RawPointer` values, this value is essentially useless + * to JS code. + */ + getPtr: RawPointer; + /** + * @brief The length of the buffer in bytes + */ + byteLength: number; + /** + * @brief Creates an `ArrayBuffer` that contains a sub-part of the buffer + * @param start The index of the byte in the source buffer to be used as the + * start for the new buffer + * @param end The index of the byte in the source buffer that follows the + * byte to be used as the last byte for the new buffer + */ + slice(start: number, end?: number): ArrayBuffer; +} + +declare function ArrayBuffer(): ArrayBuffer; + +declare type ElementType = "u8" | "i8" | "u16" | "i16" | "u32" | "i32"; + +declare class TypedArray { + /** + * @brief The length of the buffer in bytes + */ + byteLength: number; + /** + * @brief The length of the buffer in typed elements + */ + length: number; + /** + * @brief The underlying `ArrayBuffer` + */ + buffer: ArrayBuffer; +} + +declare class Uint8Array extends TypedArray<"u8"> { } +declare class Int8Array extends TypedArray<"i8"> { } +declare class Uint16Array extends TypedArray<"u16"> { } +declare class Int16Array extends TypedArray<"i16"> { } +declare class Uint32Array extends TypedArray<"u32"> { } +declare class Int32Array extends TypedArray<"i32"> { } + +declare function Uint8Array(data: ArrayBuffer | number | number[]): Uint8Array; +declare function Int8Array(data: ArrayBuffer | number | number[]): Int8Array; +declare function Uint16Array(data: ArrayBuffer | number | number[]): Uint16Array; +declare function Int16Array(data: ArrayBuffer | number | number[]): Int16Array; +declare function Uint32Array(data: ArrayBuffer | number | number[]): Uint32Array; +declare function Int32Array(data: ArrayBuffer | number | number[]): Int32Array; + +declare const console: { + /** + * @brief Prints to the UART logs at the `[I]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + log(...args: any[]): void; + /** + * @brief Prints to the UART logs at the `[D]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + debug(...args: any[]): void; + /** + * @brief Prints to the UART logs at the `[W]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + warn(...args: any[]): void; + /** + * @brief Prints to the UART logs at the `[E]` level + * @param args The arguments are converted to strings, concatenated without any + * spaces in between and printed to the logs + */ + error(...args: any[]): void; +}; + +declare class Array { + /** + * @brief Takes items out of the array + * + * Removes elements from the array and returns them in a new array + * + * @param start The index to start taking elements from + * @param deleteCount How many elements to take + * @returns The elements that were taken out of the original array as a new + * array + */ + splice(start: number, deleteCount: number): T[]; + /** + * @brief Adds a value to the end of the array + * @param value The value to add + * @returns New length of the array + */ + push(value: T): number; + /** + * @brief How many elements there are in the array + */ + length: number; +} + +declare class String { + /** + * @brief How many characters there are in the string + */ + length: number; + /** + * @brief Returns the character code at an index in the string + * @param index The index to consult + */ + charCodeAt(index: number): number; + /** + * See `charCodeAt` + */ + at(index: number): number; +} + +declare class Boolean { } + +declare class Function { } + +declare class Number { } + +declare class Object { } + +declare class RegExp { } + +declare interface IArguments { } + +declare type Partial = { [K in keyof O]?: O[K] }; diff --git a/applications/system/js_app/types/gpio/index.d.ts b/applications/system/js_app/types/gpio/index.d.ts new file mode 100644 index 00000000000..18705f8982c --- /dev/null +++ b/applications/system/js_app/types/gpio/index.d.ts @@ -0,0 +1,45 @@ +import type { Contract } from "../event_loop"; + +export interface Mode { + direction: "in" | "out"; + outMode?: "push_pull" | "open_drain"; + inMode?: "analog" | "plain_digital" | "interrupt" | "event"; + edge?: "rising" | "falling" | "both"; + pull?: "up" | "down"; +} + +export interface Pin { + /** + * Configures a pin. This may be done several times. + * @param mode Pin configuration object + */ + init(mode: Mode): void; + /** + * Sets the output value of a pin if it's been configured with + * `direction: "out"`. + * @param value Logic value to output + */ + write(value: boolean): void; + /** + * Gets the input value of a pin if it's been configured with + * `direction: "in"`, but not `inMode: "analog"`. + */ + read(): boolean; + /** + * Gets the input voltage of a pin in millivolts if it's been configured + * with `direction: "in"` and `inMode: "analog"` + */ + read_analog(): number; + /** + * Returns an `event_loop` event that can be used to listen to interrupts, + * as configured by `init` + */ + interrupt(): Contract; +} + +/** + * Returns an object that can be used to manage a GPIO pin. For the list of + * available pins, see https://docs.flipper.net/gpio-and-modules#miFsS + * @param pin Pin name (e.g. `"PC3"`) or number (e.g. `7`) + */ +export function get(pin: string | number): Pin; diff --git a/applications/system/js_app/types/gui/dialog.d.ts b/applications/system/js_app/types/gui/dialog.d.ts new file mode 100644 index 00000000000..6d9c8d43b2f --- /dev/null +++ b/applications/system/js_app/types/gui/dialog.d.ts @@ -0,0 +1,16 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + text: string, + left: string, + center: string, + right: string, +} +declare class Dialog extends View { + input: Contract<"left" | "center" | "right">; +} +declare class DialogFactory extends ViewFactory { } +declare const factory: DialogFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/empty_screen.d.ts b/applications/system/js_app/types/gui/empty_screen.d.ts new file mode 100644 index 00000000000..c71e93b3271 --- /dev/null +++ b/applications/system/js_app/types/gui/empty_screen.d.ts @@ -0,0 +1,7 @@ +import type { View, ViewFactory } from "."; + +type Props = {}; +declare class EmptyScreen extends View { } +declare class EmptyScreenFactory extends ViewFactory { } +declare const factory: EmptyScreenFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/index.d.ts b/applications/system/js_app/types/gui/index.d.ts new file mode 100644 index 00000000000..3f95ab78067 --- /dev/null +++ b/applications/system/js_app/types/gui/index.d.ts @@ -0,0 +1,41 @@ +import type { Contract } from "../event_loop"; + +type Properties = { [K: string]: any }; + +export declare class View { + set

(property: P, value: Props[P]): void; +} + +export declare class ViewFactory> { + make(): V; + makeWith(initial: Partial): V; +} + +declare class ViewDispatcher { + /** + * Event source for `sendCustom` events + */ + custom: Contract; + /** + * Event source for navigation events (back key presses) + */ + navigation: Contract; + /** + * Sends a number to the custom event handler + * @param event number to send + */ + sendCustom(event: number): void; + /** + * Switches to a view + * @param assoc View-ViewDispatcher association as returned by `add` + */ + switchTo(assoc: View): void; + /** + * Sends this ViewDispatcher to the front or back, above or below all other + * GUI viewports + * @param direction Either `"front"` or `"back"` + */ + sendTo(direction: "front" | "back"): void; +} + +export const viewDispatcher: ViewDispatcher; diff --git a/applications/system/js_app/types/gui/loading.d.ts b/applications/system/js_app/types/gui/loading.d.ts new file mode 100644 index 00000000000..73a9633494c --- /dev/null +++ b/applications/system/js_app/types/gui/loading.d.ts @@ -0,0 +1,7 @@ +import type { View, ViewFactory } from "."; + +type Props = {}; +declare class Loading extends View { } +declare class LoadingFactory extends ViewFactory { } +declare const factory: LoadingFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/submenu.d.ts b/applications/system/js_app/types/gui/submenu.d.ts new file mode 100644 index 00000000000..59d53586431 --- /dev/null +++ b/applications/system/js_app/types/gui/submenu.d.ts @@ -0,0 +1,13 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + items: string[], +}; +declare class Submenu extends View { + chosen: Contract; +} +declare class SubmenuFactory extends ViewFactory { } +declare const factory: SubmenuFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/text_box.d.ts b/applications/system/js_app/types/gui/text_box.d.ts new file mode 100644 index 00000000000..3dbbac5710c --- /dev/null +++ b/applications/system/js_app/types/gui/text_box.d.ts @@ -0,0 +1,14 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + text: string, + font: "text" | "hex", + focus: "start" | "end", +} +declare class TextBox extends View { + chosen: Contract; +} +declare class TextBoxFactory extends ViewFactory { } +declare const factory: TextBoxFactory; +export = factory; diff --git a/applications/system/js_app/types/gui/text_input.d.ts b/applications/system/js_app/types/gui/text_input.d.ts new file mode 100644 index 00000000000..96652b1d454 --- /dev/null +++ b/applications/system/js_app/types/gui/text_input.d.ts @@ -0,0 +1,14 @@ +import type { View, ViewFactory } from "."; +import type { Contract } from "../event_loop"; + +type Props = { + header: string, + minLength: number, + maxLength: number, +} +declare class TextInput extends View { + input: Contract; +} +declare class TextInputFactory extends ViewFactory { } +declare const factory: TextInputFactory; +export = factory; diff --git a/applications/system/js_app/types/math/index.d.ts b/applications/system/js_app/types/math/index.d.ts new file mode 100644 index 00000000000..25abca4af14 --- /dev/null +++ b/applications/system/js_app/types/math/index.d.ts @@ -0,0 +1,24 @@ +export function abs(n: number): number; +export function acos(n: number): number; +export function acosh(n: number): number; +export function asin(n: number): number; +export function asinh(n: number): number; +export function atan(n: number): number; +export function atan2(a: number, b: number): number; +export function atanh(n: number): number; +export function cbrt(n: number): number; +export function ceil(n: number): number; +export function clz32(n: number): number; +export function cos(n: number): number; +export function exp(n: number): number; +export function floor(n: number): number; +export function max(n: number, m: number): number; +export function min(n: number, m: number): number; +export function pow(n: number, m: number): number; +export function random(): number; +export function sign(n: number): number; +export function sin(n: number): number; +export function sqrt(n: number): number; +export function trunc(n: number): number; +declare const PI: number; +declare const EPSILON: number; diff --git a/applications/system/js_app/types/notification/index.d.ts b/applications/system/js_app/types/notification/index.d.ts new file mode 100644 index 00000000000..947daba2186 --- /dev/null +++ b/applications/system/js_app/types/notification/index.d.ts @@ -0,0 +1,20 @@ +/** + * @brief Signals success to the user via the color LED, speaker and vibration + * motor + */ +export declare function success(): void; + +/** + * @brief Signals failure to the user via the color LED, speaker and vibration + * motor + */ +export declare function error(): void; + +export type Color = "red" | "green" | "blue" | "yellow" | "cyan" | "magenta"; + +/** + * @brief Displays a basic color on the color LED + * @param color The color to display, see `Color` + * @param duration The duration, either `"short"` (10ms) or `"long"` (100ms) + */ +export declare function blink(color: Color, duration: "short" | "long"): void; diff --git a/applications/system/js_app/types/serial/index.d.ts b/applications/system/js_app/types/serial/index.d.ts new file mode 100644 index 00000000000..1a7ed6397e3 --- /dev/null +++ b/applications/system/js_app/types/serial/index.d.ts @@ -0,0 +1,77 @@ +/** + * @brief Initializes the serial port + * @param port The port to initialize (`"lpuart"` or `"start"`) + * @param baudRate + */ +export declare function setup(port: "lpuart" | "usart", baudRate: number): void; + +/** + * @brief Writes data to the serial port + * @param value The data to write: + * - Strings will get sent as ASCII. + * - Numbers will get sent as a single byte. + * - Arrays of numbers will get sent as a sequence of bytes. + * - `ArrayBuffer`s and `TypedArray`s will be sent as a sequence + * of bytes. + */ +export declare function write(value: string | number | number[] | ArrayBuffer | TypedArray): void; + +/** + * @brief Reads data from the serial port + * @param length The number of bytes to read + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. + * @returns The received data interpreted as ASCII, or `undefined` if 0 bytes + * were read. + */ +export declare function read(length: number, timeout?: number): string | undefined; + +/** + * @brief Reads data from the serial port + * + * Data is read one character after another until either a `\r` or `\n` + * character is received, neither of which is included in the result. + * + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. The timeout only + * applies to characters, not entire strings. + * @returns The received data interpreted as ASCII, or `undefined` if 0 bytes + * were read. + */ +export declare function readln(timeout?: number): string; + +/** + * @brief Reads data from the serial port + * @param length The number of bytes to read + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. + * @returns The received data as an ArrayBuffer, or `undefined` if 0 bytes were + * read. + */ +export declare function readBytes(length: number, timeout?: number): ArrayBuffer; + +/** + * @brief Reads data from the serial port, trying to match it to a pattern + * @param patterns A single pattern or an array of patterns: + * - If the argument is a single `string`, this function will + * match against the given string. + * - If the argument is an array of `number`s, this function + * will match against the given sequence of bytes, + * - If the argument is an array of `string`s, this function + * will match against any string out of the ones that were + * provided. + * - If the argument is an array of arrays of `number`s, this + * function will match against any sequence of bytes out of + * the ones that were provided. + * @param timeout The number of time, in milliseconds, after which this function + * will give up and return what it read up to that point. If + * unset, the function will wait forever. The timeout only + * applies to characters, not entire strings. + * @returns The index of the matched pattern if multiple were provided, or 0 if + * only one was provided and it matched, or `undefined` if none of the + * patterns matched. + */ +export declare function expect(patterns: string | number[] | string[] | number[][], timeout?: number): number | undefined; diff --git a/applications/system/js_app/types/storage/index.d.ts b/applications/system/js_app/types/storage/index.d.ts new file mode 100644 index 00000000000..0dd29e121cb --- /dev/null +++ b/applications/system/js_app/types/storage/index.d.ts @@ -0,0 +1,237 @@ +/** + * File readability mode: + * - `"r"`: read-only + * - `"w"`: write-only + * - `"rw"`: read-write + */ +export type AccessMode = "r" | "w" | "rw"; + +/** + * File creation mode: + * - `"open_existing"`: open file or fail if it doesn't exist + * - `"open_always"`: open file or create a new empty one if it doesn't exist + * - `"open_append"`: open file and set r/w pointer to EOF, or create a new one if it doesn't exist + * - `"create_new"`: create new file or fail if it exists + * - `"create_always"`: truncate and open file, or create a new empty one if it doesn't exist + */ +export type OpenMode = "open_existing" | "open_always" | "open_append" | "create_new" | "create_always"; + +/** Standard UNIX timestamp */ +export type Timestamp = number; + +/** File information structure */ +export declare class FileInfo { + /** + * Full path (e.g. "/ext/test", returned by `stat`) or file name + * (e.g. "test", returned by `readDirectory`) + */ + path: string; + /** + * Is the file a directory? + */ + isDirectory: boolean; + /** + * File size in bytes, or 0 in the case of directories + */ + size: number; + /** + * Time of last access as a UNIX timestamp + */ + accessTime: Timestamp; +} + +/** Filesystem information structure */ +export declare class FsInfo { + /** Total size of the filesystem, in bytes */ + totalSpace: number; + /** Free space in the filesystem, in bytes */ + freeSpace: number; +} + +// file operations + +/** File class */ +export declare class File { + /** + * Closes the file. After this method is called, all other operations + * related to this file become unavailable. + * @returns `true` on success, `false` on failure + */ + close(): boolean; + /** + * Is the file currently open? + */ + isOpen(): boolean; + /** + * Reads bytes from a file opened in read-only or read-write mode + * @param mode The data type to interpret the bytes as: a `string` decoded + * from ASCII data (`"ascii"`), or an `ArrayBuf` (`"binary"`) + * @param bytes How many bytes to read from the file + * @returns an `ArrayBuf` if the mode is `"binary"`, a `string` if the mode + * is `ascii`. The number of bytes that was actually read may be + * fewer than requested. + */ + read(mode: T extends ArrayBuffer ? "binary" : "ascii", bytes: number): T; + /** + * Writes bytes to a file opened in write-only or read-write mode + * @param data The data to write: a string that will be ASCII-encoded, or an + * ArrayBuf + * @returns the amount of bytes that was actually written + */ + write(data: ArrayBuffer | string): number; + /** + * Moves the R/W pointer forward + * @param bytes How many bytes to move the pointer forward by + * @returns `true` on success, `false` on failure + */ + seekRelative(bytes: number): boolean; + /** + * Moves the R/W pointer to an absolute position inside the file + * @param bytes The position inside the file + * @returns `true` on success, `false` on failure + */ + seekAbsolute(bytes: number): boolean; + /** + * Gets the absolute position of the R/W pointer in bytes + */ + tell(): number; + /** + * Discards the data after the current position of the R/W pointer in a file + * opened in either write-only or read-write mode. + * @returns `true` on success, `false` on failure + */ + truncate(): boolean; + /** + * Reads the total size of the file in bytes + */ + size(): number; + /** + * Detects whether the R/W pointer has reached the end of the file + */ + eof(): boolean; + /** + * Copies bytes from the R/W pointer in the current file to the R/W pointer + * in another file + * @param dest The file to copy the bytes into + * @param bytes The number of bytes to copy + * @returns `true` on success, `false` on failure + */ + copyTo(dest: File, bytes: number): boolean; +} + +/** + * Opens a file + * @param path The path to the file + * @param accessMode `"r"`, `"w"` or `"rw"`; see `AccessMode` + * @param openMode `"open_existing"`, `"open_always"`, `"open_append"`, + * `"create_new"` or `"create_always"`; see `OpenMode` + * @returns a `File` on success, or `undefined` on failure + */ +export declare function openFile(path: string, accessMode: AccessMode, openMode: OpenMode): File | undefined; +/** + * Detects whether a file exists + * @param path The path to the file + * @returns `true` on success, `false` on failure + */ +export declare function fileExists(path: string): boolean; + +// directory operations + +/** + * Reads the list of files in a directory + * @param path The path to the directory + * @returns Array of `FileInfo` structures with directory entries, + * or `undefined` on failure + */ +export declare function readDirectory(path: string): FileInfo[] | undefined; +/** + * Detects whether a directory exists + * @param path The path to the directory + */ +export declare function directoryExists(path: string): boolean; +/** + * Creates an empty directory + * @param path The path to the new directory + * @returns `true` on success, `false` on failure + */ +export declare function makeDirectory(path: string): boolean; + +// common (file/dir) operations + +/** + * Detects whether a file or a directory exists + * @param path The path to the file or directory + */ +export declare function fileOrDirExists(path: string): boolean; +/** + * Acquires metadata about a file or directory + * @param path The path to the file or directory + * @returns A `FileInfo` structure or `undefined` on failure + */ +export declare function stat(path: string): FileInfo | undefined; +/** + * Removes a file or an empty directory + * @param path The path to the file or directory + * @returns `true` on success, `false` on failure + */ +export declare function remove(path: string): boolean; +/** + * Removes a file or recursively removes a possibly non-empty directory + * @param path The path to the file or directory + * @returns `true` on success, `false` on failure + */ +export declare function rmrf(path: string): boolean; +/** + * Renames or moves a file or directory + * @param oldPath The old path to the file or directory + * @param newPath The new path that the file or directory will become accessible + * under + * @returns `true` on success, `false` on failure + */ +export declare function rename(oldPath: string, newPath: string): boolean; +/** + * Copies a file or recursively copies a possibly non-empty directory + * @param oldPath The original path to the file or directory + * @param newPath The new path that the copy of the file or directory will be + * accessible under + */ +export declare function copy(oldPath: string, newPath: string): boolean; +/** + * Fetches generic information about a filesystem + * @param filesystem The path to the filesystem (e.g. `"/ext"` or `"/int"`) + */ +export declare function fsInfo(filesystem: string): FsInfo | undefined; +/** + * Chooses the next available filename with a numeric suffix in a directory + * + * ``` + * "/ext/example_dir/example_file123.txt" + * \______________/ \__________/\_/\__/ + * dirPath fileName | | + * | +---- fileExt + * +------- selected by this function + * ``` + * + * @param dirPath The directory to look in + * @param fileName The base of the filename (the part before the numeric suffix) + * @param fileExt The extension of the filename (the part after the numeric suffix) + * @param maxLen The maximum length of the filename with the numeric suffix + * @returns The base of the filename with the next available numeric suffix, + * without the extension or the base directory. + */ +export declare function nextAvailableFilename(dirPath: string, fileName: string, fileExt: string, maxLen: number): string; + +// path operations that do not access the filesystem + +/** + * Determines whether the two paths are equivalent. Respects filesystem-defined + * path equivalence rules. + */ +export declare function arePathsEqual(path1: string, path2: string): boolean; +/** + * Determines whether a path is a subpath of another path. Respects + * filesystem-defined path equivalence rules. + * @param parentPath The parent path + * @param childPath The child path + */ +export declare function isSubpathOf(parentPath: string, childPath: string): boolean; diff --git a/applications/system/js_app/types/tests/index.d.ts b/applications/system/js_app/types/tests/index.d.ts new file mode 100644 index 00000000000..8aaeec5e523 --- /dev/null +++ b/applications/system/js_app/types/tests/index.d.ts @@ -0,0 +1,8 @@ +/** + * Unit test module. Only available if the firmware has been configured with + * `FIRMWARE_APP_SET=unit_tests`. + */ + +export function fail(message: string): never; +export function assert_eq(expected: T, result: T): void | never; +export function assert_float_close(expected: number, result: number, epsilon: number): void | never; diff --git a/documentation/doxygen/Doxyfile.cfg b/documentation/doxygen/Doxyfile.cfg index 90f36415f15..e016317494f 100644 --- a/documentation/doxygen/Doxyfile.cfg +++ b/documentation/doxygen/Doxyfile.cfg @@ -1108,7 +1108,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = +IMAGE_PATH = $(DOXY_SRC_ROOT)/documentation/images # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program diff --git a/documentation/doxygen/js.dox b/documentation/doxygen/js.dox index 33ac078d923..f5c609dd14b 100644 --- a/documentation/doxygen/js.dox +++ b/documentation/doxygen/js.dox @@ -11,12 +11,20 @@ This page contains some information on the Flipper Zero scripting engine, which JS modules use the Flipper app plugin system. Each module is compiled into a `.fal` library file and is located on a microSD card. Here is a list of implemented modules: -- @subpage js_badusb — BadUSB module -- @subpage js_serial — Serial module -- @subpage js_math — Math module -- @subpage js_dialog — Dialog module -- @subpage js_submenu — Submenu module -- @subpage js_textbox — Textbox module -- @subpage js_notification — Notifications module +- @subpage js_badusb - BadUSB module +- @subpage js_serial - Serial module +- @subpage js_math - Math module +- @subpage js_notification - Notifications module +- @subpage js_event_loop - Event Loop module +- @subpage js_gpio - GPIO module +- @subpage js_gui - GUI module and its submodules: + - @subpage js_gui__submenu - Submenu view + - @subpage js_gui__loading - Hourglass (Loading) view + - @subpage js_gui__empty_screen - Empty view + - @subpage js_gui__text_input - Keyboard-like text input + - @subpage js_gui__text_box - Simple multiline text box + - @subpage js_gui__dialog - Dialog with up to 3 options + +All modules have corresponding TypeScript declaration files, so you can set up your IDE to show suggestions when writing JS scripts. */ diff --git a/documentation/images/dialog.png b/documentation/images/dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..008ae9ce524fc2e34f234bb64f7bc4fee216a1eb GIT binary patch literal 1377 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#68 z3VXUZhE&XXd)LwLwu3~&MYckjbH^1e3urW;@tOptGJV_eUB{v*>16Y zegF1&hA(^O)G{*6ab{px#KNH9HA>Mf2wr`C!_DxlqVoTr-|vA@A-uk2^#&$}4k3mH z4Mv8JT{A^LUA|}e>DqbuneP8<-b_ocy1#D5-P`;LEust#Qy3UrIDyo8#tla%?)*P- zFZ;h4TjdWIZ$=ofiT!|3`q{btHEjvkp7V#*_Z{?^AGrn*zi${fWTs5}d*Zq5%&+-> z&)hrSCsX|ObJJclCD-oniGR8$AeogxK$U?ZkcmMcm7l?H+ppci3EfBzv1OQ%@Oqzv z^0#weYSWE2M=(O%2XyA$Gwcs7XZ+qg9YuA6hBm~XQ11>eYQ^=<`VIAQ_w3K_V~Ke8 z`tjR0-VbIY{D0-F&AXUq|6(qlzc1~6?{MA!Ij6bcdGD0m0nW6uf8FDDM}EFJ@o~=I zKNBCnF)g|m`T4bW)Wl=e! z!jtzmx7!@5`F8Qyq0Rs7xA*P*zESyjWbM6)uPa-w_w7W9aDyjnes5VbukgL?vlBiw z?~~p8Dxd6epYhw?94Wr48F(UV<^L-`uT^{3dEPb>5!pAHcQi!aUmrbtdHls!*E8OK z+*>~n$>URO8@gwf{;&F%|GxHTzfIW_)45-*-pze^JH09o$esDBZ3c@>KS&6ezW94aruGm%Eh_Hh3ifrg7TGlixSt;(kEIWz%rA;)78&q Iol`;+0D;-4I{*Lx literal 0 HcmV?d00001 diff --git a/documentation/images/empty.png b/documentation/images/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..844f45093f96cdc2120fcf9d040934888bfa89ac GIT binary patch literal 1005 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#4o z&-Zk345^s&_NpN-g95_=0|$ODLEi}nH-;VDceeT)!-Dj`d<+lxnHf5a7#cK2snH-9 gO$Bt!2rJl^Su$_wn)*l!n12~OUHx3vIVCg!0K_`c6#xJL literal 0 HcmV?d00001 diff --git a/documentation/images/loading.png b/documentation/images/loading.png new file mode 100644 index 0000000000000000000000000000000000000000..f35966f66a809f796d17a1ec1c9a097d2fe767f0 GIT binary patch literal 1173 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#68 zxO=)dhE&XXd)G0G$x*=JqK4e@&rb#B=|7*SbB818M8mDifA2@L8+Et%m%jY+u*$NY z@j|i8Uj~LAMFxgICI$i3Q3`7iluG?&_z-{m{rrpl8*Y9rlKac?VG1XMf)@kBA{GXP zQ+y4YZ-3_09*br5Rbyz-U}WeJVrW=#c;5M-hcj*GZ~i?$Nem&bVa>3td_Vt>C-*Ul*j^dJ{JM0YRNForY!+dc01$%@4jbAtG?{h9VzyePmPh%Z)PR~8? z!CaWZVG09-3n!40W$>>2es}*j#x+L}3RBq+d~ut5`Zl|PHImZb3?~Z9_>pb3W)Ql= z$2eT9G{Vongi?o%zS-KearFPzIW6$*a*Su*vtn$xBA6U*Xc)I$ztaD0e0sz9R@tyzx literal 0 HcmV?d00001 diff --git a/documentation/images/submenu.png b/documentation/images/submenu.png new file mode 100644 index 0000000000000000000000000000000000000000..1cb64e9748fd70ceeec3b5f62d0ff0af427b499a GIT binary patch literal 1774 zcmb_dYfw{16h4=T5DP(Su@DG_VnqkS2o7&hLY9}tlVgw$!l5fIuYMuJJOqh?Hv;bDXlz+R@QES-+O`s2>t-95W!&-d6Z zjtC1jT|-_20H(B%U6B9=_%8;=OHJq$eGA?YGeYQ@0HmLmKJ0rZk~P3;E^XJ2k8*3( zvc2Cu-e_q!(%pQzQk!5-KX`AQmg^W~E{@=obtwlKQ)VwSB^Kjmg^N1nmF?g)aIWx` zPRC43HUjIn0nV?2fadokg&n0<*hDKmvy=Vz*d`3ts-}+;U{)Z_=lB3z#silILjg|c z*ZoIV)vxh9lo?xIpI}-dSBIa>R|~hj00<-kC&B>iP5~Hyu-p}O_;4)Py;-AE1;>~* z(k}M!7(ace#8x*Z*KZMnAbhW^W|d7qKE2__v0ii&HC=I!^~R6S-6{rM@> zCJQBVG*R0X-T50k;#q*%axUr_f+ef7TJx0B`&?FVkCj*MJ{V>LT#%GawG$}S z)0mUILct6($mSX?S9+s{kW6pr&2i5A%0fx@ezISHZJE-&WGpzJu^v|=)rpi(SJw_! zYlJC13a-nd3-ypWjIGn-nF;H1!)?5Vs>Nwsqi4bW2hzAtpXiS= zo-xnWozppj@AnTgdsI+bOn9~v*-n~9erA@ci|&_@Qw)Pt#Kc(L z*_q3a$(qBC8=4mtU0oD79H>`G)gU%VuB2FbrjIt|DCCn17VKgET-=b4B&v7wh6p1F z*rAVXHGJXb^u#`TKF&c=WI6io?Di~xcl4r5;qy4jo~hg7fA*c?577eLkU7;g9;dC2 zJWZju3w96Wyv5euU_BZm_5|I{O7gWYLu%I2tYc)%*jbsBP#cUCcYXcH-tIAy-)>~A zq=f(>4G7?8{snle;i?QTL%RCHwNi>e!@IW$dJH-Q3}cIAnPWgCacp=_TJiH~ZqK!@?3}<6|%0 z^c=;z7%7;iWd!tSO3O>#C3nTX9nL&*WIqFMhkVnE+fny_3g=umfeS zY5`lulKQ(}ANjh@k4}i+*lJXuOw^@P2)+7w*1rO*6n;@ ziFPvP!Ei-VL6PG00X9OZqm^$DOBBL zP4Nf^qfn6R2p>=S%cB$x;ezb(R78=Y>`ZX%8J3SNR<=>@7a_XQx;B|)cpy3M*sK&c gPsRv%^LQd=Y35d0T5&{E4z$Czwrv?I!B!Q#^Ent8U0)!-xkYp}nX3cf_V}9Iq?_KBabN2qe zZ-4vVI2s#iW9?`S0I-RUiueM64bUS3SgqfVEFrz1+s2DgNE`sRUF#R(u7|A?09LP} zBR);YEtQX@7D_&HcyH+HvF~51ayF-?3*%KEK0dwG`x@f>0**HpVfe?$?MO57qIA67 zxVG3{o~KuuQE(|nyLR-daZNhc11`RY0Go~g@U=E2uexA2qP|LYJWo-|Xb0fXMnE~b z0qh)hjq?YTRkRY(-99e*5H#;Ht-S#tjqx;XXh055d;ooT2TG0qiKW*PjZ%z1Miked z9C*O*&HPv+!+2<=U7^@>N?W07*oZZ309LzAo5bKi5S2zFUk#v*-@-jI1NV2DR*GsZ z5^6jhO0d%*Z)m;Pz*vQMCTAk}(tb2S7M9q~Ov5B|)t)*=KrGigxeXDtG%!->H{~y5 zO2}tN@rQMhqS4P?0I)C>NH}kMLU4S+9b;ZV4P$s=QpikvXHt3+H(-UX6YJ&*=qx{3 z==>1YL;KZXU1sfV!68`mLBqX{n%)=l&Q-+cG@RX&&vVr+`3C{8Hr1W8=G<|2m7)mb z=r>BVbJ=z|0Bkh};d`OeXIBC#NX&9wC|RMMX)GLV5Dd(Qrew?Aq$rNyIUU1p)W@|d zM~}b0qppF~UoNx5Iwnu<+o)*XlQLngm1ErW6Z_wCvJQ35RTq#Moy!b)?U*!#l~uz+ zXCbvz+5*Z2Q+7Yuf>%?+2yk>002NSH)#q?PFxS@0)3b46G)lx4=M-EeUd~sitJHZn z#~9g`{5DRj3pjrWQpOf2W*E`&RcuMk@rtRZTeKD5!wTnzK!n>~Quv1@^S-2J+FXtQ zbB_2=ykv3ZL5BZOGm;d#MAm$(<2NXHS!r35tBSFVhsMlSB_UZF+lIO}r%t|I`F%KLW5zVKAQgUxq3JDaESFI3cm1YWZ0o>UG@q z)%j&>R#R2RWazy5{ZfWjqW?@!he*C=I>c`P;qbwvJ8PhKPO#%_1#Bgz#;ls-5wbMD zdP7m4Ua%a^Rdb}N*{+%LdzC*m-`DIIKzGhcA2LHc41=`2qgW9eTT+1ZnGCxe{t2=n*yO|6ihI{7FV(J|0 zSE3n5IvaxJs@;Swf914LTl zM(;2p-15{=X%tLMvt?#2g}YOmsQChQRv@qMO0p=Pdee`pWQ}+ZOce$W%_0tP6!A{3Ydc7N?!h(faG4h8+1cw3j@i=5-ePTC71BhIb5XiEq_ohM805RpQgKEo-4UL{g1HI z9+{2wU6{B&xiH7M2di2&;@qL)4A&InzrzqTv>b+HLYOelcWF_J7BRX)ww)oNoi93o z8Ml`s7fXwtRA|Zy*~(B^;LI3VporqCS$uQ2r!skneiZg0ejm2GwG0(>=5&gI_xHXa zU9IMCtftJ#K-Y7h-9!%lvoGW^^tRIYhI4r(6$9_W!*q*JmM*N!#BTrb^v4=KQKHTJ z29~eAx8;dnl-nkLF8M`s8Lxgu)4=Bsq@HO~T=SVRsz~iNA1tJ0)Fw$94E^qkh@vM~ zx^WGijZCp`yAs#bsYk`Y5QRJ`fK)20WwUG)^}ph;Ob6M-qd-TtX*T`wq~}hGOR!%) zoWByY01Dptm(6W3MgKp;`gh;Fy9vk*gh}@|eN=ujc-kk{yu*N2tS}e0y`7au)_+Cp ZMk}^Nr5&Jr2mJtn=)=O(d;sRX_+aAtJ?gO0Xj! z7%;&>7FmLdfDj-|wV_rCC|Otx0V4uQAPPuWOlTNj`tV2p_Q#uh-@WhNbI!M%_p7J7 zi~h!)8vy|NCr>zi55Q-dDGcZ=4X%@c2Q-83PbUK70oeRx>4D{1ZZ-yB1M{TQF`wkZ zagkeAzs>gbT^DcP$o_D4YhXyV#pp2)lSBEx4}~ZT!@}KiswUTqYs-b5W@0tWyt91* z2JT%2Kxa1q$4NKT63K!vBrd@@WQb%x4+pB}nq^%;@%#*!``q8~&b`BQkN{otH7IiF zP^~LLIF7ZTlvJrC6eTU-H1-v+>K1PkujIi*+hB=OJeeAx2ZI^a+~ z76lu>a7LUq)yPcVhFnfGM^g?edS6izOM0hRaEXD=peqGcAqkQ=z6WAaSNQpwn}g5g z$pQ}}Y%UuChdr?6h$(W6gT=do^MetQn%J5+OB;WaG;>H1OJSB!TZQNRzM^jWvyE-y zy&}a~Nb2MjRacbC`iaWA_AVs1cR@OqC%0qfGKASoszAkTws!t79%<&^LB=c1J#0916%8VY zaD%}XG7l5iG)(xzQsI@4V>1c$wZwzuxMAC%-u~n=e0Q7>|LqwzgxLz|5kT>B=1)YW zSR>`qlGBZh&{;U_QQ8W_rXsC;mGMljELo00Mr6E1o1qrD!4_4xcnR9E+CAXfUl<3l zeMJYn7idZ^{UN+-zf_$RIM;XwXTZv8!0TH=)X9Ifbm725@utV#cNZ9JE6AR&9LD1n zMDj-cCn^&U>!Lckr|k=xd^=`{0^Ht~BVo77X~G7~9DyGlKl7ez(*UVMimIrOJ^clR z1$9VRr`A0`gEkyQxx8nJY1qM=|Dv|4`z5C>xhgCQ1{-0W?bxrhs1AcDfJOwh3?NkDzG9vgYwE~H*@qthuUuA4T#*7w z?E08?qH-0zz6N0-9A@kqY8H_*Eft9Y1aj##G9!9DMW(ZCV_t3)Ocao3PmK>y%+Qjl zSb@%0cgMWB194BED%~L2Yt&X$I$f0Af8y^WR)Vj*96d(8XH)NxBi5jX%JfL)mcH>HWHx^ zDO97U>5quUx=0~LVeD&GWy|sEXOM!b8A57%-DKb3B};a33g>{t&ZI76?Xl;~V~XpZ z-UED>n^D{aCHV?xTTOeKnAbrI<}W_{3C}svlLt zQ{6og_Zz(xJ*KIia88vl>MUWPf>nlqn zPvajF0g!*W(l~w5lx_jhwz2eLnxVjkCNenDsvSGW>Os+aMgyvNC4%|e2@Oo^xE|@g#QCC zRT*}G_`tB$(Ro~`r=ew~AGFXhWmm9|tllgZW}_hSauC4eP5Q_vy-oRY=YLQ-zhmfH Wr2rOx&q?z;2PeOCcVZq7P5TEYI33~u literal 0 HcmV?d00001 diff --git a/documentation/js/js_builtin.md b/documentation/js/js_builtin.md index 3d113807b07..9c59b982246 100644 --- a/documentation/js/js_builtin.md +++ b/documentation/js/js_builtin.md @@ -41,16 +41,10 @@ print("string1", "string2", 123); Same as `print`, but output to serial console only, with corresponding log level. ## to_string -Convert a number to string. +Convert a number to string with an optional base. ### Examples: ```js -to_string(123) -``` -## to_hex_string -Convert a number to string(hex format). - -### Examples: -```js -to_hex_string(0xFF) +to_string(123) // "123" +to_string(123, 16) // "0x7b" ``` diff --git a/documentation/js/js_dialog.md b/documentation/js/js_dialog.md deleted file mode 100644 index eb027e6a738..00000000000 --- a/documentation/js/js_dialog.md +++ /dev/null @@ -1,49 +0,0 @@ -# js_dialog {#js_dialog} - -# Dialog module -```js -let dialog = require("dialog"); -``` -# Methods - -## message -Show a simple message dialog with header, text and "OK" button. - -### Parameters -- Dialog header text -- Dialog text - -### Returns -true if central button was pressed, false if the dialog was closed by back key press - -### Examples: -```js -dialog.message("Dialog demo", "Press OK to start"); -``` - -## custom -More complex dialog with configurable buttons - -### Parameters -Configuration object with the following fields: -- header: Dialog header text -- text: Dialog text -- button_left: (optional) left button name -- button_right: (optional) right button name -- button_center: (optional) central button name - -### Returns -Name of pressed button or empty string if the dialog was closed by back key press - -### Examples: -```js -let dialog_params = ({ - header: "Dialog header", - text: "Dialog text", - button_left: "Left", - button_right: "Right", - button_center: "OK" -}); - -dialog.custom(dialog_params); -``` diff --git a/documentation/js/js_event_loop.md b/documentation/js/js_event_loop.md new file mode 100644 index 00000000000..9519478c0ed --- /dev/null +++ b/documentation/js/js_event_loop.md @@ -0,0 +1,144 @@ +# js_event_loop {#js_event_loop} + +# Event Loop module +```js +let eventLoop = require("event_loop"); +``` + +The event loop is central to event-based programming in many frameworks, and our +JS subsystem is no exception. It is a good idea to familiarize yourself with the +event loop first before using any of the advanced modules (e.g. GPIO and GUI). + +## Conceptualizing the event loop +If you ever wrote JavaScript before, you have definitely seen callbacks. It's +when a function accepts another function (usually an anonymous one) as one of +the arguments, which it will call later on, e.g. when an event happens or when +data becomes ready: +```js +setTimeout(function() { console.log("Hello, World!") }, 1000); +``` + +Many JavaScript engines employ a queue that the runtime fetches events from as +they occur, subsequently calling the corresponding callbacks. This is done in a +long-running loop, hence the name "event loop". Here's the pseudocode for a +typical event loop: +```js +while(loop_is_running()) { + if(event_available_in_queue()) { + let event = fetch_event_from_queue(); + let callback = get_callback_associated_with(event); + if(callback) + callback(get_extra_data_for(event)); + } else { + // avoid wasting CPU time + sleep_until_any_event_becomes_available(); + } +} +``` + +Most JS runtimes enclose the event loop within themselves, so that most JS +programmers does not even need to be aware of its existence. This is not the +case with our JS subsystem. + +# Example +This is how one would write something similar to the `setTimeout` example above: +```js +// import module +let eventLoop = require("event_loop"); + +// create an event source that will fire once 1 second after it has been created +let timer = eventLoop.timer("oneshot", 1000); + +// subscribe a callback to the event source +eventLoop.subscribe(timer, function(_subscription, _item, eventLoop) { + print("Hello, World!"); + eventLoop.stop(); +}, eventLoop); // notice this extra argument. we'll come back to this later + +// run the loop until it is stopped +eventLoop.run(); + +// the previous line will only finish executing once `.stop()` is called, hence +// the following line will execute only after "Hello, World!" is printed +print("Stopped"); +``` + +I promised you that we'll come back to the extra argument after the callback +function. Our JavaScript engine does not support closures (anonymous functions +that access values outside of their arguments), so we ask `subscribe` to pass an +outside value (namely, `eventLoop`) as an argument to the callback so that we +can access it. We can modify this extra state: +```js +// this timer will fire every second +let timer = eventLoop.timer("periodic", 1000); +eventLoop.subscribe(timer, function(_subscription, _item, counter, eventLoop) { + print("Counter is at:", counter); + if(counter === 10) + eventLoop.stop(); + // modify the extra arguments that will be passed to us the next time + return [counter + 1, eventLoop]; +}, 0, eventLoop); +``` + +Because we have two extra arguments, if we return anything other than an array +of length 2, the arguments will be kept as-is for the next call. + +The first two arguments that get passed to our callback are: + - The subscription manager that lets us `.cancel()` our subscription + - The event item, used for events that have extra data. Timer events do not, + they just produce `undefined`. + +# API reference +## `run` +Runs the event loop until it is stopped with `stop`. + +## `subscribe` +Subscribes a function to an event. + +### Parameters + - `contract`: an event source identifier + - `callback`: the function to call when the event happens + - extra arguments: will be passed as extra arguments to the callback + +The callback will be called with at least two arguments, plus however many were +passed as extra arguments to `subscribe`. The first argument is the subscription +manager (the same one that `subscribe` itself returns). The second argument is +the event item for events that produce extra data; the ones that don't set this +to `undefined`. The callback may return an array of the same length as the count +of the extra arguments to modify them for the next time that the event handler +is called. Any other returns values are discarded. + +### Returns +A `SubscriptionManager` object: + - `SubscriptionManager.cancel()`: unsubscribes the callback from the event + +### Warning +Each event source may only have one callback associated with it. + +## `stop` +Stops the event loop. + +## `timer` +Produces an event source that fires with a constant interval either once or +indefinitely. + +### Parameters + - `mode`: either `"oneshot"` or `"periodic"` + - `interval`: the timeout (for `"oneshot"`) timers or the period (for + `"periodic"` timers) + +### Returns +A `Contract` object, as expected by `subscribe`'s first parameter. + +## `queue` +Produces a queue that can be used to exchange messages. + +### Parameters + - `length`: the maximum number of items that the queue may contain + +### Returns +A `Queue` object: + - `Queue.send(message)`: + - `message`: a value of any type that will be placed at the end of the queue + - `input`: a `Contract` (event source) that pops items from the front of the + queue diff --git a/documentation/js/js_gpio.md b/documentation/js/js_gpio.md new file mode 100644 index 00000000000..9791fb4ebcd --- /dev/null +++ b/documentation/js/js_gpio.md @@ -0,0 +1,77 @@ +# js_gpio {#js_gpio} + +# GPIO module +```js +let eventLoop = require("event_loop"); +let gpio = require("gpio"); +``` + +This module depends on the `event_loop` module, so it _must_ only be imported +after `event_loop` is imported. + +# Example +```js +let eventLoop = require("event_loop"); +let gpio = require("gpio"); + +let led = gpio.get("pc3"); +led.init({ direction: "out", outMode: "push_pull" }); + +led.write(true); +delay(1000); +led.write(false); +delay(1000); +``` + +# API reference +## `get` +Gets a `Pin` object that can be used to manage a pin. + +### Parameters + - `pin`: pin identifier (examples: `"pc3"`, `7`, `"pa6"`, `3`) + +### Returns +A `Pin` object + +## `Pin` object +### `Pin.init()` +Configures a pin + +#### Parameters + - `mode`: `Mode` object: + - `direction` (required): either `"in"` or `"out"` + - `outMode` (required for `direction: "out"`): either `"open_drain"` or + `"push_pull"` + - `inMode` (required for `direction: "in"`): either `"analog"`, + `"plain_digital"`, `"interrupt"` or `"event"` + - `edge` (required for `inMode: "interrupt"` or `"event"`): either + `"rising"`, `"falling"` or `"both"` + - `pull` (optional): either `"up"`, `"down"` or unset + +### `Pin.write()` +Writes a digital value to a pin configured with `direction: "out"` + +#### Parameters + - `value`: boolean logic level to write + +### `Pin.read()` +Reads a digital value from a pin configured with `direction: "in"` and any +`inMode` except `"analog"` + +#### Returns +Boolean logic level + +### `Pin.read_analog()` +Reads an analog voltage level in millivolts from a pin configured with +`direction: "in"` and `inMode: "analog"` + +#### Returns +Voltage on pin in millivolts + +### `Pin.interrupt()` +Attaches an interrupt to a pin configured with `direction: "in"` and +`inMode: "interrupt"` or `"event"` + +#### Returns +An event loop `Contract` object that identifies the interrupt event source. The +event does not produce any extra data. diff --git a/documentation/js/js_gui.md b/documentation/js/js_gui.md new file mode 100644 index 00000000000..4d2d2497a24 --- /dev/null +++ b/documentation/js/js_gui.md @@ -0,0 +1,161 @@ +# js_gui {#js_gui} + +# GUI module +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +``` + +This module depends on the `event_loop` module, so it _must_ only be imported +after `event_loop` is imported. + +## Conceptualizing GUI +### Event loop +It is highly recommended to familiarize yourself with the event loop first +before doing GUI-related things. + +### Canvas +The canvas is just a drawing area with no abstractions over it. Drawing on the +canvas directly (i.e. not through a viewport) is useful in case you want to +implement a custom design element, but this is rather uncommon. + +### Viewport +A viewport is a window into a rectangular portion of the canvas. Applications +always access the canvas through a viewport. + +### View +In Flipper's terminology, a "View" is a fullscreen design element that assumes +control over the entire viewport and all input events. Different types of views +are available (not all of which are unfortunately currently implemented in JS): +| View | Has JS adapter? | +|----------------------|------------------| +| `button_menu` | ❌ | +| `button_panel` | ❌ | +| `byte_input` | ❌ | +| `dialog_ex` | ✅ (as `dialog`) | +| `empty_screen` | ✅ | +| `file_browser` | ❌ | +| `loading` | ✅ | +| `menu` | ❌ | +| `number_input` | ❌ | +| `popup` | ❌ | +| `submenu` | ✅ | +| `text_box` | ✅ | +| `text_input` | ✅ | +| `variable_item_list` | ❌ | +| `widget` | ❌ | + +In JS, each view has its own set of properties (or just "props"). The programmer +can manipulate these properties in two ways: + - Instantiate a `View` using the `makeWith(props)` method, passing an object + with the initial properties + - Call `set(name, value)` to modify a property of an existing `View` + +### View Dispatcher +The view dispatcher holds references to all the views that an application needs +and switches between them as the application makes requests to do so. + +### Scene Manager +The scene manager is an optional add-on to the view dispatcher that makes +managing applications with complex navigation flows easier. It is currently +inaccessible from JS. + +### Approaches +In total, there are three different approaches that you may take when writing +a GUI application: +| Approach | Use cases | Available from JS | +|----------------|------------------------------------------------------------------------------|-------------------| +| ViewPort only | Accessing the graphics API directly, without any of the nice UI abstractions | ❌ | +| ViewDispatcher | Common UI elements that fit with the overall look of the system | ✅ | +| SceneManager | Additional navigation flow management for complex applications | ❌ | + +# Example +An example with three different views using the ViewDispatcher approach: +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let loadingView = require("gui/loading"); +let submenuView = require("gui/submenu"); +let emptyView = require("gui/empty_screen"); + +// Common pattern: declare all the views in an object. This is absolutely not +// required, but adds clarity to the script. +let views = { + // the view dispatcher auto-✨magically✨ remembers views as they are created + loading: loadingView.make(), + empty: emptyView.make(), + demos: submenuView.makeWith({ + items: [ + "Hourglass screen", + "Empty screen", + "Exit app", + ], + }), +}; + +// go to different screens depending on what was selected +eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) { + if (index === 0) { + gui.viewDispatcher.switchTo(views.loading); + } else if (index === 1) { + gui.viewDispatcher.switchTo(views.empty); + } else if (index === 2) { + eventLoop.stop(); + } +}, gui, eventLoop, views); + +// go to the demo chooser screen when the back key is pressed +eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views) { + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// run UI +gui.viewDispatcher.switchTo(views.demos); +eventLoop.run(); +``` + +# API reference +## `viewDispatcher` +The `viewDispatcher` constant holds the `ViewDispatcher` singleton. + +### `viewDispatcher.switchTo(view)` +Switches to a view, giving it control over the display and input + +#### Parameters + - `view`: the `View` to switch to + +### `viewDispatcher.sendTo(direction)` +Sends the viewport that the dispatcher manages to the front of the stackup +(effectively making it visible), or to the back (effectively making it +invisible) + +#### Parameters + - `direction`: either `"front"` or `"back"` + +### `viewDispatcher.sendCustom(event)` +Sends a custom number to the `custom` event handler + +#### Parameters + - `event`: number to send + +### `viewDispatcher.custom` +An event loop `Contract` object that identifies the custom event source, +triggered by `ViewDispatcher.sendCustom(event)` + +### `viewDispatcher.navigation` +An event loop `Contract` object that identifies the navigation event source, +triggered when the back key is pressed + +## `ViewFactory` +When you import a module implementing a view, a `ViewFactory` is instantiated. +For example, in the example above, `loadingView`, `submenuView` and `emptyView` +are view factories. + +### `ViewFactory.make()` +Creates an instance of a `View` + +### `ViewFactory.make(props)` +Creates an instance of a `View` and assigns initial properties from `props` + +#### Parameters + - `props`: simple key-value object, e.g. `{ header: "Header" }` diff --git a/documentation/js/js_gui__dialog.md b/documentation/js/js_gui__dialog.md new file mode 100644 index 00000000000..445e711282e --- /dev/null +++ b/documentation/js/js_gui__dialog.md @@ -0,0 +1,53 @@ +# js_gui__dialog {#js_gui__dialog} + +# Dialog GUI view +Displays a dialog with up to three options. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let dialogView = require("gui/dialog"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the `gui.js` example script. + +# View props +## `header` +Text that appears in bold at the top of the screen + +Type: `string` + +## `text` +Text that appears in the middle of the screen + +Type: `string` + +## `left` +Text for the left button. If unset, the left button does not show up. + +Type: `string` + +## `center` +Text for the center button. If unset, the center button does not show up. + +Type: `string` + +## `right` +Text for the right button. If unset, the right button does not show up. + +Type: `string` + +# View events +## `input` +Fires when the user presses on either of the three possible buttons. The item +contains one of the strings `"left"`, `"center"` or `"right"` depending on the +button. + +Item type: `string` diff --git a/documentation/js/js_gui__empty_screen.md b/documentation/js/js_gui__empty_screen.md new file mode 100644 index 00000000000..f9fd12553ab --- /dev/null +++ b/documentation/js/js_gui__empty_screen.md @@ -0,0 +1,22 @@ +# js_gui__empty_screen {#js_gui__empty_screen} + +# Empty Screen GUI View +Displays nothing. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let emptyView = require("gui/empty_screen"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the GUI example. + +# View props +This view does not have any props. diff --git a/documentation/js/js_gui__loading.md b/documentation/js/js_gui__loading.md new file mode 100644 index 00000000000..52f1cea49e2 --- /dev/null +++ b/documentation/js/js_gui__loading.md @@ -0,0 +1,23 @@ +# js_gui__loading {#js_gui__loading} + +# Loading GUI View +Displays an animated hourglass icon. Suppresses all `navigation` events, making +it impossible for the user to exit the view by pressing the back key. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let loadingView = require("gui/loading"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the GUI example. + +# View props +This view does not have any props. diff --git a/documentation/js/js_gui__submenu.md b/documentation/js/js_gui__submenu.md new file mode 100644 index 00000000000..28c1e65af2d --- /dev/null +++ b/documentation/js/js_gui__submenu.md @@ -0,0 +1,37 @@ +# js_gui__submenu {#js_gui__submenu} + +# Submenu GUI view +Displays a scrollable list of clickable textual entries. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let submenuView = require("gui/submenu"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the GUI example. + +# View props +## `header` +Single line of text that appears above the list + +Type: `string` + +## `items` +The list of options + +Type: `string[]` + +# View events +## `chosen` +Fires when an entry has been chosen by the user. The item contains the index of +the entry. + +Item type: `number` diff --git a/documentation/js/js_gui__text_box.md b/documentation/js/js_gui__text_box.md new file mode 100644 index 00000000000..bdad8d8b360 --- /dev/null +++ b/documentation/js/js_gui__text_box.md @@ -0,0 +1,25 @@ +# js_gui__text_box {#js_gui__text_box} + +# Text box GUI view +Displays a scrollable read-only text field. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let textBoxView = require("gui/text_box"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the `gui.js` example script. + +# View props +## `text` +Text to show in the text box. + +Type: `string` diff --git a/documentation/js/js_gui__text_input.md b/documentation/js/js_gui__text_input.md new file mode 100644 index 00000000000..030579e2e29 --- /dev/null +++ b/documentation/js/js_gui__text_input.md @@ -0,0 +1,44 @@ +# js_gui__text_input {#js_gui__text_input} + +# Text input GUI view +Displays a keyboard. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let textInputView = require("gui/text_input"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they _must_ be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +# Example +For an example refer to the `gui.js` example script. + +# View props +## `minLength` +Smallest allowed text length + +Type: `number` + +## `maxLength` +Biggest allowed text length + +Type: `number` + +Default: `32` + +## `header` +Single line of text that appears above the keyboard + +Type: `string` + +# View events +## `input` +Fires when the user selects the "save" button and the text matches the length +constrained by `minLength` and `maxLength`. + +Item type: `string` diff --git a/documentation/js/js_submenu.md b/documentation/js/js_submenu.md deleted file mode 100644 index 580a43bd5cc..00000000000 --- a/documentation/js/js_submenu.md +++ /dev/null @@ -1,48 +0,0 @@ -# js_submenu {#js_submenu} - -# Submenu module -```js -let submenu = require("submenu"); -``` -# Methods - -## setHeader -Set the submenu header text. - -### Parameters -- header (string): The submenu header text - -### Example -```js -submenu.setHeader("Select an option:"); -``` - -## addItem -Add a new submenu item. - -### Parameters -- label (string): The submenu item label text -- id (number): The submenu item ID, must be a Uint32 number - -### Example -```js -submenu.addItem("Option 1", 1); -submenu.addItem("Option 2", 2); -submenu.addItem("Option 3", 3); -``` - -## show -Show a submenu that was previously configured using `setHeader()` and `addItem()` methods. - -### Returns -The ID of the submenu item that was selected, or `undefined` if the BACK button was pressed. - -### Example -```js -let selected = submenu.show(); -if (selected === undefined) { - // if BACK button was pressed -} else if (selected === 1) { - // if item with ID 1 was selected -} -``` diff --git a/documentation/js/js_textbox.md b/documentation/js/js_textbox.md deleted file mode 100644 index 61652df1a6e..00000000000 --- a/documentation/js/js_textbox.md +++ /dev/null @@ -1,69 +0,0 @@ -# js_textbox {#js_textbox} - -# Textbox module -```js -let textbox = require("textbox"); -``` -# Methods - -## setConfig -Set focus and font for the textbox. - -### Parameters -- focus: "start" to focus on the beginning of the text, or "end" to focus on the end of the text -- font: "text" to use the default proportional font, or "hex" to use a monospaced font, which is convenient for aligned array output in HEX - -### Example -```js -textbox.setConfig("start", "text"); -textbox.addText("Hello world"); -textbox.show(); -``` - -## addText -Add text to the end of the textbox. - -### Parameters -- text (string): The text to add to the end of the textbox - -### Example -```js -textbox.addText("New text 1\nNew text 2"); -``` - -## clearText -Clear the textbox. - -### Example -```js -textbox.clearText(); -``` - -## isOpen -Return true if the textbox is open. - -### Returns -True if the textbox is open, false otherwise. - -### Example -```js -let isOpen = textbox.isOpen(); -``` - -## show -Show the textbox. You can add text to it using the `addText()` method before or after calling the `show()` method. - -### Example -```js -textbox.show(); -``` - -## close -Close the textbox. - -### Example -```js -if (textbox.isOpen()) { - textbox.close(); -} -``` diff --git a/fbt_options.py b/fbt_options.py index e30f7fc2d9b..a7705335ad3 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -75,6 +75,7 @@ "updater_app", "radio_device_cc1101_ext", "unit_tests", + "js_app", ], } diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index f4f008a71b6..b622aa7a1cf 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -418,6 +418,18 @@ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* o FURI_CRITICAL_EXIT(); } +bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* object) { + furi_check(instance); + furi_check(instance->thread_id == furi_thread_get_current_id()); + FURI_CRITICAL_ENTER(); + + FuriEventLoopItem* const* item = FuriEventLoopTree_cget(instance->tree, object); + bool result = !!item; + + FURI_CRITICAL_EXIT(); + return result; +} + /* * Private Event Loop Item functions */ diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index af5987101d4..6c5ba432c73 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -289,6 +289,23 @@ void furi_event_loop_subscribe_mutex( */ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object); +/** + * @brief Checks if the loop is subscribed to an object of any kind + * + * @param instance Event Loop instance + * @param object Object to check + */ +bool furi_event_loop_is_subscribed(FuriEventLoop* instance, FuriEventLoopObject* object); + +/** + * @brief Convenience function for `if(is_subscribed()) unsubscribe()` + */ +static inline void + furi_event_loop_maybe_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* object) { + if(furi_event_loop_is_subscribed(instance, object)) + furi_event_loop_unsubscribe(instance, object); +} + #ifdef __cplusplus } #endif diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index bcdcb364abb..f3e28a5baed 100644 --- a/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -103,6 +103,7 @@ struct mjs* mjs_create(void* context) { sizeof(struct mjs_object), MJS_OBJECT_ARENA_SIZE, MJS_OBJECT_ARENA_INC_SIZE); + mjs->object_arena.destructor = mjs_obj_destructor; gc_arena_init( &mjs->property_arena, sizeof(struct mjs_property), diff --git a/lib/mjs/mjs_object.c b/lib/mjs/mjs_object.c index 2aea1bd46ad..60bacf51458 100644 --- a/lib/mjs/mjs_object.c +++ b/lib/mjs/mjs_object.c @@ -9,6 +9,7 @@ #include "mjs_primitive.h" #include "mjs_string.h" #include "mjs_util.h" +#include "furi.h" #include "common/mg_str.h" @@ -20,6 +21,19 @@ MJS_PRIVATE mjs_val_t mjs_object_to_value(struct mjs_object* o) { } } +MJS_PRIVATE void mjs_obj_destructor(struct mjs* mjs, void* cell) { + struct mjs_object* obj = cell; + mjs_val_t obj_val = mjs_object_to_value(obj); + + struct mjs_property* destructor = mjs_get_own_property( + mjs, obj_val, MJS_DESTRUCTOR_PROP_NAME, strlen(MJS_DESTRUCTOR_PROP_NAME)); + if(!destructor) return; + if(!mjs_is_foreign(destructor->value)) return; + + mjs_custom_obj_destructor_t destructor_fn = mjs_get_ptr(mjs, destructor->value); + if(destructor_fn) destructor_fn(mjs, obj_val); +} + MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v) { struct mjs_object* ret = NULL; if(mjs_is_null(v)) { @@ -293,7 +307,8 @@ mjs_val_t * start from the end so the constructed object more closely resembles * the definition. */ - while(def->name != NULL) def++; + while(def->name != NULL) + def++; for(def--; def >= defs; def--) { mjs_val_t v = MJS_UNDEFINED; const char* ptr = (const char*)base + def->offset; diff --git a/lib/mjs/mjs_object.h b/lib/mjs/mjs_object.h index 1c4810385a7..870486d06f9 100644 --- a/lib/mjs/mjs_object.h +++ b/lib/mjs/mjs_object.h @@ -50,6 +50,11 @@ MJS_PRIVATE mjs_err_t mjs_set_internal( */ MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs); +/* + * Cell destructor for object arena + */ +MJS_PRIVATE void mjs_obj_destructor(struct mjs* mjs, void* cell); + #define MJS_PROTO_PROP_NAME "__p" /* Make it < 5 chars */ #if defined(__cplusplus) diff --git a/lib/mjs/mjs_object_public.h b/lib/mjs/mjs_object_public.h index f9f06c61647..1a021a9d85f 100644 --- a/lib/mjs/mjs_object_public.h +++ b/lib/mjs/mjs_object_public.h @@ -119,6 +119,14 @@ int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len); */ mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator); +typedef void (*mjs_custom_obj_destructor_t)(struct mjs* mjs, mjs_val_t object); + +/* + * Destructor property name. If set, must be a foreign pointer to a function + * that will be called just before the object is freed. + */ +#define MJS_DESTRUCTOR_PROP_NAME "__d" + #if defined(__cplusplus) } #endif /* __cplusplus */ diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 9b2160d53df..7943c4cfcb7 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.1,, +Version,+,77.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1116,6 +1116,7 @@ Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_event_loop_alloc,FuriEventLoop*, Function,+,furi_event_loop_free,void,FuriEventLoop* +Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObject*" Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*" Function,+,furi_event_loop_run,void,FuriEventLoop* Function,+,furi_event_loop_stop,void,FuriEventLoop* @@ -1372,6 +1373,8 @@ Function,-,furi_hal_resources_deinit_early,void, Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, +Function,+,furi_hal_resources_pin_by_name,const GpioPinRecord*,const char* +Function,+,furi_hal_resources_pin_by_number,const GpioPinRecord*,uint8_t Function,-,furi_hal_rtc_deinit_early,void, Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,DateTime* @@ -2687,6 +2690,7 @@ Function,+,text_input_get_validator_callback_context,void*,TextInput* Function,+,text_input_get_view,View*,TextInput* Function,+,text_input_reset,void,TextInput* Function,+,text_input_set_header_text,void,"TextInput*, const char*" +Function,+,text_input_set_minimum_length,void,"TextInput*, size_t" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" Function,-,tgamma,double,double @@ -2761,6 +2765,7 @@ Function,+,view_allocate_model,void,"View*, ViewModelType, size_t" Function,+,view_commit_model,void,"View*, _Bool" Function,+,view_dispatcher_add_view,void,"ViewDispatcher*, uint32_t, View*" Function,+,view_dispatcher_alloc,ViewDispatcher*, +Function,+,view_dispatcher_alloc_ex,ViewDispatcher*,FuriEventLoop* Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* Function,+,view_dispatcher_free,void,ViewDispatcher* diff --git a/targets/f18/furi_hal/furi_hal_resources.c b/targets/f18/furi_hal/furi_hal_resources.c index 45ca3e6c494..2e3654435c1 100644 --- a/targets/f18/furi_hal/furi_hal_resources.c +++ b/targets/f18/furi_hal/furi_hal_resources.c @@ -354,3 +354,19 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { } return -1; } + +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(strcasecmp(name, record->name) == 0) return record; + } + return NULL; +} + +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(record->number == number) return record; + } + return NULL; +} diff --git a/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h index 8f6173eb9dc..9a0d04cb666 100644 --- a/targets/f18/furi_hal/furi_hal_resources.h +++ b/targets/f18/furi_hal/furi_hal_resources.h @@ -121,6 +121,26 @@ void furi_hal_resources_init(void); */ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); +/** + * @brief Finds a pin by its name + * + * @param name case-insensitive pin name to look for (e.g. `"Pc3"`, `"pA4"`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name); + +/** + * @brief Finds a pin by its number + * + * @param name pin number to look for (e.g. `7`, `4`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number); + #ifdef __cplusplus } #endif diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index b5dab0f281e..c121fc71681 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,77.1,, +Version,+,77.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1221,6 +1221,7 @@ Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_event_loop_alloc,FuriEventLoop*, Function,+,furi_event_loop_free,void,FuriEventLoop* +Function,+,furi_event_loop_is_subscribed,_Bool,"FuriEventLoop*, FuriEventLoopObject*" Function,+,furi_event_loop_pend_callback,void,"FuriEventLoop*, FuriEventLoopPendingCallback, void*" Function,+,furi_event_loop_run,void,FuriEventLoop* Function,+,furi_event_loop_stop,void,FuriEventLoop* @@ -1536,6 +1537,8 @@ Function,-,furi_hal_resources_deinit_early,void, Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, +Function,+,furi_hal_resources_pin_by_name,const GpioPinRecord*,const char* +Function,+,furi_hal_resources_pin_by_number,const GpioPinRecord*,uint8_t Function,+,furi_hal_rfid_comp_set_callback,void,"FuriHalRfidCompCallback, void*" Function,+,furi_hal_rfid_comp_start,void, Function,+,furi_hal_rfid_comp_stop,void, @@ -3531,6 +3534,7 @@ Function,+,text_input_get_validator_callback_context,void*,TextInput* Function,+,text_input_get_view,View*,TextInput* Function,+,text_input_reset,void,TextInput* Function,+,text_input_set_header_text,void,"TextInput*, const char*" +Function,+,text_input_set_minimum_length,void,"TextInput*, size_t" Function,+,text_input_set_result_callback,void,"TextInput*, TextInputCallback, void*, char*, size_t, _Bool" Function,+,text_input_set_validator,void,"TextInput*, TextInputValidatorCallback, void*" Function,-,tgamma,double,double @@ -3605,6 +3609,7 @@ Function,+,view_allocate_model,void,"View*, ViewModelType, size_t" Function,+,view_commit_model,void,"View*, _Bool" Function,+,view_dispatcher_add_view,void,"ViewDispatcher*, uint32_t, View*" Function,+,view_dispatcher_alloc,ViewDispatcher*, +Function,+,view_dispatcher_alloc_ex,ViewDispatcher*,FuriEventLoop* Function,+,view_dispatcher_attach_to_gui,void,"ViewDispatcher*, Gui*, ViewDispatcherType" Function,+,view_dispatcher_enable_queue,void,ViewDispatcher* Function,+,view_dispatcher_free,void,ViewDispatcher* diff --git a/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c index 486c24230e1..123ebc42088 100644 --- a/targets/f7/furi_hal/furi_hal_resources.c +++ b/targets/f7/furi_hal/furi_hal_resources.c @@ -288,3 +288,19 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) { } return -1; } + +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(strcasecmp(name, record->name) == 0) return record; + } + return NULL; +} + +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number) { + for(size_t i = 0; i < gpio_pins_count; i++) { + const GpioPinRecord* record = &gpio_pins[i]; + if(record->number == number) return record; + } + return NULL; +} diff --git a/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h index c01b2207fff..ec8794cc1e5 100644 --- a/targets/f7/furi_hal/furi_hal_resources.h +++ b/targets/f7/furi_hal/furi_hal_resources.h @@ -227,6 +227,26 @@ void furi_hal_resources_init(void); */ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); +/** + * @brief Finds a pin by its name + * + * @param name case-insensitive pin name to look for (e.g. `"Pc3"`, `"pA4"`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_name(const char* name); + +/** + * @brief Finds a pin by its number + * + * @param name pin number to look for (e.g. `7`, `4`) + * + * @return a pointer to the corresponding `GpioPinRecord` if such a pin exists, + * `NULL` otherwise. + */ +const GpioPinRecord* furi_hal_resources_pin_by_number(uint8_t number); + #ifdef __cplusplus } #endif diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000000..2655a8b97b7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "checkJs": true, + "module": "CommonJS", + "typeRoots": [ + "./applications/system/js_app/types" + ], + "noLib": true, + }, + "include": [ + "./applications/system/js_app/examples/apps/Scripts", + "./applications/debug/unit_tests/resources/unit_tests/js", + "./applications/system/js_app/types/global.d.ts", + ] +} \ No newline at end of file From fbc3b494b7c750c70c3f7871e9b041c7acd1dfd9 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Tue, 15 Oct 2024 04:05:24 +0800 Subject: [PATCH 27/36] NFC: H World Hotel Chain Room Key Parser (#3946) * quick and easy implementation * delete debug artifacts * log level correction (warning -> info) --- applications/main/nfc/application.fam | 9 + .../main/nfc/plugins/supported_cards/hworld.c | 243 ++++++++++++++++++ 2 files changed, 252 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/hworld.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 180be622430..84151dd48bc 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -200,6 +200,15 @@ App( sources=["plugins/supported_cards/skylanders.c"], ) +App( + appid="hworld_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="hworld_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/hworld.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/hworld.c b/applications/main/nfc/plugins/supported_cards/hworld.c new file mode 100644 index 00000000000..674e7b9550b --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/hworld.c @@ -0,0 +1,243 @@ +// Flipper Zero parser for H World Hotel Key Cards +// H World operates around 10,000 hotels, most of which in mainland China +// Reverse engineering and parser written by @Torron (Github: @zinongli) +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include +#include + +#define TAG "H World" +#define ROOM_SECTOR 1 +#define VIP_SECTOR 5 +#define ROOM_SECTOR_KEY_BLOCK 7 +#define VIP_SECTOR_KEY_BLOCK 23 +#define ACCESS_INFO_BLOCK 5 +#define ROOM_NUM_DECIMAL_BLOCK 6 +#define H_WORLD_YEAR_OFFSET 2000 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static MfClassicKeyPair hworld_standard_keys[] = { + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 000 + {.a = 0x543071543071, .b = 0x5F01015F0101}, // 001 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 002 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 003 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 004 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 005 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 006 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 007 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 008 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 009 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 010 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 011 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 012 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 013 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 014 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 015 +}; + +static MfClassicKeyPair hworld_vip_keys[] = { + {.a = 0x000000000000, .b = 0xFFFFFFFFFFFF}, // 000 + {.a = 0x543071543071, .b = 0x5F01015F0101}, // 001 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 002 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 003 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 004 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 005 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 006 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 007 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 008 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 009 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 010 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 011 + {.a = 0xFFFFFFFFFFFF, .b = 0x200510241234}, // 012 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 013 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 014 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, // 015 +}; + +static bool hworld_verify(Nfc* nfc) { + bool verified = false; + + do { + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(ROOM_SECTOR); + + MfClassicKey standard_key = {0}; + bit_lib_num_to_bytes_be( + hworld_standard_keys[ROOM_SECTOR].a, COUNT_OF(standard_key.data), standard_key.data); + + MfClassicAuthContext auth_context; + MfClassicError standard_error = mf_classic_poller_sync_auth( + nfc, block_num, &standard_key, MfClassicKeyTypeA, &auth_context); + + if(standard_error != MfClassicErrorNone) { + FURI_LOG_D(TAG, "Failed static key check for block %u", block_num); + break; + } + + MfClassicKey vip_key = {0}; + bit_lib_num_to_bytes_be( + hworld_vip_keys[VIP_SECTOR].b, COUNT_OF(vip_key.data), vip_key.data); + + MfClassicError vip_error = mf_classic_poller_sync_auth( + nfc, block_num, &vip_key, MfClassicKeyTypeB, &auth_context); + + if(vip_error == MfClassicErrorNone) { + FURI_LOG_D(TAG, "VIP card detected"); + } else { + FURI_LOG_D(TAG, "Standard card detected"); + } + + verified = true; + } while(false); + + return verified; +} + +static bool hworld_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError standard_error = mf_classic_poller_sync_detect_type(nfc, &type); + MfClassicError vip_error = MfClassicErrorNotPresent; + if(standard_error != MfClassicErrorNone) break; + data->type = type; + + MfClassicDeviceKeys standard_keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be( + hworld_standard_keys[i].a, sizeof(MfClassicKey), standard_keys.key_a[i].data); + FURI_BIT_SET(standard_keys.key_a_mask, i); + bit_lib_num_to_bytes_be( + hworld_standard_keys[i].b, sizeof(MfClassicKey), standard_keys.key_b[i].data); + FURI_BIT_SET(standard_keys.key_b_mask, i); + } + + standard_error = mf_classic_poller_sync_read(nfc, &standard_keys, data); + if(standard_error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "Standard card successfully read"); + } else { + MfClassicDeviceKeys vip_keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be( + hworld_vip_keys[i].a, sizeof(MfClassicKey), vip_keys.key_a[i].data); + FURI_BIT_SET(vip_keys.key_a_mask, i); + bit_lib_num_to_bytes_be( + hworld_vip_keys[i].b, sizeof(MfClassicKey), vip_keys.key_b[i].data); + FURI_BIT_SET(vip_keys.key_b_mask, i); + } + + vip_error = mf_classic_poller_sync_read(nfc, &vip_keys, data); + + if(vip_error == MfClassicErrorNone) { + FURI_LOG_I(TAG, "VIP card successfully read"); + } else { + break; + } + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = (standard_error == MfClassicErrorNone) | (vip_error == MfClassicErrorNone); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +bool hworld_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + bool parsed = false; + + do { + // Check card type + if(data->type != MfClassicType1k) break; + + // Check static key for verificaiton + const uint8_t* data_room_sec_key_a_ptr = &data->block[ROOM_SECTOR_KEY_BLOCK].data[0]; + const uint8_t* data_room_sec_key_b_ptr = &data->block[ROOM_SECTOR_KEY_BLOCK].data[10]; + uint64_t data_room_sec_key_a = bit_lib_get_bits_64(data_room_sec_key_a_ptr, 0, 48); + uint64_t data_room_sec_key_b = bit_lib_get_bits_64(data_room_sec_key_b_ptr, 0, 48); + if((data_room_sec_key_a != hworld_standard_keys[ROOM_SECTOR].a) | + (data_room_sec_key_b != hworld_standard_keys[ROOM_SECTOR].b)) + break; + + // Check whether this card is VIP + const uint8_t* data_vip_sec_key_b_ptr = &data->block[VIP_SECTOR_KEY_BLOCK].data[10]; + uint64_t data_vip_sec_key_b = bit_lib_get_bits_64(data_vip_sec_key_b_ptr, 0, 48); + bool is_hworld_vip = (data_vip_sec_key_b == hworld_vip_keys[VIP_SECTOR].b); + uint8_t room_floor = data->block[ACCESS_INFO_BLOCK].data[13]; + uint8_t room_num = data->block[ACCESS_INFO_BLOCK].data[14]; + + // Check in date & time + uint16_t check_in_year = data->block[ACCESS_INFO_BLOCK].data[2] + H_WORLD_YEAR_OFFSET; + uint8_t check_in_month = data->block[ACCESS_INFO_BLOCK].data[3]; + uint8_t check_in_day = data->block[ACCESS_INFO_BLOCK].data[4]; + uint8_t check_in_hour = data->block[ACCESS_INFO_BLOCK].data[5]; + uint8_t check_in_minute = data->block[ACCESS_INFO_BLOCK].data[6]; + + // Expire date & time + uint16_t expire_year = data->block[ACCESS_INFO_BLOCK].data[7] + H_WORLD_YEAR_OFFSET; + uint8_t expire_month = data->block[ACCESS_INFO_BLOCK].data[8]; + uint8_t expire_day = data->block[ACCESS_INFO_BLOCK].data[9]; + uint8_t expire_hour = data->block[ACCESS_INFO_BLOCK].data[10]; + uint8_t expire_minute = data->block[ACCESS_INFO_BLOCK].data[11]; + + furi_string_cat_printf(parsed_data, "\e#H World Card\n"); + furi_string_cat_printf( + parsed_data, "%s\n", is_hworld_vip ? "VIP card" : "Standard room key"); + furi_string_cat_printf(parsed_data, "Room Num: %u%02u\n", room_floor, room_num); + furi_string_cat_printf( + parsed_data, + "Check-in Date: \n%04u-%02d-%02d\n%02d:%02d:00\n", + check_in_year, + check_in_month, + check_in_day, + check_in_hour, + check_in_minute); + furi_string_cat_printf( + parsed_data, + "Expiration Date: \n%04u-%02d-%02d\n%02d:%02d:00", + expire_year, + expire_month, + expire_day, + expire_hour, + expire_minute); + parsed = true; + } while(false); + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin hworld_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = hworld_verify, + .read = hworld_read, + .parse = hworld_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor hworld_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &hworld_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* hworld_plugin_ep(void) { + return &hworld_plugin_descriptor; +} From a6cf08523ca6bad2c96bdad275c8574aa6526769 Mon Sep 17 00:00:00 2001 From: porta Date: Tue, 15 Oct 2024 20:03:15 +0300 Subject: [PATCH 28/36] Small JS fixes (#3950) --- applications/system/js_app/examples/apps/Scripts/uart_echo.js | 4 ++-- applications/system/js_app/types/badusb/index.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/uart_echo.js index 2a0159b4690..06d6119fdcb 100644 --- a/applications/system/js_app/examples/apps/Scripts/uart_echo.js +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo.js @@ -2,10 +2,10 @@ let serial = require("serial"); serial.setup("usart", 230400); while (1) { - let rx_data = serial.readBytes(1, 0); + let rx_data = serial.readBytes(1, 1000); if (rx_data !== undefined) { serial.write(rx_data); let data_view = Uint8Array(rx_data); print("0x" + toString(data_view[0], 16)); } -} \ No newline at end of file +} diff --git a/applications/system/js_app/types/badusb/index.d.ts b/applications/system/js_app/types/badusb/index.d.ts index 2107909673c..647382dc0b3 100644 --- a/applications/system/js_app/types/badusb/index.d.ts +++ b/applications/system/js_app/types/badusb/index.d.ts @@ -78,4 +78,4 @@ export declare function print(string: string, delay?: number): void; * @param string The string to print * @param delay How many milliseconds to wait between key presses */ -export declare function println(): void; +export declare function println(string: string, delay?: number): void; From a34e09094b571de9953b2e380f1ccfec15d94007 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 16 Oct 2024 02:11:41 +0900 Subject: [PATCH 29/36] [FL-3914] BackUSB (#3951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "[FL-3896] Split BadUSB into BadUSB and BadBLE (#3931)" This reverts commit 0eaad8bf64f01a6f932647a9cda5475dd9ea1524. * Better USB-BLE switch UX * Format sources * Format images Co-authored-by: あく --- applications/main/bad_usb/application.fam | 2 +- applications/main/bad_usb/bad_usb_app.c | 25 +- applications/main/bad_usb/bad_usb_app_i.h | 8 +- .../main/bad_usb/helpers/bad_usb_hid.c | 156 +++- .../main/bad_usb/helpers/bad_usb_hid.h | 7 +- .../main/bad_usb/helpers/ducky_script.c | 4 +- .../main/bad_usb/helpers/ducky_script.h | 2 +- .../bad_usb/scenes/bad_usb_scene_config.c | 59 ++ .../bad_usb/scenes/bad_usb_scene_config.h | 3 + .../scenes/bad_usb_scene_confirm_unpair.c} | 32 +- .../main/bad_usb/scenes/bad_usb_scene_error.c | 2 +- .../scenes/bad_usb_scene_unpair_done.c | 39 + .../main/bad_usb/scenes/bad_usb_scene_work.c | 21 +- .../main/bad_usb/views/bad_usb_view.c | 19 +- .../main/bad_usb/views/bad_usb_view.h | 2 + applications/system/bad_ble/application.fam | 12 - applications/system/bad_ble/bad_ble_app.c | 196 ----- applications/system/bad_ble/bad_ble_app.h | 11 - applications/system/bad_ble/bad_ble_app_i.h | 53 -- .../system/bad_ble/helpers/bad_ble_hid.c | 157 ---- .../system/bad_ble/helpers/bad_ble_hid.h | 34 - .../system/bad_ble/helpers/ducky_script.c | 716 ------------------ .../system/bad_ble/helpers/ducky_script.h | 55 -- .../bad_ble/helpers/ducky_script_commands.c | 241 ------ .../system/bad_ble/helpers/ducky_script_i.h | 76 -- .../bad_ble/helpers/ducky_script_keycodes.c | 133 ---- applications/system/bad_ble/icon.png | Bin 96 -> 0 bytes .../system/bad_ble/scenes/bad_ble_scene.c | 30 - .../system/bad_ble/scenes/bad_ble_scene.h | 29 - .../bad_ble/scenes/bad_ble_scene_config.c | 59 -- .../bad_ble/scenes/bad_ble_scene_config.h | 7 - .../scenes/bad_ble_scene_config_layout.c | 49 -- .../bad_ble/scenes/bad_ble_scene_error.c | 65 -- .../scenes/bad_ble_scene_file_select.c | 46 -- .../scenes/bad_ble_scene_unpair_done.c | 37 - .../bad_ble/scenes/bad_ble_scene_work.c | 65 -- .../system/bad_ble/views/bad_ble_view.c | 284 ------- .../system/bad_ble/views/bad_ble_view.h | 26 - .../icons/BadUsb}/Bad_BLE_48x22.png | Bin furi/core/timer.c | 2 - 40 files changed, 349 insertions(+), 2415 deletions(-) create mode 100644 applications/main/bad_usb/scenes/bad_usb_scene_config.c rename applications/{system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c => main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c} (51%) create mode 100644 applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c delete mode 100644 applications/system/bad_ble/application.fam delete mode 100644 applications/system/bad_ble/bad_ble_app.c delete mode 100644 applications/system/bad_ble/bad_ble_app.h delete mode 100644 applications/system/bad_ble/bad_ble_app_i.h delete mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.c delete mode 100644 applications/system/bad_ble/helpers/bad_ble_hid.h delete mode 100644 applications/system/bad_ble/helpers/ducky_script.c delete mode 100644 applications/system/bad_ble/helpers/ducky_script.h delete mode 100644 applications/system/bad_ble/helpers/ducky_script_commands.c delete mode 100644 applications/system/bad_ble/helpers/ducky_script_i.h delete mode 100644 applications/system/bad_ble/helpers/ducky_script_keycodes.c delete mode 100644 applications/system/bad_ble/icon.png delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene.h delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config.h delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_error.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_file_select.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c delete mode 100644 applications/system/bad_ble/scenes/bad_ble_scene_work.c delete mode 100644 applications/system/bad_ble/views/bad_ble_view.c delete mode 100644 applications/system/bad_ble/views/bad_ble_view.h rename {applications/system/bad_ble/assets => assets/icons/BadUsb}/Bad_BLE_48x22.png (100%) diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 9844e248df9..8d3909fccc6 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -7,7 +7,7 @@ App( icon="A_BadUsb_14", order=70, resources="resources", - fap_libs=["assets"], + fap_libs=["assets", "ble_profile"], fap_icon="icon.png", fap_category="USB", ) diff --git a/applications/main/bad_usb/bad_usb_app.c b/applications/main/bad_usb/bad_usb_app.c index 1ee92bdf3f1..eda702cf4d6 100644 --- a/applications/main/bad_usb/bad_usb_app.c +++ b/applications/main/bad_usb/bad_usb_app.c @@ -35,6 +35,7 @@ static void bad_usb_load_settings(BadUsbApp* app) { FuriString* temp_str = furi_string_alloc(); uint32_t version = 0; + uint32_t interface = 0; if(flipper_format_file_open_existing(fff, BAD_USB_SETTINGS_PATH)) { do { @@ -44,6 +45,8 @@ static void bad_usb_load_settings(BadUsbApp* app) { break; if(!flipper_format_read_string(fff, "layout", temp_str)) break; + if(!flipper_format_read_uint32(fff, "interface", &interface, 1)) break; + if(interface > BadUsbHidInterfaceBle) break; state = true; } while(0); @@ -53,6 +56,7 @@ static void bad_usb_load_settings(BadUsbApp* app) { if(state) { furi_string_set(app->keyboard_layout, temp_str); + app->interface = interface; Storage* fs_api = furi_record_open(RECORD_STORAGE); FileInfo layout_file_info; @@ -64,6 +68,7 @@ static void bad_usb_load_settings(BadUsbApp* app) { } } else { furi_string_set(app->keyboard_layout, BAD_USB_SETTINGS_DEFAULT_LAYOUT); + app->interface = BadUsbHidInterfaceUsb; } furi_string_free(temp_str); @@ -79,6 +84,9 @@ static void bad_usb_save_settings(BadUsbApp* app) { fff, BAD_USB_SETTINGS_FILE_TYPE, BAD_USB_SETTINGS_VERSION)) break; if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; + uint32_t interface_id = app->interface; + if(!flipper_format_write_uint32(fff, "interface", (const uint32_t*)&interface_id, 1)) + break; } while(0); } @@ -86,6 +94,11 @@ static void bad_usb_save_settings(BadUsbApp* app) { furi_record_close(RECORD_STORAGE); } +void bad_usb_set_interface(BadUsbApp* app, BadUsbHidInterface interface) { + app->interface = interface; + bad_usb_view_set_interface(app->bad_usb_view, interface); +} + BadUsbApp* bad_usb_app_alloc(char* arg) { BadUsbApp* app = malloc(sizeof(BadUsbApp)); @@ -117,7 +130,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { // Custom Widget app->widget = widget_alloc(); view_dispatcher_add_view( - app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget)); + app->view_dispatcher, BadUsbAppViewWidget, widget_get_view(app->widget)); + + // Popup + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, BadUsbAppViewPopup, popup_get_view(app->popup)); app->var_item_list = variable_item_list_alloc(); view_dispatcher_add_view( @@ -163,9 +180,13 @@ void bad_usb_app_free(BadUsbApp* app) { bad_usb_view_free(app->bad_usb_view); // Custom Widget - view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError); + view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWidget); widget_free(app->widget); + // Popup + view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewPopup); + popup_free(app->popup); + // Config menu view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig); variable_item_list_free(app->var_item_list); diff --git a/applications/main/bad_usb/bad_usb_app_i.h b/applications/main/bad_usb/bad_usb_app_i.h index e63d0044c00..b34bd5de699 100644 --- a/applications/main/bad_usb/bad_usb_app_i.h +++ b/applications/main/bad_usb/bad_usb_app_i.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "views/bad_usb_view.h" #include @@ -33,6 +34,7 @@ struct BadUsbApp { NotificationApp* notifications; DialogsApp* dialogs; Widget* widget; + Popup* popup; VariableItemList* var_item_list; BadUsbAppError error; @@ -41,11 +43,15 @@ struct BadUsbApp { BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; + BadUsbHidInterface interface; FuriHalUsbInterface* usb_if_prev; }; typedef enum { - BadUsbAppViewError, + BadUsbAppViewWidget, + BadUsbAppViewPopup, BadUsbAppViewWork, BadUsbAppViewConfig, } BadUsbAppView; + +void bad_usb_set_interface(BadUsbApp* app, BadUsbHidInterface interface); diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.c b/applications/main/bad_usb/helpers/bad_usb_hid.c index dcba7b5e932..5d7076314af 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.c +++ b/applications/main/bad_usb/helpers/bad_usb_hid.c @@ -1,9 +1,12 @@ #include "bad_usb_hid.h" #include +#include #include #define TAG "BadUSB HID" +#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" + void* hid_usb_init(FuriHalUsbHidConfig* hid_cfg) { furi_check(furi_hal_usb_set_config(&usb_hid, hid_cfg)); return NULL; @@ -69,6 +72,155 @@ static const BadUsbHidApi hid_api_usb = { .release_all = hid_usb_release_all, .get_led_state = hid_usb_get_led_state, }; -const BadUsbHidApi* bad_usb_hid_get_interface() { - return &hid_api_usb; + +typedef struct { + Bt* bt; + FuriHalBleProfileBase* profile; + HidStateCallback state_callback; + void* callback_context; + bool is_connected; +} BleHidInstance; + +static const BleProfileHidParams ble_hid_params = { + .device_name_prefix = "BadUSB", + .mac_xor = 0x0002, +}; + +static void hid_ble_connection_status_callback(BtStatus status, void* context) { + furi_assert(context); + BleHidInstance* ble_hid = context; + ble_hid->is_connected = (status == BtStatusConnected); + if(ble_hid->state_callback) { + ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); + } +} + +void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { + UNUSED(hid_cfg); + BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); + ble_hid->bt = furi_record_open(RECORD_BT); + bt_disconnect(ble_hid->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); + furi_check(ble_hid->profile); + + furi_hal_bt_start_advertising(); + + bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); + + return ble_hid; +} + +void hid_ble_deinit(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + + bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); + bt_disconnect(ble_hid->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(ble_hid->bt); + + furi_check(bt_profile_restore_default(ble_hid->bt)); + furi_record_close(RECORD_BT); + free(ble_hid); +} + +void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + ble_hid->state_callback = cb; + ble_hid->callback_context = context; +} + +bool hid_ble_is_connected(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_hid->is_connected; +} + +bool hid_ble_kb_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_press(ble_hid->profile, button); +} + +bool hid_ble_kb_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_kb_release(ble_hid->profile, button); +} + +bool hid_ble_consumer_press(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_press(ble_hid->profile, button); +} + +bool hid_ble_consumer_release(void* inst, uint16_t button) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + return ble_profile_hid_consumer_key_release(ble_hid->profile, button); +} + +bool hid_ble_release_all(void* inst) { + BleHidInstance* ble_hid = inst; + furi_assert(ble_hid); + bool state = ble_profile_hid_kb_release_all(ble_hid->profile); + state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); + return state; +} + +uint8_t hid_ble_get_led_state(void* inst) { + UNUSED(inst); + FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); + return 0; +} + +static const BadUsbHidApi hid_api_ble = { + .init = hid_ble_init, + .deinit = hid_ble_deinit, + .set_state_callback = hid_ble_set_state_callback, + .is_connected = hid_ble_is_connected, + + .kb_press = hid_ble_kb_press, + .kb_release = hid_ble_kb_release, + .consumer_press = hid_ble_consumer_press, + .consumer_release = hid_ble_consumer_release, + .release_all = hid_ble_release_all, + .get_led_state = hid_ble_get_led_state, +}; + +const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface) { + if(interface == BadUsbHidInterfaceUsb) { + return &hid_api_usb; + } else { + return &hid_api_ble; + } +} + +void bad_usb_hid_ble_remove_pairing(void) { + Bt* bt = furi_record_open(RECORD_BT); + bt_disconnect(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + furi_hal_bt_stop_advertising(); + + bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + bt_forget_bonded_devices(bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + bt_keys_storage_set_default_path(bt); + + furi_check(bt_profile_restore_default(bt)); + furi_record_close(RECORD_BT); } diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.h b/applications/main/bad_usb/helpers/bad_usb_hid.h index feaaacd5415..71d3a58e793 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.h +++ b/applications/main/bad_usb/helpers/bad_usb_hid.h @@ -7,6 +7,11 @@ extern "C" { #include #include +typedef enum { + BadUsbHidInterfaceUsb, + BadUsbHidInterfaceBle, +} BadUsbHidInterface; + typedef struct { void* (*init)(FuriHalUsbHidConfig* hid_cfg); void (*deinit)(void* inst); @@ -21,7 +26,7 @@ typedef struct { uint8_t (*get_led_state)(void* inst); } BadUsbHidApi; -const BadUsbHidApi* bad_usb_hid_get_interface(); +const BadUsbHidApi* bad_usb_hid_get_interface(BadUsbHidInterface interface); void bad_usb_hid_ble_remove_pairing(void); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index d730fdba4df..ccc3caa811b 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -650,7 +650,7 @@ static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) { memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout))); } -BadUsbScript* bad_usb_script_open(FuriString* file_path) { +BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface) { furi_assert(file_path); BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); @@ -660,7 +660,7 @@ BadUsbScript* bad_usb_script_open(FuriString* file_path) { bad_usb->st.state = BadUsbStateInit; bad_usb->st.error[0] = '\0'; - bad_usb->hid = bad_usb_hid_get_interface(); + bad_usb->hid = bad_usb_hid_get_interface(interface); bad_usb->thread = furi_thread_alloc_ex("BadUsbWorker", 2048, bad_usb_worker, bad_usb); furi_thread_start(bad_usb->thread); diff --git a/applications/main/bad_usb/helpers/ducky_script.h b/applications/main/bad_usb/helpers/ducky_script.h index 43969d7b67d..9519623f604 100644 --- a/applications/main/bad_usb/helpers/ducky_script.h +++ b/applications/main/bad_usb/helpers/ducky_script.h @@ -34,7 +34,7 @@ typedef struct { typedef struct BadUsbScript BadUsbScript; -BadUsbScript* bad_usb_script_open(FuriString* file_path); +BadUsbScript* bad_usb_script_open(FuriString* file_path, BadUsbHidInterface interface); void bad_usb_script_close(BadUsbScript* bad_usb); diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c new file mode 100644 index 00000000000..ae440cade9e --- /dev/null +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.c @@ -0,0 +1,59 @@ +#include "../bad_usb_app_i.h" + +enum SubmenuIndex { + ConfigIndexKeyboardLayout, + ConfigIndexBleUnpair, +}; + +void bad_usb_scene_config_select_callback(void* context, uint32_t index) { + BadUsbApp* bad_usb = context; + + view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index); +} + +static void draw_menu(BadUsbApp* bad_usb) { + VariableItemList* var_item_list = bad_usb->var_item_list; + + variable_item_list_reset(var_item_list); + + variable_item_list_add(var_item_list, "Keyboard Layout (global)", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "Remove Pairing", 0, NULL, NULL); +} + +void bad_usb_scene_config_on_enter(void* context) { + BadUsbApp* bad_usb = context; + VariableItemList* var_item_list = bad_usb->var_item_list; + + variable_item_list_set_enter_callback( + var_item_list, bad_usb_scene_config_select_callback, bad_usb); + draw_menu(bad_usb); + variable_item_list_set_selected_item(var_item_list, 0); + + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig); +} + +bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == ConfigIndexKeyboardLayout) { + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout); + } else if(event.event == ConfigIndexBleUnpair) { + scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfirmUnpair); + } else { + furi_crash("Unknown key type"); + } + } + + return consumed; +} + +void bad_usb_scene_config_on_exit(void* context) { + BadUsbApp* bad_usb = context; + VariableItemList* var_item_list = bad_usb->var_item_list; + + variable_item_list_reset(var_item_list); +} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.h b/applications/main/bad_usb/scenes/bad_usb_scene_config.h index e640bb556b5..3d1b8b1a7a2 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.h +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.h @@ -1,4 +1,7 @@ ADD_SCENE(bad_usb, file_select, FileSelect) ADD_SCENE(bad_usb, work, Work) ADD_SCENE(bad_usb, error, Error) +ADD_SCENE(bad_usb, config, Config) ADD_SCENE(bad_usb, config_layout, ConfigLayout) +ADD_SCENE(bad_usb, confirm_unpair, ConfirmUnpair) +ADD_SCENE(bad_usb, unpair_done, UnpairDone) diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c b/applications/main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c similarity index 51% rename from applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c rename to applications/main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c index 63f1e92cf25..b8fd993e2e7 100644 --- a/applications/system/bad_ble/scenes/bad_ble_scene_confirm_unpair.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_confirm_unpair.c @@ -1,42 +1,42 @@ -#include "../bad_ble_app_i.h" +#include "../bad_usb_app_i.h" -void bad_ble_scene_confirm_unpair_widget_callback( +void bad_usb_scene_confirm_unpair_widget_callback( GuiButtonType type, InputType input_type, void* context) { UNUSED(input_type); SceneManagerEvent event = {.type = SceneManagerEventTypeCustom, .event = type}; - bad_ble_scene_confirm_unpair_on_event(context, event); + bad_usb_scene_confirm_unpair_on_event(context, event); } -void bad_ble_scene_confirm_unpair_on_enter(void* context) { - BadBleApp* bad_ble = context; - Widget* widget = bad_ble->widget; +void bad_usb_scene_confirm_unpair_on_enter(void* context) { + BadUsbApp* bad_usb = context; + Widget* widget = bad_usb->widget; widget_add_button_element( - widget, GuiButtonTypeLeft, "Cancel", bad_ble_scene_confirm_unpair_widget_callback, context); + widget, GuiButtonTypeLeft, "Cancel", bad_usb_scene_confirm_unpair_widget_callback, context); widget_add_button_element( widget, GuiButtonTypeRight, "Unpair", - bad_ble_scene_confirm_unpair_widget_callback, + bad_usb_scene_confirm_unpair_widget_callback, context); widget_add_text_box_element( widget, 0, 0, 128, 64, AlignCenter, AlignTop, "\e#Unpair the Device?\e#\n", false); - view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewWidget); + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewWidget); } -bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) { - BadBleApp* bad_ble = context; - SceneManager* scene_manager = bad_ble->scene_manager; +bool bad_usb_scene_confirm_unpair_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + SceneManager* scene_manager = bad_usb->scene_manager; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == GuiButtonTypeRight) { - scene_manager_next_scene(scene_manager, BadBleSceneUnpairDone); + scene_manager_next_scene(scene_manager, BadUsbSceneUnpairDone); } else if(event.event == GuiButtonTypeLeft) { scene_manager_previous_scene(scene_manager); } @@ -45,9 +45,9 @@ bool bad_ble_scene_confirm_unpair_on_event(void* context, SceneManagerEvent even return consumed; } -void bad_ble_scene_confirm_unpair_on_exit(void* context) { - BadBleApp* bad_ble = context; - Widget* widget = bad_ble->widget; +void bad_usb_scene_confirm_unpair_on_exit(void* context) { + BadUsbApp* bad_usb = context; + Widget* widget = bad_usb->widget; widget_reset(widget); } diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_error.c b/applications/main/bad_usb/scenes/bad_usb_scene_error.c index 9d3a44f12f9..ea7f797671e 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_error.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_error.c @@ -43,7 +43,7 @@ void bad_usb_scene_error_on_enter(void* context) { "Disconnect from\nPC or phone to\nuse this function."); } - view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewError); + view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWidget); } bool bad_usb_scene_error_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c b/applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c new file mode 100644 index 00000000000..9583f9bfba3 --- /dev/null +++ b/applications/main/bad_usb/scenes/bad_usb_scene_unpair_done.c @@ -0,0 +1,39 @@ +#include "../bad_usb_app_i.h" + +static void bad_usb_scene_unpair_done_popup_callback(void* context) { + BadUsbApp* bad_usb = context; + scene_manager_search_and_switch_to_previous_scene(bad_usb->scene_manager, BadUsbSceneConfig); +} + +void bad_usb_scene_unpair_done_on_enter(void* context) { + BadUsbApp* bad_usb = context; + Popup* popup = bad_usb->popup; + + bad_usb_hid_ble_remove_pairing(); + + popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58); + popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom); + popup_set_callback(popup, bad_usb_scene_unpair_done_popup_callback); + popup_set_context(popup, bad_usb); + popup_set_timeout(popup, 1500); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewPopup); +} + +bool bad_usb_scene_unpair_done_on_event(void* context, SceneManagerEvent event) { + BadUsbApp* bad_usb = context; + UNUSED(bad_usb); + UNUSED(event); + bool consumed = false; + + return consumed; +} + +void bad_usb_scene_unpair_done_on_exit(void* context) { + BadUsbApp* bad_usb = context; + Popup* popup = bad_usb->popup; + UNUSED(popup); + + popup_reset(popup); +} diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_work.c b/applications/main/bad_usb/scenes/bad_usb_scene_work.c index 0afc056b622..0382b0f8ea6 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_work.c @@ -20,14 +20,27 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { bad_usb_script_close(app->bad_usb_script); app->bad_usb_script = NULL; - scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout); + if(app->interface == BadUsbHidInterfaceBle) { + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig); + } else { + scene_manager_next_scene(app->scene_manager, BadUsbSceneConfigLayout); + } } consumed = true; } else if(event.event == InputKeyOk) { bad_usb_script_start_stop(app->bad_usb_script); consumed = true; } else if(event.event == InputKeyRight) { - bad_usb_script_pause_resume(app->bad_usb_script); + if(bad_usb_view_is_idle_state(app->bad_usb_view)) { + bad_usb_set_interface( + app, + app->interface == BadUsbHidInterfaceBle ? BadUsbHidInterfaceUsb : + BadUsbHidInterfaceBle); + bad_usb_script_close(app->bad_usb_script); + app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface); + } else { + bad_usb_script_pause_resume(app->bad_usb_script); + } consumed = true; } } else if(event.type == SceneManagerEventTypeTick) { @@ -39,7 +52,9 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) { void bad_usb_scene_work_on_enter(void* context) { BadUsbApp* app = context; - app->bad_usb_script = bad_usb_script_open(app->file_path); + bad_usb_view_set_interface(app->bad_usb_view, app->interface); + + app->bad_usb_script = bad_usb_script_open(app->file_path, app->interface); bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); FuriString* file_name; diff --git a/applications/main/bad_usb/views/bad_usb_view.c b/applications/main/bad_usb/views/bad_usb_view.c index 7fb0b1434e6..1a6f77958d4 100644 --- a/applications/main/bad_usb/views/bad_usb_view.c +++ b/applications/main/bad_usb/views/bad_usb_view.c @@ -18,6 +18,7 @@ typedef struct { BadUsbState state; bool pause_wait; uint8_t anim_frame; + BadUsbHidInterface interface; } BadUsbModel; static void bad_usb_draw_callback(Canvas* canvas, void* _model) { @@ -40,14 +41,24 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { furi_string_reset(disp_str); - canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); + if(model->interface == BadUsbHidInterfaceBle) { + canvas_draw_icon(canvas, 22, 24, &I_Bad_BLE_48x22); + } else { + canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22); + } BadUsbWorkerState state = model->state.state; if((state == BadUsbStateIdle) || (state == BadUsbStateDone) || (state == BadUsbStateNotConnected)) { elements_button_center(canvas, "Run"); - elements_button_left(canvas, "Layout"); + if(model->interface == BadUsbHidInterfaceBle) { + elements_button_right(canvas, "USB"); + elements_button_left(canvas, "Config"); + } else { + elements_button_right(canvas, "BLE"); + elements_button_left(canvas, "Layout"); + } } else if((state == BadUsbStateRunning) || (state == BadUsbStateDelay)) { elements_button_center(canvas, "Stop"); if(!model->pause_wait) { @@ -266,6 +277,10 @@ void bad_usb_view_set_state(BadUsb* bad_usb, BadUsbState* st) { true); } +void bad_usb_view_set_interface(BadUsb* bad_usb, BadUsbHidInterface interface) { + with_view_model(bad_usb->view, BadUsbModel * model, { model->interface = interface; }, true); +} + bool bad_usb_view_is_idle_state(BadUsb* bad_usb) { bool is_idle = false; with_view_model( diff --git a/applications/main/bad_usb/views/bad_usb_view.h b/applications/main/bad_usb/views/bad_usb_view.h index 45f13d650b7..bca2f4bc083 100644 --- a/applications/main/bad_usb/views/bad_usb_view.h +++ b/applications/main/bad_usb/views/bad_usb_view.h @@ -23,4 +23,6 @@ void bad_usb_view_set_layout(BadUsb* bad_usb, const char* layout); void bad_usb_view_set_state(BadUsb* bad_usb, BadUsbState* st); +void bad_usb_view_set_interface(BadUsb* bad_usb, BadUsbHidInterface interface); + bool bad_usb_view_is_idle_state(BadUsb* bad_usb); diff --git a/applications/system/bad_ble/application.fam b/applications/system/bad_ble/application.fam deleted file mode 100644 index e00e6eefccf..00000000000 --- a/applications/system/bad_ble/application.fam +++ /dev/null @@ -1,12 +0,0 @@ -App( - appid="bad_ble", - name="Bad BLE", - apptype=FlipperAppType.EXTERNAL, - entry_point="bad_ble_app", - stack_size=2 * 1024, - icon="A_BadUsb_14", - fap_libs=["assets", "ble_profile"], - fap_icon="icon.png", - fap_icon_assets="assets", - fap_category="Bluetooth", -) diff --git a/applications/system/bad_ble/bad_ble_app.c b/applications/system/bad_ble/bad_ble_app.c deleted file mode 100644 index f243371986a..00000000000 --- a/applications/system/bad_ble/bad_ble_app.c +++ /dev/null @@ -1,196 +0,0 @@ -#include "bad_ble_app_i.h" -#include -#include -#include -#include -#include - -#define BAD_BLE_SETTINGS_PATH BAD_BLE_APP_BASE_FOLDER "/.badble.settings" -#define BAD_BLE_SETTINGS_FILE_TYPE "Flipper BadBLE Settings File" -#define BAD_BLE_SETTINGS_VERSION 1 -#define BAD_BLE_SETTINGS_DEFAULT_LAYOUT BAD_BLE_APP_PATH_LAYOUT_FOLDER "/en-US.kl" - -static bool bad_ble_app_custom_event_callback(void* context, uint32_t event) { - furi_assert(context); - BadBleApp* app = context; - return scene_manager_handle_custom_event(app->scene_manager, event); -} - -static bool bad_ble_app_back_event_callback(void* context) { - furi_assert(context); - BadBleApp* app = context; - return scene_manager_handle_back_event(app->scene_manager); -} - -static void bad_ble_app_tick_event_callback(void* context) { - furi_assert(context); - BadBleApp* app = context; - scene_manager_handle_tick_event(app->scene_manager); -} - -static void bad_ble_load_settings(BadBleApp* app) { - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff = flipper_format_file_alloc(storage); - bool state = false; - - FuriString* temp_str = furi_string_alloc(); - uint32_t version = 0; - - if(flipper_format_file_open_existing(fff, BAD_BLE_SETTINGS_PATH)) { - do { - if(!flipper_format_read_header(fff, temp_str, &version)) break; - if((strcmp(furi_string_get_cstr(temp_str), BAD_BLE_SETTINGS_FILE_TYPE) != 0) || - (version != BAD_BLE_SETTINGS_VERSION)) - break; - - if(!flipper_format_read_string(fff, "layout", temp_str)) break; - - state = true; - } while(0); - } - flipper_format_free(fff); - furi_record_close(RECORD_STORAGE); - - if(state) { - furi_string_set(app->keyboard_layout, temp_str); - - Storage* fs_api = furi_record_open(RECORD_STORAGE); - FileInfo layout_file_info; - FS_Error file_check_err = storage_common_stat( - fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info); - furi_record_close(RECORD_STORAGE); - if((file_check_err != FSE_OK) || (layout_file_info.size != 256)) { - furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT); - } - } else { - furi_string_set(app->keyboard_layout, BAD_BLE_SETTINGS_DEFAULT_LAYOUT); - } - - furi_string_free(temp_str); -} - -static void bad_ble_save_settings(BadBleApp* app) { - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperFormat* fff = flipper_format_file_alloc(storage); - - if(flipper_format_file_open_always(fff, BAD_BLE_SETTINGS_PATH)) { - do { - if(!flipper_format_write_header_cstr( - fff, BAD_BLE_SETTINGS_FILE_TYPE, BAD_BLE_SETTINGS_VERSION)) - break; - if(!flipper_format_write_string(fff, "layout", app->keyboard_layout)) break; - } while(0); - } - - flipper_format_free(fff); - furi_record_close(RECORD_STORAGE); -} - -BadBleApp* bad_ble_app_alloc(char* arg) { - BadBleApp* app = malloc(sizeof(BadBleApp)); - - app->bad_ble_script = NULL; - - app->file_path = furi_string_alloc(); - app->keyboard_layout = furi_string_alloc(); - if(arg && strlen(arg)) { - furi_string_set(app->file_path, arg); - } - - bad_ble_load_settings(app); - - app->gui = furi_record_open(RECORD_GUI); - app->notifications = furi_record_open(RECORD_NOTIFICATION); - app->dialogs = furi_record_open(RECORD_DIALOGS); - - app->view_dispatcher = view_dispatcher_alloc(); - app->scene_manager = scene_manager_alloc(&bad_ble_scene_handlers, app); - - view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - view_dispatcher_set_tick_event_callback( - app->view_dispatcher, bad_ble_app_tick_event_callback, 500); - view_dispatcher_set_custom_event_callback( - app->view_dispatcher, bad_ble_app_custom_event_callback); - view_dispatcher_set_navigation_event_callback( - app->view_dispatcher, bad_ble_app_back_event_callback); - - // Custom Widget - app->widget = widget_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, BadBleAppViewWidget, widget_get_view(app->widget)); - - // Popup - app->popup = popup_alloc(); - view_dispatcher_add_view(app->view_dispatcher, BadBleAppViewPopup, popup_get_view(app->popup)); - - app->var_item_list = variable_item_list_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, - BadBleAppViewConfig, - variable_item_list_get_view(app->var_item_list)); - - app->bad_ble_view = bad_ble_view_alloc(); - view_dispatcher_add_view( - app->view_dispatcher, BadBleAppViewWork, bad_ble_view_get_view(app->bad_ble_view)); - - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - - if(!furi_string_empty(app->file_path)) { - scene_manager_next_scene(app->scene_manager, BadBleSceneWork); - } else { - furi_string_set(app->file_path, BAD_BLE_APP_BASE_FOLDER); - scene_manager_next_scene(app->scene_manager, BadBleSceneFileSelect); - } - - return app; -} - -void bad_ble_app_free(BadBleApp* app) { - furi_assert(app); - - if(app->bad_ble_script) { - bad_ble_script_close(app->bad_ble_script); - app->bad_ble_script = NULL; - } - - // Views - view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWork); - bad_ble_view_free(app->bad_ble_view); - - // Custom Widget - view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewWidget); - widget_free(app->widget); - - // Popup - view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewPopup); - popup_free(app->popup); - - // Config menu - view_dispatcher_remove_view(app->view_dispatcher, BadBleAppViewConfig); - variable_item_list_free(app->var_item_list); - - // View dispatcher - view_dispatcher_free(app->view_dispatcher); - scene_manager_free(app->scene_manager); - - // Close records - furi_record_close(RECORD_GUI); - furi_record_close(RECORD_NOTIFICATION); - furi_record_close(RECORD_DIALOGS); - - bad_ble_save_settings(app); - - furi_string_free(app->file_path); - furi_string_free(app->keyboard_layout); - - free(app); -} - -int32_t bad_ble_app(void* p) { - BadBleApp* bad_ble_app = bad_ble_app_alloc((char*)p); - - view_dispatcher_run(bad_ble_app->view_dispatcher); - - bad_ble_app_free(bad_ble_app); - return 0; -} diff --git a/applications/system/bad_ble/bad_ble_app.h b/applications/system/bad_ble/bad_ble_app.h deleted file mode 100644 index 11954836e56..00000000000 --- a/applications/system/bad_ble/bad_ble_app.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct BadBleApp BadBleApp; - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/bad_ble/bad_ble_app_i.h b/applications/system/bad_ble/bad_ble_app_i.h deleted file mode 100644 index d1f739bebbc..00000000000 --- a/applications/system/bad_ble/bad_ble_app_i.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "bad_ble_app.h" -#include "scenes/bad_ble_scene.h" -#include "helpers/ducky_script.h" -#include "helpers/bad_ble_hid.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "views/bad_ble_view.h" - -#define BAD_BLE_APP_BASE_FOLDER EXT_PATH("badusb") -#define BAD_BLE_APP_PATH_LAYOUT_FOLDER BAD_BLE_APP_BASE_FOLDER "/assets/layouts" -#define BAD_BLE_APP_SCRIPT_EXTENSION ".txt" -#define BAD_BLE_APP_LAYOUT_EXTENSION ".kl" - -typedef enum { - BadBleAppErrorNoFiles, - BadBleAppErrorCloseRpc, -} BadBleAppError; - -struct BadBleApp { - Gui* gui; - ViewDispatcher* view_dispatcher; - SceneManager* scene_manager; - NotificationApp* notifications; - DialogsApp* dialogs; - Widget* widget; - Popup* popup; - VariableItemList* var_item_list; - - BadBleAppError error; - FuriString* file_path; - FuriString* keyboard_layout; - BadBle* bad_ble_view; - BadBleScript* bad_ble_script; - - BadBleHidInterface interface; -}; - -typedef enum { - BadBleAppViewWidget, - BadBleAppViewPopup, - BadBleAppViewWork, - BadBleAppViewConfig, -} BadBleAppView; diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.c b/applications/system/bad_ble/helpers/bad_ble_hid.c deleted file mode 100644 index c34b3c64612..00000000000 --- a/applications/system/bad_ble/helpers/bad_ble_hid.c +++ /dev/null @@ -1,157 +0,0 @@ -#include "bad_ble_hid.h" -#include -#include -#include - -#define TAG "BadBLE HID" - -#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" - -typedef struct { - Bt* bt; - FuriHalBleProfileBase* profile; - HidStateCallback state_callback; - void* callback_context; - bool is_connected; -} BleHidInstance; - -static const BleProfileHidParams ble_hid_params = { - .device_name_prefix = "BadBLE", - .mac_xor = 0x0002, -}; - -static void hid_ble_connection_status_callback(BtStatus status, void* context) { - furi_assert(context); - BleHidInstance* ble_hid = context; - ble_hid->is_connected = (status == BtStatusConnected); - if(ble_hid->state_callback) { - ble_hid->state_callback(ble_hid->is_connected, ble_hid->callback_context); - } -} - -void* hid_ble_init(FuriHalUsbHidConfig* hid_cfg) { - UNUSED(hid_cfg); - BleHidInstance* ble_hid = malloc(sizeof(BleHidInstance)); - ble_hid->bt = furi_record_open(RECORD_BT); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - - ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, (void*)&ble_hid_params); - furi_check(ble_hid->profile); - - furi_hal_bt_start_advertising(); - - bt_set_status_changed_callback(ble_hid->bt, hid_ble_connection_status_callback, ble_hid); - - return ble_hid; -} - -void hid_ble_deinit(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - - bt_set_status_changed_callback(ble_hid->bt, NULL, NULL); - bt_disconnect(ble_hid->bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(ble_hid->bt); - - furi_check(bt_profile_restore_default(ble_hid->bt)); - furi_record_close(RECORD_BT); - free(ble_hid); -} - -void hid_ble_set_state_callback(void* inst, HidStateCallback cb, void* context) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - ble_hid->state_callback = cb; - ble_hid->callback_context = context; -} - -bool hid_ble_is_connected(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_hid->is_connected; -} - -bool hid_ble_kb_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_press(ble_hid->profile, button); -} - -bool hid_ble_kb_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_kb_release(ble_hid->profile, button); -} - -bool hid_ble_consumer_press(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_press(ble_hid->profile, button); -} - -bool hid_ble_consumer_release(void* inst, uint16_t button) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - return ble_profile_hid_consumer_key_release(ble_hid->profile, button); -} - -bool hid_ble_release_all(void* inst) { - BleHidInstance* ble_hid = inst; - furi_assert(ble_hid); - bool state = ble_profile_hid_kb_release_all(ble_hid->profile); - state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile); - return state; -} - -uint8_t hid_ble_get_led_state(void* inst) { - UNUSED(inst); - FURI_LOG_W(TAG, "hid_ble_get_led_state not implemented"); - return 0; -} - -static const BadBleHidApi hid_api_ble = { - .init = hid_ble_init, - .deinit = hid_ble_deinit, - .set_state_callback = hid_ble_set_state_callback, - .is_connected = hid_ble_is_connected, - - .kb_press = hid_ble_kb_press, - .kb_release = hid_ble_kb_release, - .consumer_press = hid_ble_consumer_press, - .consumer_release = hid_ble_consumer_release, - .release_all = hid_ble_release_all, - .get_led_state = hid_ble_get_led_state, -}; - -const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface) { - UNUSED(interface); - return &hid_api_ble; -} - -void bad_ble_hid_ble_remove_pairing(void) { - Bt* bt = furi_record_open(RECORD_BT); - bt_disconnect(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - - furi_hal_bt_stop_advertising(); - - bt_keys_storage_set_storage_path(bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); - bt_forget_bonded_devices(bt); - - // Wait 2nd core to update nvm storage - furi_delay_ms(200); - bt_keys_storage_set_default_path(bt); - - furi_check(bt_profile_restore_default(bt)); - furi_record_close(RECORD_BT); -} diff --git a/applications/system/bad_ble/helpers/bad_ble_hid.h b/applications/system/bad_ble/helpers/bad_ble_hid.h deleted file mode 100644 index b06385d6dd4..00000000000 --- a/applications/system/bad_ble/helpers/bad_ble_hid.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -typedef enum { - BadBleHidInterfaceBle, -} BadBleHidInterface; - -typedef struct { - void* (*init)(FuriHalUsbHidConfig* hid_cfg); - void (*deinit)(void* inst); - void (*set_state_callback)(void* inst, HidStateCallback cb, void* context); - bool (*is_connected)(void* inst); - - bool (*kb_press)(void* inst, uint16_t button); - bool (*kb_release)(void* inst, uint16_t button); - bool (*consumer_press)(void* inst, uint16_t button); - bool (*consumer_release)(void* inst, uint16_t button); - bool (*release_all)(void* inst); - uint8_t (*get_led_state)(void* inst); -} BadBleHidApi; - -const BadBleHidApi* bad_ble_hid_get_interface(BadBleHidInterface interface); - -void bad_ble_hid_ble_remove_pairing(void); - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/bad_ble/helpers/ducky_script.c b/applications/system/bad_ble/helpers/ducky_script.c deleted file mode 100644 index a903fbdc4d0..00000000000 --- a/applications/system/bad_ble/helpers/ducky_script.c +++ /dev/null @@ -1,716 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "ducky_script.h" -#include "ducky_script_i.h" -#include - -#define TAG "BadBle" - -#define WORKER_TAG TAG "Worker" - -#define BADUSB_ASCII_TO_KEY(script, x) \ - (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE) - -typedef enum { - WorkerEvtStartStop = (1 << 0), - WorkerEvtPauseResume = (1 << 1), - WorkerEvtEnd = (1 << 2), - WorkerEvtConnect = (1 << 3), - WorkerEvtDisconnect = (1 << 4), -} WorkerEvtFlags; - -static const char ducky_cmd_id[] = {"ID"}; - -static const uint8_t numpad_keys[10] = { - HID_KEYPAD_0, - HID_KEYPAD_1, - HID_KEYPAD_2, - HID_KEYPAD_3, - HID_KEYPAD_4, - HID_KEYPAD_5, - HID_KEYPAD_6, - HID_KEYPAD_7, - HID_KEYPAD_8, - HID_KEYPAD_9, -}; - -uint32_t ducky_get_command_len(const char* line) { - uint32_t len = strlen(line); - for(uint32_t i = 0; i < len; i++) { - if(line[i] == ' ') return i; - } - return 0; -} - -bool ducky_is_line_end(const char chr) { - return (chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'); -} - -uint16_t ducky_get_keycode(BadBleScript* bad_ble, const char* param, bool accept_chars) { - uint16_t keycode = ducky_get_keycode_by_name(param); - if(keycode != HID_KEYBOARD_NONE) { - return keycode; - } - - if((accept_chars) && (strlen(param) > 0)) { - return BADUSB_ASCII_TO_KEY(bad_ble, param[0]) & 0xFF; - } - return 0; -} - -bool ducky_get_number(const char* param, uint32_t* val) { - uint32_t value = 0; - if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) { - *val = value; - return true; - } - return false; -} - -void ducky_numlock_on(BadBleScript* bad_ble) { - if((bad_ble->hid->get_led_state(bad_ble->hid_inst) & HID_KB_LED_NUM) == 0) { - bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK); - bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_LOCK_NUM_LOCK); - } -} - -bool ducky_numpad_press(BadBleScript* bad_ble, const char num) { - if((num < '0') || (num > '9')) return false; - - uint16_t key = numpad_keys[num - '0']; - bad_ble->hid->kb_press(bad_ble->hid_inst, key); - bad_ble->hid->kb_release(bad_ble->hid_inst, key); - - return true; -} - -bool ducky_altchar(BadBleScript* bad_ble, const char* charcode) { - uint8_t i = 0; - bool state = false; - - bad_ble->hid->kb_press(bad_ble->hid_inst, KEY_MOD_LEFT_ALT); - - while(!ducky_is_line_end(charcode[i])) { - state = ducky_numpad_press(bad_ble, charcode[i]); - if(state == false) break; - i++; - } - - bad_ble->hid->kb_release(bad_ble->hid_inst, KEY_MOD_LEFT_ALT); - return state; -} - -bool ducky_altstring(BadBleScript* bad_ble, const char* param) { - uint32_t i = 0; - bool state = false; - - while(param[i] != '\0') { - if((param[i] < ' ') || (param[i] > '~')) { - i++; - continue; // Skip non-printable chars - } - - char temp_str[4]; - snprintf(temp_str, 4, "%u", param[i]); - - state = ducky_altchar(bad_ble, temp_str); - if(state == false) break; - i++; - } - return state; -} - -int32_t ducky_error(BadBleScript* bad_ble, const char* text, ...) { - va_list args; - va_start(args, text); - - vsnprintf(bad_ble->st.error, sizeof(bad_ble->st.error), text, args); - - va_end(args); - return SCRIPT_STATE_ERROR; -} - -bool ducky_string(BadBleScript* bad_ble, const char* param) { - uint32_t i = 0; - - while(param[i] != '\0') { - if(param[i] != '\n') { - uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, param[i]); - if(keycode != HID_KEYBOARD_NONE) { - bad_ble->hid->kb_press(bad_ble->hid_inst, keycode); - bad_ble->hid->kb_release(bad_ble->hid_inst, keycode); - } - } else { - bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN); - bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN); - } - i++; - } - bad_ble->stringdelay = 0; - return true; -} - -static bool ducky_string_next(BadBleScript* bad_ble) { - if(bad_ble->string_print_pos >= furi_string_size(bad_ble->string_print)) { - return true; - } - - char print_char = furi_string_get_char(bad_ble->string_print, bad_ble->string_print_pos); - - if(print_char != '\n') { - uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_ble, print_char); - if(keycode != HID_KEYBOARD_NONE) { - bad_ble->hid->kb_press(bad_ble->hid_inst, keycode); - bad_ble->hid->kb_release(bad_ble->hid_inst, keycode); - } - } else { - bad_ble->hid->kb_press(bad_ble->hid_inst, HID_KEYBOARD_RETURN); - bad_ble->hid->kb_release(bad_ble->hid_inst, HID_KEYBOARD_RETURN); - } - - bad_ble->string_print_pos++; - - return false; -} - -static int32_t ducky_parse_line(BadBleScript* bad_ble, FuriString* line) { - uint32_t line_len = furi_string_size(line); - const char* line_tmp = furi_string_get_cstr(line); - - if(line_len == 0) { - return SCRIPT_STATE_NEXT_LINE; // Skip empty lines - } - FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp); - - // Ducky Lang Functions - int32_t cmd_result = ducky_execute_cmd(bad_ble, line_tmp); - if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) { - return cmd_result; - } - - // Special keys + modifiers - uint16_t key = ducky_get_keycode(bad_ble, line_tmp, false); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_ble, "No keycode defined for %s", line_tmp); - } - if((key & 0xFF00) != 0) { - // It's a modifier key - line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1]; - key |= ducky_get_keycode(bad_ble, line_tmp, true); - } - bad_ble->hid->kb_press(bad_ble->hid_inst, key); - bad_ble->hid->kb_release(bad_ble->hid_inst, key); - return 0; -} - -static bool ducky_set_usb_id(BadBleScript* bad_ble, const char* line) { - if(sscanf(line, "%lX:%lX", &bad_ble->hid_cfg.vid, &bad_ble->hid_cfg.pid) == 2) { - bad_ble->hid_cfg.manuf[0] = '\0'; - bad_ble->hid_cfg.product[0] = '\0'; - - uint8_t id_len = ducky_get_command_len(line); - if(!ducky_is_line_end(line[id_len + 1])) { - sscanf( - &line[id_len + 1], - "%31[^\r\n:]:%31[^\r\n]", - bad_ble->hid_cfg.manuf, - bad_ble->hid_cfg.product); - } - FURI_LOG_D( - WORKER_TAG, - "set id: %04lX:%04lX mfr:%s product:%s", - bad_ble->hid_cfg.vid, - bad_ble->hid_cfg.pid, - bad_ble->hid_cfg.manuf, - bad_ble->hid_cfg.product); - return true; - } - return false; -} - -static void bad_ble_hid_state_callback(bool state, void* context) { - furi_assert(context); - BadBleScript* bad_ble = context; - - if(state == true) { - furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtConnect); - } else { - furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtDisconnect); - } -} - -static bool ducky_script_preload(BadBleScript* bad_ble, File* script_file) { - uint8_t ret = 0; - uint32_t line_len = 0; - - furi_string_reset(bad_ble->line); - - do { - ret = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); - for(uint16_t i = 0; i < ret; i++) { - if(bad_ble->file_buf[i] == '\n' && line_len > 0) { - bad_ble->st.line_nb++; - line_len = 0; - } else { - if(bad_ble->st.line_nb == 0) { // Save first line - furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); - } - line_len++; - } - } - if(storage_file_eof(script_file)) { - if(line_len > 0) { - bad_ble->st.line_nb++; - break; - } - } - } while(ret > 0); - - const char* line_tmp = furi_string_get_cstr(bad_ble->line); - bool id_set = false; // Looking for ID command at first line - if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) { - id_set = ducky_set_usb_id(bad_ble, &line_tmp[strlen(ducky_cmd_id) + 1]); - } - - if(id_set) { - bad_ble->hid_inst = bad_ble->hid->init(&bad_ble->hid_cfg); - } else { - bad_ble->hid_inst = bad_ble->hid->init(NULL); - } - bad_ble->hid->set_state_callback(bad_ble->hid_inst, bad_ble_hid_state_callback, bad_ble); - - storage_file_seek(script_file, 0, true); - furi_string_reset(bad_ble->line); - - return true; -} - -static int32_t ducky_script_execute_next(BadBleScript* bad_ble, File* script_file) { - int32_t delay_val = 0; - - if(bad_ble->repeat_cnt > 0) { - bad_ble->repeat_cnt--; - delay_val = ducky_parse_line(bad_ble, bad_ble->line_prev); - if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line - return 0; - } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays - return delay_val; - } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button - return delay_val; - } else if(delay_val < 0) { // Script error - bad_ble->st.error_line = bad_ble->st.line_cur - 1; - FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur - 1U); - return SCRIPT_STATE_ERROR; - } else { - return delay_val + bad_ble->defdelay; - } - } - - furi_string_set(bad_ble->line_prev, bad_ble->line); - furi_string_reset(bad_ble->line); - - while(1) { - if(bad_ble->buf_len == 0) { - bad_ble->buf_len = storage_file_read(script_file, bad_ble->file_buf, FILE_BUFFER_LEN); - if(storage_file_eof(script_file)) { - if((bad_ble->buf_len < FILE_BUFFER_LEN) && (bad_ble->file_end == false)) { - bad_ble->file_buf[bad_ble->buf_len] = '\n'; - bad_ble->buf_len++; - bad_ble->file_end = true; - } - } - - bad_ble->buf_start = 0; - if(bad_ble->buf_len == 0) return SCRIPT_STATE_END; - } - for(uint8_t i = bad_ble->buf_start; i < (bad_ble->buf_start + bad_ble->buf_len); i++) { - if(bad_ble->file_buf[i] == '\n' && furi_string_size(bad_ble->line) > 0) { - bad_ble->st.line_cur++; - bad_ble->buf_len = bad_ble->buf_len + bad_ble->buf_start - (i + 1); - bad_ble->buf_start = i + 1; - furi_string_trim(bad_ble->line); - delay_val = ducky_parse_line(bad_ble, bad_ble->line); - if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line - return 0; - } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays - return delay_val; - } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button - return delay_val; - } else if(delay_val < 0) { - bad_ble->st.error_line = bad_ble->st.line_cur; - FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_ble->st.line_cur); - return SCRIPT_STATE_ERROR; - } else { - return delay_val + bad_ble->defdelay; - } - } else { - furi_string_push_back(bad_ble->line, bad_ble->file_buf[i]); - } - } - bad_ble->buf_len = 0; - if(bad_ble->file_end) return SCRIPT_STATE_END; - } - - return 0; -} - -static uint32_t bad_ble_flags_get(uint32_t flags_mask, uint32_t timeout) { - uint32_t flags = furi_thread_flags_get(); - furi_check((flags & FuriFlagError) == 0); - if(flags == 0) { - flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); - furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout)); - } else { - uint32_t state = furi_thread_flags_clear(flags); - furi_check((state & FuriFlagError) == 0); - } - return flags; -} - -static int32_t bad_ble_worker(void* context) { - BadBleScript* bad_ble = context; - - BadBleWorkerState worker_state = BadBleStateInit; - BadBleWorkerState pause_state = BadBleStateRunning; - int32_t delay_val = 0; - - FURI_LOG_I(WORKER_TAG, "Init"); - File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - bad_ble->line = furi_string_alloc(); - bad_ble->line_prev = furi_string_alloc(); - bad_ble->string_print = furi_string_alloc(); - - while(1) { - if(worker_state == BadBleStateInit) { // State: initialization - if(storage_file_open( - script_file, - furi_string_get_cstr(bad_ble->file_path), - FSAM_READ, - FSOM_OPEN_EXISTING)) { - if((ducky_script_preload(bad_ble, script_file)) && (bad_ble->st.line_nb > 0)) { - if(bad_ble->hid->is_connected(bad_ble->hid_inst)) { - worker_state = BadBleStateIdle; // Ready to run - } else { - worker_state = BadBleStateNotConnected; // USB not connected - } - } else { - worker_state = BadBleStateScriptError; // Script preload error - } - } else { - FURI_LOG_E(WORKER_TAG, "File open error"); - worker_state = BadBleStateFileError; // File open error - } - bad_ble->st.state = worker_state; - - } else if(worker_state == BadBleStateNotConnected) { // State: USB not connected - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop, - FuriWaitForever); - - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtConnect) { - worker_state = BadBleStateIdle; // Ready to run - } else if(flags & WorkerEvtStartStop) { - worker_state = BadBleStateWillRun; // Will run when USB is connected - } - bad_ble->st.state = worker_state; - - } else if(worker_state == BadBleStateIdle) { // State: ready to start - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtDisconnect, FuriWaitForever); - - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtStartStop) { // Start executing script - dolphin_deed(DolphinDeedBadUsbPlayScript); - delay_val = 0; - bad_ble->buf_len = 0; - bad_ble->st.line_cur = 0; - bad_ble->defdelay = 0; - bad_ble->stringdelay = 0; - bad_ble->defstringdelay = 0; - bad_ble->repeat_cnt = 0; - bad_ble->key_hold_nb = 0; - bad_ble->file_end = false; - storage_file_seek(script_file, 0, true); - worker_state = BadBleStateRunning; - } else if(flags & WorkerEvtDisconnect) { - worker_state = BadBleStateNotConnected; // USB disconnected - } - bad_ble->st.state = worker_state; - - } else if(worker_state == BadBleStateWillRun) { // State: start on connection - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtConnect | WorkerEvtStartStop, FuriWaitForever); - - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtConnect) { // Start executing script - dolphin_deed(DolphinDeedBadUsbPlayScript); - delay_val = 0; - bad_ble->buf_len = 0; - bad_ble->st.line_cur = 0; - bad_ble->defdelay = 0; - bad_ble->stringdelay = 0; - bad_ble->defstringdelay = 0; - bad_ble->repeat_cnt = 0; - bad_ble->file_end = false; - storage_file_seek(script_file, 0, true); - // extra time for PC to recognize Flipper as keyboard - flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop, - FuriFlagWaitAny | FuriFlagNoClear, - 1500); - if(flags == (unsigned)FuriFlagErrorTimeout) { - // If nothing happened - start script execution - worker_state = BadBleStateRunning; - } else if(flags & WorkerEvtStartStop) { - worker_state = BadBleStateIdle; - furi_thread_flags_clear(WorkerEvtStartStop); - } - } else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution - worker_state = BadBleStateNotConnected; - } - bad_ble->st.state = worker_state; - - } else if(worker_state == BadBleStateRunning) { // State: running - uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); - uint32_t flags = furi_thread_flags_wait( - WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, - FuriFlagWaitAny, - delay_cur); - - delay_val -= delay_cur; - if(!(flags & FuriFlagError)) { - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtStartStop) { - worker_state = BadBleStateIdle; // Stop executing script - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtDisconnect) { - worker_state = BadBleStateNotConnected; // USB disconnected - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtPauseResume) { - pause_state = BadBleStateRunning; - worker_state = BadBleStatePaused; // Pause - } - bad_ble->st.state = worker_state; - continue; - } else if( - (flags == (unsigned)FuriFlagErrorTimeout) || - (flags == (unsigned)FuriFlagErrorResource)) { - if(delay_val > 0) { - bad_ble->st.delay_remain--; - continue; - } - bad_ble->st.state = BadBleStateRunning; - delay_val = ducky_script_execute_next(bad_ble, script_file); - if(delay_val == SCRIPT_STATE_ERROR) { // Script error - delay_val = 0; - worker_state = BadBleStateScriptError; - bad_ble->st.state = worker_state; - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(delay_val == SCRIPT_STATE_END) { // End of script - delay_val = 0; - worker_state = BadBleStateIdle; - bad_ble->st.state = BadBleStateDone; - bad_ble->hid->release_all(bad_ble->hid_inst); - continue; - } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays - delay_val = bad_ble->defdelay; - bad_ble->string_print_pos = 0; - worker_state = BadBleStateStringDelay; - } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input - worker_state = BadBleStateWaitForBtn; - bad_ble->st.state = BadBleStateWaitForBtn; // Show long delays - } else if(delay_val > 1000) { - bad_ble->st.state = BadBleStateDelay; // Show long delays - bad_ble->st.delay_remain = delay_val / 1000; - } - } else { - furi_check((flags & FuriFlagError) == 0); - } - } else if(worker_state == BadBleStateWaitForBtn) { // State: Wait for button Press - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, - FuriWaitForever); - if(!(flags & FuriFlagError)) { - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtStartStop) { - delay_val = 0; - worker_state = BadBleStateRunning; - } else if(flags & WorkerEvtDisconnect) { - worker_state = BadBleStateNotConnected; // USB disconnected - bad_ble->hid->release_all(bad_ble->hid_inst); - } - bad_ble->st.state = worker_state; - continue; - } - } else if(worker_state == BadBleStatePaused) { // State: Paused - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, - FuriWaitForever); - if(!(flags & FuriFlagError)) { - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtStartStop) { - worker_state = BadBleStateIdle; // Stop executing script - bad_ble->st.state = worker_state; - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtDisconnect) { - worker_state = BadBleStateNotConnected; // USB disconnected - bad_ble->st.state = worker_state; - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtPauseResume) { - if(pause_state == BadBleStateRunning) { - if(delay_val > 0) { - bad_ble->st.state = BadBleStateDelay; - bad_ble->st.delay_remain = delay_val / 1000; - } else { - bad_ble->st.state = BadBleStateRunning; - delay_val = 0; - } - worker_state = BadBleStateRunning; // Resume - } else if(pause_state == BadBleStateStringDelay) { - bad_ble->st.state = BadBleStateRunning; - worker_state = BadBleStateStringDelay; // Resume - } - } - continue; - } - } else if(worker_state == BadBleStateStringDelay) { // State: print string with delays - uint32_t delay = (bad_ble->stringdelay == 0) ? bad_ble->defstringdelay : - bad_ble->stringdelay; - uint32_t flags = bad_ble_flags_get( - WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, - delay); - - if(!(flags & FuriFlagError)) { - if(flags & WorkerEvtEnd) { - break; - } else if(flags & WorkerEvtStartStop) { - worker_state = BadBleStateIdle; // Stop executing script - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtDisconnect) { - worker_state = BadBleStateNotConnected; // USB disconnected - bad_ble->hid->release_all(bad_ble->hid_inst); - } else if(flags & WorkerEvtPauseResume) { - pause_state = BadBleStateStringDelay; - worker_state = BadBleStatePaused; // Pause - } - bad_ble->st.state = worker_state; - continue; - } else if( - (flags == (unsigned)FuriFlagErrorTimeout) || - (flags == (unsigned)FuriFlagErrorResource)) { - bool string_end = ducky_string_next(bad_ble); - if(string_end) { - bad_ble->stringdelay = 0; - worker_state = BadBleStateRunning; - } - } else { - furi_check((flags & FuriFlagError) == 0); - } - } else if( - (worker_state == BadBleStateFileError) || - (worker_state == BadBleStateScriptError)) { // State: error - uint32_t flags = - bad_ble_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command - - if(flags & WorkerEvtEnd) { - break; - } - } - } - - bad_ble->hid->set_state_callback(bad_ble->hid_inst, NULL, NULL); - bad_ble->hid->deinit(bad_ble->hid_inst); - - storage_file_close(script_file); - storage_file_free(script_file); - furi_string_free(bad_ble->line); - furi_string_free(bad_ble->line_prev); - furi_string_free(bad_ble->string_print); - - FURI_LOG_I(WORKER_TAG, "End"); - - return 0; -} - -static void bad_ble_script_set_default_keyboard_layout(BadBleScript* bad_ble) { - furi_assert(bad_ble); - memset(bad_ble->layout, HID_KEYBOARD_NONE, sizeof(bad_ble->layout)); - memcpy(bad_ble->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_ble->layout))); -} - -BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface) { - furi_assert(file_path); - - BadBleScript* bad_ble = malloc(sizeof(BadBleScript)); - bad_ble->file_path = furi_string_alloc(); - furi_string_set(bad_ble->file_path, file_path); - bad_ble_script_set_default_keyboard_layout(bad_ble); - - bad_ble->st.state = BadBleStateInit; - bad_ble->st.error[0] = '\0'; - bad_ble->hid = bad_ble_hid_get_interface(interface); - - bad_ble->thread = furi_thread_alloc_ex("BadBleWorker", 2048, bad_ble_worker, bad_ble); - furi_thread_start(bad_ble->thread); - return bad_ble; -} //-V773 - -void bad_ble_script_close(BadBleScript* bad_ble) { - furi_assert(bad_ble); - furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtEnd); - furi_thread_join(bad_ble->thread); - furi_thread_free(bad_ble->thread); - furi_string_free(bad_ble->file_path); - free(bad_ble); -} - -void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path) { - furi_assert(bad_ble); - - if((bad_ble->st.state == BadBleStateRunning) || (bad_ble->st.state == BadBleStateDelay)) { - // do not update keyboard layout while a script is running - return; - } - - File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - if(!furi_string_empty(layout_path)) { //-V1051 - if(storage_file_open( - layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) { - uint16_t layout[128]; - if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) { - memcpy(bad_ble->layout, layout, sizeof(layout)); - } - } - storage_file_close(layout_file); - } else { - bad_ble_script_set_default_keyboard_layout(bad_ble); - } - storage_file_free(layout_file); -} - -void bad_ble_script_start_stop(BadBleScript* bad_ble) { - furi_assert(bad_ble); - furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtStartStop); -} - -void bad_ble_script_pause_resume(BadBleScript* bad_ble) { - furi_assert(bad_ble); - furi_thread_flags_set(furi_thread_get_id(bad_ble->thread), WorkerEvtPauseResume); -} - -BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble) { - furi_assert(bad_ble); - return &(bad_ble->st); -} diff --git a/applications/system/bad_ble/helpers/ducky_script.h b/applications/system/bad_ble/helpers/ducky_script.h deleted file mode 100644 index 044cae82561..00000000000 --- a/applications/system/bad_ble/helpers/ducky_script.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include "bad_ble_hid.h" - -typedef enum { - BadBleStateInit, - BadBleStateNotConnected, - BadBleStateIdle, - BadBleStateWillRun, - BadBleStateRunning, - BadBleStateDelay, - BadBleStateStringDelay, - BadBleStateWaitForBtn, - BadBleStatePaused, - BadBleStateDone, - BadBleStateScriptError, - BadBleStateFileError, -} BadBleWorkerState; - -typedef struct { - BadBleWorkerState state; - size_t line_cur; - size_t line_nb; - uint32_t delay_remain; - size_t error_line; - char error[64]; -} BadBleState; - -typedef struct BadBleScript BadBleScript; - -BadBleScript* bad_ble_script_open(FuriString* file_path, BadBleHidInterface interface); - -void bad_ble_script_close(BadBleScript* bad_ble); - -void bad_ble_script_set_keyboard_layout(BadBleScript* bad_ble, FuriString* layout_path); - -void bad_ble_script_start(BadBleScript* bad_ble); - -void bad_ble_script_stop(BadBleScript* bad_ble); - -void bad_ble_script_start_stop(BadBleScript* bad_ble); - -void bad_ble_script_pause_resume(BadBleScript* bad_ble); - -BadBleState* bad_ble_script_get_state(BadBleScript* bad_ble); - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/bad_ble/helpers/ducky_script_commands.c b/applications/system/bad_ble/helpers/ducky_script_commands.c deleted file mode 100644 index f70c5eba400..00000000000 --- a/applications/system/bad_ble/helpers/ducky_script_commands.c +++ /dev/null @@ -1,241 +0,0 @@ -#include -#include "ducky_script.h" -#include "ducky_script_i.h" - -typedef int32_t (*DuckyCmdCallback)(BadBleScript* bad_usb, const char* line, int32_t param); - -typedef struct { - char* name; - DuckyCmdCallback callback; - int32_t param; -} DuckyCmd; - -static int32_t ducky_fnc_delay(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint32_t delay_val = 0; - bool state = ducky_get_number(line, &delay_val); - if((state) && (delay_val > 0)) { - return (int32_t)delay_val; - } - - return ducky_error(bad_usb, "Invalid number %s", line); -} - -static int32_t ducky_fnc_defdelay(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - bool state = ducky_get_number(line, &bad_usb->defdelay); - if(!state) { - return ducky_error(bad_usb, "Invalid number %s", line); - } - return 0; -} - -static int32_t ducky_fnc_strdelay(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - bool state = ducky_get_number(line, &bad_usb->stringdelay); - if(!state) { - return ducky_error(bad_usb, "Invalid number %s", line); - } - return 0; -} - -static int32_t ducky_fnc_defstrdelay(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - bool state = ducky_get_number(line, &bad_usb->defstringdelay); - if(!state) { - return ducky_error(bad_usb, "Invalid number %s", line); - } - return 0; -} - -static int32_t ducky_fnc_string(BadBleScript* bad_usb, const char* line, int32_t param) { - line = &line[ducky_get_command_len(line) + 1]; - furi_string_set_str(bad_usb->string_print, line); - if(param == 1) { - furi_string_cat(bad_usb->string_print, "\n"); - } - - if(bad_usb->stringdelay == 0 && - bad_usb->defstringdelay == 0) { // stringdelay not set - run command immediately - bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print)); - if(!state) { - return ducky_error(bad_usb, "Invalid string %s", line); - } - } else { // stringdelay is set - run command in thread to keep handling external events - return SCRIPT_STATE_STRING_START; - } - - return 0; -} - -static int32_t ducky_fnc_repeat(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - bool state = ducky_get_number(line, &bad_usb->repeat_cnt); - if((!state) || (bad_usb->repeat_cnt == 0)) { - return ducky_error(bad_usb, "Invalid number %s", line); - } - return 0; -} - -static int32_t ducky_fnc_sysrq(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); - bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); - bad_usb->hid->kb_press(bad_usb->hid_inst, key); - bad_usb->hid->release_all(bad_usb->hid_inst); - return 0; -} - -static int32_t ducky_fnc_altchar(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - ducky_numlock_on(bad_usb); - bool state = ducky_altchar(bad_usb, line); - if(!state) { - return ducky_error(bad_usb, "Invalid altchar %s", line); - } - return 0; -} - -static int32_t ducky_fnc_altstring(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - ducky_numlock_on(bad_usb); - bool state = ducky_altstring(bad_usb, line); - if(!state) { - return ducky_error(bad_usb, "Invalid altstring %s", line); - } - return 0; -} - -static int32_t ducky_fnc_hold(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_usb, "No keycode defined for %s", line); - } - bad_usb->key_hold_nb++; - if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) { - return ducky_error(bad_usb, "Too many keys are hold"); - } - bad_usb->hid->kb_press(bad_usb->hid_inst, key); - return 0; -} - -static int32_t ducky_fnc_release(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_usb, "No keycode defined for %s", line); - } - if(bad_usb->key_hold_nb == 0) { - return ducky_error(bad_usb, "No keys are hold"); - } - bad_usb->key_hold_nb--; - bad_usb->hid->kb_release(bad_usb->hid_inst, key); - return 0; -} - -static int32_t ducky_fnc_media(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_media_keycode_by_name(line); - if(key == HID_CONSUMER_UNASSIGNED) { - return ducky_error(bad_usb, "No keycode defined for %s", line); - } - bad_usb->hid->consumer_press(bad_usb->hid_inst, key); - bad_usb->hid->consumer_release(bad_usb->hid_inst, key); - return 0; -} - -static int32_t ducky_fnc_globe(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - - line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); - if(key == HID_KEYBOARD_NONE) { - return ducky_error(bad_usb, "No keycode defined for %s", line); - } - - bad_usb->hid->consumer_press(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE); - bad_usb->hid->kb_press(bad_usb->hid_inst, key); - bad_usb->hid->kb_release(bad_usb->hid_inst, key); - bad_usb->hid->consumer_release(bad_usb->hid_inst, HID_CONSUMER_FN_GLOBE); - return 0; -} - -static int32_t ducky_fnc_waitforbutton(BadBleScript* bad_usb, const char* line, int32_t param) { - UNUSED(param); - UNUSED(bad_usb); - UNUSED(line); - - return SCRIPT_STATE_WAIT_FOR_BTN; -} - -static const DuckyCmd ducky_commands[] = { - {"REM", NULL, -1}, - {"ID", NULL, -1}, - {"DELAY", ducky_fnc_delay, -1}, - {"STRING", ducky_fnc_string, 0}, - {"STRINGLN", ducky_fnc_string, 1}, - {"DEFAULT_DELAY", ducky_fnc_defdelay, -1}, - {"DEFAULTDELAY", ducky_fnc_defdelay, -1}, - {"STRINGDELAY", ducky_fnc_strdelay, -1}, - {"STRING_DELAY", ducky_fnc_strdelay, -1}, - {"DEFAULT_STRING_DELAY", ducky_fnc_defstrdelay, -1}, - {"DEFAULTSTRINGDELAY", ducky_fnc_defstrdelay, -1}, - {"REPEAT", ducky_fnc_repeat, -1}, - {"SYSRQ", ducky_fnc_sysrq, -1}, - {"ALTCHAR", ducky_fnc_altchar, -1}, - {"ALTSTRING", ducky_fnc_altstring, -1}, - {"ALTCODE", ducky_fnc_altstring, -1}, - {"HOLD", ducky_fnc_hold, -1}, - {"RELEASE", ducky_fnc_release, -1}, - {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1}, - {"MEDIA", ducky_fnc_media, -1}, - {"GLOBE", ducky_fnc_globe, -1}, -}; - -#define TAG "BadBle" - -#define WORKER_TAG TAG "Worker" - -int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line) { - size_t cmd_word_len = strcspn(line, " "); - for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) { - size_t cmd_compare_len = strlen(ducky_commands[i].name); - - if(cmd_compare_len != cmd_word_len) { - continue; - } - - if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) { - if(ducky_commands[i].callback == NULL) { - return 0; - } else { - return (ducky_commands[i].callback)(bad_usb, line, ducky_commands[i].param); - } - } - } - - return SCRIPT_STATE_CMD_UNKNOWN; -} diff --git a/applications/system/bad_ble/helpers/ducky_script_i.h b/applications/system/bad_ble/helpers/ducky_script_i.h deleted file mode 100644 index a5581d20655..00000000000 --- a/applications/system/bad_ble/helpers/ducky_script_i.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include "ducky_script.h" -#include "bad_ble_hid.h" - -#define SCRIPT_STATE_ERROR (-1) -#define SCRIPT_STATE_END (-2) -#define SCRIPT_STATE_NEXT_LINE (-3) -#define SCRIPT_STATE_CMD_UNKNOWN (-4) -#define SCRIPT_STATE_STRING_START (-5) -#define SCRIPT_STATE_WAIT_FOR_BTN (-6) - -#define FILE_BUFFER_LEN 16 - -struct BadBleScript { - FuriHalUsbHidConfig hid_cfg; - const BadBleHidApi* hid; - void* hid_inst; - FuriThread* thread; - BadBleState st; - - FuriString* file_path; - uint8_t file_buf[FILE_BUFFER_LEN + 1]; - uint8_t buf_start; - uint8_t buf_len; - bool file_end; - - uint32_t defdelay; - uint32_t stringdelay; - uint32_t defstringdelay; - uint16_t layout[128]; - - FuriString* line; - FuriString* line_prev; - uint32_t repeat_cnt; - uint8_t key_hold_nb; - - FuriString* string_print; - size_t string_print_pos; -}; - -uint16_t ducky_get_keycode(BadBleScript* bad_usb, const char* param, bool accept_chars); - -uint32_t ducky_get_command_len(const char* line); - -bool ducky_is_line_end(const char chr); - -uint16_t ducky_get_keycode_by_name(const char* param); - -uint16_t ducky_get_media_keycode_by_name(const char* param); - -bool ducky_get_number(const char* param, uint32_t* val); - -void ducky_numlock_on(BadBleScript* bad_usb); - -bool ducky_numpad_press(BadBleScript* bad_usb, const char num); - -bool ducky_altchar(BadBleScript* bad_usb, const char* charcode); - -bool ducky_altstring(BadBleScript* bad_usb, const char* param); - -bool ducky_string(BadBleScript* bad_usb, const char* param); - -int32_t ducky_execute_cmd(BadBleScript* bad_usb, const char* line); - -int32_t ducky_error(BadBleScript* bad_usb, const char* text, ...); - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/bad_ble/helpers/ducky_script_keycodes.c b/applications/system/bad_ble/helpers/ducky_script_keycodes.c deleted file mode 100644 index 290618c131b..00000000000 --- a/applications/system/bad_ble/helpers/ducky_script_keycodes.c +++ /dev/null @@ -1,133 +0,0 @@ -#include -#include "ducky_script_i.h" - -typedef struct { - char* name; - uint16_t keycode; -} DuckyKey; - -static const DuckyKey ducky_keys[] = { - {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT}, - {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT}, - {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT}, - {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI}, - {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT}, - {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL}, - - {"CTRL", KEY_MOD_LEFT_CTRL}, - {"CONTROL", KEY_MOD_LEFT_CTRL}, - {"SHIFT", KEY_MOD_LEFT_SHIFT}, - {"ALT", KEY_MOD_LEFT_ALT}, - {"GUI", KEY_MOD_LEFT_GUI}, - {"WINDOWS", KEY_MOD_LEFT_GUI}, - - {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW}, - {"DOWN", HID_KEYBOARD_DOWN_ARROW}, - {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW}, - {"LEFT", HID_KEYBOARD_LEFT_ARROW}, - {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW}, - {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, - {"UPARROW", HID_KEYBOARD_UP_ARROW}, - {"UP", HID_KEYBOARD_UP_ARROW}, - - {"ENTER", HID_KEYBOARD_RETURN}, - {"BREAK", HID_KEYBOARD_PAUSE}, - {"PAUSE", HID_KEYBOARD_PAUSE}, - {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, - {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, - {"BACKSPACE", HID_KEYBOARD_DELETE}, - {"END", HID_KEYBOARD_END}, - {"ESC", HID_KEYBOARD_ESCAPE}, - {"ESCAPE", HID_KEYBOARD_ESCAPE}, - {"HOME", HID_KEYBOARD_HOME}, - {"INSERT", HID_KEYBOARD_INSERT}, - {"NUMLOCK", HID_KEYPAD_NUMLOCK}, - {"PAGEUP", HID_KEYBOARD_PAGE_UP}, - {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, - {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, - {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, - {"SPACE", HID_KEYBOARD_SPACEBAR}, - {"TAB", HID_KEYBOARD_TAB}, - {"MENU", HID_KEYBOARD_APPLICATION}, - {"APP", HID_KEYBOARD_APPLICATION}, - - {"F1", HID_KEYBOARD_F1}, - {"F2", HID_KEYBOARD_F2}, - {"F3", HID_KEYBOARD_F3}, - {"F4", HID_KEYBOARD_F4}, - {"F5", HID_KEYBOARD_F5}, - {"F6", HID_KEYBOARD_F6}, - {"F7", HID_KEYBOARD_F7}, - {"F8", HID_KEYBOARD_F8}, - {"F9", HID_KEYBOARD_F9}, - {"F10", HID_KEYBOARD_F10}, - {"F11", HID_KEYBOARD_F11}, - {"F12", HID_KEYBOARD_F12}, - {"F13", HID_KEYBOARD_F13}, - {"F14", HID_KEYBOARD_F14}, - {"F15", HID_KEYBOARD_F15}, - {"F16", HID_KEYBOARD_F16}, - {"F17", HID_KEYBOARD_F17}, - {"F18", HID_KEYBOARD_F18}, - {"F19", HID_KEYBOARD_F19}, - {"F20", HID_KEYBOARD_F20}, - {"F21", HID_KEYBOARD_F21}, - {"F22", HID_KEYBOARD_F22}, - {"F23", HID_KEYBOARD_F23}, - {"F24", HID_KEYBOARD_F24}, -}; - -static const DuckyKey ducky_media_keys[] = { - {"POWER", HID_CONSUMER_POWER}, - {"REBOOT", HID_CONSUMER_RESET}, - {"SLEEP", HID_CONSUMER_SLEEP}, - {"LOGOFF", HID_CONSUMER_AL_LOGOFF}, - - {"EXIT", HID_CONSUMER_AC_EXIT}, - {"HOME", HID_CONSUMER_AC_HOME}, - {"BACK", HID_CONSUMER_AC_BACK}, - {"FORWARD", HID_CONSUMER_AC_FORWARD}, - {"REFRESH", HID_CONSUMER_AC_REFRESH}, - - {"SNAPSHOT", HID_CONSUMER_SNAPSHOT}, - - {"PLAY", HID_CONSUMER_PLAY}, - {"PAUSE", HID_CONSUMER_PAUSE}, - {"PLAY_PAUSE", HID_CONSUMER_PLAY_PAUSE}, - {"NEXT_TRACK", HID_CONSUMER_SCAN_NEXT_TRACK}, - {"PREV_TRACK", HID_CONSUMER_SCAN_PREVIOUS_TRACK}, - {"STOP", HID_CONSUMER_STOP}, - {"EJECT", HID_CONSUMER_EJECT}, - - {"MUTE", HID_CONSUMER_MUTE}, - {"VOLUME_UP", HID_CONSUMER_VOLUME_INCREMENT}, - {"VOLUME_DOWN", HID_CONSUMER_VOLUME_DECREMENT}, - - {"FN", HID_CONSUMER_FN_GLOBE}, - {"BRIGHT_UP", HID_CONSUMER_BRIGHTNESS_INCREMENT}, - {"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT}, -}; - -uint16_t ducky_get_keycode_by_name(const char* param) { - for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { - size_t key_cmd_len = strlen(ducky_keys[i].name); - if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) && - (ducky_is_line_end(param[key_cmd_len]))) { - return ducky_keys[i].keycode; - } - } - - return HID_KEYBOARD_NONE; -} - -uint16_t ducky_get_media_keycode_by_name(const char* param) { - for(size_t i = 0; i < COUNT_OF(ducky_media_keys); i++) { - size_t key_cmd_len = strlen(ducky_media_keys[i].name); - if((strncmp(param, ducky_media_keys[i].name, key_cmd_len) == 0) && - (ducky_is_line_end(param[key_cmd_len]))) { - return ducky_media_keys[i].keycode; - } - } - - return HID_CONSUMER_UNASSIGNED; -} diff --git a/applications/system/bad_ble/icon.png b/applications/system/bad_ble/icon.png deleted file mode 100644 index 27355f8dbab9f62f03c3114bd345117b72703df2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xkYHHq`AGmsse8IOhE&W+{!>5Ur5*F)|L$Vj tulr2n@!{d|IW8GdR-3Ztdxz&lMuxeII5)+8P-F*b^>p=fS?83{1OR(_90~vc diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.c b/applications/system/bad_ble/scenes/bad_ble_scene.c deleted file mode 100644 index 351bb1e7945..00000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "bad_ble_scene.h" - -// Generate scene on_enter handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, -void (*const bad_ble_scene_on_enter_handlers[])(void*) = { -#include "bad_ble_scene_config.h" -}; -#undef ADD_SCENE - -// Generate scene on_event handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, -bool (*const bad_ble_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { -#include "bad_ble_scene_config.h" -}; -#undef ADD_SCENE - -// Generate scene on_exit handlers array -#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, -void (*const bad_ble_scene_on_exit_handlers[])(void* context) = { -#include "bad_ble_scene_config.h" -}; -#undef ADD_SCENE - -// Initialize scene handlers configuration structure -const SceneManagerHandlers bad_ble_scene_handlers = { - .on_enter_handlers = bad_ble_scene_on_enter_handlers, - .on_event_handlers = bad_ble_scene_on_event_handlers, - .on_exit_handlers = bad_ble_scene_on_exit_handlers, - .scene_num = BadBleSceneNum, -}; diff --git a/applications/system/bad_ble/scenes/bad_ble_scene.h b/applications/system/bad_ble/scenes/bad_ble_scene.h deleted file mode 100644 index 25b19fc4b55..00000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -// Generate scene id and total number -#define ADD_SCENE(prefix, name, id) BadBleScene##id, -typedef enum { -#include "bad_ble_scene_config.h" - BadBleSceneNum, -} BadBleScene; -#undef ADD_SCENE - -extern const SceneManagerHandlers bad_ble_scene_handlers; - -// Generate scene on_enter handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); -#include "bad_ble_scene_config.h" -#undef ADD_SCENE - -// Generate scene on_event handlers declaration -#define ADD_SCENE(prefix, name, id) \ - bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); -#include "bad_ble_scene_config.h" -#undef ADD_SCENE - -// Generate scene on_exit handlers declaration -#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); -#include "bad_ble_scene_config.h" -#undef ADD_SCENE diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.c b/applications/system/bad_ble/scenes/bad_ble_scene_config.c deleted file mode 100644 index 1f64f19039d..00000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_config.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "../bad_ble_app_i.h" - -enum SubmenuIndex { - ConfigIndexKeyboardLayout, - ConfigIndexBleUnpair, -}; - -void bad_ble_scene_config_select_callback(void* context, uint32_t index) { - BadBleApp* bad_ble = context; - - view_dispatcher_send_custom_event(bad_ble->view_dispatcher, index); -} - -static void draw_menu(BadBleApp* bad_ble) { - VariableItemList* var_item_list = bad_ble->var_item_list; - - variable_item_list_reset(var_item_list); - - variable_item_list_add(var_item_list, "Keyboard Layout (Global)", 0, NULL, NULL); - - variable_item_list_add(var_item_list, "Unpair Device", 0, NULL, NULL); -} - -void bad_ble_scene_config_on_enter(void* context) { - BadBleApp* bad_ble = context; - VariableItemList* var_item_list = bad_ble->var_item_list; - - variable_item_list_set_enter_callback( - var_item_list, bad_ble_scene_config_select_callback, bad_ble); - draw_menu(bad_ble); - variable_item_list_set_selected_item(var_item_list, 0); - - view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewConfig); -} - -bool bad_ble_scene_config_on_event(void* context, SceneManagerEvent event) { - BadBleApp* bad_ble = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == ConfigIndexKeyboardLayout) { - scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfigLayout); - } else if(event.event == ConfigIndexBleUnpair) { - scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneConfirmUnpair); - } else { - furi_crash("Unknown key type"); - } - } - - return consumed; -} - -void bad_ble_scene_config_on_exit(void* context) { - BadBleApp* bad_ble = context; - VariableItemList* var_item_list = bad_ble->var_item_list; - - variable_item_list_reset(var_item_list); -} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config.h b/applications/system/bad_ble/scenes/bad_ble_scene_config.h deleted file mode 100644 index 5675fca59b7..00000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_config.h +++ /dev/null @@ -1,7 +0,0 @@ -ADD_SCENE(bad_ble, file_select, FileSelect) -ADD_SCENE(bad_ble, work, Work) -ADD_SCENE(bad_ble, error, Error) -ADD_SCENE(bad_ble, config, Config) -ADD_SCENE(bad_ble, config_layout, ConfigLayout) -ADD_SCENE(bad_ble, confirm_unpair, ConfirmUnpair) -ADD_SCENE(bad_ble, unpair_done, UnpairDone) diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c b/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c deleted file mode 100644 index 594525dd7b7..00000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_config_layout.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "../bad_ble_app_i.h" -#include - -static bool bad_ble_layout_select(BadBleApp* bad_ble) { - furi_assert(bad_ble); - - FuriString* predefined_path; - predefined_path = furi_string_alloc(); - if(!furi_string_empty(bad_ble->keyboard_layout)) { - furi_string_set(predefined_path, bad_ble->keyboard_layout); - } else { - furi_string_set(predefined_path, BAD_BLE_APP_PATH_LAYOUT_FOLDER); - } - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options( - &browser_options, BAD_BLE_APP_LAYOUT_EXTENSION, &I_keyboard_10px); - browser_options.base_path = BAD_BLE_APP_PATH_LAYOUT_FOLDER; - browser_options.skip_assets = false; - - // Input events and views are managed by file_browser - bool res = dialog_file_browser_show( - bad_ble->dialogs, bad_ble->keyboard_layout, predefined_path, &browser_options); - - furi_string_free(predefined_path); - return res; -} - -void bad_ble_scene_config_layout_on_enter(void* context) { - BadBleApp* bad_ble = context; - - if(bad_ble_layout_select(bad_ble)) { - scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneWork); - } else { - scene_manager_previous_scene(bad_ble->scene_manager); - } -} - -bool bad_ble_scene_config_layout_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - // BadBleApp* bad_ble = context; - return false; -} - -void bad_ble_scene_config_layout_on_exit(void* context) { - UNUSED(context); - // BadBleApp* bad_ble = context; -} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_error.c b/applications/system/bad_ble/scenes/bad_ble_scene_error.c deleted file mode 100644 index c9c2b12da2b..00000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_error.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "../bad_ble_app_i.h" - -typedef enum { - BadBleCustomEventErrorBack, -} BadBleCustomEvent; - -static void - bad_ble_scene_error_event_callback(GuiButtonType result, InputType type, void* context) { - furi_assert(context); - BadBleApp* app = context; - - if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { - view_dispatcher_send_custom_event(app->view_dispatcher, BadBleCustomEventErrorBack); - } -} - -void bad_ble_scene_error_on_enter(void* context) { - BadBleApp* app = context; - - if(app->error == BadBleAppErrorNoFiles) { - widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43); - widget_add_string_multiline_element( - app->widget, - 81, - 4, - AlignCenter, - AlignTop, - FontSecondary, - "No SD card or\napp data found.\nThis app will not\nwork without\nrequired files."); - widget_add_button_element( - app->widget, GuiButtonTypeLeft, "Back", bad_ble_scene_error_event_callback, app); - } else if(app->error == BadBleAppErrorCloseRpc) { - widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); - widget_add_string_multiline_element( - app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nIs Active!"); - widget_add_string_multiline_element( - app->widget, - 3, - 30, - AlignLeft, - AlignTop, - FontSecondary, - "Disconnect from\nPC or phone to\nuse this function."); - } - - view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWidget); -} - -bool bad_ble_scene_error_on_event(void* context, SceneManagerEvent event) { - BadBleApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == BadBleCustomEventErrorBack) { - view_dispatcher_stop(app->view_dispatcher); - consumed = true; - } - } - return consumed; -} - -void bad_ble_scene_error_on_exit(void* context) { - BadBleApp* app = context; - widget_reset(app->widget); -} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c b/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c deleted file mode 100644 index 2a182a874d2..00000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_file_select.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "../bad_ble_app_i.h" -#include -#include - -static bool bad_ble_file_select(BadBleApp* bad_ble) { - furi_assert(bad_ble); - - DialogsFileBrowserOptions browser_options; - dialog_file_browser_set_basic_options( - &browser_options, BAD_BLE_APP_SCRIPT_EXTENSION, &I_badusb_10px); - browser_options.base_path = BAD_BLE_APP_BASE_FOLDER; - browser_options.skip_assets = true; - - // Input events and views are managed by file_browser - bool res = dialog_file_browser_show( - bad_ble->dialogs, bad_ble->file_path, bad_ble->file_path, &browser_options); - - return res; -} - -void bad_ble_scene_file_select_on_enter(void* context) { - BadBleApp* bad_ble = context; - - if(bad_ble->bad_ble_script) { - bad_ble_script_close(bad_ble->bad_ble_script); - bad_ble->bad_ble_script = NULL; - } - - if(bad_ble_file_select(bad_ble)) { - scene_manager_next_scene(bad_ble->scene_manager, BadBleSceneWork); - } else { - view_dispatcher_stop(bad_ble->view_dispatcher); - } -} - -bool bad_ble_scene_file_select_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); - // BadBleApp* bad_ble = context; - return false; -} - -void bad_ble_scene_file_select_on_exit(void* context) { - UNUSED(context); - // BadBleApp* bad_ble = context; -} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c b/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c deleted file mode 100644 index 4c1fe3366b1..00000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_unpair_done.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "../bad_ble_app_i.h" - -static void bad_ble_scene_unpair_done_popup_callback(void* context) { - BadBleApp* bad_ble = context; - scene_manager_search_and_switch_to_previous_scene(bad_ble->scene_manager, BadBleSceneConfig); -} - -void bad_ble_scene_unpair_done_on_enter(void* context) { - BadBleApp* bad_ble = context; - Popup* popup = bad_ble->popup; - - popup_set_icon(popup, 48, 4, &I_DolphinDone_80x58); - popup_set_header(popup, "Done", 20, 19, AlignLeft, AlignBottom); - popup_set_callback(popup, bad_ble_scene_unpair_done_popup_callback); - popup_set_context(popup, bad_ble); - popup_set_timeout(popup, 1000); - popup_enable_timeout(popup); - - view_dispatcher_switch_to_view(bad_ble->view_dispatcher, BadBleAppViewPopup); -} - -bool bad_ble_scene_unpair_done_on_event(void* context, SceneManagerEvent event) { - BadBleApp* bad_ble = context; - UNUSED(bad_ble); - UNUSED(event); - - bool consumed = false; - - return consumed; -} - -void bad_ble_scene_unpair_done_on_exit(void* context) { - BadBleApp* bad_ble = context; - Popup* popup = bad_ble->popup; - - popup_reset(popup); -} diff --git a/applications/system/bad_ble/scenes/bad_ble_scene_work.c b/applications/system/bad_ble/scenes/bad_ble_scene_work.c deleted file mode 100644 index ff71edc3c21..00000000000 --- a/applications/system/bad_ble/scenes/bad_ble_scene_work.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "../helpers/ducky_script.h" -#include "../bad_ble_app_i.h" -#include "../views/bad_ble_view.h" -#include -#include "toolbox/path.h" - -void bad_ble_scene_work_button_callback(InputKey key, void* context) { - furi_assert(context); - BadBleApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, key); -} - -bool bad_ble_scene_work_on_event(void* context, SceneManagerEvent event) { - BadBleApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == InputKeyLeft) { - if(bad_ble_view_is_idle_state(app->bad_ble_view)) { - bad_ble_script_close(app->bad_ble_script); - app->bad_ble_script = NULL; - - scene_manager_next_scene(app->scene_manager, BadBleSceneConfig); - } - consumed = true; - } else if(event.event == InputKeyOk) { - bad_ble_script_start_stop(app->bad_ble_script); - consumed = true; - } else if(event.event == InputKeyRight) { - bad_ble_script_pause_resume(app->bad_ble_script); - consumed = true; - } - } else if(event.type == SceneManagerEventTypeTick) { - bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); - } - return consumed; -} - -void bad_ble_scene_work_on_enter(void* context) { - BadBleApp* app = context; - - app->bad_ble_script = bad_ble_script_open(app->file_path, app->interface); - bad_ble_script_set_keyboard_layout(app->bad_ble_script, app->keyboard_layout); - - FuriString* file_name; - file_name = furi_string_alloc(); - path_extract_filename(app->file_path, file_name, true); - bad_ble_view_set_file_name(app->bad_ble_view, furi_string_get_cstr(file_name)); - furi_string_free(file_name); - - FuriString* layout; - layout = furi_string_alloc(); - path_extract_filename(app->keyboard_layout, layout, true); - bad_ble_view_set_layout(app->bad_ble_view, furi_string_get_cstr(layout)); - furi_string_free(layout); - - bad_ble_view_set_state(app->bad_ble_view, bad_ble_script_get_state(app->bad_ble_script)); - - bad_ble_view_set_button_callback(app->bad_ble_view, bad_ble_scene_work_button_callback, app); - view_dispatcher_switch_to_view(app->view_dispatcher, BadBleAppViewWork); -} - -void bad_ble_scene_work_on_exit(void* context) { - UNUSED(context); -} diff --git a/applications/system/bad_ble/views/bad_ble_view.c b/applications/system/bad_ble/views/bad_ble_view.c deleted file mode 100644 index 28f935733ef..00000000000 --- a/applications/system/bad_ble/views/bad_ble_view.c +++ /dev/null @@ -1,284 +0,0 @@ -#include "bad_ble_view.h" -#include "../helpers/ducky_script.h" -#include -#include -#include -#include "bad_ble_icons.h" - -#define MAX_NAME_LEN 64 - -struct BadBle { - View* view; - BadBleButtonCallback callback; - void* context; -}; - -typedef struct { - char file_name[MAX_NAME_LEN]; - char layout[MAX_NAME_LEN]; - BadBleState state; - bool pause_wait; - uint8_t anim_frame; -} BadBleModel; - -static void bad_ble_draw_callback(Canvas* canvas, void* _model) { - BadBleModel* model = _model; - - FuriString* disp_str; - disp_str = furi_string_alloc_set(model->file_name); - elements_string_fit_width(canvas, disp_str, 128 - 2); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str)); - - if(strlen(model->layout) == 0) { - furi_string_set(disp_str, "(default)"); - } else { - furi_string_printf(disp_str, "(%s)", model->layout); - } - elements_string_fit_width(canvas, disp_str, 128 - 2); - canvas_draw_str( - canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str)); - - furi_string_reset(disp_str); - - canvas_draw_icon(canvas, 22, 24, &I_Bad_BLE_48x22); - - BadBleWorkerState state = model->state.state; - - if((state == BadBleStateIdle) || (state == BadBleStateDone) || - (state == BadBleStateNotConnected)) { - elements_button_center(canvas, "Run"); - elements_button_left(canvas, "Config"); - } else if((state == BadBleStateRunning) || (state == BadBleStateDelay)) { - elements_button_center(canvas, "Stop"); - if(!model->pause_wait) { - elements_button_right(canvas, "Pause"); - } - } else if(state == BadBleStatePaused) { - elements_button_center(canvas, "End"); - elements_button_right(canvas, "Resume"); - } else if(state == BadBleStateWaitForBtn) { - elements_button_center(canvas, "Press to continue"); - } else if(state == BadBleStateWillRun) { - elements_button_center(canvas, "Cancel"); - } - - if(state == BadBleStateNotConnected) { - canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect"); - canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "to device"); - } else if(state == BadBleStateWillRun) { - canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run"); - canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect"); - } else if(state == BadBleStateFileError) { - canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File"); - canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR"); - } else if(state == BadBleStateScriptError) { - canvas_draw_icon(canvas, 4, 26, &I_Error_18x18); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); - canvas_set_font(canvas, FontSecondary); - furi_string_printf(disp_str, "line %zu", model->state.error_line); - canvas_draw_str_aligned( - canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - - furi_string_set_str(disp_str, model->state.error); - elements_string_fit_width(canvas, disp_str, canvas_width(canvas)); - canvas_draw_str_aligned( - canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - } else if(state == BadBleStateIdle) { - canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18); - canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0"); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - } else if(state == BadBleStateRunning) { - if(model->anim_frame == 0) { - canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); - } else { - canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21); - } - canvas_set_font(canvas, FontBigNumbers); - furi_string_printf( - disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); - canvas_draw_str_aligned( - canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - } else if(state == BadBleStateDone) { - canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21); - canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100"); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - } else if(state == BadBleStateDelay) { - if(model->anim_frame == 0) { - canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); - } else { - canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); - } - canvas_set_font(canvas, FontBigNumbers); - furi_string_printf( - disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); - canvas_draw_str_aligned( - canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - canvas_set_font(canvas, FontSecondary); - furi_string_printf(disp_str, "delay %lus", model->state.delay_remain); - canvas_draw_str_aligned( - canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - } else if((state == BadBleStatePaused) || (state == BadBleStateWaitForBtn)) { - if(model->anim_frame == 0) { - canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21); - } else { - canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21); - } - canvas_set_font(canvas, FontBigNumbers); - furi_string_printf( - disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb); - canvas_draw_str_aligned( - canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); - furi_string_reset(disp_str); - canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 127, 50, AlignRight, AlignBottom, "Paused"); - furi_string_reset(disp_str); - } else { - canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18); - } - - furi_string_free(disp_str); -} - -static bool bad_ble_input_callback(InputEvent* event, void* context) { - furi_assert(context); - BadBle* bad_ble = context; - bool consumed = false; - - if(event->type == InputTypeShort) { - if(event->key == InputKeyLeft) { - consumed = true; - furi_assert(bad_ble->callback); - bad_ble->callback(event->key, bad_ble->context); - } else if(event->key == InputKeyOk) { - with_view_model( - bad_ble->view, BadBleModel * model, { model->pause_wait = false; }, true); - consumed = true; - furi_assert(bad_ble->callback); - bad_ble->callback(event->key, bad_ble->context); - } else if(event->key == InputKeyRight) { - with_view_model( - bad_ble->view, - BadBleModel * model, - { - if((model->state.state == BadBleStateRunning) || - (model->state.state == BadBleStateDelay)) { - model->pause_wait = true; - } - }, - true); - consumed = true; - furi_assert(bad_ble->callback); - bad_ble->callback(event->key, bad_ble->context); - } - } - - return consumed; -} - -BadBle* bad_ble_view_alloc(void) { - BadBle* bad_ble = malloc(sizeof(BadBle)); - - bad_ble->view = view_alloc(); - view_allocate_model(bad_ble->view, ViewModelTypeLocking, sizeof(BadBleModel)); - view_set_context(bad_ble->view, bad_ble); - view_set_draw_callback(bad_ble->view, bad_ble_draw_callback); - view_set_input_callback(bad_ble->view, bad_ble_input_callback); - - return bad_ble; -} - -void bad_ble_view_free(BadBle* bad_ble) { - furi_assert(bad_ble); - view_free(bad_ble->view); - free(bad_ble); -} - -View* bad_ble_view_get_view(BadBle* bad_ble) { - furi_assert(bad_ble); - return bad_ble->view; -} - -void bad_ble_view_set_button_callback( - BadBle* bad_ble, - BadBleButtonCallback callback, - void* context) { - furi_assert(bad_ble); - furi_assert(callback); - with_view_model( - bad_ble->view, - BadBleModel * model, - { - UNUSED(model); - bad_ble->callback = callback; - bad_ble->context = context; - }, - true); -} - -void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name) { - furi_assert(name); - with_view_model( - bad_ble->view, - BadBleModel * model, - { strlcpy(model->file_name, name, MAX_NAME_LEN); }, - true); -} - -void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout) { - furi_assert(layout); - with_view_model( - bad_ble->view, - BadBleModel * model, - { strlcpy(model->layout, layout, MAX_NAME_LEN); }, - true); -} - -void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st) { - furi_assert(st); - with_view_model( - bad_ble->view, - BadBleModel * model, - { - memcpy(&(model->state), st, sizeof(BadBleState)); - model->anim_frame ^= 1; - if(model->state.state == BadBleStatePaused) { - model->pause_wait = false; - } - }, - true); -} - -bool bad_ble_view_is_idle_state(BadBle* bad_ble) { - bool is_idle = false; - with_view_model( - bad_ble->view, - BadBleModel * model, - { - if((model->state.state == BadBleStateIdle) || - (model->state.state == BadBleStateDone) || - (model->state.state == BadBleStateNotConnected)) { - is_idle = true; - } - }, - false); - return is_idle; -} diff --git a/applications/system/bad_ble/views/bad_ble_view.h b/applications/system/bad_ble/views/bad_ble_view.h deleted file mode 100644 index e26488818e4..00000000000 --- a/applications/system/bad_ble/views/bad_ble_view.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include "../helpers/ducky_script.h" - -typedef struct BadBle BadBle; -typedef void (*BadBleButtonCallback)(InputKey key, void* context); - -BadBle* bad_ble_view_alloc(void); - -void bad_ble_view_free(BadBle* bad_ble); - -View* bad_ble_view_get_view(BadBle* bad_ble); - -void bad_ble_view_set_button_callback( - BadBle* bad_ble, - BadBleButtonCallback callback, - void* context); - -void bad_ble_view_set_file_name(BadBle* bad_ble, const char* name); - -void bad_ble_view_set_layout(BadBle* bad_ble, const char* layout); - -void bad_ble_view_set_state(BadBle* bad_ble, BadBleState* st); - -bool bad_ble_view_is_idle_state(BadBle* bad_ble); diff --git a/applications/system/bad_ble/assets/Bad_BLE_48x22.png b/assets/icons/BadUsb/Bad_BLE_48x22.png similarity index 100% rename from applications/system/bad_ble/assets/Bad_BLE_48x22.png rename to assets/icons/BadUsb/Bad_BLE_48x22.png diff --git a/furi/core/timer.c b/furi/core/timer.c index ddd82e33194..fe3f3db17fa 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -119,8 +119,6 @@ FuriStatus furi_timer_stop(FuriTimer* instance) { furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); - furi_timer_flush(); - return FuriStatusOk; } From dc9548d0dc9ab7c2813707eb6ab47d8e23f06da4 Mon Sep 17 00:00:00 2001 From: Zinong Li <131403964+zinongli@users.noreply.github.com> Date: Wed, 16 Oct 2024 01:17:33 +0800 Subject: [PATCH 30/36] New Static Keys for Mifare Classic Dictionary (#3947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../main/nfc/resources/nfc/assets/mf_classic_dict.nfc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc index 25f750102f7..e2b74f8475d 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc @@ -1351,3 +1351,8 @@ E69DD9015A43 C8382A233993 7B304F2A12A6 FC9418BF788B + +# H World Hotel Chain Room Keys +543071543071 +5F01015F0101 +200510241234 From c917135c9488ee58947d797c44c6bee182825c10 Mon Sep 17 00:00:00 2001 From: Kowalski Dragon Date: Tue, 15 Oct 2024 19:23:49 +0200 Subject: [PATCH 31/36] [BadUSB] Improve ChromeOS and GNOME demo scripts (#3948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BadUSB] Gnome Demo: Support most terminals and force sh shell when not using Bash as default * [BadUSB] ChromeOS Demo: Minor improvements, such as exit overview, select omnibox and add a page title Signed-off-by: Kowalski Dragon (kowalski7cc) <5065094+kowalski7cc@users.noreply.github.com> Co-authored-by: Kowalski Dragon (kowalski7cc) <5065094+kowalski7cc@users.noreply.github.com> Co-authored-by: あく --- .../bad_usb/resources/badusb/demo_chromeos.txt | 9 +++++++-- .../bad_usb/resources/badusb/demo_gnome.txt | 18 +++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/applications/main/bad_usb/resources/badusb/demo_chromeos.txt b/applications/main/bad_usb/resources/badusb/demo_chromeos.txt index c5f675fb338..7f42574ce10 100644 --- a/applications/main/bad_usb/resources/badusb/demo_chromeos.txt +++ b/applications/main/bad_usb/resources/badusb/demo_chromeos.txt @@ -1,12 +1,17 @@ -REM This is BadUSB demo script for ChromeOS by kowalski7cc +REM This is BadUSB demo script for Chrome and ChromeOS by kowalski7cc +REM Exit from Overview +ESC REM Open a new tab CTRL t REM wait for some slower chromebooks DELAY 1000 +REM Make sure we have omnibox focus +CTRL l +DELAY 200 REM Open an empty editable page DEFAULT_DELAY 50 -STRING data:text/html,