Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
noproto
2025-08-24 22:55:51 -04:00
583 changed files with 14421 additions and 4617 deletions
+52 -50
View File
@@ -1,69 +1,71 @@
# Who owns all the fish by default
* @skotopes @DrZlo13 @hedger @gsurkov
* @DrZlo13 @hedger @gsurkov
# Apps
/applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
/applications/debug/accessor/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/battery_test_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
/applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
/applications/debug/file_browser_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/lfrfid_debug/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/text_box_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/uart_echo/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/usb_mouse/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/usb_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/bt_debug_app/ @DrZlo13 @hedger @gsurkov @gornekich
/applications/debug/accessor/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/battery_test_app/ @DrZlo13 @hedger @gsurkov @gornekich
/applications/debug/bt_debug_app/ @DrZlo13 @hedger @gsurkov @gornekich
/applications/debug/file_browser_test/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/lfrfid_debug/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/text_box_test/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/uart_echo/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/usb_mouse/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/debug/usb_test/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/main/archive/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/main/bad_usb/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/main/gpio/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/main/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov
/applications/main/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra
/applications/main/subghz/ @skotopes @DrZlo13 @hedger @gsurkov @Skorpionm
/applications/main/u2f/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/main/archive/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/main/bad_usb/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/main/gpio/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/main/ibutton/ @DrZlo13 @hedger @gsurkov
/applications/main/infrared/ @DrZlo13 @hedger @gsurkov
/applications/main/nfc/ @DrZlo13 @hedger @gsurkov @gornekich
/applications/main/subghz/ @DrZlo13 @hedger @gsurkov @Skorpionm
/applications/main/u2f/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/bt/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
/applications/services/cli/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/crypto/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/desktop/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/dolphin/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/power/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
/applications/services/rpc/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/bt/ @DrZlo13 @hedger @gsurkov @gornekich
/applications/services/cli/ @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3
/applications/services/crypto/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/desktop/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/dolphin/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/power/ @DrZlo13 @hedger @gsurkov @gornekich
/applications/services/rpc/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/bt_settings_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
/applications/services/desktop_settings/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/dolphin_passport/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/power_settings_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
/applications/services/bt_settings_app/ @DrZlo13 @hedger @gsurkov @gornekich
/applications/services/desktop_settings/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/dolphin_passport/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/services/power_settings_app/ @DrZlo13 @hedger @gsurkov @gornekich
/applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/applications/system/js_app/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3
/applications/system/storage_move_to_sd/ @DrZlo13 @hedger @gsurkov @nminaylov
/applications/system/js_app/ @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3
/applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @gornekich @Astrrra @Skorpionm
/applications/debug/unit_tests/ @DrZlo13 @hedger @gsurkov @nminaylov @gornekich @Skorpionm
/applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov
/applications/examples/example_thermo/ @DrZlo13 @hedger @gsurkov
# Firmware targets
/targets/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/targets/ @DrZlo13 @hedger @gsurkov @nminaylov
# Assets
/applications/main/infrared/resources/ @skotopes @DrZlo13 @hedger @gsurkov
/applications/main/infrared/resources/ @DrZlo13 @hedger @gsurkov
# Documentation
/documentation/ @skotopes @DrZlo13 @hedger @gsurkov
/scripts/toolchain/ @skotopes @DrZlo13 @hedger @gsurkov
/documentation/ @DrZlo13 @hedger @gsurkov @portasynthinca3
/scripts/toolchain/ @DrZlo13 @hedger @gsurkov
# Lib
/lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
/lib/digital_signal/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich
/lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
/lib/lfrfid/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/lib/libusb_stm32/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/lib/mbedtls/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/lib/mjs/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3
/lib/nanopb/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov
/lib/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra
/lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov
/lib/subghz/ @skotopes @DrZlo13 @hedger @gsurkov @Skorpionm
/lib/stm32wb_copro/ @DrZlo13 @hedger @gsurkov @gornekich
/lib/digital_signal/ @DrZlo13 @hedger @gsurkov @gornekich
/lib/infrared/ @DrZlo13 @hedger @gsurkov
/lib/lfrfid/ @DrZlo13 @hedger @gsurkov @nminaylov
/lib/libusb_stm32/ @DrZlo13 @hedger @gsurkov @nminaylov
/lib/mbedtls/ @DrZlo13 @hedger @gsurkov @nminaylov
/lib/mjs/ @DrZlo13 @hedger @gsurkov @nminaylov @portasynthinca3
/lib/nanopb/ @DrZlo13 @hedger @gsurkov @nminaylov
/lib/nfc/ @DrZlo13 @hedger @gsurkov @gornekich
/lib/one_wire/ @DrZlo13 @hedger @gsurkov
/lib/subghz/ @DrZlo13 @hedger @gsurkov @Skorpionm
/lib/toolbox/ @DrZlo13 @hedger @gsurkov
/lib/toolbox/cli @DrZlo13 @hedger @gsurkov @portasynthinca3
# CI/CD
/.github/workflows/ @skotopes @DrZlo13 @hedger @gsurkov
/.github/workflows/ @DrZlo13 @hedger @gsurkov
+6 -8
View File
@@ -55,15 +55,13 @@ jobs:
fi
python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE"
- name: 'Generate documentation'
uses: mattnotmitt/doxygen-action@edge
env:
DOXY_SRC_ROOT: "${{ github.workspace }}"
DOXY_CONFIG_DIR: "${{ github.workspace }}/documentation/doxygen"
DOXY_OUTPUT_DIR: "${{ github.workspace }}/documentation/doxygen/build"
- name: install-doxygen
uses: AdarshRawat1/Install-Doxygen@v1.0
with:
working-directory: 'documentation/'
doxyfile-path: './doxygen/Doxyfile-awesome.cfg'
version: "1.12.0"
- name: 'Generate documentation'
run: ./fbt doxygen
- name: 'Upload documentation'
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' && needs.check-secret.outputs.s3-valid-config == 'true' }}
+3 -3
View File
@@ -21,7 +21,7 @@ jobs:
- name: 'Flash unit tests firmware'
id: flashing
if: success()
timeout-minutes: 20
timeout-minutes: 5
run: |
source scripts/toolchain/fbtenv.sh
./fbt resources firmware_latest flash LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1
@@ -30,7 +30,7 @@ jobs:
- name: 'Copy assets and unit data, reboot and wait for flipper'
id: copy
if: steps.flashing.outcome == 'success'
timeout-minutes: 7
timeout-minutes: 5
run: |
source scripts/toolchain/fbtenv.sh
python3 scripts/testops.py -t=15 await_flipper
@@ -42,7 +42,7 @@ jobs:
- name: 'Run units and validate results'
id: run_units
if: steps.copy.outcome == 'success'
timeout-minutes: 7
timeout-minutes: 5
run: |
source scripts/toolchain/fbtenv.sh
python3 scripts/testops.py run_units
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
- name: 'Flashing target firmware'
id: first_full_flash
timeout-minutes: 20
timeout-minutes: 5
run: |
source scripts/toolchain/fbtenv.sh
python3 scripts/testops.py -t=180 await_flipper
@@ -29,7 +29,7 @@ jobs:
- name: 'Validating updater'
id: second_full_flash
timeout-minutes: 10
timeout-minutes: 5
if: success()
run: |
source scripts/toolchain/fbtenv.sh
+4
View File
@@ -67,3 +67,7 @@ PVS-Studio.log
# JS packages
node_modules/
# cli_perf script output in case of errors
/block.bin
/return_block.bin
+1
View File
@@ -12,6 +12,7 @@
"SConstruct": "python",
"*.fam": "python"
},
"clangd.checkUpdates": false,
"clangd.path": "${workspaceFolder}/toolchain/current/bin/clangd@FBT_PLATFORM_EXECUTABLE_EXT@",
"clangd.arguments": [
"--query-driver=**/arm-none-eabi-*",
+15
View File
@@ -412,6 +412,21 @@ distenv.PhonyTarget(
],
)
# Measure CLI loopback performance
distenv.PhonyTarget(
"cli_perf",
[
[
"${PYTHON3}",
"${FBT_SCRIPT_DIR}/serial_cli_perf.py",
"-p",
"${FLIP_PORT}",
"${ARGS}",
]
],
)
# Update WiFi devboard firmware with release channel
distenv.PhonyTarget(
"devboard_flash",
+5 -2
View File
@@ -2,6 +2,7 @@
#include <furi.h>
#include <furi_hal.h>
#include <stdarg.h>
#include <power/power_service/power.h>
void AccessorApp::run(void) {
AccessorEvent event;
@@ -35,16 +36,18 @@ AccessorApp::AccessorApp()
: text_store{0} {
notification = static_cast<NotificationApp*>(furi_record_open(RECORD_NOTIFICATION));
expansion = static_cast<Expansion*>(furi_record_open(RECORD_EXPANSION));
power = static_cast<Power*>(furi_record_open(RECORD_POWER));
onewire_host = onewire_host_alloc(&gpio_ibutton);
expansion_disable(expansion);
furi_hal_power_enable_otg();
power_enable_otg(power, true);
}
AccessorApp::~AccessorApp() {
furi_hal_power_disable_otg();
power_enable_otg(power, false);
expansion_enable(expansion);
furi_record_close(RECORD_EXPANSION);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_POWER);
onewire_host_free(onewire_host);
}
@@ -7,6 +7,7 @@
#include <one_wire/one_wire_host.h>
#include <notification/notification_messages.h>
#include <expansion/expansion.h>
#include <power/power_service/power.h>
class AccessorApp {
public:
@@ -53,4 +54,5 @@ private:
NotificationApp* notification;
Expansion* expansion;
Power* power;
};
@@ -6,6 +6,5 @@ App(
entry_point="accessor_app",
requires=["gui"],
stack_size=4 * 1024,
order=40,
fap_category="Debug",
)
@@ -8,7 +8,6 @@ App(
"power",
],
stack_size=1 * 1024,
order=130,
fap_category="Debug",
fap_libs=["assets"],
)
@@ -5,6 +5,5 @@ App(
entry_point="blink_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=10,
fap_category="Debug",
)
@@ -13,6 +13,5 @@ App(
"bt_debug",
],
stack_size=1 * 1024,
order=110,
fap_category="Debug",
)
@@ -10,6 +10,5 @@ App(
"ccid_test",
],
stack_size=1 * 1024,
order=120,
fap_category="Debug",
)
@@ -24,8 +24,49 @@ typedef enum {
CrashTestSubmenuAssertMessage,
CrashTestSubmenuCrash,
CrashTestSubmenuHalt,
CrashTestSubmenuHeapUnderflow,
CrashTestSubmenuHeapOverflow,
} CrashTestSubmenu;
static void crash_test_corrupt_heap_underflow(void) {
const size_t block_size = 1000;
const size_t underflow_size = 123;
uint8_t* block = malloc(block_size);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow" // that's what we want!
memset(block - underflow_size, 0xDD, underflow_size); // -V769
#pragma GCC diagnostic pop
free(block); // should crash here (if compiled with DEBUG=1)
// If we got here, the heap wasn't able to detect our corruption and crash
furi_crash("Test failed, should've crashed with \"FreeRTOS Assert\" error");
}
static void crash_test_corrupt_heap_overflow(void) {
const size_t block_size = 1000;
const size_t overflow_size = 123;
uint8_t* block1 = malloc(block_size);
uint8_t* block2 = malloc(block_size);
memset(block2, 12, 34); // simulate use to avoid optimization // -V597 // -V1086
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow" // that's what we want!
memset(block1 + block_size, 0xDD, overflow_size); // -V769 // -V512
#pragma GCC diagnostic pop
uint8_t* block3 = malloc(block_size);
memset(block3, 12, 34); // simulate use to avoid optimization // -V597 // -V1086
free(block3); // should crash here (if compiled with DEBUG=1)
free(block2);
free(block1);
// If we got here, the heap wasn't able to detect our corruption and crash
furi_crash("Test failed, should've crashed with \"FreeRTOS Assert\" error");
}
static void crash_test_submenu_callback(void* context, uint32_t index) {
CrashTest* instance = (CrashTest*)context;
UNUSED(instance);
@@ -49,6 +90,12 @@ static void crash_test_submenu_callback(void* context, uint32_t index) {
case CrashTestSubmenuHalt:
furi_halt("Crash test: furi_halt");
break;
case CrashTestSubmenuHeapUnderflow:
crash_test_corrupt_heap_underflow();
break;
case CrashTestSubmenuHeapOverflow:
crash_test_corrupt_heap_overflow();
break;
default:
furi_crash();
}
@@ -94,6 +141,18 @@ CrashTest* crash_test_alloc(void) {
instance->submenu, "Crash", CrashTestSubmenuCrash, crash_test_submenu_callback, instance);
submenu_add_item(
instance->submenu, "Halt", CrashTestSubmenuHalt, crash_test_submenu_callback, instance);
submenu_add_item(
instance->submenu,
"Heap underflow",
CrashTestSubmenuHeapUnderflow,
crash_test_submenu_callback,
instance);
submenu_add_item(
instance->submenu,
"Heap overflow",
CrashTestSubmenuHeapOverflow,
crash_test_submenu_callback,
instance);
return instance;
}
@@ -5,6 +5,5 @@ App(
entry_point="direct_draw_app",
requires=["gui", "input"],
stack_size=2 * 1024,
order=70,
fap_category="Debug",
)
@@ -6,6 +6,5 @@ App(
requires=["gui"],
fap_libs=["u8g2"],
stack_size=1 * 1024,
order=120,
fap_category="Debug",
)
@@ -5,6 +5,5 @@ App(
entry_point="event_loop_blink_test_app",
requires=["input"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
)
@@ -6,7 +6,6 @@ App(
requires=["expansion_start"],
fap_libs=["assets"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
fap_file_assets="assets",
)
@@ -5,7 +5,6 @@ App(
entry_point="file_browser_app",
requires=["gui"],
stack_size=2 * 1024,
order=150,
fap_category="Debug",
fap_icon_assets="icons",
)
@@ -5,6 +5,5 @@ App(
entry_point="keypad_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=30,
fap_category="Debug",
)
@@ -11,6 +11,5 @@ App(
"lfrfid_debug",
],
stack_size=1 * 1024,
order=100,
fap_category="Debug",
)
@@ -0,0 +1,8 @@
App(
appid="loader_chaining_a",
name="Loader Chaining Test: App A",
apptype=FlipperAppType.DEBUG,
entry_point="chaining_test_app_a",
stack_size=1 * 1024,
fap_category="Debug",
)
@@ -0,0 +1,164 @@
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <loader/loader.h>
#include <dialogs/dialogs.h>
#define TAG "LoaderChainingA"
#define CHAINING_TEST_B "/ext/apps/Debug/loader_chaining_b.fap"
#define NONEXISTENT_APP "Some nonexistent app"
typedef struct {
Gui* gui;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
Loader* loader;
DialogsApp* dialogs;
} LoaderChainingA;
typedef enum {
LoaderChainingASubmenuLaunchB,
LoaderChainingASubmenuLaunchBThenA,
LoaderChainingASubmenuLaunchNonexistentSilent,
LoaderChainingASubmenuLaunchNonexistentGui,
LoaderChainingASubmenuLaunchNonexistentGuiThenA,
} LoaderChainingASubmenu;
static void loader_chaining_a_submenu_callback(void* context, uint32_t index) {
LoaderChainingA* app = context;
FuriString* self_path = furi_string_alloc();
furi_check(loader_get_application_launch_path(app->loader, self_path));
switch(index) {
case LoaderChainingASubmenuLaunchB:
loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui);
view_dispatcher_stop(app->view_dispatcher);
break;
case LoaderChainingASubmenuLaunchBThenA:
loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui);
loader_enqueue_launch(
app->loader,
furi_string_get_cstr(self_path),
"Hello to you from the future",
LoaderDeferredLaunchFlagGui);
break;
case LoaderChainingASubmenuLaunchNonexistentSilent:
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagNone);
break;
case LoaderChainingASubmenuLaunchNonexistentGui:
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui);
break;
case LoaderChainingASubmenuLaunchNonexistentGuiThenA:
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui);
loader_enqueue_launch(
app->loader,
furi_string_get_cstr(self_path),
"Hello to you from the future",
LoaderDeferredLaunchFlagGui);
break;
}
furi_string_free(self_path);
view_dispatcher_stop(app->view_dispatcher);
}
static bool loader_chaining_a_nav_callback(void* context) {
LoaderChainingA* app = context;
view_dispatcher_stop(app->view_dispatcher);
return true;
}
LoaderChainingA* loader_chaining_a_alloc(void) {
LoaderChainingA* app = malloc(sizeof(LoaderChainingA));
app->gui = furi_record_open(RECORD_GUI);
app->loader = furi_record_open(RECORD_LOADER);
app->dialogs = furi_record_open(RECORD_DIALOGS);
app->view_dispatcher = view_dispatcher_alloc();
app->submenu = submenu_alloc();
submenu_add_item(
app->submenu,
"Launch B",
LoaderChainingASubmenuLaunchB,
loader_chaining_a_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Launch B, then A",
LoaderChainingASubmenuLaunchBThenA,
loader_chaining_a_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Trigger error: silent",
LoaderChainingASubmenuLaunchNonexistentSilent,
loader_chaining_a_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Trigger error: GUI",
LoaderChainingASubmenuLaunchNonexistentGui,
loader_chaining_a_submenu_callback,
app);
submenu_add_item(
app->submenu,
"Error, then launch A",
LoaderChainingASubmenuLaunchNonexistentGuiThenA,
loader_chaining_a_submenu_callback,
app);
view_dispatcher_add_view(app->view_dispatcher, 0, submenu_get_view(app->submenu));
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, loader_chaining_a_nav_callback);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
return app;
}
void loader_chaining_a_free(LoaderChainingA* app) {
furi_record_close(RECORD_DIALOGS);
furi_record_close(RECORD_LOADER);
furi_record_close(RECORD_GUI);
view_dispatcher_remove_view(app->view_dispatcher, 0);
submenu_free(app->submenu);
view_dispatcher_free(app->view_dispatcher);
free(app);
}
int32_t chaining_test_app_a(const char* arg) {
LoaderChainingA* app = loader_chaining_a_alloc();
if(arg) {
if(strlen(arg)) {
DialogMessage* message = dialog_message_alloc();
FuriString* text;
dialog_message_set_header(message, "Hi, I am A", 64, 0, AlignCenter, AlignTop);
text = furi_string_alloc_printf("Me from the past says:\n%s", arg);
dialog_message_set_buttons(message, NULL, "ok!", NULL);
dialog_message_set_text(
message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter);
dialog_message_show(app->dialogs, message);
dialog_message_free(message);
furi_string_free(text);
}
}
view_dispatcher_run(app->view_dispatcher);
loader_chaining_a_free(app);
return 0;
}
@@ -0,0 +1,8 @@
App(
appid="loader_chaining_b",
name="Loader Chaining Test: App B",
apptype=FlipperAppType.DEBUG,
entry_point="chaining_test_app_b",
stack_size=1 * 1024,
fap_category="Debug",
)
@@ -0,0 +1,27 @@
#include <furi.h>
#include <dialogs/dialogs.h>
#include <loader/loader.h>
int32_t chaining_test_app_b(const char* arg) {
if(!arg) return 0;
Loader* loader = furi_record_open(RECORD_LOADER);
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* message = dialog_message_alloc();
dialog_message_set_header(message, "Hi, I am B", 64, 0, AlignCenter, AlignTop);
FuriString* text = furi_string_alloc_printf("And A told me:\n%s", arg);
dialog_message_set_text(message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter);
dialog_message_set_buttons(message, "Just quit", NULL, "Launch A");
DialogMessageButton result = dialog_message_show(dialogs, message);
dialog_message_free(message);
furi_string_free(text);
if(result == DialogMessageButtonRight)
loader_enqueue_launch(
loader, "/ext/apps/Debug/loader_chaining_a.fap", NULL, LoaderDeferredLaunchFlagGui);
furi_record_close(RECORD_LOADER);
furi_record_close(RECORD_DIALOGS);
return 0;
}
@@ -5,6 +5,5 @@ App(
entry_point="locale_test_app",
requires=["gui", "locale"],
stack_size=2 * 1024,
order=70,
fap_category="Debug",
)
@@ -5,6 +5,5 @@ App(
entry_point="rpc_debug_app",
requires=["gui", "rpc_start", "notification"],
stack_size=2 * 1024,
order=10,
fap_category="Debug",
)
@@ -5,7 +5,6 @@ App(
entry_point="speaker_debug_app",
requires=["gui", "notification"],
stack_size=2 * 1024,
order=10,
fap_category="Debug",
fap_libs=["music_worker"],
)
@@ -1,8 +1,10 @@
#include <furi.h>
#include <notification/notification.h>
#include <music_worker/music_worker.h>
#include <cli/cli.h>
#include <toolbox/args.h>
#include <toolbox/pipe.h>
#include <toolbox/cli/cli_registry.h>
#include <cli/cli_main_commands.h>
#define TAG "SpeakerDebug"
@@ -19,14 +21,14 @@ typedef struct {
typedef struct {
MusicWorker* music_worker;
FuriMessageQueue* message_queue;
Cli* cli;
CliRegistry* cli_registry;
} SpeakerDebugApp;
static SpeakerDebugApp* speaker_app_alloc(void) {
SpeakerDebugApp* app = (SpeakerDebugApp*)malloc(sizeof(SpeakerDebugApp));
app->music_worker = music_worker_alloc();
app->message_queue = furi_message_queue_alloc(8, sizeof(SpeakerDebugAppMessage));
app->cli = furi_record_open(RECORD_CLI);
app->cli_registry = furi_record_open(RECORD_CLI);
return app;
}
@@ -37,8 +39,8 @@ static void speaker_app_free(SpeakerDebugApp* app) {
free(app);
}
static void speaker_app_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
static void speaker_app_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(pipe);
SpeakerDebugApp* app = (SpeakerDebugApp*)context;
SpeakerDebugAppMessage message;
@@ -95,7 +97,8 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
return;
}
cli_add_command(app->cli, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app);
cli_registry_add_command(
app->cli_registry, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app);
SpeakerDebugAppMessage message;
FuriStatus status;
@@ -110,7 +113,7 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
}
}
cli_delete_command(app->cli, CLI_COMMAND);
cli_registry_delete_command(app->cli_registry, CLI_COMMAND);
}
int32_t speaker_debug_app(void* arg) {
@@ -6,7 +6,6 @@ App(
entry_point="subghz_test_app",
requires=["gui"],
stack_size=4 * 1024,
order=50,
fap_icon="subghz_test_10px.png",
fap_category="Debug",
fap_icon_assets="images",
@@ -5,6 +5,5 @@ App(
entry_point="text_box_element_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=140,
fap_category="Debug",
)
@@ -5,6 +5,5 @@ App(
entry_point="text_box_view_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=140,
fap_category="Debug",
)
@@ -5,6 +5,5 @@ App(
entry_point="uart_echo_app",
requires=["gui"],
stack_size=2 * 1024,
order=70,
fap_category="Debug",
)
+89 -9
View File
@@ -16,6 +16,9 @@
#define LINES_ON_SCREEN 6
#define COLUMNS_ON_SCREEN 21
#define DEFAULT_BAUD_RATE 230400
#define DEFAULT_DATA_BITS FuriHalSerialDataBits8
#define DEFAULT_PARITY FuriHalSerialParityNone
#define DEFAULT_STOP_BITS FuriHalSerialStopBits1
typedef struct UartDumpModel UartDumpModel;
@@ -49,11 +52,12 @@ typedef enum {
WorkerEventRxOverrunError = (1 << 4),
WorkerEventRxFramingError = (1 << 5),
WorkerEventRxNoiseError = (1 << 6),
WorkerEventRxParityError = (1 << 7),
} WorkerEventFlags;
#define WORKER_EVENTS_MASK \
(WorkerEventStop | WorkerEventRxData | WorkerEventRxIdle | WorkerEventRxOverrunError | \
WorkerEventRxFramingError | WorkerEventRxNoiseError)
WorkerEventRxFramingError | WorkerEventRxNoiseError | WorkerEventRxParityError)
const NotificationSequence sequence_notification = {
&message_display_backlight_on,
@@ -62,6 +66,13 @@ const NotificationSequence sequence_notification = {
NULL,
};
const NotificationSequence sequence_error = {
&message_display_backlight_on,
&message_red_255,
&message_delay_10,
NULL,
};
static void uart_echo_view_draw_callback(Canvas* canvas, void* _model) {
UartDumpModel* model = _model;
@@ -133,6 +144,9 @@ static void
if(event & FuriHalSerialRxEventOverrunError) {
flag |= WorkerEventRxOverrunError;
}
if(event & FuriHalSerialRxEventParityError) {
flag |= WorkerEventRxParityError;
}
furi_thread_flags_set(furi_thread_get_id(app->worker_thread), flag);
}
@@ -227,13 +241,21 @@ static int32_t uart_echo_worker(void* context) {
if(events & WorkerEventRxNoiseError) {
furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect NE\r\n", 13);
}
if(events & WorkerEventRxParityError) {
furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect PE\r\n", 13);
}
notification_message(app->notification, &sequence_error);
}
}
return 0;
}
static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
static UartEchoApp* uart_echo_app_alloc(
uint32_t baudrate,
FuriHalSerialDataBits data_bits,
FuriHalSerialParity parity,
FuriHalSerialStopBits stop_bits) {
UartEchoApp* app = malloc(sizeof(UartEchoApp));
app->rx_stream = furi_stream_buffer_alloc(2048, 1);
@@ -275,6 +297,7 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
app->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart);
furi_check(app->serial_handle);
furi_hal_serial_init(app->serial_handle, baudrate);
furi_hal_serial_configure_framing(app->serial_handle, data_bits, parity, stop_bits);
furi_hal_serial_async_rx_start(app->serial_handle, uart_echo_on_irq_cb, app, true);
@@ -318,19 +341,76 @@ static void uart_echo_app_free(UartEchoApp* app) {
free(app);
}
// silences "same-assignment" false positives in the arg parser below
// -V::1048
int32_t uart_echo_app(void* p) {
uint32_t baudrate = DEFAULT_BAUD_RATE;
FuriHalSerialDataBits data_bits = DEFAULT_DATA_BITS;
FuriHalSerialParity parity = DEFAULT_PARITY;
FuriHalSerialStopBits stop_bits = DEFAULT_STOP_BITS;
if(p) {
const char* baudrate_str = p;
if(strint_to_uint32(baudrate_str, NULL, &baudrate, 10) != StrintParseNoError) {
FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str);
baudrate = DEFAULT_BAUD_RATE;
// parse argument
char* parse_ptr = p;
bool parse_success = false;
do {
if(strint_to_uint32(parse_ptr, &parse_ptr, &baudrate, 10) != StrintParseNoError) break;
if(*(parse_ptr++) != '_') break;
uint16_t data_bits_int;
if(strint_to_uint16(parse_ptr, &parse_ptr, &data_bits_int, 10) != StrintParseNoError)
break;
if(data_bits_int == 6)
data_bits = FuriHalSerialDataBits6;
else if(data_bits_int == 7)
data_bits = FuriHalSerialDataBits7;
else if(data_bits_int == 8)
data_bits = FuriHalSerialDataBits8;
else if(data_bits_int == 9)
data_bits = FuriHalSerialDataBits9;
else
break;
char parity_char = *(parse_ptr++);
if(parity_char == 'N')
parity = FuriHalSerialParityNone;
else if(parity_char == 'E')
parity = FuriHalSerialParityEven;
else if(parity_char == 'O')
parity = FuriHalSerialParityOdd;
else
break;
uint16_t stop_bits_int;
if(strint_to_uint16(parse_ptr, &parse_ptr, &stop_bits_int, 10) != StrintParseNoError)
break;
if(stop_bits_int == 5)
stop_bits = FuriHalSerialStopBits0_5;
else if(stop_bits_int == 1)
stop_bits = FuriHalSerialStopBits1;
else if(stop_bits_int == 15)
stop_bits = FuriHalSerialStopBits1_5;
else if(stop_bits_int == 2)
stop_bits = FuriHalSerialStopBits2;
else
break;
parse_success = true;
} while(0);
if(!parse_success) {
FURI_LOG_I(
TAG,
"Couldn't parse baud rate and framing (%s). Applying defaults (%d_8N1)",
(const char*)p,
DEFAULT_BAUD_RATE);
}
}
FURI_LOG_I(TAG, "Using baudrate: %lu", baudrate);
UartEchoApp* app = uart_echo_app_alloc(baudrate);
UartEchoApp* app = uart_echo_app_alloc(baudrate, data_bits, parity, stop_bits);
view_dispatcher_run(app->view_dispatcher);
uart_echo_app_free(app);
return 0;
@@ -4,10 +4,10 @@ App(
entry_point="unit_tests_on_system_start",
sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"],
cdefines=["APP_UNIT_TESTS"],
requires=["system_settings", "subghz_start"],
requires=["system_settings", "cli_subghz"],
provides=["delay_test"],
resources="resources",
order=100,
order=30,
)
App(
@@ -18,7 +18,7 @@ App(
entry_point="delay_test_app",
stack_size=1 * 1024,
requires=["unit_tests"],
order=110,
order=30,
)
App(
@@ -12,4 +12,4 @@ tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"]));
tests.assert_eq("flipperdevices", flipper.firmwareVendor);
tests.assert_eq(0, flipper.jsSdkVersion[0]);
tests.assert_eq(1, flipper.jsSdkVersion[1]);
tests.assert_eq(3, flipper.jsSdkVersion[1]);
+6 -5
View File
@@ -2,8 +2,9 @@
#include "tests/test_api.h"
#include <cli/cli.h>
#include <toolbox/cli/cli_command.h>
#include <toolbox/path.h>
#include <toolbox/pipe.h>
#include <loader/loader.h>
#include <storage/storage.h>
#include <notification/notification_messages.h>
@@ -25,7 +26,7 @@ struct TestRunner {
NotificationApp* notification;
// Temporary used things
Cli* cli;
PipeSide* pipe;
FuriString* args;
// ELF related stuff
@@ -38,14 +39,14 @@ struct TestRunner {
int minunit_status;
};
TestRunner* test_runner_alloc(Cli* cli, FuriString* args) {
TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args) {
TestRunner* instance = malloc(sizeof(TestRunner));
instance->storage = furi_record_open(RECORD_STORAGE);
instance->loader = furi_record_open(RECORD_LOADER);
instance->notification = furi_record_open(RECORD_NOTIFICATION);
instance->cli = cli;
instance->pipe = pipe;
instance->args = args;
instance->composite_resolver = composite_api_resolver_alloc();
@@ -147,7 +148,7 @@ static void test_runner_run_internal(TestRunner* instance) {
}
while(true) {
if(cli_cmd_interrupt_received(instance->cli)) {
if(cli_is_pipe_broken_or_is_etx_next_char(instance->pipe)) {
break;
}
+4 -4
View File
@@ -1,12 +1,12 @@
#pragma once
#include <furi.h>
#include <toolbox/pipe.h>
typedef struct TestRunner TestRunner;
typedef struct Cli Cli;
TestRunner* test_runner_alloc(Cli* cli, FuriString* args);
TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args);
void test_runner_free(TestRunner* isntance);
void test_runner_free(TestRunner* instance);
void test_runner_run(TestRunner* isntance);
void test_runner_run(TestRunner* instance);
@@ -446,6 +446,55 @@ static int32_t test_furi_event_loop_consumer(void* p) {
return 0;
}
typedef struct {
FuriEventLoop* event_loop;
FuriSemaphore* semaphore;
size_t counter;
} SelfUnsubTestTimerContext;
static void test_self_unsub_semaphore_callback(FuriEventLoopObject* object, void* context) {
furi_event_loop_unsubscribe(context, object); // shouldn't crash here
}
static void test_self_unsub_timer_callback(void* arg) {
SelfUnsubTestTimerContext* context = arg;
if(context->counter == 0) {
furi_semaphore_release(context->semaphore);
} else if(context->counter == 1) {
furi_event_loop_stop(context->event_loop);
}
context->counter++;
}
void test_furi_event_loop_self_unsubscribe(void) {
FuriEventLoop* event_loop = furi_event_loop_alloc();
FuriSemaphore* semaphore = furi_semaphore_alloc(1, 0);
furi_event_loop_subscribe_semaphore(
event_loop,
semaphore,
FuriEventLoopEventIn,
test_self_unsub_semaphore_callback,
event_loop);
SelfUnsubTestTimerContext timer_context = {
.event_loop = event_loop,
.semaphore = semaphore,
.counter = 0,
};
FuriEventLoopTimer* timer = furi_event_loop_timer_alloc(
event_loop, test_self_unsub_timer_callback, FuriEventLoopTimerTypePeriodic, &timer_context);
furi_event_loop_timer_start(timer, furi_ms_to_ticks(20));
furi_event_loop_run(event_loop);
furi_event_loop_timer_free(timer);
furi_semaphore_free(semaphore);
furi_event_loop_free(event_loop);
}
void test_furi_event_loop(void) {
TestFuriEventLoopData data = {};
@@ -30,7 +30,9 @@ static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context
}
void test_stdin(void) {
FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback();
FuriThreadStdinReadCallback in_cb;
void* in_ctx;
furi_thread_get_stdin_callback(&in_cb, &in_ctx);
furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC);
char buf[256];
@@ -63,13 +65,14 @@ void test_stdin(void) {
fgets(buf, sizeof(buf), stdin);
mu_assert_string_eq(" World!\n", buf);
furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC);
furi_thread_set_stdin_callback(in_cb, in_ctx);
}
// stdout
static FuriString* mock_out;
FuriThreadStdoutWriteCallback original_out_cb;
static FuriThreadStdoutWriteCallback original_out_cb;
static void* original_out_ctx;
static void mock_out_cb(const char* data, size_t size, void* context) {
furi_check(context == CONTEXT_MAGIC);
@@ -83,7 +86,7 @@ static void assert_and_clear_mock_out(const char* expected) {
// return the original stdout callback for the duration of the check
// if the check fails, we don't want the error to end up in our buffer,
// we want to be able to see it!
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
furi_thread_set_stdout_callback(original_out_cb, original_out_ctx);
mu_assert_string_eq(expected, furi_string_get_cstr(mock_out));
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);
@@ -91,7 +94,7 @@ static void assert_and_clear_mock_out(const char* expected) {
}
void test_stdout(void) {
original_out_cb = furi_thread_get_stdout_callback();
furi_thread_get_stdout_callback(&original_out_cb, &original_out_ctx);
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);
mock_out = furi_string_alloc();
@@ -104,5 +107,5 @@ void test_stdout(void) {
assert_and_clear_mock_out("Hello!");
furi_string_free(mock_out);
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
furi_thread_set_stdout_callback(original_out_cb, original_out_ctx);
}
@@ -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_furi_event_loop_self_unsubscribe(void);
void test_errno_saving(void);
void test_furi_primitives(void);
void test_stdin(void);
@@ -46,6 +47,10 @@ MU_TEST(mu_test_furi_event_loop) {
test_furi_event_loop();
}
MU_TEST(mu_test_furi_event_loop_self_unsubscribe) {
test_furi_event_loop_self_unsubscribe();
}
MU_TEST(mu_test_errno_saving) {
test_errno_saving();
}
@@ -68,6 +73,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_furi_event_loop_self_unsubscribe);
MU_RUN_TEST(mu_test_stdio);
MU_RUN_TEST(mu_test_errno_saving);
MU_RUN_TEST(mu_test_furi_primitives);
@@ -6,9 +6,12 @@
#include <storage/storage.h>
#include <applications/system/js_app/js_thread.h>
#include <applications/system/js_app/js_value.h>
#include <stdint.h>
#define TAG "JsUnitTests"
#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js")
typedef enum {
@@ -73,7 +76,311 @@ MU_TEST(js_test_storage) {
js_test_run(JS_SCRIPT_PATH("storage"));
}
static void js_value_test_compatibility_matrix(struct mjs* mjs) {
static const JsValueType types[] = {
JsValueTypeAny,
JsValueTypeAnyArray,
JsValueTypeAnyObject,
JsValueTypeFunction,
JsValueTypeRawPointer,
JsValueTypeInt32,
JsValueTypeDouble,
JsValueTypeString,
JsValueTypeBool,
};
mjs_val_t values[] = {
mjs_mk_undefined(),
mjs_mk_foreign(mjs, (void*)0xDEADBEEF),
mjs_mk_array(mjs),
mjs_mk_object(mjs),
mjs_mk_number(mjs, 123.456),
mjs_mk_string(mjs, "test", ~0, false),
mjs_mk_boolean(mjs, true),
};
// for proper matrix formatting and better readability
#define YES true
#define NO_ false
static const bool success_matrix[COUNT_OF(types)][COUNT_OF(values)] = {
// types:
{YES, YES, YES, YES, YES, YES, YES}, // any
{NO_, NO_, YES, NO_, NO_, NO_, NO_}, // array
{NO_, NO_, YES, YES, NO_, NO_, NO_}, // obj
{NO_, NO_, NO_, NO_, NO_, NO_, NO_}, // fn
{NO_, YES, NO_, NO_, NO_, NO_, NO_}, // ptr
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // int32
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // double
{NO_, NO_, NO_, NO_, NO_, YES, NO_}, // str
{NO_, NO_, NO_, NO_, NO_, NO_, YES}, // bool
//
//und ptr arr obj num str bool <- values
};
#undef NO_
#undef YES
for(size_t i = 0; i < COUNT_OF(types); i++) {
for(size_t j = 0; j < COUNT_OF(values); j++) {
const JsValueDeclaration declaration = {
.type = types[i],
.n_children = 0,
};
// we only care about the status, not the result. double has the largest size out of
// all the results
uint8_t result[sizeof(double)];
JsValueParseStatus status;
JS_VALUE_PARSE(
mjs,
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
JsValueParseFlagNone,
&status,
&values[j],
result);
if((status == JsValueParseStatusOk) != success_matrix[i][j]) {
FURI_LOG_E(TAG, "type %zu, value %zu", i, j);
mu_fail("see serial logs");
}
}
}
}
static void js_value_test_literal(struct mjs* mjs) {
static const JsValueType types[] = {
JsValueTypeAny,
JsValueTypeAnyArray,
JsValueTypeAnyObject,
};
mjs_val_t values[] = {
mjs_mk_undefined(),
mjs_mk_array(mjs),
mjs_mk_object(mjs),
};
mu_assert_int_eq(COUNT_OF(types), COUNT_OF(values));
for(size_t i = 0; i < COUNT_OF(types); i++) {
const JsValueDeclaration declaration = {
.type = types[i],
.n_children = 0,
};
mjs_val_t result;
JsValueParseStatus status;
JS_VALUE_PARSE(
mjs,
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
JsValueParseFlagNone,
&status,
&values[i],
&result);
mu_assert_int_eq(JsValueParseStatusOk, status);
mu_assert(result == values[i], "wrong result");
}
}
static void js_value_test_primitive(
struct mjs* mjs,
JsValueType type,
const void* c_value,
size_t c_value_size,
mjs_val_t js_val) {
const JsValueDeclaration declaration = {
.type = type,
.n_children = 0,
};
uint8_t result[c_value_size];
JsValueParseStatus status;
JS_VALUE_PARSE(
mjs,
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
JsValueParseFlagNone,
&status,
&js_val,
result);
mu_assert_int_eq(JsValueParseStatusOk, status);
if(type == JsValueTypeString) {
const char* result_str = *(const char**)&result;
mu_assert_string_eq(c_value, result_str);
} else {
mu_assert_mem_eq(c_value, result, c_value_size);
}
}
static void js_value_test_primitives(struct mjs* mjs) {
int32_t i32 = 123;
js_value_test_primitive(mjs, JsValueTypeInt32, &i32, sizeof(i32), mjs_mk_number(mjs, i32));
double dbl = 123.456;
js_value_test_primitive(mjs, JsValueTypeDouble, &dbl, sizeof(dbl), mjs_mk_number(mjs, dbl));
const char* str = "test";
js_value_test_primitive(
mjs, JsValueTypeString, str, strlen(str) + 1, mjs_mk_string(mjs, str, ~0, false));
bool boolean = true;
js_value_test_primitive(
mjs, JsValueTypeBool, &boolean, sizeof(boolean), mjs_mk_boolean(mjs, boolean));
}
static uint32_t
js_value_test_enum(struct mjs* mjs, const JsValueDeclaration* decl, const char* value) {
mjs_val_t str = mjs_mk_string(mjs, value, ~0, false);
uint32_t result;
JsValueParseStatus status;
JS_VALUE_PARSE(
mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result);
if(status != JsValueParseStatusOk) return 0;
return result;
}
static void js_value_test_enums(struct mjs* mjs) {
static const JsValueEnumVariant enum_1_variants[] = {
{"variant 1", 1},
{"variant 2", 2},
{"variant 3", 3},
};
static const JsValueDeclaration enum_1 = JS_VALUE_ENUM(uint32_t, enum_1_variants);
static const JsValueEnumVariant enum_2_variants[] = {
{"read", 4},
{"write", 8},
};
static const JsValueDeclaration enum_2 = JS_VALUE_ENUM(uint32_t, enum_2_variants);
mu_assert_int_eq(1, js_value_test_enum(mjs, &enum_1, "variant 1"));
mu_assert_int_eq(2, js_value_test_enum(mjs, &enum_1, "variant 2"));
mu_assert_int_eq(3, js_value_test_enum(mjs, &enum_1, "variant 3"));
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_1, "not a thing"));
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 1"));
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 2"));
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 3"));
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "not a thing"));
mu_assert_int_eq(4, js_value_test_enum(mjs, &enum_2, "read"));
mu_assert_int_eq(8, js_value_test_enum(mjs, &enum_2, "write"));
}
static void js_value_test_object(struct mjs* mjs) {
static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE(JsValueTypeInt32);
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
static const JsValueEnumVariant enum_variants[] = {
{"variant 1", 1},
{"variant 2", 2},
{"variant 3", 3},
};
static const JsValueDeclaration enum_decl = JS_VALUE_ENUM(uint32_t, enum_variants);
static const JsValueObjectField fields[] = {
{"int", &int_decl},
{"str", &str_decl},
{"enum", &enum_decl},
};
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
mjs_val_t object = mjs_mk_object(mjs);
JS_ASSIGN_MULTI(mjs, object) {
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
JS_FIELD("int", mjs_mk_number(mjs, 123));
JS_FIELD("enum", mjs_mk_string(mjs, "variant 2", ~0, false));
}
const char* result_str;
int32_t result_int;
uint32_t result_enum;
JsValueParseStatus status;
JS_VALUE_PARSE(
mjs,
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
JsValueParseFlagNone,
&status,
&object,
&result_int,
&result_str,
&result_enum);
mu_assert_int_eq(JsValueParseStatusOk, status);
mu_assert_string_eq("Helloooo!", result_str);
mu_assert_int_eq(123, result_int);
mu_assert_int_eq(2, result_enum);
}
static void js_value_test_default(struct mjs* mjs) {
static const JsValueDeclaration int_decl =
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123);
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
static const JsValueObjectField fields[] = {
{"int", &int_decl},
{"str", &str_decl},
};
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
mjs_val_t object = mjs_mk_object(mjs);
JS_ASSIGN_MULTI(mjs, object) {
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
JS_FIELD("int", mjs_mk_undefined());
}
const char* result_str;
int32_t result_int;
JsValueParseStatus status;
JS_VALUE_PARSE(
mjs,
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
JsValueParseFlagNone,
&status,
&object,
&result_int,
&result_str);
mu_assert_string_eq("Helloooo!", result_str);
mu_assert_int_eq(123, result_int);
}
static void js_value_test_args_fn(struct mjs* mjs) {
static const JsValueDeclaration arg_list[] = {
JS_VALUE_SIMPLE(JsValueTypeInt32),
JS_VALUE_SIMPLE(JsValueTypeInt32),
JS_VALUE_SIMPLE(JsValueTypeInt32),
};
static const JsValueArguments args = JS_VALUE_ARGS(arg_list);
int32_t a, b, c;
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &args, &a, &b, &c);
mu_assert_int_eq(123, a);
mu_assert_int_eq(456, b);
mu_assert_int_eq(-420, c);
}
static void js_value_test_args(struct mjs* mjs) {
mjs_val_t function = MJS_MK_FN(js_value_test_args_fn);
mjs_val_t result;
mjs_val_t args[] = {
mjs_mk_number(mjs, 123),
mjs_mk_number(mjs, 456),
mjs_mk_number(mjs, -420),
};
mu_assert_int_eq(
MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args));
}
MU_TEST(js_value_test) {
struct mjs* mjs = mjs_create(NULL);
js_value_test_compatibility_matrix(mjs);
js_value_test_literal(mjs);
js_value_test_primitives(mjs);
js_value_test_enums(mjs);
js_value_test_object(mjs);
js_value_test_default(mjs);
js_value_test_args(mjs);
mjs_destroy(mjs);
}
MU_TEST_SUITE(test_js) {
MU_RUN_TEST(js_value_test);
MU_RUN_TEST(js_test_basic);
MU_RUN_TEST(js_test_math);
MU_RUN_TEST(js_test_event_loop);
@@ -396,6 +396,8 @@ void minunit_printf_warning(const char* format, ...);
return; \
} else { minunit_print_progress(); })
//-V:mu_assert_string_eq:526, 547
#define mu_assert_string_eq(expected, result) \
MU__SAFE_BLOCK( \
const char* minunit_tmp_e = expected; const char* minunit_tmp_r = result; \
@@ -416,6 +418,8 @@ void minunit_printf_warning(const char* format, ...);
return; \
} else { minunit_print_progress(); })
//-V:mu_assert_mem_eq:526
#define mu_assert_mem_eq(expected, result, size) \
MU__SAFE_BLOCK( \
const void* minunit_tmp_e = expected; const void* minunit_tmp_r = result; \
@@ -262,7 +262,7 @@ static void mf_ultralight_reader_test(const char* path) {
nfc_listener_start(mfu_listener, NULL, NULL);
MfUltralightData* mfu_data = mf_ultralight_alloc();
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
nfc_listener_stop(mfu_listener);
@@ -315,7 +315,7 @@ MU_TEST(ntag_213_locked_reader) {
nfc_listener_start(mfu_listener, NULL, NULL);
MfUltralightData* mfu_data = mf_ultralight_alloc();
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
nfc_listener_stop(mfu_listener);
@@ -353,7 +353,7 @@ static void mf_ultralight_write(void) {
MfUltralightData* mfu_data = mf_ultralight_alloc();
// Initial read
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
mu_assert(
@@ -371,7 +371,7 @@ static void mf_ultralight_write(void) {
}
// Verification read
error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
nfc_listener_stop(mfu_listener);
@@ -25,16 +25,13 @@ MU_TEST(pipe_test_trivial) {
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(alice));
mu_assert_int_eq(i, pipe_bytes_available(bob));
if(pipe_send(alice, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
break;
}
if(pipe_spaces_available(alice) == 0) break;
furi_check(pipe_send(alice, &i, sizeof(uint8_t)) == sizeof(uint8_t));
mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(bob));
mu_assert_int_eq(i, pipe_bytes_available(alice));
if(pipe_send(bob, &i, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
break;
}
furi_check(pipe_send(bob, &i, sizeof(uint8_t)) == sizeof(uint8_t));
}
pipe_free(alice);
@@ -43,10 +40,9 @@ MU_TEST(pipe_test_trivial) {
for(uint8_t i = 0;; ++i) {
mu_assert_int_eq(PIPE_SIZE - i, pipe_bytes_available(bob));
if(pipe_bytes_available(bob) == 0) break;
uint8_t value;
if(pipe_receive(bob, &value, sizeof(uint8_t), 0) != sizeof(uint8_t)) {
break;
}
furi_check(pipe_receive(bob, &value, sizeof(uint8_t)) == sizeof(uint8_t));
mu_assert_int_eq(i, value);
}
@@ -68,16 +64,15 @@ typedef struct {
static void on_data_arrived(PipeSide* pipe, void* context) {
AncillaryThreadContext* ctx = context;
ctx->flag |= TestFlagDataArrived;
uint8_t buffer[PIPE_SIZE];
size_t size = pipe_receive(pipe, buffer, sizeof(buffer), 0);
pipe_send(pipe, buffer, size, 0);
uint8_t input;
size_t size = pipe_receive(pipe, &input, sizeof(input));
pipe_send(pipe, &input, size);
}
static void on_space_freed(PipeSide* pipe, void* context) {
UNUSED(pipe);
AncillaryThreadContext* ctx = context;
ctx->flag |= TestFlagSpaceFreed;
const char* message = "Hi!";
pipe_send(pipe, message, strlen(message), 0);
}
static void on_became_broken(PipeSide* pipe, void* context) {
@@ -117,22 +112,16 @@ MU_TEST(pipe_test_event_loop) {
furi_thread_start(thread);
const char* message = "Hello!";
pipe_send(alice, message, strlen(message), FuriWaitForever);
pipe_send(alice, message, strlen(message));
char buffer_1[16];
size_t size = pipe_receive(alice, buffer_1, sizeof(buffer_1), FuriWaitForever);
size_t size = pipe_receive(alice, buffer_1, strlen(message));
buffer_1[size] = 0;
char buffer_2[16];
const char* expected_reply = "Hi!";
size = pipe_receive(alice, buffer_2, sizeof(buffer_2), FuriWaitForever);
buffer_2[size] = 0;
pipe_free(alice);
furi_thread_join(thread);
mu_assert_string_eq(message, buffer_1);
mu_assert_string_eq(expected_reply, buffer_2);
mu_assert_int_eq(
TestFlagDataArrived | TestFlagSpaceFreed | TestFlagBecameBroken,
furi_thread_get_return_code(thread));
@@ -3,7 +3,6 @@
#include <rpc/rpc.h>
#include <rpc/rpc_i.h>
#include <cli/cli.h>
#include <storage/storage.h>
#include <loader/loader.h>
#include <storage/filesystem_api_defines.h>
@@ -521,11 +521,6 @@ MU_TEST(test_storage_data_path) {
// check that appsdata folder exists
mu_check(storage_dir_exists(storage, APPS_DATA_PATH));
// check that cli folder exists
mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli")));
storage_simply_remove(storage, APPSDATA_APP_PATH("cli"));
furi_record_close(RECORD_STORAGE);
}
@@ -8,6 +8,7 @@
#include <rpc/rpc_i.h>
#include <flipper.pb.h>
#include <applications/system/js_app/js_thread.h>
#include <applications/system/js_app/js_value.h>
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
@@ -38,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
JsThread*,
(const char* script_path, JsThreadCallback callback, void* context)),
API_METHOD(js_thread_stop, void, (JsThread * worker)),
API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)),
API_METHOD(
js_value_parse,
JsValueParseStatus,
(struct mjs * mjs,
const JsValueParseDeclaration declaration,
JsValueParseFlag flags,
mjs_val_t* buffer,
size_t buf_size,
mjs_val_t* source,
size_t n_c_vals,
...)),
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
+9 -6
View File
@@ -1,21 +1,24 @@
#include <furi.h>
#include <cli/cli.h>
#include <toolbox/pipe.h>
#include <toolbox/cli/cli_command.h>
#include <toolbox/cli/cli_registry.h>
#include <cli/cli_main_commands.h>
#include "test_runner.h"
void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
void unit_tests_cli(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
TestRunner* test_runner = test_runner_alloc(cli, args);
TestRunner* test_runner = test_runner_alloc(pipe, args);
test_runner_run(test_runner);
test_runner_free(test_runner);
}
void unit_tests_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
CliRegistry* registry = furi_record_open(RECORD_CLI);
cli_registry_add_command(
registry, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
furi_record_close(RECORD_CLI);
#endif
}
@@ -5,6 +5,5 @@ App(
entry_point="usb_mouse_app",
requires=["gui"],
stack_size=1 * 1024,
order=60,
fap_category="Debug",
)
@@ -5,6 +5,5 @@ App(
entry_point="usb_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=50,
fap_category="Debug",
)
@@ -5,6 +5,5 @@ App(
entry_point="vibro_test_app",
requires=["gui"],
stack_size=1 * 1024,
order=20,
fap_category="Debug",
)
@@ -85,7 +85,7 @@ typedef struct {
volatile SubGhzDeviceCC1101ExtState state;
volatile SubGhzDeviceCC1101ExtRegulation regulation;
const GpioPin* async_mirror_pin;
FuriHalSpiBusHandle* spi_bus_handle;
const FuriHalSpiBusHandle* spi_bus_handle;
const GpioPin* g0_pin;
SubGhzDeviceCC1101ExtAsyncTx async_tx;
SubGhzDeviceCC1101ExtAsyncRx async_rx;
@@ -22,7 +22,7 @@
#include <one_wire/maxim_crc.h>
#include <one_wire/one_wire_host.h>
#include <furi_hal_power.h>
#include <power/power_service/power.h>
#define UPDATE_PERIOD_MS 1000UL
#define TEXT_STORE_SIZE 64U
@@ -76,6 +76,7 @@ typedef struct {
FuriThread* reader_thread;
FuriMessageQueue* event_queue;
OneWireHost* onewire;
Power* power;
float temp_celsius;
bool has_device;
} ExampleThermoContext;
@@ -273,7 +274,7 @@ static void example_thermo_input_callback(InputEvent* event, void* ctx) {
/* Starts the reader thread and handles the input */
static void example_thermo_run(ExampleThermoContext* context) {
/* Enable power on external pins */
furi_hal_power_enable_otg();
power_enable_otg(context->power, true);
/* Configure the hardware in host mode */
onewire_host_start(context->onewire);
@@ -309,7 +310,7 @@ static void example_thermo_run(ExampleThermoContext* context) {
onewire_host_stop(context->onewire);
/* Disable power on external pins */
furi_hal_power_disable_otg();
power_enable_otg(context->power, false);
}
/******************** Initialisation & startup *****************************/
@@ -334,6 +335,8 @@ static ExampleThermoContext* example_thermo_context_alloc(void) {
context->onewire = onewire_host_alloc(&THERMO_GPIO_PIN);
context->power = furi_record_open(RECORD_POWER);
return context;
}
@@ -348,6 +351,7 @@ static void example_thermo_context_free(ExampleThermoContext* context) {
view_port_free(context->view_port);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_POWER);
}
/* The application's entry point. Execution starts from here. */
+1 -6
View File
@@ -21,11 +21,6 @@ App(
name="On start hooks",
apptype=FlipperAppType.METAPACKAGE,
provides=[
"ibutton_start",
"onewire_start",
"subghz_start",
"infrared_start",
"lfrfid_start",
"nfc_start",
"cli",
],
)
+1 -1
View File
@@ -7,5 +7,5 @@ App(
requires=["gui"],
stack_size=4 * 1024,
icon="A_FileManager_14",
order=0,
order=10,
)
@@ -1,8 +1,9 @@
#include "archive_apps.h"
#include "archive_browser.h"
static const char* known_apps[] = {
static const char* const known_apps[] = {
[ArchiveAppTypeU2f] = "u2f",
[ArchiveAppTypeSetting] = "setting",
};
ArchiveAppTypeEnum archive_get_app_type(const char* path) {
@@ -36,6 +37,8 @@ bool archive_app_is_available(void* context, const char* path) {
furi_record_close(RECORD_STORAGE);
return file_exists;
} else if(app == ArchiveAppTypeSetting) {
return true;
} else {
return false;
}
@@ -53,6 +56,9 @@ bool archive_app_read_dir(void* context, const char* path) {
if(app == ArchiveAppTypeU2f) {
archive_add_app_item(browser, "/app:u2f/U2F Token");
return true;
} else if(app == ArchiveAppTypeSetting) {
archive_add_app_item(browser, path);
return true;
} else {
return false;
}
@@ -75,6 +81,8 @@ void archive_app_delete_file(void* context, const char* path) {
if(archive_is_favorite("/app:u2f/U2F Token")) {
archive_favorites_delete("/app:u2f/U2F Token");
}
} else if(app == ArchiveAppTypeSetting) {
// can't delete a setting!
}
if(res) {
@@ -4,12 +4,14 @@
typedef enum {
ArchiveAppTypeU2f,
ArchiveAppTypeSetting,
ArchiveAppTypeUnknown,
ArchiveAppsTotal,
} ArchiveAppTypeEnum;
static const ArchiveFileTypeEnum app_file_types[] = {
[ArchiveAppTypeU2f] = ArchiveFileTypeU2f,
[ArchiveAppTypeSetting] = ArchiveFileTypeSetting,
[ArchiveAppTypeUnknown] = ArchiveFileTypeUnknown,
};
@@ -7,7 +7,7 @@
#define TAB_DEFAULT ArchiveTabFavorites // Start tab
#define FILE_LIST_BUF_LEN 50
static const char* tab_default_paths[] = {
static const char* const tab_default_paths[] = {
[ArchiveTabFavorites] = "/app:favorites",
[ArchiveTabIButton] = EXT_PATH("ibutton"),
[ArchiveTabNFC] = EXT_PATH("nfc"),
@@ -20,7 +20,7 @@ static const char* tab_default_paths[] = {
[ArchiveTabBrowser] = STORAGE_EXT_PATH_PREFIX,
};
static const char* known_ext[] = {
static const char* const known_ext[] = {
[ArchiveFileTypeIButton] = ".ibtn",
[ArchiveFileTypeNFC] = ".nfc",
[ArchiveFileTypeSubGhz] = ".sub",
@@ -34,6 +34,7 @@ static const char* known_ext[] = {
[ArchiveFileTypeFolder] = "?",
[ArchiveFileTypeUnknown] = "*",
[ArchiveFileTypeAppOrJs] = ".fap|.js",
[ArchiveFileTypeSetting] = "?",
};
static const ArchiveFileTypeEnum known_type[] = {
@@ -1,9 +1,10 @@
#include "archive_favorites.h"
#include "archive_files.h"
#include "archive_apps.h"
#include "archive_browser.h"
#include <dialogs/dialogs.h>
#define ARCHIVE_FAV_FILE_BUF_LEN 32
static bool archive_favorites_read_line(File* file, FuriString* str_result) {
@@ -337,3 +338,46 @@ void archive_favorites_save(void* context) {
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting) {
DialogMessage* message = dialog_message_alloc();
FuriString* setting_path = furi_string_alloc_set_str(app_name);
if(setting) {
furi_string_push_back(setting_path, '/');
furi_string_cat_str(setting_path, setting);
}
const char* setting_path_str = furi_string_get_cstr(setting_path);
bool is_favorite = archive_is_favorite("/app:setting/%s", setting_path_str);
dialog_message_set_header(
message,
is_favorite ? "Unpin This Setting?" : "Pin This Setting?",
64,
0,
AlignCenter,
AlignTop);
dialog_message_set_text(
message,
is_favorite ? "It will no longer be\naccessible from the\nFavorites menu" :
"It will be accessible from the\nFavorites menu",
64,
32,
AlignCenter,
AlignCenter);
dialog_message_set_buttons(
message, is_favorite ? "Unpin" : "Go back", NULL, is_favorite ? "Keep pinned" : "Pin");
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessageButton button = dialog_message_show(dialogs, message);
furi_record_close(RECORD_DIALOGS);
if(is_favorite && button == DialogMessageButtonLeft) {
archive_favorites_delete("/app:setting/%s", setting_path_str);
} else if(!is_favorite && button == DialogMessageButtonRight) {
archive_file_append(ARCHIVE_FAV_PATH, "/app:setting/%s\n", setting_path_str);
}
furi_string_free(setting_path);
dialog_message_free(message);
}
@@ -12,3 +12,13 @@ bool archive_is_favorite(const char* format, ...) _ATTRIBUTE((__format__(__print
bool archive_favorites_rename(const char* src, const char* dst);
void archive_add_to_favorites(const char* file_path);
void archive_favorites_save(void* context);
/**
* Intended to be called by settings apps to handle long presses, as well as
* internally from within the archive
*
* @param app_name name of the referring application
* @param setting name of the setting, which will be both displayed to the user
* and passed to the application as an argument upon recall
*/
void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting);
@@ -20,6 +20,7 @@ typedef enum {
ArchiveFileTypeFolder,
ArchiveFileTypeUnknown,
ArchiveFileTypeAppOrJs,
ArchiveFileTypeSetting,
ArchiveFileTypeLoading,
} ArchiveFileTypeEnum;
@@ -42,7 +42,7 @@ static void archive_loader_callback(const void* message, void* context) {
const LoaderEvent* event = message;
ArchiveApp* archive = (ArchiveApp*)context;
if(event->type == LoaderEventTypeApplicationStopped) {
if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
view_dispatcher_send_custom_event(
archive->view_dispatcher, ArchiveBrowserEventListRefresh);
}
@@ -52,20 +52,37 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec
UNUSED(browser);
Loader* loader = furi_record_open(RECORD_LOADER);
const char* app_name = archive_get_flipper_app_name(selected->type);
if(app_name) {
if(selected->is_app) {
char* param = strrchr(furi_string_get_cstr(selected->path), '/');
if(param != NULL) {
param++;
}
loader_start_with_gui_error(loader, app_name, param);
if(selected->type == ArchiveFileTypeSetting) {
FuriString* app_name = furi_string_alloc_set(selected->path);
furi_string_right(app_name, furi_string_search_char(app_name, '/', 1) + 1);
size_t slash = furi_string_search_char(app_name, '/', 1);
if(slash != FURI_STRING_FAILURE) {
furi_string_left(app_name, slash);
FuriString* app_args =
furi_string_alloc_set_str(furi_string_get_cstr(app_name) + slash + 1);
loader_start_with_gui_error(
loader, furi_string_get_cstr(app_name), furi_string_get_cstr(app_args));
furi_string_free(app_args);
} else {
loader_start_with_gui_error(loader, app_name, furi_string_get_cstr(selected->path));
loader_start_with_gui_error(loader, furi_string_get_cstr(app_name), NULL);
}
furi_string_free(app_name);
} else {
loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL);
const char* app_name = archive_get_flipper_app_name(selected->type);
if(app_name) {
if(selected->is_app) {
char* param = strrchr(furi_string_get_cstr(selected->path), '/');
if(param != NULL) {
param++;
}
loader_start_with_gui_error(loader, app_name, param);
} else {
loader_start_with_gui_error(
loader, app_name, furi_string_get_cstr(selected->path));
}
} else {
loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL);
}
}
furi_record_close(RECORD_LOADER);
@@ -28,6 +28,7 @@ static const Icon* ArchiveItemIcons[] = {
[ArchiveFileTypeInfrared] = &I_ir_10px,
[ArchiveFileTypeBadUsb] = &I_badusb_10px,
[ArchiveFileTypeU2f] = &I_u2f_10px,
[ArchiveFileTypeSetting] = &I_settings_10px,
[ArchiveFileTypeUpdateManifest] = &I_update_10px,
[ArchiveFileTypeFolder] = &I_dir_10px,
[ArchiveFileTypeUnknown] = &I_unknown_10px,
@@ -37,6 +37,31 @@ bool hid_usb_kb_release(void* inst, uint16_t button) {
return furi_hal_hid_kb_release(button);
}
bool hid_usb_mouse_press(void* inst, uint8_t button) {
UNUSED(inst);
return furi_hal_hid_mouse_press(button);
}
bool hid_usb_mouse_release(void* inst, uint8_t button) {
UNUSED(inst);
return furi_hal_hid_mouse_release(button);
}
bool hid_usb_mouse_scroll(void* inst, int8_t delta) {
UNUSED(inst);
return furi_hal_hid_mouse_scroll(delta);
}
bool hid_usb_mouse_move(void* inst, int8_t dx, int8_t dy) {
UNUSED(inst);
return furi_hal_hid_mouse_move(dx, dy);
}
bool hid_usb_mouse_release_all(void* inst) {
UNUSED(inst);
return furi_hal_hid_mouse_release(0);
}
bool hid_usb_consumer_press(void* inst, uint16_t button) {
UNUSED(inst);
return furi_hal_hid_consumer_key_press(button);
@@ -51,6 +76,7 @@ bool hid_usb_release_all(void* inst) {
UNUSED(inst);
bool state = furi_hal_hid_kb_release_all();
state &= furi_hal_hid_consumer_key_release_all();
state &= hid_usb_mouse_release_all(inst);
return state;
}
@@ -67,6 +93,10 @@ static const BadUsbHidApi hid_api_usb = {
.kb_press = hid_usb_kb_press,
.kb_release = hid_usb_kb_release,
.mouse_press = hid_usb_mouse_press,
.mouse_release = hid_usb_mouse_release,
.mouse_scroll = hid_usb_mouse_scroll,
.mouse_move = hid_usb_mouse_move,
.consumer_press = hid_usb_consumer_press,
.consumer_release = hid_usb_consumer_release,
.release_all = hid_usb_release_all,
@@ -157,6 +187,27 @@ bool hid_ble_kb_release(void* inst, uint16_t button) {
return ble_profile_hid_kb_release(ble_hid->profile, button);
}
bool hid_ble_mouse_press(void* inst, uint8_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_mouse_press(ble_hid->profile, button);
}
bool hid_ble_mouse_release(void* inst, uint8_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_mouse_release(ble_hid->profile, button);
}
bool hid_ble_mouse_scroll(void* inst, int8_t delta) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_mouse_scroll(ble_hid->profile, delta);
}
bool hid_ble_mouse_move(void* inst, int8_t dx, int8_t dy) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
return ble_profile_hid_mouse_move(ble_hid->profile, dx, dy);
}
bool hid_ble_consumer_press(void* inst, uint16_t button) {
BleHidInstance* ble_hid = inst;
furi_assert(ble_hid);
@@ -174,6 +225,7 @@ bool hid_ble_release_all(void* 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);
state &= ble_profile_hid_mouse_release_all(ble_hid->profile);
return state;
}
@@ -191,6 +243,10 @@ static const BadUsbHidApi hid_api_ble = {
.kb_press = hid_ble_kb_press,
.kb_release = hid_ble_kb_release,
.mouse_press = hid_ble_mouse_press,
.mouse_release = hid_ble_mouse_release,
.mouse_scroll = hid_ble_mouse_scroll,
.mouse_move = hid_ble_mouse_move,
.consumer_press = hid_ble_consumer_press,
.consumer_release = hid_ble_consumer_release,
.release_all = hid_ble_release_all,
@@ -20,6 +20,10 @@ typedef struct {
bool (*kb_press)(void* inst, uint16_t button);
bool (*kb_release)(void* inst, uint16_t button);
bool (*mouse_press)(void* inst, uint8_t button);
bool (*mouse_release)(void* inst, uint8_t button);
bool (*mouse_scroll)(void* inst, int8_t delta);
bool (*mouse_move)(void* inst, int8_t dx, int8_t dy);
bool (*consumer_press)(void* inst, uint16_t button);
bool (*consumer_release)(void* inst, uint16_t button);
bool (*release_all)(void* inst);
@@ -40,11 +40,8 @@ static const uint8_t numpad_keys[10] = {
};
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;
char* first_space = strchr(line, ' ');
return first_space ? (first_space - line) : 0;
}
bool ducky_is_line_end(const char chr) {
@@ -180,29 +177,46 @@ static bool ducky_string_next(BadUsbScript* bad_usb) {
static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
uint32_t line_len = furi_string_size(line);
const char* line_tmp = furi_string_get_cstr(line);
const char* line_cstr = 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);
FURI_LOG_D(WORKER_TAG, "line:%s", line_cstr);
// Ducky Lang Functions
int32_t cmd_result = ducky_execute_cmd(bad_usb, line_tmp);
int32_t cmd_result = ducky_execute_cmd(bad_usb, line_cstr);
if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) {
return cmd_result;
}
// Special keys + modifiers
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
if(key == HID_KEYBOARD_NONE) {
return ducky_error(bad_usb, "No keycode defined for %s", line_tmp);
// Mouse Keys
uint16_t key = ducky_get_mouse_keycode_by_name(line_cstr);
if(key != HID_MOUSE_INVALID) {
bad_usb->hid->mouse_press(bad_usb->hid_inst, key);
bad_usb->hid->mouse_release(bad_usb->hid_inst, key);
return 0;
}
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_usb, line_tmp, true);
// Parse chain of modifiers linked by spaces and hyphens
uint16_t modifiers = 0;
while(1) {
key = ducky_get_next_modifier_keycode_by_name(&line_cstr);
if(key == HID_KEYBOARD_NONE) break;
modifiers |= key;
char next_char = *line_cstr;
if(next_char == ' ' || next_char == '-') line_cstr++;
}
// Main key
char next_char = *line_cstr;
uint16_t main_key = ducky_get_keycode_by_name(line_cstr);
if(!main_key && next_char) main_key = BADUSB_ASCII_TO_KEY(bad_usb, next_char);
key = modifiers | main_key;
if(key == 0 && next_char) ducky_error(bad_usb, "No keycode defined for %s", line_cstr);
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
return 0;
@@ -1,4 +1,5 @@
#include <furi_hal.h>
#include <lib/toolbox/strint.h>
#include "ducky_script.h"
#include "ducky_script_i.h"
@@ -124,34 +125,58 @@ static int32_t ducky_fnc_altstring(BadUsbScript* bad_usb, const char* line, int3
static int32_t ducky_fnc_hold(BadUsbScript* 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");
return ducky_error(bad_usb, "Too many keys are held");
}
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
return 0;
// Handle Mouse Keys here
uint16_t key = ducky_get_mouse_keycode_by_name(line);
if(key != HID_MOUSE_NONE) {
bad_usb->key_hold_nb++;
bad_usb->hid->mouse_press(bad_usb->hid_inst, key);
return 0;
}
// Handle Keyboard keys here
key = ducky_get_keycode(bad_usb, line, true);
if(key != HID_KEYBOARD_NONE) {
bad_usb->key_hold_nb++;
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
return 0;
}
// keyboard and mouse were none
return ducky_error(bad_usb, "Unknown keycode for %s", line);
}
static int32_t ducky_fnc_release(BadUsbScript* 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");
return ducky_error(bad_usb, "No keys are held");
}
bad_usb->key_hold_nb--;
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
return 0;
// Handle Mouse Keys here
uint16_t key = ducky_get_mouse_keycode_by_name(line);
if(key != HID_MOUSE_NONE) {
bad_usb->key_hold_nb--;
bad_usb->hid->mouse_release(bad_usb->hid_inst, key);
return 0;
}
//Handle Keyboard Keys here
key = ducky_get_keycode(bad_usb, line, true);
if(key != HID_KEYBOARD_NONE) {
bad_usb->key_hold_nb--;
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
return 0;
}
// keyboard and mouse were none
return ducky_error(bad_usb, "No keycode defined for %s", line);
}
static int32_t ducky_fnc_media(BadUsbScript* bad_usb, const char* line, int32_t param) {
@@ -191,6 +216,43 @@ static int32_t ducky_fnc_waitforbutton(BadUsbScript* bad_usb, const char* line,
return SCRIPT_STATE_WAIT_FOR_BTN;
}
static int32_t ducky_fnc_mouse_scroll(BadUsbScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[strcspn(line, " ") + 1];
int32_t mouse_scroll_dist = 0;
if(strint_to_int32(line, NULL, &mouse_scroll_dist, 10) != StrintParseNoError) {
return ducky_error(bad_usb, "Invalid Number %s", line);
}
bad_usb->hid->mouse_scroll(bad_usb->hid_inst, mouse_scroll_dist);
return 0;
}
static int32_t ducky_fnc_mouse_move(BadUsbScript* bad_usb, const char* line, int32_t param) {
UNUSED(param);
line = &line[strcspn(line, " ") + 1];
int32_t mouse_move_x = 0;
int32_t mouse_move_y = 0;
if(strint_to_int32(line, NULL, &mouse_move_x, 10) != StrintParseNoError) {
return ducky_error(bad_usb, "Invalid Number %s", line);
}
line = &line[strcspn(line, " ") + 1];
if(strint_to_int32(line, NULL, &mouse_move_y, 10) != StrintParseNoError) {
return ducky_error(bad_usb, "Invalid Number %s", line);
}
bad_usb->hid->mouse_move(bad_usb->hid_inst, mouse_move_x, mouse_move_y);
return 0;
}
static const DuckyCmd ducky_commands[] = {
{"REM", NULL, -1},
{"ID", NULL, -1},
@@ -213,6 +275,10 @@ static const DuckyCmd ducky_commands[] = {
{"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1},
{"MEDIA", ducky_fnc_media, -1},
{"GLOBE", ducky_fnc_globe, -1},
{"MOUSEMOVE", ducky_fnc_mouse_move, -1},
{"MOUSE_MOVE", ducky_fnc_mouse_move, -1},
{"MOUSESCROLL", ducky_fnc_mouse_scroll, -1},
{"MOUSE_SCROLL", ducky_fnc_mouse_scroll, -1},
};
#define TAG "BadUsb"
@@ -18,6 +18,9 @@ extern "C" {
#define FILE_BUFFER_LEN 16
#define HID_MOUSE_INVALID 0
#define HID_MOUSE_NONE 0
struct BadUsbScript {
FuriHalUsbHidConfig hid_cfg;
const BadUsbHidApi* hid;
@@ -51,10 +54,14 @@ uint32_t ducky_get_command_len(const char* line);
bool ducky_is_line_end(const char chr);
uint16_t ducky_get_next_modifier_keycode_by_name(const char** param);
uint16_t ducky_get_keycode_by_name(const char* param);
uint16_t ducky_get_media_keycode_by_name(const char* param);
uint8_t ducky_get_mouse_keycode_by_name(const char* param);
bool ducky_get_number(const char* param, uint32_t* val);
void ducky_numlock_on(BadUsbScript* bad_usb);
@@ -6,21 +6,16 @@ typedef struct {
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},
static const DuckyKey ducky_modifier_keys[] = {
{"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},
};
static const DuckyKey ducky_keys[] = {
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
@@ -108,6 +103,34 @@ static const DuckyKey ducky_media_keys[] = {
{"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT},
};
static const DuckyKey ducky_mouse_keys[] = {
{"LEFTCLICK", HID_MOUSE_BTN_LEFT},
{"LEFT_CLICK", HID_MOUSE_BTN_LEFT},
{"RIGHTCLICK", HID_MOUSE_BTN_RIGHT},
{"RIGHT_CLICK", HID_MOUSE_BTN_RIGHT},
{"MIDDLECLICK", HID_MOUSE_BTN_WHEEL},
{"MIDDLE_CLICK", HID_MOUSE_BTN_WHEEL},
{"WHEELCLICK", HID_MOUSE_BTN_WHEEL},
{"WHEEL_CLICK", HID_MOUSE_BTN_WHEEL},
};
uint16_t ducky_get_next_modifier_keycode_by_name(const char** param) {
const char* input_str = *param;
for(size_t i = 0; i < COUNT_OF(ducky_modifier_keys); i++) {
size_t key_cmd_len = strlen(ducky_modifier_keys[i].name);
if((strncmp(input_str, ducky_modifier_keys[i].name, key_cmd_len) == 0)) {
char next_char_after_key = input_str[key_cmd_len];
if(ducky_is_line_end(next_char_after_key) || (next_char_after_key == '-')) {
*param = &input_str[key_cmd_len];
return ducky_modifier_keys[i].keycode;
}
}
}
return HID_KEYBOARD_NONE;
}
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);
@@ -131,3 +154,15 @@ uint16_t ducky_get_media_keycode_by_name(const char* param) {
return HID_CONSUMER_UNASSIGNED;
}
uint8_t ducky_get_mouse_keycode_by_name(const char* param) {
for(size_t i = 0; i < COUNT_OF(ducky_mouse_keys); i++) {
size_t key_cmd_len = strlen(ducky_mouse_keys[i].name);
if((strncmp(param, ducky_mouse_keys[i].name, key_cmd_len) == 0) &&
(ducky_is_line_end(param[key_cmd_len]))) {
return ducky_mouse_keys[i].keycode;
}
}
return HID_MOUSE_INVALID;
}
@@ -0,0 +1,46 @@
ID 1234:abcd Generic:USB Keyboard
REM Declare ourselves as a generic usb keyboard
REM You can override this to use something else
REM Check the `lsusb` command to know your own devices IDs
DEFAULT_DELAY 200
DEFAULT_STRING_DELAY 100
DELAY 1000
REM Test all mouse functions
LEFTCLICK
RIGHTCLICK
MIDDLECLICK
DELAY 1000
MOUSEMOVE -10 0
REPEAT 20
MOUSEMOVE 0 10
REPEAT 20
MOUSEMOVE 10 0
REPEAT 20
MOUSEMOVE 0 -10
REPEAT 20
DELAY 1000
MOUSESCROLL -50
MOUSESCROLL 50
DELAY 1000
REM Verify Mouse hold working
HOLD LEFTCLICK
DELAY 2000
RELEASE LEFTCLICK
DELAY 1000
REM Verify KB hold working
HOLD M
DELAY 2000
RELEASE M
ENTER
+3
View File
@@ -30,6 +30,8 @@ GpioApp* gpio_app_alloc(void) {
app->gui = furi_record_open(RECORD_GUI);
app->gpio_items = gpio_items_alloc();
app->power = furi_record_open(RECORD_POWER);
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
@@ -100,6 +102,7 @@ void gpio_app_free(GpioApp* app) {
// Close records
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_POWER);
expansion_enable(app->expansion);
furi_record_close(RECORD_EXPANSION);
+2
View File
@@ -5,6 +5,7 @@
#include "scenes/gpio_scene.h"
#include "gpio_custom_event.h"
#include "usb_uart_bridge.h"
#include <power/power_service/power.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
@@ -27,6 +28,7 @@ struct GpioApp {
SceneManager* scene_manager;
Widget* widget;
DialogEx* dialog;
Power* power;
VariableItemList* var_item_list;
VariableItem* var_item_flow;
@@ -60,7 +60,7 @@ void gpio_scene_start_on_enter(void* context) {
GpioOtgSettingsNum,
gpio_scene_start_var_list_change_callback,
app);
if(furi_hal_power_is_otg_enabled()) {
if(power_is_otg_enabled(app->power)) {
variable_item_set_current_value_index(item, GpioOtgOn);
variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOn]);
} else {
@@ -80,9 +80,9 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GpioStartEventOtgOn) {
furi_hal_power_enable_otg();
power_enable_otg(app->power, true);
} else if(event.event == GpioStartEventOtgOff) {
furi_hal_power_disable_otg();
power_enable_otg(app->power, false);
} else if(event.event == GpioStartEventManualControl) {
scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemTest);
scene_manager_next_scene(app->scene_manager, GpioSceneTest);
+14 -16
View File
@@ -1,7 +1,6 @@
#include "usb_uart_bridge.h"
#include "usb_cdc.h"
#include <cli/cli_vcp.h>
#include <cli/cli.h>
#include <toolbox/api_lock.h>
#include <furi_hal.h>
#include <furi_hal_usb_cdc.h>
@@ -61,6 +60,8 @@ struct UsbUartBridge {
FuriApiLock cfg_lock;
CliVcp* cli_vcp;
uint8_t rx_buf[USB_CDC_PKT_LEN];
};
@@ -106,15 +107,11 @@ static void usb_uart_on_irq_rx_dma_cb(
static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
furi_hal_usb_unlock();
if(vcp_ch == 0) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_session_close(cli);
furi_record_close(RECORD_CLI);
cli_vcp_disable(usb_uart->cli_vcp);
furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
} else {
furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true);
Cli* cli = furi_record_open(RECORD_CLI);
cli_session_open(cli, &cli_vcp);
furi_record_close(RECORD_CLI);
cli_vcp_enable(usb_uart->cli_vcp);
}
furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart);
}
@@ -123,9 +120,7 @@ static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
UNUSED(usb_uart);
furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
if(vcp_ch != 0) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_session_close(cli);
furi_record_close(RECORD_CLI);
cli_vcp_disable(usb_uart->cli_vcp);
}
}
@@ -177,13 +172,15 @@ static int32_t usb_uart_worker(void* context) {
memcpy(&usb_uart->cfg, &usb_uart->cfg_new, sizeof(UsbUartConfig));
usb_uart->cli_vcp = furi_record_open(RECORD_CLI_VCP);
usb_uart->rx_stream = furi_stream_buffer_alloc(USB_UART_RX_BUF_SIZE, 1);
usb_uart->tx_sem = furi_semaphore_alloc(1, 1);
usb_uart->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
usb_uart->tx_thread =
furi_thread_alloc_ex("UsbUartTxWorker", 512, usb_uart_tx_thread, usb_uart);
furi_thread_alloc_ex("UsbUartTxWorker", 768, usb_uart_tx_thread, usb_uart);
usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch);
usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch);
@@ -288,8 +285,6 @@ static int32_t usb_uart_worker(void* context) {
usb_uart_update_ctrl_lines(usb_uart);
}
}
usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch);
usb_uart_serial_deinit(usb_uart);
furi_hal_gpio_init(USB_USART_DE_RE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
@@ -302,15 +297,18 @@ static int32_t usb_uart_worker(void* context) {
furi_thread_join(usb_uart->tx_thread);
furi_thread_free(usb_uart->tx_thread);
usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch);
usb_uart_serial_deinit(usb_uart);
furi_stream_buffer_free(usb_uart->rx_stream);
furi_mutex_free(usb_uart->usb_mutex);
furi_semaphore_free(usb_uart->tx_sem);
furi_hal_usb_unlock();
furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
Cli* cli = furi_record_open(RECORD_CLI);
cli_session_open(cli, &cli_vcp);
furi_record_close(RECORD_CLI);
cli_vcp_enable(usb_uart->cli_vcp);
furi_record_close(RECORD_CLI_VCP);
return 0;
}
+4 -4
View File
@@ -13,10 +13,10 @@ App(
)
App(
appid="ibutton_start",
apptype=FlipperAppType.STARTUP,
appid="cli_ikey",
targets=["f7"],
entry_point="ibutton_on_system_start",
apptype=FlipperAppType.PLUGIN,
entry_point="cli_ikey_ep",
requires=["cli"],
sources=["ibutton_cli.c"],
order=60,
)
+14 -25
View File
@@ -1,26 +1,14 @@
#include <furi.h>
#include <furi_hal.h>
#include <cli/cli.h>
#include <cli/cli_main_commands.h>
#include <toolbox/args.h>
#include <toolbox/pipe.h>
#include <ibutton/ibutton_key.h>
#include <ibutton/ibutton_worker.h>
#include <ibutton/ibutton_protocols.h>
static void ibutton_cli(Cli* cli, FuriString* args, void* context);
// app cli function
void ibutton_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli, cli);
furi_record_close(RECORD_CLI);
#else
UNUSED(ibutton_cli);
#endif
}
static void ibutton_cli_print_usage(void) {
printf("Usage:\r\n");
printf("ikey read\r\n");
@@ -92,7 +80,7 @@ static void ibutton_cli_worker_read_cb(void* context) {
furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE);
}
static void ibutton_cli_read(Cli* cli) {
static void ibutton_cli_read(PipeSide* pipe) {
iButtonProtocols* protocols = ibutton_protocols_alloc();
iButtonWorker* worker = ibutton_worker_alloc(protocols);
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
@@ -113,7 +101,7 @@ static void ibutton_cli_read(Cli* cli) {
break;
}
if(cli_cmd_interrupt_received(cli)) break;
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
}
ibutton_worker_stop(worker);
@@ -138,7 +126,7 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult
furi_event_flag_set(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE);
}
void ibutton_cli_write(Cli* cli, FuriString* args) {
void ibutton_cli_write(PipeSide* pipe, FuriString* args) {
iButtonProtocols* protocols = ibutton_protocols_alloc();
iButtonWorker* worker = ibutton_worker_alloc(protocols);
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
@@ -181,7 +169,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
}
}
if(cli_cmd_interrupt_received(cli)) break;
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
}
} while(false);
@@ -195,7 +183,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
furi_event_flag_free(write_context.event);
}
void ibutton_cli_emulate(Cli* cli, FuriString* args) {
void ibutton_cli_emulate(PipeSide* pipe, FuriString* args) {
iButtonProtocols* protocols = ibutton_protocols_alloc();
iButtonWorker* worker = ibutton_worker_alloc(protocols);
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
@@ -214,7 +202,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) {
ibutton_worker_emulate_start(worker, key);
while(!cli_cmd_interrupt_received(cli)) {
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(100);
};
@@ -228,8 +216,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) {
ibutton_protocols_free(protocols);
}
void ibutton_cli(Cli* cli, FuriString* args, void* context) {
UNUSED(cli);
static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
FuriString* cmd;
cmd = furi_string_alloc();
@@ -241,14 +228,16 @@ void ibutton_cli(Cli* cli, FuriString* args, void* context) {
}
if(furi_string_cmp_str(cmd, "read") == 0) {
ibutton_cli_read(cli);
ibutton_cli_read(pipe);
} else if(furi_string_cmp_str(cmd, "write") == 0) {
ibutton_cli_write(cli, args);
ibutton_cli_write(pipe, args);
} else if(furi_string_cmp_str(cmd, "emulate") == 0) {
ibutton_cli_emulate(cli, args);
ibutton_cli_emulate(pipe, args);
} else {
ibutton_cli_print_usage();
}
furi_string_free(cmd);
}
CLI_COMMAND_INTERFACE(ikey, execute, CliCommandFlagDefault, 1024, CLI_APPID);
+4 -4
View File
@@ -15,14 +15,14 @@ App(
)
App(
appid="infrared_start",
apptype=FlipperAppType.STARTUP,
appid="cli_ir",
targets=["f7"],
entry_point="infrared_on_system_start",
apptype=FlipperAppType.PLUGIN,
entry_point="cli_ir_ep",
requires=["cli"],
sources=[
"infrared_cli.c",
"infrared_brute_force.c",
"infrared_signal.c",
],
order=20,
)
+6 -6
View File
@@ -1,6 +1,6 @@
#include "infrared_app_i.h"
#include <furi_hal_power.h>
#include <power/power_service/power.h>
#include <string.h>
#include <toolbox/path.h>
@@ -501,12 +501,12 @@ void infrared_set_tx_pin(InfraredApp* infrared, FuriHalInfraredTxPin tx_pin) {
}
void infrared_enable_otg(InfraredApp* infrared, bool enable) {
if(enable) {
furi_hal_power_enable_otg();
} else {
furi_hal_power_disable_otg();
}
Power* power = furi_record_open(RECORD_POWER);
power_enable_otg(power, enable);
infrared->app_state.is_otg_enabled = enable;
furi_record_close(RECORD_POWER);
}
static void infrared_load_settings(InfraredApp* infrared) {
@@ -2,26 +2,61 @@
#include <stdlib.h>
#include <m-dict.h>
#include <m-array.h>
#include <flipper_format/flipper_format.h>
#include "infrared_signal.h"
ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST);
typedef struct {
uint32_t index;
uint32_t count;
size_t index;
SignalPositionArray_t signals;
} InfraredBruteForceRecord;
static inline void ir_bf_record_init(InfraredBruteForceRecord* record) {
record->index = 0;
SignalPositionArray_init(record->signals);
}
#define IR_BF_RECORD_INIT(r) (ir_bf_record_init(&(r)))
static inline void
ir_bf_record_init_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) {
dest->index = src->index;
SignalPositionArray_init_set(dest->signals, src->signals);
}
#define IR_BF_RECORD_INIT_SET(d, s) (ir_bf_record_init_set(&(d), &(s)))
static inline void
ir_bf_record_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) {
dest->index = src->index;
SignalPositionArray_set(dest->signals, src->signals);
}
#define IR_BF_RECORD_SET(d, s) (ir_bf_record_set(&(d), &(s)))
static inline void ir_bf_record_clear(InfraredBruteForceRecord* record) {
SignalPositionArray_clear(record->signals);
}
#define IR_BF_RECORD_CLEAR(r) (ir_bf_record_clear(&(r)))
#define IR_BF_RECORD_OPLIST \
(INIT(IR_BF_RECORD_INIT), \
INIT_SET(IR_BF_RECORD_INIT_SET), \
SET(IR_BF_RECORD_SET), \
CLEAR(IR_BF_RECORD_CLEAR))
DICT_DEF2(
InfraredBruteForceRecordDict,
FuriString*,
FURI_STRING_OPLIST,
InfraredBruteForceRecord,
M_POD_OPLIST);
IR_BF_RECORD_OPLIST);
struct InfraredBruteForce {
FlipperFormat* ff;
const char* db_filename;
FuriString* current_record_name;
InfraredBruteForceRecord current_record;
InfraredSignal* current_signal;
InfraredBruteForceRecordDict_t records;
bool is_started;
@@ -39,6 +74,7 @@ InfraredBruteForce* infrared_brute_force_alloc(void) {
}
void infrared_brute_force_free(InfraredBruteForce* brute_force) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
InfraredBruteForceRecordDict_clear(brute_force->records);
furi_string_free(brute_force->current_record_name);
@@ -46,11 +82,13 @@ void infrared_brute_force_free(InfraredBruteForce* brute_force) {
}
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
brute_force->db_filename = db_filename;
}
InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
furi_assert(brute_force->db_filename);
InfraredErrorCode error = InfraredErrorCodeNone;
@@ -66,19 +104,19 @@ InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* br
break;
}
bool signals_valid = false;
bool signal_valid = false;
while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) {
size_t signal_start = flipper_format_tell(ff);
error = infrared_signal_read_body(signal, ff);
signals_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
if(!signals_valid) break;
signal_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
if(!signal_valid) break;
InfraredBruteForceRecord* record =
InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
if(record) { //-V547
++(record->count);
}
furi_assert(record);
SignalPositionArray_push_back(record->signals, signal_start);
}
if(!signals_valid) break;
if(!signal_valid) break;
} while(false);
infrared_signal_free(signal);
@@ -93,6 +131,7 @@ bool infrared_brute_force_start(
InfraredBruteForce* brute_force,
uint32_t index,
uint32_t* record_count) {
furi_check(brute_force);
furi_assert(!brute_force->is_started);
bool success = false;
*record_count = 0;
@@ -103,9 +142,10 @@ bool infrared_brute_force_start(
InfraredBruteForceRecordDict_next(it)) {
const InfraredBruteForceRecordDict_itref_t* record = InfraredBruteForceRecordDict_cref(it);
if(record->value.index == index) {
*record_count = record->value.count;
*record_count = SignalPositionArray_size(record->value.signals);
if(*record_count) {
furi_string_set(brute_force->current_record_name, record->key);
brute_force->current_record = record->value;
}
break;
}
@@ -124,10 +164,12 @@ bool infrared_brute_force_start(
}
bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) {
furi_check(brute_force);
return brute_force->is_started;
}
void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
furi_check(brute_force);
furi_assert(brute_force->is_started);
furi_string_reset(brute_force->current_record_name);
infrared_signal_free(brute_force->current_signal);
@@ -138,25 +180,32 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
furi_record_close(RECORD_STORAGE);
}
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index) {
furi_check(brute_force);
furi_assert(brute_force->is_started);
const bool success = infrared_signal_search_by_name_and_read(
brute_force->current_signal,
brute_force->ff,
furi_string_get_cstr(brute_force->current_record_name)) ==
InfraredErrorCodeNone;
if(success) {
infrared_signal_transmit(brute_force->current_signal);
}
return success;
if(signal_index >= SignalPositionArray_size(brute_force->current_record.signals)) return false;
size_t signal_start =
*SignalPositionArray_cget(brute_force->current_record.signals, signal_index);
if(!flipper_format_seek(brute_force->ff, signal_start, FlipperFormatOffsetFromStart))
return false;
if(INFRARED_ERROR_PRESENT(
infrared_signal_read_body(brute_force->current_signal, brute_force->ff)))
return false;
infrared_signal_transmit(brute_force->current_signal);
return true;
}
void infrared_brute_force_add_record(
InfraredBruteForce* brute_force,
uint32_t index,
const char* name) {
InfraredBruteForceRecord value = {.index = index, .count = 0};
InfraredBruteForceRecord value;
ir_bf_record_init(&value);
value.index = index;
FuriString* key;
key = furi_string_alloc_set(name);
InfraredBruteForceRecordDict_set_at(brute_force->records, key, value);
@@ -78,18 +78,16 @@ bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force);
void infrared_brute_force_stop(InfraredBruteForce* brute_force);
/**
* @brief Send the next signal from the chosen category.
*
* This function is called repeatedly until no more signals are left
* in the chosen signal category.
*
* @warning Transmission must be started first by calling infrared_brute_force_start()
* before calling this function.
*
* @param[in,out] brute_force pointer to the instance to be used.
* @returns true if the next signal existed and could be transmitted, false otherwise.
* @brief Send an arbitrary signal from the chosen category.
*
* @param[in] brute_force pointer to the instance
* @param signal_index the index of the signal within the category, must be
* between 0 and `record_count` as told by
* `infrared_brute_force_start`
*
* @returns true on success, false otherwise
*/
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force);
bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index);
/**
* @brief Add a signal category to an InfraredBruteForce instance's dictionary.
+36 -44
View File
@@ -1,11 +1,11 @@
#include <cli/cli.h>
#include <cli/cli_i.h>
#include <cli/cli_main_commands.h>
#include <infrared.h>
#include <infrared_worker.h>
#include <furi_hal_infrared.h>
#include <flipper_format.h>
#include <toolbox/args.h>
#include <toolbox/strint.h>
#include <toolbox/pipe.h>
#include <m-dict.h>
#include "infrared_signal.h"
@@ -19,14 +19,14 @@
DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST)
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args);
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args);
static void infrared_cli_process_decode(Cli* cli, FuriString* args);
static void infrared_cli_process_universal(Cli* cli, FuriString* args);
static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args);
static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args);
static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args);
static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args);
static const struct {
const char* cmd;
void (*process_function)(Cli* cli, FuriString* args);
void (*process_function)(PipeSide* pipe, FuriString* args);
} infrared_cli_commands[] = {
{.cmd = "rx", .process_function = infrared_cli_start_ir_rx},
{.cmd = "tx", .process_function = infrared_cli_start_ir_tx},
@@ -38,7 +38,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
furi_assert(received_signal);
char buf[100];
size_t buf_cnt;
Cli* cli = (Cli*)context;
PipeSide* pipe = (PipeSide*)context;
if(infrared_worker_signal_is_decoded(received_signal)) {
const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal);
@@ -52,20 +52,20 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command,
message->repeat ? " R" : "");
cli_write(cli, (uint8_t*)buf, buf_cnt);
pipe_send(pipe, buf, buf_cnt);
} else {
const uint32_t* timings;
size_t timings_cnt;
infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
buf_cnt = snprintf(buf, sizeof(buf), "RAW, %zu samples:\r\n", timings_cnt);
cli_write(cli, (uint8_t*)buf, buf_cnt);
pipe_send(pipe, buf, buf_cnt);
for(size_t i = 0; i < timings_cnt; ++i) {
buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]);
cli_write(cli, (uint8_t*)buf, buf_cnt);
pipe_send(pipe, buf, buf_cnt);
}
buf_cnt = snprintf(buf, sizeof(buf), "\r\n");
cli_write(cli, (uint8_t*)buf, buf_cnt);
pipe_send(pipe, buf, buf_cnt);
}
}
@@ -124,9 +124,7 @@ static void infrared_cli_print_usage(void) {
infrared_cli_print_universal_remotes();
}
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
UNUSED(cli);
static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args) {
bool enable_decoding = true;
if(!furi_string_empty(args)) {
@@ -142,10 +140,10 @@ static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
InfraredWorker* worker = infrared_worker_alloc();
infrared_worker_rx_enable_signal_decoding(worker, enable_decoding);
infrared_worker_rx_start(worker);
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, pipe);
printf("Receiving %s INFRARED...\r\nPress Ctrl+C to abort\r\n", enable_decoding ? "" : "RAW");
while(!cli_cmd_interrupt_received(cli)) {
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(50);
}
@@ -214,8 +212,8 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) {
return infrared_signal_is_valid(signal);
}
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) {
UNUSED(cli);
static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args) {
UNUSED(pipe);
const char* str = furi_string_get_cstr(args);
InfraredSignal* signal = infrared_signal_alloc();
@@ -335,8 +333,8 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o
return ret;
}
static void infrared_cli_process_decode(Cli* cli, FuriString* args) {
UNUSED(cli);
static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args) {
UNUSED(pipe);
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage);
FlipperFormat* output_file = NULL;
@@ -455,8 +453,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) {
furi_record_close(RECORD_STORAGE);
}
static void
infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) {
static void infrared_cli_brute_force_signals(
PipeSide* pipe,
FuriString* remote_name,
FuriString* signal_name) {
InfraredBruteForce* brute_force = infrared_brute_force_alloc();
FuriString* remote_path = furi_string_alloc_printf(
"%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name));
@@ -475,25 +475,24 @@ static void
break;
}
uint32_t record_count;
uint32_t signal_count, current_signal = 0;
bool running = infrared_brute_force_start(
brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &record_count);
brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &signal_count);
if(record_count <= 0) {
if(signal_count <= 0) {
printf("Invalid signal name.\r\n");
break;
}
printf("Sending %lu signal(s)...\r\n", record_count);
printf("Sending %lu signal(s)...\r\n", signal_count);
printf("Press Ctrl-C to stop.\r\n");
int records_sent = 0;
while(running) {
running = infrared_brute_force_send_next(brute_force);
running = infrared_brute_force_send(brute_force, current_signal);
if(cli_cmd_interrupt_received(cli)) break;
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100));
printf("\r%d%% complete.", (int)((float)current_signal++ / (float)signal_count * 100));
fflush(stdout);
}
@@ -505,7 +504,7 @@ static void
infrared_brute_force_free(brute_force);
}
static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args) {
FuriString* arg1 = furi_string_alloc();
FuriString* arg2 = furi_string_alloc();
@@ -520,14 +519,14 @@ static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
} else if(furi_string_equal_str(arg1, "list")) {
infrared_cli_list_remote_signals(arg2);
} else {
infrared_cli_brute_force_signals(cli, arg1, arg2);
infrared_cli_brute_force_signals(pipe, arg1, arg2);
}
furi_string_free(arg1);
furi_string_free(arg2);
}
static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
if(furi_hal_infrared_is_busy()) {
printf("INFRARED is busy. Exiting.");
@@ -547,19 +546,12 @@ static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
}
if(i < COUNT_OF(infrared_cli_commands)) {
infrared_cli_commands[i].process_function(cli, args);
infrared_cli_commands[i].process_function(pipe, args);
} else {
infrared_cli_print_usage();
}
furi_string_free(command);
}
void infrared_on_system_start(void) {
#ifdef SRV_CLI
Cli* cli = (Cli*)furi_record_open(RECORD_CLI);
cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(infrared_cli_start_ir);
#endif
}
CLI_COMMAND_INTERFACE(ir, execute, CliCommandFlagDefault, 2048, CLI_APPID);
@@ -3,7 +3,7 @@
#include <stdint.h>
#include <stddef.h>
enum InfraredCustomEventType {
typedef enum {
// Reserve first 100 events for button types and indexes, starting from 0
InfraredCustomEventTypeReserved = 100,
InfraredCustomEventTypeMenuSelected,
@@ -13,7 +13,7 @@ enum InfraredCustomEventType {
InfraredCustomEventTypeTextEditDone,
InfraredCustomEventTypePopupClosed,
InfraredCustomEventTypeButtonSelected,
InfraredCustomEventTypeBackPressed,
InfraredCustomEventTypePopupInput,
InfraredCustomEventTypeTaskFinished,
InfraredCustomEventTypeRpcLoadFile,
@@ -27,7 +27,7 @@ enum InfraredCustomEventType {
InfraredCustomEventTypeGpioTxPinChanged,
InfraredCustomEventTypeGpioOtgChanged,
};
} InfraredCustomEventType;
#pragma pack(push, 1)
typedef union {
@@ -1896,3 +1896,42 @@ 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
#
# Model: Fujitsu ASTG12LVCC
#
name: Off
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 3258 1573 427 404 426 404 425 1180 428 403 427 1183 425 402 427 402 428 402 427 1180 428 1181 427 404 426 403 427 402 428 1181 427 1181 427 402 427 405 425 402 427 402 427 403 427 402 428 402 428 403 426 401 429 403 427 402 428 403 427 403 427 1180 428 401 428 404 425 401 428 402 427 402 427 402 427 402 428 1180 427 401 428 403 427 402 427 401 428 1180 427 401 428 402 427 402 428 402 427 401 428 403 427 1180 427 402 427 1180 427 1180 427 1177 429 1179 427 1179 427 1178 428
#
name: Dh
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 39677 99167 3233 1570 425 405 425 404 425 1184 424 405 425 1182 426 405 424 404 426 404 425 1181 427 1182 426 403 427 403 426 404 426 1183 425 1183 425 403 426 404 426 406 424 404 425 405 425 402 427 405 425 404 425 403 426 404 426 404 425 402 427 405 424 1182 425 402 427 404 426 403 426 404 425 404 426 404 425 404 425 1183 424 406 423 404 426 403 426 404 425 1181 427 1182 426 1181 426 1181 426 1181 425 1182 425 1181 426 1182 426 403 426 404 425 1182 426 404 425 404 425 405 425 404 426 403 426 403 427 404 426 404 426 1182 426 1182 426 403 426 404 426 1182 426 405 424 404 426 403 426 1182 426 405 425 403 426 1182 426 404 426 1183 425 403 426 403 426 404 425 403 426 405 425 403 426 1182 425 1182 425 403 427 404 425 1181 426 403 427 403 426 404 425 406 424 404 426 404 425 404 426 404 425 404 426 404 426 404 426 404 426 404 425 404 426 404 426 403 426 403 427 404 425 402 427 405 425 403 426 404 425 404 425 404 425 405 425 404 425 404 425 404 426 403 426 402 427 403 427 403 426 1182 425 404 426 404 425 403 426 1182 425 403 426 1181 426 403 427 403 426 404 425 405 425
#
name: Cool_hi
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 39674 99137 3228 1573 422 407 421 408 422 1185 423 408 422 1187 421 410 419 409 421 408 421 1186 422 1187 421 408 421 408 422 409 421 1187 420 1186 422 408 421 410 419 407 424 408 421 407 421 410 420 409 421 408 422 408 421 408 422 407 422 410 419 408 422 1187 420 408 421 408 422 408 421 408 421 408 421 408 420 409 421 1187 444 382 422 408 422 408 421 408 445 1162 419 1187 420 1184 423 1185 421 1184 423 1186 421 1186 421 1187 422 409 419 409 420 1186 422 407 420 409 422 409 420 407 422 407 422 411 419 406 421 409 422 1185 446 1162 420 409 421 409 421 1189 418 407 421 408 422 407 422 409 420 409 421 408 420 412 417 1187 421 407 422 408 420 410 421 408 421 409 421 409 445 384 420 410 421 407 421 407 422 409 421 1187 420 409 419 409 421 408 422 408 421 410 419 409 420 410 419 410 420 407 422 409 420 408 421 407 422 408 421 408 421 410 419 409 420 407 423 407 422 409 421 410 419 411 418 408 421 408 422 410 420 407 421 409 419 409 421 409 419 408 422 407 422 407 422 409 420 1188 419 409 421 409 420 409 419 1189 419 1186 421 1188 419 1187 420 408 421 407 422 1188 419
#
name: Cool_lo
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 39689 99188 3229 1576 421 407 422 409 420 1188 419 409 421 1187 422 408 422 409 419 410 419 1187 422 1186 423 410 419 409 421 409 420 1187 420 1188 420 410 447 382 420 409 422 410 446 383 422 409 420 407 422 410 395 435 420 408 422 407 422 410 420 409 445 1162 420 410 420 409 420 410 420 409 421 410 419 409 421 409 419 1189 420 407 422 409 395 437 419 410 418 1186 422 1186 423 1187 420 1185 422 1188 420 1184 421 1188 419 1188 419 408 420 410 419 1186 421 408 420 410 419 410 419 409 419 411 418 409 421 410 419 409 420 1187 445 1163 419 412 417 409 420 1188 419 409 419 410 420 409 444 1164 418 1187 419 1189 419 409 419 1187 420 408 422 409 419 410 420 409 419 410 419 434 393 411 420 409 421 408 421 409 419 409 421 1188 418 410 419 410 420 410 418 412 417 409 445 385 419 409 420 410 420 408 419 409 421 410 419 411 419 408 446 382 421 409 420 409 420 410 418 409 420 409 419 410 419 412 442 384 419 411 416 412 419 409 420 410 419 410 419 410 418 410 420 409 420 409 420 434 394 1187 419 412 417 410 418 410 420 1188 419 1187 420 1188 419 410 419 1188 419 411 418 434 396
#
name: Heat_hi
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 39692 99118 3226 1571 423 406 423 405 423 1185 421 405 424 1183 423 406 422 408 422 405 423 1184 446 1160 422 409 420 406 424 405 424 1185 422 1183 424 404 425 406 422 408 421 406 423 407 422 406 423 407 421 406 424 407 422 407 422 404 425 407 421 409 420 1185 422 406 423 408 421 404 424 405 424 407 447 383 421 406 424 1184 447 380 424 406 422 409 421 407 423 1183 447 1159 424 1185 422 1185 421 1184 422 1185 422 1185 421 1185 423 407 423 406 423 1185 423 406 424 406 423 408 446 381 424 406 423 408 421 406 424 406 423 1185 423 1184 422 407 423 407 422 1187 421 408 421 407 423 407 423 405 424 1185 422 1186 421 1184 423 407 422 407 422 1186 422 407 422 406 423 408 422 405 423 408 447 383 420 409 421 406 423 407 423 1184 423 407 423 407 422 408 421 408 423 406 424 406 422 409 422 406 423 408 421 408 421 406 422 406 424 407 422 406 423 409 421 407 422 408 423 406 423 406 423 409 446 382 447 384 420 407 423 405 424 406 423 406 423 407 423 407 422 406 423 405 422 407 424 406 422 1185 422 406 423 407 422 1183 423 1184 422 407 422 1185 423 1186 421 1184 424 407 422 1185 422
#
name: Heat_lo
type: raw
frequency: 38000
duty_cycle: 0.330000
data: 39670 99106 3227 1570 424 406 422 407 422 1183 424 405 424 1184 448 380 423 407 421 406 424 1183 424 1185 421 406 423 404 424 405 424 1184 423 1182 425 407 448 380 423 407 422 405 423 406 422 406 424 405 423 407 421 406 423 407 422 405 424 405 423 406 423 1184 422 408 421 408 422 405 424 406 421 407 422 406 423 405 423 1183 424 406 423 405 423 405 423 405 423 1186 421 1184 422 1184 422 1185 422 1184 447 1159 423 1184 422 1184 422 408 421 407 423 1184 421 407 448 381 422 405 423 409 421 406 422 406 422 407 422 406 423 1183 423 1185 422 406 423 405 424 1184 423 408 421 405 424 405 424 1184 422 1185 422 1184 422 407 423 408 420 409 420 1185 447 382 423 405 423 408 421 406 423 407 422 406 423 406 423 408 421 406 423 1183 424 407 422 406 424 405 424 406 423 407 423 406 423 408 422 407 422 405 424 408 421 407 422 407 422 406 423 406 423 407 422 406 423 406 422 408 421 407 422 408 421 407 422 406 423 408 422 406 423 405 423 409 422 406 422 406 423 406 423 407 422 407 423 405 424 1184 423 407 421 406 424 1184 423 1184 422 407 423 1183 423 405 424 1184 423 409 420 407 422
#
File diff suppressed because it is too large Load Diff
@@ -2,15 +2,30 @@
#include <dolphin/dolphin.h>
void infrared_scene_universal_common_item_callback(void* context, uint32_t index) {
#pragma pack(push, 1)
typedef union {
uint32_t packed_value;
struct {
bool is_paused;
uint8_t padding;
uint16_t signal_index;
};
} InfraredSceneState;
#pragma pack(pop)
void infrared_scene_universal_common_item_callback(void* context, uint32_t index, InputType type) {
InfraredApp* infrared = context;
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
if(type == InputTypeShort) {
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
}
}
static void infrared_scene_universal_common_progress_back_callback(void* context) {
static void infrared_scene_universal_common_progress_input_callback(
void* context,
InfraredProgressViewInput input) {
InfraredApp* infrared = context;
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1);
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypePopupInput, input);
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
}
@@ -19,8 +34,8 @@ static void
ViewStack* view_stack = infrared->view_stack;
InfraredProgressView* progress = infrared->progress;
infrared_progress_view_set_progress_total(progress, record_count);
infrared_progress_view_set_back_callback(
progress, infrared_scene_universal_common_progress_back_callback, infrared);
infrared_progress_view_set_input_callback(
progress, infrared_scene_universal_common_progress_input_callback, infrared);
view_stack_add_view(view_stack, infrared_progress_view_get_view(progress));
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
}
@@ -51,29 +66,111 @@ void infrared_scene_universal_common_on_enter(void* context) {
infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback);
}
static void infrared_scene_universal_common_handle_popup_input(
InfraredApp* infrared,
InfraredProgressViewInput input) {
InfraredBruteForce* brute_force = infrared->brute_force;
SceneManager* scene_manager = infrared->scene_manager;
uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager);
switch(input) {
case InfraredProgressViewInputStop: {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
break;
}
case InfraredProgressViewInputPause: {
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
infrared_progress_view_set_paused(infrared->progress, true);
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
scene_state.is_paused = true;
if(scene_state.signal_index)
scene_state.signal_index--; // when running, the state stores the next index
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
break;
}
case InfraredProgressViewInputResume: {
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
infrared_progress_view_set_paused(infrared->progress, false);
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
scene_state.is_paused = false;
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
break;
}
case InfraredProgressViewInputNextSignal: {
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
scene_state.signal_index++;
if(infrared_progress_view_set_progress(infrared->progress, scene_state.signal_index + 1))
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
break;
}
case InfraredProgressViewInputPreviousSignal: {
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
if(scene_state.signal_index) {
scene_state.signal_index--;
if(infrared_progress_view_set_progress(
infrared->progress, scene_state.signal_index + 1))
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
}
break;
}
case InfraredProgressViewInputSendSingle: {
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
infrared_brute_force_send(infrared->brute_force, scene_state.signal_index);
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
break;
}
default:
furi_crash();
}
}
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
InfraredApp* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
InfraredBruteForce* brute_force = infrared->brute_force;
uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager);
bool consumed = false;
if(infrared_brute_force_is_started(brute_force)) {
if(event.type == SceneManagerEventTypeTick) {
bool success = infrared_brute_force_send_next(brute_force);
if(success) {
success = infrared_progress_view_increase_progress(infrared->progress);
InfraredSceneState scene_state = {
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
if(!scene_state.is_paused) {
bool success = infrared_brute_force_send(brute_force, scene_state.signal_index);
if(success) {
success = infrared_progress_view_set_progress(
infrared->progress, scene_state.signal_index + 1);
scene_state.signal_index++;
scene_manager_set_scene_state(
scene_manager, scene_id, scene_state.packed_value);
}
if(!success) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
}
consumed = true;
}
if(!success) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
}
consumed = true;
} else if(event.type == SceneManagerEventTypeCustom) {
if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) {
infrared_brute_force_stop(brute_force);
infrared_scene_universal_common_hide_popup(infrared);
uint16_t event_type;
int16_t event_value;
infrared_custom_event_unpack(event.event, &event_type, &event_value);
if(event_type == InfraredCustomEventTypePopupInput) {
infrared_scene_universal_common_handle_popup_input(infrared, event_value);
consumed = true;
}
consumed = true;
}
} else {
if(event.type == SceneManagerEventTypeBack) {
@@ -87,6 +184,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e
if(event_type == InfraredCustomEventTypeButtonSelected) {
uint32_t record_count;
if(infrared_brute_force_start(brute_force, event_value, &record_count)) {
scene_manager_set_scene_state(infrared->scene_manager, scene_id, 0);
dolphin_deed(DolphinDeedIrSend);
infrared_scene_universal_common_show_popup(infrared, record_count);
} else {
@@ -5,4 +5,4 @@
void infrared_scene_universal_common_on_enter(void* context);
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event);
void infrared_scene_universal_common_on_exit(void* context);
void infrared_scene_universal_common_item_callback(void* context, uint32_t index);
void infrared_scene_universal_common_item_callback(void* context, uint32_t index, InputType type);
@@ -118,7 +118,7 @@ void infrared_scene_universal_ac_on_enter(void* context) {
button_panel_add_icon(button_panel, 0, 60, &I_cool_30x51);
button_panel_add_icon(button_panel, 34, 60, &I_heat_30x51);
button_panel_add_label(button_panel, 4, 10, FontPrimary, "AC remote");
button_panel_add_label(button_panel, 24, 10, FontPrimary, "AC");
infrared_scene_universal_common_on_enter(context);
}
@@ -114,7 +114,7 @@ void infrared_scene_universal_audio_on_enter(void* context) {
context);
infrared_brute_force_add_record(brute_force, i++, "Vol_up");
button_panel_add_label(button_panel, 1, 10, FontPrimary, "Mus. remote");
button_panel_add_label(button_panel, 1, 10, FontPrimary, "Audio player");
button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30);
infrared_scene_universal_common_on_enter(context);
@@ -63,7 +63,7 @@ void infrared_scene_universal_projector_on_enter(void* context) {
context);
infrared_brute_force_add_record(brute_force, i++, "Vol_dn");
button_panel_add_label(button_panel, 3, 11, FontPrimary, "Proj. remote");
button_panel_add_label(button_panel, 10, 11, FontPrimary, "Projector");
button_panel_add_icon(button_panel, 17, 72, &I_vol_ac_text_30x30);
infrared_scene_universal_common_on_enter(context);
@@ -91,7 +91,7 @@ void infrared_scene_universal_tv_on_enter(void* context) {
context);
infrared_brute_force_add_record(brute_force, i++, "Ch_prev");
button_panel_add_label(button_panel, 5, 10, FontPrimary, "TV remote");
button_panel_add_label(button_panel, 25, 10, FontPrimary, "TV");
infrared_scene_universal_common_on_enter(context);
}
@@ -14,54 +14,80 @@
struct InfraredProgressView {
View* view;
InfraredProgressViewBackCallback back_callback;
InfraredProgressViewInputCallback input_callback;
void* context;
};
typedef struct {
size_t progress;
size_t progress_total;
bool is_paused;
} InfraredProgressViewModel;
bool infrared_progress_view_increase_progress(InfraredProgressView* progress) {
furi_assert(progress);
bool result = false;
InfraredProgressViewModel* model = view_get_model(progress->view);
if(model->progress < model->progress_total) {
++model->progress;
result = model->progress < model->progress_total;
}
view_commit_model(progress->view, true);
return result;
}
static void infrared_progress_view_draw_callback(Canvas* canvas, void* _model) {
InfraredProgressViewModel* model = (InfraredProgressViewModel*)_model;
uint8_t x = 0;
uint8_t y = 36;
uint8_t y = 25;
uint8_t width = 63;
uint8_t height = 59;
uint8_t height = 81;
elements_bold_rounded_frame(canvas, x, y, width, height);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(
canvas, x + 34, y + 9, AlignCenter, AlignCenter, "Sending ...");
canvas,
x + 32,
y + 9,
AlignCenter,
AlignCenter,
model->is_paused ? "Paused" : "Sending...");
float progress_value = (float)model->progress / model->progress_total;
elements_progress_bar(canvas, x + 4, y + 19, width - 7, progress_value);
uint8_t percent_value = 100 * model->progress / model->progress_total;
char percents_string[10] = {0};
snprintf(percents_string, sizeof(percents_string), "%d%%", percent_value);
char progress_string[16] = {0};
if(model->is_paused) {
snprintf(
progress_string,
sizeof(progress_string),
"%zu/%zu",
model->progress,
model->progress_total);
} else {
uint8_t percent_value = 100 * model->progress / model->progress_total;
snprintf(progress_string, sizeof(progress_string), "%d%%", percent_value);
}
elements_multiline_text_aligned(
canvas, x + 33, y + 37, AlignCenter, AlignCenter, percents_string);
canvas, x + 33, y + 37, AlignCenter, AlignCenter, progress_string);
canvas_draw_icon(canvas, x + 14, y + height - 14, &I_Pin_back_arrow_10x8);
canvas_draw_str(canvas, x + 30, y + height - 6, "= stop");
uint8_t buttons_x = x + (model->is_paused ? 10 : 14);
uint8_t buttons_y = y + (model->is_paused ? 46 : 50);
canvas_draw_icon(canvas, buttons_x + 0, buttons_y + 0, &I_Pin_back_arrow_10x8);
canvas_draw_str(canvas, buttons_x + 14, buttons_y + 8, model->is_paused ? "resume" : "stop");
canvas_draw_icon(canvas, buttons_x + 1, buttons_y + 10, &I_Ok_btn_9x9);
canvas_draw_str(canvas, buttons_x + 14, buttons_y + 17, model->is_paused ? "send 1" : "pause");
if(model->is_paused) {
canvas_draw_icon(canvas, buttons_x + 2, buttons_y + 21, &I_ButtonLeftSmall_3x5);
canvas_draw_icon(canvas, buttons_x + 7, buttons_y + 21, &I_ButtonRightSmall_3x5);
canvas_draw_str(canvas, buttons_x + 14, buttons_y + 26, "select");
}
}
bool infrared_progress_view_set_progress(InfraredProgressView* instance, uint16_t progress) {
bool result;
with_view_model(
instance->view,
InfraredProgressViewModel * model,
{
result = progress <= model->progress_total;
if(result) model->progress = progress;
},
true);
return result;
}
void infrared_progress_view_set_progress_total(
@@ -74,15 +100,45 @@ void infrared_progress_view_set_progress_total(
view_commit_model(progress->view, false);
}
void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_paused) {
with_view_model(
instance->view, InfraredProgressViewModel * model, { model->is_paused = is_paused; }, true);
}
bool infrared_progress_view_input_callback(InputEvent* event, void* context) {
InfraredProgressView* instance = context;
if((event->type == InputTypeShort) && (event->key == InputKeyBack)) {
if(instance->back_callback) {
instance->back_callback(instance->context);
}
if(event->type == InputTypePress || event->type == InputTypeRelease) {
return false;
}
if(!instance->input_callback) return false;
with_view_model(
instance->view,
InfraredProgressViewModel * model,
{
if(model->is_paused) {
if(event->key == InputKeyLeft)
instance->input_callback(
instance->context, InfraredProgressViewInputPreviousSignal);
else if(event->key == InputKeyRight)
instance->input_callback(
instance->context, InfraredProgressViewInputNextSignal);
else if(event->key == InputKeyOk)
instance->input_callback(
instance->context, InfraredProgressViewInputSendSingle);
else if(event->key == InputKeyBack)
instance->input_callback(instance->context, InfraredProgressViewInputResume);
} else {
if(event->key == InputKeyOk)
instance->input_callback(instance->context, InfraredProgressViewInputPause);
else if(event->key == InputKeyBack)
instance->input_callback(instance->context, InfraredProgressViewInputStop);
}
},
false);
return true;
}
@@ -106,12 +162,12 @@ void infrared_progress_view_free(InfraredProgressView* progress) {
free(progress);
}
void infrared_progress_view_set_back_callback(
void infrared_progress_view_set_input_callback(
InfraredProgressView* instance,
InfraredProgressViewBackCallback callback,
InfraredProgressViewInputCallback callback,
void* context) {
furi_assert(instance);
instance->back_callback = callback;
instance->input_callback = callback;
instance->context = context;
}
@@ -10,11 +10,20 @@
extern "C" {
#endif
/** Anonumous instance */
/** Anonymous instance */
typedef struct InfraredProgressView InfraredProgressView;
/** Callback for back button handling */
typedef void (*InfraredProgressViewBackCallback)(void*);
typedef enum {
InfraredProgressViewInputStop,
InfraredProgressViewInputPause,
InfraredProgressViewInputResume,
InfraredProgressViewInputPreviousSignal,
InfraredProgressViewInputNextSignal,
InfraredProgressViewInputSendSingle,
} InfraredProgressViewInput;
/** Callback for input handling */
typedef void (*InfraredProgressViewInputCallback)(void* context, InfraredProgressViewInput event);
/** Allocate and initialize Infrared view
*
@@ -35,13 +44,12 @@ void infrared_progress_view_free(InfraredProgressView* instance);
*/
View* infrared_progress_view_get_view(InfraredProgressView* instance);
/** Increase progress on progress view module
/** Set progress of progress view module
*
* @param instance view module
* @retval true - value is incremented and maximum is reached,
* false - value is incremented and maximum is not reached
* @param progress progress value
*/
bool infrared_progress_view_increase_progress(InfraredProgressView* instance);
bool infrared_progress_view_set_progress(InfraredProgressView* instance, uint16_t progress);
/** Set maximum progress value
*
@@ -52,15 +60,22 @@ void infrared_progress_view_set_progress_total(
InfraredProgressView* instance,
uint16_t progress_max);
/** Set back button callback
/** Selects the variant of the View
*
* @param instance view instance
* @param is_paused the "paused" variant is displayed if true; the "sending" one if false
*/
void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_paused);
/** Set input callback
*
* @param instance - view module
* @param callback - callback to call for back button
* @param callback - callback to call for input
* @param context - context to pass to callback
*/
void infrared_progress_view_set_back_callback(
void infrared_progress_view_set_input_callback(
InfraredProgressView* instance,
InfraredProgressViewBackCallback callback,
InfraredProgressViewInputCallback callback,
void* context);
#ifdef __cplusplus
+4 -4
View File
@@ -13,10 +13,10 @@ App(
)
App(
appid="lfrfid_start",
appid="cli_rfid",
targets=["f7"],
apptype=FlipperAppType.STARTUP,
entry_point="lfrfid_on_system_start",
apptype=FlipperAppType.PLUGIN,
entry_point="cli_rfid_ep",
requires=["cli"],
sources=["lfrfid_cli.c"],
order=50,
)
+23 -33
View File
@@ -1,11 +1,12 @@
#include <furi.h>
#include <furi_hal.h>
#include <stdarg.h>
#include <cli/cli.h>
#include <cli/cli_main_commands.h>
#include <lib/toolbox/args.h>
#include <lib/lfrfid/lfrfid_worker.h>
#include <storage/storage.h>
#include <toolbox/stream/file_stream.h>
#include <toolbox/pipe.h>
#include <toolbox/varint.h>
@@ -14,15 +15,6 @@
#include <lfrfid/lfrfid_raw_file.h>
#include <toolbox/pulse_protocols/pulse_glue.h>
static void lfrfid_cli(Cli* cli, FuriString* args, void* context);
// app cli function
void lfrfid_on_system_start(void) {
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL);
furi_record_close(RECORD_CLI);
}
static void lfrfid_cli_print_usage(void) {
printf("Usage:\r\n");
printf("rfid read <optional: normal | indala> - read in ASK/PSK mode\r\n");
@@ -49,7 +41,7 @@ static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId p
furi_event_flag_set(context->event, 1 << result);
}
static void lfrfid_cli_read(Cli* cli, FuriString* args) {
static void lfrfid_cli_read(PipeSide* pipe, FuriString* args) {
FuriString* type_string;
type_string = furi_string_alloc();
LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto;
@@ -96,7 +88,7 @@ static void lfrfid_cli_read(Cli* cli, FuriString* args) {
}
}
if(cli_cmd_interrupt_received(cli)) break;
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
}
lfrfid_worker_stop(worker);
@@ -192,7 +184,7 @@ static void lfrfid_cli_write_callback(LFRFIDWorkerWriteResult result, void* ctx)
furi_event_flag_set(events, 1 << result);
}
static void lfrfid_cli_write(Cli* cli, FuriString* args) {
static void lfrfid_cli_write(PipeSide* pipe, FuriString* args) {
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
ProtocolId protocol;
@@ -212,7 +204,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) {
(1 << LFRFIDWorkerWriteProtocolCannotBeWritten) |
(1 << LFRFIDWorkerWriteFobCannotBeWritten);
while(!cli_cmd_interrupt_received(cli)) {
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100);
if(flags != (unsigned)FuriFlagErrorTimeout) {
if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) {
@@ -239,7 +231,7 @@ static void lfrfid_cli_write(Cli* cli, FuriString* args) {
furi_event_flag_free(event);
}
static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
static void lfrfid_cli_emulate(PipeSide* pipe, FuriString* args) {
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
ProtocolId protocol;
@@ -254,7 +246,7 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
lfrfid_worker_emulate_start(worker, protocol);
printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n");
while(!cli_cmd_interrupt_received(cli)) {
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
furi_delay_ms(100);
}
printf("Emulation stopped\r\n");
@@ -265,8 +257,8 @@ static void lfrfid_cli_emulate(Cli* cli, FuriString* args) {
protocol_dict_free(dict);
}
static void lfrfid_cli_raw_analyze(Cli* cli, FuriString* args) {
UNUSED(cli);
static void lfrfid_cli_raw_analyze(PipeSide* pipe, FuriString* args) {
UNUSED(pipe);
FuriString *filepath, *info_string;
filepath = furi_string_alloc();
info_string = furi_string_alloc();
@@ -392,9 +384,7 @@ static void lfrfid_cli_raw_read_callback(LFRFIDWorkerReadRawResult result, void*
furi_event_flag_set(event, 1 << result);
}
static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) {
UNUSED(cli);
static void lfrfid_cli_raw_read(PipeSide* pipe, FuriString* args) {
FuriString *filepath, *type_string;
filepath = furi_string_alloc();
type_string = furi_string_alloc();
@@ -452,7 +442,7 @@ static void lfrfid_cli_raw_read(Cli* cli, FuriString* args) {
}
}
if(cli_cmd_interrupt_received(cli)) break;
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
}
if(overrun) {
@@ -479,9 +469,7 @@ static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result,
furi_event_flag_set(event, 1 << result);
}
static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) {
UNUSED(cli);
static void lfrfid_cli_raw_emulate(PipeSide* pipe, FuriString* args) {
FuriString* filepath;
filepath = furi_string_alloc();
Storage* storage = furi_record_open(RECORD_STORAGE);
@@ -527,7 +515,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) {
}
}
if(cli_cmd_interrupt_received(cli)) break;
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
}
if(overrun) {
@@ -548,7 +536,7 @@ static void lfrfid_cli_raw_emulate(Cli* cli, FuriString* args) {
furi_string_free(filepath);
}
static void lfrfid_cli(Cli* cli, FuriString* args, void* context) {
static void execute(PipeSide* pipe, FuriString* args, void* context) {
UNUSED(context);
FuriString* cmd;
cmd = furi_string_alloc();
@@ -560,20 +548,22 @@ static void lfrfid_cli(Cli* cli, FuriString* args, void* context) {
}
if(furi_string_cmp_str(cmd, "read") == 0) {
lfrfid_cli_read(cli, args);
lfrfid_cli_read(pipe, args);
} else if(furi_string_cmp_str(cmd, "write") == 0) {
lfrfid_cli_write(cli, args);
lfrfid_cli_write(pipe, args);
} else if(furi_string_cmp_str(cmd, "emulate") == 0) {
lfrfid_cli_emulate(cli, args);
lfrfid_cli_emulate(pipe, args);
} else if(furi_string_cmp_str(cmd, "raw_read") == 0) {
lfrfid_cli_raw_read(cli, args);
lfrfid_cli_raw_read(pipe, args);
} else if(furi_string_cmp_str(cmd, "raw_emulate") == 0) {
lfrfid_cli_raw_emulate(cli, args);
lfrfid_cli_raw_emulate(pipe, args);
} else if(furi_string_cmp_str(cmd, "raw_analyze") == 0) {
lfrfid_cli_raw_analyze(cli, args);
lfrfid_cli_raw_analyze(pipe, args);
} else {
lfrfid_cli_print_usage();
}
furi_string_free(cmd);
}
CLI_COMMAND_INTERFACE(rfid, execute, CliCommandFlagDefault, 2048, CLI_APPID);
-1
View File
@@ -8,7 +8,6 @@
#include <assets_icons.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <cli/cli.h>
#include <notification/notification_messages.h>
#include <gui/modules/submenu.h>
+15 -5
View File
@@ -248,10 +248,20 @@ App(
)
App(
appid="nfc_start",
appid="disney_infinity_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="disney_infinity_plugin_ep",
targets=["f7"],
apptype=FlipperAppType.STARTUP,
entry_point="nfc_on_system_start",
sources=["nfc_cli.c"],
order=30,
requires=["nfc"],
fap_libs=["mbedtls"],
sources=["plugins/supported_cards/disney_infinity.c"],
)
App(
appid="cli_nfc",
targets=["f7"],
apptype=FlipperAppType.PLUGIN,
entry_point="cli_nfc_ep",
requires=["cli"],
sources=["nfc_cli.c"],
)
@@ -37,11 +37,13 @@ void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) {
}
void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str) {
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
const uint16_t block_count = iso15693_3_get_block_count(data);
const uint8_t block_size = iso15693_3_get_block_size(data);
if((data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) &&
(block_count > 0 && block_size > 0)) {
furi_string_cat(str, "\e#Memory data\n\e*--------------------\n");
const uint16_t block_count = iso15693_3_get_block_count(data);
const uint8_t block_size = iso15693_3_get_block_size(data);
const uint16_t display_block_count =
MIN(NFC_RENDER_ISO15693_3_MAX_BYTES / block_size, block_count);
@@ -14,7 +14,8 @@ enum {
SubmenuIndexDetectReader = SubmenuIndexCommonMax,
SubmenuIndexWrite,
SubmenuIndexUpdate,
SubmenuIndexDictAttack
SubmenuIndexDictAttack,
SubmenuIndexCrackNonces,
};
static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) {
@@ -128,6 +129,13 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) {
SubmenuIndexDictAttack,
nfc_protocol_support_common_submenu_callback,
instance);
submenu_add_item(
submenu,
"Crack nonces in MFKey32",
SubmenuIndexCrackNonces,
nfc_protocol_support_common_submenu_callback,
instance);
}
}
@@ -193,6 +201,11 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexDetectReader) {
scene_manager_set_scene_state(
instance->scene_manager,
NfcSceneSaveConfirm,
NfcSceneSaveConfirmStateDetectReader);
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm);
dolphin_deed(DolphinDeedNfcDetectReader);
consumed = true;
@@ -205,6 +218,11 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag
} else if(event.event == SubmenuIndexCommonEdit) {
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
consumed = true;
} else if(event.event == SubmenuIndexCrackNonces) {
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneSaveConfirm, NfcSceneSaveConfirmStateCrackNonces);
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm);
consumed = true;
}
}

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