diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index aab3ff353..5af8ceedb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 +/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 @drunkbatya -/scripts/toolchain/ @skotopes @DrZlo13 @hedger @gsurkov @drunkbatya +/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 -/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 @drunkbatya +/.github/workflows/ @DrZlo13 @hedger @gsurkov diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bc2178ae..66a2bdf73 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,7 @@ env: DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work FBT_GIT_SUBMODULE_SHALLOW: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: main: diff --git a/.github/workflows/build_compact.yml b/.github/workflows/build_compact.yml index f45275204..f98ab8b49 100644 --- a/.github/workflows/build_compact.yml +++ b/.github/workflows/build_compact.yml @@ -6,6 +6,7 @@ on: env: FBT_TOOLCHAIN_PATH: /runner/_work FBT_GIT_SUBMODULE_SHALLOW: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: compact: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1fa025085..064c43655 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,6 +9,7 @@ on: env: TARGETS: f7 DEFAULT_TARGET: f7 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: check-secret: @@ -54,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' }} diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml index 90302ce1a..9ee7884c8 100644 --- a/.github/workflows/merge_report.yml +++ b/.github/workflows/merge_report.yml @@ -7,6 +7,7 @@ on: env: FBT_TOOLCHAIN_PATH: /runner/_work + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: merge_report: diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 8eb6fea48..3f1a164bc 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -11,6 +11,7 @@ env: DEFAULT_TARGET: f7 FBT_TOOLCHAIN_PATH: /runner/_work FBT_GIT_SUBMODULE_SHALLOW: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: analyse_c_cpp: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index d37337452..d8d83abb8 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -5,64 +5,56 @@ on: env: TARGETS: f7 DEFAULT_TARGET: f7 - FBT_TOOLCHAIN_PATH: /opt + FBT_TOOLCHAIN_PATH: /opt/ FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: run_units_on_bench: - runs-on: [self-hosted, FlipperZeroUnitTest] + runs-on: [ self-hosted, FlipperZeroTest ] steps: - - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} - - name: 'Get flipper from device manager (mock)' - id: device - run: | - echo "flipper=auto" >> $GITHUB_OUTPUT - - name: 'Flash unit tests firmware' id: flashing if: success() - timeout-minutes: 10 + timeout-minutes: 5 run: | - ./fbt resources firmware_latest flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 + source scripts/toolchain/fbtenv.sh + ./fbt resources firmware_latest flash LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 - - name: 'Wait for flipper and format ext' - id: format_ext + + - name: 'Copy assets and unit data, reboot and wait for flipper' + id: copy if: steps.flashing.outcome == 'success' timeout-minutes: 5 run: | source scripts/toolchain/fbtenv.sh - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=120 await_flipper - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext - - - name: 'Copy assets and unit data, reboot and wait for flipper' - id: copy - if: steps.format_ext.outcome == 'success' - timeout-minutes: 7 - run: | - source scripts/toolchain/fbtenv.sh - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=15 await_flipper - rm -rf build/latest/resources/dolphin - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send build/latest/resources /ext - python3 scripts/power.py -p ${{steps.device.outputs.flipper}} reboot - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=15 await_flipper + python3 scripts/testops.py -t=15 await_flipper + python3 scripts/storage.py -f send build/latest/resources /ext + python3 scripts/storage.py -f send /region_data /ext/.int/.region_data + python3 scripts/power.py reboot + python3 scripts/testops.py -t=30 await_flipper - 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 -p ${{steps.device.outputs.flipper}} + python3 scripts/testops.py run_units + + - name: 'Upload test results' + if: failure() && steps.flashing.outcome == 'success' && steps.run_units.outcome != 'skipped' + uses: actions/upload-artifact@v4 + with: + name: unit-tests_output + path: unit_tests*.txt - name: 'Check GDB output' if: failure() && steps.flashing.outcome == 'success' run: | - ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt gdb_trace_all LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml index dbe5df883..b5265df9c 100644 --- a/.github/workflows/updater_test.yml +++ b/.github/workflows/updater_test.yml @@ -1,73 +1,39 @@ name: 'Updater test' on: pull_request: + env: TARGETS: f7 DEFAULT_TARGET: f7 - FBT_TOOLCHAIN_PATH: /opt + FBT_TOOLCHAIN_PATH: /opt/ FBT_GIT_SUBMODULE_SHALLOW: 1 jobs: test_updater_on_bench: - runs-on: [self-hosted, FlipperZeroUpdaterTest] + runs-on: [self-hosted, FlipperZeroTest ] steps: - - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 1 - submodules: false ref: ${{ github.event.pull_request.head.sha }} - - name: 'Get flipper from device manager (mock)' - id: device - run: | - echo "flipper=auto" >> $GITHUB_OUTPUT - echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT - - name: 'Flashing target firmware' id: first_full_flash - timeout-minutes: 10 + timeout-minutes: 5 run: | source scripts/toolchain/fbtenv.sh - ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=180 await_flipper + python3 scripts/testops.py -t=180 await_flipper + ./fbt flash_usb_full FORCE=1 + - name: 'Validating updater' id: second_full_flash - timeout-minutes: 10 + timeout-minutes: 5 if: success() run: | source scripts/toolchain/fbtenv.sh - ./fbt flash_usb PORT=${{steps.device.outputs.flipper}} FORCE=1 - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=180 await_flipper + python3 scripts/testops.py -t=180 await_flipper + ./fbt flash_usb FORCE=1 + python3 scripts/testops.py -t=180 await_flipper - - name: 'Get last release tag' - id: release_tag - if: failure() - run: | - echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT - - - name: 'Wipe workspace' - run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; - - - name: 'Checkout latest release' - uses: actions/checkout@v4 - if: failure() - with: - fetch-depth: 1 - ref: ${{ steps.release_tag.outputs.tag }} - - - name: 'Flash last release' - if: failure() - run: | - ./fbt flash SWD_TRANSPORT_SERIAL=${{steps.device.outputs.stlink}} FORCE=1 - - - name: 'Wait for flipper and format ext' - if: failure() - run: | - source scripts/toolchain/fbtenv.sh - python3 scripts/testops.py -p=${{steps.device.outputs.flipper}} -t=180 await_flipper - python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext diff --git a/.gitignore b/.gitignore index 84b8e8319..79f2a8058 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,7 @@ PVS-Studio.log # JS packages node_modules/ + +# cli_perf script output in case of errors +/block.bin +/return_block.bin diff --git a/.vscode/example/settings.json.tmpl b/.vscode/example/settings.json.tmpl index 5e5b5dcf4..b8e9f81cd 100644 --- a/.vscode/example/settings.json.tmpl +++ b/.vscode/example/settings.json.tmpl @@ -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-*", diff --git a/SConstruct b/SConstruct index 127967968..44aa3bc17 100644 --- a/SConstruct +++ b/SConstruct @@ -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", diff --git a/applications/debug/accessor/accessor_app.cpp b/applications/debug/accessor/accessor_app.cpp index 8d43acc13..59f5d6cc7 100644 --- a/applications/debug/accessor/accessor_app.cpp +++ b/applications/debug/accessor/accessor_app.cpp @@ -2,6 +2,7 @@ #include #include #include +#include void AccessorApp::run(void) { AccessorEvent event; @@ -35,16 +36,18 @@ AccessorApp::AccessorApp() : text_store{0} { notification = static_cast(furi_record_open(RECORD_NOTIFICATION)); expansion = static_cast(furi_record_open(RECORD_EXPANSION)); + power = static_cast(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); } diff --git a/applications/debug/accessor/accessor_app.h b/applications/debug/accessor/accessor_app.h index 890552f5f..1961f9cbf 100644 --- a/applications/debug/accessor/accessor_app.h +++ b/applications/debug/accessor/accessor_app.h @@ -7,6 +7,7 @@ #include #include #include +#include class AccessorApp { public: @@ -53,4 +54,5 @@ private: NotificationApp* notification; Expansion* expansion; + Power* power; }; diff --git a/applications/debug/accessor/application.fam b/applications/debug/accessor/application.fam index 65a6c8666..4b24f98eb 100644 --- a/applications/debug/accessor/application.fam +++ b/applications/debug/accessor/application.fam @@ -6,6 +6,5 @@ App( entry_point="accessor_app", requires=["gui"], stack_size=4 * 1024, - order=40, fap_category="Debug", ) diff --git a/applications/debug/battery_test_app/application.fam b/applications/debug/battery_test_app/application.fam index 5f4acd83d..0ab68c086 100644 --- a/applications/debug/battery_test_app/application.fam +++ b/applications/debug/battery_test_app/application.fam @@ -8,7 +8,6 @@ App( "power", ], stack_size=1 * 1024, - order=130, fap_category="Debug", fap_libs=["assets"], ) diff --git a/applications/debug/blink_test/application.fam b/applications/debug/blink_test/application.fam index d7d873fb9..066e7a207 100644 --- a/applications/debug/blink_test/application.fam +++ b/applications/debug/blink_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="blink_test_app", requires=["gui"], stack_size=1 * 1024, - order=10, fap_category="Debug", ) diff --git a/applications/debug/bt_debug_app/application.fam b/applications/debug/bt_debug_app/application.fam index 8ed1ccc05..831b51ade 100644 --- a/applications/debug/bt_debug_app/application.fam +++ b/applications/debug/bt_debug_app/application.fam @@ -13,6 +13,5 @@ App( "bt_debug", ], stack_size=1 * 1024, - order=110, fap_category="Debug", ) diff --git a/applications/debug/ccid_test/application.fam b/applications/debug/ccid_test/application.fam index ad9076770..dfd6de05f 100644 --- a/applications/debug/ccid_test/application.fam +++ b/applications/debug/ccid_test/application.fam @@ -10,6 +10,5 @@ App( "ccid_test", ], stack_size=1 * 1024, - order=120, fap_category="Debug", ) diff --git a/applications/debug/crash_test/crash_test.c b/applications/debug/crash_test/crash_test.c index 2b2be13d6..4c5a53ceb 100644 --- a/applications/debug/crash_test/crash_test.c +++ b/applications/debug/crash_test/crash_test.c @@ -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; } diff --git a/applications/debug/direct_draw/application.fam b/applications/debug/direct_draw/application.fam index 11b3bc6ba..1e7d4b1c4 100644 --- a/applications/debug/direct_draw/application.fam +++ b/applications/debug/direct_draw/application.fam @@ -5,6 +5,5 @@ App( entry_point="direct_draw_app", requires=["gui", "input"], stack_size=2 * 1024, - order=70, fap_category="Debug", ) diff --git a/applications/debug/display_test/application.fam b/applications/debug/display_test/application.fam index 7b2357b01..1e0d3f775 100644 --- a/applications/debug/display_test/application.fam +++ b/applications/debug/display_test/application.fam @@ -6,6 +6,5 @@ App( requires=["gui"], fap_libs=["u8g2"], stack_size=1 * 1024, - order=120, fap_category="Debug", ) diff --git a/applications/debug/event_loop_blink_test/application.fam b/applications/debug/event_loop_blink_test/application.fam index 7d42ad339..6e4aaa48d 100644 --- a/applications/debug/event_loop_blink_test/application.fam +++ b/applications/debug/event_loop_blink_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="event_loop_blink_test_app", requires=["input"], stack_size=1 * 1024, - order=20, fap_category="Debug", ) diff --git a/applications/debug/expansion_test/application.fam b/applications/debug/expansion_test/application.fam index 9bc4b2fc2..30f325a92 100644 --- a/applications/debug/expansion_test/application.fam +++ b/applications/debug/expansion_test/application.fam @@ -6,7 +6,6 @@ App( requires=["expansion_start"], fap_libs=["assets"], stack_size=1 * 1024, - order=20, fap_category="Debug", fap_file_assets="assets", ) diff --git a/applications/debug/file_browser_test/application.fam b/applications/debug/file_browser_test/application.fam index bb08ad2c5..b610558e9 100644 --- a/applications/debug/file_browser_test/application.fam +++ b/applications/debug/file_browser_test/application.fam @@ -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", ) diff --git a/applications/debug/keypad_test/application.fam b/applications/debug/keypad_test/application.fam index 90851950b..ed7408e71 100644 --- a/applications/debug/keypad_test/application.fam +++ b/applications/debug/keypad_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="keypad_test_app", requires=["gui"], stack_size=1 * 1024, - order=30, fap_category="Debug", ) diff --git a/applications/debug/lfrfid_debug/application.fam b/applications/debug/lfrfid_debug/application.fam index 323f77818..d312dbda2 100644 --- a/applications/debug/lfrfid_debug/application.fam +++ b/applications/debug/lfrfid_debug/application.fam @@ -11,6 +11,5 @@ App( "lfrfid_debug", ], stack_size=1 * 1024, - order=100, fap_category="Debug", ) diff --git a/applications/debug/loader_chaining_a/application.fam b/applications/debug/loader_chaining_a/application.fam new file mode 100644 index 000000000..408efdcb1 --- /dev/null +++ b/applications/debug/loader_chaining_a/application.fam @@ -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", +) diff --git a/applications/debug/loader_chaining_a/loader_chaining_a.c b/applications/debug/loader_chaining_a/loader_chaining_a.c new file mode 100644 index 000000000..b3f303e2d --- /dev/null +++ b/applications/debug/loader_chaining_a/loader_chaining_a.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/applications/debug/loader_chaining_b/application.fam b/applications/debug/loader_chaining_b/application.fam new file mode 100644 index 000000000..5b8767e50 --- /dev/null +++ b/applications/debug/loader_chaining_b/application.fam @@ -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", +) diff --git a/applications/debug/loader_chaining_b/loader_chaining_b.c b/applications/debug/loader_chaining_b/loader_chaining_b.c new file mode 100644 index 000000000..439e6e25e --- /dev/null +++ b/applications/debug/loader_chaining_b/loader_chaining_b.c @@ -0,0 +1,27 @@ +#include +#include +#include + +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; +} diff --git a/applications/debug/locale_test/application.fam b/applications/debug/locale_test/application.fam index d341122f9..757be7155 100644 --- a/applications/debug/locale_test/application.fam +++ b/applications/debug/locale_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="locale_test_app", requires=["gui", "locale"], stack_size=2 * 1024, - order=70, fap_category="Debug", ) diff --git a/applications/debug/rpc_debug_app/application.fam b/applications/debug/rpc_debug_app/application.fam index d71065afa..795f83287 100644 --- a/applications/debug/rpc_debug_app/application.fam +++ b/applications/debug/rpc_debug_app/application.fam @@ -5,6 +5,5 @@ App( entry_point="rpc_debug_app", requires=["gui", "rpc_start", "notification"], stack_size=2 * 1024, - order=10, fap_category="Debug", ) diff --git a/applications/debug/speaker_debug/application.fam b/applications/debug/speaker_debug/application.fam index 68d8b188b..c7f5629a7 100644 --- a/applications/debug/speaker_debug/application.fam +++ b/applications/debug/speaker_debug/application.fam @@ -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"], ) diff --git a/applications/debug/speaker_debug/speaker_debug.c b/applications/debug/speaker_debug/speaker_debug.c index 3f685ab30..6a9956b07 100644 --- a/applications/debug/speaker_debug/speaker_debug.c +++ b/applications/debug/speaker_debug/speaker_debug.c @@ -1,8 +1,10 @@ #include #include #include -#include #include +#include +#include +#include #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) { diff --git a/applications/debug/subghz_test/application.fam b/applications/debug/subghz_test/application.fam index 1b3e19d73..927ca7f89 100644 --- a/applications/debug/subghz_test/application.fam +++ b/applications/debug/subghz_test/application.fam @@ -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", diff --git a/applications/debug/text_box_element_test/application.fam b/applications/debug/text_box_element_test/application.fam index 5e1abcddc..78dfe75f6 100644 --- a/applications/debug/text_box_element_test/application.fam +++ b/applications/debug/text_box_element_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="text_box_element_test_app", requires=["gui"], stack_size=1 * 1024, - order=140, fap_category="Debug", ) diff --git a/applications/debug/text_box_view_test/application.fam b/applications/debug/text_box_view_test/application.fam index e356a278e..6a3225d88 100644 --- a/applications/debug/text_box_view_test/application.fam +++ b/applications/debug/text_box_view_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="text_box_view_test_app", requires=["gui"], stack_size=1 * 1024, - order=140, fap_category="Debug", ) diff --git a/applications/debug/uart_echo/application.fam b/applications/debug/uart_echo/application.fam index 7b030bcfa..d95302364 100644 --- a/applications/debug/uart_echo/application.fam +++ b/applications/debug/uart_echo/application.fam @@ -5,6 +5,5 @@ App( entry_point="uart_echo_app", requires=["gui"], stack_size=2 * 1024, - order=70, fap_category="Debug", ) diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index 4298dc33d..7150b830b 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -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; diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index dec3283e4..72b8cafcb 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -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( @@ -236,3 +236,11 @@ App( entry_point="get_api", requires=["unit_tests"], ) + +App( + appid="test_pipe", + sources=["tests/common/*.c", "tests/pipe/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) diff --git a/applications/debug/unit_tests/resources/unit_tests/js/basic.js b/applications/debug/unit_tests/resources/unit_tests/js/basic.js index a08041e9f..0ef904ecb 100644 --- a/applications/debug/unit_tests/resources/unit_tests/js/basic.js +++ b/applications/debug/unit_tests/resources/unit_tests/js/basic.js @@ -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]); diff --git a/applications/debug/unit_tests/test_runner.c b/applications/debug/unit_tests/test_runner.c index de29e91b3..ad7d31b02 100644 --- a/applications/debug/unit_tests/test_runner.c +++ b/applications/debug/unit_tests/test_runner.c @@ -2,8 +2,9 @@ #include "tests/test_api.h" -#include +#include #include +#include #include #include #include @@ -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; } diff --git a/applications/debug/unit_tests/test_runner.h b/applications/debug/unit_tests/test_runner.h index 43aba8bb1..0e9495263 100644 --- a/applications/debug/unit_tests/test_runner.h +++ b/applications/debug/unit_tests/test_runner.h @@ -1,12 +1,12 @@ #pragma once #include +#include 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); diff --git a/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c b/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c index 888a66444..934634c71 100644 --- a/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c +++ b/applications/debug/unit_tests/tests/flipper_format/flipper_format_test.c @@ -265,6 +265,7 @@ static bool test_write(const char* file_name) { if(!flipper_format_file_open_always(file, file_name)) break; if(!flipper_format_write_header_cstr(file, test_filetype, test_version)) break; if(!flipper_format_write_comment_cstr(file, "This is comment")) break; + if(!flipper_format_write_empty_line(file)) break; if(!flipper_format_write_string_cstr(file, test_string_key, test_string_data)) break; if(!flipper_format_write_int32(file, test_int_key, test_int_data, COUNT_OF(test_int_data))) break; diff --git a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c index 73f38ab77..08e0e8ad2 100644 --- a/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_event_loop_test.c @@ -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 = {}; diff --git a/applications/debug/unit_tests/tests/furi/furi_stdio_test.c b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c new file mode 100644 index 000000000..d80bd7bd5 --- /dev/null +++ b/applications/debug/unit_tests/tests/furi/furi_stdio_test.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include "../test.h" // IWYU pragma: keep + +#define TAG "StdioTest" + +#define CONTEXT_MAGIC ((void*)0xDEADBEEF) + +// stdin + +static char mock_in[256]; +static size_t mock_in_len, mock_in_pos; + +static void set_mock_in(const char* str) { + size_t len = strlen(str); + strcpy(mock_in, str); + mock_in_len = len; + mock_in_pos = 0; +} + +static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context) { + UNUSED(wait); + furi_check(context == CONTEXT_MAGIC); + size_t remaining = mock_in_len - mock_in_pos; + size = MIN(remaining, size); + memcpy(buffer, mock_in + mock_in_pos, size); + mock_in_pos += size; + return size; +} + +void test_stdin(void) { + 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]; + + // plain in + set_mock_in("Hello, World!\n"); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq("Hello, World!\n", buf); + mu_assert_int_eq(EOF, getchar()); + + // ungetc + ungetc('i', stdin); + ungetc('H', stdin); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq("Hi", buf); + mu_assert_int_eq(EOF, getchar()); + + // ungetc + plain in + set_mock_in(" World"); + ungetc('i', stdin); + ungetc('H', stdin); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq("Hi World", buf); + mu_assert_int_eq(EOF, getchar()); + + // partial plain in + set_mock_in("Hello, World!\n"); + fgets(buf, strlen("Hello") + 1, stdin); + mu_assert_string_eq("Hello", buf); + mu_assert_int_eq(',', getchar()); + fgets(buf, sizeof(buf), stdin); + mu_assert_string_eq(" World!\n", buf); + + furi_thread_set_stdin_callback(in_cb, in_ctx); +} + +// stdout + +static FuriString* mock_out; +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); + // there's no furi_string_cat_strn :( + for(size_t i = 0; i < size; i++) { + furi_string_push_back(mock_out, data[i]); + } +} + +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, original_out_ctx); + mu_assert_string_eq(expected, furi_string_get_cstr(mock_out)); + furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC); + + furi_string_reset(mock_out); +} + +void test_stdout(void) { + 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(); + + puts("Hello, World!"); + assert_and_clear_mock_out("Hello, World!\n"); + + printf("He"); + printf("llo!"); + fflush(stdout); + assert_and_clear_mock_out("Hello!"); + + furi_string_free(mock_out); + furi_thread_set_stdout_callback(original_out_cb, original_out_ctx); +} diff --git a/applications/debug/unit_tests/tests/furi/furi_test.c b/applications/debug/unit_tests/tests/furi/furi_test.c index 193a8124d..03d49aa7e 100644 --- a/applications/debug/unit_tests/tests/furi/furi_test.c +++ b/applications/debug/unit_tests/tests/furi/furi_test.c @@ -8,8 +8,11 @@ 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); +void test_stdout(void); static int foo = 0; @@ -44,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(); } @@ -52,6 +59,11 @@ MU_TEST(mu_test_furi_primitives) { test_furi_primitives(); } +MU_TEST(mu_test_stdio) { + test_stdin(); + test_stdout(); +} + MU_TEST_SUITE(test_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_check); @@ -61,6 +73,8 @@ 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); } diff --git a/applications/debug/unit_tests/tests/js/js_test.c b/applications/debug/unit_tests/tests/js/js_test.c index af590e899..dd695a3a1 100644 --- a/applications/debug/unit_tests/tests/js/js_test.c +++ b/applications/debug/unit_tests/tests/js/js_test.c @@ -6,9 +6,12 @@ #include #include +#include #include +#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); diff --git a/applications/debug/unit_tests/tests/minunit.h b/applications/debug/unit_tests/tests/minunit.h index 9ca3bb403..c854c4673 100644 --- a/applications/debug/unit_tests/tests/minunit.h +++ b/applications/debug/unit_tests/tests/minunit.h @@ -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; \ diff --git a/applications/debug/unit_tests/tests/nfc/nfc_test.c b/applications/debug/unit_tests/tests/nfc/nfc_test.c index 4ba934b6d..e028b8041 100644 --- a/applications/debug/unit_tests/tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/tests/nfc/nfc_test.c @@ -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); diff --git a/applications/debug/unit_tests/tests/pipe/pipe_test.c b/applications/debug/unit_tests/tests/pipe/pipe_test.c new file mode 100644 index 000000000..f0227b353 --- /dev/null +++ b/applications/debug/unit_tests/tests/pipe/pipe_test.c @@ -0,0 +1,142 @@ +#include "../test.h" // IWYU pragma: keep + +#include +#include + +#define PIPE_SIZE 128U +#define PIPE_TRG_LEVEL 1U + +MU_TEST(pipe_test_trivial) { + PipeSideBundle bundle = pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL); + PipeSide* alice = bundle.alices_side; + PipeSide* bob = bundle.bobs_side; + + mu_assert_int_eq(PipeRoleAlice, pipe_role(alice)); + mu_assert_int_eq(PipeRoleBob, pipe_role(bob)); + mu_assert_int_eq(PipeStateOpen, pipe_state(alice)); + mu_assert_int_eq(PipeStateOpen, pipe_state(bob)); + + mu_assert_int_eq(PIPE_SIZE, pipe_spaces_available(alice)); + mu_assert_int_eq(PIPE_SIZE, pipe_spaces_available(bob)); + mu_assert_int_eq(0, pipe_bytes_available(alice)); + mu_assert_int_eq(0, pipe_bytes_available(bob)); + + for(uint8_t i = 0;; ++i) { + mu_assert_int_eq(PIPE_SIZE - i, pipe_spaces_available(alice)); + mu_assert_int_eq(i, pipe_bytes_available(bob)); + + 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)); + + furi_check(pipe_send(bob, &i, sizeof(uint8_t)) == sizeof(uint8_t)); + } + + pipe_free(alice); + mu_assert_int_eq(PipeStateBroken, pipe_state(bob)); + + 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; + furi_check(pipe_receive(bob, &value, sizeof(uint8_t)) == sizeof(uint8_t)); + + mu_assert_int_eq(i, value); + } + + pipe_free(bob); +} + +typedef enum { + TestFlagDataArrived = 1 << 0, + TestFlagSpaceFreed = 1 << 1, + TestFlagBecameBroken = 1 << 2, +} TestFlag; + +typedef struct { + TestFlag flag; + FuriEventLoop* event_loop; +} AncillaryThreadContext; + +static void on_data_arrived(PipeSide* pipe, void* context) { + AncillaryThreadContext* ctx = context; + ctx->flag |= TestFlagDataArrived; + 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; +} + +static void on_became_broken(PipeSide* pipe, void* context) { + UNUSED(pipe); + AncillaryThreadContext* ctx = context; + ctx->flag |= TestFlagBecameBroken; + furi_event_loop_stop(ctx->event_loop); +} + +static int32_t ancillary_thread(void* context) { + PipeSide* pipe = context; + AncillaryThreadContext thread_ctx = { + .flag = 0, + .event_loop = furi_event_loop_alloc(), + }; + + pipe_attach_to_event_loop(pipe, thread_ctx.event_loop); + pipe_set_callback_context(pipe, &thread_ctx); + pipe_set_data_arrived_callback(pipe, on_data_arrived, 0); + pipe_set_space_freed_callback(pipe, on_space_freed, FuriEventLoopEventFlagEdge); + pipe_set_broken_callback(pipe, on_became_broken, 0); + + furi_event_loop_run(thread_ctx.event_loop); + + pipe_detach_from_event_loop(pipe); + pipe_free(pipe); + furi_event_loop_free(thread_ctx.event_loop); + return thread_ctx.flag; +} + +MU_TEST(pipe_test_event_loop) { + PipeSideBundle bundle = pipe_alloc(PIPE_SIZE, PIPE_TRG_LEVEL); + PipeSide* alice = bundle.alices_side; + PipeSide* bob = bundle.bobs_side; + + FuriThread* thread = furi_thread_alloc_ex("PipeTestAnc", 2048, ancillary_thread, bob); + furi_thread_start(thread); + + const char* message = "Hello!"; + pipe_send(alice, message, strlen(message)); + + char buffer_1[16]; + size_t size = pipe_receive(alice, buffer_1, strlen(message)); + buffer_1[size] = 0; + + pipe_free(alice); + furi_thread_join(thread); + + mu_assert_string_eq(message, buffer_1); + mu_assert_int_eq( + TestFlagDataArrived | TestFlagSpaceFreed | TestFlagBecameBroken, + furi_thread_get_return_code(thread)); + + furi_thread_free(thread); +} + +MU_TEST_SUITE(test_pipe) { + MU_RUN_TEST(pipe_test_trivial); + MU_RUN_TEST(pipe_test_event_loop); +} + +int run_minunit_test_pipe(void) { + MU_RUN_SUITE(test_pipe); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_pipe) diff --git a/applications/debug/unit_tests/tests/rpc/rpc_test.c b/applications/debug/unit_tests/tests/rpc/rpc_test.c index 5d26bdb30..82ab872ce 100644 --- a/applications/debug/unit_tests/tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/tests/rpc/rpc_test.c @@ -3,7 +3,6 @@ #include #include -#include #include #include #include diff --git a/applications/debug/unit_tests/tests/storage/storage_test.c b/applications/debug/unit_tests/tests/storage/storage_test.c index 75c52ef9a..9d5c68a44 100644 --- a/applications/debug/unit_tests/tests/storage/storage_test.c +++ b/applications/debug/unit_tests/tests/storage/storage_test.c @@ -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); } diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 10b089022..4f0e4dec9 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -8,6 +8,7 @@ #include #include #include +#include static constexpr auto unit_tests_api_table = sort(create_array_t( API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)), @@ -38,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( 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))); diff --git a/applications/debug/unit_tests/unit_tests.c b/applications/debug/unit_tests/unit_tests.c index 237cb9080..8605cb781 100644 --- a/applications/debug/unit_tests/unit_tests.c +++ b/applications/debug/unit_tests/unit_tests.c @@ -1,21 +1,24 @@ #include -#include +#include +#include +#include +#include #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 } diff --git a/applications/debug/usb_mouse/application.fam b/applications/debug/usb_mouse/application.fam index 7747613d5..e57b3f108 100644 --- a/applications/debug/usb_mouse/application.fam +++ b/applications/debug/usb_mouse/application.fam @@ -5,6 +5,5 @@ App( entry_point="usb_mouse_app", requires=["gui"], stack_size=1 * 1024, - order=60, fap_category="Debug", ) diff --git a/applications/debug/usb_test/application.fam b/applications/debug/usb_test/application.fam index 463bb4a26..6481518b4 100644 --- a/applications/debug/usb_test/application.fam +++ b/applications/debug/usb_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="usb_test_app", requires=["gui"], stack_size=1 * 1024, - order=50, fap_category="Debug", ) diff --git a/applications/debug/vibro_test/application.fam b/applications/debug/vibro_test/application.fam index c35a7223f..dafa83eac 100644 --- a/applications/debug/vibro_test/application.fam +++ b/applications/debug/vibro_test/application.fam @@ -5,6 +5,5 @@ App( entry_point="vibro_test_app", requires=["gui"], stack_size=1 * 1024, - order=20, fap_category="Debug", ) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index ae3556396..357214505 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -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; diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index e5af819e9..4b225b70c 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -22,7 +22,7 @@ #include #include -#include +#include #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. */ diff --git a/applications/main/application.fam b/applications/main/application.fam index 0a90ee224..9d8604206 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -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", ], ) diff --git a/applications/main/archive/application.fam b/applications/main/archive/application.fam index f0a980ab0..00075107a 100644 --- a/applications/main/archive/application.fam +++ b/applications/main/archive/application.fam @@ -7,5 +7,5 @@ App( requires=["gui"], stack_size=4 * 1024, icon="A_FileManager_14", - order=0, + order=10, ) diff --git a/applications/main/archive/helpers/archive_apps.c b/applications/main/archive/helpers/archive_apps.c index 7aca29364..65d5a5af9 100644 --- a/applications/main/archive/helpers/archive_apps.c +++ b/applications/main/archive/helpers/archive_apps.c @@ -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) { diff --git a/applications/main/archive/helpers/archive_apps.h b/applications/main/archive/helpers/archive_apps.h index d9d1dec34..f5d050387 100644 --- a/applications/main/archive/helpers/archive_apps.h +++ b/applications/main/archive/helpers/archive_apps.h @@ -4,12 +4,14 @@ typedef enum { ArchiveAppTypeU2f, + ArchiveAppTypeSetting, ArchiveAppTypeUnknown, ArchiveAppsTotal, } ArchiveAppTypeEnum; static const ArchiveFileTypeEnum app_file_types[] = { [ArchiveAppTypeU2f] = ArchiveFileTypeU2f, + [ArchiveAppTypeSetting] = ArchiveFileTypeSetting, [ArchiveAppTypeUnknown] = ArchiveFileTypeUnknown, }; diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index fea6ddf7f..b5dc37b1d 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -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[] = { diff --git a/applications/main/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c index 351d68c1d..5ed3adfe3 100644 --- a/applications/main/archive/helpers/archive_favorites.c +++ b/applications/main/archive/helpers/archive_favorites.c @@ -1,9 +1,10 @@ - #include "archive_favorites.h" #include "archive_files.h" #include "archive_apps.h" #include "archive_browser.h" +#include + #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); +} diff --git a/applications/main/archive/helpers/archive_favorites.h b/applications/main/archive/helpers/archive_favorites.h index 75070c44d..3a43a0e75 100644 --- a/applications/main/archive/helpers/archive_favorites.h +++ b/applications/main/archive/helpers/archive_favorites.h @@ -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); diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index ad7662342..ea354bd5a 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -20,6 +20,7 @@ typedef enum { ArchiveFileTypeFolder, ArchiveFileTypeUnknown, ArchiveFileTypeAppOrJs, + ArchiveFileTypeSetting, ArchiveFileTypeLoading, } ArchiveFileTypeEnum; diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index d09595037..d600815f6 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -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); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 9e0918373..06e3bca29 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -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, diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.c b/applications/main/bad_usb/helpers/bad_usb_hid.c index 5d7076314..c6226cf37 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.c +++ b/applications/main/bad_usb/helpers/bad_usb_hid.c @@ -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, diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.h b/applications/main/bad_usb/helpers/bad_usb_hid.h index 71d3a58e7..e4758ab68 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.h +++ b/applications/main/bad_usb/helpers/bad_usb_hid.h @@ -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); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index ccc3caa81..99034028d 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -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; diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index 79dcdd531..1b4ff55cb 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -1,4 +1,5 @@ #include +#include #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" diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h index 464c8a72b..2b15c2586 100644 --- a/applications/main/bad_usb/helpers/ducky_script_i.h +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -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); diff --git a/applications/main/bad_usb/helpers/ducky_script_keycodes.c b/applications/main/bad_usb/helpers/ducky_script_keycodes.c index 290618c13..ce957bb4e 100644 --- a/applications/main/bad_usb/helpers/ducky_script_keycodes.c +++ b/applications/main/bad_usb/helpers/ducky_script_keycodes.c @@ -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; +} diff --git a/applications/main/bad_usb/resources/badusb/test_mouse.txt b/applications/main/bad_usb/resources/badusb/test_mouse.txt new file mode 100644 index 000000000..97391cf17 --- /dev/null +++ b/applications/main/bad_usb/resources/badusb/test_mouse.txt @@ -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 \ No newline at end of file diff --git a/applications/main/gpio/gpio_app.c b/applications/main/gpio/gpio_app.c index 234cc793a..a21813955 100644 --- a/applications/main/gpio/gpio_app.c +++ b/applications/main/gpio/gpio_app.c @@ -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); diff --git a/applications/main/gpio/gpio_app_i.h b/applications/main/gpio/gpio_app_i.h index ce4cb6f55..4fbe25ad8 100644 --- a/applications/main/gpio/gpio_app_i.h +++ b/applications/main/gpio/gpio_app_i.h @@ -5,6 +5,7 @@ #include "scenes/gpio_scene.h" #include "gpio_custom_event.h" #include "usb_uart_bridge.h" +#include #include #include @@ -27,6 +28,7 @@ struct GpioApp { SceneManager* scene_manager; Widget* widget; DialogEx* dialog; + Power* power; VariableItemList* var_item_list; VariableItem* var_item_flow; diff --git a/applications/main/gpio/scenes/gpio_scene_start.c b/applications/main/gpio/scenes/gpio_scene_start.c index 421936488..0f37d77d8 100644 --- a/applications/main/gpio/scenes/gpio_scene_start.c +++ b/applications/main/gpio/scenes/gpio_scene_start.c @@ -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); diff --git a/applications/main/gpio/usb_uart_bridge.c b/applications/main/gpio/usb_uart_bridge.c index f6e68b109..3e1cefb93 100644 --- a/applications/main/gpio/usb_uart_bridge.c +++ b/applications/main/gpio/usb_uart_bridge.c @@ -1,7 +1,6 @@ #include "usb_uart_bridge.h" #include "usb_cdc.h" #include -#include #include #include #include @@ -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; } diff --git a/applications/main/ibutton/application.fam b/applications/main/ibutton/application.fam index 01c02ec23..84afe0f02 100644 --- a/applications/main/ibutton/application.fam +++ b/applications/main/ibutton/application.fam @@ -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, ) diff --git a/applications/main/ibutton/ibutton_cli.c b/applications/main/ibutton/ibutton_cli.c index 2338ca3c3..0b9a59586 100644 --- a/applications/main/ibutton/ibutton_cli.c +++ b/applications/main/ibutton/ibutton_cli.c @@ -1,26 +1,14 @@ #include #include -#include +#include #include +#include #include #include #include -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); diff --git a/applications/main/infrared/application.fam b/applications/main/infrared/application.fam index 575bebbe4..79b3fdbfa 100644 --- a/applications/main/infrared/application.fam +++ b/applications/main/infrared/application.fam @@ -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, ) diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index a93fd766d..4a03a8220 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -1,6 +1,6 @@ #include "infrared_app_i.h" -#include +#include #include #include @@ -88,6 +88,19 @@ static void infrared_rpc_command_callback(const RpcAppSystemEvent* event, void* view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressIndex); } + } else if(event->type == RpcAppEventTypeButtonPressRelease) { + furi_assert( + event->data.type == RpcAppSystemEventDataTypeString || + event->data.type == RpcAppSystemEventDataTypeInt32); + if(event->data.type == RpcAppSystemEventDataTypeString) { + furi_string_set(infrared->button_name, event->data.string); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressReleaseName); + } else { + infrared->app_state.current_button_index = event->data.i32; + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPressReleaseIndex); + } } else if(event->type == RpcAppEventTypeButtonRelease) { view_dispatcher_send_custom_event( infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease); @@ -411,6 +424,26 @@ void infrared_tx_stop(InfraredApp* infrared) { infrared->app_state.last_transmit_time = furi_get_tick(); } +void infrared_tx_send_once(InfraredApp* infrared) { + if(infrared->app_state.is_transmitting) { + return; + } + + dolphin_deed(DolphinDeedIrSend); + infrared_signal_transmit(infrared->current_signal); +} + +InfraredErrorCode infrared_tx_send_once_button_index(InfraredApp* infrared, size_t button_index) { + furi_assert(button_index < infrared_remote_get_signal_count(infrared->remote)); + + InfraredErrorCode error = infrared_remote_load_signal( + infrared->remote, infrared->current_signal, infrared->app_state.current_button_index); + if(!INFRARED_ERROR_PRESENT(error)) { + infrared_tx_send_once(infrared); + } + + return error; +} void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) { view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewLoading); furi_thread_set_callback(infrared->task_thread, callback); @@ -468,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) { diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 692cc9671..75d8502f2 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -218,6 +218,20 @@ InfraredErrorCode infrared_tx_start_button_index(InfraredApp* infrared, size_t b */ void infrared_tx_stop(InfraredApp* infrared); +/** + * @brief Transmit the currently loaded signal once. + * + * @param[in,out] infrared pointer to the application instance. + */ +void infrared_tx_send_once(InfraredApp* infrared); + +/** + * @brief Load the signal under the given index and transmit it once. + * + * @param[in,out] infrared pointer to the application instance. + */ +InfraredErrorCode infrared_tx_send_once_button_index(InfraredApp* infrared, size_t button_index); + /** * @brief Start a blocking task in a separate thread. * diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 8c7422d5e..1ec4645e9 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -2,26 +2,61 @@ #include #include +#include #include #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); diff --git a/applications/main/infrared/infrared_brute_force.h b/applications/main/infrared/infrared_brute_force.h index 879642257..2c75d37f2 100644 --- a/applications/main/infrared/infrared_brute_force.h +++ b/applications/main/infrared/infrared_brute_force.h @@ -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. diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index b700cf121..eb13bcd79 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -1,11 +1,11 @@ -#include -#include +#include #include #include #include #include #include #include +#include #include #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); diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index 02d9a276f..7109a48b7 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -3,7 +3,7 @@ #include #include -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, @@ -21,11 +21,13 @@ enum InfraredCustomEventType { InfraredCustomEventTypeRpcButtonPressName, InfraredCustomEventTypeRpcButtonPressIndex, InfraredCustomEventTypeRpcButtonRelease, + InfraredCustomEventTypeRpcButtonPressReleaseName, + InfraredCustomEventTypeRpcButtonPressReleaseIndex, InfraredCustomEventTypeRpcSessionClose, InfraredCustomEventTypeGpioTxPinChanged, InfraredCustomEventTypeGpioOtgChanged, -}; +} InfraredCustomEventType; #pragma pack(push, 1) typedef union { diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index f57fe3aa1..96b4eb1c8 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -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 +# diff --git a/applications/main/infrared/resources/infrared/assets/audio.ir b/applications/main/infrared/resources/infrared/assets/audio.ir index 20070bbe0..efd0382e3 100644 --- a/applications/main/infrared/resources/infrared/assets/audio.ir +++ b/applications/main/infrared/resources/infrared/assets/audio.ir @@ -4650,3 +4650,42 @@ type: parsed protocol: NECext address: 7F 01 00 00 command: 69 96 00 00 +# +# Model : NAD DR2 remote for NAD D7050 and D3020 +# +name: Power +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 25 DA 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 88 77 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 8C 73 00 00 +# +name: Mute +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 94 6B 00 00 +# +name: Next +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 1A E5 00 00 +# +name: Prev +type: parsed +protocol: NECext +address: 87 7C 00 00 +command: 1D E2 00 00 +# diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index ae3c4d4b4..bff8adfa0 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -2474,7 +2474,7 @@ protocol: RC5 address: 01 00 00 00 command: 14 00 00 00 # -# Model: Elitelux L32HD1000 +# Model: Elitelux L32HD1000 / Vivax TV-32LE114T2S2SM / Sansui # name: Power type: parsed @@ -3657,3 +3657,1179 @@ type: parsed protocol: NECext address: AD ED 00 00 command: C5 3A 00 00 +# +# Model: AKAI ATE_22Y604W +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9292 4518 635 522 635 522 635 523 634 523 662 494 664 494 663 1625 661 519 662 1602 660 1626 660 1650 636 1650 636 1651 635 1651 635 522 634 1653 633 524 633 1655 631 526 631 527 630 1657 630 527 630 527 630 527 630 1658 630 527 630 1658 630 1658 630 527 631 1658 629 1658 629 1658 629 40773 9295 2239 630 98164 9297 2241 630 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9319 4500 657 499 658 498 660 498 659 500 658 498 660 499 688 1600 685 472 634 1654 634 1680 607 1681 607 1681 607 1681 607 1680 608 549 609 1680 608 550 608 1681 607 550 607 1682 631 1657 631 527 631 527 631 527 631 1658 631 527 631 1659 630 527 631 528 630 1659 630 1659 630 1658 631 40798 9302 2242 630 98247 9298 2243 631 98266 9301 2243 630 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9335 4533 632 501 659 499 660 499 660 500 660 501 658 500 689 1604 685 474 635 1683 608 1659 632 1683 608 1684 607 1684 608 1684 608 551 609 1683 609 551 609 1684 608 1684 608 1685 607 1685 632 528 632 528 632 528 632 1661 631 529 631 529 631 529 631 529 631 1662 631 1662 631 1662 631 40877 9313 2247 631 98424 9314 2248 631 98437 9314 2247 632 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9305 4503 660 498 690 470 688 470 687 473 635 524 635 524 635 1657 634 525 663 1629 662 1630 659 1655 609 1682 609 1682 609 1682 633 525 635 1657 634 525 634 525 634 526 633 527 632 1660 631 528 632 528 631 528 631 1661 631 1661 631 1661 631 1660 631 528 631 1661 631 1661 630 1661 630 40849 9309 2243 631 98361 9313 2244 631 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9338 4507 658 500 689 470 688 471 636 525 635 525 635 524 636 1658 635 525 663 1655 637 1630 661 1655 635 1657 635 1657 636 1657 635 524 635 1658 635 1658 634 1658 634 526 634 1660 632 1661 632 528 632 528 632 528 632 528 632 528 632 1662 631 528 632 528 632 1662 631 1662 631 1662 631 40877 9317 2245 631 98426 9319 2246 631 +# +# Model: Brandt B3230HD_TV +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9344 4504 663 494 666 494 666 495 665 496 663 521 638 522 637 1634 660 522 638 1633 660 1655 638 1656 636 1656 636 1657 636 1658 635 526 633 1661 632 1662 631 1662 631 1663 631 1663 631 1662 632 530 631 530 631 530 630 530 631 530 631 530 631 530 631 530 631 1663 631 1664 630 1663 631 40893 9321 2247 631 98484 9323 2247 632 +# +# Model: BUSH TV_VL32HDLED +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9028 4480 593 1667 589 541 597 532 596 534 594 562 566 564 564 539 589 567 571 558 570 1663 594 1666 591 1696 571 1662 595 1666 591 1669 598 1662 595 562 566 563 565 539 589 567 571 1661 596 561 567 563 565 564 564 1669 598 1662 595 1691 566 1668 599 558 570 1663 594 1692 565 575 1669 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9200 4445 656 510 628 511 626 1616 626 514 623 542 597 542 597 542 597 542 597 1648 596 1648 596 543 596 1648 596 1648 596 1648 596 1648 596 1648 596 543 596 543 595 1649 595 1648 596 543 595 544 596 543 594 545 596 1648 595 1649 593 545 595 543 595 1650 595 1648 595 1649 595 1649 595 39850 9193 2218 595 95963 9218 2218 596 95989 9193 2219 595 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9226 4417 657 508 628 511 625 1617 624 515 622 517 622 517 621 517 622 517 622 1622 621 1623 620 518 621 1622 622 1623 620 1623 621 1623 621 1623 620 518 621 518 620 518 621 1623 621 1623 619 519 620 518 620 518 621 1624 620 1623 595 1649 619 519 620 519 619 1625 619 1648 596 1625 594 39874 9194 2192 620 95968 9197 2242 571 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9205 4446 630 536 628 511 627 1616 626 539 598 542 597 542 597 542 597 542 597 1647 597 1647 597 542 597 1647 597 1648 596 1648 596 1648 596 1648 596 1648 596 1648 596 542 597 1648 596 542 597 542 597 543 597 542 597 542 597 543 597 1648 596 543 596 1648 596 1648 596 1648 596 1648 596 39850 9202 2218 596 95967 9230 2217 597 +# +# Model: ContinentalEdison +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9209 4446 655 512 628 510 628 1616 626 515 622 542 597 542 597 542 597 542 597 1648 596 1648 597 542 597 1647 597 1647 597 1647 597 1647 597 1647 597 1648 596 542 597 1648 596 542 597 1648 597 542 597 542 597 542 597 542 597 1648 596 543 596 1648 597 543 596 1648 596 1648 597 1648 596 39847 9203 2218 596 95986 9203 2218 596 95964 9230 2218 596 95965 9232 2218 596 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9225 4476 586 1675 613 527 612 529 610 530 609 532 607 558 581 558 582 559 581 559 581 1681 581 1681 581 1681 581 1681 581 1681 581 1681 581 1681 581 1683 581 1681 581 1681 581 1681 581 559 581 559 581 558 582 559 580 559 581 559 581 559 581 559 581 1681 581 1681 581 1682 580 1682 581 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 337 1695 339 664 340 664 339 665 338 664 339 693 310 1695 339 1696 338 693 310 1697 337 666 337 693 310 693 310 1723 311 692 311 44457 309 1723 310 692 311 692 311 692 311 693 310 1723 310 694 334 669 334 1700 333 672 331 1703 330 1727 306 1704 330 697 306 1727 306 42378 309 1724 333 670 333 670 333 672 331 673 330 673 330 1704 329 1704 329 674 329 1727 306 698 305 698 305 698 305 1728 305 698 305 44436 309 1724 334 670 333 671 332 672 331 697 306 1728 306 674 329 697 306 1728 306 698 306 1728 306 1727 306 1728 305 698 305 1727 306 42378 309 1724 334 669 334 671 332 672 330 673 330 697 306 1703 330 1727 306 697 306 1727 306 697 306 698 305 697 306 1728 305 697 306 44431 309 1724 334 670 333 670 333 672 331 696 306 1727 306 697 306 697 306 1727 306 697 306 1727 306 1727 306 1727 306 697 306 1728 305 42373 309 1724 334 670 333 670 333 671 332 697 306 697 306 1727 305 1727 306 697 306 1728 305 697 306 697 306 697 306 1727 306 697 306 44427 309 1724 334 669 334 670 333 672 331 697 306 1728 306 697 306 697 306 1727 306 697 306 1727 306 1727 306 1727 306 697 306 1728 305 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 336 1697 336 667 336 668 335 668 336 667 337 1696 338 1696 337 1696 337 667 336 1697 337 668 335 668 335 669 334 1699 334 693 309 43422 335 1723 310 694 309 694 309 695 333 671 332 672 331 673 331 673 331 1704 330 673 330 1703 331 1704 330 1703 331 673 331 1704 330 43424 309 1725 308 695 308 696 332 672 332 673 331 1703 331 1703 330 1703 331 673 331 1703 331 673 331 673 330 673 331 1703 331 673 331 43418 308 1725 308 695 333 671 332 672 331 672 331 673 331 673 330 673 330 1703 331 673 331 1703 331 1704 330 1703 331 673 330 1703 331 43418 309 1725 308 695 308 696 332 672 331 673 330 1703 331 1703 331 1703 331 673 331 1703 330 673 330 673 330 673 331 1703 331 673 331 43419 308 1725 308 695 308 696 307 697 331 673 331 672 331 672 331 673 330 1703 331 673 330 1703 331 1703 331 1703 331 673 331 1703 331 43419 309 1725 308 695 308 696 332 672 331 673 330 1703 331 1703 331 1703 331 673 330 1704 330 673 330 673 330 673 330 1703 331 673 330 43424 309 1725 309 695 333 671 332 672 331 672 331 673 331 673 330 673 331 1703 331 673 331 1703 331 1704 330 1703 331 672 332 1703 331 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 310 1696 337 664 339 693 310 693 310 665 338 666 337 665 338 1696 338 665 338 1723 310 666 337 693 310 693 310 1723 310 693 310 45493 309 1724 309 693 310 693 310 692 311 692 311 1722 311 1723 310 693 310 1724 334 669 334 1700 331 1701 308 1727 331 673 330 1703 330 41342 333 1699 334 670 333 670 333 671 332 672 331 671 332 672 331 1702 331 671 332 1702 331 672 331 672 331 672 331 1702 331 672 331 45459 333 1699 334 670 333 671 332 696 306 673 331 1701 332 1702 331 672 331 1702 331 672 331 1702 331 1702 331 1701 332 672 331 1702 331 +# +# Model: Grandin +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 337 1698 336 667 336 665 338 666 337 665 392 1644 337 666 338 1696 338 666 338 1723 310 667 337 667 336 693 310 1723 311 692 311 44474 310 1723 310 692 311 692 311 693 310 693 310 694 309 1725 334 670 333 1701 332 672 332 1702 331 1702 332 1702 331 672 332 1702 331 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 970 719 972 718 1822 718 944 747 920 771 921 799 893 802 893 799 893 1644 893 799 1767 770 918 86138 893 798 894 798 1768 772 915 778 913 779 913 804 888 807 888 804 888 1627 911 805 1737 801 888 86143 893 798 918 774 1766 775 913 780 912 780 912 779 913 783 912 780 912 1625 913 780 1763 777 912 86141 892 798 918 774 1766 776 912 780 911 780 912 780 912 783 912 780 912 1626 912 780 1762 801 888 86137 892 799 917 774 1766 775 913 780 912 780 912 779 913 783 912 781 911 1650 888 805 1738 801 887 86148 892 799 918 775 1765 800 888 804 888 804 888 804 888 808 888 804 888 1651 888 805 1738 801 888 86133 944 772 919 773 1767 774 914 778 914 778 914 778 914 781 915 777 915 1624 914 778 1766 774 914 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 972 719 1823 716 973 719 972 720 945 747 920 772 920 802 893 1645 1743 796 918 773 918 774 916 86148 917 774 1767 772 916 777 914 778 913 779 913 779 913 783 913 1625 1763 776 913 779 913 779 913 86148 918 773 1767 773 915 778 914 779 913 779 913 780 912 783 913 1625 1764 777 912 780 912 780 912 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 946 745 947 745 1850 688 945 746 944 748 893 798 893 802 893 1644 1743 794 894 797 894 1643 918 85284 892 798 894 798 1769 769 918 775 915 778 914 778 914 782 913 1649 1739 775 913 778 914 1625 913 85274 943 771 919 773 1766 773 915 778 914 777 915 777 915 781 914 1623 1765 774 914 778 914 1623 914 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 972 719 1822 715 972 717 973 719 970 721 919 772 919 1647 1743 794 894 797 894 797 918 773 918 86132 892 798 1768 770 918 774 916 778 913 803 889 804 888 1653 1739 800 889 804 888 804 888 804 888 86144 918 773 1769 771 915 777 915 777 915 778 914 778 914 1627 1765 774 914 777 915 777 915 778 914 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 975 746 946 717 1879 660 974 719 972 721 920 798 894 1648 1744 794 894 798 894 797 894 1644 918 85281 920 773 945 749 1793 769 917 775 916 777 915 777 915 1628 1765 774 915 778 914 778 914 1624 914 85282 943 772 919 773 1767 774 914 778 914 778 914 778 914 1627 1766 774 914 778 914 778 914 1624 914 85311 919 773 919 774 1766 773 915 778 914 778 914 778 914 1627 1766 774 915 778 914 778 914 1624 914 +# +# Model: Grundig AndroidTV +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 920 772 920 772 1771 768 920 771 920 772 920 771 948 748 948 743 948 1590 946 746 1795 1613 919 85291 892 799 893 799 1767 772 916 777 914 778 914 779 913 783 913 779 913 1625 913 780 1762 1623 912 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 872 800 873 797 1723 808 875 798 875 827 873 772 900 799 873 799 873 1614 873 800 1718 816 867 86386 844 828 844 828 1744 786 845 828 845 828 845 828 845 828 844 828 845 1643 844 828 1693 838 844 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 896 775 845 828 1746 785 845 828 846 827 848 826 845 1641 1693 838 844 828 845 828 845 828 845 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 957 716 1725 805 878 826 847 826 846 827 875 797 847 1641 1695 834 874 799 873 800 872 1617 870 85382 847 824 1695 835 873 800 871 803 869 804 868 806 867 1645 1691 841 842 831 842 831 842 1646 842 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 868 804 868 805 1717 812 870 804 896 777 869 803 870 804 869 1618 1744 790 867 805 866 807 866 86344 867 807 866 807 1716 816 868 806 867 805 867 806 868 805 869 1622 1713 841 843 804 867 806 869 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 925 747 1743 787 926 747 925 747 926 746 953 719 872 801 927 1562 1772 785 897 776 894 1594 867 85501 926 747 1771 785 897 776 894 779 867 806 867 807 866 807 866 1622 1713 818 866 807 866 1622 866 85508 926 748 1770 786 869 804 868 806 867 807 866 807 866 807 866 1623 1713 819 865 807 866 1623 866 85517 925 748 1770 786 896 777 868 806 867 807 866 807 866 807 866 1622 1714 818 866 807 866 1623 865 +# +# Model: GRUNDIG UNKNOWN +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 846 826 847 797 1723 837 846 826 847 826 847 826 846 826 846 826 846 1641 847 827 1717 1631 867 85430 845 827 845 827 1718 813 869 805 867 806 867 808 865 809 864 809 864 1624 864 832 1689 1635 863 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 926 751 1760 756 922 756 921 782 897 782 897 783 897 784 897 784 897 1624 897 784 1736 787 895 85521 921 780 1734 784 896 783 894 785 895 784 895 784 895 785 895 784 896 1625 895 785 1735 787 896 85546 894 783 1732 763 917 783 896 783 896 784 895 784 895 785 894 785 897 1624 897 784 1734 786 897 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 953 724 927 750 1764 755 924 755 924 758 922 782 898 782 899 1624 1737 786 898 784 897 784 898 85552 923 756 921 757 1759 783 898 782 898 782 898 783 897 783 897 1623 1737 785 897 784 897 784 897 85557 927 750 926 753 1762 782 898 782 898 759 921 782 898 783 898 1623 1737 785 898 784 898 783 898 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 952 725 1763 752 927 753 925 756 922 780 899 781 899 782 899 1623 1737 786 897 784 897 1626 895 84746 892 785 1731 787 894 785 894 785 895 786 895 789 891 786 895 1627 1732 789 894 788 893 1628 894 84705 929 748 1766 751 930 750 929 750 929 750 929 752 928 753 927 1593 1766 756 926 756 925 1597 924 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 896 782 895 784 1733 785 896 785 868 812 894 784 920 762 871 812 918 1605 893 787 1735 1629 892 84752 893 784 895 781 1735 762 919 783 895 786 897 783 896 785 896 786 895 1625 897 784 1738 1625 896 84727 916 784 896 781 1736 785 897 783 896 784 896 785 897 784 895 785 897 1624 897 783 1737 1627 897 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 921 753 1759 785 896 782 897 782 897 783 895 784 896 1623 1733 786 895 784 895 784 893 785 895 85568 893 783 1734 786 893 785 895 783 895 785 894 785 895 1622 1733 786 894 784 896 784 895 784 895 +# +# Model: GuestTek Marriot_Hotel +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 5349 102096 3672 530 1194 102545 5171 102367 969 658 1613 481 1499 103984 503 388 210 337 161 +# +name: Vol_up +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 48 B7 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 44 BB 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 0A F5 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 06 F9 00 00 +# +# Model: Haier L42C1180 +# +name: Mute +type: parsed +protocol: NECext +address: 00 7F 00 00 +command: 5A A5 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9021 4377 655 452 654 452 654 1567 654 453 653 453 653 453 652 454 651 455 651 1568 654 1569 653 455 651 1570 652 1570 652 1570 652 1571 651 1572 649 480 626 481 624 482 623 1599 623 483 623 484 622 484 622 484 622 1601 621 1601 621 1601 621 484 622 1601 621 1601 621 1601 621 1601 621 39912 8910 2137 622 95435 8933 2137 622 95434 8934 2137 622 95434 8934 2137 622 95434 8934 2137 622 95434 8933 2137 622 95434 8933 2138 621 95436 8932 2138 621 95435 8933 2138 621 95435 8933 2137 622 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8990 4407 626 479 627 479 627 1596 626 479 627 480 681 425 681 425 681 425 681 1541 680 1542 679 428 626 1596 626 1596 626 1596 626 1596 626 1596 626 480 626 1597 625 481 625 482 624 506 600 506 600 506 600 507 599 1624 622 483 623 1599 623 1599 623 1599 623 1600 622 1600 622 1600 622 39912 8909 2138 623 95460 8906 2140 623 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8990 4406 626 481 625 481 625 1595 627 481 625 481 653 453 654 452 654 452 654 1567 654 1568 654 453 653 1568 654 1569 653 1569 653 1569 653 1570 652 1594 627 1595 626 479 626 480 625 481 624 482 624 483 623 483 623 483 623 484 622 1600 622 1600 622 1600 622 1600 622 1600 622 1600 622 39913 8911 2137 622 95438 8934 2136 623 +# +# Model: Hisense ER22601A +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9020 4375 657 450 656 451 654 1566 656 451 655 451 653 453 626 480 653 452 655 1567 654 1568 654 454 651 1568 654 1568 654 1569 653 1570 651 1572 650 1595 626 479 626 480 625 1597 624 482 624 482 624 483 623 483 623 483 623 1599 623 1599 623 483 623 1599 623 1599 623 1599 623 1599 623 39900 8912 2136 623 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 976 723 1776 796 925 804 897 804 897 803 897 803 897 803 897 803 922 1610 919 783 1767 807 914 86103 921 778 1769 805 916 786 914 788 913 787 914 787 914 788 913 811 890 1617 914 787 1764 832 889 86082 920 778 1768 804 916 786 914 786 914 786 914 786 914 786 915 786 915 1616 915 786 1763 809 913 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 950 750 980 720 1800 770 926 774 926 774 926 775 925 1632 1775 796 922 779 920 782 919 782 919 86232 897 802 923 777 1772 801 918 782 919 783 917 783 918 1613 1769 802 918 783 917 783 918 783 917 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 979 721 1776 794 926 775 925 775 925 775 925 775 925 1606 1800 796 922 779 919 781 919 1612 919 85400 924 776 1772 800 919 781 919 782 918 781 919 782 919 1612 1770 801 919 782 918 782 919 1612 919 85390 923 776 1772 799 919 781 919 781 919 781 919 781 919 1612 1770 801 919 782 918 782 918 1613 918 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 979 720 977 724 1776 795 925 775 924 775 951 750 950 750 950 1582 1796 799 919 781 919 782 919 86138 949 752 946 777 1771 801 919 782 919 782 918 782 918 782 918 1612 1769 802 918 782 918 782 918 +# +# Model: Hisense K321UW +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 951 749 1832 741 978 721 927 774 926 776 924 776 924 801 899 1632 1773 798 920 782 918 1613 917 85257 898 802 1773 800 919 782 918 782 919 782 919 782 918 782 918 1613 1769 802 918 783 918 1614 917 85260 898 802 1772 800 919 782 919 782 919 782 919 782 918 782 919 1613 1769 802 918 782 918 1613 918 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8510 4237 528 1592 528 1592 528 526 529 526 529 526 529 526 529 526 529 527 528 1591 529 1591 529 1592 528 527 528 1590 530 526 529 526 529 525 528 22533 529 1590 529 1592 528 526 529 526 529 526 529 526 529 526 529 526 529 1592 528 1591 555 1564 556 500 555 1565 554 500 529 526 529 524 529 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8507 4232 528 1591 528 1590 529 527 528 527 527 526 528 527 528 526 529 525 530 526 529 1591 528 1591 528 1590 529 1591 528 528 527 527 527 525 528 22529 525 1592 527 1592 526 528 527 527 528 529 525 527 528 527 527 527 528 527 527 1592 527 1593 526 1592 527 1592 527 527 528 554 500 527 526 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8505 4231 528 1593 526 1595 524 525 529 526 529 526 528 525 529 527 527 526 528 1590 529 1590 529 1590 529 1590 529 1591 528 527 527 527 527 525 528 21460 528 1590 529 1590 529 525 529 526 528 526 528 526 528 526 528 527 527 1592 527 1592 527 1591 528 1591 528 1591 528 526 528 526 528 525 528 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8506 4232 528 1591 528 1591 528 526 528 526 528 528 526 526 528 527 527 526 528 526 528 526 528 1591 528 1592 527 1591 528 525 529 526 528 526 527 23593 528 1591 528 1592 527 526 528 526 528 526 528 526 529 527 527 528 526 525 529 527 527 1591 528 1591 528 1591 528 526 528 526 528 526 527 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8505 4231 528 1591 528 1591 527 527 527 526 528 526 552 503 527 528 526 526 528 1591 528 527 527 526 529 1592 527 1592 527 525 529 527 527 526 527 23590 527 1590 528 1590 528 525 529 526 528 526 528 526 528 526 528 526 528 1590 529 526 528 526 528 1590 529 1591 528 526 528 526 528 523 530 +# +# Model: Kendo CP20M36VT +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8505 4233 526 1591 528 1591 528 526 528 526 528 527 527 527 527 526 528 528 526 528 526 526 528 525 529 1591 528 1591 528 527 527 526 528 524 529 24651 528 1595 524 1591 527 526 528 528 526 526 528 526 528 528 526 527 527 526 528 526 528 527 527 1592 527 1591 527 527 527 528 526 525 552 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9121 4377 685 475 658 476 656 1610 656 479 654 482 651 483 651 483 651 483 651 1618 650 1618 650 506 627 1640 628 1641 627 1641 627 1640 628 1641 627 1641 627 1641 627 506 627 506 628 507 627 506 627 507 627 507 626 507 627 507 627 1641 626 1641 627 1641 627 1641 627 1641 627 1641 627 39937 9096 2169 651 +# +# Model: LG OLED48C37LA (LG_OLED C3 models) +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9097 4402 685 475 659 475 658 1609 657 477 656 480 653 480 654 480 653 481 653 1615 653 1614 654 481 653 1615 653 1615 653 1615 653 1615 653 1615 653 1615 653 481 652 481 653 1616 652 482 652 482 652 482 652 482 652 482 652 1616 652 1616 651 482 652 1617 651 1617 651 1640 628 1640 628 39937 9097 2167 652 +# +name: Mute +type: parsed +protocol: NECext +address: 83 7A 00 00 +command: 09 00 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 83 7A 00 00 +command: 02 40 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 83 7A 00 00 +command: 03 40 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 83 7A 00 00 +command: 01 00 00 00 +# +# Model: Manta +# +name: Ch_next +type: parsed +protocol: NECext +address: 83 7A 00 00 +command: 00 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8073 3997 524 502 495 505 492 1508 498 503 494 1505 501 1500 495 1504 491 1510 496 3988 522 502 495 1505 501 501 496 504 493 1507 499 502 495 1505 501 501 496 18806 8072 3997 524 502 495 505 492 1507 499 502 495 1505 490 1509 497 1504 491 1510 496 3988 522 502 495 1505 501 500 497 503 494 1506 500 501 496 1504 491 510 498 18806 8072 3998 523 503 494 506 491 1509 497 504 493 1506 499 1501 494 1506 500 1502 493 3989 522 504 493 1507 499 502 495 505 492 1508 498 503 494 1506 499 502 495 18807 8072 3998 523 503 494 506 491 1509 497 504 493 1506 500 1501 494 1506 500 1502 493 3989 521 503 494 1506 500 502 495 505 492 1508 498 503 494 1506 500 502 495 18807 8072 3998 523 502 495 505 492 1508 498 503 494 1505 501 1500 495 1504 491 1510 496 3988 523 502 495 1505 501 501 496 503 494 1506 500 501 496 1504 491 510 498 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8069 3998 522 503 494 506 491 1509 496 505 492 1507 498 1501 494 1506 499 1502 493 3989 521 1503 492 1508 497 1503 492 508 500 1501 494 506 491 510 498 504 493 17810 8067 4003 517 508 500 501 496 1504 491 510 498 1502 493 1507 498 1501 494 1508 497 3984 526 1474 521 1504 501 1500 495 505 492 1509 496 505 492 508 500 502 495 17809 8069 4000 520 506 491 509 499 1501 494 507 501 1499 496 1503 492 1508 497 1504 491 3991 519 1480 525 1500 495 1505 500 500 497 1503 492 509 499 503 494 507 490 17809 8069 3999 521 505 492 508 500 1500 495 506 491 1508 497 1502 493 1507 498 1503 492 3990 520 1504 491 1509 496 1504 491 509 499 1501 494 507 501 501 496 505 492 17808 8070 3998 523 503 494 506 491 1509 496 504 493 1507 498 1501 494 1506 499 1502 493 3988 522 1502 493 1507 498 1502 493 507 501 1500 495 505 492 509 499 502 495 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8066 4002 519 507 501 500 497 1503 492 508 500 1500 495 1480 525 1475 520 1506 499 3983 517 508 500 1500 495 1481 524 501 496 1504 491 510 498 503 494 507 501 18803 8073 3997 524 503 494 506 491 1483 522 504 493 1506 499 1476 519 1482 523 1478 517 3989 521 504 493 1482 523 1478 517 508 500 1476 519 507 501 500 497 505 492 18809 8066 4003 517 508 500 501 496 1503 492 509 499 1501 494 1480 525 1475 520 1481 524 3983 516 509 499 1501 494 1482 523 502 495 1505 500 500 497 504 493 508 500 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8069 4000 520 480 517 508 500 1500 495 505 492 1508 497 1502 493 1506 499 1502 493 3989 521 1503 492 1509 496 503 494 1506 499 1501 494 506 491 510 498 504 493 17807 8072 3997 524 501 496 505 492 1508 497 502 495 1505 500 1499 496 1504 491 1510 495 3986 524 1500 495 1506 499 500 497 1503 492 1508 497 503 494 507 501 501 496 17804 8064 4004 517 509 499 501 496 1504 491 509 499 1500 495 1505 500 1499 496 1505 500 3980 520 1505 500 1500 495 505 492 1508 497 1502 493 508 500 501 496 505 492 17807 8072 3995 526 500 497 503 494 1506 499 501 496 1504 491 1508 497 1503 492 1509 496 3985 515 1509 496 1503 492 508 500 1500 495 1506 499 501 496 505 492 509 499 17803 8065 4003 518 508 500 501 496 1504 491 509 499 1502 493 1507 498 1502 493 1508 497 3985 525 1500 495 1505 500 500 497 1503 492 1508 497 504 493 508 500 501 496 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8064 4006 525 501 496 504 493 1507 498 502 495 1505 500 1499 496 1504 501 1500 495 3987 523 1501 494 1507 498 502 495 505 492 1508 497 1503 492 509 499 503 494 17808 8069 4000 520 505 492 508 500 1500 495 506 491 1508 497 1503 492 1508 497 1504 491 3991 519 1505 500 1501 494 507 501 500 497 1502 493 1508 497 503 494 508 500 17803 8064 4006 525 501 496 504 493 1507 498 503 494 1505 500 1500 495 1505 500 1500 495 3988 522 1502 493 1507 498 503 494 506 491 1508 497 1503 492 509 499 503 494 +# +# Model: NEC E425 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8068 3999 521 504 493 507 501 1498 497 504 493 1505 500 1475 520 1479 526 1500 495 3987 523 502 495 1505 500 501 496 504 493 1506 499 1501 494 507 501 500 497 18802 8072 3996 524 502 495 505 492 1507 498 503 494 1505 500 1475 520 1480 525 1476 519 3987 523 501 496 1504 491 510 498 502 495 1504 501 1475 520 505 492 509 498 18798 8065 4001 519 507 501 499 498 1502 493 507 501 1499 496 1504 501 1473 522 1504 501 3981 518 506 491 1509 496 505 492 508 499 1500 495 1505 500 500 497 505 492 18808 8065 4001 519 507 501 500 497 1503 492 508 499 1500 495 1505 500 1499 496 1506 499 3983 516 508 499 1501 494 507 501 500 497 1502 493 1508 497 504 493 508 500 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8953 4403 601 417 701 516 602 515 602 411 706 515 603 515 575 1635 603 514 628 1553 628 1609 600 1583 627 1606 598 1610 601 1608 600 495 624 1607 599 516 603 515 601 517 572 547 599 1608 599 518 601 516 598 545 573 1610 573 1637 572 1636 597 1612 599 518 600 1610 598 1610 599 1611 598 39250 8972 2171 599 94711 8976 2167 602 94731 8955 2168 602 94737 8927 2198 598 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3554 1678 495 403 468 1248 495 403 497 374 497 375 497 374 497 374 497 375 496 375 496 375 495 377 493 378 492 380 490 1254 489 383 488 383 489 383 488 383 488 383 488 383 489 383 488 383 489 383 488 1255 488 383 488 384 488 383 488 384 487 384 487 384 487 384 488 384 488 384 487 1256 487 384 488 384 487 1256 488 1256 488 384 487 384 487 384 488 1256 487 384 488 384 487 1257 487 1257 487 385 486 1257 487 +# +# Model: Panasonic N2QAYA_152 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3526 1678 495 403 468 1248 496 403 496 375 496 375 497 375 496 375 496 376 495 376 495 376 495 377 493 378 492 380 490 1254 488 384 488 383 488 384 488 384 487 384 487 384 487 384 488 384 487 384 488 1257 486 385 486 385 486 385 487 385 486 386 486 409 462 409 462 410 461 410 462 410 461 1282 462 409 462 1282 462 1282 461 410 462 410 461 410 461 410 462 1282 461 410 462 1282 461 1282 462 410 461 1282 462 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3519 1775 426 448 422 1332 418 455 425 449 421 459 421 452 418 456 424 450 420 459 421 452 418 456 424 450 420 460 420 1339 422 458 422 451 419 455 425 449 421 459 421 452 418 455 425 449 421 459 421 1339 422 457 423 451 419 454 426 448 422 458 422 451 419 454 426 448 422 1331 419 454 426 1327 423 450 420 1307 454 1299 451 449 421 453 427 1326 424 449 421 1333 417 456 424 1329 421 1304 446 454 426 1327 423 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3516 1747 444 456 424 1329 421 453 427 446 424 451 419 460 420 454 426 447 423 451 419 461 419 454 426 447 423 451 419 1334 427 448 422 458 422 451 419 455 425 450 420 459 421 453 417 456 424 450 420 1333 417 457 423 456 424 449 421 453 427 447 423 457 423 450 420 454 426 1328 422 451 419 456 424 455 425 448 422 1331 419 455 425 448 422 1332 418 455 425 449 421 459 421 452 418 1336 425 449 421 1332 418 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3482 1730 448 425 450 1296 444 429 446 427 448 424 451 422 453 420 445 428 447 425 450 423 452 421 444 429 446 426 449 1297 454 419 446 427 448 425 450 423 452 420 445 428 447 426 449 424 451 421 444 1303 448 425 450 422 453 420 445 428 447 425 450 423 452 421 444 429 446 427 448 424 451 422 453 419 446 428 447 1298 453 420 445 428 447 426 449 424 451 421 444 429 446 427 448 1298 453 420 445 1301 450 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3512 1700 478 395 480 1266 474 399 476 396 479 394 481 392 473 400 475 397 478 395 480 393 482 390 475 398 477 396 479 1267 473 399 476 397 478 395 480 392 473 400 475 398 477 396 479 394 481 391 474 1272 479 394 481 392 473 400 475 398 477 395 480 393 482 391 474 399 476 1269 482 391 474 399 476 397 478 395 480 1266 474 398 477 396 479 1267 473 399 476 397 478 394 481 392 473 1273 478 395 480 1266 474 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3596 1604 513 388 429 1282 459 442 428 442 428 442 428 442 428 442 428 441 429 441 429 442 428 442 428 443 426 445 449 1289 452 446 423 447 423 448 422 448 422 448 422 448 422 448 422 448 422 448 422 1319 422 448 422 448 422 448 422 448 422 448 422 448 422 448 423 448 422 448 422 448 422 448 422 448 422 448 422 1319 422 448 422 449 422 448 422 448 422 448 422 448 422 448 422 1319 422 448 422 1320 421 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3480 1715 457 441 429 1284 457 442 428 442 428 442 428 442 428 442 428 441 429 442 428 442 453 417 453 417 453 418 451 1289 451 421 449 447 423 447 423 447 423 447 423 448 422 448 422 448 423 447 423 1318 423 447 423 448 422 448 423 447 423 448 423 448 422 448 422 448 422 1319 422 448 422 448 422 448 423 448 422 1319 422 448 423 448 422 1319 422 448 422 448 422 448 422 448 422 1319 422 448 423 1319 422 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3537 1660 458 441 429 1311 430 442 428 442 428 442 428 442 428 442 428 442 428 442 428 442 428 442 453 417 453 417 453 1287 453 420 449 422 447 447 423 448 422 448 422 448 422 448 422 448 422 448 422 1319 422 448 422 447 423 448 422 448 422 448 422 448 422 448 422 448 423 448 422 1319 422 448 422 448 423 1319 422 1319 423 448 422 448 422 448 422 1319 422 448 422 448 422 1319 422 1319 422 448 422 1319 422 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3481 1715 457 441 429 1311 430 442 429 442 428 442 428 442 428 442 428 441 429 442 428 442 453 417 453 417 452 419 450 1289 451 422 447 447 423 447 423 447 423 448 422 448 422 448 422 448 422 448 422 1319 422 448 422 448 422 448 422 448 422 448 423 448 422 448 422 448 422 448 422 448 422 1319 422 448 422 1319 422 1319 422 448 422 448 422 448 422 448 422 1319 422 448 422 1319 422 1319 422 448 422 1319 422 +# +# Model: Panasonic TC-P50S2 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3505 1690 483 416 454 1258 483 416 454 416 454 417 428 442 428 442 428 441 455 416 454 416 454 416 454 417 452 419 450 1289 451 421 448 423 447 448 422 448 422 448 422 448 422 448 422 448 422 448 422 1319 422 448 422 448 422 448 422 448 423 448 422 448 423 448 422 448 422 1319 422 448 422 1319 422 448 422 1319 422 1319 422 448 422 448 423 1319 422 448 422 1319 422 448 422 1319 422 1320 421 449 421 1319 422 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 228 144285 3545 1690 497 411 495 1256 496 382 465 410 466 410 493 383 493 386 489 411 464 413 462 415 460 417 459 417 459 418 459 1295 458 418 459 418 459 418 458 418 458 418 459 418 458 418 459 418 459 418 459 1295 459 418 459 418 458 418 459 418 458 418 458 418 458 419 458 418 458 419 458 419 458 418 458 419 458 419 458 1296 458 418 458 418 458 419 457 419 458 419 457 419 458 419 458 1296 457 419 457 1296 458 +# +# Model: Panasonic Unknown_Full +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3404 1652 462 422 432 1230 457 427 437 420 434 424 440 417 436 420 434 424 440 417 436 421 433 424 440 417 436 421 432 1228 459 425 439 419 434 422 432 426 438 419 434 422 431 426 438 419 435 422 431 1230 457 426 438 420 433 423 431 426 438 420 433 423 430 427 437 420 434 1228 459 424 440 1221 466 418 435 1225 462 1226 461 422 432 426 438 1224 463 420 433 1228 459 399 465 1222 465 1223 464 420 433 1225 462 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 2737 825 494 832 494 390 492 392 1352 1328 461 450 405 450 435 477 297 587 296 644 295 92395 2734 829 491 836 490 394 491 419 1286 1395 296 587 378 1474 239 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 182 7827 172 2332 177 2328 181 2323 176 2330 179 1309 175 1331 174 2331 178 1328 177 2328 181 1307 177 2327 182 1325 180 1326 179 1309 176 1331 174 1332 173 2333 176 2310 178 1328 177 2328 181 1325 180 2306 182 1325 180 2325 174 8340 183 7825 175 2330 179 2326 173 2333 176 2310 178 1328 177 1329 176 2329 180 1308 176 2329 180 1326 179 2326 173 1334 182 1306 179 1328 177 1329 176 1312 183 2322 177 2329 180 1326 179 2325 174 1315 180 2326 173 1333 183 2323 176 8339 183 7824 175 2330 179 2307 181 2324 175 2331 178 1328 177 1330 175 2311 177 1329 176 2329 180 1326 179 2327 182 1305 179 1328 177 1328 177 1311 173 1334 182 2323 176 2330 179 1326 571 1916 180 1327 178 2325 587 920 575 1930 579 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 179 7828 182 2323 176 2328 181 2323 176 2329 180 1308 176 1330 175 2329 180 1326 179 2307 181 2323 176 2329 180 2325 174 1332 173 1315 180 1327 179 1328 177 2327 182 2322 177 1311 173 2332 177 1329 176 1330 175 1312 183 1324 181 8332 180 7826 174 2331 178 2326 173 2313 176 2330 179 1327 178 1328 177 2327 182 1306 179 2326 183 2322 177 2328 181 2323 176 1312 183 1324 181 1325 180 1325 180 2307 181 2323 176 1331 174 2330 179 1327 178 1310 175 1331 174 1332 173 8323 179 7845 176 2311 177 2327 182 2322 177 2328 181 1325 180 1308 177 2328 181 1325 180 2325 174 2330 179 2326 173 2314 174 1332 173 1332 173 1333 183 1306 178 2326 183 2322 177 1329 176 2328 181 1307 177 1329 176 1330 175 1313 182 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 183 7824 176 2329 180 2324 175 2329 180 2324 175 1314 181 1325 180 2324 175 1331 174 2331 178 2307 181 2324 175 1331 174 1331 174 1314 181 1326 179 1326 179 2325 174 2332 177 1310 174 2330 179 1327 178 1328 177 1310 174 2330 179 8334 178 7827 173 2332 177 2327 182 2323 176 2310 178 1328 177 1328 177 2327 182 1306 179 2326 183 2322 177 2327 182 1324 181 1307 177 1329 176 1330 176 1331 174 2311 177 2328 181 1325 180 2324 175 1332 173 1313 182 1325 180 2324 175 8339 173 1383 2522 3925 179 2325 576 1909 590 1915 594 1910 589 918 588 919 576 1911 175 1332 173 2330 592 1912 587 1918 581 907 588 918 598 908 587 920 575 913 592 1914 182 2320 592 914 581 1927 180 1307 178 1327 592 915 580 1925 574 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 176 7830 180 2324 175 2329 180 2324 175 2329 180 1308 176 1329 176 2328 181 1325 180 2305 183 2321 178 1329 177 2327 182 1324 181 1306 178 1328 177 1329 176 2327 182 2304 174 1332 173 2332 177 1328 177 1310 174 2330 179 1327 178 8333 179 7826 174 2330 179 2325 174 2313 175 2329 180 1326 179 1326 179 2325 174 1315 180 2324 175 2329 180 1326 179 2325 174 1314 181 1325 180 1326 179 1308 177 2328 181 2323 176 1330 175 2329 180 1308 176 1330 175 2328 181 1325 180 8314 177 7845 176 2310 178 2327 182 2322 177 2327 182 1323 182 1306 179 2326 173 1333 183 2322 177 2327 182 1305 179 2326 173 1333 183 1323 182 1305 179 1327 178 2326 173 2331 178 1327 178 2308 180 1326 179 1327 178 2325 174 1315 180 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 182 7824 176 2328 181 2323 176 2328 181 2324 175 1312 183 1323 182 2322 177 1330 175 2309 179 2325 174 1332 173 1333 183 1305 179 1327 178 1328 177 1328 177 2308 180 262 177 1885 175 295 175 861 174 2330 179 99 313 911 573 915 590 1916 180 64 349 1910 176 265 174 7896 177 3081 835 3912 182 2322 177 207 179 1942 180 2304 174 268 181 1881 179 263 176 889 177 264 175 886 583 1925 182 271 178 855 180 240 178 1907 174 2331 178 1327 178 1309 176 1329 590 916 589 919 173 306 175 833 181 317 174 1832 175 2329 180 1325 180 309 182 1833 174 1313 182 1325 180 289 181 1853 175 2329 180 8313 178 7845 176 2309 179 2325 174 2331 178 2326 173 1333 183 1304 180 2324 175 1332 173 2330 179 2325 174 1314 181 1325 180 1326 179 1327 178 1308 177 1330 175 2329 180 2324 175 1312 183 2322 177 1329 176 1329 176 2329 180 2304 174 8339 173 351 3575 3902 594 1911 588 1916 593 1911 588 1916 583 904 591 916 589 1915 594 911 584 1920 579 1907 592 915 590 915 591 916 579 908 597 909 596 909 586 1919 580 1907 179 1326 592 1912 597 908 587 901 594 1911 588 1916 593 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9047 4385 682 474 682 1578 708 476 679 1581 706 477 679 1582 705 1582 705 1582 678 1583 679 1607 679 1582 678 478 679 478 678 477 679 1582 705 1582 679 1583 679 1608 704 1582 705 478 678 1582 705 478 678 478 678 478 679 477 679 478 679 478 678 1582 705 478 678 1583 704 1582 705 1582 679 39574 9073 4387 679 478 678 1583 679 503 677 1584 705 478 678 1582 705 1582 705 1582 705 1582 705 1582 705 1582 678 479 677 479 678 479 677 1583 679 1608 704 1582 705 1582 705 1582 705 478 678 1583 704 478 678 478 678 1582 680 478 703 453 704 453 703 1557 703 480 676 1584 704 1583 704 478 678 +# +name: Ch_next +type: parsed +protocol: Samsung32 +address: 05 00 00 00 +command: 12 00 00 00 +# +# Model: Samsung BN59-01081A +# +name: Ch_prev +type: parsed +protocol: Samsung32 +address: 05 00 00 00 +command: 10 00 00 00 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4499 4472 566 1662 565 1664 563 1665 562 565 538 564 539 562 541 560 543 558 545 1683 544 1658 569 1686 541 559 544 557 536 565 538 563 540 561 542 559 544 1684 543 558 545 556 537 564 539 562 541 560 543 558 545 1684 543 558 545 1683 544 1684 543 1660 567 1688 539 1689 538 1664 563 565 538 563 540 561 542 559 544 42973 4495 4472 566 1662 565 1663 564 1664 563 564 539 562 541 560 543 558 545 556 537 1691 536 1691 536 1666 572 555 538 564 539 561 542 559 544 557 536 565 538 1689 538 563 540 560 543 558 545 555 538 563 540 561 542 1686 541 559 544 1684 543 1684 543 1658 569 1685 542 1686 541 1660 567 559 544 557 536 565 538 563 540 42959 4499 4466 562 1666 572 1656 571 1656 571 556 537 564 539 562 541 559 544 556 537 1691 536 1690 537 1664 563 564 539 562 541 560 543 557 536 565 538 563 540 1688 539 561 542 559 544 556 537 564 539 562 541 560 543 1684 543 558 545 1682 545 1683 544 1657 570 1684 543 1659 568 1659 568 559 544 557 536 565 538 563 540 42955 4503 4463 565 1663 564 1663 564 1664 563 563 540 561 542 558 545 556 537 564 539 1688 539 1688 539 1662 565 562 541 559 544 557 536 565 538 563 540 560 543 1685 542 559 544 556 537 564 539 562 541 559 544 557 536 1692 535 565 538 1690 537 1690 537 1664 563 1691 536 1666 572 1656 571 556 537 564 539 562 541 559 544 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4507 4464 564 1690 537 1691 536 1666 572 555 538 538 565 562 541 559 544 557 536 1693 545 1656 571 1657 570 557 536 565 538 563 540 561 542 558 545 1683 544 1657 570 1684 543 558 545 555 538 563 540 561 542 559 544 557 536 565 538 563 540 1688 539 1662 565 1663 564 1663 564 1664 563 564 539 536 567 560 543 558 545 42962 4496 4470 568 1686 541 1660 567 1661 566 560 543 558 545 556 537 564 539 561 542 1686 541 1660 567 1687 540 534 569 558 545 555 538 563 540 561 542 1686 541 1686 541 1686 541 533 570 557 536 565 538 563 540 560 543 558 545 555 538 563 540 1661 566 1687 540 1661 566 1661 566 1662 565 561 542 533 570 557 536 564 539 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4506 4465 563 1692 546 1658 569 1659 568 559 544 557 536 565 538 563 540 561 542 1687 540 1662 565 1689 538 563 540 561 542 560 543 558 545 556 537 1665 562 1693 545 530 563 1692 546 555 538 564 539 562 541 560 543 558 545 556 537 1665 562 539 564 1690 537 1691 536 1692 535 1693 545 556 537 539 564 563 540 561 542 42974 4505 4464 564 1690 537 1665 562 1667 571 530 563 565 538 563 540 561 542 559 544 1684 543 1659 568 1661 566 561 542 559 544 557 536 565 538 563 540 1663 564 1690 537 563 540 1662 565 559 558 545 556 537 564 539 562 541 1688 539 561 542 1687 540 1688 539 1688 539 1663 564 563 540 561 542 559 544 557 536 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4498 4471 567 1661 566 1662 565 1664 563 564 539 563 540 561 542 559 544 557 536 1693 545 1684 543 1659 568 559 544 557 536 566 537 564 539 562 541 560 543 1686 541 560 543 558 545 1684 543 558 545 556 537 564 539 1690 537 564 539 1689 538 1690 537 564 539 1689 538 1665 562 1666 572 556 537 564 539 563 540 560 543 42964 4504 4464 564 1690 537 1665 562 1666 572 555 538 564 539 562 541 560 543 558 545 1683 544 1684 543 1659 568 558 545 556 537 565 538 563 540 560 543 558 545 1683 544 556 537 565 538 1690 537 564 539 561 542 560 543 1685 542 558 545 1683 544 1657 570 557 536 1692 546 1683 544 1684 543 557 536 566 537 563 540 561 542 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4506 4464 564 1691 536 1692 546 1656 571 556 537 564 539 562 541 560 543 558 545 1683 544 1684 543 1685 542 558 545 556 537 565 538 563 540 561 542 558 545 556 537 564 539 562 541 1687 540 561 542 558 545 556 537 1691 536 1665 562 1692 546 1656 571 556 537 1691 536 1692 546 1683 544 557 536 565 538 563 540 560 543 42966 4502 4466 562 1693 545 1683 544 1684 543 558 545 556 537 564 539 561 542 559 544 1684 543 1684 543 1658 569 558 545 530 563 564 539 562 541 560 543 558 535 566 537 564 539 562 541 1687 540 560 543 558 545 555 538 1691 536 1665 562 1693 545 1657 570 557 536 1666 572 1683 544 1658 569 557 546 529 564 563 540 561 542 +# +# Model: Samsung LE37S71B +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4497 4474 564 1690 537 1691 536 1666 572 556 537 564 539 562 541 560 543 558 545 1683 544 1683 544 1684 543 532 571 556 537 564 539 562 541 560 543 1659 568 1686 541 1661 566 1662 565 562 541 560 543 558 535 565 538 563 540 561 542 558 545 556 537 1665 562 1666 572 1656 571 1684 543 557 546 555 538 564 539 562 541 42966 4502 4466 562 1692 535 1693 545 1657 570 556 537 565 538 562 541 560 543 543 1656 571 1656 571 1657 570 557 536 565 538 563 540 560 543 558 545 1683 544 1683 544 1657 570 1684 543 531 562 565 538 563 540 560 543 558 545 556 537 564 539 561 542 1660 567 1686 541 1687 540 1662 565 562 541 533 570 557 536 565 538 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4567 4475 732 1555 703 1608 705 1608 705 478 676 460 669 507 651 506 649 508 621 1640 672 1616 673 1639 674 508 648 508 648 508 647 485 644 511 647 508 648 1639 674 508 649 508 647 485 645 511 647 508 648 509 647 1639 674 509 647 1613 673 1641 673 1640 673 1640 672 1617 671 1640 673 48544 4566 4505 648 1639 674 1639 674 1639 647 510 648 508 648 509 648 508 648 508 648 1639 647 1642 673 1639 674 508 648 509 648 508 647 485 645 511 647 509 647 1640 673 509 647 509 646 486 643 511 647 509 647 509 647 1640 673 509 647 1615 672 1640 673 1640 673 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4583 4485 687 1600 690 1623 716 1596 690 493 660 470 634 521 662 495 659 497 658 1629 684 1630 631 1657 682 500 656 500 656 500 656 500 656 475 653 1633 682 1631 682 1631 682 500 629 502 630 526 656 500 656 500 656 500 656 500 657 500 630 1632 682 1631 682 1631 682 1631 654 1633 682 48536 4553 4518 656 1632 682 1631 682 1631 682 500 630 502 629 526 656 500 656 501 656 1631 682 1631 631 1657 656 526 655 501 655 501 655 501 655 501 629 1634 680 1632 681 1632 681 501 629 502 629 527 654 501 655 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4551 4491 683 1630 658 1628 685 1628 685 500 654 499 631 525 657 499 656 500 656 1632 681 1632 652 1635 681 501 655 501 655 501 655 501 655 501 628 1634 680 1632 681 501 655 1632 680 479 626 528 628 528 654 502 654 502 654 502 655 1632 654 504 627 1659 680 1632 681 1633 654 1634 679 48515 4596 4498 654 1634 653 1633 654 1633 628 529 653 502 654 502 654 502 654 502 654 1633 653 1635 679 1633 680 503 653 503 653 479 651 504 627 528 654 1633 680 1633 680 503 627 1634 679 503 653 503 653 503 653 503 653 503 627 504 627 1660 679 503 653 1634 679 1634 652 1636 678 1634 679 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1221 1189 435 588 436 890 433 2388 435 590 434 1489 434 1789 434 1190 434 1188 436 2689 435 1488 435 1190 434 86920 327 929 326 377 327 652 328 +# +# Model: Samsung Royal_Caribbean +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1195 1216 407 616 408 917 406 2414 410 617 406 1518 405 1815 409 1215 434 590 408 2717 406 1516 408 2417 407 86346 375 881 375 326 378 602 377 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1253 1157 464 560 465 858 460 2364 460 563 462 1462 463 1761 467 1156 465 1159 462 2661 467 1456 469 1155 466 86886 331 925 330 373 328 652 331 +# +# Model: Samsung TV_1 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 1224 1186 467 556 437 887 441 2382 463 560 433 1490 466 1757 461 1163 458 566 438 2686 463 1460 465 2359 434 86319 301 953 302 402 330 649 334 +# +name: Vol_dn +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 0b 00 00 00 +# +name: Mute +type: parsed +protocol: Samsung32 +address: 07 00 00 00 +command: 0f 00 00 00 +# +# Model: Sencor 25801 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9040 4407 659 463 659 1579 661 461 685 437 658 463 658 463 658 464 657 470 656 1585 654 491 629 1611 628 1613 626 1614 625 1615 625 1615 625 502 625 1615 625 497 625 496 625 1614 625 1615 625 497 625 497 624 503 624 496 625 1615 624 1615 624 497 625 496 625 1615 624 1615 624 1614 624 41051 9036 2175 625 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 264 1848 264 792 264 792 264 792 264 792 264 792 264 1848 264 1848 264 792 264 1848 264 792 264 792 264 792 264 1848 264 792 264 43560 264 1848 264 792 264 792 264 792 264 792 264 1848 264 792 264 792 264 1848 264 792 264 1848 264 1848 264 1848 264 792 264 1848 264 43560 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 264 1848 264 792 264 792 264 792 264 792 264 1848 264 792 264 792 264 792 264 1848 264 792 264 792 264 792 264 1848 264 792 264 43560 264 1848 264 792 264 792 264 792 264 792 264 792 264 1848 264 1848 264 1848 264 792 264 1848 264 1848 264 1848 264 792 264 1848 264 43560 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 264 1848 264 792 264 792 264 792 264 792 264 792 264 1848 264 792 264 792 264 1848 264 792 264 792 264 792 264 1848 264 792 264 43560 264 1848 264 792 264 792 264 792 264 792 264 1848 264 792 264 1848 264 1848 264 792 264 1848 264 1848 264 1848 264 792 264 1848 264 43560 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 264 1848 264 792 264 792 264 792 264 792 264 792 264 792 264 1848 264 792 264 1848 264 792 264 792 264 792 264 1848 264 792 264 43560 264 1848 264 792 264 792 264 792 264 792 264 1848 264 1848 264 792 264 1848 264 792 264 1848 264 1848 264 1848 264 792 264 1848 264 43560 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 264 1848 264 792 264 792 264 792 264 792 264 1848 264 792 264 1848 264 792 264 1848 264 792 264 792 264 792 264 1848 264 792 264 43560 264 1848 264 792 264 792 264 792 264 792 264 792 264 1848 264 792 264 1848 264 792 264 1848 264 1848 264 1848 264 792 264 1848 264 43560 +# +# Model: Sharp Aquos_32BG3E +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 264 1848 264 792 264 792 264 792 264 792 264 1848 264 1848 264 1848 264 792 264 1848 264 792 264 792 264 792 264 1848 264 792 264 43560 264 1848 264 792 264 792 264 792 264 792 264 792 264 792 264 792 264 1848 264 792 264 1848 264 1848 264 1848 264 792 264 1848 264 43560 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3381 1656 439 401 438 1239 439 401 438 1240 439 401 438 1239 440 401 438 1239 440 400 439 1238 440 401 438 1239 440 1240 438 426 438 1240 439 400 439 1240 439 1240 438 1240 438 1240 438 401 438 401 438 402 437 1242 436 402 437 1242 437 402 437 403 436 1242 437 402 437 403 436 403 436 403 436 1242 437 1242 437 403 436 1242 437 403 436 403 436 403 436 1242 437 403 436 403 436 403 436 1243 436 403 436 1242 437 1242 437 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3378 1656 440 400 439 1239 440 400 439 1239 439 400 439 1239 440 399 440 1238 440 400 439 1239 521 371 412 1215 464 1215 464 399 440 1239 439 399 440 1239 439 1240 438 1240 438 1241 438 401 438 401 438 402 437 1242 437 402 437 1242 437 402 437 402 437 1242 437 402 437 402 437 402 437 1242 437 1242 437 1242 437 402 437 1242 437 402 437 402 437 402 437 1242 437 402 437 402 437 402 437 402 437 402 437 1242 437 1242 437 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3374 1661 436 403 436 1243 436 404 435 1243 436 404 435 1243 436 403 435 1242 464 376 463 1215 464 378 461 1216 463 1219 460 402 437 1242 436 403 436 1243 435 1244 435 1244 434 1245 433 405 434 406 433 406 433 1246 433 406 433 1246 433 406 433 406 433 1246 433 406 433 406 433 406 433 406 433 406 433 1246 433 406 433 1246 433 406 433 406 433 406 433 1246 433 406 433 406 433 406 433 1246 433 1246 433 1246 433 1246 433 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3516 1521 438 401 438 1240 439 401 438 1240 439 401 438 1239 440 400 439 1238 466 375 465 1213 465 375 522 1156 522 1158 520 372 466 1184 494 372 412 1240 439 1240 438 1241 438 1241 437 402 437 402 437 402 437 1242 437 402 437 1242 437 403 436 402 437 1243 436 403 436 403 436 403 436 1243 436 403 436 1243 436 403 436 1243 436 403 436 403 436 403 436 1243 436 403 436 403 436 403 436 403 436 1243 436 1243 436 1243 436 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3382 1654 467 374 440 1238 465 375 440 1238 441 398 441 1238 466 374 466 1211 442 424 415 1238 441 424 415 1240 465 1238 441 398 441 1238 441 398 441 1238 440 1238 440 1239 439 1240 439 400 439 400 439 401 438 1241 438 401 438 1241 438 401 438 401 438 1241 438 401 438 401 438 401 438 1241 438 401 439 401 438 401 438 1241 438 401 438 401 438 401 438 1241 438 401 438 401 438 401 438 401 438 1241 438 401 438 1241 438 +# +# Model: Sharp g0684cesa_NES_TV +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3361 1649 443 423 416 1237 442 398 441 1235 444 398 441 1237 442 398 441 1237 442 397 442 1236 443 397 442 1236 443 1237 442 399 440 1236 443 398 441 1263 416 1263 472 1207 471 1207 470 371 414 423 416 423 416 1263 416 423 416 1263 416 423 416 423 416 1262 417 422 417 422 417 422 417 422 417 1262 416 422 417 422 417 1262 416 423 441 398 441 398 441 1238 441 398 441 398 441 399 440 1239 440 399 440 399 440 1238 441 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 278 1811 277 788 246 794 250 764 280 786 248 792 252 1813 275 1815 273 791 253 1812 276 789 255 785 249 791 253 1812 276 789 255 45322 280 1809 279 786 248 766 278 788 246 794 250 1815 273 792 252 788 246 1819 280 785 249 1817 271 1819 280 1810 278 787 247 1818 281 43217 274 1818 270 794 250 764 280 786 248 792 252 788 256 1809 279 1811 277 788 246 1819 280 785 249 766 278 762 272 1819 280 785 248 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 278 1812 276 762 282 758 276 765 279 761 273 1818 281 1809 279 1811 277 762 282 1809 279 760 274 766 278 762 282 1809 279 760 274 44279 276 1813 275 763 281 759 275 766 278 762 272 768 276 764 280 760 274 1817 271 768 276 1815 273 1817 271 1819 280 759 275 1816 272 44276 279 1812 276 763 281 758 276 765 279 761 273 1818 281 1810 278 1811 277 762 272 1819 279 760 274 766 278 762 282 1809 279 760 274 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 272 1817 271 794 250 790 254 786 248 792 252 762 272 794 250 1815 273 792 252 1813 275 790 254 785 249 766 278 1813 275 789 255 46372 273 1817 271 794 250 763 281 785 248 792 252 1813 275 1814 274 791 253 1812 276 789 255 1810 278 1812 276 1813 275 790 254 1811 277 42170 277 1814 274 791 253 787 247 793 251 763 281 759 275 791 253 1812 276 789 255 1810 278 787 247 793 251 789 255 1810 278 787 247 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 275 1814 274 791 253 787 247 793 251 789 255 1810 278 787 247 1818 281 785 249 1816 272 793 251 789 255 785 249 1816 272 766 278 45325 274 1815 273 792 252 762 272 794 250 790 254 786 247 1818 270 794 250 1815 273 792 252 1813 275 1815 273 1816 272 793 251 1814 274 43224 277 1814 274 764 280 786 248 792 252 788 246 1820 279 786 247 1817 271 768 276 1815 273 792 252 761 273 794 250 1815 273 791 253 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 272 1817 271 794 250 790 254 786 248 792 252 1813 275 790 254 759 275 792 252 1813 275 789 255 785 248 792 252 1813 275 789 255 46372 273 1817 271 793 251 763 281 759 275 791 253 787 247 1818 281 1810 278 1812 276 789 255 1809 279 1811 277 1813 275 790 254 1811 277 42169 277 1815 273 792 252 787 247 794 250 789 255 1810 278 787 247 794 250 789 255 1810 278 761 273 793 251 789 255 1810 278 787 247 +# +# Model: Sharp LC-RC1-16 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 273 1816 272 767 277 789 255 785 249 791 253 787 246 1818 281 785 248 765 279 1812 276 789 255 759 275 791 253 1812 276 789 255 46372 281 1808 280 785 249 791 253 787 247 793 251 1814 274 791 253 1812 276 1814 274 791 253 1812 276 1814 274 1815 273 792 252 1813 275 42172 272 1819 280 785 249 765 279 761 273 768 276 764 280 1811 277 788 246 768 276 1815 273 792 252 788 246 794 250 1815 273 791 253 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 352 1747 353 694 354 694 353 694 354 694 354 693 354 1747 354 1745 355 693 355 1746 354 693 355 691 357 691 356 1745 355 689 356 46388 358 1740 359 689 358 688 360 689 358 689 359 1741 358 688 359 689 359 1741 358 691 356 1743 356 1743 356 1742 357 690 357 1741 356 44286 261 1839 260 786 262 786 262 785 263 785 263 786 262 1838 262 1838 262 786 262 1839 261 786 262 784 264 787 261 1837 263 783 262 46491 261 1839 261 786 262 786 262 786 261 786 262 1839 261 786 262 786 262 1839 261 786 262 1838 262 1838 262 1838 262 786 262 1835 262 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 308 1788 312 735 313 734 313 735 313 736 312 735 313 736 312 1788 312 735 313 1786 314 735 313 735 313 734 314 1787 313 731 314 47491 314 1786 314 734 314 734 314 734 314 732 316 1786 315 1784 316 733 315 1786 315 732 316 1785 315 1787 314 1785 315 733 315 1781 317 43284 314 1784 316 731 317 730 318 732 316 732 316 732 316 731 317 1783 317 732 316 1784 316 733 315 731 317 731 317 1784 317 730 315 47500 313 1785 315 733 315 733 315 733 315 734 314 1787 313 1786 315 733 315 1787 313 733 315 1787 313 1786 314 1787 313 734 314 1783 314 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 357 1739 361 688 360 690 357 688 360 691 356 1741 359 689 359 1741 358 690 358 1742 357 691 357 690 357 693 355 1744 356 689 356 46399 355 1743 357 691 357 691 357 691 357 692 355 692 356 1744 356 692 356 1744 356 692 356 1744 355 1745 355 1745 263 785 263 1835 263 44390 261 1839 262 786 262 787 261 787 261 786 262 1839 261 787 261 1840 261 786 262 1839 262 786 262 785 263 785 263 1839 261 784 261 46497 262 1839 261 786 262 786 262 787 261 786 262 786 262 1838 262 786 262 1840 260 787 261 1839 261 1840 260 1840 260 787 261 1835 263 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 260 1838 262 787 261 786 262 787 261 786 261 1838 262 1838 262 1838 262 786 262 1839 261 786 262 786 262 786 262 1839 261 784 261 45433 261 1839 261 785 263 785 263 785 263 785 263 786 262 786 262 785 263 1838 262 786 262 1839 261 1837 263 1838 262 786 262 1835 263 45436 356 1744 356 691 356 691 356 692 356 691 356 1743 357 1744 356 1745 355 691 356 1745 354 692 356 693 354 691 356 1745 355 691 353 45343 359 1742 358 688 360 688 359 689 359 687 361 688 360 689 359 688 360 1741 359 688 360 1742 358 1739 361 1741 359 689 359 1739 359 45337 286 1813 287 761 287 761 287 761 287 760 288 1813 287 1813 287 1813 287 760 288 1813 287 760 288 760 288 760 288 1812 288 757 288 45413 287 1814 286 761 287 761 287 759 289 760 288 760 288 761 287 760 288 1813 287 762 286 1813 287 1813 287 1813 361 685 288 1810 288 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 356 1743 358 689 359 690 358 689 359 689 359 1741 359 688 360 688 360 688 360 1739 362 687 361 687 361 686 362 1739 361 684 361 47444 287 1812 288 760 288 759 289 760 288 759 288 760 288 1813 287 1812 288 1812 312 736 288 1812 288 1812 288 1813 287 760 288 1810 287 43309 286 1812 288 761 310 736 288 759 289 762 286 1812 312 737 311 736 312 736 312 1790 311 737 311 736 312 736 312 1789 311 734 312 47501 313 1786 314 733 315 734 314 733 315 734 314 733 315 1785 315 1785 315 1786 314 733 315 1785 315 1786 314 1786 314 731 317 1782 316 43279 339 1760 317 731 339 709 316 730 318 733 315 1783 317 732 316 731 317 731 317 1784 316 732 316 731 317 731 317 1785 315 729 316 +# +# Model: Sharp Roku_TV +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 287 1812 288 760 288 761 287 761 287 761 287 760 288 1814 286 760 288 761 287 1813 287 760 288 760 288 760 288 1813 287 757 312 47498 340 1759 341 707 341 709 315 731 340 708 340 1760 340 707 341 1759 318 1783 317 730 318 1782 318 1783 317 1784 316 731 317 1782 316 43284 314 1787 314 736 312 734 314 734 314 734 314 735 313 1787 314 735 313 734 314 1787 313 735 313 734 314 733 315 1787 314 732 313 47500 285 1813 361 685 363 687 361 687 361 686 362 1739 360 687 361 1740 359 1740 360 689 358 1741 359 1743 356 1746 353 690 357 1741 356 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 195 1833 300 766 280 760 275 790 276 737 309 731 304 1801 301 1804 309 731 304 1801 270 795 282 758 277 762 273 1832 270 769 246 45851 326 1780 302 739 307 785 282 732 303 736 310 1795 307 732 303 763 303 1775 307 733 334 1798 273 1832 270 1810 251 814 273 1780 281 43762 302 1804 309 758 277 737 330 762 284 730 305 734 301 1803 310 1796 306 733 302 1829 273 767 279 734 301 791 275 1804 278 762 253 45870 307 1798 304 763 272 767 279 787 279 760 275 1829 284 730 305 734 301 1804 309 757 278 1827 275 1804 278 1828 274 765 270 1835 278 43740 303 1776 306 787 279 760 275 765 281 759 307 758 277 1775 307 1799 303 736 299 1832 281 759 276 763 304 736 299 1832 281 733 302 45820 306 1800 302 764 282 758 277 788 278 762 284 1821 281 732 303 736 310 1796 307 733 302 1829 273 1806 276 1830 272 767 268 1837 245 43772 302 1778 304 789 277 762 284 756 279 786 249 765 301 1777 336 1770 301 764 282 1824 278 761 274 765 301 738 308 1824 278 761 274 +# +# Model: Sharp TV2 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 254 1721 360 681 354 738 308 706 329 711 355 1774 307 1772 361 1744 327 687 359 1772 299 742 335 705 330 736 279 1825 298 742 283 44773 384 1721 360 707 308 707 359 733 302 711 335 705 361 704 331 708 338 1766 336 704 331 1773 329 1776 306 1773 360 681 323 1782 331 44726 411 1722 328 686 360 733 302 711 335 705 361 1742 329 1803 330 1749 332 708 327 1777 335 705 330 710 325 741 274 1830 303 737 278 44778 359 1747 355 712 303 711 355 711 335 705 330 709 337 703 363 703 332 1770 332 709 337 1767 335 1771 300 1752 360 733 302 1776 326 44731 355 1751 330 711 355 737 309 705 330 710 336 1793 309 1771 331 1774 307 706 360 1771 300 740 326 714 332 735 280 1798 325 741 274 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 277 1806 274 775 281 776 279 770 275 774 281 768 277 1814 277 1806 274 775 280 1803 277 780 276 773 282 766 279 1831 249 781 274 45962 281 1803 277 771 274 783 273 802 253 770 275 1834 257 801 255 768 277 1807 273 775 280 1811 280 1804 276 1806 274 774 282 1811 280 43887 275 1809 282 767 278 779 276 799 256 766 279 771 274 1843 248 1810 281 767 278 1806 274 782 274 776 279 796 249 1807 273 784 282 45962 279 1804 276 772 273 784 282 768 277 798 247 1836 255 802 253 796 249 1808 283 766 279 1813 278 1805 275 1808 272 776 279 1813 278 43890 282 1801 279 769 276 781 274 775 280 769 276 773 283 1834 257 1801 279 769 276 1808 272 784 282 767 278 772 273 1810 281 776 279 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 282 1801 280 769 276 781 275 774 282 768 277 1805 276 781 275 775 281 769 276 1832 249 809 247 776 280 770 275 1834 247 784 282 47004 273 1811 280 768 277 780 276 773 283 767 278 771 274 1816 275 1809 272 1811 280 768 277 1815 276 1807 274 1809 282 767 278 1813 278 42841 284 1799 282 768 277 780 276 774 282 767 278 1805 276 781 275 774 282 768 277 1806 275 782 274 775 281 769 276 1807 274 783 283 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 273 1810 281 768 277 780 276 799 257 767 278 797 248 1816 275 774 282 768 277 1806 275 808 248 801 255 795 250 1807 274 809 247 46989 278 1805 276 799 246 784 282 767 278 798 247 1835 256 775 281 1803 278 1806 275 773 283 1809 272 1811 280 1803 278 771 274 1817 274 42868 278 1806 275 799 257 775 281 768 277 798 247 776 280 1811 280 770 275 773 283 1801 280 777 279 771 274 801 255 1802 279 778 278 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 282 1801 280 769 276 781 275 775 281 768 277 772 273 784 282 1801 280 770 275 1807 274 784 282 767 278 771 274 1809 282 775 281 47005 282 1801 280 770 275 782 274 776 280 769 276 1807 274 1817 274 775 280 1803 278 771 274 1817 274 1809 282 1801 280 770 275 1815 276 42841 273 1811 280 769 276 781 275 774 282 768 277 772 273 783 273 1811 280 794 251 1806 275 782 274 776 280 769 276 1808 273 783 283 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 277 1805 276 799 246 785 281 768 277 798 247 1810 281 775 281 1804 277 771 274 1810 281 801 255 795 250 772 273 1811 280 776 280 45957 274 1809 272 777 279 778 278 772 273 776 280 769 276 1815 276 799 246 1811 280 769 276 1815 276 1807 274 1809 282 767 278 1813 278 43890 280 1803 278 797 248 783 273 776 280 796 249 1834 247 784 282 1802 279 796 249 1808 273 783 283 793 252 770 275 1808 273 784 282 +# +# Model: Silver LE410004 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 281 1803 278 771 274 782 274 775 281 769 276 1807 274 1817 274 1835 256 766 280 1804 277 780 276 773 283 767 278 1831 250 780 276 44910 276 1835 256 766 280 777 279 771 275 774 282 767 278 779 277 773 283 1800 281 768 277 1814 277 1806 275 1808 283 766 279 1811 280 44937 280 1803 278 771 274 783 283 766 279 770 275 1808 273 1819 272 1811 280 768 277 1807 274 782 274 776 280 769 276 1833 248 784 282 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8958 4449 510 4475 515 4444 515 2213 508 4477 513 2215 516 2212 509 2219 512 2217 514 2214 517 2211 540 2214 517 2211 510 4449 510 2218 513 4472 507 2220 511 30572 8960 2218 513 87698 8966 2211 510 87701 8963 2214 568 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 8956 4451 508 2220 511 2217 514 4470 510 4449 510 2218 544 2211 510 2218 513 2215 516 2212 509 2220 511 2217 514 2214 517 2211 510 2245 517 4441 508 2220 511 35049 8961 2215 516 87696 8959 2217 514 87698 8956 2220 511 87701 8964 2213 508 +# +name: Ch_prev +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Ch_next +type: parsed +protocol: SIRC +address: 01 00 00 00 +command: 11 00 00 00 +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 1C 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 4B 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 4F 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 09 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 05 00 00 00 +# +# Model: Strong TVD221_B1825 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 08 00 00 00 +# +name: Power +type: raw +frequency: 36700 +duty_cycle: 0.330000 +data: 3488 3488 872 2616 872 872 872 872 872 2616 872 872 872 2616 872 872 872 872 872 872 872 872 872 872 872 2616 872 872 872 2616 872 2616 872 872 872 2616 872 872 872 2616 872 2616 872 2616 872 2616 872 2616 872 872 872 34008 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3488 3488 872 2616 872 872 872 872 872 2616 872 872 872 2616 872 872 872 872 872 872 872 872 872 2616 872 2616 872 872 872 2616 872 2616 872 872 872 2616 872 872 872 2616 872 2616 872 2616 872 2616 872 872 872 872 872 34008 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3488 3488 872 2616 872 872 872 872 872 2616 872 872 872 2616 872 2616 872 872 872 872 872 872 872 2616 872 2616 872 872 872 2616 872 2616 872 872 872 2616 872 872 872 872 872 2616 872 2616 872 2616 872 872 872 872 872 34008 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3488 3488 872 2616 872 872 872 872 872 2616 872 872 872 2616 872 872 872 872 872 872 872 872 872 2616 872 872 872 872 872 2616 872 2616 872 872 872 2616 872 872 872 2616 872 2616 872 2616 872 2616 872 872 872 2616 872 34008 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3488 3488 872 2616 872 872 872 872 872 2616 872 872 872 2616 872 872 872 872 872 872 872 2616 872 872 872 872 872 872 872 2616 872 2616 872 872 872 2616 872 872 872 2616 872 2616 872 2616 872 872 872 2616 872 2616 872 34008 +# +# Model: TCL 32S327 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 3488 3488 872 2616 872 872 872 872 872 2616 872 872 872 2616 872 2616 872 2616 872 872 872 872 872 2616 872 2616 872 872 872 2616 872 2616 872 872 872 2616 872 872 872 872 872 872 872 2616 872 2616 872 872 872 872 872 34008 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4012 3982 514 1984 519 1979 514 1984 519 1979 514 983 519 980 512 1988 516 982 520 1979 514 1984 519 1978 515 983 519 980 511 987 515 983 519 980 511 1988 516 1982 511 987 515 1983 521 978 514 985 517 981 521 1978 515 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 171 91301 168 17617 175 4907 167 15077 177 55723 170 4910 174 7448 174 15071 173 4908 176 17609 172 7450 172 7451 171 68432 169 7454 168 7454 168 4913 171 7451 171 4911 173 4908 176 7446 176 7446 176 50643 176 4905 169 4912 172 7450 172 7450 172 7451 171 4911 173 7448 174 4908 176 4905 169 7452 170 7453 169 50649 169 4912 173 4909 175 7447 175 7448 174 7448 174 4908 176 7446 176 4906 168 4913 171 7450 172 7451 171 50647 171 4911 173 4908 176 7445 177 7445 177 7446 176 4906 168 7454 168 4913 171 4910 174 7448 174 7449 173 50645 173 9990 169 7453 169 7454 168 7455 177 4905 169 7452 170 4912 172 4909 175 7447 175 7447 175 50644 174 4908 176 4905 169 7453 169 7454 168 7454 178 4904 170 7452 170 4912 172 4909 175 7447 175 7448 174 55726 177 4905 169 7453 169 7452 170 7453 169 4914 170 7451 171 4910 174 4907 177 7446 176 7446 176 50642 176 4906 168 4913 171 7452 170 7452 170 7453 169 4912 172 7450 172 4910 174 4907 177 7445 177 7446 176 50642 176 9987 172 7450 171 15074 170 4911 173 7450 172 4909 175 4906 168 7455 177 7446 176 50641 176 9988 171 7451 171 7451 171 7452 170 4912 172 7450 172 4909 175 4906 168 7454 178 7444 177 50641 167 4914 170 4911 174 7449 173 7449 173 7449 173 4909 175 7447 175 4907 177 4904 170 15075 169 50650 168 4913 171 4910 174 7448 174 7448 174 7448 174 4908 176 7447 175 4907 177 4903 171 7452 170 7452 170 50648 170 4912 172 4909 175 7446 176 7447 175 7448 174 4907 177 7446 176 4906 168 4912 172 7450 172 7451 171 50648 170 4912 172 4908 176 7446 176 7446 176 7447 175 4906 168 7454 178 4904 170 4911 173 15072 172 50646 173 4908 176 4906 168 7454 168 7455 177 7444 168 4915 169 7453 169 4913 171 4910 174 7448 174 7448 174 50644 174 4908 176 4904 170 7453 169 7453 169 7454 178 4904 170 40 198 7213 170 4910 174 4907 177 7446 176 7446 176 50643 175 4905 169 4913 171 7451 171 7452 170 7452 170 4911 173 7449 173 4909 175 4905 169 7454 168 7454 168 50650 178 4904 170 4911 173 7449 173 7450 172 7451 171 4910 174 7448 174 4907 177 4905 169 7453 169 7453 169 50649 169 4913 172 4910 174 15071 173 7449 173 4908 176 7447 175 4906 168 4913 171 7451 171 7451 171 50648 170 4911 173 4909 175 7446 176 7447 175 7448 174 4908 176 7446 176 4905 169 4913 171 15074 170 50648 170 4912 172 20154 175 7448 174 4907 177 7445 177 4904 170 4912 172 7450 172 7450 172 50647 170 4911 173 4908 176 7447 174 7447 174 7448 174 4908 176 7446 175 4906 168 4913 171 7451 171 7452 170 50649 168 4912 172 4910 174 7447 175 7448 174 7449 172 4909 175 7447 174 4907 177 4904 170 7452 170 7453 169 50649 169 4913 171 4910 174 7449 173 7449 173 7450 172 4909 175 7447 175 4906 168 4914 170 7452 170 7453 169 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 170 86221 170 7452 169 7453 168 12536 170 7452 169 43026 168 15077 177 7445 176 20150 168 15076 178 4904 170 7452 170 73516 168 7454 168 12536 171 7452 170 12534 172 7450 171 7451 171 43025 170 7452 170 15075 169 15077 177 4904 170 7452 170 7452 170 12535 172 7450 172 81138 178 12526 170 27779 172 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 169 12535 174 88758 177 7445 168 4913 172 7450 173 4908 177 7445 168 48108 171 4910 175 4907 168 7454 169 7454 169 7453 170 7454 169 7452 171 4911 174 7448 175 4907 168 7454 169 +# +# Model: Telefunken d32f660x5cwi +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 173 7448 175 7445 178 7443 170 7451 172 7449 174 7447 176 7445 168 4912 173 7447 176 7446 177 4903 172 43015 177 7443 170 7451 172 7448 175 7445 178 7444 169 7452 171 7449 174 4907 168 7452 171 7450 173 4907 168 43018 175 7447 176 7444 169 7453 170 7450 173 7448 175 7446 177 399 174 6870 169 4911 174 7447 176 7445 168 4912 173 43013 169 7451 172 7451 172 7448 175 7445 168 7453 170 7451 172 7449 174 4906 169 7452 171 7450 173 4907 168 43019 174 7447 176 7445 178 7443 170 7450 173 7448 175 7446 177 7444 169 4910 175 7447 176 7444 169 4913 172 43012 170 7452 171 7450 173 7448 175 7445 168 7453 170 7450 173 7449 174 4907 168 7452 171 7450 173 4908 177 43010 171 7448 175 7446 177 7444 169 7451 172 7448 175 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 328 605 321 283 643 290 313 589 316 287 639 596 642 589 316 285 318 285 641 591 637 294 309 587 328 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 331 601 314 289 647 285 308 595 320 282 644 591 647 584 321 282 644 587 318 285 641 290 313 583 332 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 330 632 294 281 645 288 315 585 320 284 642 593 645 585 320 284 642 589 649 583 644 586 329 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 333 600 315 288 638 294 309 594 321 281 645 591 636 595 643 589 638 592 313 290 646 585 330 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 334 628 287 289 647 285 308 593 322 307 619 589 649 582 323 280 646 586 641 589 316 287 639 89967 331 602 313 290 646 286 307 595 320 283 643 591 647 584 321 282 644 588 639 591 314 288 638 +# +# Model: Thomson 40FS3003 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 332 601 314 290 646 286 307 594 321 282 644 590 648 582 323 281 645 586 641 290 313 583 644 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9219 4484 662 469 661 469 661 1627 660 471 658 474 656 499 631 499 631 499 631 1657 630 1657 631 500 630 1657 631 1657 631 1657 630 1657 631 1657 631 500 630 500 630 500 630 1657 630 500 631 500 630 500 631 500 630 1657 630 1658 630 1657 631 500 630 1657 631 1658 630 1658 630 1658 630 40107 9106 2202 631 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9218 4484 636 495 660 469 661 1627 660 471 658 472 658 474 656 475 655 474 656 1632 655 1632 656 474 657 1632 656 1631 657 1632 656 1631 656 1632 656 474 656 1632 655 475 656 474 657 474 656 474 656 474 656 474 656 1632 655 474 656 1632 656 1632 656 1632 656 1632 656 1632 656 1632 656 40103 9107 2177 655 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9245 4429 689 467 662 468 661 1626 660 471 658 473 657 474 656 474 656 474 656 1631 657 1631 657 474 656 1631 656 1632 656 1631 657 1631 657 1631 656 1632 656 1631 657 474 656 474 656 474 657 474 656 474 656 474 657 474 656 474 656 1631 656 1632 656 1632 656 1632 656 1632 656 1631 656 40082 9109 2175 656 +# +# Model: Vizio D43-C1 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9219 4485 636 495 660 469 661 1626 661 471 658 473 657 474 656 499 631 500 630 1657 630 1657 631 500 630 1657 630 1657 631 1657 631 1657 630 1657 631 1657 631 500 630 500 630 1657 631 500 630 500 630 500 630 500 631 500 630 1657 631 1657 631 500 630 1657 631 1658 630 1657 631 1658 630 39868 9106 2178 655 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9016 4408 603 512 602 512 629 1599 686 428 630 483 630 484 629 485 628 492 627 1604 625 1605 624 491 623 1630 599 1631 598 1631 598 1631 598 1636 599 515 599 515 599 515 599 1631 599 515 599 515 599 515 599 521 599 1631 599 1631 599 1631 598 515 599 1631 598 1631 598 1631 598 1632 598 39999 9016 2166 626 95735 9012 2168 625 95735 9013 2167 626 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 988 605 587 1176 588 589 586 882 587 1178 585 588 588 589 587 1470 587 1175 589 588 587 590 586 13118 986 608 587 1177 587 588 587 882 587 1178 586 588 587 589 587 1470 587 1177 587 590 585 588 588 13117 987 609 586 1177 587 590 585 882 587 1177 587 589 586 588 588 1472 585 1176 588 588 587 588 587 13118 986 608 587 1177 586 589 587 881 588 1177 587 588 587 589 587 1470 587 1177 587 589 586 588 588 13116 988 609 586 1178 586 589 586 883 586 1177 587 589 586 590 586 1471 586 1178 585 588 587 588 588 13117 987 609 586 1177 587 589 587 883 586 1177 586 589 587 589 586 1472 585 1176 588 589 587 589 587 13116 988 610 585 1178 586 588 588 882 587 1176 588 590 586 588 587 1471 586 1177 586 589 587 589 586 +# +# Model: Zenith SC3492Z +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 990 603 589 1175 589 587 588 882 587 1175 588 588 588 589 586 1470 587 1471 587 588 588 881 588 12528 988 609 586 1177 587 589 586 882 587 1177 586 588 587 589 587 1470 587 1471 587 588 588 881 588 12527 989 607 588 1175 589 588 588 881 588 1175 588 588 588 587 589 1470 587 1470 588 589 587 881 588 12528 988 607 588 1175 589 588 587 882 587 1178 586 587 588 588 588 1472 586 1470 588 588 587 881 588 12529 987 608 587 1175 589 587 589 882 587 1175 589 589 587 587 589 1469 588 1470 588 587 589 881 588 12528 988 607 588 1175 589 590 585 883 586 1176 587 587 589 588 588 1471 586 1470 588 587 589 883 586 12527 989 607 588 1176 588 588 588 880 589 1176 588 588 588 589 586 1469 588 1469 589 588 587 881 588 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 611 386 612 3984 612 4984 612 387 611 3985 612 387 611 3986 611 4985 611 387 611 3986 611 4985 611 387 611 3986 610 4987 609 4985 611 389 609 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 595 403 595 4002 594 5001 595 404 594 4002 594 405 593 4003 594 5001 595 5001 595 405 593 4003 594 405 593 4004 592 5004 592 406 592 4004 593 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 596 401 597 4000 596 5000 596 402 596 4001 596 402 596 4002 595 5000 596 5000 596 403 595 4002 595 402 596 4003 594 5002 594 5000 596 404 594 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 484 514 485 4112 485 5111 485 514 485 4112 484 5110 486 513 486 4111 485 5110 486 514 485 4112 484 513 486 4112 484 5112 484 5113 483 513 486 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 591 405 593 4005 591 5005 591 407 591 4006 591 408 590 4006 591 5006 590 5004 592 406 591 4005 592 5005 591 407 591 4006 591 407 591 4007 590 +# +# Model: Zenith tv +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 598 400 598 3999 597 4999 596 401 597 4000 597 5000 596 402 596 3999 598 4999 597 401 597 4000 597 4998 598 402 596 4000 597 402 596 4000 597 +# +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 561 442 562 4055 591 5032 592 413 591 4026 593 411 593 4026 593 5030 618 387 617 4001 592 5032 591 440 564 4028 591 5033 590 5034 589 440 563 +# +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 591 411 593 4025 593 5030 593 411 593 4025 593 411 593 4025 618 5005 619 5003 593 439 564 4027 592 439 564 4029 590 5033 590 439 564 4055 563 123130 617 387 618 3999 593 5031 592 439 564 4027 592 439 564 4029 590 5034 589 5059 563 440 563 4056 563 442 561 4058 561 5063 560 443 560 4059 560 +# +name: Ch_next +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 591 412 592 4026 593 5030 593 411 593 4025 594 411 617 4001 618 5005 619 5004 592 439 565 4028 590 5032 591 439 564 4055 564 440 563 4056 563 +# +name: Ch_prev +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 591 412 592 4026 592 5029 594 411 593 4025 593 5030 593 411 617 4001 619 5003 617 415 565 4027 592 5032 591 439 564 4030 589 439 564 4055 564 +# +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 591 413 591 4027 592 5030 593 411 593 4026 593 412 592 4025 618 5005 620 5004 592 439 564 4028 591 439 564 4029 590 5033 590 5035 588 440 563 122125 617 387 619 4000 592 5032 591 439 564 4028 591 439 565 4030 589 5034 589 5059 564 441 562 4057 562 442 561 4058 561 5063 560 5063 560 444 560 +# +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 590 412 592 4026 593 5030 593 412 592 4025 594 5029 594 412 616 4002 618 5003 593 439 564 4027 592 439 564 4029 590 5033 590 5035 588 440 563 diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index a52f141c4..4e5a965a7 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -2,15 +2,30 @@ #include -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 { diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.h b/applications/main/infrared/scenes/common/infrared_scene_universal_common.h index a6c697d77..277598e42 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.h +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.h @@ -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); diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 8f9dc4338..35cd971d8 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -124,6 +124,49 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { rpc_system_app_confirm(infrared->rpc_ctx, result); + } else if( + event.event == InfraredCustomEventTypeRpcButtonPressReleaseName || + event.event == InfraredCustomEventTypeRpcButtonPressReleaseIndex) { + bool result = false; + + // Send the signal once and stop + if(rpc_state == InfraredRpcStateLoaded) { + if(event.event == InfraredCustomEventTypeRpcButtonPressReleaseName) { + const char* button_name = furi_string_get_cstr(infrared->button_name); + size_t index; + const bool index_found = + infrared_remote_get_signal_index(infrared->remote, button_name, &index); + app_state->current_button_index = index_found ? (signed)index : + InfraredButtonIndexNone; + FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name); + } else { + FURI_LOG_D( + TAG, "Sending signal with index \"%ld\"", app_state->current_button_index); + } + if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { + InfraredErrorCode error = infrared_tx_send_once_button_index( + infrared, app_state->current_button_index); + if(!INFRARED_ERROR_PRESENT(error)) { + const char* remote_name = infrared_remote_get_name(infrared->remote); + infrared_text_store_set(infrared, 0, "emulating\n%s", remote_name); + + infrared_scene_rpc_show(infrared); + result = true; + } else { + rpc_system_app_set_error_code( + infrared->rpc_ctx, RpcAppSystemErrorCodeInternalParse); + rpc_system_app_set_error_text( + infrared->rpc_ctx, "Cannot load button data"); + result = false; + } + } + } + + if(result) { + scene_manager_set_scene_state( + infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); + } + rpc_system_app_confirm(infrared->rpc_ctx, result); } else if( event.event == InfraredCustomEventTypeRpcExit || event.event == InfraredCustomEventTypeRpcSessionClose || diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index 9288a4a4d..15c4b8db2 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -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); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_audio.c b/applications/main/infrared/scenes/infrared_scene_universal_audio.c index a15b2ce99..223251f10 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_audio.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_audio.c @@ -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); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_projector.c b/applications/main/infrared/scenes/infrared_scene_universal_projector.c index c665444fb..c939a0f1b 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_projector.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_projector.c @@ -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); diff --git a/applications/main/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c index 16633e29c..6130861ee 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_tv.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_tv.c @@ -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); } diff --git a/applications/main/infrared/views/infrared_progress_view.c b/applications/main/infrared/views/infrared_progress_view.c index 432da7ff1..76454b5bd 100644 --- a/applications/main/infrared/views/infrared_progress_view.c +++ b/applications/main/infrared/views/infrared_progress_view.c @@ -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; } diff --git a/applications/main/infrared/views/infrared_progress_view.h b/applications/main/infrared/views/infrared_progress_view.h index c44f1a482..c33f1e553 100644 --- a/applications/main/infrared/views/infrared_progress_view.h +++ b/applications/main/infrared/views/infrared_progress_view.h @@ -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 diff --git a/applications/main/lfrfid/application.fam b/applications/main/lfrfid/application.fam index c067d786f..d6fca74f4 100644 --- a/applications/main/lfrfid/application.fam +++ b/applications/main/lfrfid/application.fam @@ -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, ) diff --git a/applications/main/lfrfid/lfrfid_cli.c b/applications/main/lfrfid/lfrfid_cli.c index a25032d6a..63ca046b9 100644 --- a/applications/main/lfrfid/lfrfid_cli.c +++ b/applications/main/lfrfid/lfrfid_cli.c @@ -1,11 +1,12 @@ #include #include #include -#include +#include #include #include #include #include +#include #include @@ -14,15 +15,6 @@ #include #include -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 - 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); diff --git a/applications/main/lfrfid/lfrfid_i.h b/applications/main/lfrfid/lfrfid_i.h index 2fcedcd7f..29a5eb902 100644 --- a/applications/main/lfrfid/lfrfid_i.h +++ b/applications/main/lfrfid/lfrfid_i.h @@ -8,7 +8,6 @@ #include #include #include -#include #include #include diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 6dbde7c37..f645033b2 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -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"], ) diff --git a/applications/main/nfc/helpers/mf_classic_key_cache.c b/applications/main/nfc/helpers/mf_classic_key_cache.c index 1b945660c..763c4dea7 100644 --- a/applications/main/nfc/helpers/mf_classic_key_cache.c +++ b/applications/main/nfc/helpers/mf_classic_key_cache.c @@ -166,7 +166,7 @@ void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfCl } } -bool mf_classic_key_cahce_get_next_key( +bool mf_classic_key_cache_get_next_key( MfClassicKeyCache* instance, uint8_t* sector_num, MfClassicKey* key, diff --git a/applications/main/nfc/helpers/mf_classic_key_cache.h b/applications/main/nfc/helpers/mf_classic_key_cache.h index b09f4526b..50a1f5c30 100644 --- a/applications/main/nfc/helpers/mf_classic_key_cache.h +++ b/applications/main/nfc/helpers/mf_classic_key_cache.h @@ -16,7 +16,7 @@ bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data); -bool mf_classic_key_cahce_get_next_key( +bool mf_classic_key_cache_get_next_key( MfClassicKeyCache* instance, uint8_t* sector_num, MfClassicKey* key, diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c index ba8f10b93..fbf331d3b 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.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); diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 4fece16be..5c668d530 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -14,7 +14,8 @@ enum { SubmenuIndexDetectReader = SubmenuIndexCommonMax, SubmenuIndexWrite, SubmenuIndexUpdate, - SubmenuIndexDictAttack + SubmenuIndexDictAttack, + SubmenuIndexCrackNonces, }; static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { @@ -72,7 +73,7 @@ static NfcCommand nfc_scene_read_poller_callback_mf_classic(NfcGenericEvent even uint8_t sector_num = 0; MfClassicKey key = {}; MfClassicKeyType key_type = MfClassicKeyTypeA; - if(mf_classic_key_cahce_get_next_key( + if(mf_classic_key_cache_get_next_key( instance->mfc_key_cache, §or_num, &key, &key_type)) { mfc_event->data->read_sector_request_data.sector_num = sector_num; mfc_event->data->read_sector_request_data.key = key; @@ -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; } } diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index 783cbb871..96e4a30f9 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -180,6 +180,9 @@ void nfc_render_mf_desfire_file_settings_data( case MfDesfireFileTypeCyclicRecord: type = "cyclic"; break; + case MfDesfireFileTypeTransactionMac: + type = "txn-mac"; + break; default: type = "unknown"; } @@ -237,6 +240,15 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_cat_printf(str, "size %lu\n", record_size); furi_string_cat_printf(str, "num %lu max %lu\n", record_count, settings->record.max); break; + case MfDesfireFileTypeTransactionMac: + record_count = 0; + furi_string_cat_printf( + str, + "key opt %02X ver %02X\n", + settings->transaction_mac.key_option, + settings->transaction_mac.key_version); + furi_string_cat_printf(str, "cnt limit %lu\n", settings->transaction_mac.counter_limit); + break; } bool is_auth_required = true; diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 5365d6bef..7a829c329 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -470,6 +470,26 @@ static void nfc_show_initial_scene_for_device(NfcApp* nfc) { scene_manager_next_scene(nfc->scene_manager, scene); } +void nfc_app_run_external(NfcApp* nfc, const char* app_path) { + furi_assert(nfc); + furi_assert(app_path); + + Loader* loader = furi_record_open(RECORD_LOADER); + + loader_enqueue_launch(loader, app_path, NULL, LoaderDeferredLaunchFlagGui); + + FuriString* self_path = furi_string_alloc(); + furi_check(loader_get_application_launch_path(loader, self_path)); + + loader_enqueue_launch( + loader, furi_string_get_cstr(self_path), NULL, LoaderDeferredLaunchFlagGui); + furi_string_free(self_path); + + furi_record_close(RECORD_LOADER); + + view_dispatcher_stop(nfc->view_dispatcher); +} + int32_t nfc_app(void* p) { if(!nfc_is_hal_ready()) return 0; diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index e13af79aa..9eb26a847 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -36,6 +35,7 @@ #include "helpers/felica_auth.h" #include "helpers/slix_unlock.h" +#include #include #include #include @@ -86,6 +86,8 @@ #define NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH \ (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict.nfc") +#define NFC_MFKEY32_APP_PATH (EXT_PATH("apps/NFC/mfkey.fap")) + typedef enum { NfcRpcStateIdle, NfcRpcStateEmulating, @@ -182,6 +184,11 @@ typedef enum { NfcViewDetectReader, } NfcView; +typedef enum { + NfcSceneSaveConfirmStateDetectReader, + NfcSceneSaveConfirmStateCrackNonces, +} NfcSceneSaveConfirmState; + int32_t nfc_task(void* p); void nfc_text_store_set(NfcApp* nfc, const char* text, ...); @@ -217,3 +224,5 @@ bool nfc_save_file(NfcApp* instance, FuriString* path); void nfc_make_app_folder(NfcApp* instance); void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string); + +void nfc_app_run_external(NfcApp* nfc, const char* app_path); diff --git a/applications/main/nfc/nfc_cli.c b/applications/main/nfc/nfc_cli.c index 90ac26d7c..af3fd62eb 100644 --- a/applications/main/nfc/nfc_cli.c +++ b/applications/main/nfc/nfc_cli.c @@ -1,8 +1,9 @@ #include #include -#include +#include #include #include +#include #include @@ -17,7 +18,7 @@ static void nfc_cli_print_usage(void) { } } -static void nfc_cli_field(Cli* cli, FuriString* args) { +static void nfc_cli_field(PipeSide* pipe, FuriString* args) { UNUSED(args); // Check if nfc worker is not busy if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { @@ -32,7 +33,7 @@ static void nfc_cli_field(Cli* cli, FuriString* args) { printf("Field is on. Don't leave device in this mode for too long.\r\n"); printf("Press 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(50); } @@ -40,7 +41,7 @@ static void nfc_cli_field(Cli* cli, FuriString* args) { furi_hal_nfc_release(); } -static void nfc_cli(Cli* cli, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -52,7 +53,7 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) { } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "field") == 0) { - nfc_cli_field(cli, args); + nfc_cli_field(pipe, args); break; } } @@ -63,12 +64,4 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -void nfc_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "nfc", CliCommandFlagDefault, nfc_cli, NULL); - furi_record_close(RECORD_CLI); -#else - UNUSED(nfc_cli); -#endif -} +CLI_COMMAND_INTERFACE(nfc, execute, CliCommandFlagDefault, 1024, CLI_APPID); diff --git a/applications/main/nfc/plugins/supported_cards/clipper.c b/applications/main/nfc/plugins/supported_cards/clipper.c index 3eba82425..eb459d419 100644 --- a/applications/main/nfc/plugins/supported_cards/clipper.c +++ b/applications/main/nfc/plugins/supported_cards/clipper.c @@ -101,7 +101,8 @@ static const IdMapping bart_zones[] = { {.id = 0x001d, .name = "Lake Merrit"}, {.id = 0x001e, .name = "Fruitvale"}, {.id = 0x001f, .name = "Coliseum"}, - {.id = 0x0021, .name = "San Leandro"}, + {.id = 0x0020, .name = "San Leandro"}, + {.id = 0x0021, .name = "Bay Fair"}, {.id = 0x0022, .name = "Hayward"}, {.id = 0x0023, .name = "South Hayward"}, {.id = 0x0024, .name = "Union City"}, @@ -131,6 +132,9 @@ static const IdMapping muni_zones[] = { {.id = 0x000b, .name = "Castro"}, {.id = 0x000c, .name = "Forest Hill"}, // Guessed {.id = 0x000d, .name = "West Portal"}, + {.id = 0x0019, .name = "Union Square/Market Street"}, + {.id = 0x001a, .name = "Chinatown - Rose Pak"}, + {.id = 0x001b, .name = "Yerba Buena/Moscone"}, }; static const size_t kNumMUNIZones = COUNT(muni_zones); diff --git a/applications/main/nfc/plugins/supported_cards/disney_infinity.c b/applications/main/nfc/plugins/supported_cards/disney_infinity.c new file mode 100644 index 000000000..a98d39ec2 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/disney_infinity.c @@ -0,0 +1,121 @@ +#include +#include "nfc_supported_card_plugin.h" + +#include + +#include +#include +#include +#include + +#define TAG "DisneyInfinity" +#define UID_LEN 7 + +// Derived from https://nfc.toys/#new-interoperability-for-infinity +static uint8_t seed[38] = {0x0A, 0x14, 0xFD, 0x05, 0x07, 0xFF, 0x4B, 0xCD, 0x02, 0x6B, + 0xA8, 0x3F, 0x0A, 0x3B, 0x89, 0xA9, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x28, 0x63, 0x29, 0x20, 0x44, 0x69, 0x73, + 0x6E, 0x65, 0x79, 0x20, 0x32, 0x30, 0x31, 0x33}; + +void di_key(const uint8_t* uid, MfClassicKey* key) { + uint8_t hash[20]; + memcpy(seed + 16, uid, UID_LEN); + mbedtls_sha1(seed, sizeof(seed), hash); + key->data[0] = hash[3]; + key->data[1] = hash[2]; + key->data[2] = hash[1]; + key->data[3] = hash[0]; + key->data[4] = hash[7]; + key->data[5] = hash[6]; +} + +static bool disney_infinity_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + size_t* uid_len = 0; + bool is_read = false; + MfClassicData* data = mf_classic_alloc(); + + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len); + MfClassicDeviceKeys keys = {}; + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + data->type = type; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + di_key(uid_bytes, &keys.key_a[i]); + di_key(uid_bytes, &keys.key_b[i]); + FURI_BIT_SET(keys.key_a_mask, i); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data: %d", error); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool disney_infinity_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + size_t* uid_len = 0; + bool parsed = false; + FuriString* name = furi_string_alloc(); + const uint8_t verify_sector = 0; + MfClassicKey key = {}; + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len); + + do { + // verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + + di_key(uid_bytes, &key); + if(memcmp(key.data, sec_tr->key_a.data, 6) != 0) break; + + // At some point I'd like to add name lookup like Skylanders + furi_string_printf(parsed_data, "\e#Disney Infinity\n"); + + parsed = true; + + } while(false); + + furi_string_free(name); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin disney_infinity_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = NULL, // Need UID to verify key(s) + .read = disney_infinity_read, + .parse = disney_infinity_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor disney_infinity_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &disney_infinity_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* disney_infinity_plugin_ep(void) { + return &disney_infinity_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/ndef.c b/applications/main/nfc/plugins/supported_cards/ndef.c index fb2c4da48..06982e111 100644 --- a/applications/main/nfc/plugins/supported_cards/ndef.c +++ b/applications/main/nfc/plugins/supported_cards/ndef.c @@ -22,6 +22,7 @@ #include #include +#include #define TAG "NDEF" @@ -181,30 +182,34 @@ static bool ndef_get(Ndef* ndef, size_t pos, size_t len, void* buf) { // So the first 93 (31*3) data blocks correspond to 128 real blocks. // Last 128 blocks are 8 sectors: 15 data blocks, 1 sector trailer. // So the last 120 (8*15) data blocks correspond to 128 real blocks. - div_t small_sector_data_blocks = div(pos, MF_CLASSIC_BLOCK_SIZE); + const size_t real_block_data_offset = pos % MF_CLASSIC_BLOCK_SIZE; + size_t small_sector_data_blocks = pos / MF_CLASSIC_BLOCK_SIZE; size_t large_sector_data_blocks = 0; - if(small_sector_data_blocks.quot > 93) { - large_sector_data_blocks = small_sector_data_blocks.quot - 93; - small_sector_data_blocks.quot = 93; + if(small_sector_data_blocks > 93) { + large_sector_data_blocks = small_sector_data_blocks - 93; + small_sector_data_blocks = 93; } - div_t small_sectors = div(small_sector_data_blocks.quot, 3); - size_t real_block = small_sectors.quot * 4 + small_sectors.rem; - if(small_sectors.quot >= 16) { + const size_t small_sector_block_offset = small_sector_data_blocks % 3; + const size_t small_sectors = small_sector_data_blocks / 3; + size_t real_block = small_sectors * 4 + small_sector_block_offset; + if(small_sectors >= 16) { real_block += 4; // Skip MAD2 } if(large_sector_data_blocks) { - div_t large_sectors = div(large_sector_data_blocks, 15); - real_block += large_sectors.quot * 16 + large_sectors.rem; + const size_t large_sector_block_offset = large_sector_data_blocks % 15; + const size_t large_sectors = large_sector_data_blocks / 15; + real_block += large_sectors * 16 + large_sector_block_offset; } - const uint8_t* cur = &ndef->mfc.blocks[real_block].data[small_sector_data_blocks.rem]; + const uint8_t* cur = &ndef->mfc.blocks[real_block].data[real_block_data_offset]; while(len) { size_t sector_trailer = mf_classic_get_sector_trailer_num_by_block(real_block); const uint8_t* end = &ndef->mfc.blocks[sector_trailer].data[0]; - size_t chunk_len = MIN((size_t)(end - cur), len); + const size_t chunk_len = MIN((size_t)(end - cur), len); memcpy(buf, cur, chunk_len); + buf += chunk_len; len -= chunk_len; if(len) { @@ -244,7 +249,9 @@ static inline bool is_printable(char c) { static bool is_text(const uint8_t* buf, size_t len) { for(size_t i = 0; i < len; i++) { - if(!is_printable(buf[i])) return false; + if(!is_printable(buf[i]) && !(buf[i] == '\0' && i == len - 1)) { + return false; + } } return true; } @@ -260,7 +267,7 @@ static bool ndef_dump(Ndef* ndef, const char* prefix, size_t pos, size_t len, bo for(size_t i = 0; i < len; i++) { char c; if(!ndef_get(ndef, pos + i, 1, &c)) return false; - if(!is_printable(c)) { + if(!is_printable(c) && !(c == '\0' && i == len - 1)) { furi_string_left(ndef->output, string_prev); force_hex = true; break; @@ -268,14 +275,18 @@ static bool ndef_dump(Ndef* ndef, const char* prefix, size_t pos, size_t len, bo furi_string_push_back(ndef->output, c); } } - if(force_hex) { - for(size_t i = 0; i < len; i++) { - uint8_t b; - if(!ndef_get(ndef, pos + i, 1, &b)) return false; - furi_string_cat_printf(ndef->output, "%02X ", b); + if(!force_hex) { + furi_string_cat(ndef->output, "\n"); + } else { + uint8_t buf[4]; + for(size_t i = 0; i < len; i += sizeof(buf)) { + uint8_t buf_len = MIN(sizeof(buf), len - i); + if(!ndef_get(ndef, pos + i, buf_len, &buf)) return false; + pretty_format_bytes_hex_canonical( + ndef->output, 4, PRETTY_FORMAT_FONT_MONOSPACE, buf, buf_len); + furi_string_cat(ndef->output, "\n"); } } - furi_string_cat(ndef->output, "\n"); return true; } @@ -285,9 +296,7 @@ static void if(!force_hex && is_text(buf, len)) { furi_string_cat_printf(ndef->output, "%.*s", len, (const char*)buf); } else { - for(size_t i = 0; i < len; i++) { - furi_string_cat_printf(ndef->output, "%02X ", ((const uint8_t*)buf)[i]); - } + pretty_format_bytes_hex_canonical(ndef->output, 4, PRETTY_FORMAT_FONT_MONOSPACE, buf, len); } furi_string_cat(ndef->output, "\n"); } @@ -582,7 +591,7 @@ bool ndef_parse_record( NdefTnf tnf, const char* type, uint8_t type_len) { - FURI_LOG_D(TAG, "payload type: %.*s len: %hu", type_len, type, len); + FURI_LOG_D(TAG, "payload type: %.*s len: %hu pos: %zu", type_len, type, len, pos); if(!len) { furi_string_cat(ndef->output, "Empty\n"); return true; @@ -887,13 +896,13 @@ static bool ndef_mfc_parse(const NfcDevice* device, FuriString* parsed_data) { for(uint8_t mad = 0; mad < COUNT_OF(mads); mad++) { const size_t block = mads[mad].block; const size_t sector = mf_classic_get_sector_by_block(block); - if(sector_count <= sector) break; // Skip this MAD if not present + if(sector_count <= sector) continue; // Skip this MAD if not present // Check MAD key const MfClassicSectorTrailer* sector_trailer = mf_classic_get_sector_trailer_by_sector(data, sector); const uint64_t sector_key_a = bit_lib_bytes_to_num_be( sector_trailer->key_a.data, COUNT_OF(sector_trailer->key_a.data)); - if(sector_key_a != mad_key) return false; + if(sector_key_a != mad_key) continue; // Find NDEF AIDs for(uint8_t aid_index = 0; aid_index < mads[mad].aid_count; aid_index++) { const uint8_t* aid = &data->block[block].data[2 + aid_index * AID_SIZE]; @@ -917,7 +926,7 @@ static bool ndef_mfc_parse(const NfcDevice* device, FuriString* parsed_data) { data_size = 93 + (sector_count - 32) * 15; } else { data_size = sector_count * 3; - if(sector_count >= 16) { + if(sector_count > 16) { data_size -= 3; // Skip MAD2 } } diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 9f2491691..49bbaebe8 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -310,9 +310,11 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { last_payment_date.year, last_payment_date.hour, last_payment_date.minute); - //payment amount. This needs to be investigated more, currently it shows incorrect amount on some cards. - uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8]; - furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100); + //Last payment amount. + uint16_t last_payment = ((data->block[18].data[10] << 16) | + (data->block[18].data[9] << 8) | (data->block[18].data[8])) / + 100; + furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment); furi_string_free(card_number_s); furi_string_free(tmp_s); //This is for 4K Plantains. @@ -369,9 +371,11 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { last_payment_date.year, last_payment_date.hour, last_payment_date.minute); - //payment amount - uint16_t last_payment = (data->block[18].data[9] << 8) | data->block[18].data[8]; - furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment / 100); + //Last payment amount + uint16_t last_payment = ((data->block[18].data[10] << 16) | + (data->block[18].data[9] << 8) | (data->block[18].data[8])) / + 100; + furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment); furi_string_free(card_number_s); furi_string_free(tmp_s); } diff --git a/applications/main/nfc/plugins/supported_cards/skylanders.c b/applications/main/nfc/plugins/supported_cards/skylanders.c index 6c199f114..b5dc0ab86 100644 --- a/applications/main/nfc/plugins/supported_cards/skylanders.c +++ b/applications/main/nfc/plugins/supported_cards/skylanders.c @@ -7,13 +7,36 @@ #include #include -#define TAG "Skylanders" +#define TAG "Skylanders" +#define POLY UINT64_C(0x42f0e1eba9ea3693) +#define TOP UINT64_C(0x800000000000) +#define UID_LEN 4 +#define KEY_MASK 0xFFFFFFFFFFFF static const uint64_t skylanders_key = 0x4b0b20107ccb; static const char* nfc_resources_header = "Flipper NFC resources"; static const uint32_t nfc_resources_file_version = 1; +uint64_t crc64_like(uint64_t result, uint8_t sector) { + result ^= (uint64_t)sector << 40; + for(int i = 0; i < 8; i++) { + result = (result & TOP) ? (result << 1) ^ POLY : result << 1; + } + return result; +} + +uint64_t taghash(uint32_t uid) { + uint64_t result = 0x9AE903260CC4; + uint8_t uidBytes[UID_LEN] = {0}; + memcpy(uidBytes, &uid, UID_LEN); + + for(int i = 0; i < UID_LEN; i++) { + result = crc64_like(result, uidBytes[i]); + } + return result; +} + static bool skylanders_search_data( Storage* storage, const char* file_name, @@ -88,6 +111,12 @@ static bool skylanders_read(Nfc* nfc, NfcDevice* device) { MfClassicData* data = mf_classic_alloc(); nfc_device_copy_data(device, NfcProtocolMfClassic, data); + size_t* uid_len = 0; + const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len); + uint32_t uid = 0; + memcpy(&uid, uid_bytes, sizeof(uid)); + uint64_t hash = taghash(uid); + do { MfClassicType type = MfClassicType1k; MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); @@ -96,10 +125,18 @@ static bool skylanders_read(Nfc* nfc, NfcDevice* device) { data->type = type; MfClassicDeviceKeys keys = {}; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data); - FURI_BIT_SET(keys.key_a_mask, i); - bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_b[i].data); - FURI_BIT_SET(keys.key_b_mask, i); + if(i == 0) { + bit_lib_num_to_bytes_be(skylanders_key, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + } else { + uint64_t sectorhash = crc64_like(hash, i); + uint64_t key = sectorhash & KEY_MASK; + uint8_t* keyBytes = (uint8_t*)&key; + memcpy(keys.key_a[i].data, keyBytes, sizeof(MfClassicKey)); + FURI_BIT_SET(keys.key_a_mask, i); + memset(keys.key_b[i].data, 0, sizeof(MfClassicKey)); + FURI_BIT_SET(keys.key_b_mask, i); + } } error = mf_classic_poller_sync_read(nfc, &keys, data); @@ -134,7 +171,7 @@ static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) { uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); if(key != skylanders_key) break; - const uint16_t id = (uint16_t)*data->block[1].data; + const uint16_t id = data->block[1].data[1] << 8 | data->block[1].data[0]; if(id == 0) break; Storage* storage = furi_record_open(RECORD_STORAGE); diff --git a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc index 4d2f92336..180d35ff5 100644 --- a/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc +++ b/applications/main/nfc/resources/nfc/assets/mf_classic_dict.nfc @@ -1285,7 +1285,11 @@ ABFEDC124578 5E594208EF02 AF9E38D36582 10DF4D1859C8 +# +# Key B B5244E79B0C8 +# +# Ukraine hotel F5C1C4C5DE34 BB7923232725 A95BD5BB4FC5 diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c index 660674ceb..3311ef0e0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c @@ -1,4 +1,10 @@ #include "../nfc_app_i.h" +#include "loader/loader.h" + +typedef enum { + NfcSceneMfClassicMfKeyCompleteStateAppMissing, + NfcSceneMfClassicMfKeyCompleteStateAppPresent, +} NfcSceneMfClassicMfKeyCompleteState; void nfc_scene_mf_classic_mfkey_complete_callback( GuiButtonType result, @@ -15,22 +21,47 @@ void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) { widget_add_string_element( instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Completed!"); - widget_add_string_multiline_element( - instance->widget, - 64, - 13, - AlignCenter, - AlignTop, - FontSecondary, - "Now use Mfkey32 to extract \nkeys: r.flipper.net/nfc-tools"); - widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25); - widget_add_button_element( - instance->widget, - GuiButtonTypeRight, - "Finish", - nfc_scene_mf_classic_mfkey_complete_callback, - instance); + NfcSceneMfClassicMfKeyCompleteState scene_state = + storage_common_exists(instance->storage, NFC_MFKEY32_APP_PATH) ? + NfcSceneMfClassicMfKeyCompleteStateAppPresent : + NfcSceneMfClassicMfKeyCompleteStateAppMissing; + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfClassicMfkeyComplete, scene_state); + + if(scene_state == NfcSceneMfClassicMfKeyCompleteStateAppMissing) { + widget_add_string_multiline_element( + instance->widget, + 64, + 13, + AlignCenter, + AlignTop, + FontSecondary, + "Now use Mfkey32 to extract \nkeys: r.flipper.net/nfc-tools"); + widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Finish", + nfc_scene_mf_classic_mfkey_complete_callback, + instance); + } else { + widget_add_string_multiline_element( + instance->widget, + 60, + 16, + AlignLeft, + AlignTop, + FontSecondary, + "Now run Mfkey32\n to extract \nkeys"); + widget_add_icon_element(instance->widget, 5, 18, &I_WarningDolphin_45x42); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Run", + nfc_scene_mf_classic_mfkey_complete_callback, + instance); + } view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } @@ -40,8 +71,14 @@ bool nfc_scene_mf_classic_mfkey_complete_on_event(void* context, SceneManagerEve if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeRight) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcSceneStart); + NfcSceneMfClassicMfKeyCompleteState scene_state = scene_manager_get_scene_state( + instance->scene_manager, NfcSceneMfClassicMfkeyComplete); + if(scene_state == NfcSceneMfClassicMfKeyCompleteStateAppMissing) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneStart); + } else { + nfc_app_run_external(instance, NFC_MFKEY32_APP_PATH); + } } } else if(event.type == SceneManagerEventTypeBack) { const uint32_t prev_scenes[] = {NfcSceneSavedMenu, NfcSceneStart}; diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c index 7c76260b4..a477a08b9 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c @@ -34,7 +34,7 @@ NfcCommand nfc_mf_classic_update_initial_worker_callback(NfcGenericEvent event, uint8_t sector_num = 0; MfClassicKey key = {}; MfClassicKeyType key_type = MfClassicKeyTypeA; - if(mf_classic_key_cahce_get_next_key( + if(mf_classic_key_cache_get_next_key( instance->mfc_key_cache, §or_num, &key, &key_type)) { mfc_event->data->read_sector_request_data.sector_num = sector_num; mfc_event->data->read_sector_request_data.key = key; diff --git a/applications/main/nfc/scenes/nfc_scene_save_confirm.c b/applications/main/nfc/scenes/nfc_scene_save_confirm.c index 9d0a206d3..d8fb20642 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_save_confirm.c @@ -29,7 +29,14 @@ bool nfc_scene_save_confirm_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); consumed = true; } else if(event.event == DialogExResultLeft) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); + NfcSceneSaveConfirmState scene_state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm); + + NfcScene scene = scene_state == NfcSceneSaveConfirmStateCrackNonces ? + NfcSceneMfClassicMfkeyComplete : + NfcSceneMfClassicDetectReader; + + scene_manager_next_scene(nfc->scene_manager, scene); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 9dbfd1ae7..06999fe9f 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -33,7 +33,13 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfUltralightCKeys); } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); + NfcSceneSaveConfirmState scene_state = + scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm); + + NfcScene scene = scene_state == NfcSceneSaveConfirmStateCrackNonces ? + NfcSceneMfClassicMfkeyComplete : + NfcSceneMfClassicDetectReader; + scene_manager_next_scene(nfc->scene_manager, scene); consumed = true; } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { consumed = scene_manager_search_and_switch_to_another_scene( diff --git a/applications/main/onewire/application.fam b/applications/main/onewire/application.fam index 3d35abce9..e38bcdfef 100644 --- a/applications/main/onewire/application.fam +++ b/applications/main/onewire/application.fam @@ -1,6 +1,8 @@ App( - appid="onewire_start", - apptype=FlipperAppType.STARTUP, - entry_point="onewire_on_system_start", - order=60, + appid="cli_onewire", + targets=["f7"], + apptype=FlipperAppType.PLUGIN, + entry_point="cli_onewire_ep", + requires=["cli"], + sources=["onewire_cli.c"], ) diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c index af3d4e803..193de76e4 100644 --- a/applications/main/onewire/onewire_cli.c +++ b/applications/main/onewire/onewire_cli.c @@ -1,38 +1,29 @@ #include #include -#include +#include +#include +#include #include #include -static void onewire_cli(Cli* cli, FuriString* args, void* context); - -void onewire_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "onewire", CliCommandFlagDefault, onewire_cli, cli); - furi_record_close(RECORD_CLI); -#else - UNUSED(onewire_cli); -#endif -} - static void onewire_cli_print_usage(void) { printf("Usage:\r\n"); printf("onewire search\r\n"); } -static void onewire_cli_search(Cli* cli) { - UNUSED(cli); +static void onewire_cli_search(PipeSide* pipe) { + UNUSED(pipe); OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton); + Power* power = furi_record_open(RECORD_POWER); uint8_t address[8]; bool done = false; printf("Search started\r\n"); onewire_host_start(onewire); - furi_hal_power_enable_otg(); + power_enable_otg(power, true); while(!done) { if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) { @@ -49,11 +40,13 @@ static void onewire_cli_search(Cli* cli) { furi_delay_ms(100); } - furi_hal_power_disable_otg(); + power_enable_otg(power, false); + onewire_host_free(onewire); + furi_record_close(RECORD_POWER); } -void onewire_cli(Cli* cli, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -65,8 +58,10 @@ void onewire_cli(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "search") == 0) { - onewire_cli_search(cli); + onewire_cli_search(pipe); } furi_string_free(cmd); } + +CLI_COMMAND_INTERFACE(onewire, execute, CliCommandFlagDefault, 1024, CLI_APPID); diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 1abcf7f54..fe7b07b1e 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -20,10 +20,10 @@ App( ) App( - appid="subghz_start", + appid="cli_subghz", targets=["f7"], - apptype=FlipperAppType.STARTUP, - entry_point="subghz_on_system_start", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_subghz_ep", + requires=["cli"], sources=["subghz_cli.c", "helpers/subghz_chat.c"], - order=40, ) diff --git a/applications/main/subghz/helpers/subghz_chat.c b/applications/main/subghz/helpers/subghz_chat.c index 9945b69c8..5c55aedeb 100644 --- a/applications/main/subghz/helpers/subghz_chat.c +++ b/applications/main/subghz/helpers/subghz_chat.c @@ -1,5 +1,6 @@ #include "subghz_chat.h" #include +#include #define TAG "SubGhzChat" @@ -14,7 +15,7 @@ struct SubGhzChatWorker { FuriMessageQueue* event_queue; uint32_t last_time_rx_data; - Cli* cli; + PipeSide* pipe; }; /** Worker thread @@ -30,7 +31,7 @@ static int32_t subghz_chat_worker_thread(void* context) { event.event = SubGhzChatEventUserEntrance; furi_message_queue_put(instance->event_queue, &event, 0); while(instance->worker_running) { - if(cli_read_timeout(instance->cli, (uint8_t*)&c, 1, 1000) == 1) { + if(pipe_receive(instance->pipe, (uint8_t*)&c, 1) == 1) { event.event = SubGhzChatEventInputData; event.c = c; furi_message_queue_put(instance->event_queue, &event, FuriWaitForever); @@ -55,10 +56,10 @@ static void subghz_chat_worker_update_rx_event_chat(void* context) { furi_message_queue_put(instance->event_queue, &event, FuriWaitForever); } -SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli) { +SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe) { SubGhzChatWorker* instance = malloc(sizeof(SubGhzChatWorker)); - instance->cli = cli; + instance->pipe = pipe; instance->thread = furi_thread_alloc_ex("SubGhzChat", 2048, subghz_chat_worker_thread, instance); diff --git a/applications/main/subghz/helpers/subghz_chat.h b/applications/main/subghz/helpers/subghz_chat.h index 2c454b75d..25fce0ecf 100644 --- a/applications/main/subghz/helpers/subghz_chat.h +++ b/applications/main/subghz/helpers/subghz_chat.h @@ -1,7 +1,7 @@ #pragma once #include "../subghz_i.h" #include -#include +#include typedef struct SubGhzChatWorker SubGhzChatWorker; @@ -19,7 +19,7 @@ typedef struct { char c; } SubGhzChatEvent; -SubGhzChatWorker* subghz_chat_worker_alloc(Cli* cli); +SubGhzChatWorker* subghz_chat_worker_alloc(PipeSide* pipe); void subghz_chat_worker_free(SubGhzChatWorker* instance); bool subghz_chat_worker_start( SubGhzChatWorker* instance, diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index fe2c08fc6..571f3feb9 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -49,6 +49,7 @@ typedef enum { SubGhzCustomEventSceneRpcLoad, SubGhzCustomEventSceneRpcButtonPress, SubGhzCustomEventSceneRpcButtonRelease, + SubGhzCustomEventSceneRpcButtonPressRelease, SubGhzCustomEventSceneRpcSessionClose, SubGhzCustomEventViewReceiverOK, diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index e3a0c6057..eaa56c549 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -4,27 +4,22 @@ #include #include +#include + #define TAG "SubGhz" static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) { UNUSED(instance); - uint8_t attempts = 5; - while(--attempts > 0) { - if(furi_hal_power_enable_otg()) break; - } - if(attempts == 0) { - if(furi_hal_power_get_usb_voltage() < 4.5f) { - FURI_LOG_E( - TAG, - "Error power otg enable. BQ2589 check otg fault = %d", - furi_hal_power_check_otg_fault() ? 1 : 0); - } - } + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) { UNUSED(instance); - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } SubGhzTxRx* subghz_txrx_alloc(void) { diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index c1476746d..040a15114 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -85,6 +85,43 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); rpc_system_app_confirm(subghz->rpc_ctx, result); + } else if(event.event == SubGhzCustomEventSceneRpcButtonPressRelease) { + bool result = false; + if(state == SubGhzRpcStateLoaded) { + switch( + subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) { + case SubGhzTxRxStartTxStateErrorOnlyRx: + rpc_system_app_set_error_code( + subghz->rpc_ctx, RpcAppSystemErrorCodeRegionLock); + rpc_system_app_set_error_text( + subghz->rpc_ctx, + "Transmission on this frequency is restricted in your region"); + break; + case SubGhzTxRxStartTxStateErrorParserOthers: + rpc_system_app_set_error_code( + subghz->rpc_ctx, RpcAppSystemErrorCodeInternalParse); + rpc_system_app_set_error_text( + subghz->rpc_ctx, "Error in protocol parameters description"); + break; + + default: //if(SubGhzTxRxStartTxStateOk) + result = true; + subghz_blink_start(subghz); + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateTx); + break; + } + } + + // Stop transmission + if(state == SubGhzRpcStateTx) { + subghz_txrx_stop(subghz->txrx); + subghz_blink_stop(subghz); + result = true; + } + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle); + rpc_system_app_confirm(subghz->rpc_ctx, result); } else if(event.event == SubGhzCustomEventSceneRpcLoad) { bool result = false; if(state == SubGhzRpcStateIdle) { diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 22e81f2eb..b17a232a8 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -43,6 +43,9 @@ static void subghz_rpc_command_callback(const RpcAppSystemEvent* event, void* co } else if(event->type == RpcAppEventTypeButtonRelease) { view_dispatcher_send_custom_event( subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonRelease); + } else if(event->type == RpcAppEventTypeButtonPressRelease) { + view_dispatcher_send_custom_event( + subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonPressRelease); } else { rpc_system_app_confirm(subghz->rpc_ctx, false); } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 6375f2eee..08f2406dd 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -16,6 +18,7 @@ #include #include +#include #include "helpers/subghz_chat.h" @@ -28,22 +31,15 @@ #define TAG "SubGhzCli" static void subghz_cli_radio_device_power_on(void) { - uint8_t attempts = 5; - while(--attempts > 0) { - if(furi_hal_power_enable_otg()) break; - } - if(attempts == 0) { - if(furi_hal_power_get_usb_voltage() < 4.5f) { - FURI_LOG_E( - "TAG", - "Error power otg enable. BQ2589 check otg fault = %d", - furi_hal_power_check_otg_fault() ? 1 : 0); - } - } + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } static void subghz_cli_radio_device_power_off(void) { - if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } static SubGhzEnvironment* subghz_cli_environment_init(void) { @@ -68,7 +64,7 @@ static SubGhzEnvironment* subghz_cli_environment_init(void) { return environment; } -void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_tx_carrier(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -98,7 +94,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { if(furi_hal_subghz_tx()) { printf("Transmitting at frequency %lu Hz\r\n", frequency); printf("Press CTRL+C to stop\r\n"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); } } else { @@ -111,7 +107,7 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_power_suppress_charge_exit(); } -void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_rx_carrier(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -139,7 +135,7 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_rx(); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); printf("RSSI: %03.1fdbm\r", (double)furi_hal_subghz_get_rssi()); fflush(stdout); @@ -172,7 +168,7 @@ static const SubGhzDevice* subghz_cli_command_get_device(uint32_t* device_ind) { return device; } -void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_tx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t key = 0x0074BADE; @@ -242,7 +238,9 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { furi_hal_power_suppress_charge_enter(); if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { - while(!(subghz_devices_is_async_complete_tx(device) || cli_cmd_interrupt_received(cli))) { + while( + !(subghz_devices_is_async_complete_tx(device) || + cli_is_pipe_broken_or_is_etx_next_char(pipe))) { printf("."); fflush(stdout); furi_delay_ms(333); @@ -299,7 +297,7 @@ static void subghz_cli_command_rx_callback( furi_string_free(text); } -void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_rx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT @@ -355,7 +353,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { frequency, device_ind); LevelDuration level_duration; - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { int ret = furi_stream_buffer_receive( instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == sizeof(LevelDuration)) { @@ -388,7 +386,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { free(instance); } -void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_rx_raw(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); uint32_t frequency = 433920000; @@ -426,7 +424,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { printf("Listening at %lu. Press CTRL+C to stop\r\n", frequency); LevelDuration level_duration; size_t counter = 0; - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { int ret = furi_stream_buffer_receive( instance->stream, &level_duration, sizeof(LevelDuration), 10); if(ret == 0) { @@ -462,7 +460,7 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { free(instance); } -void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { +void subghz_cli_command_decode_raw(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* file_name; file_name = furi_string_alloc(); @@ -532,7 +530,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { furi_string_get_cstr(file_name)); LevelDuration level_duration; - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_us(500); //you need to have time to read from the file from the SD card level_duration = subghz_file_encoder_worker_get_level_duration(file_worker_encoder); if(!level_duration_is_reset(level_duration)) { @@ -577,7 +575,7 @@ static FuriHalSubGhzPreset subghz_cli_get_preset_name(const char* preset_name) { return preset; } -void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) { // -V524 +void subghz_cli_command_tx_from_file(PipeSide* pipe, FuriString* args, void* context) { // -V524 UNUSED(context); FuriString* file_name; file_name = furi_string_alloc(); @@ -615,7 +613,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) if(furi_string_size(args)) { char* args_cstr = (char*)furi_string_get_cstr(args); StrintParseError parse_err = StrintParseNoError; - parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &repeat, 10); parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); if(parse_err) { cli_print_usage( @@ -781,7 +779,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { while( !(subghz_devices_is_async_complete_tx(device) || - cli_cmd_interrupt_received(cli))) { + cli_is_pipe_broken_or_is_etx_next_char(pipe))) { printf("."); fflush(stdout); furi_delay_ms(333); @@ -795,11 +793,11 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { subghz_transmitter_stop(transmitter); repeat--; - if(!cli_cmd_interrupt_received(cli) && repeat) + if(!cli_is_pipe_broken_or_is_etx_next_char(pipe) && repeat) subghz_transmitter_deserialize(transmitter, fff_data_raw); } - } while(!cli_cmd_interrupt_received(cli) && + } while(!cli_is_pipe_broken_or_is_etx_next_char(pipe) && (repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW"))); subghz_devices_sleep(device); @@ -844,8 +842,8 @@ static void subghz_cli_command_print_usage(void) { } } -static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) { - UNUSED(cli); +static void subghz_cli_command_encrypt_keeloq(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); uint8_t iv[16]; FuriString* source; @@ -887,8 +885,8 @@ static void subghz_cli_command_encrypt_keeloq(Cli* cli, FuriString* args) { furi_string_free(source); } -static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { - UNUSED(cli); +static void subghz_cli_command_encrypt_raw(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); uint8_t iv[16]; FuriString* source; @@ -924,7 +922,7 @@ static void subghz_cli_command_encrypt_raw(Cli* cli, FuriString* args) { furi_string_free(source); } -static void subghz_cli_command_chat(Cli* cli, FuriString* args) { +static void subghz_cli_command_chat(PipeSide* pipe, FuriString* args) { uint32_t frequency = 433920000; uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT @@ -958,7 +956,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { return; } - SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(cli); + SubGhzChatWorker* subghz_chat = subghz_chat_worker_alloc(pipe); if(!subghz_chat_worker_start(subghz_chat, device, frequency)) { printf("Startup error SubGhzChatWorker\r\n"); @@ -999,13 +997,12 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { chat_event = subghz_chat_worker_get_event_chat(subghz_chat); switch(chat_event.event) { case SubGhzChatEventInputData: - if(chat_event.c == CliSymbolAsciiETX) { + if(chat_event.c == CliKeyETX) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); break; - } else if( - (chat_event.c == CliSymbolAsciiBackspace) || (chat_event.c == CliSymbolAsciiDel)) { + } else if((chat_event.c == CliKeyBackspace) || (chat_event.c == CliKeyDEL)) { size_t len = furi_string_utf8_length(input); if(len > furi_string_utf8_length(name)) { printf("%s", "\e[D\e[1P"); @@ -1027,7 +1024,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { } furi_string_set(input, sysmsg); } - } else if(chat_event.c == CliSymbolAsciiCR) { + } else if(chat_event.c == CliKeyCR) { printf("\r\n"); furi_string_push_back(input, '\r'); furi_string_push_back(input, '\n'); @@ -1041,7 +1038,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { furi_string_printf(input, "%s", furi_string_get_cstr(name)); printf("%s", furi_string_get_cstr(input)); fflush(stdout); - } else if(chat_event.c == CliSymbolAsciiLF) { + } else if(chat_event.c == CliKeyLF) { //cut out the symbol \n } else { putc(chat_event.c, stdout); @@ -1095,7 +1092,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { break; } } - if(!cli_is_connected(cli)) { + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) { printf("\r\n"); chat_event.event = SubGhzChatEventUserExit; subghz_chat_worker_put_event_chat(subghz_chat, &chat_event); @@ -1120,7 +1117,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { printf("\r\nExit chat\r\n"); } -static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { +static void execute(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -1131,53 +1128,53 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "chat") == 0) { - subghz_cli_command_chat(cli, args); + subghz_cli_command_chat(pipe, args); break; } if(furi_string_cmp_str(cmd, "tx") == 0) { - subghz_cli_command_tx(cli, args, context); + subghz_cli_command_tx(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "rx") == 0) { - subghz_cli_command_rx(cli, args, context); + subghz_cli_command_rx(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "rx_raw") == 0) { - subghz_cli_command_rx_raw(cli, args, context); + subghz_cli_command_rx_raw(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "decode_raw") == 0) { - subghz_cli_command_decode_raw(cli, args, context); + subghz_cli_command_decode_raw(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "tx_from_file") == 0) { - subghz_cli_command_tx_from_file(cli, args, context); + subghz_cli_command_tx_from_file(pipe, args, context); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "encrypt_keeloq") == 0) { - subghz_cli_command_encrypt_keeloq(cli, args); + subghz_cli_command_encrypt_keeloq(pipe, args); break; } if(furi_string_cmp_str(cmd, "encrypt_raw") == 0) { - subghz_cli_command_encrypt_raw(cli, args); + subghz_cli_command_encrypt_raw(pipe, args); break; } if(furi_string_cmp_str(cmd, "tx_carrier") == 0) { - subghz_cli_command_tx_carrier(cli, args, context); + subghz_cli_command_tx_carrier(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "rx_carrier") == 0) { - subghz_cli_command_rx_carrier(cli, args, context); + subghz_cli_command_rx_carrier(pipe, args, context); break; } } @@ -1188,14 +1185,4 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -void subghz_on_system_start(void) { -#ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - - cli_add_command(cli, "subghz", CliCommandFlagDefault, subghz_cli_command, NULL); - - furi_record_close(RECORD_CLI); -#else - UNUSED(subghz_cli_command); -#endif -} +CLI_COMMAND_INTERFACE(subghz, execute, CliCommandFlagDefault, 2048, CLI_APPID); diff --git a/applications/main/subghz/subghz_cli.h b/applications/main/subghz/subghz_cli.h index f6388218f..275135581 100644 --- a/applications/main/subghz/subghz_cli.h +++ b/applications/main/subghz/subghz_cli.h @@ -1,5 +1,3 @@ #pragma once -#include - void subghz_on_system_start(void); diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 08687a4f7..b210dd22b 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -28,6 +28,8 @@ #include "rpc/rpc_app.h" +#include + #include "helpers/subghz_threshold_rssi.h" #include "helpers/subghz_txrx.h" diff --git a/applications/main/u2f/u2f.c b/applications/main/u2f/u2f.c index 0143eb245..132baf4f9 100644 --- a/applications/main/u2f/u2f.c +++ b/applications/main/u2f/u2f.c @@ -280,6 +280,8 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) { MCHECK(mbedtls_md_hmac_update(&hmac_ctx, private, sizeof(private))); MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id))); MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, handle.hash)); + + mbedtls_md_free(&hmac_ctx); } // Generate public key @@ -387,6 +389,8 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) { MCHECK(mbedtls_md_hmac_update(&hmac_ctx, priv_key, sizeof(priv_key))); MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id))); MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, mac_control)); + + mbedtls_md_free(&hmac_ctx); } if(memcmp(req->key_handle.hash, mac_control, sizeof(mac_control)) != 0) { diff --git a/applications/services/application.fam b/applications/services/application.fam index 9ffb26dd6..a1a0429fa 100644 --- a/applications/services/application.fam +++ b/applications/services/application.fam @@ -3,6 +3,7 @@ App( name="Basic services", apptype=FlipperAppType.METAPACKAGE, provides=[ + "cli_vcp", "crypto_start", "rpc_start", "expansion_start", diff --git a/applications/services/bt/application.fam b/applications/services/bt/application.fam index 2d2840e3a..0e4cc918f 100644 --- a/applications/services/bt/application.fam +++ b/applications/services/bt/application.fam @@ -21,5 +21,5 @@ App( appid="bt_start", apptype=FlipperAppType.STARTUP, entry_point="bt_on_system_start", - order=70, + order=40, ) diff --git a/applications/services/bt/bt_cli.c b/applications/services/bt/bt_cli.c index 7505c424d..d81bc0505 100644 --- a/applications/services/bt/bt_cli.c +++ b/applications/services/bt/bt_cli.c @@ -1,15 +1,17 @@ #include #include -#include #include +#include +#include +#include #include #include "bt_settings.h" #include "bt_service/bt.h" #include -static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void bt_cli_command_hci_info(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); FuriString* buffer; @@ -19,7 +21,7 @@ static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) { furi_string_free(buffer); } -static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_carrier_tx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int power = 0; @@ -41,7 +43,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_tone_tx(channel, 0x19 + power); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); } furi_hal_bt_stop_tone_tx(); @@ -51,7 +53,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) } while(false); } -static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_carrier_rx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; @@ -69,7 +71,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) furi_hal_bt_start_packet_rx(channel, 1); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); printf("RSSI: %6.1f dB\r", (double)furi_hal_bt_get_rssi()); fflush(stdout); @@ -82,7 +84,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) } while(false); } -static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_packet_tx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int pattern = 0; @@ -119,7 +121,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_packet_tx(channel, pattern, datarate); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); } furi_hal_bt_stop_packet_test(); @@ -130,7 +132,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) } while(false); } -static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) { +static void bt_cli_command_packet_rx(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int channel = 0; int datarate = 1; @@ -152,7 +154,7 @@ static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) printf("Press CTRL+C to stop\r\n"); furi_hal_bt_start_packet_rx(channel, datarate); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { furi_delay_ms(250); printf("RSSI: %03.1f dB\r", (double)furi_hal_bt_get_rssi()); fflush(stdout); @@ -179,7 +181,7 @@ static void bt_cli_print_usage(void) { } } -static void bt_cli(Cli* cli, FuriString* args, void* context) { +static void bt_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); furi_record_open(RECORD_BT); @@ -194,24 +196,24 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) { break; } if(furi_string_cmp_str(cmd, "hci_info") == 0) { - bt_cli_command_hci_info(cli, args, NULL); + bt_cli_command_hci_info(pipe, args, NULL); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) && furi_hal_bt_is_testing_supported()) { if(furi_string_cmp_str(cmd, "tx_carrier") == 0) { - bt_cli_command_carrier_tx(cli, args, NULL); + bt_cli_command_carrier_tx(pipe, args, NULL); break; } if(furi_string_cmp_str(cmd, "rx_carrier") == 0) { - bt_cli_command_carrier_rx(cli, args, NULL); + bt_cli_command_carrier_rx(pipe, args, NULL); break; } if(furi_string_cmp_str(cmd, "tx_packet") == 0) { - bt_cli_command_packet_tx(cli, args, NULL); + bt_cli_command_packet_tx(pipe, args, NULL); break; } if(furi_string_cmp_str(cmd, "rx_packet") == 0) { - bt_cli_command_packet_rx(cli, args, NULL); + bt_cli_command_packet_rx(pipe, args, NULL); break; } } @@ -229,8 +231,8 @@ static void bt_cli(Cli* cli, FuriString* args, void* context) { void bt_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, RECORD_BT, CliCommandFlagDefault, bt_cli, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command(registry, "bt", CliCommandFlagDefault, bt_cli, NULL); furi_record_close(RECORD_CLI); #else UNUSED(bt_cli); diff --git a/applications/services/cli/application.fam b/applications/services/cli/application.fam index 7a57bb607..b305fb6b0 100644 --- a/applications/services/cli/application.fam +++ b/applications/services/cli/application.fam @@ -1,10 +1,51 @@ App( appid="cli", - name="CliSrv", - apptype=FlipperAppType.SERVICE, - entry_point="cli_srv", + apptype=FlipperAppType.STARTUP, + entry_point="cli_on_system_start", cdefines=["SRV_CLI"], - stack_size=4 * 1024, - order=30, - sdk_headers=["cli.h", "cli_vcp.h"], + sources=[ + "cli_command_gpio.c", + "cli_main_commands.c", + "cli_main_shell.c", + ], + # This STARTUP has to be processed before those that depend on the "cli" record. + # "cli" used to be a SERVICE, but it's been converted into a STARTUP in order to + # reduce RAM usage. The "block until record has been created" mechanism + # unfortunately leads to a deadlock if the STARTUPs are processed sequentially. + order=0, +) + +App( + appid="cli_vcp", + name="CliVcpSrv", + apptype=FlipperAppType.SERVICE, + entry_point="cli_vcp_srv", + stack_size=1024, + order=10, + sdk_headers=["cli_vcp.h", "cli.h"], + sources=["cli_vcp.c"], +) + +App( + appid="cli_hello_world", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_hello_world_ep", + requires=["cli"], + sources=["commands/hello_world.c"], +) + +App( + appid="cli_neofetch", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_neofetch_ep", + requires=["cli"], + sources=["commands/neofetch.c"], +) + +App( + appid="cli_subshell_demo", + apptype=FlipperAppType.PLUGIN, + entry_point="cli_subshell_demo_ep", + requires=["cli"], + sources=["commands/subshell_demo.c"], ) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c deleted file mode 100644 index 0d8f52c04..000000000 --- a/applications/services/cli/cli.c +++ /dev/null @@ -1,484 +0,0 @@ -#include "cli_i.h" -#include "cli_commands.h" -#include "cli_vcp.h" -#include -#include - -#define TAG "CliSrv" - -#define CLI_INPUT_LEN_LIMIT 256 - -Cli* cli_alloc(void) { - Cli* cli = malloc(sizeof(Cli)); - - CliCommandTree_init(cli->commands); - - cli->last_line = furi_string_alloc(); - cli->line = furi_string_alloc(); - - cli->session = NULL; - - cli->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - - cli->idle_sem = furi_semaphore_alloc(1, 0); - - return cli; -} - -void cli_putc(Cli* cli, char c) { - furi_check(cli); - if(cli->session != NULL) { - cli->session->tx((uint8_t*)&c, 1); - } -} - -char cli_getc(Cli* cli) { - furi_check(cli); - char c = 0; - if(cli->session != NULL) { - if(cli->session->rx((uint8_t*)&c, 1, FuriWaitForever) == 0) { - cli_reset(cli); - furi_delay_tick(10); - } - } else { - cli_reset(cli); - furi_delay_tick(10); - } - return c; -} - -void cli_write(Cli* cli, const uint8_t* buffer, size_t size) { - furi_check(cli); - if(cli->session != NULL) { - cli->session->tx(buffer, size); - } -} - -size_t cli_read(Cli* cli, uint8_t* buffer, size_t size) { - furi_check(cli); - if(cli->session != NULL) { - return cli->session->rx(buffer, size, FuriWaitForever); - } else { - return 0; - } -} - -size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout) { - furi_check(cli); - if(cli->session != NULL) { - return cli->session->rx(buffer, size, timeout); - } else { - return 0; - } -} - -bool cli_is_connected(Cli* cli) { - furi_check(cli); - if(cli->session != NULL) { - return cli->session->is_connected(); - } - return false; -} - -bool cli_cmd_interrupt_received(Cli* cli) { - furi_check(cli); - char c = '\0'; - if(cli_is_connected(cli)) { - if(cli->session->rx((uint8_t*)&c, 1, 0) == 1) { - return c == CliSymbolAsciiETX; - } - } else { - return true; - } - return false; -} - -void cli_print_usage(const char* cmd, const char* usage, const char* arg) { - furi_check(cmd); - furi_check(arg); - furi_check(usage); - - printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage); -} - -void cli_motd(void) { - printf("\r\n" - " _.-------.._ -,\r\n" - " .-\"```\"--..,,_/ /`-, -, \\ \r\n" - " .:\" /:/ /'\\ \\ ,_..., `. | |\r\n" - " / ,----/:/ /`\\ _\\~`_-\"` _;\r\n" - " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n" - " | | | 0 | | .-' ,/` /\r\n" - " | ,..\\ \\ ,.-\"` ,/` /\r\n" - " ; : `/`\"\"\\` ,/--==,/-----,\r\n" - " | `-...| -.___-Z:_______J...---;\r\n" - " : ` _-'\r\n" - " _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n" - "| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n" - "| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n" - "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" - "\r\n" - "Welcome to Flipper Zero Command Line Interface!\r\n" - "Read the manual: https://docs.flipper.net/development/cli\r\n" - "Run `help` or `?` to list available commands\r\n" - "\r\n"); - - const Version* firmware_version = furi_hal_version_get_firmware_version(); - if(firmware_version) { - printf( - "Firmware version: %s %s (%s%s built on %s)\r\n", - version_get_gitbranch(firmware_version), - version_get_version(firmware_version), - version_get_githash(firmware_version), - version_get_dirty_flag(firmware_version) ? "-dirty" : "", - version_get_builddate(firmware_version)); - } -} - -void cli_nl(Cli* cli) { - UNUSED(cli); - printf("\r\n"); -} - -void cli_prompt(Cli* cli) { - UNUSED(cli); - printf("\r\n>: %s", furi_string_get_cstr(cli->line)); - fflush(stdout); -} - -void cli_reset(Cli* cli) { - // cli->last_line is cleared and cli->line's buffer moved to cli->last_line - furi_string_move(cli->last_line, cli->line); - // Reiniting cli->line - cli->line = furi_string_alloc(); - cli->cursor_position = 0; -} - -static void cli_handle_backspace(Cli* cli) { - if(cli->cursor_position > 0) { - furi_assert(furi_string_size(cli->line) > 0); - // Other side - printf("\e[D\e[1P"); - fflush(stdout); - // Our side - furi_string_replace_at(cli->line, cli->cursor_position - 1, 1, ""); - - cli->cursor_position--; - } else { - cli_putc(cli, CliSymbolAsciiBell); - } -} - -static void cli_normalize_line(Cli* cli) { - furi_string_trim(cli->line); - cli->cursor_position = furi_string_size(cli->line); -} - -static void cli_execute_command(Cli* cli, CliCommand* command, FuriString* args) { - if(!(command->flags & CliCommandFlagInsomniaSafe)) { - furi_hal_power_insomnia_enter(); - } - - // Ensure that we running alone - if(!(command->flags & CliCommandFlagParallelSafe)) { - Loader* loader = furi_record_open(RECORD_LOADER); - bool safety_lock = loader_lock(loader); - if(safety_lock) { - // Execute command - command->callback(cli, args, command->context); - loader_unlock(loader); - } else { - printf("Other application is running, close it first"); - } - furi_record_close(RECORD_LOADER); - } else { - // Execute command - command->callback(cli, args, command->context); - } - - if(!(command->flags & CliCommandFlagInsomniaSafe)) { - furi_hal_power_insomnia_exit(); - } -} - -static void cli_handle_enter(Cli* cli) { - cli_normalize_line(cli); - - if(furi_string_size(cli->line) == 0) { - cli_prompt(cli); - return; - } - - // Command and args container - FuriString* command; - command = furi_string_alloc(); - FuriString* args; - args = furi_string_alloc(); - - // Split command and args - size_t ws = furi_string_search_char(cli->line, ' '); - if(ws == FURI_STRING_FAILURE) { - furi_string_set(command, cli->line); - } else { - furi_string_set_n(command, cli->line, 0, ws); - furi_string_set_n(args, cli->line, ws, furi_string_size(cli->line)); - furi_string_trim(args); - } - - // Search for command - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - CliCommand* cli_command_ptr = CliCommandTree_get(cli->commands, command); - - if(cli_command_ptr) { //-V547 - CliCommand cli_command; - memcpy(&cli_command, cli_command_ptr, sizeof(CliCommand)); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - cli_nl(cli); - cli_execute_command(cli, &cli_command, args); - } else { - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - cli_nl(cli); - printf( - "`%s` command not found, use `help` or `?` to list all available commands", - furi_string_get_cstr(command)); - cli_putc(cli, CliSymbolAsciiBell); - } - - cli_reset(cli); - cli_prompt(cli); - - // Cleanup command and args - furi_string_free(command); - furi_string_free(args); -} - -static void cli_handle_autocomplete(Cli* cli) { - cli_normalize_line(cli); - - if(furi_string_size(cli->line) == 0) { - return; - } - - cli_nl(cli); - - // Prepare common base for autocomplete - FuriString* common; - common = furi_string_alloc(); - // Iterate throw commands - for - M_EACH(cli_command, cli->commands, CliCommandTree_t) { - // Process only if starts with line buffer - if(furi_string_start_with(*cli_command->key_ptr, cli->line)) { - // Show autocomplete option - printf("%s\r\n", furi_string_get_cstr(*cli_command->key_ptr)); - // Process common base for autocomplete - if(furi_string_size(common) > 0) { - // Choose shortest string - const size_t key_size = furi_string_size(*cli_command->key_ptr); - const size_t common_size = furi_string_size(common); - const size_t min_size = key_size > common_size ? common_size : key_size; - size_t i = 0; - while(i < min_size) { - // Stop when do not match - if(furi_string_get_char(*cli_command->key_ptr, i) != - furi_string_get_char(common, i)) { - break; - } - i++; - } - // Cut right part if any - furi_string_left(common, i); - } else { - // Start with something - furi_string_set(common, *cli_command->key_ptr); - } - } - } - // Replace line buffer if autocomplete better - if(furi_string_size(common) > furi_string_size(cli->line)) { - furi_string_set(cli->line, common); - cli->cursor_position = furi_string_size(cli->line); - } - // Cleanup - furi_string_free(common); - // Show prompt - cli_prompt(cli); -} - -static void cli_handle_escape(Cli* cli, char c) { - if(c == 'A') { - // Use previous command if line buffer is empty - if(furi_string_size(cli->line) == 0 && furi_string_cmp(cli->line, cli->last_line) != 0) { - // Set line buffer and cursor position - furi_string_set(cli->line, cli->last_line); - cli->cursor_position = furi_string_size(cli->line); - // Show new line to user - printf("%s", furi_string_get_cstr(cli->line)); - } - } else if(c == 'B') { - } else if(c == 'C') { - if(cli->cursor_position < furi_string_size(cli->line)) { - cli->cursor_position++; - printf("\e[C"); - } - } else if(c == 'D') { - if(cli->cursor_position > 0) { - cli->cursor_position--; - printf("\e[D"); - } - } - fflush(stdout); -} - -void cli_process_input(Cli* cli) { - char in_chr = cli_getc(cli); - size_t rx_len; - - if(in_chr == CliSymbolAsciiTab) { - cli_handle_autocomplete(cli); - } else if(in_chr == CliSymbolAsciiSOH) { - furi_delay_ms(33); // We are too fast, Minicom is not ready yet - cli_motd(); - cli_prompt(cli); - } else if(in_chr == CliSymbolAsciiETX) { - cli_reset(cli); - cli_prompt(cli); - } else if(in_chr == CliSymbolAsciiEOT) { - cli_reset(cli); - } else if(in_chr == CliSymbolAsciiEsc) { - rx_len = cli_read(cli, (uint8_t*)&in_chr, 1); - if((rx_len > 0) && (in_chr == '[')) { - cli_read(cli, (uint8_t*)&in_chr, 1); - cli_handle_escape(cli, in_chr); - } else { - cli_putc(cli, CliSymbolAsciiBell); - } - } else if(in_chr == CliSymbolAsciiBackspace || in_chr == CliSymbolAsciiDel) { - cli_handle_backspace(cli); - } else if(in_chr == CliSymbolAsciiCR) { - cli_handle_enter(cli); - } else if( - (in_chr >= 0x20 && in_chr < 0x7F) && //-V560 - (furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) { - if(cli->cursor_position == furi_string_size(cli->line)) { - furi_string_push_back(cli->line, in_chr); - cli_putc(cli, in_chr); - } else { - // Insert character to line buffer - const char in_str[2] = {in_chr, 0}; - furi_string_replace_at(cli->line, cli->cursor_position, 0, in_str); - - // Print character in replace mode - printf("\e[4h%c\e[4l", in_chr); - fflush(stdout); - } - cli->cursor_position++; - } else { - cli_putc(cli, CliSymbolAsciiBell); - } -} - -void cli_add_command( - Cli* cli, - const char* name, - CliCommandFlag flags, - CliCallback callback, - void* context) { - furi_check(cli); - FuriString* name_str; - name_str = furi_string_alloc_set(name); - furi_string_trim(name_str); - - size_t name_replace; - do { - name_replace = furi_string_replace(name_str, " ", "_"); - } while(name_replace != FURI_STRING_FAILURE); - - CliCommand c; - c.callback = callback; - c.context = context; - c.flags = flags; - - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - CliCommandTree_set_at(cli->commands, name_str, c); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - - furi_string_free(name_str); -} - -void cli_delete_command(Cli* cli, const char* name) { - furi_check(cli); - FuriString* name_str; - name_str = furi_string_alloc_set(name); - furi_string_trim(name_str); - - size_t name_replace; - do { - name_replace = furi_string_replace(name_str, " ", "_"); - } while(name_replace != FURI_STRING_FAILURE); - - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - CliCommandTree_erase(cli->commands, name_str); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); - - furi_string_free(name_str); -} - -void cli_session_open(Cli* cli, void* session) { - furi_check(cli); - - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - cli->session = session; - if(cli->session != NULL) { - cli->session->init(); - furi_thread_set_stdout_callback(cli->session->tx_stdout); - } else { - furi_thread_set_stdout_callback(NULL); - } - furi_semaphore_release(cli->idle_sem); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); -} - -void cli_session_close(Cli* cli) { - furi_check(cli); - - furi_check(furi_mutex_acquire(cli->mutex, FuriWaitForever) == FuriStatusOk); - if(cli->session != NULL) { - cli->session->deinit(); - } - cli->session = NULL; - furi_thread_set_stdout_callback(NULL); - furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); -} - -int32_t cli_srv(void* p) { - UNUSED(p); - Cli* cli = cli_alloc(); - - // Init basic cli commands - cli_commands_init(cli); - - furi_record_create(RECORD_CLI, cli); - - if(cli->session != NULL) { - furi_thread_set_stdout_callback(cli->session->tx_stdout); - } else { - furi_thread_set_stdout_callback(NULL); - } - - if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { - cli_session_open(cli, &cli_vcp); - } else { - FURI_LOG_W(TAG, "Skipping start in special boot mode"); - } - - while(1) { - if(cli->session != NULL) { - cli_process_input(cli); - } else { - furi_check(furi_semaphore_acquire(cli->idle_sem, FuriWaitForever) == FuriStatusOk); - } - } - - return 0; -} diff --git a/applications/services/cli/cli.h b/applications/services/cli/cli.h index bb84670a7..ca787d3db 100644 --- a/applications/services/cli/cli.h +++ b/applications/services/cli/cli.h @@ -1,134 +1,13 @@ -/** - * @file cli.h - * Cli API - */ - #pragma once -#include -#ifdef __cplusplus -extern "C" { -#endif +/* + * Compatibility header for ease of porting existing apps. + * In short: + * Cli* is replaced with with CliRegistry* + * cli_* functions are replaced with cli_registry_* functions + * (i.e., cli_add_command() is now cli_registry_add_command()) +*/ -typedef enum { - CliSymbolAsciiSOH = 0x01, - CliSymbolAsciiETX = 0x03, - CliSymbolAsciiEOT = 0x04, - CliSymbolAsciiBell = 0x07, - CliSymbolAsciiBackspace = 0x08, - CliSymbolAsciiTab = 0x09, - CliSymbolAsciiLF = 0x0A, - CliSymbolAsciiCR = 0x0D, - CliSymbolAsciiEsc = 0x1B, - CliSymbolAsciiUS = 0x1F, - CliSymbolAsciiSpace = 0x20, - CliSymbolAsciiDel = 0x7F, -} CliSymbols; - -typedef enum { - CliCommandFlagDefault = 0, /**< Default, loader lock is used */ - CliCommandFlagParallelSafe = - (1 << 0), /**< Safe to run in parallel with other apps, loader lock is not used */ - CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */ -} CliCommandFlag; +#include #define RECORD_CLI "cli" - -/** Cli type anonymous structure */ -typedef struct Cli Cli; - -/** Cli callback function pointer. Implement this interface and use - * add_cli_command - * @param args string with what was passed after command - * @param context pointer to whatever you gave us on cli_add_command - */ -typedef void (*CliCallback)(Cli* cli, FuriString* args, void* context); - -/** Add cli command Registers you command callback - * - * @param cli pointer to cli instance - * @param name command name - * @param flags CliCommandFlag - * @param callback callback function - * @param context pointer to whatever we need to pass to callback - */ -void cli_add_command( - Cli* cli, - const char* name, - CliCommandFlag flags, - CliCallback callback, - void* context); - -/** Print unified cmd usage tip - * - * @param cmd cmd name - * @param usage usage tip - * @param arg arg passed by user - */ -void cli_print_usage(const char* cmd, const char* usage, const char* arg); - -/** Delete cli command - * - * @param cli pointer to cli instance - * @param name command name - */ -void cli_delete_command(Cli* cli, const char* name); - -/** Read from terminal - * - * @param cli Cli instance - * @param buffer pointer to buffer - * @param size size of buffer in bytes - * - * @return bytes read - */ -size_t cli_read(Cli* cli, uint8_t* buffer, size_t size); - -/** Non-blocking read from terminal - * - * @param cli Cli instance - * @param buffer pointer to buffer - * @param size size of buffer in bytes - * @param timeout timeout value in ms - * - * @return bytes read - */ -size_t cli_read_timeout(Cli* cli, uint8_t* buffer, size_t size, uint32_t timeout); - -/** Non-blocking check for interrupt command received - * - * @param cli Cli instance - * - * @return true if received - */ -bool cli_cmd_interrupt_received(Cli* cli); - -/** Write to terminal Do it only from inside of cli call. - * - * @param cli Cli instance - * @param buffer pointer to buffer - * @param size size of buffer in bytes - */ -void cli_write(Cli* cli, const uint8_t* buffer, size_t size); - -/** Read character - * - * @param cli Cli instance - * - * @return char - */ -char cli_getc(Cli* cli); - -/** New line Send new ine sequence - */ -void cli_nl(Cli* cli); - -void cli_session_open(Cli* cli, void* session); - -void cli_session_close(Cli* cli); - -bool cli_is_connected(Cli* cli); - -#ifdef __cplusplus -} -#endif diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index 010a7dfbe..f6337265d 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include void cli_command_gpio_print_usage(void) { printf("Usage:\r\n"); @@ -70,8 +72,8 @@ static GpioParseReturn gpio_command_parse(FuriString* args, size_t* pin_num, uin return ret; } -void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_gpio_mode(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -93,7 +95,7 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { if(gpio_pins[num].debug) { //-V779 printf( "Changing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); - char c = cli_getc(cli); + char c = getchar(); if(c != 'y' && c != 'Y') { printf("Cancelled.\r\n"); return; @@ -110,8 +112,8 @@ void cli_command_gpio_mode(Cli* cli, FuriString* args, void* context) { } } -void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_gpio_read(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -131,7 +133,8 @@ void cli_command_gpio_read(Cli* cli, FuriString* args, void* context) { printf("Pin %s <= %u", gpio_pins[num].name, val); } -void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { +void cli_command_gpio_set(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); size_t num = 0; @@ -159,7 +162,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { if(gpio_pins[num].debug) { printf( "Setting this pin may damage hardware. Are you sure you want to continue? (y/n)?\r\n"); - char c = cli_getc(cli); + char c = getchar(); if(c != 'y' && c != 'Y') { printf("Cancelled.\r\n"); return; @@ -170,7 +173,7 @@ void cli_command_gpio_set(Cli* cli, FuriString* args, void* context) { printf("Pin %s => %u", gpio_pins[num].name, !!value); } -void cli_command_gpio(Cli* cli, FuriString* args, void* context) { +void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -181,17 +184,17 @@ void cli_command_gpio(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "mode") == 0) { - cli_command_gpio_mode(cli, args, context); + cli_command_gpio_mode(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "set") == 0) { - cli_command_gpio_set(cli, args, context); + cli_command_gpio_set(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "read") == 0) { - cli_command_gpio_read(cli, args, context); + cli_command_gpio_read(pipe, args, context); break; } diff --git a/applications/services/cli/cli_command_gpio.h b/applications/services/cli/cli_command_gpio.h index 7ae5aa625..c1911fb65 100644 --- a/applications/services/cli/cli_command_gpio.h +++ b/applications/services/cli/cli_command_gpio.h @@ -1,5 +1,5 @@ #pragma once -#include "cli_i.h" +#include -void cli_command_gpio(Cli* cli, FuriString* args, void* context); +void cli_command_gpio(PipeSide* pipe, FuriString* args, void* context); diff --git a/applications/services/cli/cli_commands.h b/applications/services/cli/cli_commands.h deleted file mode 100644 index 184eeb373..000000000 --- a/applications/services/cli/cli_commands.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "cli_i.h" - -void cli_commands_init(Cli* cli); diff --git a/applications/services/cli/cli_i.h b/applications/services/cli/cli_i.h deleted file mode 100644 index d4cac6e7d..000000000 --- a/applications/services/cli/cli_i.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include "cli.h" - -#include -#include - -#include -#include -#include - -#include "cli_vcp.h" - -#define CLI_LINE_SIZE_MAX -#define CLI_COMMANDS_TREE_RANK 4 - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - CliCallback callback; - void* context; - uint32_t flags; -} CliCommand; - -struct CliSession { - void (*init)(void); - void (*deinit)(void); - size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout); - void (*tx)(const uint8_t* buffer, size_t size); - void (*tx_stdout)(const char* data, size_t size); - bool (*is_connected)(void); -}; - -BPTREE_DEF2( - CliCommandTree, - CLI_COMMANDS_TREE_RANK, - FuriString*, - FURI_STRING_OPLIST, - CliCommand, - M_POD_OPLIST) - -#define M_OPL_CliCommandTree_t() BPTREE_OPLIST(CliCommandTree, M_POD_OPLIST) - -struct Cli { - CliCommandTree_t commands; - FuriMutex* mutex; - FuriSemaphore* idle_sem; - FuriString* last_line; - FuriString* line; - CliSession* session; - - size_t cursor_position; -}; - -Cli* cli_alloc(void); - -void cli_reset(Cli* cli); - -void cli_putc(Cli* cli, char c); - -void cli_stdout_callback(void* _cookie, const char* data, size_t size); - -#ifdef __cplusplus -} -#endif diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_main_commands.c similarity index 74% rename from applications/services/cli/cli_commands.c rename to applications/services/cli/cli_main_commands.c index e4503b274..508a650de 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_main_commands.c @@ -1,5 +1,6 @@ -#include "cli_commands.h" +#include "cli_main_commands.h" #include "cli_command_gpio.h" +#include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" @@ -34,8 +36,8 @@ void cli_command_info_callback(const char* key, const char* value, bool last, vo * @param args The arguments * @param context The context */ -void cli_command_info(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_info(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); if(context) { furi_hal_info_get(cli_command_info_callback, '_', NULL); @@ -53,56 +55,16 @@ void cli_command_info(Cli* cli, FuriString* args, void* context) { } } -void cli_command_help(Cli* cli, FuriString* args, void* context) { - UNUSED(args); - UNUSED(context); - printf("Commands available:"); - - // Command count - const size_t commands_count = CliCommandTree_size(cli->commands); - const size_t commands_count_mid = commands_count / 2 + commands_count % 2; - - // Use 2 iterators from start and middle to show 2 columns - CliCommandTree_it_t it_left; - CliCommandTree_it(it_left, cli->commands); - CliCommandTree_it_t it_right; - CliCommandTree_it(it_right, cli->commands); - for(size_t i = 0; i < commands_count_mid; i++) - CliCommandTree_next(it_right); - - // Iterate throw tree - for(size_t i = 0; i < commands_count_mid; i++) { - printf("\r\n"); - // Left Column - if(!CliCommandTree_end_p(it_left)) { - printf("%-30s", furi_string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr)); - CliCommandTree_next(it_left); - } - // Right Column - if(!CliCommandTree_end_p(it_right)) { - printf("%s", furi_string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr)); - CliCommandTree_next(it_right); - } - }; - - if(furi_string_size(args) > 0) { - cli_nl(cli); - printf("`"); - printf("%s", furi_string_get_cstr(args)); - printf("` command not found"); - } -} - -void cli_command_uptime(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_uptime(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60); } -void cli_command_date(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_date(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); DateTime datetime = {0}; @@ -174,7 +136,8 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) { #define CLI_COMMAND_LOG_BUFFER_SIZE 64 void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* context) { - furi_stream_buffer_send(context, buffer, size, 0); + PipeSide* pipe = context; + pipe_send(pipe, buffer, size); } bool cli_command_log_level_set_from_string(FuriString* level) { @@ -196,16 +159,13 @@ bool cli_command_log_level_set_from_string(FuriString* level) { return false; } -void cli_command_log(Cli* cli, FuriString* args, void* context) { +void cli_command_log(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); - FuriStreamBuffer* ring = furi_stream_buffer_alloc(CLI_COMMAND_LOG_RING_SIZE, 1); - uint8_t buffer[CLI_COMMAND_LOG_BUFFER_SIZE]; FuriLogLevel previous_level = furi_log_get_level(); bool restore_log_level = false; if(furi_string_size(args) > 0) { if(!cli_command_log_level_set_from_string(args)) { - furi_stream_buffer_free(ring); return; } restore_log_level = true; @@ -217,16 +177,15 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { FuriLogHandler log_handler = { .callback = cli_command_log_tx_callback, - .context = ring, + .context = pipe, }; furi_log_add_handler(log_handler); printf("Use to list available log levels\r\n"); printf("Press CTRL+C to stop...\r\n"); - while(!cli_cmd_interrupt_received(cli)) { - size_t ret = furi_stream_buffer_receive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50); - cli_write(cli, buffer, ret); + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { + furi_delay_ms(100); } furi_log_remove_handler(log_handler); @@ -235,12 +194,10 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) { // There will be strange behaviour if log level is set from settings while log command is running furi_log_set_level(previous_level); } - - furi_stream_buffer_free(ring); } -void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_sysctl_debug(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "0")) { furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug); @@ -253,8 +210,8 @@ void cli_command_sysctl_debug(Cli* cli, FuriString* args, void* context) { } } -void cli_command_sysctl_heap_track(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_sysctl_heap_track(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "none")) { furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackModeNone); @@ -288,7 +245,7 @@ void cli_command_sysctl_print_usage(void) { #endif } -void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { +void cli_command_sysctl(PipeSide* pipe, FuriString* args, void* context) { FuriString* cmd; cmd = furi_string_alloc(); @@ -299,12 +256,12 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "debug") == 0) { - cli_command_sysctl_debug(cli, args, context); + cli_command_sysctl_debug(pipe, args, context); break; } if(furi_string_cmp_str(cmd, "heap_track") == 0) { - cli_command_sysctl_heap_track(cli, args, context); + cli_command_sysctl_heap_track(pipe, args, context); break; } @@ -314,8 +271,8 @@ void cli_command_sysctl(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -void cli_command_vibro(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_vibro(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); if(!furi_string_cmp(args, "0")) { @@ -341,8 +298,8 @@ void cli_command_vibro(Cli* cli, FuriString* args, void* context) { } } -void cli_command_led(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_led(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); // Get first word as light name NotificationMessage notification_led_message; @@ -396,23 +353,23 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) { furi_record_close(RECORD_NOTIFICATION); } -static void cli_command_top(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); int interval = 1000; args_read_int_and_trim(args, &interval); FuriThreadList* thread_list = furi_thread_list_alloc(); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { uint32_t tick = furi_get_tick(); furi_thread_enumerate(thread_list); - if(interval) printf("\e[2J\e[0;0f"); // Clear display and return to 0 + if(interval) printf(ANSI_CURSOR_POS("1", "1")); uint32_t uptime = tick / furi_kernel_get_tick_frequency(); printf( - "Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus\r\n", + "Threads: %zu, ISR Time: %0.2f%%, Uptime: %luh%lum%lus" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", furi_thread_list_size(thread_list), (double)furi_thread_list_get_isr_time(thread_list), uptime / 60 / 60, @@ -420,14 +377,16 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { uptime % 60); printf( - "Heap: total %zu, free %zu, minimum %zu, max block %zu\r\n\r\n", + "Heap: total %zu, free %zu, minimum %zu, max block %zu" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", memmgr_get_total_heap(), memmgr_get_free_heap(), memmgr_get_minimum_free_heap(), memmgr_heap_get_max_free_block()); printf( - "%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s\r\n", + "%-17s %-20s %-10s %5s %12s %6s %10s %7s %5s" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", "AppID", "Name", "State", @@ -436,12 +395,13 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { "Stack", "Stack Min", "Heap", - "CPU"); + "%CPU"); for(size_t i = 0; i < furi_thread_list_size(thread_list); i++) { const FuriThreadListItem* item = furi_thread_list_get_at(thread_list, i); printf( - "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f\r\n", + "%-17s %-20s %-10s %5d 0x%08lx %6lu %10lu %7zu %5.1f" ANSI_ERASE_LINE( + ANSI_ERASE_FROM_CURSOR_TO_END) "\r\n", item->app_id, item->name, item->state, @@ -453,6 +413,9 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { (double)item->cpu); } + printf(ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END)); + fflush(stdout); + if(interval > 0) { furi_delay_ms(interval); } else { @@ -462,8 +425,8 @@ static void cli_command_top(Cli* cli, FuriString* args, void* context) { furi_thread_list_free(thread_list); } -void cli_command_free(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_free(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -476,16 +439,16 @@ void cli_command_free(Cli* cli, FuriString* args, void* context) { printf("Maximum pool block: %zu\r\n", memmgr_pool_get_max_block()); } -void cli_command_free_blocks(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_free_blocks(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); memmgr_heap_printf_free_blocks(); } -void cli_command_i2c(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +void cli_command_i2c(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); @@ -507,24 +470,53 @@ void cli_command_i2c(Cli* cli, FuriString* args, void* context) { furi_hal_i2c_release(&furi_hal_i2c_handle_external); } -void cli_commands_init(Cli* cli) { - cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); - cli_add_command(cli, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); - cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); +/** + * Echoes any bytes it receives except ASCII ETX (0x03, Ctrl+C) + */ +void cli_command_echo(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(args); + UNUSED(context); - cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL); - cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL); + uint8_t buffer[256]; - cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL); - cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); - cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); - cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL); - cli_add_command(cli, "top", CliCommandFlagParallelSafe, cli_command_top, NULL); - cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL); - cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); + while(true) { + size_t to_read = CLAMP(pipe_bytes_available(pipe), sizeof(buffer), 1UL); + size_t read = pipe_receive(pipe, buffer, to_read); + if(read < to_read) break; - cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); - cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL); - cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL); - cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL); + if(memchr(buffer, CliKeyETX, read)) break; + + size_t written = pipe_send(pipe, buffer, read); + if(written < read) break; + } +} + +void cli_main_commands_init(CliRegistry* registry) { + cli_registry_add_command( + registry, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true); + cli_registry_add_command(registry, "info", CliCommandFlagParallelSafe, cli_command_info, NULL); + cli_registry_add_command( + registry, "device_info", CliCommandFlagParallelSafe, cli_command_info, (void*)true); + + cli_registry_add_command( + registry, "uptime", CliCommandFlagParallelSafe, cli_command_uptime, NULL); + cli_registry_add_command(registry, "date", CliCommandFlagParallelSafe, cli_command_date, NULL); + cli_registry_add_command(registry, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); + cli_registry_add_command(registry, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL); + cli_registry_add_command(registry, "top", CliCommandFlagParallelSafe, cli_command_top, NULL); + cli_registry_add_command(registry, "free", CliCommandFlagParallelSafe, cli_command_free, NULL); + cli_registry_add_command( + registry, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); + cli_registry_add_command(registry, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL); + + cli_registry_add_command(registry, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); + cli_registry_add_command(registry, "led", CliCommandFlagDefault, cli_command_led, NULL); + cli_registry_add_command(registry, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL); + cli_registry_add_command(registry, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL); +} + +void cli_on_system_start(void) { + CliRegistry* registry = cli_registry_alloc(); + cli_main_commands_init(registry); + furi_record_create(RECORD_CLI, registry); } diff --git a/applications/services/cli/cli_main_commands.h b/applications/services/cli/cli_main_commands.h new file mode 100644 index 000000000..ebee4ba1e --- /dev/null +++ b/applications/services/cli/cli_main_commands.h @@ -0,0 +1,9 @@ +#pragma once + +#include "cli.h" +#include +#include + +#define CLI_APPID "cli" + +void cli_main_commands_init(CliRegistry* registry); diff --git a/applications/services/cli/cli_main_shell.c b/applications/services/cli/cli_main_shell.c new file mode 100644 index 000000000..7550bef04 --- /dev/null +++ b/applications/services/cli/cli_main_shell.c @@ -0,0 +1,46 @@ +#include "cli_main_shell.h" +#include "cli_main_commands.h" +#include +#include +#include + +void cli_main_motd(void* context) { + UNUSED(context); + printf(ANSI_FLIPPER_BRAND_ORANGE + "\r\n" + " _.-------.._ -,\r\n" + " .-\"```\"--..,,_/ /`-, -, \\ \r\n" + " .:\" /:/ /'\\ \\ ,_..., `. | |\r\n" + " / ,----/:/ /`\\ _\\~`_-\"` _;\r\n" + " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\n" + " | | | 0 | | .-' ,/` /\r\n" + " | ,..\\ \\ ,.-\"` ,/` /\r\n" + " ; : `/`\"\"\\` ,/--==,/-----,\r\n" + " | `-...| -.___-Z:_______J...---;\r\n" + " : ` _-'\r\n" + " _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\n" + "| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\n" + "| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\n" + "|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\n" + "\r\n" ANSI_FG_BR_WHITE "Welcome to Flipper Zero Command Line Interface!\r\n" + "Read the manual: https://docs.flipper.net/development/cli\r\n" + "Run `help` or `?` to list available commands\r\n" + "\r\n" ANSI_RESET); + + const Version* firmware_version = furi_hal_version_get_firmware_version(); + if(firmware_version) { + printf( + "Firmware version: %s %s (%s%s built on %s)\r\n", + version_get_gitbranch(firmware_version), + version_get_version(firmware_version), + version_get_githash(firmware_version), + version_get_dirty_flag(firmware_version) ? "-dirty" : "", + version_get_builddate(firmware_version)); + } +} + +const CliCommandExternalConfig cli_main_ext_config = { + .search_directory = "/ext/apps_data/cli/plugins", + .fal_prefix = "cli_", + .appid = CLI_APPID, +}; diff --git a/applications/services/cli/cli_main_shell.h b/applications/services/cli/cli_main_shell.h new file mode 100644 index 000000000..576839990 --- /dev/null +++ b/applications/services/cli/cli_main_shell.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +void cli_main_motd(void* context); + +extern const CliCommandExternalConfig cli_main_ext_config; diff --git a/applications/services/cli/cli_vcp.c b/applications/services/cli/cli_vcp.c index cdabaaa05..1f9c77b08 100644 --- a/applications/services/cli/cli_vcp.c +++ b/applications/services/cli/cli_vcp.c @@ -1,316 +1,327 @@ -#include "cli_i.h" // IWYU pragma: keep +#include "cli_vcp.h" #include #include #include +#include +#include +#include +#include +#include "cli_main_shell.h" +#include "cli_main_commands.h" #define TAG "CliVcp" -#define USB_CDC_PKT_LEN CDC_DATA_SZ -#define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3) -#define VCP_TX_BUF_SIZE (USB_CDC_PKT_LEN * 3) +#define USB_CDC_PKT_LEN CDC_DATA_SZ +#define VCP_BUF_SIZE (USB_CDC_PKT_LEN * 3) +#define VCP_IF_NUM 0 +#define VCP_MESSAGE_Q_LEN 8 -#define VCP_IF_NUM 0 - -#ifdef CLI_VCP_DEBUG -#define VCP_DEBUG(...) FURI_LOG_D(TAG, __VA_ARGS__) +#ifdef CLI_VCP_TRACE +#define VCP_TRACE(...) FURI_LOG_T(__VA_ARGS__) #else -#define VCP_DEBUG(...) +#define VCP_TRACE(...) #endif -typedef enum { - VcpEvtStop = (1 << 0), - VcpEvtConnect = (1 << 1), - VcpEvtDisconnect = (1 << 2), - VcpEvtStreamRx = (1 << 3), - VcpEvtRx = (1 << 4), - VcpEvtStreamTx = (1 << 5), - VcpEvtTx = (1 << 6), -} WorkerEvtFlags; - -#define VCP_THREAD_FLAG_ALL \ - (VcpEvtStop | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | \ - VcpEvtStreamTx) - typedef struct { - FuriThread* thread; + enum { + CliVcpMessageTypeEnable, + CliVcpMessageTypeDisable, + } type; + FuriApiLock api_lock; + union {}; +} CliVcpMessage; - FuriStreamBuffer* tx_stream; - FuriStreamBuffer* rx_stream; +typedef enum { + CliVcpInternalEventConnected, + CliVcpInternalEventDisconnected, + CliVcpInternalEventTxDone, + CliVcpInternalEventRx, +} CliVcpInternalEvent; - volatile bool connected; - volatile bool running; +struct CliVcp { + FuriEventLoop* event_loop; + FuriMessageQueue* message_queue; // is_currently_transmitting) return; + if(!cli_vcp->own_pipe) return; -static void cli_vcp_init(void) { - if(vcp == NULL) { - vcp = malloc(sizeof(CliVcp)); - vcp->tx_stream = furi_stream_buffer_alloc(VCP_TX_BUF_SIZE, 1); - vcp->rx_stream = furi_stream_buffer_alloc(VCP_RX_BUF_SIZE, 1); + uint8_t buf[USB_CDC_PKT_LEN]; + size_t to_receive_from_pipe = MIN(sizeof(buf), pipe_bytes_available(cli_vcp->own_pipe)); + size_t length = pipe_receive(cli_vcp->own_pipe, buf, to_receive_from_pipe); + if(length > 0 || cli_vcp->previous_tx_length == USB_CDC_PKT_LEN) { + VCP_TRACE(TAG, "cdc_send length=%zu", length); + cli_vcp->is_currently_transmitting = true; + furi_hal_cdc_send(VCP_IF_NUM, buf, length); } - furi_assert(vcp->thread == NULL); - - vcp->connected = false; - - vcp->thread = furi_thread_alloc_ex("CliVcpWorker", 1024, vcp_worker, NULL); - furi_thread_start(vcp->thread); - - FURI_LOG_I(TAG, "Init OK"); + cli_vcp->previous_tx_length = length; } -static void cli_vcp_deinit(void) { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStop); - furi_thread_join(vcp->thread); - furi_thread_free(vcp->thread); - vcp->thread = NULL; +/** + * Called in the following cases: + * - new data arrived at the endpoint; + * - data was read out of the pipe. + */ +static void cli_vcp_maybe_receive_data(CliVcp* cli_vcp) { + if(!cli_vcp->own_pipe) return; + if(pipe_spaces_available(cli_vcp->own_pipe) < USB_CDC_PKT_LEN) return; + + uint8_t buf[USB_CDC_PKT_LEN]; + size_t length = furi_hal_cdc_receive(VCP_IF_NUM, buf, sizeof(buf)); + VCP_TRACE(TAG, "cdc_receive length=%zu", length); + furi_check(pipe_send(cli_vcp->own_pipe, buf, length) == length); } -static int32_t vcp_worker(void* context) { - UNUSED(context); - bool tx_idle = true; - size_t missed_rx = 0; - uint8_t last_tx_pkt_len = 0; +// ============= +// CDC callbacks +// ============= - // Switch USB to VCP mode (if it is not set yet) - vcp->usb_if_prev = furi_hal_usb_get_config(); - if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) { +static void cli_vcp_signal_internal_event(CliVcp* cli_vcp, CliVcpInternalEvent event) { + furi_check(furi_message_queue_put(cli_vcp->internal_evt_queue, &event, 0) == FuriStatusOk); +} + +static void cli_vcp_cdc_tx_done(void* context) { + CliVcp* cli_vcp = context; + cli_vcp->is_currently_transmitting = false; + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventTxDone); +} + +static void cli_vcp_cdc_rx(void* context) { + CliVcp* cli_vcp = context; + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventRx); +} + +static void cli_vcp_cdc_state_callback(void* context, CdcState state) { + CliVcp* cli_vcp = context; + if(state == CdcStateDisconnected) { + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected); + } + // `Connected` events are generated by DTR going active +} + +static void cli_vcp_cdc_ctrl_line_callback(void* context, CdcCtrlLine ctrl_lines) { + CliVcp* cli_vcp = context; + if(ctrl_lines & CdcCtrlLineDTR) { + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventConnected); + } else { + cli_vcp_signal_internal_event(cli_vcp, CliVcpInternalEventDisconnected); + } +} + +static CdcCallbacks cdc_callbacks = { + .tx_ep_callback = cli_vcp_cdc_tx_done, + .rx_ep_callback = cli_vcp_cdc_rx, + .state_callback = cli_vcp_cdc_state_callback, + .ctrl_line_callback = cli_vcp_cdc_ctrl_line_callback, + .config_callback = NULL, +}; + +// ====================== +// Pipe callback handlers +// ====================== + +static void cli_vcp_data_from_shell(PipeSide* pipe, void* context) { + UNUSED(pipe); + CliVcp* cli_vcp = context; + cli_vcp_maybe_send_data(cli_vcp); +} + +static void cli_vcp_shell_ready(PipeSide* pipe, void* context) { + UNUSED(pipe); + CliVcp* cli_vcp = context; + cli_vcp_maybe_receive_data(cli_vcp); +} + +/** + * Processes messages arriving from other threads + */ +static void cli_vcp_message_received(FuriEventLoopObject* object, void* context) { + CliVcp* cli_vcp = context; + CliVcpMessage message; + furi_check(furi_message_queue_get(object, &message, 0) == FuriStatusOk); + + switch(message.type) { + case CliVcpMessageTypeEnable: + if(cli_vcp->is_enabled) break; + FURI_LOG_D(TAG, "Enabling"); + cli_vcp->is_enabled = true; + + // switch usb mode + cli_vcp->previous_interface = furi_hal_usb_get_config(); furi_hal_usb_set_config(&usb_cdc_single, NULL); + furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_callbacks, cli_vcp); + break; + + case CliVcpMessageTypeDisable: + if(!cli_vcp->is_enabled) break; + FURI_LOG_D(TAG, "Disabling"); + cli_vcp->is_enabled = false; + + // restore usb mode + furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL); + furi_hal_usb_set_config(cli_vcp->previous_interface, NULL); + break; } - furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL); - FURI_LOG_D(TAG, "Start"); - vcp->running = true; - - while(1) { - uint32_t flags = - furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); - furi_assert(!(flags & FuriFlagError)); - - // VCP session opened - if(flags & VcpEvtConnect) { - VCP_DEBUG("Connect"); - - if(vcp->connected == false) { - vcp->connected = true; - furi_stream_buffer_send(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever); - } - } - - // VCP session closed - if(flags & VcpEvtDisconnect) { - VCP_DEBUG("Disconnect"); - - if(vcp->connected == true) { - vcp->connected = false; - furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); - } - } - - // Rx buffer was read, maybe there is enough space for new data? - if((flags & VcpEvtStreamRx) && (missed_rx > 0)) { - VCP_DEBUG("StreamRx"); - - if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { - flags |= VcpEvtRx; - missed_rx--; - } - } - - // New data received - if(flags & VcpEvtRx) { - if(furi_stream_buffer_spaces_available(vcp->rx_stream) >= USB_CDC_PKT_LEN) { - int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN); - VCP_DEBUG("Rx %ld", len); - - if(len > 0) { - furi_check( - furi_stream_buffer_send( - vcp->rx_stream, vcp->data_buffer, len, FuriWaitForever) == - (size_t)len); - } - } else { - VCP_DEBUG("Rx missed"); - missed_rx++; - } - } - - // New data in Tx buffer - if(flags & VcpEvtStreamTx) { - VCP_DEBUG("StreamTx"); - - if(tx_idle) { - flags |= VcpEvtTx; - } - } - - // CDC write transfer done - if(flags & VcpEvtTx) { - size_t len = - furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - - VCP_DEBUG("Tx %d", len); - - if(len > 0) { // Some data left in Tx buffer. Sending it now - tx_idle = false; - furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len); - last_tx_pkt_len = len; - } else { // There is nothing to send. - if(last_tx_pkt_len == 64) { - // Send extra zero-length packet if last packet len is 64 to indicate transfer end - furi_hal_cdc_send(VCP_IF_NUM, NULL, 0); - } else { - // Set flag to start next transfer instantly - tx_idle = true; - } - last_tx_pkt_len = 0; - } - } - - if(flags & VcpEvtStop) { - vcp->connected = false; - vcp->running = false; - furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL); - // Restore previous USB mode (if it was set during init) - if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) { - furi_hal_usb_unlock(); - furi_hal_usb_set_config(vcp->usb_if_prev, NULL); - } - furi_stream_buffer_receive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0); - furi_stream_buffer_send(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever); - break; - } - } - FURI_LOG_D(TAG, "End"); - return 0; + api_lock_unlock(message.api_lock); } -static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) { - furi_assert(vcp); - furi_assert(buffer); +/** + * Processes messages arriving from CDC event callbacks + */ +static void cli_vcp_internal_event_happened(FuriEventLoopObject* object, void* context) { + CliVcp* cli_vcp = context; + CliVcpInternalEvent event; + furi_check(furi_message_queue_get(object, &event, 0) == FuriStatusOk); - if(vcp->running == false) { + switch(event) { + case CliVcpInternalEventRx: { + VCP_TRACE(TAG, "Rx"); + cli_vcp_maybe_receive_data(cli_vcp); + break; + } + + case CliVcpInternalEventTxDone: { + VCP_TRACE(TAG, "TxDone"); + cli_vcp_maybe_send_data(cli_vcp); + break; + } + + case CliVcpInternalEventDisconnected: { + if(!cli_vcp->is_connected) return; + FURI_LOG_D(TAG, "Disconnected"); + cli_vcp->is_connected = false; + + // disconnect our side of the pipe + pipe_detach_from_event_loop(cli_vcp->own_pipe); + pipe_free(cli_vcp->own_pipe); + cli_vcp->own_pipe = NULL; + break; + } + + case CliVcpInternalEventConnected: { + if(cli_vcp->is_connected) return; + FURI_LOG_D(TAG, "Connected"); + cli_vcp->is_connected = true; + + // wait for previous shell to stop + furi_check(!cli_vcp->own_pipe); + if(cli_vcp->shell) { + cli_shell_join(cli_vcp->shell); + cli_shell_free(cli_vcp->shell); + pipe_free(cli_vcp->shell_pipe); + } + + // start shell thread + PipeSideBundle bundle = pipe_alloc(VCP_BUF_SIZE, 1); + cli_vcp->own_pipe = bundle.alices_side; + cli_vcp->shell_pipe = bundle.bobs_side; + pipe_attach_to_event_loop(cli_vcp->own_pipe, cli_vcp->event_loop); + pipe_set_callback_context(cli_vcp->own_pipe, cli_vcp); + pipe_set_data_arrived_callback( + cli_vcp->own_pipe, cli_vcp_data_from_shell, FuriEventLoopEventFlagEdge); + pipe_set_space_freed_callback( + cli_vcp->own_pipe, cli_vcp_shell_ready, FuriEventLoopEventFlagEdge); + furi_delay_ms(33); // we are too fast, minicom isn't ready yet + cli_vcp->shell = cli_shell_alloc( + cli_main_motd, NULL, cli_vcp->shell_pipe, cli_vcp->main_registry, &cli_main_ext_config); + cli_shell_start(cli_vcp->shell); + break; + } + } +} + +// ============ +// Thread stuff +// ============ + +static CliVcp* cli_vcp_alloc(void) { + CliVcp* cli_vcp = malloc(sizeof(CliVcp)); + + cli_vcp->event_loop = furi_event_loop_alloc(); + + cli_vcp->message_queue = furi_message_queue_alloc(VCP_MESSAGE_Q_LEN, sizeof(CliVcpMessage)); + furi_event_loop_subscribe_message_queue( + cli_vcp->event_loop, + cli_vcp->message_queue, + FuriEventLoopEventIn, + cli_vcp_message_received, + cli_vcp); + + cli_vcp->internal_evt_queue = + furi_message_queue_alloc(VCP_MESSAGE_Q_LEN, sizeof(CliVcpInternalEvent)); + furi_event_loop_subscribe_message_queue( + cli_vcp->event_loop, + cli_vcp->internal_evt_queue, + FuriEventLoopEventIn, + cli_vcp_internal_event_happened, + cli_vcp); + + cli_vcp->main_registry = furi_record_open(RECORD_CLI); + + return cli_vcp; +} + +int32_t cli_vcp_srv(void* p) { + UNUSED(p); + + if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { + FURI_LOG_W(TAG, "Skipping start in special boot mode"); + furi_thread_suspend(furi_thread_get_current_id()); return 0; } - VCP_DEBUG("rx %u start", size); + CliVcp* cli_vcp = cli_vcp_alloc(); + furi_record_create(RECORD_CLI_VCP, cli_vcp); + furi_event_loop_run(cli_vcp->event_loop); - size_t rx_cnt = 0; - - while(size > 0) { - size_t batch_size = size; - if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE; - - size_t len = furi_stream_buffer_receive(vcp->rx_stream, buffer, batch_size, timeout); - VCP_DEBUG("rx %u ", batch_size); - - if(len == 0) break; - if(vcp->running == false) { - // EOT command is received after VCP session close - rx_cnt += len; - break; - } - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx); - size -= len; - buffer += len; - rx_cnt += len; - } - - VCP_DEBUG("rx %u end", size); - return rx_cnt; + return 0; } -static void cli_vcp_tx(const uint8_t* buffer, size_t size) { - furi_assert(vcp); - furi_assert(buffer); +// ========== +// Public API +// ========== - if(vcp->running == false) { - return; - } - - VCP_DEBUG("tx %u start", size); - - while(size > 0 && vcp->connected) { - size_t batch_size = size; - if(batch_size > USB_CDC_PKT_LEN) batch_size = USB_CDC_PKT_LEN; - - furi_stream_buffer_send(vcp->tx_stream, buffer, batch_size, FuriWaitForever); - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx); - VCP_DEBUG("tx %u", batch_size); - - size -= batch_size; - buffer += batch_size; - } - - VCP_DEBUG("tx %u end", size); +static void cli_vcp_synchronous_request(CliVcp* cli_vcp, CliVcpMessage* message) { + message->api_lock = api_lock_alloc_locked(); + furi_message_queue_put(cli_vcp->message_queue, message, FuriWaitForever); + api_lock_wait_unlock_and_free(message->api_lock); } -static void cli_vcp_tx_stdout(const char* data, size_t size) { - cli_vcp_tx((const uint8_t*)data, size); +void cli_vcp_enable(CliVcp* cli_vcp) { + furi_check(cli_vcp); + CliVcpMessage message = { + .type = CliVcpMessageTypeEnable, + }; + cli_vcp_synchronous_request(cli_vcp, &message); } -static void vcp_state_callback(void* context, uint8_t state) { - UNUSED(context); - if(state == 0) { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect); - } +void cli_vcp_disable(CliVcp* cli_vcp) { + furi_check(cli_vcp); + CliVcpMessage message = { + .type = CliVcpMessageTypeDisable, + }; + cli_vcp_synchronous_request(cli_vcp, &message); } - -static void vcp_on_cdc_control_line(void* context, uint8_t state) { - UNUSED(context); - // bit 0: DTR state, bit 1: RTS state - bool dtr = state & (1 << 0); - - if(dtr == true) { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtConnect); - } else { - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect); - } -} - -static void vcp_on_cdc_rx(void* context) { - UNUSED(context); - uint32_t ret = furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtRx); - furi_check(!(ret & FuriFlagError)); -} - -static void vcp_on_cdc_tx_complete(void* context) { - UNUSED(context); - furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtTx); -} - -static bool cli_vcp_is_connected(void) { - furi_assert(vcp); - return vcp->connected; -} - -CliSession cli_vcp = { - cli_vcp_init, - cli_vcp_deinit, - cli_vcp_rx, - cli_vcp_tx, - cli_vcp_tx_stdout, - cli_vcp_is_connected, -}; diff --git a/applications/services/cli/cli_vcp.h b/applications/services/cli/cli_vcp.h index 3aef2ef70..10e286183 100644 --- a/applications/services/cli/cli_vcp.h +++ b/applications/services/cli/cli_vcp.h @@ -9,9 +9,12 @@ extern "C" { #endif -typedef struct CliSession CliSession; +#define RECORD_CLI_VCP "cli_vcp" -extern CliSession cli_vcp; +typedef struct CliVcp CliVcp; + +void cli_vcp_enable(CliVcp* cli_vcp); +void cli_vcp_disable(CliVcp* cli_vcp); #ifdef __cplusplus } diff --git a/applications/services/cli/commands/hello_world.c b/applications/services/cli/commands/hello_world.c new file mode 100644 index 000000000..b77f3e663 --- /dev/null +++ b/applications/services/cli/commands/hello_world.c @@ -0,0 +1,10 @@ +#include "../cli_main_commands.h" + +static void execute(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + UNUSED(context); + puts("Hello, World!"); +} + +CLI_COMMAND_INTERFACE(hello_world, execute, CliCommandFlagParallelSafe, 768, CLI_APPID); diff --git a/applications/services/cli/commands/neofetch.c b/applications/services/cli/commands/neofetch.c new file mode 100644 index 000000000..0e50a0d8d --- /dev/null +++ b/applications/services/cli/commands/neofetch.c @@ -0,0 +1,160 @@ +#include "../cli_main_commands.h" +#include +#include +#include +#include +#include +#include + +static void execute(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + UNUSED(context); + + static const char* const neofetch_logo[] = { + " _.-------.._ -,", + " .-\"```\"--..,,_/ /`-, -, \\ ", + " .:\" /:/ /'\\ \\ ,_..., `. | |", + " / ,----/:/ /`\\ _\\~`_-\"` _;", + " ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ ", + " | | | 0 | | .-' ,/` /", + " | ,..\\ \\ ,.-\"` ,/` /", + "; : `/`\"\"\\` ,/--==,/-----,", + "| `-...| -.___-Z:_______J...---;", + ": ` _-'", + }; +#define NEOFETCH_COLOR ANSI_FLIPPER_BRAND_ORANGE + + // Determine logo parameters + size_t logo_height = COUNT_OF(neofetch_logo), logo_width = 0; + for(size_t i = 0; i < logo_height; i++) + logo_width = MAX(logo_width, strlen(neofetch_logo[i])); + logo_width += 4; // space between logo and info + + // Format hostname delimiter + const size_t size_of_hostname = 4 + strlen(furi_hal_version_get_name_ptr()); + char delimiter[64]; + memset(delimiter, '-', size_of_hostname); + delimiter[size_of_hostname] = '\0'; + + // Get heap info + size_t heap_total = memmgr_get_total_heap(); + size_t heap_used = heap_total - memmgr_get_free_heap(); + uint16_t heap_percent = (100 * heap_used) / heap_total; + + // Get storage info + Storage* storage = furi_record_open(RECORD_STORAGE); + uint64_t ext_total, ext_free, ext_used, ext_percent; + storage_common_fs_info(storage, "/ext", &ext_total, &ext_free); + ext_used = ext_total - ext_free; + ext_percent = (100 * ext_used) / ext_total; + ext_used /= 1024 * 1024; + ext_total /= 1024 * 1024; + furi_record_close(RECORD_STORAGE); + + // Get battery info + uint16_t charge_percent = furi_hal_power_get_pct(); + const char* charge_state; + if(furi_hal_power_is_charging()) { + if((charge_percent < 100) && (!furi_hal_power_is_charging_done())) { + charge_state = "charging"; + } else { + charge_state = "charged"; + } + } else { + charge_state = "discharging"; + } + + // Get misc info + uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency(); + const Version* version = version_get(); + uint16_t major, minor; + furi_hal_info_get_api_version(&major, &minor); + + // Print ASCII art with info + const size_t info_height = 16; + for(size_t i = 0; i < MAX(logo_height, info_height); i++) { + printf(NEOFETCH_COLOR "%-*s", logo_width, (i < logo_height) ? neofetch_logo[i] : ""); + switch(i) { + case 0: // you@ + printf("you" ANSI_RESET "@" NEOFETCH_COLOR "%s", furi_hal_version_get_name_ptr()); + break; + case 1: // delimiter + printf(ANSI_RESET "%s", delimiter); + break; + case 2: // OS: FURI (SDK .) + printf( + "OS" ANSI_RESET ": FURI %s %s %s %s (SDK %hu.%hu)", + version_get_version(version), + version_get_gitbranch(version), + version_get_version(version), + version_get_githash(version), + major, + minor); + break; + case 3: // Host: + printf( + "Host" ANSI_RESET ": %s %s", + furi_hal_version_get_model_code(), + furi_hal_version_get_device_name_ptr()); + break; + case 4: // Kernel: FreeRTOS .. + printf( + "Kernel" ANSI_RESET ": FreeRTOS %d.%d.%d", + tskKERNEL_VERSION_MAJOR, + tskKERNEL_VERSION_MINOR, + tskKERNEL_VERSION_BUILD); + break; + case 5: // Uptime: ?h?m?s + printf( + "Uptime" ANSI_RESET ": %luh%lum%lus", + uptime / 60 / 60, + uptime / 60 % 60, + uptime % 60); + break; + case 6: // ST7567 128x64 @ 1 bpp in 1.4" + printf("Display" ANSI_RESET ": ST7567 128x64 @ 1 bpp in 1.4\""); + break; + case 7: // DE: GuiSrv + printf("DE" ANSI_RESET ": GuiSrv"); + break; + case 8: // Shell: CliSrv + printf("Shell" ANSI_RESET ": CliShell"); + break; + case 9: // CPU: STM32WB55RG @ 64 MHz + printf("CPU" ANSI_RESET ": STM32WB55RG @ 64 MHz"); + break; + case 10: // Memory: / B (??%) + printf( + "Memory" ANSI_RESET ": %zu / %zu B (%hu%%)", heap_used, heap_total, heap_percent); + break; + case 11: // Disk (/ext): / MiB (??%) + printf( + "Disk (/ext)" ANSI_RESET ": %llu / %llu MiB (%llu%%)", + ext_used, + ext_total, + ext_percent); + break; + case 12: // Battery: ??% () + printf("Battery" ANSI_RESET ": %hu%% (%s)" ANSI_RESET, charge_percent, charge_state); + break; + case 13: // empty space + break; + case 14: // Colors (line 1) + for(size_t j = 30; j <= 37; j++) + printf("\e[%dm███", j); + break; + case 15: // Colors (line 2) + for(size_t j = 90; j <= 97; j++) + printf("\e[%dm███", j); + break; + default: + break; + } + printf("\r\n"); + } + printf(ANSI_RESET); +#undef NEOFETCH_COLOR +} + +CLI_COMMAND_INTERFACE(neofetch, execute, CliCommandFlagParallelSafe, 2048, CLI_APPID); diff --git a/applications/services/cli/commands/subshell_demo.c b/applications/services/cli/commands/subshell_demo.c new file mode 100644 index 000000000..f0013c4a0 --- /dev/null +++ b/applications/services/cli/commands/subshell_demo.c @@ -0,0 +1,43 @@ +#include "../cli_main_commands.h" +#include +#include +#include + +#define RAINBOW_SUBCOMMAND \ + ANSI_FG_RED "s" ANSI_FG_YELLOW "u" ANSI_FG_BLUE "b" ANSI_FG_GREEN "c" ANSI_FG_MAGENTA \ + "o" ANSI_FG_RED "m" ANSI_FG_YELLOW "m" ANSI_FG_BLUE "a" ANSI_FG_GREEN \ + "n" ANSI_FG_MAGENTA "d" + +static void subcommand(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + UNUSED(context); + printf("This is a ✨" RAINBOW_SUBCOMMAND ANSI_RESET "✨!"); +} + +static void motd(void* context) { + UNUSED(context); + printf("\r\n"); + printf("+------------------------------------+\r\n"); + printf("| Hello world! |\r\n"); + printf("| This is the " ANSI_FG_GREEN "MOTD" ANSI_RESET " for our " ANSI_FG_BLUE + "subshell" ANSI_RESET "! |\r\n"); + printf("+------------------------------------+\r\n"); +} + +static void execute(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(args); + UNUSED(context); + CliRegistry* registry = cli_registry_alloc(); + cli_registry_add_command(registry, "subcommand", CliCommandFlagParallelSafe, subcommand, NULL); + + CliShell* shell = cli_shell_alloc(motd, NULL, pipe, registry, NULL); + cli_shell_set_prompt(shell, "subshell"); + cli_shell_start(shell); + cli_shell_join(shell); + + cli_shell_free(shell); + cli_registry_free(registry); +} + +CLI_COMMAND_INTERFACE(subshell_demo, execute, CliCommandFlagParallelSafe, 2048, CLI_APPID); diff --git a/applications/services/crypto/application.fam b/applications/services/crypto/application.fam index 7771c5ed2..1d07ca409 100644 --- a/applications/services/crypto/application.fam +++ b/applications/services/crypto/application.fam @@ -2,5 +2,5 @@ App( appid="crypto_start", apptype=FlipperAppType.STARTUP, entry_point="crypto_on_system_start", - order=10, + order=20, ) diff --git a/applications/services/crypto/crypto_cli.c b/applications/services/crypto/crypto_cli.c index 744fa7151..32b4199f5 100644 --- a/applications/services/crypto/crypto_cli.c +++ b/applications/services/crypto/crypto_cli.c @@ -2,7 +2,10 @@ #include #include -#include +#include +#include +#include +#include void crypto_cli_print_usage(void) { printf("Usage:\r\n"); @@ -17,7 +20,7 @@ void crypto_cli_print_usage(void) { "\tstore_key \t - Store key in secure enclave. !!! NON-REVERSABLE OPERATION - READ MANUAL FIRST !!!\r\n"); } -void crypto_cli_encrypt(Cli* cli, FuriString* args) { +void crypto_cli_encrypt(PipeSide* pipe, FuriString* args) { int key_slot = 0; bool key_loaded = false; uint8_t iv[16]; @@ -44,15 +47,15 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) { FuriString* input; input = furi_string_alloc(); char c; - while(cli_read(cli, (uint8_t*)&c, 1) == 1) { - if(c == CliSymbolAsciiETX) { + while(pipe_receive(pipe, (uint8_t*)&c, 1) == 1) { + if(c == CliKeyETX) { printf("\r\n"); break; } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); furi_string_push_back(input, c); - } else if(c == CliSymbolAsciiCR) { + } else if(c == CliKeyCR) { printf("\r\n"); furi_string_cat(input, "\r\n"); } @@ -92,7 +95,7 @@ void crypto_cli_encrypt(Cli* cli, FuriString* args) { } } -void crypto_cli_decrypt(Cli* cli, FuriString* args) { +void crypto_cli_decrypt(PipeSide* pipe, FuriString* args) { int key_slot = 0; bool key_loaded = false; uint8_t iv[16]; @@ -119,15 +122,15 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { FuriString* hex_input; hex_input = furi_string_alloc(); char c; - while(cli_read(cli, (uint8_t*)&c, 1) == 1) { - if(c == CliSymbolAsciiETX) { + while(pipe_receive(pipe, (uint8_t*)&c, 1) == 1) { + if(c == CliKeyETX) { printf("\r\n"); break; } else if(c >= 0x20 && c < 0x7F) { putc(c, stdout); fflush(stdout); furi_string_push_back(hex_input, c); - } else if(c == CliSymbolAsciiCR) { + } else if(c == CliKeyCR) { printf("\r\n"); } } @@ -164,8 +167,8 @@ void crypto_cli_decrypt(Cli* cli, FuriString* args) { } } -void crypto_cli_has_key(Cli* cli, FuriString* args) { - UNUSED(cli); +void crypto_cli_has_key(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); int key_slot = 0; uint8_t iv[16] = {0}; @@ -186,8 +189,8 @@ void crypto_cli_has_key(Cli* cli, FuriString* args) { } while(0); } -void crypto_cli_store_key(Cli* cli, FuriString* args) { - UNUSED(cli); +void crypto_cli_store_key(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); int key_slot = 0; int key_size = 0; FuriString* key_type; @@ -279,7 +282,7 @@ void crypto_cli_store_key(Cli* cli, FuriString* args) { furi_string_free(key_type); } -static void crypto_cli(Cli* cli, FuriString* args, void* context) { +static void crypto_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -291,22 +294,22 @@ static void crypto_cli(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "encrypt") == 0) { - crypto_cli_encrypt(cli, args); + crypto_cli_encrypt(pipe, args); break; } if(furi_string_cmp_str(cmd, "decrypt") == 0) { - crypto_cli_decrypt(cli, args); + crypto_cli_decrypt(pipe, args); break; } if(furi_string_cmp_str(cmd, "has_key") == 0) { - crypto_cli_has_key(cli, args); + crypto_cli_has_key(pipe, args); break; } if(furi_string_cmp_str(cmd, "store_key") == 0) { - crypto_cli_store_key(cli, args); + crypto_cli_store_key(pipe, args); break; } @@ -318,8 +321,8 @@ static void crypto_cli(Cli* cli, FuriString* args, void* context) { void crypto_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command(registry, "crypto", CliCommandFlagDefault, crypto_cli, NULL); furi_record_close(RECORD_CLI); #else UNUSED(crypto_cli); diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 1132760d5..60f1c21b9 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -1,6 +1,5 @@ #include "desktop_i.h" -#include #include #include @@ -28,9 +27,7 @@ static void desktop_loader_callback(const void* message, void* context) { if(event->type == LoaderEventTypeApplicationBeforeLoad) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); - } else if( - event->type == LoaderEventTypeApplicationLoadFailed || - event->type == LoaderEventTypeApplicationStopped) { + } else if(event->type == LoaderEventTypeNoMoreAppsInQueue) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } @@ -396,9 +393,9 @@ void desktop_lock(Desktop* desktop) { furi_hal_rtc_set_flag(FuriHalRtcFlagLock); if(desktop_pin_code_is_set()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_close(cli); - furi_record_close(RECORD_CLI); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_disable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); } desktop_auto_lock_inhibit(desktop); @@ -426,9 +423,9 @@ void desktop_unlock(Desktop* desktop) { furi_hal_rtc_set_pin_fails(0); if(desktop_pin_code_is_set()) { - Cli* cli = furi_record_open(RECORD_CLI); - cli_session_open(cli, &cli_vcp); - furi_record_close(RECORD_CLI); + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_enable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); } DesktopStatus status = {.locked = false}; @@ -525,6 +522,10 @@ int32_t desktop_srv(void* p) { if(desktop_pin_code_is_set()) { desktop_lock(desktop); + } else { + CliVcp* cli_vcp = furi_record_open(RECORD_CLI_VCP); + cli_vcp_enable(cli_vcp); + furi_record_close(RECORD_CLI_VCP); } if(storage_file_exists(desktop->storage, SLIDESHOW_FS_PATH)) { diff --git a/applications/services/expansion/application.fam b/applications/services/expansion/application.fam index dbdde0a52..f85450e40 100644 --- a/applications/services/expansion/application.fam +++ b/applications/services/expansion/application.fam @@ -8,5 +8,5 @@ App( ], requires=["rpc_start"], provides=["expansion_settings"], - order=150, + order=100, ) diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index c05b9cc85..ac2a5935b 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -1,6 +1,8 @@ #include "expansion_worker.h" +#include #include + #include #include @@ -250,9 +252,13 @@ static bool expansion_worker_handle_state_connected( if(!expansion_worker_rpc_session_open(instance)) break; instance->state = ExpansionWorkerStateRpcActive; } else if(command == ExpansionFrameControlCommandEnableOtg) { - furi_hal_power_enable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } else if(command == ExpansionFrameControlCommandDisableOtg) { - furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } else { break; } diff --git a/applications/services/gui/modules/button_panel.c b/applications/services/gui/modules/button_panel.c index 9301870ef..efc512bc5 100644 --- a/applications/services/gui/modules/button_panel.c +++ b/applications/services/gui/modules/button_panel.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -46,6 +47,7 @@ ARRAY_DEF(ButtonMatrix, ButtonArray_t); struct ButtonPanel { View* view; + bool freeze_input; }; typedef struct { @@ -63,7 +65,7 @@ static void button_panel_process_up(ButtonPanel* button_panel); static void button_panel_process_down(ButtonPanel* button_panel); static void button_panel_process_left(ButtonPanel* button_panel); static void button_panel_process_right(ButtonPanel* button_panel); -static void button_panel_process_ok(ButtonPanel* button_panel); +static void button_panel_process_ok(ButtonPanel* button_panel, InputType input); static void button_panel_view_draw_callback(Canvas* canvas, void* _model); static bool button_panel_view_input_callback(InputEvent* event, void* context); @@ -347,7 +349,7 @@ static void button_panel_process_right(ButtonPanel* button_panel) { true); } -void button_panel_process_ok(ButtonPanel* button_panel) { +void button_panel_process_ok(ButtonPanel* button_panel, InputType type) { ButtonItem* button_item = NULL; with_view_model( @@ -360,7 +362,7 @@ void button_panel_process_ok(ButtonPanel* button_panel) { true); if(button_item && button_item->callback) { - button_item->callback(button_item->callback_context, button_item->index); + button_item->callback(button_item->callback_context, button_item->index, type); } } @@ -368,8 +370,15 @@ static bool button_panel_view_input_callback(InputEvent* event, void* context) { ButtonPanel* button_panel = context; furi_assert(button_panel); bool consumed = false; - - if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + if((event->type == InputTypePress) || (event->type == InputTypeRelease)) { + button_panel->freeze_input = (event->type == InputTypePress); + } + consumed = true; + button_panel_process_ok(button_panel, event->type); + } + if(!button_panel->freeze_input && + (!(event->type == InputTypePress) && !(event->type == InputTypeRelease))) { switch(event->key) { case InputKeyUp: consumed = true; @@ -387,10 +396,6 @@ static bool button_panel_view_input_callback(InputEvent* event, void* context) { consumed = true; button_panel_process_right(button_panel); break; - case InputKeyOk: - consumed = true; - button_panel_process_ok(button_panel); - break; default: break; } diff --git a/applications/services/gui/modules/button_panel.h b/applications/services/gui/modules/button_panel.h index 1218222b8..159e14336 100644 --- a/applications/services/gui/modules/button_panel.h +++ b/applications/services/gui/modules/button_panel.h @@ -15,7 +15,7 @@ extern "C" { typedef struct ButtonPanel ButtonPanel; /** Callback type to call for handling selecting button_panel items */ -typedef void (*ButtonItemCallback)(void* context, uint32_t index); +typedef void (*ButtonItemCallback)(void* context, uint32_t index, InputType type); /** Allocate new button_panel module. * diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 5b1bdccda..104044cd7 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -11,8 +11,12 @@ struct Submenu { typedef struct { FuriString* label; uint32_t index; - SubmenuItemCallback callback; + union { + SubmenuItemCallback callback; + SubmenuItemCallbackEx callback_ex; + }; void* callback_context; + bool has_extended_events; } SubmenuItem; static void SubmenuItem_init(SubmenuItem* item) { @@ -57,7 +61,7 @@ typedef struct { static void submenu_process_up(Submenu* submenu); static void submenu_process_down(Submenu* submenu); -static void submenu_process_ok(Submenu* submenu); +static void submenu_process_ok(Submenu* submenu, InputType input_type); static void submenu_view_draw_callback(Canvas* canvas, void* _model) { SubmenuModel* model = _model; @@ -120,7 +124,10 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { furi_assert(submenu); bool consumed = false; - if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + consumed = true; + submenu_process_ok(submenu, event->type); + } else if(event->type == InputTypeShort) { switch(event->key) { case InputKeyUp: consumed = true; @@ -130,10 +137,6 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { consumed = true; submenu_process_down(submenu); break; - case InputKeyOk: - consumed = true; - submenu_process_ok(submenu); - break; default: break; } @@ -211,6 +214,31 @@ void submenu_add_item( item->index = index; item->callback = callback; item->callback_context = callback_context; + item->has_extended_events = false; + }, + true); +} + +void submenu_add_item_ex( + Submenu* submenu, + const char* label, + uint32_t index, + SubmenuItemCallbackEx callback, + void* callback_context) { + SubmenuItem* item = NULL; + furi_check(label); + furi_check(submenu); + + with_view_model( + submenu->view, + SubmenuModel * model, + { + item = SubmenuItemArray_push_new(model->items); + furi_string_set_str(item->label, label); + item->index = index; + item->callback_ex = callback; + item->callback_context = callback_context; + item->has_extended_events = true; }, true); } @@ -357,7 +385,7 @@ void submenu_process_down(Submenu* submenu) { true); } -void submenu_process_ok(Submenu* submenu) { +void submenu_process_ok(Submenu* submenu, InputType input_type) { SubmenuItem* item = NULL; with_view_model( @@ -371,8 +399,12 @@ void submenu_process_ok(Submenu* submenu) { }, true); - if(item && item->callback) { + if(!item) return; + + if(!item->has_extended_events && input_type == InputTypeShort && item->callback) { item->callback(item->callback_context, item->index); + } else if(item->has_extended_events && item->callback_ex) { + item->callback_ex(item->callback_context, input_type, item->index); } } diff --git a/applications/services/gui/modules/submenu.h b/applications/services/gui/modules/submenu.h index e435f94a2..b4bfc226f 100644 --- a/applications/services/gui/modules/submenu.h +++ b/applications/services/gui/modules/submenu.h @@ -14,6 +14,7 @@ extern "C" { /** Submenu anonymous structure */ typedef struct Submenu Submenu; typedef void (*SubmenuItemCallback)(void* context, uint32_t index); +typedef void (*SubmenuItemCallbackEx)(void* context, InputType input_type, uint32_t index); /** Allocate and initialize submenu * @@ -53,6 +54,22 @@ void submenu_add_item( SubmenuItemCallback callback, void* callback_context); +/** Add item to submenu with extended press events + * + * @param submenu Submenu instance + * @param label menu item label + * @param index menu item index, used for callback, may be + * the same with other items + * @param callback menu item extended callback + * @param callback_context menu item callback context + */ +void submenu_add_item_ex( + Submenu* submenu, + const char* label, + uint32_t index, + SubmenuItemCallbackEx callback, + void* callback_context); + /** Change label of an existing item * * @param submenu Submenu instance diff --git a/applications/services/gui/modules/widget.c b/applications/services/gui/modules/widget.c index ea3315d8d..f8d2fc346 100644 --- a/applications/services/gui/modules/widget.c +++ b/applications/services/gui/modules/widget.c @@ -195,14 +195,27 @@ void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* i widget_add_element(widget, icon_element); } -void widget_add_frame_element( +void widget_add_rect_element( Widget* widget, uint8_t x, uint8_t y, uint8_t width, uint8_t height, - uint8_t radius) { + uint8_t radius, + bool fill) { furi_check(widget); - WidgetElement* frame_element = widget_element_frame_create(x, y, width, height, radius); - widget_add_element(widget, frame_element); + WidgetElement* rect_element = widget_element_rect_create(x, y, width, height, radius, fill); + widget_add_element(widget, rect_element); +} + +void widget_add_circle_element(Widget* widget, uint8_t x, uint8_t y, uint8_t radius, bool fill) { + furi_check(widget); + WidgetElement* circle_element = widget_element_circle_create(x, y, radius, fill); + widget_add_element(widget, circle_element); +} + +void widget_add_line_element(Widget* widget, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { + furi_check(widget); + WidgetElement* line_element = widget_element_line_create(x1, y1, x2, y2); + widget_add_element(widget, line_element); } diff --git a/applications/services/gui/modules/widget.h b/applications/services/gui/modules/widget.h index b31370296..bda444963 100644 --- a/applications/services/gui/modules/widget.h +++ b/applications/services/gui/modules/widget.h @@ -152,21 +152,57 @@ void widget_add_button_element( void widget_add_icon_element(Widget* widget, uint8_t x, uint8_t y, const Icon* icon); /** Add Frame Element + * + * @param widget Widget instance + * @param x top left x coordinate + * @param y top left y coordinate + * @param width frame width + * @param height frame height + * @param radius frame radius + * + * @warning deprecated, use widget_add_rect_element instead + */ +#define widget_add_frame_element(widget, x, y, width, height, radius) \ + widget_add_rect_element((widget), (x), (y), (width), (height), (radius), false) + +/** Add Rect Element * * @param widget Widget instance * @param x top left x coordinate * @param y top left y coordinate - * @param width frame width - * @param height frame height - * @param radius frame radius + * @param width rect width + * @param height rect height + * @param radius corner radius + * @param fill whether to fill the box or not */ -void widget_add_frame_element( +void widget_add_rect_element( Widget* widget, uint8_t x, uint8_t y, uint8_t width, uint8_t height, - uint8_t radius); + uint8_t radius, + bool fill); + +/** Add Circle Element + * + * @param widget Widget instance + * @param x center x coordinate + * @param y center y coordinate + * @param radius circle radius + * @param fill whether to fill the circle or not + */ +void widget_add_circle_element(Widget* widget, uint8_t x, uint8_t y, uint8_t radius, bool fill); + +/** Add Line Element + * + * @param widget Widget instance + * @param x1 first x coordinate + * @param y1 first y coordinate + * @param x2 second x coordinate + * @param y2 second y coordinate + */ +void widget_add_line_element(Widget* widget, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); #ifdef __cplusplus } diff --git a/applications/services/gui/modules/widget_elements/widget_element.h b/applications/services/gui/modules/widget_elements/widget_element.h index 473fabd04..8f14053f4 100644 --- a/applications/services/gui/modules/widget_elements/widget_element.h +++ b/applications/services/gui/modules/widget_elements/widget_element.h @@ -5,6 +5,8 @@ #pragma once +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/applications/services/gui/modules/widget_elements/widget_element_circle.c b/applications/services/gui/modules/widget_elements/widget_element_circle.c new file mode 100644 index 000000000..47a952429 --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_circle.c @@ -0,0 +1,45 @@ +#include "widget_element_i.h" + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t radius; + bool fill; +} GuiCircleModel; + +static void gui_circle_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiCircleModel* model = element->model; + if(model->fill) { + canvas_draw_disc(canvas, model->x, model->y, model->radius); + } else { + canvas_draw_circle(canvas, model->x, model->y, model->radius); + } +} + +static void gui_circle_free(WidgetElement* gui_circle) { + furi_assert(gui_circle); + + free(gui_circle->model); + free(gui_circle); +} + +WidgetElement* widget_element_circle_create(uint8_t x, uint8_t y, uint8_t radius, bool fill) { + // Allocate and init model + GuiCircleModel* model = malloc(sizeof(GuiCircleModel)); + model->x = x; + model->y = y; + model->radius = radius; + model->fill = fill; + + // Allocate and init Element + WidgetElement* gui_circle = malloc(sizeof(WidgetElement)); + gui_circle->parent = NULL; + gui_circle->input = NULL; + gui_circle->draw = gui_circle_draw; + gui_circle->free = gui_circle_free; + gui_circle->model = model; + + return gui_circle; +} diff --git a/applications/services/gui/modules/widget_elements/widget_element_frame.c b/applications/services/gui/modules/widget_elements/widget_element_frame.c deleted file mode 100644 index a7265348e..000000000 --- a/applications/services/gui/modules/widget_elements/widget_element_frame.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "widget_element_i.h" - -typedef struct { - uint8_t x; - uint8_t y; - uint8_t width; - uint8_t height; - uint8_t radius; -} GuiFrameModel; - -static void gui_frame_draw(Canvas* canvas, WidgetElement* element) { - furi_assert(canvas); - furi_assert(element); - GuiFrameModel* model = element->model; - canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius); -} - -static void gui_frame_free(WidgetElement* gui_frame) { - furi_assert(gui_frame); - - free(gui_frame->model); - free(gui_frame); -} - -WidgetElement* widget_element_frame_create( - uint8_t x, - uint8_t y, - uint8_t width, - uint8_t height, - uint8_t radius) { - // Allocate and init model - GuiFrameModel* model = malloc(sizeof(GuiFrameModel)); - model->x = x; - model->y = y; - model->width = width; - model->height = height; - model->radius = radius; - - // Allocate and init Element - WidgetElement* gui_frame = malloc(sizeof(WidgetElement)); - gui_frame->parent = NULL; - gui_frame->input = NULL; - gui_frame->draw = gui_frame_draw; - gui_frame->free = gui_frame_free; - gui_frame->model = model; - - return gui_frame; -} diff --git a/applications/services/gui/modules/widget_elements/widget_element_i.h b/applications/services/gui/modules/widget_elements/widget_element_i.h index 2bced5576..adaf09168 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_i.h +++ b/applications/services/gui/modules/widget_elements/widget_element_i.h @@ -73,14 +73,16 @@ WidgetElement* widget_element_button_create( /** Create icon element */ WidgetElement* widget_element_icon_create(uint8_t x, uint8_t y, const Icon* icon); -/** Create frame element */ -WidgetElement* widget_element_frame_create( +/** Create rect element */ +WidgetElement* widget_element_rect_create( uint8_t x, uint8_t y, uint8_t width, uint8_t height, - uint8_t radius); + uint8_t radius, + bool fill); +/** Create text scroll element */ WidgetElement* widget_element_text_scroll_create( uint8_t x, uint8_t y, @@ -88,6 +90,12 @@ WidgetElement* widget_element_text_scroll_create( uint8_t height, const char* text); +/** Create circle element */ +WidgetElement* widget_element_circle_create(uint8_t x, uint8_t y, uint8_t radius, bool fill); + +/** Create line element */ +WidgetElement* widget_element_line_create(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); + #ifdef __cplusplus } #endif diff --git a/applications/services/gui/modules/widget_elements/widget_element_line.c b/applications/services/gui/modules/widget_elements/widget_element_line.c new file mode 100644 index 000000000..4cccdc976 --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_line.c @@ -0,0 +1,41 @@ +#include "widget_element_i.h" + +typedef struct { + uint8_t x1; + uint8_t y1; + uint8_t x2; + uint8_t y2; +} GuiLineModel; + +static void gui_line_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiLineModel* model = element->model; + canvas_draw_line(canvas, model->x1, model->y1, model->x2, model->y2); +} + +static void gui_line_free(WidgetElement* gui_line) { + furi_assert(gui_line); + + free(gui_line->model); + free(gui_line); +} + +WidgetElement* widget_element_line_create(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { + // Allocate and init model + GuiLineModel* model = malloc(sizeof(GuiLineModel)); + model->x1 = x1; + model->y1 = y1; + model->x2 = x2; + model->y2 = y2; + + // Allocate and init Element + WidgetElement* gui_line = malloc(sizeof(WidgetElement)); + gui_line->parent = NULL; + gui_line->input = NULL; + gui_line->draw = gui_line_draw; + gui_line->free = gui_line_free; + gui_line->model = model; + + return gui_line; +} diff --git a/applications/services/gui/modules/widget_elements/widget_element_rect.c b/applications/services/gui/modules/widget_elements/widget_element_rect.c new file mode 100644 index 000000000..6539f09a8 --- /dev/null +++ b/applications/services/gui/modules/widget_elements/widget_element_rect.c @@ -0,0 +1,55 @@ +#include "widget_element_i.h" + +typedef struct { + uint8_t x; + uint8_t y; + uint8_t width; + uint8_t height; + uint8_t radius; + bool fill; +} GuiRectModel; + +static void gui_rect_draw(Canvas* canvas, WidgetElement* element) { + furi_assert(canvas); + furi_assert(element); + GuiRectModel* model = element->model; + if(model->fill) { + canvas_draw_rbox(canvas, model->x, model->y, model->width, model->height, model->radius); + } else { + canvas_draw_rframe(canvas, model->x, model->y, model->width, model->height, model->radius); + } +} + +static void gui_rect_free(WidgetElement* gui_rect) { + furi_assert(gui_rect); + + free(gui_rect->model); + free(gui_rect); +} + +WidgetElement* widget_element_rect_create( + uint8_t x, + uint8_t y, + uint8_t width, + uint8_t height, + uint8_t radius, + bool fill) { + // Allocate and init model + GuiRectModel* model = malloc(sizeof(GuiRectModel)); + model->x = x; + model->y = y; + model->width = width; + model->height = height; + model->radius = radius; + model->fill = fill; + + // Allocate and init Element + WidgetElement* gui_rect = malloc(sizeof(WidgetElement)); + gui_rect->parent = NULL; + gui_rect->input = NULL; + gui_rect->draw = gui_rect_draw; + gui_rect->free = gui_rect_free; + gui_rect->model = model; + + return gui_rect; +} diff --git a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c index 4c9c39dff..491ffc6bc 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c +++ b/applications/services/gui/modules/widget_elements/widget_element_text_scroll.c @@ -19,8 +19,8 @@ typedef struct { uint8_t width; uint8_t height; FuriString* text; - uint8_t scroll_pos_total; - uint8_t scroll_pos_current; + uint16_t scroll_pos_total; + uint16_t scroll_pos_current; bool text_formatted; } WidgetElementTextScrollModel; diff --git a/applications/services/gui/scene_manager.c b/applications/services/gui/scene_manager.c index 11acc0796..485e31d11 100644 --- a/applications/services/gui/scene_manager.c +++ b/applications/services/gui/scene_manager.c @@ -230,6 +230,11 @@ bool scene_manager_search_and_switch_to_another_scene( } } +uint32_t scene_manager_get_current_scene(SceneManager* scene_manager) { + furi_check(scene_manager); + return *SceneManagerIdStack_back(scene_manager->scene_id_stack); +} + void scene_manager_stop(SceneManager* scene_manager) { furi_check(scene_manager); diff --git a/applications/services/gui/scene_manager.h b/applications/services/gui/scene_manager.h index 54dfa9cd4..8dad92aac 100644 --- a/applications/services/gui/scene_manager.h +++ b/applications/services/gui/scene_manager.h @@ -170,6 +170,14 @@ bool scene_manager_search_and_switch_to_another_scene( SceneManager* scene_manager, uint32_t scene_id); +/** Get id of current scene + * + * @param scene_manager SceneManager instance + * + * @return Scene ID + */ +uint32_t scene_manager_get_current_scene(SceneManager* scene_manager); + /** Exit from current scene * * @param scene_manager SceneManager instance diff --git a/applications/services/input/input.c b/applications/services/input/input.c index 6cbafb795..d7e9f3d31 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -4,8 +4,10 @@ #include #include #include -#include #include +#include +#include +#include #define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2) #define INPUT_PRESS_TICKS 150 @@ -25,7 +27,7 @@ typedef struct { } InputPinState; /** Input CLI command handler */ -void input_cli(Cli* cli, FuriString* args, void* context); +void input_cli(PipeSide* pipe, FuriString* args, void* context); // #define INPUT_DEBUG @@ -92,8 +94,10 @@ int32_t input_srv(void* p) { #endif #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command( + registry, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub); + furi_record_close(RECORD_CLI); #endif InputPinState pin_states[input_pins_count]; diff --git a/applications/services/input/input_cli.c b/applications/services/input/input_cli.c index 8e711c895..d5ea57418 100644 --- a/applications/services/input/input_cli.c +++ b/applications/services/input/input_cli.c @@ -1,8 +1,9 @@ #include "input.h" #include -#include +#include #include +#include static void input_cli_usage(void) { printf("Usage:\r\n"); @@ -19,7 +20,7 @@ static void input_cli_dump_events_callback(const void* value, void* ctx) { furi_message_queue_put(input_queue, value, FuriWaitForever); } -static void input_cli_dump(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) { +static void input_cli_dump(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) { UNUSED(args); FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); FuriPubSubSubscription* input_subscription = @@ -27,7 +28,7 @@ static void input_cli_dump(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) InputEvent input_event; printf("Press CTRL+C to stop\r\n"); - while(!cli_cmd_interrupt_received(cli)) { + while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) { if(furi_message_queue_get(input_queue, &input_event, 100) == FuriStatusOk) { printf( "key: %s type: %s\r\n", @@ -47,8 +48,8 @@ static void input_cli_send_print_usage(void) { printf("\t\t \t - one of 'press', 'release', 'short', 'long'\r\n"); } -static void input_cli_send(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) { - UNUSED(cli); +static void input_cli_send(PipeSide* pipe, FuriString* args, FuriPubSub* event_pubsub) { + UNUSED(pipe); InputEvent event; FuriString* key_str; key_str = furi_string_alloc(); @@ -97,8 +98,7 @@ static void input_cli_send(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) furi_string_free(key_str); } -void input_cli(Cli* cli, FuriString* args, void* context) { - furi_assert(cli); +void input_cli(PipeSide* pipe, FuriString* args, void* context) { furi_assert(context); FuriPubSub* event_pubsub = context; FuriString* cmd; @@ -110,11 +110,11 @@ void input_cli(Cli* cli, FuriString* args, void* context) { break; } if(furi_string_cmp_str(cmd, "dump") == 0) { - input_cli_dump(cli, args, event_pubsub); + input_cli_dump(pipe, args, event_pubsub); break; } if(furi_string_cmp_str(cmd, "send") == 0) { - input_cli_send(cli, args, event_pubsub); + input_cli_send(pipe, args, event_pubsub); break; } diff --git a/applications/services/loader/application.fam b/applications/services/loader/application.fam index f4d006e07..5600bdf62 100644 --- a/applications/services/loader/application.fam +++ b/applications/services/loader/application.fam @@ -19,5 +19,5 @@ App( apptype=FlipperAppType.STARTUP, entry_point="loader_on_system_start", requires=["loader"], - order=90, + order=80, ) diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 72cac4b62..7b37f2510 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -167,6 +167,13 @@ static void loader_show_gui_error( furi_record_close(RECORD_DIALOGS); } +static void loader_generic_synchronous_request(Loader* loader, LoaderMessage* message) { + furi_check(loader); + message->api_lock = api_lock_alloc_locked(); + furi_message_queue_put(loader->queue, message, FuriWaitForever); + api_lock_wait_unlock_and_free(message->api_lock); +} + LoaderStatus loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) { furi_check(loader); @@ -202,16 +209,12 @@ void loader_start_detached_with_gui_error(Loader* loader, const char* name, cons } bool loader_lock(Loader* loader) { - furi_check(loader); - - LoaderMessage message; LoaderMessageBoolResult result; - message.type = LoaderMessageTypeLock; - message.api_lock = api_lock_alloc_locked(); - message.bool_value = &result; - furi_message_queue_put(loader->queue, &message, FuriWaitForever); - api_lock_wait_unlock_and_free(message.api_lock); - + LoaderMessage message = { + .type = LoaderMessageTypeLock, + .bool_value = &result, + }; + loader_generic_synchronous_request(loader, &message); return result.value; } @@ -225,16 +228,12 @@ void loader_unlock(Loader* loader) { } bool loader_is_locked(Loader* loader) { - furi_check(loader); - - LoaderMessage message; LoaderMessageBoolResult result; - message.type = LoaderMessageTypeIsLocked; - message.api_lock = api_lock_alloc_locked(); - message.bool_value = &result; - furi_message_queue_put(loader->queue, &message, FuriWaitForever); - api_lock_wait_unlock_and_free(message.api_lock); - + LoaderMessage message = { + .type = LoaderMessageTypeIsLocked, + .bool_value = &result, + }; + loader_generic_synchronous_request(loader, &message); return result.value; } @@ -256,42 +255,63 @@ FuriPubSub* loader_get_pubsub(Loader* loader) { } bool loader_signal(Loader* loader, uint32_t signal, void* arg) { - furi_check(loader); - LoaderMessageBoolResult result; - LoaderMessage message = { .type = LoaderMessageTypeSignal, - .api_lock = api_lock_alloc_locked(), .signal.signal = signal, .signal.arg = arg, .bool_value = &result, }; - - furi_message_queue_put(loader->queue, &message, FuriWaitForever); - api_lock_wait_unlock_and_free(message.api_lock); - + loader_generic_synchronous_request(loader, &message); return result.value; } bool loader_get_application_name(Loader* loader, FuriString* name) { - furi_check(loader); - LoaderMessageBoolResult result; - LoaderMessage message = { .type = LoaderMessageTypeGetApplicationName, - .api_lock = api_lock_alloc_locked(), .application_name = name, .bool_value = &result, }; - - furi_message_queue_put(loader->queue, &message, FuriWaitForever); - api_lock_wait_unlock_and_free(message.api_lock); - + loader_generic_synchronous_request(loader, &message); return result.value; } +bool loader_get_application_launch_path(Loader* loader, FuriString* name) { + LoaderMessageBoolResult result; + LoaderMessage message = { + .type = LoaderMessageTypeGetApplicationLaunchPath, + .application_name = name, + .bool_value = &result, + }; + loader_generic_synchronous_request(loader, &message); + return result.value; +} + +void loader_enqueue_launch( + Loader* loader, + const char* name, + const char* args, + LoaderDeferredLaunchFlag flags) { + LoaderMessage message = { + .type = LoaderMessageTypeEnqueueLaunch, + .defer_start = + { + .name_or_path = strdup(name), + .args = args ? strdup(args) : NULL, + .flags = flags, + }, + }; + loader_generic_synchronous_request(loader, &message); +} + +void loader_clear_launch_queue(Loader* loader) { + LoaderMessage message = { + .type = LoaderMessageTypeClearLaunchQueue, + }; + loader_generic_synchronous_request(loader, &message); +} + // callbacks static void loader_menu_closed_callback(void* context) { @@ -328,12 +348,10 @@ static Loader* loader_alloc(void) { Loader* loader = malloc(sizeof(Loader)); loader->pubsub = furi_pubsub_alloc(); loader->queue = furi_message_queue_alloc(1, sizeof(LoaderMessage)); - loader->loader_menu = NULL; - loader->loader_applications = NULL; - loader->app.args = NULL; - loader->app.thread = NULL; - loader->app.insomniac = false; - loader->app.fap = NULL; + loader->gui = furi_record_open(RECORD_GUI); + loader->view_holder = view_holder_alloc(); + loader->loading = loading_alloc(); + view_holder_attach_to_gui(loader->view_holder, loader->gui); return loader; } @@ -400,9 +418,6 @@ static void loader_start_internal_app( const FlipperInternalApplication* app, const char* args) { FURI_LOG_I(TAG, "Starting %s", app->name); - LoaderEvent event; - event.type = LoaderEventTypeApplicationBeforeLoad; - furi_pubsub_publish(loader->pubsub, &event); // store args furi_assert(loader->app.args == NULL); @@ -490,10 +505,6 @@ static LoaderMessageLoaderStatusResult loader_start_external_app( result.value = loader_make_success_status(error_message); result.error = LoaderStatusErrorUnknown; - LoaderEvent event; - event.type = LoaderEventTypeApplicationBeforeLoad; - furi_pubsub_publish(loader->pubsub, &event); - do { loader->app.fap = flipper_application_alloc(storage, firmware_api_interface); size_t start = furi_get_tick(); @@ -548,6 +559,7 @@ static LoaderMessageLoaderStatusResult loader_start_external_app( if(result.value != LoaderStatusOk) { flipper_application_free(loader->app.fap); loader->app.fap = NULL; + LoaderEvent event; event.type = LoaderEventTypeApplicationLoadFailed; furi_pubsub_publish(loader->pubsub, &event); } @@ -597,6 +609,10 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name( status.value = loader_make_success_status(error_message); status.error = LoaderStatusErrorUnknown; + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); + do { // check lock if(loader_do_is_locked(loader)) { @@ -656,6 +672,10 @@ static LoaderMessageLoaderStatusResult loader_do_start_by_name( LoaderStatusErrorUnknownApp, error_message, "Application \"%s\" not found", name); } while(false); + if(status.value == LoaderStatusOk) { + loader->app.launch_path = furi_string_alloc_set_str(name); + } + return status; } @@ -673,6 +693,58 @@ static void loader_do_unlock(Loader* loader) { loader->app.thread = NULL; } +static void loader_do_emit_queue_empty_event(Loader* loader) { + if(loader_do_is_locked(loader)) return; + FURI_LOG_I(TAG, "Launch queue empty"); + LoaderEvent event; + event.type = LoaderEventTypeNoMoreAppsInQueue; + furi_pubsub_publish(loader->pubsub, &event); +} + +static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record); + +static void loader_do_next_deferred_launch_if_available(Loader* loader) { + LoaderDeferredLaunchRecord record; + if(loader_queue_pop(&loader->launch_queue, &record)) { + loader_do_deferred_launch(loader, &record); + loader_queue_item_clear(&record); + } else { + loader_do_emit_queue_empty_event(loader); + } +} + +static bool loader_do_deferred_launch(Loader* loader, LoaderDeferredLaunchRecord* record) { + furi_assert(loader); + furi_assert(record); + + bool is_successful = false; + FuriString* error_message = furi_string_alloc(); + view_holder_set_view(loader->view_holder, loading_get_view(loader->loading)); + view_holder_send_to_front(loader->view_holder); + + do { + const char* app_name_str = record->name_or_path; + const char* app_args = record->args; + FURI_LOG_I(TAG, "Deferred launch: %s", app_name_str); + + LoaderMessageLoaderStatusResult result = + loader_do_start_by_name(loader, app_name_str, app_args, error_message); + if(result.value == LoaderStatusOk) { + is_successful = true; + break; + } + + if(record->flags & LoaderDeferredLaunchFlagGui) + loader_show_gui_error(result, app_name_str, error_message); + + loader_do_next_deferred_launch_if_available(loader); + } while(false); + + view_holder_set_view(loader->view_holder, NULL); + furi_string_free(error_message); + return is_successful; +} + static void loader_do_app_closed(Loader* loader) { furi_assert(loader->app.thread); @@ -697,11 +769,15 @@ static void loader_do_app_closed(Loader* loader) { loader->app.thread = NULL; } + furi_string_free(loader->app.launch_path); + FURI_LOG_I(TAG, "Application stopped. Free heap: %zu", memmgr_get_free_heap()); LoaderEvent event; event.type = LoaderEventTypeApplicationStopped; furi_pubsub_publish(loader->pubsub, &event); + + loader_do_next_deferred_launch_if_available(loader); } static bool loader_is_application_running(Loader* loader) { @@ -726,6 +802,15 @@ static bool loader_do_get_application_name(Loader* loader, FuriString* name) { return false; } +static bool loader_do_get_application_launch_path(Loader* loader, FuriString* path) { + if(loader_is_application_running(loader)) { + furi_string_set(path, loader->app.launch_path); + return true; + } + + return false; +} + // app int32_t loader_srv(void* p) { @@ -748,16 +833,20 @@ int32_t loader_srv(void* p) { while(true) { if(furi_message_queue_get(loader->queue, &message, FuriWaitForever) == FuriStatusOk) { switch(message.type) { - case LoaderMessageTypeStartByName: - *(message.status_value) = loader_do_start_by_name( + case LoaderMessageTypeStartByName: { + LoaderMessageLoaderStatusResult status = loader_do_start_by_name( loader, message.start.name, message.start.args, message.start.error_message); + *(message.status_value) = status; + if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader); api_lock_unlock(message.api_lock); break; + } case LoaderMessageTypeStartByNameDetachedWithGuiError: { FuriString* error_message = furi_string_alloc(); LoaderMessageLoaderStatusResult status = loader_do_start_by_name( loader, message.start.name, message.start.args, error_message); loader_show_gui_error(status, message.start.name, error_message); + if(status.value != LoaderStatusOk) loader_do_emit_queue_empty_event(loader); if(message.start.name) free((void*)message.start.name); if(message.start.args) free((void*)message.start.args); furi_string_free(error_message); @@ -796,6 +885,19 @@ int32_t loader_srv(void* p) { loader_do_get_application_name(loader, message.application_name); api_lock_unlock(message.api_lock); break; + case LoaderMessageTypeGetApplicationLaunchPath: + message.bool_value->value = + loader_do_get_application_launch_path(loader, message.application_name); + api_lock_unlock(message.api_lock); + break; + case LoaderMessageTypeEnqueueLaunch: + furi_check(loader_queue_push(&loader->launch_queue, &message.defer_start)); + api_lock_unlock(message.api_lock); + break; + case LoaderMessageTypeClearLaunchQueue: + loader_queue_clear(&loader->launch_queue); + api_lock_unlock(message.api_lock); + break; } } } diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index cacfbff68..d732379a7 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -20,13 +20,19 @@ typedef enum { typedef enum { LoaderEventTypeApplicationBeforeLoad, LoaderEventTypeApplicationLoadFailed, - LoaderEventTypeApplicationStopped + LoaderEventTypeApplicationStopped, + LoaderEventTypeNoMoreAppsInQueue, //type == LoaderEventTypeApplicationStopped) { + if(event->type == LoaderEventTypeNoMoreAppsInQueue) { furi_thread_flags_set(thread_id, APPLICATION_STOP_EVENT); } } diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index f3ea30df2..265779e8f 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -1,11 +1,13 @@ #include "loader.h" #include -#include +#include +#include #include #include #include #include +#include static void loader_cli_print_usage(void) { printf("Usage:\r\n"); @@ -110,8 +112,8 @@ static void loader_cli_signal(FuriString* args, Loader* loader) { } } -static void loader_cli(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void loader_cli(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); Loader* loader = furi_record_open(RECORD_LOADER); @@ -140,8 +142,9 @@ static void loader_cli(Cli* cli, FuriString* args, void* context) { void loader_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command( + registry, RECORD_LOADER, CliCommandFlagParallelSafe, loader_cli, NULL); furi_record_close(RECORD_CLI); #else UNUSED(loader_cli); diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index 92f1e88e0..2bf42c655 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -2,11 +2,20 @@ #include #include #include + +#include +#include +#include + +#include + #include "loader.h" #include "loader_menu.h" #include "loader_applications.h" +#include "loader_queue.h" typedef struct { + FuriString* launch_path; char* args; FuriThread* thread; bool insomniac; @@ -19,6 +28,12 @@ struct Loader { LoaderMenu* loader_menu; LoaderApplications* loader_applications; LoaderAppData app; + + LoaderLaunchQueue launch_queue; + + Gui* gui; + ViewHolder* view_holder; + Loading* loading; }; typedef enum { @@ -33,6 +48,9 @@ typedef enum { LoaderMessageTypeStartByNameDetachedWithGuiError, LoaderMessageTypeSignal, LoaderMessageTypeGetApplicationName, + LoaderMessageTypeGetApplicationLaunchPath, + LoaderMessageTypeEnqueueLaunch, + LoaderMessageTypeClearLaunchQueue, } LoaderMessageType; typedef struct { @@ -72,6 +90,7 @@ typedef struct { union { LoaderMessageStartByName start; + LoaderDeferredLaunchRecord defer_start; LoaderMessageSignal signal; FuriString* application_name; }; diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c index 25763a0bc..dc1b673d5 100644 --- a/applications/services/loader/loader_menu.c +++ b/applications/services/loader/loader_menu.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "loader.h" #include "loader_menu.h" @@ -71,10 +72,16 @@ static void loader_menu_applications_callback(void* context, uint32_t index) { loader_menu_start(name); } -static void loader_menu_settings_menu_callback(void* context, uint32_t index) { +static void + loader_menu_settings_menu_callback(void* context, InputType input_type, uint32_t index) { UNUSED(context); - const char* name = FLIPPER_SETTINGS_APPS[index].name; - loader_menu_start(name); + if(input_type == InputTypeShort) { + const char* name = FLIPPER_SETTINGS_APPS[index].name; + loader_menu_start(name); + } else if(input_type == InputTypeLong) { + const char* name = FLIPPER_SETTINGS_APPS[index].name; + archive_favorites_handle_setting_pin_unpin(name, NULL); + } } static void loader_menu_switch_to_settings(void* context, uint32_t index) { @@ -128,7 +135,7 @@ static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) { static void loader_menu_build_submenu(LoaderMenuApp* app, LoaderMenu* loader_menu) { for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { - submenu_add_item( + submenu_add_item_ex( app->settings_menu, FLIPPER_SETTINGS_APPS[i].name, i, diff --git a/applications/services/loader/loader_queue.c b/applications/services/loader/loader_queue.c new file mode 100644 index 000000000..517dcad75 --- /dev/null +++ b/applications/services/loader/loader_queue.c @@ -0,0 +1,32 @@ +#include "loader_queue.h" + +void loader_queue_item_clear(LoaderDeferredLaunchRecord* item) { + free(item->args); + free(item->name_or_path); +} + +bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) { + if(!queue->item_cnt) return false; + + *item = queue->items[0]; + queue->item_cnt--; + memmove( + &queue->items[0], &queue->items[1], queue->item_cnt * sizeof(LoaderDeferredLaunchRecord)); + + return true; +} + +bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item) { + if(queue->item_cnt == LOADER_QUEUE_MAX_SIZE) return false; + + queue->items[queue->item_cnt] = *item; + queue->item_cnt++; + + return true; +} + +void loader_queue_clear(LoaderLaunchQueue* queue) { + for(size_t i = 0; i < queue->item_cnt; i++) + loader_queue_item_clear(&queue->items[i]); + queue->item_cnt = 0; +} diff --git a/applications/services/loader/loader_queue.h b/applications/services/loader/loader_queue.h new file mode 100644 index 000000000..c40130e39 --- /dev/null +++ b/applications/services/loader/loader_queue.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "loader.h" + +#define LOADER_QUEUE_MAX_SIZE 4 + +typedef struct { + char* name_or_path; + char* args; + LoaderDeferredLaunchFlag flags; +} LoaderDeferredLaunchRecord; + +typedef struct { + LoaderDeferredLaunchRecord items[LOADER_QUEUE_MAX_SIZE]; + size_t item_cnt; +} LoaderLaunchQueue; + +/** + * @brief Frees internal data in a `DeferredLaunchRecord` + * + * @param[out] item Record to clear + */ +void loader_queue_item_clear(LoaderDeferredLaunchRecord* item); + +/** + * @brief Fetches the next item from the launch queue + * + * @param[inout] queue Queue instance + * @param[out] item Item output + * + * @return `true` if `item` was populated, `false` if queue is empty + */ +bool loader_queue_pop(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item); + +/** + * @brief Puts an item into the launch queue + * + * @param[inout] queue Queue instance + * @param[in] item Item to put in the queue + * + * @return `true` if the item was put into the queue, `false` if there's no more + * space left + */ +bool loader_queue_push(LoaderLaunchQueue* queue, LoaderDeferredLaunchRecord* item); + +/** + * @brief Clears the launch queue + * + * @param[inout] queue Queue instance + */ +void loader_queue_clear(LoaderLaunchQueue* queue); diff --git a/applications/services/locale/application.fam b/applications/services/locale/application.fam index c762d02d6..09f49161f 100644 --- a/applications/services/locale/application.fam +++ b/applications/services/locale/application.fam @@ -4,6 +4,6 @@ App( apptype=FlipperAppType.STARTUP, entry_point="locale_on_system_start", cdefines=["SRV_LOCALE"], - order=90, + order=70, sdk_headers=["locale.h"], ) diff --git a/applications/services/power/application.fam b/applications/services/power/application.fam index f14d88c54..0e69ec6ec 100644 --- a/applications/services/power/application.fam +++ b/applications/services/power/application.fam @@ -22,5 +22,5 @@ App( apptype=FlipperAppType.STARTUP, entry_point="power_on_system_start", requires=["power"], - order=80, + order=50, ) diff --git a/applications/services/power/power_cli.c b/applications/services/power/power_cli.c index 93d0f232a..f630cb2e8 100644 --- a/applications/services/power/power_cli.c +++ b/applications/services/power/power_cli.c @@ -1,12 +1,14 @@ #include "power_cli.h" #include -#include +#include +#include #include #include +#include -void power_cli_off(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_off(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); printf("It's now safe to disconnect USB from your flipper\r\n"); @@ -14,33 +16,36 @@ void power_cli_off(Cli* cli, FuriString* args) { power_off(power); } -void power_cli_reboot(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_reboot(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); power_reboot(power, PowerBootModeNormal); } -void power_cli_reboot2dfu(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_reboot2dfu(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); UNUSED(args); Power* power = furi_record_open(RECORD_POWER); power_reboot(power, PowerBootModeDfu); } -void power_cli_5v(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_5v(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); + Power* power = furi_record_open(RECORD_POWER); if(!furi_string_cmp(args, "0")) { - furi_hal_power_disable_otg(); + power_enable_otg(power, false); } else if(!furi_string_cmp(args, "1")) { - furi_hal_power_enable_otg(); + power_enable_otg(power, true); } else { cli_print_usage("power_otg", "<1|0>", furi_string_get_cstr(args)); } + + furi_record_close(RECORD_POWER); } -void power_cli_3v3(Cli* cli, FuriString* args) { - UNUSED(cli); +void power_cli_3v3(PipeSide* pipe, FuriString* args) { + UNUSED(pipe); if(!furi_string_cmp(args, "0")) { furi_hal_power_disable_external_3_3v(); } else if(!furi_string_cmp(args, "1")) { @@ -64,7 +69,7 @@ static void power_cli_command_print_usage(void) { } } -void power_cli(Cli* cli, FuriString* args, void* context) { +void power_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; cmd = furi_string_alloc(); @@ -76,28 +81,28 @@ void power_cli(Cli* cli, FuriString* args, void* context) { } if(furi_string_cmp_str(cmd, "off") == 0) { - power_cli_off(cli, args); + power_cli_off(pipe, args); break; } if(furi_string_cmp_str(cmd, "reboot") == 0) { - power_cli_reboot(cli, args); + power_cli_reboot(pipe, args); break; } if(furi_string_cmp_str(cmd, "reboot2dfu") == 0) { - power_cli_reboot2dfu(cli, args); + power_cli_reboot2dfu(pipe, args); break; } if(furi_string_cmp_str(cmd, "5v") == 0) { - power_cli_5v(cli, args); + power_cli_5v(pipe, args); break; } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { if(furi_string_cmp_str(cmd, "3v3") == 0) { - power_cli_3v3(cli, args); + power_cli_3v3(pipe, args); break; } } @@ -110,10 +115,8 @@ void power_cli(Cli* cli, FuriString* args, void* context) { void power_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - - cli_add_command(cli, "power", CliCommandFlagParallelSafe, power_cli, NULL); - + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command(registry, "power", CliCommandFlagParallelSafe, power_cli, NULL); furi_record_close(RECORD_CLI); #else UNUSED(power_cli); diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index b73c4a1dd..fa86328df 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -64,6 +64,7 @@ static bool power_update_info(Power* power) { .is_charging = furi_hal_power_is_charging(), .gauge_is_ok = furi_hal_power_gauge_is_ok(), .is_shutdown_requested = furi_hal_power_is_shutdown_requested(), + .is_otg_enabled = furi_hal_power_is_otg_enabled(), .charge = furi_hal_power_get_pct(), .health = furi_hal_power_get_bat_health_pct(), .capacity_remaining = furi_hal_power_get_battery_remaining_capacity(), @@ -216,6 +217,30 @@ static void power_message_callback(FuriEventLoopObject* object, void* context) { case PowerMessageTypeShowBatteryLowWarning: power->show_battery_low_warning = *msg.bool_param; break; + case PowerMessageTypeSwitchOTG: + power->is_otg_requested = *msg.bool_param; + if(power->is_otg_requested) { + // Only try to enable if VBUS voltage is low, otherwise charger will refuse + if(power->info.voltage_vbus < 4.5f) { + size_t retries = 5; + while(retries-- > 0) { + if(furi_hal_power_enable_otg()) { + break; + } + } + if(!retries) { + FURI_LOG_W(TAG, "Failed to enable OTG, will try later"); + } + } else { + FURI_LOG_W( + TAG, + "Postponing OTG enable: VBUS(%0.1f) >= 4.5v", + (double)power->info.voltage_vbus); + } + } else { + furi_hal_power_disable_otg(); + } + break; default: furi_crash(); } @@ -241,9 +266,18 @@ static void power_tick_callback(void* context) { if(need_refresh) { view_port_update(power->battery_view_port); } - // Check OTG status and disable it in case of fault - if(furi_hal_power_is_otg_enabled()) { - furi_hal_power_check_otg_status(); + // Check OTG status, disable in case of a fault + if(furi_hal_power_check_otg_fault()) { + FURI_LOG_E(TAG, "OTG fault detected, disabling OTG"); + furi_hal_power_disable_otg(); + power->is_otg_requested = false; + } + + // Change OTG state if needed (i.e. after disconnecting USB power) + if(power->is_otg_requested && + (!power->info.is_otg_enabled && power->info.voltage_vbus < 4.5f)) { + FURI_LOG_D(TAG, "OTG requested but not enabled, enabling OTG"); + furi_hal_power_enable_otg(); } } diff --git a/applications/services/power/power_service/power.h b/applications/services/power/power_service/power.h index 0168a8656..0e1de6449 100644 --- a/applications/services/power/power_service/power.h +++ b/applications/services/power/power_service/power.h @@ -39,6 +39,7 @@ typedef struct { bool gauge_is_ok; bool is_charging; bool is_shutdown_requested; + bool is_otg_enabled; float current_charger; float current_gauge; @@ -96,6 +97,19 @@ bool power_is_battery_healthy(Power* power); */ void power_enable_low_battery_level_notification(Power* power, bool enable); +/** Enable or disable OTG + * + * @param power Power instance + * @param enable true - enable, false - disable + */ +void power_enable_otg(Power* power, bool enable); + +/** Check OTG status + * + * @return true if OTG is requested + */ +bool power_is_otg_enabled(Power* power); + #ifdef __cplusplus } #endif diff --git a/applications/services/power/power_service/power_api.c b/applications/services/power/power_service/power_api.c index 6f7515f5e..f634f15e3 100644 --- a/applications/services/power/power_service/power_api.c +++ b/applications/services/power/power_service/power_api.c @@ -70,3 +70,22 @@ void power_enable_low_battery_level_notification(Power* power, bool enable) { furi_check( furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk); } + +void power_enable_otg(Power* power, bool enable) { + furi_check(power); + + PowerMessage msg = { + .type = PowerMessageTypeSwitchOTG, + .bool_param = &enable, + .lock = api_lock_alloc_locked(), + }; + + furi_check( + furi_message_queue_put(power->message_queue, &msg, FuriWaitForever) == FuriStatusOk); + api_lock_wait_unlock_and_free(msg.lock); +} + +bool power_is_otg_enabled(Power* power) { + furi_check(power); + return power->is_otg_requested; +} diff --git a/applications/services/power/power_service/power_i.h b/applications/services/power/power_service/power_i.h index a0c02623a..f08fd8f88 100644 --- a/applications/services/power/power_service/power_i.h +++ b/applications/services/power/power_service/power_i.h @@ -33,6 +33,7 @@ struct Power { bool battery_low; bool show_battery_low_warning; + bool is_otg_requested; uint8_t battery_level; uint8_t power_off_timeout; }; @@ -48,6 +49,7 @@ typedef enum { PowerMessageTypeGetInfo, PowerMessageTypeIsBatteryHealthy, PowerMessageTypeShowBatteryLowWarning, + PowerMessageTypeSwitchOTG, } PowerMessageType; typedef struct { diff --git a/applications/services/region/application.fam b/applications/services/region/application.fam index a4cdc94ea..9b2451213 100644 --- a/applications/services/region/application.fam +++ b/applications/services/region/application.fam @@ -6,5 +6,5 @@ App( entry_point="region_on_system_start", cdefines=["SRV_REGION"], requires=["storage"], - order=170, + order=120, ) diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 41d55841e..1a19348ff 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -9,7 +9,8 @@ #include -#include +#include +#include #include #include #include @@ -435,9 +436,14 @@ void rpc_on_system_start(void* p) { rpc->busy_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command( - cli, "start_rpc_session", CliCommandFlagParallelSafe, rpc_cli_command_start_session, rpc); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command( + registry, + "start_rpc_session", + CliCommandFlagParallelSafe, + rpc_cli_command_start_session, + rpc); + furi_record_close(RECORD_CLI); furi_record_create(RECORD_RPC, rpc); } diff --git a/applications/services/rpc/rpc_app.c b/applications/services/rpc/rpc_app.c index aa2a3f64f..2b9a6542d 100644 --- a/applications/services/rpc/rpc_app.c +++ b/applications/services/rpc/rpc_app.c @@ -258,6 +258,41 @@ static void rpc_system_app_button_release(const PB_Main* request, void* context) } } +static void rpc_system_app_button_press_release(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(request->which_content == PB_Main_app_button_press_release_request_tag); + + RpcAppSystem* rpc_app = context; + furi_assert(rpc_app); + + if(rpc_app->callback) { + FURI_LOG_D(TAG, "ButtonPressRelease"); + + RpcAppSystemEvent event; + event.type = RpcAppEventTypeButtonPressRelease; + + if(strlen(request->content.app_button_press_release_request.args) != 0) { + event.data.type = RpcAppSystemEventDataTypeString; + event.data.string = request->content.app_button_press_release_request.args; + } else { + event.data.type = RpcAppSystemEventDataTypeInt32; + event.data.i32 = request->content.app_button_press_release_request.index; + } + + rpc_system_app_error_reset(rpc_app); + rpc_system_app_set_last_command(rpc_app, request->command_id, &event); + + rpc_app->callback(&event, rpc_app->callback_context); + + } else { + rpc_system_app_send_error_response( + rpc_app, + request->command_id, + PB_CommandStatus_ERROR_APP_NOT_RUNNING, + "ButtonPressRelease"); + } +} + static void rpc_system_app_get_error_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_app_get_error_request_tag); @@ -332,6 +367,7 @@ void rpc_system_app_confirm(RpcAppSystem* rpc_app, bool result) { rpc_app->last_event_type == RpcAppEventTypeLoadFile || rpc_app->last_event_type == RpcAppEventTypeButtonPress || rpc_app->last_event_type == RpcAppEventTypeButtonRelease || + rpc_app->last_event_type == RpcAppEventTypeButtonPressRelease || rpc_app->last_event_type == RpcAppEventTypeDataExchange); const uint32_t last_command_id = rpc_app->last_command_id; @@ -432,6 +468,9 @@ void* rpc_system_app_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_app_button_release; rpc_add_handler(session, PB_Main_app_button_release_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_app_button_press_release; + rpc_add_handler(session, PB_Main_app_button_press_release_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_app_get_error_process; rpc_add_handler(session, PB_Main_app_get_error_request_tag, &rpc_handler); diff --git a/applications/services/rpc/rpc_app.h b/applications/services/rpc/rpc_app.h index aa6fd81cc..377d9ccb3 100644 --- a/applications/services/rpc/rpc_app.h +++ b/applications/services/rpc/rpc_app.h @@ -90,6 +90,13 @@ typedef enum { * all activities to be conducted while a button is being pressed. */ RpcAppEventTypeButtonRelease, + /** + * @brief The client has informed the application that a button has been pressed and released. + * + * This command's meaning is application-specific, e.g. to perform an action + * once without repeating it. + */ + RpcAppEventTypeButtonPressRelease, /** * @brief The client has sent a byte array of arbitrary size. * @@ -162,6 +169,7 @@ void rpc_system_app_send_exited(RpcAppSystem* rpc_app); * - RpcAppEventTypeLoadFile * - RpcAppEventTypeButtonPress * - RpcAppEventTypeButtonRelease + * - RpcAppEventTypeButtonPressRelease * - RpcAppEventTypeDataExchange * * Not confirming these events will result in a client-side timeout. diff --git a/applications/services/rpc/rpc_cli.c b/applications/services/rpc/rpc_cli.c index 4612752a8..fda059ec8 100644 --- a/applications/services/rpc/rpc_cli.c +++ b/applications/services/rpc/rpc_cli.c @@ -1,25 +1,26 @@ -#include +#include +#include #include #include #include +#include #define TAG "RpcCli" typedef struct { - Cli* cli; + PipeSide* pipe; bool session_close_request; FuriSemaphore* terminate_semaphore; } CliRpc; -#define CLI_READ_BUFFER_SIZE 64 +#define CLI_READ_BUFFER_SIZE 64UL static void rpc_cli_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) { furi_assert(context); furi_assert(bytes); furi_assert(bytes_len > 0); CliRpc* cli_rpc = context; - - cli_write(cli_rpc->cli, bytes, bytes_len); + pipe_send(cli_rpc->pipe, bytes, bytes_len); } static void rpc_cli_session_close_callback(void* context) { @@ -36,9 +37,9 @@ static void rpc_cli_session_terminated_callback(void* context) { furi_semaphore_release(cli_rpc->terminate_semaphore); } -void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { +void rpc_cli_command_start_session(PipeSide* pipe, FuriString* args, void* context) { UNUSED(args); - furi_assert(cli); + furi_assert(pipe); furi_assert(context); Rpc* rpc = context; @@ -53,7 +54,7 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { return; } - CliRpc cli_rpc = {.cli = cli, .session_close_request = false}; + CliRpc cli_rpc = {.pipe = pipe, .session_close_request = false}; cli_rpc.terminate_semaphore = furi_semaphore_alloc(1, 0); rpc_session_set_context(rpc_session, &cli_rpc); rpc_session_set_send_bytes_callback(rpc_session, rpc_cli_send_bytes_callback); @@ -64,8 +65,9 @@ void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context) { size_t size_received = 0; while(1) { - size_received = cli_read_timeout(cli_rpc.cli, buffer, CLI_READ_BUFFER_SIZE, 50); - if(!cli_is_connected(cli_rpc.cli) || cli_rpc.session_close_request) { + size_t to_receive = CLAMP(pipe_bytes_available(cli_rpc.pipe), CLI_READ_BUFFER_SIZE, 1UL); + size_received = pipe_receive(cli_rpc.pipe, buffer, to_receive); + if(size_received < to_receive || cli_rpc.session_close_request) { break; } diff --git a/applications/services/rpc/rpc_gpio.c b/applications/services/rpc/rpc_gpio.c index 40fc898a0..d05783afc 100644 --- a/applications/services/rpc/rpc_gpio.c +++ b/applications/services/rpc/rpc_gpio.c @@ -4,6 +4,7 @@ #include #include #include +#include static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) { switch(rpc_pin) { @@ -218,12 +219,16 @@ void rpc_system_gpio_set_otg_mode(const PB_Main* request, void* context) { const PB_Gpio_GpioOtgMode mode = request->content.gpio_set_otg_mode.mode; + Power* power = furi_record_open(RECORD_POWER); + if(mode == PB_Gpio_GpioOtgMode_OFF) { - furi_hal_power_disable_otg(); + power_enable_otg(power, false); } else { - furi_hal_power_enable_otg(); + power_enable_otg(power, true); } + furi_record_close(RECORD_POWER); + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); } diff --git a/applications/services/rpc/rpc_i.h b/applications/services/rpc/rpc_i.h index 0342df2b6..df1f17de4 100644 --- a/applications/services/rpc/rpc_i.h +++ b/applications/services/rpc/rpc_i.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #ifdef __cplusplus extern "C" { @@ -46,7 +46,7 @@ void rpc_desktop_free(void* ctx); void rpc_debug_print_message(const PB_Main* message); void rpc_debug_print_data(const char* prefix, uint8_t* buffer, size_t size); -void rpc_cli_command_start_session(Cli* cli, FuriString* args, void* context); +void rpc_cli_command_start_session(PipeSide* pipe, FuriString* args, void* context); PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error); diff --git a/applications/services/storage/application.fam b/applications/services/storage/application.fam index 7aa721cc3..047500fa3 100644 --- a/applications/services/storage/application.fam +++ b/applications/services/storage/application.fam @@ -16,5 +16,5 @@ App( apptype=FlipperAppType.STARTUP, entry_point="storage_on_system_start", requires=["storage"], - order=90, + order=60, ) diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 441b58da6..2dab63e53 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -1,7 +1,9 @@ #include #include -#include +#include +#include +#include #include #include #include @@ -10,6 +12,7 @@ #include #include #include +#include #define MAX_NAME_LENGTH 255 @@ -19,8 +22,8 @@ static void storage_cli_print_error(FS_Error error) { printf("Storage error: %s\r\n", storage_error_get_desc(error)); } -static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_info(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -69,13 +72,14 @@ static void storage_cli_info(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_format(Cli* cli, FuriString* path, FuriString* args) { +static void storage_cli_format(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); if(furi_string_cmp_str(path, STORAGE_INT_PATH_PREFIX) == 0) { storage_cli_print_error(FSE_NOT_IMPLEMENTED); } else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) { printf("Formatting SD card, All data will be lost! Are you sure (y/n)?\r\n"); - char answer = cli_getc(cli); + char answer = getchar(); if(answer == 'y' || answer == 'Y') { Storage* api = furi_record_open(RECORD_STORAGE); printf("Formatting, please wait...\r\n"); @@ -96,8 +100,8 @@ static void storage_cli_format(Cli* cli, FuriString* path, FuriString* args) { } } -static void storage_cli_list(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_list(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); if(furi_string_cmp_str(path, "/") == 0) { printf("\t[D] int\r\n"); @@ -134,13 +138,13 @@ static void storage_cli_list(Cli* cli, FuriString* path, FuriString* args) { } } -static void storage_cli_tree(Cli* cli, FuriString* path, FuriString* args) { +static void storage_cli_tree(PipeSide* pipe, FuriString* path, FuriString* args) { UNUSED(args); if(furi_string_cmp_str(path, "/") == 0) { furi_string_set(path, STORAGE_INT_PATH_PREFIX); - storage_cli_tree(cli, path, NULL); + storage_cli_tree(pipe, path, NULL); furi_string_set(path, STORAGE_EXT_PATH_PREFIX); - storage_cli_tree(cli, path, NULL); + storage_cli_tree(pipe, path, NULL); } else { Storage* api = furi_record_open(RECORD_STORAGE); DirWalk* dir_walk = dir_walk_alloc(api); @@ -176,8 +180,8 @@ static void storage_cli_tree(Cli* cli, FuriString* path, FuriString* args) { } } -static void storage_cli_read(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_read(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -208,7 +212,8 @@ static void storage_cli_read(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) { +static void storage_cli_write(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -222,9 +227,9 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) { uint32_t read_index = 0; while(true) { - uint8_t symbol = cli_getc(cli); + uint8_t symbol = getchar(); - if(symbol == CliSymbolAsciiETX) { + if(symbol == CliKeyETX) { size_t write_size = read_index % buffer_size; if(write_size > 0) { @@ -263,7 +268,8 @@ static void storage_cli_write(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args) { +static void storage_cli_read_chunks(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -280,7 +286,7 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args uint8_t* data = malloc(buffer_size); while(file_size > 0) { printf("\r\nReady?\r\n"); - cli_getc(cli); + getchar(); size_t read_size = storage_file_read(file, data, buffer_size); for(size_t i = 0; i < read_size; i++) { @@ -302,31 +308,34 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args furi_record_close(RECORD_STORAGE); } -static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args) { +static void storage_cli_write_chunk(PipeSide* pipe, FuriString* path, FuriString* args) { Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); - uint32_t buffer_size; - if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) != + uint32_t need_to_read; + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &need_to_read, 10) != StrintParseNoError) { storage_cli_print_usage(); } else { if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { printf("Ready\r\n"); + const size_t buffer_size = 1024; + uint8_t* buffer = malloc(buffer_size); - if(buffer_size) { - uint8_t* buffer = malloc(buffer_size); + while(need_to_read) { + size_t to_read_this_time = MIN(buffer_size, need_to_read); + size_t read_this_time = pipe_receive(pipe, buffer, to_read_this_time); + if(read_this_time != to_read_this_time) break; - size_t read_bytes = cli_read(cli, buffer, buffer_size); - - size_t written_size = storage_file_write(file, buffer, read_bytes); - - if(written_size != buffer_size) { + size_t wrote_this_time = storage_file_write(file, buffer, read_this_time); + if(wrote_this_time != read_this_time) { storage_cli_print_error(storage_file_get_error(file)); + break; } - - free(buffer); + need_to_read -= read_this_time; } + + free(buffer); } else { storage_cli_print_error(storage_file_get_error(file)); } @@ -337,8 +346,8 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args furi_record_close(RECORD_STORAGE); } -static void storage_cli_stat(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_stat(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -379,8 +388,8 @@ static void storage_cli_stat(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_timestamp(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_timestamp(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); @@ -396,8 +405,8 @@ static void storage_cli_timestamp(Cli* cli, FuriString* path, FuriString* args) furi_record_close(RECORD_STORAGE); } -static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) { - UNUSED(cli); +static void storage_cli_copy(PipeSide* pipe, FuriString* old_path, FuriString* args) { + UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); FuriString* new_path; new_path = furi_string_alloc(); @@ -417,8 +426,8 @@ static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_remove(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_remove(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); FS_Error error = storage_common_remove(api, furi_string_get_cstr(path)); @@ -430,8 +439,8 @@ static void storage_cli_remove(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args) { - UNUSED(cli); +static void storage_cli_rename(PipeSide* pipe, FuriString* old_path, FuriString* args) { + UNUSED(pipe); Storage* api = furi_record_open(RECORD_STORAGE); FuriString* new_path; new_path = furi_string_alloc(); @@ -451,8 +460,8 @@ static void storage_cli_rename(Cli* cli, FuriString* old_path, FuriString* args) furi_record_close(RECORD_STORAGE); } -static void storage_cli_mkdir(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_mkdir(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); FS_Error error = storage_common_mkdir(api, furi_string_get_cstr(path)); @@ -464,8 +473,8 @@ static void storage_cli_mkdir(Cli* cli, FuriString* path, FuriString* args) { furi_record_close(RECORD_STORAGE); } -static void storage_cli_md5(Cli* cli, FuriString* path, FuriString* args) { - UNUSED(cli); +static void storage_cli_md5(PipeSide* pipe, FuriString* path, FuriString* args) { + UNUSED(pipe); UNUSED(args); Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -491,8 +500,8 @@ static bool tar_extract_file_callback(const char* name, bool is_directory, void* return true; } -static void storage_cli_extract(Cli* cli, FuriString* old_path, FuriString* args) { - UNUSED(cli); +static void storage_cli_extract(PipeSide* pipe, FuriString* old_path, FuriString* args) { + UNUSED(pipe); FuriString* new_path = furi_string_alloc(); if(!args_read_probably_quoted_string_and_trim(args, new_path)) { @@ -526,7 +535,7 @@ static void storage_cli_extract(Cli* cli, FuriString* old_path, FuriString* args furi_record_close(RECORD_STORAGE); } -typedef void (*StorageCliCommandCallback)(Cli* cli, FuriString* path, FuriString* args); +typedef void (*StorageCliCommandCallback)(PipeSide* pipe, FuriString* path, FuriString* args); typedef struct { const char* command; @@ -631,7 +640,7 @@ static void storage_cli_print_usage(void) { } } -void storage_cli(Cli* cli, FuriString* args, void* context) { +void storage_cli(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); FuriString* cmd; FuriString* path; @@ -653,7 +662,7 @@ void storage_cli(Cli* cli, FuriString* args, void* context) { for(; i < COUNT_OF(storage_cli_commands); ++i) { const StorageCliCommand* command_descr = &storage_cli_commands[i]; if(furi_string_cmp_str(cmd, command_descr->command) == 0) { - command_descr->impl(cli, path, args); + command_descr->impl(pipe, path, args); break; } } @@ -667,11 +676,12 @@ void storage_cli(Cli* cli, FuriString* args, void* context) { furi_string_free(cmd); } -static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context) { +static void storage_cli_factory_reset(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(args); UNUSED(context); printf("All data will be lost! Are you sure (y/n)?\r\n"); - char c = cli_getc(cli); + char c = getchar(); if(c == 'y' || c == 'Y') { printf("Data will be wiped after reboot.\r\n"); @@ -687,10 +697,15 @@ static void storage_cli_factory_reset(Cli* cli, FuriString* args, void* context) void storage_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, RECORD_STORAGE, CliCommandFlagParallelSafe, storage_cli, NULL); - cli_add_command( - cli, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command( + registry, + "storage", + CliCommandFlagParallelSafe | CliCommandFlagUseShellThread, + storage_cli, + NULL); + cli_registry_add_command( + registry, "factory_reset", CliCommandFlagParallelSafe, storage_cli_factory_reset, NULL); furi_record_close(RECORD_CLI); #else UNUSED(storage_cli_factory_reset); diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index ada2bfdd4..2462b32bd 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -20,8 +20,11 @@ static const NotificationSequence sequence_note_c = { NULL, }; -#define CONTRAST_COUNT 11 +#define CONTRAST_COUNT 17 const char* const contrast_text[CONTRAST_COUNT] = { + "-8", + "-7", + "-6", "-5", "-4", "-3", @@ -33,8 +36,14 @@ const char* const contrast_text[CONTRAST_COUNT] = { "+3", "+4", "+5", + "+6", + "+7", + "+8", }; const int32_t contrast_value[CONTRAST_COUNT] = { + -8, + -7, + -6, -5, -4, -3, @@ -46,44 +55,47 @@ const int32_t contrast_value[CONTRAST_COUNT] = { 3, 4, 5, + 6, + 7, + 8, }; -#define BACKLIGHT_COUNT 5 +#define BACKLIGHT_COUNT 21 const char* const backlight_text[BACKLIGHT_COUNT] = { - "0%", - "25%", - "50%", - "75%", - "100%", + "0%", "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", + "55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%", }; const float backlight_value[BACKLIGHT_COUNT] = { - 0.0f, - 0.25f, - 0.5f, - 0.75f, - 1.0f, + 0.00f, 0.05f, 0.10f, 0.15f, 0.20f, 0.25f, 0.30f, 0.35f, 0.40f, 0.45f, 0.50f, + 0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f, }; -#define VOLUME_COUNT 5 +#define VOLUME_COUNT 21 const char* const volume_text[VOLUME_COUNT] = { - "0%", - "25%", - "50%", - "75%", - "100%", + "0%", "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", + "55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%", +}; +const float volume_value[VOLUME_COUNT] = { + 0.00f, 0.05f, 0.10f, 0.15f, 0.20f, 0.25f, 0.30f, 0.35f, 0.40f, 0.45f, 0.50f, + 0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f, }; -const float volume_value[VOLUME_COUNT] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f}; -#define DELAY_COUNT 6 +#define DELAY_COUNT 11 const char* const delay_text[DELAY_COUNT] = { "1s", "5s", + "10s", "15s", "30s", "60s", + "90s", "120s", + "5min", + "10min", + "30min", }; -const uint32_t delay_value[DELAY_COUNT] = {1000, 5000, 15000, 30000, 60000, 120000}; +const uint32_t delay_value[DELAY_COUNT] = + {1000, 5000, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000, 1800000}; #define VIBRO_COUNT 2 const char* const vibro_text[VIBRO_COUNT] = { diff --git a/applications/settings/power_settings_app/power_settings_app.c b/applications/settings/power_settings_app/power_settings_app.c index d43bd4108..6959bdf41 100644 --- a/applications/settings/power_settings_app/power_settings_app.c +++ b/applications/settings/power_settings_app/power_settings_app.c @@ -1,5 +1,16 @@ #include "power_settings_app.h" +const SubmenuSettingsHelperDescriptor settings_helper_descriptor = { + .app_name = "Power", + .options_cnt = 3, + .options = + { + {.name = "Battery Info", .scene_id = PowerSettingsAppSceneBatteryInfo}, + {.name = "Reboot", .scene_id = PowerSettingsAppSceneReboot}, + {.name = "Power OFF", .scene_id = PowerSettingsAppScenePowerOff}, + }, +}; + static bool power_settings_custom_event_callback(void* context, uint32_t event) { furi_assert(context); PowerSettingsApp* app = context; @@ -18,7 +29,7 @@ static void power_settings_tick_event_callback(void* context) { scene_manager_handle_tick_event(app->scene_manager); } -PowerSettingsApp* power_settings_app_alloc(uint32_t first_scene) { +PowerSettingsApp* power_settings_app_alloc(void) { PowerSettingsApp* app = malloc(sizeof(PowerSettingsApp)); // Records @@ -50,13 +61,23 @@ PowerSettingsApp* power_settings_app_alloc(uint32_t first_scene) { view_dispatcher_add_view( app->view_dispatcher, PowerSettingsAppViewDialog, dialog_ex_get_view(app->dialog)); - // Set first scene - scene_manager_next_scene(app->scene_manager, first_scene); + // Helper + app->settings_helper = submenu_settings_helpers_alloc(&settings_helper_descriptor); + submenu_settings_helpers_assign_objects( + app->settings_helper, + app->view_dispatcher, + app->scene_manager, + app->submenu, + PowerSettingsAppViewSubmenu, + PowerSettingsAppSceneStart); + return app; } void power_settings_app_free(PowerSettingsApp* app) { furi_assert(app); + // Helper + submenu_settings_helpers_free(app->settings_helper); // Views view_dispatcher_remove_view(app->view_dispatcher, PowerSettingsAppViewBatteryInfo); battery_info_free(app->batery_info); @@ -74,11 +95,14 @@ void power_settings_app_free(PowerSettingsApp* app) { } int32_t power_settings_app(void* p) { - uint32_t first_scene = PowerSettingsAppSceneStart; - if(p && strlen(p) && !strcmp(p, "off")) { - first_scene = PowerSettingsAppScenePowerOff; + PowerSettingsApp* app = power_settings_app_alloc(); + if(!submenu_settings_helpers_app_start(app->settings_helper, p)) { + uint32_t first_scene = PowerSettingsAppSceneStart; + if(p && strlen(p) && !strcmp(p, "off")) { + first_scene = PowerSettingsAppScenePowerOff; + } + scene_manager_next_scene(app->scene_manager, first_scene); } - PowerSettingsApp* app = power_settings_app_alloc(first_scene); view_dispatcher_run(app->view_dispatcher); power_settings_app_free(app); return 0; diff --git a/applications/settings/power_settings_app/power_settings_app.h b/applications/settings/power_settings_app/power_settings_app.h index e19e7c983..b33d63db2 100644 --- a/applications/settings/power_settings_app/power_settings_app.h +++ b/applications/settings/power_settings_app/power_settings_app.h @@ -14,6 +14,8 @@ #include "scenes/power_settings_scene.h" +#include + typedef struct { Power* power; Gui* gui; @@ -23,6 +25,7 @@ typedef struct { Submenu* submenu; DialogEx* dialog; PowerInfo info; + SubmenuSettingsHelper* settings_helper; } PowerSettingsApp; typedef enum { diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_start.c b/applications/settings/power_settings_app/scenes/power_settings_scene_start.c index 63f123d89..f8a5a7edb 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_start.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_start.c @@ -1,64 +1,16 @@ #include "../power_settings_app.h" -enum PowerSettingsSubmenuIndex { - PowerSettingsSubmenuIndexBatteryInfo, - PowerSettingsSubmenuIndexReboot, - PowerSettingsSubmenuIndexOff, -}; - -static void power_settings_scene_start_submenu_callback(void* context, uint32_t index) { - furi_assert(context); - PowerSettingsApp* app = context; - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - void power_settings_scene_start_on_enter(void* context) { PowerSettingsApp* app = context; - Submenu* submenu = app->submenu; - - submenu_add_item( - submenu, - "Battery Info", - PowerSettingsSubmenuIndexBatteryInfo, - power_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "Reboot", - PowerSettingsSubmenuIndexReboot, - power_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "Power OFF", - PowerSettingsSubmenuIndexOff, - power_settings_scene_start_submenu_callback, - app); - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, PowerSettingsAppSceneStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, PowerSettingsAppViewSubmenu); + submenu_settings_helpers_scene_enter(app->settings_helper); } bool power_settings_scene_start_on_event(void* context, SceneManagerEvent event) { PowerSettingsApp* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == PowerSettingsSubmenuIndexBatteryInfo) { - scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneBatteryInfo); - } else if(event.event == PowerSettingsSubmenuIndexReboot) { - scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneReboot); - } else if(event.event == PowerSettingsSubmenuIndexOff) { - scene_manager_next_scene(app->scene_manager, PowerSettingsAppScenePowerOff); - } - scene_manager_set_scene_state(app->scene_manager, PowerSettingsAppSceneStart, event.event); - consumed = true; - } - return consumed; + return submenu_settings_helpers_scene_event(app->settings_helper, event); } void power_settings_scene_start_on_exit(void* context) { PowerSettingsApp* app = context; - submenu_reset(app->submenu); + submenu_settings_helpers_scene_exit(app->settings_helper); } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c index e351a2ef7..f03474ac1 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_start.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_start.c @@ -1,131 +1,20 @@ #include "../storage_settings.h" -enum StorageSettingsStartSubmenuIndex { - StorageSettingsStartSubmenuIndexInternalInfo, - StorageSettingsStartSubmenuIndexSDInfo, - StorageSettingsStartSubmenuIndexUnmount, - StorageSettingsStartSubmenuIndexFormat, - StorageSettingsStartSubmenuIndexBenchy, - StorageSettingsStartSubmenuIndexFactoryReset -}; - -static void storage_settings_scene_start_submenu_callback(void* context, uint32_t index) { - StorageSettings* app = context; - - view_dispatcher_send_custom_event(app->view_dispatcher, index); -} - void storage_settings_scene_start_on_enter(void* context) { StorageSettings* app = context; - Submenu* submenu = app->submenu; - - submenu_add_item( - submenu, - "About Internal Storage", - StorageSettingsStartSubmenuIndexInternalInfo, - storage_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "About SD Card", - StorageSettingsStartSubmenuIndexSDInfo, - storage_settings_scene_start_submenu_callback, - app); FS_Error sd_status = storage_sd_status(app->fs_api); - if(sd_status != FSE_OK) { - submenu_add_item( - submenu, - "Mount SD Card", - StorageSettingsStartSubmenuIndexUnmount, - storage_settings_scene_start_submenu_callback, - app); - } else { - submenu_add_item( - submenu, - "Unmount SD Card", - StorageSettingsStartSubmenuIndexUnmount, - storage_settings_scene_start_submenu_callback, - app); - } - - submenu_add_item( - submenu, - "Format SD Card", - StorageSettingsStartSubmenuIndexFormat, - storage_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "Benchmark SD Card", - StorageSettingsStartSubmenuIndexBenchy, - storage_settings_scene_start_submenu_callback, - app); - submenu_add_item( - submenu, - "Factory Reset", - StorageSettingsStartSubmenuIndexFactoryReset, - storage_settings_scene_start_submenu_callback, - app); - - submenu_set_selected_item( - submenu, scene_manager_get_scene_state(app->scene_manager, StorageSettingsStart)); - - view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewSubmenu); + app->helper_descriptor->options[STORAGE_SETTINGS_MOUNT_INDEX].name = + (sd_status != FSE_OK) ? "Mount SD Card" : "Unmount SD Card"; + submenu_settings_helpers_scene_enter(app->settings_helper); } bool storage_settings_scene_start_on_event(void* context, SceneManagerEvent event) { StorageSettings* app = context; - bool consumed = false; - - if(event.type == SceneManagerEventTypeCustom) { - switch(event.event) { - case StorageSettingsStartSubmenuIndexSDInfo: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexSDInfo); - scene_manager_next_scene(app->scene_manager, StorageSettingsSDInfo); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexInternalInfo: - scene_manager_set_scene_state( - app->scene_manager, - StorageSettingsStart, - StorageSettingsStartSubmenuIndexInternalInfo); - scene_manager_next_scene(app->scene_manager, StorageSettingsInternalInfo); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexUnmount: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexUnmount); - scene_manager_next_scene(app->scene_manager, StorageSettingsUnmountConfirm); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexFormat: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexFormat); - scene_manager_next_scene(app->scene_manager, StorageSettingsFormatConfirm); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexBenchy: - scene_manager_set_scene_state( - app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexBenchy); - scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmarkConfirm); - consumed = true; - break; - case StorageSettingsStartSubmenuIndexFactoryReset: - scene_manager_set_scene_state( - app->scene_manager, - StorageSettingsStart, - StorageSettingsStartSubmenuIndexFactoryReset); - scene_manager_next_scene(app->scene_manager, StorageSettingsFactoryReset); - consumed = true; - break; - } - } - return consumed; + return submenu_settings_helpers_scene_event(app->settings_helper, event); } void storage_settings_scene_start_on_exit(void* context) { StorageSettings* app = context; - submenu_reset(app->submenu); + submenu_settings_helpers_scene_exit(app->settings_helper); } diff --git a/applications/settings/storage_settings/storage_settings.c b/applications/settings/storage_settings/storage_settings.c index 354632890..07656431c 100644 --- a/applications/settings/storage_settings/storage_settings.c +++ b/applications/settings/storage_settings/storage_settings.c @@ -1,5 +1,19 @@ #include "storage_settings.h" +const SubmenuSettingsHelperDescriptor descriptor_template = { + .app_name = "Storage", + .options_cnt = 6, + .options = + { + {.name = "About Internal Storage", .scene_id = StorageSettingsInternalInfo}, + {.name = "About SD Card", .scene_id = StorageSettingsSDInfo}, + {.name = "Unmount SD Card", .scene_id = StorageSettingsUnmountConfirm}, + {.name = "Format SD Card", .scene_id = StorageSettingsFormatConfirm}, + {.name = "Benchmark SD Card", .scene_id = StorageSettingsBenchmarkConfirm}, + {.name = "Factory Reset", .scene_id = StorageSettingsFactoryReset}, + }, +}; + static bool storage_settings_custom_event_callback(void* context, uint32_t event) { furi_assert(context); StorageSettings* app = context; @@ -40,12 +54,27 @@ static StorageSettings* storage_settings_alloc(void) { view_dispatcher_add_view( app->view_dispatcher, StorageSettingsViewDialogEx, dialog_ex_get_view(app->dialog_ex)); - scene_manager_next_scene(app->scene_manager, StorageSettingsStart); + size_t descriptor_size = + sizeof(SubmenuSettingsHelperDescriptor) + + (descriptor_template.options_cnt * sizeof(SubmenuSettingsHelperOption)); + app->helper_descriptor = malloc(descriptor_size); + memcpy(app->helper_descriptor, &descriptor_template, descriptor_size); + app->settings_helper = submenu_settings_helpers_alloc(app->helper_descriptor); + submenu_settings_helpers_assign_objects( + app->settings_helper, + app->view_dispatcher, + app->scene_manager, + app->submenu, + StorageSettingsViewSubmenu, + StorageSettingsStart); return app; } static void storage_settings_free(StorageSettings* app) { + submenu_settings_helpers_free(app->settings_helper); + free(app->helper_descriptor); + view_dispatcher_remove_view(app->view_dispatcher, StorageSettingsViewSubmenu); submenu_free(app->submenu); @@ -68,6 +97,10 @@ int32_t storage_settings_app(void* p) { UNUSED(p); StorageSettings* app = storage_settings_alloc(); + if(!submenu_settings_helpers_app_start(app->settings_helper, p)) { + scene_manager_next_scene(app->scene_manager, StorageSettingsStart); + } + view_dispatcher_run(app->view_dispatcher); storage_settings_free(app); diff --git a/applications/settings/storage_settings/storage_settings.h b/applications/settings/storage_settings/storage_settings.h index fd841623e..5f4c6404e 100644 --- a/applications/settings/storage_settings/storage_settings.h +++ b/applications/settings/storage_settings/storage_settings.h @@ -16,6 +16,10 @@ #include "scenes/storage_settings_scene.h" +#include + +#define STORAGE_SETTINGS_MOUNT_INDEX 2 + #ifdef __cplusplus extern "C" { #endif @@ -36,6 +40,10 @@ typedef struct { // text FuriString* text_string; + + // helpers + SubmenuSettingsHelperDescriptor* helper_descriptor; + SubmenuSettingsHelper* settings_helper; } StorageSettings; typedef enum { diff --git a/applications/system/hid_app/application.fam b/applications/system/hid_app/application.fam index cc218c31a..aae5ebf9d 100644 --- a/applications/system/hid_app/application.fam +++ b/applications/system/hid_app/application.fam @@ -3,11 +3,11 @@ App( name="Remote", apptype=FlipperAppType.EXTERNAL, entry_point="hid_usb_app", - stack_size=1 * 1024, + stack_size=2 * 1024, sources=["*.c", "!transport_ble.c"], cdefines=["HID_TRANSPORT_USB"], fap_description="Use Flipper as a HID remote control over USB", - fap_version="1.0", + fap_version="1.1", fap_category="USB", fap_icon="hid_usb_10px.png", fap_icon_assets="assets", @@ -20,12 +20,12 @@ App( name="Remote", apptype=FlipperAppType.EXTERNAL, entry_point="hid_ble_app", - stack_size=1 * 1024, + stack_size=2 * 1024, sources=["*.c", "!transport_usb.c"], cdefines=["HID_TRANSPORT_BLE"], fap_libs=["ble_profile"], fap_description="Use Flipper as a HID remote control over Bluetooth", - fap_version="1.0", + fap_version="1.1", fap_category="Bluetooth", fap_icon="hid_ble_10px.png", fap_icon_assets="assets", diff --git a/applications/system/hid_app/views/hid_mouse_clicker.c b/applications/system/hid_app/views/hid_mouse_clicker.c index e289b7179..491f9962b 100644 --- a/applications/system/hid_app/views/hid_mouse_clicker.c +++ b/applications/system/hid_app/views/hid_mouse_clicker.c @@ -124,7 +124,7 @@ static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) { bool consumed = false; bool rate_changed = false; - if(event->type != InputTypeShort && event->type != InputTypeRepeat) { + if(event->type == InputTypePress || event->type == InputTypeRelease) { return false; } diff --git a/applications/system/hid_app/views/hid_tiktok.c b/applications/system/hid_app/views/hid_tiktok.c index f1501027c..f0997f72e 100644 --- a/applications/system/hid_app/views/hid_tiktok.c +++ b/applications/system/hid_app/views/hid_tiktok.c @@ -103,7 +103,10 @@ static void hid_tiktok_reset_cursor(HidTikTok* hid_tiktok) { furi_delay_ms(50); } // Move cursor from the corner - hid_hal_mouse_move(hid_tiktok->hid, 20, 120); + // Actions split for some mobiles to properly process mouse movements + hid_hal_mouse_move(hid_tiktok->hid, 10, 60); + furi_delay_ms(3); + hid_hal_mouse_move(hid_tiktok->hid, 0, 60); furi_delay_ms(50); } @@ -162,29 +165,30 @@ static bool hid_tiktok_input_callback(InputEvent* event, void* context) { consumed = true; } else if(event->type == InputTypeShort) { if(event->key == InputKeyOk) { + // delays adjusted for emulation of a finger tap hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); - furi_delay_ms(50); + furi_delay_ms(25); hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); - furi_delay_ms(50); + furi_delay_ms(75); hid_hal_mouse_press(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); - furi_delay_ms(50); + furi_delay_ms(25); hid_hal_mouse_release(hid_tiktok->hid, HID_MOUSE_BTN_LEFT); consumed = true; } else if(event->key == InputKeyUp) { // Emulate up swipe - hid_hal_mouse_scroll(hid_tiktok->hid, -6); hid_hal_mouse_scroll(hid_tiktok->hid, -12); - hid_hal_mouse_scroll(hid_tiktok->hid, -19); + hid_hal_mouse_scroll(hid_tiktok->hid, -24); + hid_hal_mouse_scroll(hid_tiktok->hid, -38); + hid_hal_mouse_scroll(hid_tiktok->hid, -24); hid_hal_mouse_scroll(hid_tiktok->hid, -12); - hid_hal_mouse_scroll(hid_tiktok->hid, -6); consumed = true; } else if(event->key == InputKeyDown) { // Emulate down swipe - hid_hal_mouse_scroll(hid_tiktok->hid, 6); hid_hal_mouse_scroll(hid_tiktok->hid, 12); - hid_hal_mouse_scroll(hid_tiktok->hid, 19); + hid_hal_mouse_scroll(hid_tiktok->hid, 24); + hid_hal_mouse_scroll(hid_tiktok->hid, 38); + hid_hal_mouse_scroll(hid_tiktok->hid, 24); hid_hal_mouse_scroll(hid_tiktok->hid, 12); - hid_hal_mouse_scroll(hid_tiktok->hid, 6); consumed = true; } else if(event->key == InputKeyBack) { hid_hal_consumer_key_release_all(hid_tiktok->hid); diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 73bdde21e..843ab5543 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -5,12 +5,13 @@ App( entry_point="js_app", stack_size=2 * 1024, resources="examples", - order=0, + order=10, provides=["js_app_start"], sources=[ "js_app.c", "js_modules.c", "js_thread.c", + "js_value.c", "plugin_api/app_api_table.cpp", "views/console_view.c", "modules/js_flipper.c", @@ -22,7 +23,7 @@ App( appid="js_app_start", apptype=FlipperAppType.STARTUP, entry_point="js_app_on_system_start", - order=160, + order=110, sources=["js_app.c"], ) @@ -110,6 +111,23 @@ App( fap_libs=["assets"], ) +App( + appid="js_gui__widget", + apptype=FlipperAppType.PLUGIN, + entry_point="js_view_widget_ep", + requires=["js_app"], + sources=["modules/js_gui/widget.c"], +) + +App( + appid="js_gui__icon", + apptype=FlipperAppType.PLUGIN, + entry_point="js_gui_icon_ep", + requires=["js_app"], + sources=["modules/js_gui/icon.c"], + fap_libs=["assets"], +) + App( appid="js_notification", apptype=FlipperAppType.PLUGIN, diff --git a/applications/system/js_app/examples/apps/Scripts/gpio.js b/applications/system/js_app/examples/apps/Scripts/gpio.js index 24d0f0286..6ea0a948f 100644 --- a/applications/system/js_app/examples/apps/Scripts/gpio.js +++ b/applications/system/js_app/examples/apps/Scripts/gpio.js @@ -3,6 +3,7 @@ let gpio = require("gpio"); // initialize pins let led = gpio.get("pc3"); // same as `gpio.get(7)` +let led2 = gpio.get("pa7"); // same as `gpio.get(2)` let pot = gpio.get("pc0"); // same as `gpio.get(16)` let button = gpio.get("pc1"); // same as `gpio.get(15)` led.init({ direction: "out", outMode: "push_pull" }); @@ -16,6 +17,13 @@ eventLoop.subscribe(eventLoop.timer("periodic", 1000), function (_, _item, led, return [led, !state]; }, led, true); +// cycle led pwm +print("Commencing PWM (PA7)"); +eventLoop.subscribe(eventLoop.timer("periodic", 10), function (_, _item, led2, state) { + led2.pwmWrite(10000, state); + return [led2, (state + 1) % 101]; +}, led2, 0); + // read potentiometer when button is pressed print("Press the button (PC1)"); eventLoop.subscribe(button.interrupt(), function (_, _item, pot) { diff --git a/applications/system/js_app/examples/apps/Scripts/gui.js b/applications/system/js_app/examples/apps/Scripts/gui.js index a1e023853..bc63a7ef6 100644 --- a/applications/system/js_app/examples/apps/Scripts/gui.js +++ b/applications/system/js_app/examples/apps/Scripts/gui.js @@ -9,8 +9,23 @@ let byteInputView = require("gui/byte_input"); let textBoxView = require("gui/text_box"); let dialogView = require("gui/dialog"); let filePicker = require("gui/file_picker"); +let widget = require("gui/widget"); +let icon = require("gui/icon"); let flipper = require("flipper"); +// declare clock widget children +let cuteDolphinWithWatch = icon.getBuiltin("DolphinWait_59x54"); +let jsLogo = icon.getBuiltin("js_script_10px"); +let stopwatchWidgetElements = [ + { element: "string", x: 67, y: 44, align: "bl", font: "big_numbers", text: "00 00" }, + { element: "string", x: 77, y: 22, align: "bl", font: "primary", text: "Stopwatch" }, + { element: "rect", x: 64, y: 27, w: 28, h: 20, radius: 3, fill: false }, + { element: "rect", x: 100, y: 27, w: 28, h: 20, radius: 3, fill: false }, + { element: "icon", x: 0, y: 5, iconData: cuteDolphinWithWatch }, + { element: "icon", x: 64, y: 13, iconData: jsLogo }, + { element: "button", button: "right", text: "Back" }, +]; + // declare view instances let views = { loading: loadingView.make(), @@ -31,6 +46,7 @@ let views = { longText: textBoxView.makeWith({ text: "This is a very long string that demonstrates the TextBox view. Use the D-Pad to scroll backwards and forwards.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rhoncus est malesuada quam egestas ultrices. Maecenas non eros a nulla eleifend vulputate et ut risus. Quisque in mauris mattis, venenatis risus eget, aliquam diam. Fusce pretium feugiat mauris, ut faucibus ex volutpat in. Phasellus volutpat ex sed gravida consectetur. Aliquam sed lectus feugiat, tristique lectus et, bibendum lacus. Ut sit amet augue eu sapien elementum aliquam quis vitae tortor. Vestibulum quis commodo odio. In elementum fermentum massa, eu pellentesque nibh cursus at. Integer eleifend lacus nec purus elementum sodales. Nulla elementum neque urna, non vulputate massa semper sed. Fusce ut nisi vitae dui blandit congue pretium vitae turpis.", }), + stopwatchWidget: widget.makeWith({}, stopwatchWidgetElements), demos: submenuView.makeWith({ header: "Choose a demo", items: [ @@ -40,6 +56,7 @@ let views = { "Byte input", "Text box", "File picker", + "Widget", "Exit app", ], }), @@ -72,6 +89,8 @@ eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, v views.helloDialog.set("center", "Nice!"); gui.viewDispatcher.switchTo(views.helloDialog); } else if (index === 6) { + gui.viewDispatcher.switchTo(views.stopwatchWidget); + } else if (index === 7) { eventLoop.stop(); } }, gui, eventLoop, views); @@ -111,6 +130,31 @@ eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views gui.viewDispatcher.switchTo(views.demos); }, gui, views, eventLoop); +// go to the demo chooser screen when the right key is pressed on the widget screen +eventLoop.subscribe(views.stopwatchWidget.button, function (_sub, buttonId, gui, views) { + if (buttonId === "right") + gui.viewDispatcher.switchTo(views.demos); +}, gui, views); + +// count time +eventLoop.subscribe(eventLoop.timer("periodic", 500), function (_sub, _item, views, stopwatchWidgetElements, halfSeconds) { + let text = (halfSeconds / 2 / 60).toString(); + if (halfSeconds < 10 * 60 * 2) + text = "0" + text; + + text += (halfSeconds % 2 === 0) ? ":" : " "; + + if (((halfSeconds / 2) % 60) < 10) + text += "0"; + text += ((halfSeconds / 2) % 60).toString(); + + stopwatchWidgetElements[0].text = text; + views.stopwatchWidget.setChildren(stopwatchWidgetElements); + + halfSeconds++; + return [views, stopwatchWidgetElements, halfSeconds]; +}, views, stopwatchWidgetElements, 0); + // run UI gui.viewDispatcher.switchTo(views.demos); eventLoop.run(); diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo_8e1.js b/applications/system/js_app/examples/apps/Scripts/uart_echo_8e1.js new file mode 100644 index 000000000..171bb4637 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo_8e1.js @@ -0,0 +1,15 @@ +// This script is like uart_echo, except it uses 8E1 framing (8 data bits, even +// parity, 1 stop bit) as opposed to the default 8N1 (8 data bits, no parity, +// 1 stop bit) + +let serial = require("serial"); +serial.setup("usart", 230400, { dataBits: "8", parity: "even", stopBits: "1" }); + +while (1) { + let rx_data = serial.readBytes(1, 1000); + if (rx_data !== undefined) { + serial.write(rx_data); + let data_view = Uint8Array(rx_data); + print("0x" + data_view[0].toString(16)); + } +} diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c index c321150df..3084b9b2a 100644 --- a/applications/system/js_app/js_app.c +++ b/applications/system/js_app/js_app.c @@ -4,7 +4,9 @@ #include "js_app_i.h" #include #include -#include +#include +#include +#include #define TAG "JS app" @@ -131,12 +133,14 @@ int32_t js_app(void* arg) { } //-V773 typedef struct { - Cli* cli; + PipeSide* pipe; FuriSemaphore* exit_sem; } JsCliContext; static void js_cli_print(JsCliContext* ctx, const char* msg) { - cli_write(ctx->cli, (uint8_t*)msg, strlen(msg)); + UNUSED(ctx); + UNUSED(msg); + pipe_send(ctx->pipe, msg, strlen(msg)); } static void js_cli_exit(JsCliContext* ctx) { @@ -170,7 +174,7 @@ static void js_cli_callback(JsThreadEvent event, const char* msg, void* context) } } -void js_cli_execute(Cli* cli, FuriString* args, void* context) { +void js_cli_execute(PipeSide* pipe, FuriString* args, void* context) { UNUSED(context); const char* path = furi_string_get_cstr(args); @@ -187,14 +191,14 @@ void js_cli_execute(Cli* cli, FuriString* args, void* context) { break; } - JsCliContext ctx = {.cli = cli}; + JsCliContext ctx = {.pipe = pipe}; ctx.exit_sem = furi_semaphore_alloc(1, 0); printf("Running script %s, press CTRL+C to stop\r\n", path); JsThread* js_thread = js_thread_run(path, js_cli_callback, &ctx); while(furi_semaphore_acquire(ctx.exit_sem, 100) != FuriStatusOk) { - if(cli_cmd_interrupt_received(cli)) break; + if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break; } js_thread_stop(js_thread); @@ -206,8 +210,8 @@ void js_cli_execute(Cli* cli, FuriString* args, void* context) { void js_app_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = furi_record_open(RECORD_CLI); - cli_add_command(cli, "js", CliCommandFlagDefault, js_cli_execute, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command(registry, "js", CliCommandFlagDefault, js_cli_execute, NULL); furi_record_close(RECORD_CLI); #endif } diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index bffa553a8..f9c08058f 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -202,12 +202,15 @@ static JsSdkCompatStatus return JsSdkCompatStatusCompatible; } -#define JS_SDK_COMPAT_ARGS \ - int32_t major, minor; \ - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&major), JS_ARG_INT32(&minor)); +static const JsValueDeclaration js_sdk_version_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE(JsValueTypeInt32), +}; +static const JsValueArguments js_sdk_version_args = JS_VALUE_ARGS(js_sdk_version_arg_list); void js_sdk_compatibility_status(struct mjs* mjs) { - JS_SDK_COMPAT_ARGS; + int32_t major, minor; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor); JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor); switch(status) { case JsSdkCompatStatusCompatible: @@ -223,7 +226,8 @@ void js_sdk_compatibility_status(struct mjs* mjs) { } void js_is_sdk_compatible(struct mjs* mjs) { - JS_SDK_COMPAT_ARGS; + int32_t major, minor; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor); JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor); mjs_return(mjs, mjs_mk_boolean(mjs, status == JsSdkCompatStatusCompatible)); } @@ -246,7 +250,8 @@ static bool js_internal_compat_ask_user(const char* message) { } void js_check_sdk_compatibility(struct mjs* mjs) { - JS_SDK_COMPAT_ARGS; + int32_t major, minor; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_version_args, &major, &minor); JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor); if(status != JsSdkCompatStatusCompatible) { FURI_LOG_E( @@ -267,6 +272,10 @@ void js_check_sdk_compatibility(struct mjs* mjs) { static const char* extra_features[] = { "baseline", // dummy "feature" + "gpio-pwm", + "gui-widget", + "serial-framing", + "gui-widget-extras", }; /** @@ -296,15 +305,20 @@ static bool js_internal_supports_all_of(struct mjs* mjs, mjs_val_t feature_arr) return true; } +static const JsValueDeclaration js_sdk_features_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAnyArray), +}; +static const JsValueArguments js_sdk_features_args = JS_VALUE_ARGS(js_sdk_features_arg_list); + void js_does_sdk_support(struct mjs* mjs) { mjs_val_t features; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_features_args, &features); mjs_return(mjs, mjs_mk_boolean(mjs, js_internal_supports_all_of(mjs, features))); } void js_check_sdk_features(struct mjs* mjs) { mjs_val_t features; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_sdk_features_args, &features); if(!js_internal_supports_all_of(mjs, features)) { FURI_LOG_E(TAG, "Script requests unsupported features"); diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index 1dfd59521..c6f72bbe2 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -2,16 +2,21 @@ #include #include "js_thread_i.h" +#include "js_value.h" #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define PLUGIN_APP_ID "js" #define PLUGIN_API_VERSION 1 #define JS_SDK_VENDOR "flipperdevices" #define JS_SDK_MAJOR 0 -#define JS_SDK_MINOR 1 +#define JS_SDK_MINOR 3 /** * @brief Returns the foreign pointer in `obj["_"]` @@ -25,14 +30,13 @@ /** * @brief Syntax sugar for constructing an object * - * @example - * ```c + * Example: + * * mjs_val_t my_obj = mjs_mk_object(mjs); * JS_ASSIGN_MULTI(mjs, my_obj) { * JS_FIELD("method1", MJS_MK_FN(js_storage_file_is_open)); * JS_FIELD("method2", MJS_MK_FN(js_storage_file_is_open)); * } - * ``` */ #define JS_ASSIGN_MULTI(mjs, object) \ for(struct { \ @@ -64,184 +68,6 @@ typedef enum { JsForeignMagic_JsEventLoopContract, } JsForeignMagic; -// Are you tired of your silly little JS+C glue code functions being 75% -// argument validation code and 25% actual logic? Introducing: ASS (Argument -// Schema for Scripts)! ASS is a set of macros that reduce the typical -// boilerplate code of "check argument count, get arguments, validate arguments, -// extract C values from arguments" down to just one line! - -/** - * When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies - * that the function requires exactly as many arguments as were specified. - */ -#define JS_EXACTLY == -/** - * When passed as the second argument to `JS_FETCH_ARGS_OR_RETURN`, signifies - * that the function requires at least as many arguments as were specified. - */ -#define JS_AT_LEAST >= - -#define JS_ENUM_MAP(var_name, ...) \ - static const JsEnumMapping var_name##_mapping[] = { \ - {NULL, sizeof(var_name)}, \ - __VA_ARGS__, \ - {NULL, 0}, \ - }; - -typedef struct { - const char* name; - size_t value; -} JsEnumMapping; - -typedef struct { - void* out; - int (*validator)(mjs_val_t); - void (*converter)(struct mjs*, mjs_val_t*, void* out, const void* extra); - const char* expected_type; - bool (*extended_validator)(struct mjs*, mjs_val_t, const void* extra); - const void* extra_data; -} _js_arg_decl; - -static inline void _js_to_int32(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { - UNUSED(extra); - *(int32_t*)out = mjs_get_int32(mjs, *in); -} -#define JS_ARG_INT32(out) ((_js_arg_decl){out, mjs_is_number, _js_to_int32, "number", NULL, NULL}) - -static inline void _js_to_ptr(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { - UNUSED(extra); - *(void**)out = mjs_get_ptr(mjs, *in); -} -#define JS_ARG_PTR(out) \ - ((_js_arg_decl){out, mjs_is_foreign, _js_to_ptr, "opaque pointer", NULL, NULL}) - -static inline void _js_to_string(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { - UNUSED(extra); - *(const char**)out = mjs_get_string(mjs, in, NULL); -} -#define JS_ARG_STR(out) ((_js_arg_decl){out, mjs_is_string, _js_to_string, "string", NULL, NULL}) - -static inline void _js_to_bool(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { - UNUSED(extra); - *(bool*)out = !!mjs_get_bool(mjs, *in); -} -#define JS_ARG_BOOL(out) ((_js_arg_decl){out, mjs_is_boolean, _js_to_bool, "boolean", NULL, NULL}) - -static inline void _js_passthrough(struct mjs* mjs, mjs_val_t* in, void* out, const void* extra) { - UNUSED(extra); - UNUSED(mjs); - *(mjs_val_t*)out = *in; -} -#define JS_ARG_ANY(out) ((_js_arg_decl){out, NULL, _js_passthrough, "any", NULL, NULL}) -#define JS_ARG_OBJ(out) ((_js_arg_decl){out, mjs_is_object, _js_passthrough, "any", NULL, NULL}) -#define JS_ARG_FN(out) \ - ((_js_arg_decl){out, mjs_is_function, _js_passthrough, "function", NULL, NULL}) -#define JS_ARG_ARR(out) ((_js_arg_decl){out, mjs_is_array, _js_passthrough, "array", NULL, NULL}) - -static inline bool _js_validate_struct(struct mjs* mjs, mjs_val_t val, const void* extra) { - JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra; - JsForeignMagic struct_magic = *(JsForeignMagic*)mjs_get_ptr(mjs, val); - return struct_magic == expected_magic; -} -#define JS_ARG_STRUCT(type, out) \ - ((_js_arg_decl){ \ - out, \ - mjs_is_foreign, \ - _js_to_ptr, \ - #type, \ - _js_validate_struct, \ - (void*)JsForeignMagic##_##type}) - -static inline bool _js_validate_obj_w_struct(struct mjs* mjs, mjs_val_t val, const void* extra) { - JsForeignMagic expected_magic = (JsForeignMagic)(size_t)extra; - JsForeignMagic struct_magic = *(JsForeignMagic*)JS_GET_INST(mjs, val); - return struct_magic == expected_magic; -} -#define JS_ARG_OBJ_WITH_STRUCT(type, out) \ - ((_js_arg_decl){ \ - out, \ - mjs_is_object, \ - _js_passthrough, \ - #type, \ - _js_validate_obj_w_struct, \ - (void*)JsForeignMagic##_##type}) - -static inline bool _js_validate_enum(struct mjs* mjs, mjs_val_t val, const void* extra) { - for(const JsEnumMapping* mapping = (JsEnumMapping*)extra + 1; mapping->name; mapping++) - if(strcmp(mapping->name, mjs_get_string(mjs, &val, NULL)) == 0) return true; - return false; -} -static inline void - _js_convert_enum(struct mjs* mjs, mjs_val_t* val, void* out, const void* extra) { - const JsEnumMapping* mapping = (JsEnumMapping*)extra; - size_t size = mapping->value; // get enum size from first entry - for(mapping++; mapping->name; mapping++) { - if(strcmp(mapping->name, mjs_get_string(mjs, val, NULL)) == 0) { - if(size == 1) - *(uint8_t*)out = mapping->value; - else if(size == 2) - *(uint16_t*)out = mapping->value; - else if(size == 4) - *(uint32_t*)out = mapping->value; - else if(size == 8) - *(uint64_t*)out = mapping->value; - return; - } - } - // unreachable, thanks to _js_validate_enum -} -#define JS_ARG_ENUM(var_name, name) \ - ((_js_arg_decl){ \ - &var_name, \ - mjs_is_string, \ - _js_convert_enum, \ - name " enum", \ - _js_validate_enum, \ - var_name##_mapping}) - -//-V:JS_FETCH_ARGS_OR_RETURN:1008 -/** - * @brief Fetches and validates the arguments passed to a JS function - * - * Example: `int32_t my_arg; JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&my_arg));` - * - * @warning This macro executes `return;` by design in case of an argument count - * mismatch or a validation failure - */ -#define JS_FETCH_ARGS_OR_RETURN(mjs, arg_operator, ...) \ - _js_arg_decl _js_args[] = {__VA_ARGS__}; \ - int _js_arg_cnt = COUNT_OF(_js_args); \ - mjs_val_t _js_arg_vals[_js_arg_cnt]; \ - if(!(mjs_nargs(mjs) arg_operator _js_arg_cnt)) \ - JS_ERROR_AND_RETURN( \ - mjs, \ - MJS_BAD_ARGS_ERROR, \ - "expected %s%d arguments, got %d", \ - #arg_operator, \ - _js_arg_cnt, \ - mjs_nargs(mjs)); \ - for(int _i = 0; _i < _js_arg_cnt; _i++) { \ - _js_arg_vals[_i] = mjs_arg(mjs, _i); \ - if(_js_args[_i].validator) \ - if(!_js_args[_i].validator(_js_arg_vals[_i])) \ - JS_ERROR_AND_RETURN( \ - mjs, \ - MJS_BAD_ARGS_ERROR, \ - "argument %d: expected %s", \ - _i, \ - _js_args[_i].expected_type); \ - if(_js_args[_i].extended_validator) \ - if(!_js_args[_i].extended_validator(mjs, _js_arg_vals[_i], _js_args[_i].extra_data)) \ - JS_ERROR_AND_RETURN( \ - mjs, \ - MJS_BAD_ARGS_ERROR, \ - "argument %d: expected %s", \ - _i, \ - _js_args[_i].expected_type); \ - _js_args[_i].converter( \ - mjs, &_js_arg_vals[_i], _js_args[_i].out, _js_args[_i].extra_data); \ - } - /** * @brief Prepends an error, sets the JS return value to `undefined` and returns * from the C function @@ -254,6 +80,18 @@ static inline void return; \ } while(0) +/** + * @brief Prepends an error, sets the JS return value to `undefined` and returns + * a value C function + * @warning This macro executes `return;` by design + */ +#define JS_ERROR_AND_RETURN_VAL(mjs, error_code, ret_val, ...) \ + do { \ + mjs_prepend_errorf(mjs, error_code, __VA_ARGS__); \ + mjs_return(mjs, MJS_UNDEFINED); \ + return ret_val; \ + } while(0) + typedef struct JsModules JsModules; typedef void* (*JsModuleConstructor)(struct mjs* mjs, mjs_val_t* object, JsModules* modules); @@ -304,3 +142,7 @@ void js_does_sdk_support(struct mjs* mjs); * @brief `checkSdkFeatures` function */ void js_check_sdk_features(struct mjs* mjs); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 600c2676e..a41a28d11 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -92,7 +92,7 @@ static void js_console_debug(struct mjs* mjs) { } static void js_exit_flag_poll(struct mjs* mjs) { - uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, 0); + uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0); if(flags & FuriFlagError) { return; } @@ -102,7 +102,8 @@ static void js_exit_flag_poll(struct mjs* mjs) { } bool js_delay_with_flags(struct mjs* mjs, uint32_t time) { - uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, time); + uint32_t flags = + furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, time); if(flags & FuriFlagError) { return false; } @@ -124,7 +125,7 @@ uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags_mask, uint32_t timeout) { uint32_t flags = furi_thread_flags_get(); furi_check((flags & FuriFlagError) == 0); if(flags == 0) { - flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); + flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny | FuriFlagNoClear, timeout); } else { uint32_t state = furi_thread_flags_clear(flags & flags_mask); furi_check((state & FuriFlagError) == 0); @@ -197,18 +198,15 @@ static void js_require(struct mjs* mjs) { } static void js_parse_int(struct mjs* mjs) { - const char* str; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_AT_LEAST, JS_ARG_STR(&str)); + static const JsValueDeclaration js_parse_int_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 10), + }; + static const JsValueArguments js_parse_int_args = JS_VALUE_ARGS(js_parse_int_arg_list); - int32_t base = 10; - if(mjs_nargs(mjs) >= 2) { - mjs_val_t base_arg = mjs_arg(mjs, 1); - if(!mjs_is_number(base_arg)) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "Base must be a number"); - mjs_return(mjs, MJS_UNDEFINED); - } - base = mjs_get_int(mjs, base_arg); - } + const char* str; + int32_t base; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_parse_int_args, &str, &base); int32_t num; if(strint_to_int32(str, NULL, &num, base) != StrintParseNoError) { diff --git a/applications/system/js_app/js_thread_i.h b/applications/system/js_app/js_thread_i.h index a73cbb4bc..5fbdb06d0 100644 --- a/applications/system/js_app/js_thread_i.h +++ b/applications/system/js_app/js_thread_i.h @@ -11,6 +11,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define INST_PROP_NAME "_" typedef enum { @@ -23,3 +27,7 @@ bool js_delay_with_flags(struct mjs* mjs, uint32_t time); void js_flags_set(struct mjs* mjs, uint32_t flags); uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/js_app/js_value.c b/applications/system/js_app/js_value.c new file mode 100644 index 000000000..6ce1cf37a --- /dev/null +++ b/applications/system/js_app/js_value.c @@ -0,0 +1,291 @@ +#include "js_value.h" +#include + +#ifdef APP_UNIT_TESTS +#define JS_VAL_DEBUG +#endif + +size_t js_value_buffer_size(const JsValueParseDeclaration declaration) { + if(declaration.source == JsValueParseSourceValue) { + const JsValueDeclaration* value_decl = declaration.value_decl; + JsValueType type = value_decl->type & JsValueTypeMask; + + if(type == JsValueTypeString) return 1; + + if(type == JsValueTypeObject) { + size_t total = 0; + for(size_t i = 0; i < value_decl->n_children; i++) + total += js_value_buffer_size( + JS_VALUE_PARSE_SOURCE_VALUE(value_decl->object_fields[i].value)); + return total; + } + + return 0; + + } else { + const JsValueArguments* arg_decl = declaration.argument_decl; + size_t total = 0; + for(size_t i = 0; i < arg_decl->n_children; i++) + total += js_value_buffer_size(JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i])); + return total; + } +} + +static size_t js_value_resulting_c_values_count(const JsValueParseDeclaration declaration) { + if(declaration.source == JsValueParseSourceValue) { + const JsValueDeclaration* value_decl = declaration.value_decl; + JsValueType type = value_decl->type & JsValueTypeMask; + + if(type == JsValueTypeObject) { + size_t total = 0; + for(size_t i = 0; i < value_decl->n_children; i++) + total += js_value_resulting_c_values_count( + JS_VALUE_PARSE_SOURCE_VALUE(value_decl->object_fields[i].value)); + return total; + } + + return 1; + + } else { + const JsValueArguments* arg_decl = declaration.argument_decl; + size_t total = 0; + for(size_t i = 0; i < arg_decl->n_children; i++) + total += js_value_resulting_c_values_count( + JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i])); + return total; + } +} + +#define PREPEND_JS_ERROR_AND_RETURN(mjs, flags, ...) \ + do { \ + if((flags) & JsValueParseFlagReturnOnError) \ + mjs_prepend_errorf((mjs), MJS_BAD_ARGS_ERROR, __VA_ARGS__); \ + return JsValueParseStatusJsError; \ + } while(0) + +#define PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, type) \ + PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "expected %s", type) + +static void js_value_assign_enum_val(void* destination, JsValueType type_w_flags, uint32_t value) { + if(type_w_flags & JsValueTypeEnumSize1) { + *(uint8_t*)destination = value; + } else if(type_w_flags & JsValueTypeEnumSize2) { + *(uint16_t*)destination = value; + } else if(type_w_flags & JsValueTypeEnumSize4) { + *(uint32_t*)destination = value; + } +} + +static bool js_value_is_null_or_undefined(mjs_val_t* val_ptr) { + return mjs_is_null(*val_ptr) || mjs_is_undefined(*val_ptr); +} + +static bool js_value_maybe_assign_default( + const JsValueDeclaration* declaration, + mjs_val_t* val_ptr, + void* destination, + size_t size) { + if((declaration->type & JsValueTypePermitNull) && js_value_is_null_or_undefined(val_ptr)) { + memcpy(destination, &declaration->default_value, size); + return true; + } + return false; +} + +typedef int (*MjsTypecheckFn)(mjs_val_t value); + +static JsValueParseStatus js_value_parse_literal( + struct mjs* mjs, + JsValueParseFlag flags, + mjs_val_t* destination, + mjs_val_t* source, + MjsTypecheckFn typecheck, + const char* type_name) { + if(!typecheck(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, type_name); + *destination = *source; + return JsValueParseStatusOk; +} + +static JsValueParseStatus js_value_parse_va( + struct mjs* mjs, + const JsValueParseDeclaration declaration, + JsValueParseFlag flags, + mjs_val_t* source, + mjs_val_t* buffer, + size_t* buffer_index, + va_list* out_pointers) { + if(declaration.source == JsValueParseSourceArguments) { + const JsValueArguments* arg_decl = declaration.argument_decl; + + for(size_t i = 0; i < arg_decl->n_children; i++) { + mjs_val_t arg_val = mjs_arg(mjs, i); + JsValueParseStatus status = js_value_parse_va( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(&arg_decl->arguments[i]), + flags, + &arg_val, + buffer, + buffer_index, + out_pointers); + if(status != JsValueParseStatusOk) return status; + } + + return JsValueParseStatusOk; + } + + const JsValueDeclaration* value_decl = declaration.value_decl; + JsValueType type_w_flags = value_decl->type; + JsValueType type_noflags = type_w_flags & JsValueTypeMask; + bool is_null_but_allowed = (type_w_flags & JsValueTypePermitNull) && + js_value_is_null_or_undefined(source); + + void* destination = NULL; + if(type_noflags != JsValueTypeObject) destination = va_arg(*out_pointers, void*); + + switch(type_noflags) { + // Literal terms + case JsValueTypeAny: + *(mjs_val_t*)destination = *source; + break; + case JsValueTypeAnyArray: + return js_value_parse_literal(mjs, flags, destination, source, mjs_is_array, "array"); + case JsValueTypeAnyObject: + return js_value_parse_literal(mjs, flags, destination, source, mjs_is_object, "array"); + case JsValueTypeFunction: + return js_value_parse_literal( + mjs, flags, destination, source, mjs_is_function, "function"); + + // Primitive types + case JsValueTypeRawPointer: { + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(void*))) break; + if(!mjs_is_foreign(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "pointer"); + *(void**)destination = mjs_get_ptr(mjs, *source); + break; + } + case JsValueTypeInt32: { + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(int32_t))) break; + if(!mjs_is_number(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "number"); + *(int32_t*)destination = mjs_get_int32(mjs, *source); + break; + } + case JsValueTypeDouble: { + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(double))) break; + if(!mjs_is_number(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "number"); + *(double*)destination = mjs_get_double(mjs, *source); + break; + } + case JsValueTypeBool: { + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(bool))) break; + if(!mjs_is_boolean(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "bool"); + *(bool*)destination = mjs_get_bool(mjs, *source); + break; + } + case JsValueTypeString: { + if(js_value_maybe_assign_default(value_decl, source, destination, sizeof(const char*))) + break; + if(!mjs_is_string(*source)) PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "string"); + buffer[*buffer_index] = *source; + *(const char**)destination = mjs_get_string(mjs, &buffer[*buffer_index], NULL); + (*buffer_index)++; + break; + } + + // Types with children + case JsValueTypeEnum: { + if(is_null_but_allowed) { + js_value_assign_enum_val( + destination, type_w_flags, value_decl->default_value.enum_val); + + } else if(mjs_is_string(*source)) { + const char* str = mjs_get_string(mjs, source, NULL); + furi_check(str); + + bool match_found = false; + for(size_t i = 0; i < value_decl->n_children; i++) { + const JsValueEnumVariant* variant = &value_decl->enum_variants[i]; + if(strcmp(str, variant->string_value) == 0) { + js_value_assign_enum_val(destination, type_w_flags, variant->num_value); + match_found = true; + break; + } + } + + if(!match_found) + PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "one of permitted strings"); + + } else { + PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "string"); + } + break; + } + + case JsValueTypeObject: { + if(!(is_null_but_allowed || mjs_is_object(*source))) + PREPEND_JS_EXPECTED_ERROR_AND_RETURN(mjs, flags, "object"); + for(size_t i = 0; i < value_decl->n_children; i++) { + const JsValueObjectField* field = &value_decl->object_fields[i]; + mjs_val_t field_val = mjs_get(mjs, *source, field->field_name, ~0); + JsValueParseStatus status = js_value_parse_va( + mjs, + JS_VALUE_PARSE_SOURCE_VALUE(field->value), + flags, + &field_val, + buffer, + buffer_index, + out_pointers); + if(status != JsValueParseStatusOk) + PREPEND_JS_ERROR_AND_RETURN(mjs, flags, "field %s: ", field->field_name); + } + break; + } + + case JsValueTypeMask: + case JsValueTypeEnumSize1: + case JsValueTypeEnumSize2: + case JsValueTypeEnumSize4: + case JsValueTypePermitNull: + furi_crash(); + } + + return JsValueParseStatusOk; +} + +JsValueParseStatus js_value_parse( + 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, + ...) { + furi_check(mjs); + furi_check(buffer); + + if(declaration.source == JsValueParseSourceValue) { + furi_check(source); + furi_check(declaration.value_decl); + } else { + furi_check(source == NULL); + furi_check(declaration.argument_decl); + } + +#ifdef JS_VAL_DEBUG + furi_check(buf_size == js_value_buffer_size(declaration)); + furi_check(n_c_vals == js_value_resulting_c_values_count(declaration)); +#else + UNUSED(js_value_resulting_c_values_count); +#endif + + va_list out_pointers; + va_start(out_pointers, n_c_vals); + + size_t buffer_index = 0; + JsValueParseStatus status = + js_value_parse_va(mjs, declaration, flags, source, buffer, &buffer_index, &out_pointers); + furi_check(buffer_index <= buf_size); + + va_end(out_pointers); + + return status; +} diff --git a/applications/system/js_app/js_value.h b/applications/system/js_app/js_value.h new file mode 100644 index 000000000..765bcb3bb --- /dev/null +++ b/applications/system/js_app/js_value.h @@ -0,0 +1,212 @@ +#pragma once + +#include +#include "js_modules.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + // literal types + JsValueTypeAny, //mjs, &result, context->callback, @@ -68,6 +63,12 @@ static void js_event_loop_callback_generic(void* param) { context->arity, context->arguments); + bool is_error = strcmp(mjs_strerror(context->mjs, error), "NO_ERROR") != 0; + bool asked_to_stop = js_flags_wait(context->mjs, ThreadEventStop, 0) & ThreadEventStop; + if(is_error || asked_to_stop) { + furi_event_loop_stop(context->event_loop); + } + // save returned args for next call if(mjs_array_length(context->mjs, result) != context->arity - SYSTEM_ARGS) return; for(size_t i = 0; i < context->arity - SYSTEM_ARGS; i++) { @@ -111,11 +112,14 @@ static void js_event_loop_subscription_cancel(struct mjs* mjs) { JsEventLoopSubscription* subscription = JS_GET_CONTEXT(mjs); if(subscription->object_type == JsEventLoopObjectTypeTimer) { + // timer operations are deferred, which creates lifetime issues + // just stop the timer and let the cleanup routine free everything when the script is done furi_event_loop_timer_stop(subscription->object); - } else { - furi_event_loop_unsubscribe(subscription->loop, subscription->object); + return; } + furi_event_loop_unsubscribe(subscription->loop, subscription->object); + free(subscription->context->arguments); free(subscription->context); @@ -140,10 +144,16 @@ static void js_event_loop_subscribe(struct mjs* mjs) { JsEventLoop* module = JS_GET_CONTEXT(mjs); // get arguments + static const JsValueDeclaration js_loop_subscribe_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeRawPointer), + JS_VALUE_SIMPLE(JsValueTypeFunction), + }; + static const JsValueArguments js_loop_subscribe_args = + JS_VALUE_ARGS(js_loop_subscribe_arg_list); + JsEventLoopContract* contract; mjs_val_t callback; - JS_FETCH_ARGS_OR_RETURN( - mjs, JS_AT_LEAST, JS_ARG_STRUCT(JsEventLoopContract, &contract), JS_ARG_FN(&callback)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_subscribe_args, &contract, &callback); // create subscription object JsEventLoopSubscription* subscription = malloc(sizeof(JsEventLoopSubscription)); @@ -158,6 +168,7 @@ static void js_event_loop_subscribe(struct mjs* mjs) { mjs_set(mjs, subscription_obj, "cancel", ~0, MJS_MK_FN(js_event_loop_subscription_cancel)); // create callback context + context->event_loop = module->loop; context->object_type = contract->object_type; context->arity = mjs_nargs(mjs) - SYSTEM_ARGS + 2; context->arguments = calloc(context->arity, sizeof(mjs_val_t)); @@ -237,20 +248,22 @@ static void js_event_loop_stop(struct mjs* mjs) { * event */ static void js_event_loop_timer(struct mjs* mjs) { - // get arguments - const char* mode_str; - int32_t interval; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&mode_str), JS_ARG_INT32(&interval)); - JsEventLoop* module = JS_GET_CONTEXT(mjs); + static const JsValueEnumVariant js_loop_timer_mode_variants[] = { + {"periodic", FuriEventLoopTimerTypePeriodic}, + {"oneshot", FuriEventLoopTimerTypeOnce}, + }; + + static const JsValueDeclaration js_loop_timer_arg_list[] = { + JS_VALUE_ENUM(FuriEventLoopTimerType, js_loop_timer_mode_variants), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_loop_timer_args = JS_VALUE_ARGS(js_loop_timer_arg_list); FuriEventLoopTimerType mode; - if(strcasecmp(mode_str, "periodic") == 0) { - mode = FuriEventLoopTimerTypePeriodic; - } else if(strcasecmp(mode_str, "oneshot") == 0) { - mode = FuriEventLoopTimerTypeOnce; - } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "argument 0: unknown mode"); - } + int32_t interval; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_timer_args, &mode, &interval); + + JsEventLoop* module = JS_GET_CONTEXT(mjs); // make timer contract JsEventLoopContract* contract = malloc(sizeof(JsEventLoopContract)); @@ -288,8 +301,14 @@ static mjs_val_t */ static void js_event_loop_queue_send(struct mjs* mjs) { // get arguments + static const JsValueDeclaration js_loop_q_send_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_loop_q_send_args = JS_VALUE_ARGS(js_loop_q_send_arg_list); + mjs_val_t message; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&message)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_q_send_args, &message); + JsEventLoopContract* contract = JS_GET_CONTEXT(mjs); // send message @@ -306,8 +325,14 @@ static void js_event_loop_queue_send(struct mjs* mjs) { */ static void js_event_loop_queue(struct mjs* mjs) { // get arguments + static const JsValueDeclaration js_loop_q_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_loop_q_args = JS_VALUE_ARGS(js_loop_q_arg_list); + int32_t length; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&length)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_loop_q_args, &length); + JsEventLoop* module = JS_GET_CONTEXT(mjs); // make queue contract @@ -333,37 +358,22 @@ static void js_event_loop_queue(struct mjs* mjs) { mjs_return(mjs, queue); } -static void js_event_loop_tick(void* param) { - JsEventLoopTickContext* context = param; - uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny | FuriFlagNoClear, 0); - if(flags & FuriFlagError) { - return; - } - if(flags & ThreadEventStop) { - furi_event_loop_stop(context->loop); - mjs_exit(context->mjs); - } -} - static void* js_event_loop_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { UNUSED(modules); mjs_val_t event_loop_obj = mjs_mk_object(mjs); JsEventLoop* module = malloc(sizeof(JsEventLoop)); - JsEventLoopTickContext* tick_ctx = malloc(sizeof(JsEventLoopTickContext)); module->loop = furi_event_loop_alloc(); - tick_ctx->loop = module->loop; - tick_ctx->mjs = mjs; - module->tick_context = tick_ctx; - furi_event_loop_tick_set(module->loop, 10, js_event_loop_tick, tick_ctx); SubscriptionArray_init(module->subscriptions); ContractArray_init(module->owned_contracts); - mjs_set(mjs, event_loop_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, module)); - mjs_set(mjs, event_loop_obj, "subscribe", ~0, MJS_MK_FN(js_event_loop_subscribe)); - mjs_set(mjs, event_loop_obj, "run", ~0, MJS_MK_FN(js_event_loop_run)); - mjs_set(mjs, event_loop_obj, "stop", ~0, MJS_MK_FN(js_event_loop_stop)); - mjs_set(mjs, event_loop_obj, "timer", ~0, MJS_MK_FN(js_event_loop_timer)); - mjs_set(mjs, event_loop_obj, "queue", ~0, MJS_MK_FN(js_event_loop_queue)); + JS_ASSIGN_MULTI(mjs, event_loop_obj) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, module)); + JS_FIELD("subscribe", MJS_MK_FN(js_event_loop_subscribe)); + JS_FIELD("run", MJS_MK_FN(js_event_loop_run)); + JS_FIELD("stop", MJS_MK_FN(js_event_loop_stop)); + JS_FIELD("timer", MJS_MK_FN(js_event_loop_timer)); + JS_FIELD("queue", MJS_MK_FN(js_event_loop_queue)); + } *object = event_loop_obj; return module; @@ -418,7 +428,6 @@ static void js_event_loop_destroy(void* inst) { ContractArray_clear(module->owned_contracts); furi_event_loop_free(module->loop); - free(module->tick_context); free(module); } } diff --git a/applications/system/js_app/modules/js_gpio.c b/applications/system/js_app/modules/js_gpio.c index ae3fefd71..63de6900a 100644 --- a/applications/system/js_app/modules/js_gpio.c +++ b/applications/system/js_app/modules/js_gpio.c @@ -1,6 +1,7 @@ #include "../js_modules.h" // IWYU pragma: keep #include "./js_event_loop/js_event_loop.h" #include +#include #include #include #include @@ -17,6 +18,7 @@ typedef struct { FuriSemaphore* interrupt_semaphore; JsEventLoopContract* interrupt_contract; FuriHalAdcChannel adc_channel; + FuriHalPwmOutputId pwm_output; FuriHalAdcHandle* adc_handle; } JsGpioPinInst; @@ -52,83 +54,114 @@ static void js_gpio_int_cb(void* arg) { * ``` */ static void js_gpio_init(struct mjs* mjs) { - // deconstruct mode object - mjs_val_t mode_arg; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&mode_arg)); - mjs_val_t direction_arg = mjs_get(mjs, mode_arg, "direction", ~0); - mjs_val_t out_mode_arg = mjs_get(mjs, mode_arg, "outMode", ~0); - mjs_val_t in_mode_arg = mjs_get(mjs, mode_arg, "inMode", ~0); - mjs_val_t edge_arg = mjs_get(mjs, mode_arg, "edge", ~0); - mjs_val_t pull_arg = mjs_get(mjs, mode_arg, "pull", ~0); + // direction variants + typedef enum { + JsGpioDirectionIn, + JsGpioDirectionOut, + } JsGpioDirection; + static const JsValueEnumVariant js_gpio_direction_variants[] = { + {"in", JsGpioDirectionIn}, + {"out", JsGpioDirectionOut}, + }; + static const JsValueDeclaration js_gpio_direction = + JS_VALUE_ENUM(JsGpioDirection, js_gpio_direction_variants); - // get strings - const char* direction = mjs_get_string(mjs, &direction_arg, NULL); - const char* out_mode = mjs_get_string(mjs, &out_mode_arg, NULL); - const char* in_mode = mjs_get_string(mjs, &in_mode_arg, NULL); - const char* edge = mjs_get_string(mjs, &edge_arg, NULL); - const char* pull = mjs_get_string(mjs, &pull_arg, NULL); - if(!direction) - JS_ERROR_AND_RETURN( - mjs, MJS_BAD_ARGS_ERROR, "Expected string in \"direction\" field of mode object"); - if(!out_mode) out_mode = "open_drain"; - if(!in_mode) in_mode = "plain_digital"; - if(!edge) edge = "rising"; + // inMode variants + typedef enum { + JsGpioInModeAnalog = (0 << 0), + JsGpioInModePlainDigital = (1 << 0), + JsGpioInModeInterrupt = (2 << 0), + JsGpioInModeEvent = (3 << 0), + } JsGpioInMode; + static const JsValueEnumVariant js_gpio_in_mode_variants[] = { + {"analog", JsGpioInModeAnalog}, + {"plain_digital", JsGpioInModePlainDigital}, + {"interrupt", JsGpioInModeInterrupt}, + {"event", JsGpioInModeEvent}, + }; + static const JsValueDeclaration js_gpio_in_mode = + JS_VALUE_ENUM_W_DEFAULT(JsGpioInMode, js_gpio_in_mode_variants, JsGpioInModePlainDigital); + + // outMode variants + typedef enum { + JsGpioOutModePushPull, + JsGpioOutModeOpenDrain, + } JsGpioOutMode; + static const JsValueEnumVariant js_gpio_out_mode_variants[] = { + {"push_pull", JsGpioOutModePushPull}, + {"open_drain", JsGpioOutModeOpenDrain}, + }; + static const JsValueDeclaration js_gpio_out_mode = + JS_VALUE_ENUM_W_DEFAULT(JsGpioOutMode, js_gpio_out_mode_variants, JsGpioOutModeOpenDrain); + + // edge variants + typedef enum { + JsGpioEdgeRising = (0 << 2), + JsGpioEdgeFalling = (1 << 2), + JsGpioEdgeBoth = (2 << 2), + } JsGpioEdge; + static const JsValueEnumVariant js_gpio_edge_variants[] = { + {"rising", JsGpioEdgeRising}, + {"falling", JsGpioEdgeFalling}, + {"both", JsGpioEdgeBoth}, + }; + static const JsValueDeclaration js_gpio_edge = + JS_VALUE_ENUM_W_DEFAULT(JsGpioEdge, js_gpio_edge_variants, JsGpioEdgeRising); + + // pull variants + static const JsValueEnumVariant js_gpio_pull_variants[] = { + {"up", GpioPullUp}, + {"down", GpioPullDown}, + }; + static const JsValueDeclaration js_gpio_pull = + JS_VALUE_ENUM_W_DEFAULT(GpioPull, js_gpio_pull_variants, GpioPullNo); + + // complete mode object + static const JsValueObjectField js_gpio_mode_object_fields[] = { + {"direction", &js_gpio_direction}, + {"inMode", &js_gpio_in_mode}, + {"outMode", &js_gpio_out_mode}, + {"edge", &js_gpio_edge}, + {"pull", &js_gpio_pull}, + }; + + // function args + static const JsValueDeclaration js_gpio_init_arg_list[] = { + JS_VALUE_OBJECT_W_DEFAULTS(js_gpio_mode_object_fields), + }; + static const JsValueArguments js_gpio_init_args = JS_VALUE_ARGS(js_gpio_init_arg_list); + + JsGpioDirection direction; + JsGpioInMode in_mode; + JsGpioOutMode out_mode; + JsGpioEdge edge; + GpioPull pull; + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_gpio_init_args, &direction, &in_mode, &out_mode, &edge, &pull); - // convert strings to mode GpioMode mode; - if(strcmp(direction, "out") == 0) { - if(strcmp(out_mode, "push_pull") == 0) - mode = GpioModeOutputPushPull; - else if(strcmp(out_mode, "open_drain") == 0) - mode = GpioModeOutputOpenDrain; - else - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid outMode"); - } else if(strcmp(direction, "in") == 0) { - if(strcmp(in_mode, "analog") == 0) { - mode = GpioModeAnalog; - } else if(strcmp(in_mode, "plain_digital") == 0) { - mode = GpioModeInput; - } else if(strcmp(in_mode, "interrupt") == 0) { - if(strcmp(edge, "rising") == 0) - mode = GpioModeInterruptRise; - else if(strcmp(edge, "falling") == 0) - mode = GpioModeInterruptFall; - else if(strcmp(edge, "both") == 0) - mode = GpioModeInterruptRiseFall; - else - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); - } else if(strcmp(in_mode, "event") == 0) { - if(strcmp(edge, "rising") == 0) - mode = GpioModeEventRise; - else if(strcmp(edge, "falling") == 0) - mode = GpioModeEventFall; - else if(strcmp(edge, "both") == 0) - mode = GpioModeEventRiseFall; - else - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid edge"); - } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid inMode"); - } + if(direction == JsGpioDirectionOut) { + static const GpioMode js_gpio_out_mode_lut[] = { + [JsGpioOutModePushPull] = GpioModeOutputPushPull, + [JsGpioOutModeOpenDrain] = GpioModeOutputOpenDrain, + }; + mode = js_gpio_out_mode_lut[out_mode]; } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid direction"); + static const GpioMode js_gpio_in_mode_lut[] = { + [JsGpioInModeAnalog] = GpioModeAnalog, + [JsGpioInModePlainDigital] = GpioModeInput, + [JsGpioInModeInterrupt | JsGpioEdgeRising] = GpioModeInterruptRise, + [JsGpioInModeInterrupt | JsGpioEdgeFalling] = GpioModeInterruptFall, + [JsGpioInModeInterrupt | JsGpioEdgeBoth] = GpioModeInterruptRiseFall, + [JsGpioInModeEvent | JsGpioEdgeRising] = GpioModeEventRise, + [JsGpioInModeEvent | JsGpioEdgeFalling] = GpioModeEventFall, + [JsGpioInModeEvent | JsGpioEdgeBoth] = GpioModeEventRiseFall, + }; + mode = js_gpio_in_mode_lut[in_mode | edge]; } - // convert pull - GpioPull pull_mode; - if(!pull) { - pull_mode = GpioPullNo; - } else if(strcmp(pull, "up") == 0) { - pull_mode = GpioPullUp; - } else if(strcmp(pull, "down") == 0) { - pull_mode = GpioPullDown; - } else { - JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "invalid pull"); - } - - // init GPIO JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); - furi_hal_gpio_init(manager_data->pin, mode, pull_mode, GpioSpeedVeryHigh); - mjs_return(mjs, MJS_UNDEFINED); + furi_hal_gpio_init(manager_data->pin, mode, pull, GpioSpeedVeryHigh); } /** @@ -144,8 +177,13 @@ static void js_gpio_init(struct mjs* mjs) { * ``` */ static void js_gpio_write(struct mjs* mjs) { + static const JsValueDeclaration js_gpio_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeBool), + }; + static const JsValueArguments js_gpio_write_args = JS_VALUE_ARGS(js_gpio_write_arg_list); bool level; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_BOOL(&level)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_write_args, &level); + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); furi_hal_gpio_write(manager_data->pin, level); mjs_return(mjs, MJS_UNDEFINED); @@ -231,6 +269,95 @@ static void js_gpio_read_analog(struct mjs* mjs) { mjs_return(mjs, mjs_mk_number(mjs, (double)millivolts)); } +/** + * @brief Determines whether this pin supports PWM + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * assert_eq(true, gpio.get("pa4").isPwmSupported()); + * assert_eq(false, gpio.get("pa5").isPwmSupported()); + * ``` + */ +static void js_gpio_is_pwm_supported(struct mjs* mjs) { + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + mjs_return(mjs, mjs_mk_boolean(mjs, manager_data->pwm_output != FuriHalPwmOutputIdNone)); +} + +/** + * @brief Sets PWM parameters and starts the PWM + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let pa4 = gpio.get("pa4"); + * pa4.pwmWrite(10000, 50); + * ``` + */ +static void js_gpio_pwm_write(struct mjs* mjs) { + static const JsValueDeclaration js_gpio_pwm_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_gpio_pwm_write_args = + JS_VALUE_ARGS(js_gpio_pwm_write_arg_list); + int32_t frequency, duty; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_pwm_write_args, &frequency, &duty); + + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + if(manager_data->pwm_output == FuriHalPwmOutputIdNone) { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin"); + } + + if(furi_hal_pwm_is_running(manager_data->pwm_output)) { + furi_hal_pwm_set_params(manager_data->pwm_output, frequency, duty); + } else { + furi_hal_pwm_start(manager_data->pwm_output, frequency, duty); + } +} + +/** + * @brief Determines whether PWM is running + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * assert_eq(false, gpio.get("pa4").isPwmRunning()); + * ``` + */ +static void js_gpio_is_pwm_running(struct mjs* mjs) { + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + if(manager_data->pwm_output == FuriHalPwmOutputIdNone) { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin"); + } + + mjs_return(mjs, mjs_mk_boolean(mjs, furi_hal_pwm_is_running(manager_data->pwm_output))); +} + +/** + * @brief Stops PWM + * + * Example usage: + * + * ```js + * let gpio = require("gpio"); + * let pa4 = gpio.get("pa4"); + * pa4.pwmWrite(10000, 50); + * pa4.pwmStop(); + * ``` + */ +static void js_gpio_pwm_stop(struct mjs* mjs) { + JsGpioPinInst* manager_data = JS_GET_CONTEXT(mjs); + if(manager_data->pwm_output == FuriHalPwmOutputIdNone) { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "PWM is not supported on this pin"); + } + + furi_hal_pwm_stop(manager_data->pwm_output); +} + /** * @brief Returns an object that manages a specified pin. * @@ -242,8 +369,13 @@ static void js_gpio_read_analog(struct mjs* mjs) { * ``` */ static void js_gpio_get(struct mjs* mjs) { + static const JsValueDeclaration js_gpio_get_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gpio_get_args = JS_VALUE_ARGS(js_gpio_get_arg_list); mjs_val_t name_arg; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&name_arg)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gpio_get_args, &name_arg); + const char* name_string = mjs_get_string(mjs, &name_arg, NULL); const GpioPinRecord* pin_record = NULL; @@ -269,12 +401,19 @@ static void js_gpio_get(struct mjs* mjs) { manager_data->interrupt_semaphore = furi_semaphore_alloc(UINT32_MAX, 0); manager_data->adc_handle = module->adc_handle; manager_data->adc_channel = pin_record->channel; - mjs_set(mjs, manager, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, manager_data)); - mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init)); - mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write)); - mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read)); - mjs_set(mjs, manager, "readAnalog", ~0, MJS_MK_FN(js_gpio_read_analog)); - mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt)); + manager_data->pwm_output = pin_record->pwm_output; + JS_ASSIGN_MULTI(mjs, manager) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, manager_data)); + JS_FIELD("init", MJS_MK_FN(js_gpio_init)); + JS_FIELD("write", MJS_MK_FN(js_gpio_write)); + JS_FIELD("read", MJS_MK_FN(js_gpio_read)); + JS_FIELD("readAnalog", MJS_MK_FN(js_gpio_read_analog)); + JS_FIELD("interrupt", MJS_MK_FN(js_gpio_interrupt)); + JS_FIELD("isPwmSupported", MJS_MK_FN(js_gpio_is_pwm_supported)); + JS_FIELD("pwmWrite", MJS_MK_FN(js_gpio_pwm_write)); + JS_FIELD("isPwmRunning", MJS_MK_FN(js_gpio_is_pwm_running)); + JS_FIELD("pwmStop", MJS_MK_FN(js_gpio_pwm_stop)); + } mjs_return(mjs, manager); // remember pin diff --git a/applications/system/js_app/modules/js_gui/file_picker.c b/applications/system/js_app/modules/js_gui/file_picker.c index 49cf5e89d..7b36596cd 100644 --- a/applications/system/js_app/modules/js_gui/file_picker.c +++ b/applications/system/js_app/modules/js_gui/file_picker.c @@ -3,8 +3,14 @@ #include static void js_gui_file_picker_pick_file(struct mjs* mjs) { + static const JsValueDeclaration js_picker_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), + }; + static const JsValueArguments js_picker_args = JS_VALUE_ARGS(js_picker_arg_list); + const char *base_path, *extension; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&base_path), JS_ARG_STR(&extension)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_picker_args, &base_path, &extension); DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); const DialogsFileBrowserOptions browser_options = { diff --git a/applications/system/js_app/modules/js_gui/icon.c b/applications/system/js_app/modules/js_gui/icon.c new file mode 100644 index 000000000..4fc6da2e0 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/icon.c @@ -0,0 +1,150 @@ +#include "../../js_modules.h" +#include +#include +#include +#include + +typedef struct { + const char* name; + const Icon* data; +} IconDefinition; + +#define ICON_DEF(icon) \ + (IconDefinition) { \ + .name = #icon, .data = &I_##icon \ + } + +static const IconDefinition builtin_icons[] = { + ICON_DEF(DolphinWait_59x54), + ICON_DEF(js_script_10px), +}; + +// Firmware's Icon struct needs a frames array, and uses a small CompressHeader +// Here we use a variable size allocation to add the uncompressed data in same allocation +// Also use a one-long array pointing to later in the same struct as the frames array +// CompressHeader includes a first is_compressed byte so we don't need to compress (.fxbm is uncompressed) +typedef struct FURI_PACKED { + Icon icon; + uint8_t* frames[1]; + struct { + uint8_t is_compressed; + uint8_t uncompressed_data[]; + } frame; +} FxbmIconWrapper; + +LIST_DEF(FxbmIconWrapperList, FxbmIconWrapper*, M_PTR_OPLIST); // NOLINT +#define M_OPL_FxbmIconWrapperList_t() LIST_OPLIST(FxbmIconWrapperList) + +typedef struct { + FxbmIconWrapperList_t fxbm_list; +} JsGuiIconInst; + +static const JsValueDeclaration js_icon_get_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), +}; +static const JsValueArguments js_icon_get_args = JS_VALUE_ARGS(js_icon_get_arg_list); + +static void js_gui_icon_get_builtin(struct mjs* mjs) { + const char* icon_name; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_icon_get_args, &icon_name); + + for(size_t i = 0; i < COUNT_OF(builtin_icons); i++) { + if(strcmp(icon_name, builtin_icons[i].name) == 0) { + mjs_return(mjs, mjs_mk_foreign(mjs, (void*)builtin_icons[i].data)); + return; + } + } + + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "no such built-in icon"); +} + +static void js_gui_icon_load_fxbm(struct mjs* mjs) { + const char* fxbm_path; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_icon_get_args, &fxbm_path); + + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + FxbmIconWrapper* fxbm = NULL; + + do { + if(!storage_file_open(file, fxbm_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + break; + } + + struct { + uint32_t size; // Total following size including width and height values + uint32_t width; + uint32_t height; + } fxbm_header; + if(storage_file_read(file, &fxbm_header, sizeof(fxbm_header)) != sizeof(fxbm_header)) { + break; + } + + size_t frame_size = fxbm_header.size - sizeof(uint32_t) * 2; + fxbm = malloc(sizeof(FxbmIconWrapper) + frame_size); + if(storage_file_read(file, fxbm->frame.uncompressed_data, frame_size) != frame_size) { + free(fxbm); + fxbm = NULL; + break; + } + + FURI_CONST_ASSIGN(fxbm->icon.width, fxbm_header.width); + FURI_CONST_ASSIGN(fxbm->icon.height, fxbm_header.height); + FURI_CONST_ASSIGN(fxbm->icon.frame_count, 1); + FURI_CONST_ASSIGN(fxbm->icon.frame_rate, 1); + FURI_CONST_ASSIGN_PTR(fxbm->icon.frames, fxbm->frames); + fxbm->frames[0] = (void*)&fxbm->frame; + fxbm->frame.is_compressed = false; + } while(false); + + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + if(!fxbm) { + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "could not load .fxbm icon"); + } + + JsGuiIconInst* js_icon = JS_GET_CONTEXT(mjs); + FxbmIconWrapperList_push_back(js_icon->fxbm_list, fxbm); + mjs_return(mjs, mjs_mk_foreign(mjs, (void*)&fxbm->icon)); +} + +static void* js_gui_icon_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { + UNUSED(modules); + JsGuiIconInst* js_icon = malloc(sizeof(JsGuiIconInst)); + FxbmIconWrapperList_init(js_icon->fxbm_list); + *object = mjs_mk_object(mjs); + JS_ASSIGN_MULTI(mjs, *object) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, js_icon)); + JS_FIELD("getBuiltin", MJS_MK_FN(js_gui_icon_get_builtin)); + JS_FIELD("loadFxbm", MJS_MK_FN(js_gui_icon_load_fxbm)); + } + return js_icon; +} + +static void js_gui_icon_destroy(void* inst) { + JsGuiIconInst* js_icon = inst; + for + M_EACH(fxbm, js_icon->fxbm_list, FxbmIconWrapperList_t) { + free(*fxbm); + } + FxbmIconWrapperList_clear(js_icon->fxbm_list); + free(js_icon); +} + +static const JsModuleDescriptor js_gui_icon_desc = { + "gui__icon", + js_gui_icon_create, + js_gui_icon_destroy, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_gui_icon_desc, +}; + +const FlipperAppPluginDescriptor* js_gui_icon_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_gui/js_gui.c b/applications/system/js_app/modules/js_gui/js_gui.c index 22d04855d..c20d980aa 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.c +++ b/applications/system/js_app/modules/js_gui/js_gui.c @@ -68,8 +68,14 @@ static bool js_gui_vd_nav_callback(void* context) { * @brief `viewDispatcher.sendCustom` */ static void js_gui_vd_send_custom(struct mjs* mjs) { + static const JsValueDeclaration js_gui_vd_send_custom_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_gui_vd_send_custom_args = + JS_VALUE_ARGS(js_gui_vd_send_custom_arg_list); + int32_t event; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&event)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_send_custom_args, &event); JsGui* module = JS_GET_CONTEXT(mjs); view_dispatcher_send_custom_event(module->dispatcher, (uint32_t)event); @@ -79,15 +85,25 @@ static void js_gui_vd_send_custom(struct mjs* mjs) { * @brief `viewDispatcher.sendTo` */ static void js_gui_vd_send_to(struct mjs* mjs) { - enum { - SendDirToFront, - SendDirToBack, - } send_direction; - JS_ENUM_MAP(send_direction, {"front", SendDirToFront}, {"back", SendDirToBack}); - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ENUM(send_direction, "SendDirection")); + typedef enum { + JsSendDirToFront, + JsSendDirToBack, + } JsSendDir; + static const JsValueEnumVariant js_send_dir_variants[] = { + {"front", JsSendDirToFront}, + {"back", JsSendDirToBack}, + }; + static const JsValueDeclaration js_gui_vd_send_to_arg_list[] = { + JS_VALUE_ENUM(JsSendDir, js_send_dir_variants), + }; + static const JsValueArguments js_gui_vd_send_to_args = + JS_VALUE_ARGS(js_gui_vd_send_to_arg_list); + + JsSendDir send_direction; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_send_to_args, &send_direction); JsGui* module = JS_GET_CONTEXT(mjs); - if(send_direction == SendDirToBack) { + if(send_direction == JsSendDirToBack) { view_dispatcher_send_to_back(module->dispatcher); } else { view_dispatcher_send_to_front(module->dispatcher); @@ -98,8 +114,15 @@ static void js_gui_vd_send_to(struct mjs* mjs) { * @brief `viewDispatcher.switchTo` */ static void js_gui_vd_switch_to(struct mjs* mjs) { + static const JsValueDeclaration js_gui_vd_switch_to_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_vd_switch_to_args = + JS_VALUE_ARGS(js_gui_vd_switch_to_arg_list); + mjs_val_t view; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&view)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vd_switch_to_args, &view); + JsGuiViewData* view_data = JS_GET_INST(mjs, view); mjs_val_t vd_obj = mjs_get_this(mjs); JsGui* module = JS_GET_INST(mjs, vd_obj); @@ -247,19 +270,96 @@ static bool return false; } +/** + * @brief Sets the list of children. Not available from JS. + */ +static bool + js_gui_view_internal_set_children(struct mjs* mjs, mjs_val_t children, JsGuiViewData* data) { + data->descriptor->reset_children(data->specific_view, data->custom_data); + + for(size_t i = 0; i < mjs_array_length(mjs, children); i++) { + mjs_val_t child = mjs_array_get(mjs, children, i); + if(!data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child)) + return false; + } + + return true; +} + /** * @brief `View.set` */ static void js_gui_view_set(struct mjs* mjs) { + static const JsValueDeclaration js_gui_view_set_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_view_set_args = JS_VALUE_ARGS(js_gui_view_set_arg_list); + const char* name; mjs_val_t value; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&name), JS_ARG_ANY(&value)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_set_args, &name, &value); + JsGuiViewData* data = JS_GET_CONTEXT(mjs); bool success = js_gui_view_assign(mjs, name, value, data); UNUSED(success); mjs_return(mjs, MJS_UNDEFINED); } +/** + * @brief `View.addChild` + */ +static void js_gui_view_add_child(struct mjs* mjs) { + static const JsValueDeclaration js_gui_view_add_child_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_view_add_child_args = + JS_VALUE_ARGS(js_gui_view_add_child_arg_list); + + mjs_val_t child; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_add_child_args, &child); + + JsGuiViewData* data = JS_GET_CONTEXT(mjs); + if(!data->descriptor->add_child || !data->descriptor->reset_children) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); + + bool success = data->descriptor->add_child(mjs, data->specific_view, data->custom_data, child); + UNUSED(success); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief `View.resetChildren` + */ +static void js_gui_view_reset_children(struct mjs* mjs) { + JsGuiViewData* data = JS_GET_CONTEXT(mjs); + if(!data->descriptor->add_child || !data->descriptor->reset_children) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); + + data->descriptor->reset_children(data->specific_view, data->custom_data); + mjs_return(mjs, MJS_UNDEFINED); +} + +/** + * @brief `View.setChildren` + */ +static void js_gui_view_set_children(struct mjs* mjs) { + static const JsValueDeclaration js_gui_view_set_children_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAnyArray), + }; + static const JsValueArguments js_gui_view_set_children_args = + JS_VALUE_ARGS(js_gui_view_set_children_arg_list); + + mjs_val_t children; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_view_set_children_args, &children); + + JsGuiViewData* data = JS_GET_CONTEXT(mjs); + if(!data->descriptor->add_child || !data->descriptor->reset_children) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); + + js_gui_view_internal_set_children(mjs, children, data); +} + /** * @brief `View` destructor */ @@ -283,7 +383,12 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr // generic view API mjs_val_t view_obj = mjs_mk_object(mjs); - mjs_set(mjs, view_obj, "set", ~0, MJS_MK_FN(js_gui_view_set)); + JS_ASSIGN_MULTI(mjs, view_obj) { + JS_FIELD("set", MJS_MK_FN(js_gui_view_set)); + JS_FIELD("addChild", MJS_MK_FN(js_gui_view_add_child)); + JS_FIELD("resetChildren", MJS_MK_FN(js_gui_view_reset_children)); + JS_FIELD("setChildren", MJS_MK_FN(js_gui_view_set_children)); + } // object data JsGuiViewData* data = malloc(sizeof(JsGuiViewData)); @@ -304,7 +409,6 @@ static mjs_val_t js_gui_make_view(struct mjs* mjs, const JsViewDescriptor* descr * @brief `ViewFactory.make` */ static void js_gui_vf_make(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); mjs_return(mjs, js_gui_make_view(mjs, descriptor)); } @@ -313,8 +417,15 @@ static void js_gui_vf_make(struct mjs* mjs) { * @brief `ViewFactory.makeWith` */ static void js_gui_vf_make_with(struct mjs* mjs) { - mjs_val_t props; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&props)); + static const JsValueDeclaration js_gui_vf_make_with_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAnyObject), + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_gui_vf_make_with_args = + JS_VALUE_ARGS(js_gui_vf_make_with_arg_list); + + mjs_val_t props, children; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_gui_vf_make_with_args, &props, &children); const JsViewDescriptor* descriptor = JS_GET_CONTEXT(mjs); // make the object like normal @@ -334,6 +445,14 @@ static void js_gui_vf_make_with(struct mjs* mjs) { } } + // assign children + if(mjs_is_array(children)) { + if(!data->descriptor->add_child || !data->descriptor->reset_children) + JS_ERROR_AND_RETURN(mjs, MJS_BAD_ARGS_ERROR, "this View can't have children"); + + if(!js_gui_view_internal_set_children(mjs, children, data)) return; + } + mjs_return(mjs, view_obj); } diff --git a/applications/system/js_app/modules/js_gui/js_gui.h b/applications/system/js_app/modules/js_gui/js_gui.h index d400d0a33..d9d98df39 100644 --- a/applications/system/js_app/modules/js_gui/js_gui.h +++ b/applications/system/js_app/modules/js_gui/js_gui.h @@ -50,6 +50,11 @@ typedef void (*JsViewFree)(void* specific_view); typedef void* (*JsViewCustomMake)(struct mjs* mjs, void* specific_view, mjs_val_t view_obj); /** @brief Context destruction for glue code */ typedef void (*JsViewCustomDestroy)(void* specific_view, void* custom_state, FuriEventLoop* loop); +/** @brief `addChild` callback for glue code */ +typedef bool ( + *JsViewAddChild)(struct mjs* mjs, void* specific_view, void* custom_state, mjs_val_t child_obj); +/** @brief `resetChildren` callback for glue code */ +typedef void (*JsViewResetChildren)(void* specific_view, void* custom_state); /** * @brief Descriptor for a JS view @@ -66,15 +71,22 @@ typedef struct { JsViewAlloc alloc; JsViewGetView get_view; JsViewFree free; + JsViewCustomMake custom_make; // get_view -> [custom_make (if set)] -> props[i].assign -> [custom_destroy (if_set)] -> free -// \_______________ creation ________________/ \___ usage ___/ \_________ destruction _________/ +// +-> add_child -+ +// +-> reset_children -+ +// alloc -> get_view -> custom_make -+-> props[i].assign -+> custom_destroy -> free +// \__________ creation __________/ \____ use ____/ \___ destruction ____/ /** * @brief Creates a JS `ViewFactory` object diff --git a/applications/system/js_app/modules/js_gui/widget.c b/applications/system/js_app/modules/js_gui/widget.c new file mode 100644 index 000000000..bb2898030 --- /dev/null +++ b/applications/system/js_app/modules/js_gui/widget.c @@ -0,0 +1,317 @@ +#include "../../js_modules.h" // IWYU pragma: keep +#include "js_gui.h" +#include "../js_event_loop/js_event_loop.h" +#include + +typedef struct { + FuriMessageQueue* queue; + JsEventLoopContract contract; +} JsWidgetCtx; + +#define QUEUE_LEN 2 + +/** + * @brief Parses position (X and Y) from an element declaration object + */ +static bool element_get_position(struct mjs* mjs, mjs_val_t element, int32_t* x, int32_t* y) { + mjs_val_t x_in = mjs_get(mjs, element, "x", ~0); + mjs_val_t y_in = mjs_get(mjs, element, "y", ~0); + if(!mjs_is_number(x_in) || !mjs_is_number(y_in)) return false; + *x = mjs_get_int32(mjs, x_in); + *y = mjs_get_int32(mjs, y_in); + return true; +} + +/** + * @brief Parses size (W and h) from an element declaration object + */ +static bool element_get_size(struct mjs* mjs, mjs_val_t element, int32_t* w, int32_t* h) { + mjs_val_t w_in = mjs_get(mjs, element, "w", ~0); + mjs_val_t h_in = mjs_get(mjs, element, "h", ~0); + if(!mjs_is_number(w_in) || !mjs_is_number(h_in)) return false; + *w = mjs_get_int32(mjs, w_in); + *h = mjs_get_int32(mjs, h_in); + return true; +} + +/** + * @brief Parses alignment (V and H) from an element declaration object + */ +static bool + element_get_alignment(struct mjs* mjs, mjs_val_t element, Align* align_v, Align* align_h) { + mjs_val_t align_in = mjs_get(mjs, element, "align", ~0); + const char* align = mjs_get_string(mjs, &align_in, NULL); + if(!align) return false; + if(strlen(align) != 2) return false; + + if(align[0] == 't') { + *align_v = AlignTop; + } else if(align[0] == 'c') { + *align_v = AlignCenter; + } else if(align[0] == 'b') { + *align_v = AlignBottom; + } else { + return false; + } + + if(align[1] == 'l') { + *align_h = AlignLeft; + } else if(align[1] == 'm') { // m = middle + *align_h = AlignCenter; + } else if(align[1] == 'r') { + *align_h = AlignRight; + } else { + return false; + } + + return true; +} + +/** + * @brief Parses font from an element declaration object + */ +static bool element_get_font(struct mjs* mjs, mjs_val_t element, Font* font) { + mjs_val_t font_in = mjs_get(mjs, element, "font", ~0); + const char* font_str = mjs_get_string(mjs, &font_in, NULL); + if(!font_str) return false; + + if(strcmp(font_str, "primary") == 0) { + *font = FontPrimary; + } else if(strcmp(font_str, "secondary") == 0) { + *font = FontSecondary; + } else if(strcmp(font_str, "keyboard") == 0) { + *font = FontKeyboard; + } else if(strcmp(font_str, "big_numbers") == 0) { + *font = FontBigNumbers; + } else { + return false; + } + return true; +} + +/** + * @brief Parses text from an element declaration object + */ +static bool element_get_text(struct mjs* mjs, mjs_val_t element, mjs_val_t* text) { + *text = mjs_get(mjs, element, "text", ~0); + return mjs_is_string(*text); +} + +/** + * @brief Widget button element callback + */ +static void js_widget_button_callback(GuiButtonType result, InputType type, JsWidgetCtx* context) { + UNUSED(type); + furi_check(furi_message_queue_put(context->queue, &result, 0) == FuriStatusOk); +} + +#define DESTRUCTURE_OR_RETURN(mjs, child_obj, part, ...) \ + if(!element_get_##part(mjs, child_obj, __VA_ARGS__)) \ + JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element " #part); + +static bool js_widget_add_child( + struct mjs* mjs, + Widget* widget, + JsWidgetCtx* context, + mjs_val_t child_obj) { + UNUSED(context); + if(!mjs_is_object(child_obj)) + JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "child must be an object"); + + mjs_val_t element_type_term = mjs_get(mjs, child_obj, "element", ~0); + const char* element_type = mjs_get_string(mjs, &element_type_term, NULL); + if(!element_type) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "child object must have `element` property"); + + if((strcmp(element_type, "string") == 0) || (strcmp(element_type, "string_multiline") == 0)) { + int32_t x, y; + Align align_v, align_h; + Font font; + mjs_val_t text; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + DESTRUCTURE_OR_RETURN(mjs, child_obj, alignment, &align_v, &align_h); + DESTRUCTURE_OR_RETURN(mjs, child_obj, font, &font); + DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text); + if(strcmp(element_type, "string") == 0) { + widget_add_string_element( + widget, x, y, align_h, align_v, font, mjs_get_string(mjs, &text, NULL)); + } else { + widget_add_string_multiline_element( + widget, x, y, align_h, align_v, font, mjs_get_string(mjs, &text, NULL)); + } + + } else if(strcmp(element_type, "text_box") == 0) { + int32_t x, y, w, h; + Align align_v, align_h; + Font font; + mjs_val_t text; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h); + DESTRUCTURE_OR_RETURN(mjs, child_obj, alignment, &align_v, &align_h); + DESTRUCTURE_OR_RETURN(mjs, child_obj, font, &font); + DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text); + mjs_val_t strip_to_dots_in = mjs_get(mjs, child_obj, "stripToDots", ~0); + if(!mjs_is_boolean(strip_to_dots_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element stripToDots"); + bool strip_to_dots = mjs_get_bool(mjs, strip_to_dots_in); + widget_add_text_box_element( + widget, x, y, w, h, align_h, align_v, mjs_get_string(mjs, &text, NULL), strip_to_dots); + + } else if(strcmp(element_type, "text_scroll") == 0) { + int32_t x, y, w, h; + mjs_val_t text; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h); + DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text); + widget_add_text_scroll_element(widget, x, y, w, h, mjs_get_string(mjs, &text, NULL)); + + } else if(strcmp(element_type, "button") == 0) { + mjs_val_t btn_in = mjs_get(mjs, child_obj, "button", ~0); + const char* btn_name = mjs_get_string(mjs, &btn_in, NULL); + if(!btn_name) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element button"); + GuiButtonType btn_type; + if(strcmp(btn_name, "left") == 0) { + btn_type = GuiButtonTypeLeft; + } else if(strcmp(btn_name, "center") == 0) { + btn_type = GuiButtonTypeCenter; + } else if(strcmp(btn_name, "right") == 0) { + btn_type = GuiButtonTypeRight; + } else { + JS_ERROR_AND_RETURN_VAL(mjs, MJS_BAD_ARGS_ERROR, false, "incorrect button type"); + } + mjs_val_t text; + DESTRUCTURE_OR_RETURN(mjs, child_obj, text, &text); + widget_add_button_element( + widget, + btn_type, + mjs_get_string(mjs, &text, NULL), + (ButtonCallback)js_widget_button_callback, + context); + + } else if(strcmp(element_type, "icon") == 0) { + int32_t x, y; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + mjs_val_t icon_data_in = mjs_get(mjs, child_obj, "iconData", ~0); + if(!mjs_is_foreign(icon_data_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element iconData"); + const Icon* icon = mjs_get_ptr(mjs, icon_data_in); + widget_add_icon_element(widget, x, y, icon); + + } else if(strcmp(element_type, "rect") == 0) { + int32_t x, y, w, h; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + DESTRUCTURE_OR_RETURN(mjs, child_obj, size, &w, &h); + mjs_val_t radius_in = mjs_get(mjs, child_obj, "radius", ~0); + if(!mjs_is_number(radius_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius"); + int32_t radius = mjs_get_int32(mjs, radius_in); + mjs_val_t fill_in = mjs_get(mjs, child_obj, "fill", ~0); + if(!mjs_is_boolean(fill_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element fill"); + int32_t fill = mjs_get_bool(mjs, fill_in); + widget_add_rect_element(widget, x, y, w, h, radius, fill); + + } else if(strcmp(element_type, "circle") == 0) { + int32_t x, y; + DESTRUCTURE_OR_RETURN(mjs, child_obj, position, &x, &y); + mjs_val_t radius_in = mjs_get(mjs, child_obj, "radius", ~0); + if(!mjs_is_number(radius_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element radius"); + int32_t radius = mjs_get_int32(mjs, radius_in); + mjs_val_t fill_in = mjs_get(mjs, child_obj, "fill", ~0); + if(!mjs_is_boolean(fill_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element fill"); + int32_t fill = mjs_get_bool(mjs, fill_in); + widget_add_circle_element(widget, x, y, radius, fill); + + } else if(strcmp(element_type, "line") == 0) { + int32_t x1, y1, x2, y2; + mjs_val_t x1_in = mjs_get(mjs, child_obj, "x1", ~0); + mjs_val_t y1_in = mjs_get(mjs, child_obj, "y1", ~0); + mjs_val_t x2_in = mjs_get(mjs, child_obj, "x2", ~0); + mjs_val_t y2_in = mjs_get(mjs, child_obj, "y2", ~0); + if(!mjs_is_number(x1_in) || !mjs_is_number(y1_in) || !mjs_is_number(x2_in) || + !mjs_is_number(y2_in)) + JS_ERROR_AND_RETURN_VAL( + mjs, MJS_BAD_ARGS_ERROR, false, "failed to fetch element positions"); + x1 = mjs_get_int32(mjs, x1_in); + y1 = mjs_get_int32(mjs, y1_in); + x2 = mjs_get_int32(mjs, x2_in); + y2 = mjs_get_int32(mjs, y2_in); + widget_add_line_element(widget, x1, y1, x2, y2); + } + + return true; +} + +static void js_widget_reset_children(Widget* widget, void* state) { + UNUSED(state); + widget_reset(widget); +} + +static mjs_val_t js_widget_button_event_transformer( + struct mjs* mjs, + FuriMessageQueue* queue, + JsWidgetCtx* context) { + UNUSED(context); + GuiButtonType btn_type; + furi_check(furi_message_queue_get(queue, &btn_type, 0) == FuriStatusOk); + const char* btn_name; + if(btn_type == GuiButtonTypeLeft) { + btn_name = "left"; + } else if(btn_type == GuiButtonTypeCenter) { + btn_name = "center"; + } else if(btn_type == GuiButtonTypeRight) { + btn_name = "right"; + } else { + furi_crash(); + } + return mjs_mk_string(mjs, btn_name, ~0, false); +} + +static void* js_widget_custom_make(struct mjs* mjs, Widget* widget, mjs_val_t view_obj) { + UNUSED(widget); + JsWidgetCtx* context = malloc(sizeof(JsWidgetCtx)); + context->queue = furi_message_queue_alloc(QUEUE_LEN, sizeof(GuiButtonType)); + context->contract = (JsEventLoopContract){ + .magic = JsForeignMagic_JsEventLoopContract, + .object_type = JsEventLoopObjectTypeQueue, + .object = context->queue, + .non_timer = + { + .event = FuriEventLoopEventIn, + .transformer = (JsEventLoopTransformer)js_widget_button_event_transformer, + }, + }; + mjs_set(mjs, view_obj, "button", ~0, mjs_mk_foreign(mjs, &context->contract)); + return context; +} + +static void js_widget_custom_destroy(Widget* widget, JsWidgetCtx* context, FuriEventLoop* loop) { + UNUSED(widget); + furi_event_loop_maybe_unsubscribe(loop, context->queue); + furi_message_queue_free(context->queue); + free(context); +} + +static const JsViewDescriptor view_descriptor = { + .alloc = (JsViewAlloc)widget_alloc, + .free = (JsViewFree)widget_free, + .get_view = (JsViewGetView)widget_get_view, + .custom_make = (JsViewCustomMake)js_widget_custom_make, + .custom_destroy = (JsViewCustomDestroy)js_widget_custom_destroy, + .add_child = (JsViewAddChild)js_widget_add_child, + .reset_children = (JsViewResetChildren)js_widget_reset_children, + .prop_cnt = 0, + .props = {}, +}; +JS_GUI_VIEW_DEF(widget, &view_descriptor); diff --git a/applications/system/js_app/modules/js_serial.c b/applications/system/js_app/modules/js_serial.c index b1e578fbc..d903939ce 100644 --- a/applications/system/js_app/modules/js_serial.c +++ b/applications/system/js_app/modules/js_serial.c @@ -20,14 +20,6 @@ typedef struct { char* data; } PatternArrayItem; -static const struct { - const char* name; - const FuriHalSerialId value; -} serial_channels[] = { - {"usart", FuriHalSerialIdUsart}, - {"lpuart", FuriHalSerialIdLpuart}, -}; - ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST); static void @@ -43,52 +35,62 @@ static void } static void js_serial_setup(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); - furi_assert(serial); + static const JsValueEnumVariant js_serial_id_variants[] = { + {"lpuart", FuriHalSerialIdLpuart}, + {"usart", FuriHalSerialIdUsart}, + }; - if(serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + static const JsValueEnumVariant js_serial_data_bit_variants[] = { + {"6", FuriHalSerialDataBits6}, + {"7", FuriHalSerialDataBits7}, + {"8", FuriHalSerialDataBits8}, + {"9", FuriHalSerialDataBits9}, + }; + static const JsValueDeclaration js_serial_data_bits = JS_VALUE_ENUM_W_DEFAULT( + FuriHalSerialDataBits, js_serial_data_bit_variants, FuriHalSerialDataBits8); - bool args_correct = false; - FuriHalSerialId serial_id = FuriHalSerialIdMax; - uint32_t baudrate = 0; + static const JsValueEnumVariant js_serial_parity_variants[] = { + {"none", FuriHalSerialParityNone}, + {"even", FuriHalSerialParityEven}, + {"odd", FuriHalSerialParityOdd}, + }; + static const JsValueDeclaration js_serial_parity = JS_VALUE_ENUM_W_DEFAULT( + FuriHalSerialParity, js_serial_parity_variants, FuriHalSerialParityNone); - do { - if(mjs_nargs(mjs) != 2) break; + static const JsValueEnumVariant js_serial_stop_bit_variants[] = { + {"0.5", FuriHalSerialStopBits0_5}, + {"1", FuriHalSerialStopBits1}, + {"1.5", FuriHalSerialStopBits1_5}, + {"2", FuriHalSerialStopBits2}, + }; + static const JsValueDeclaration js_serial_stop_bits = JS_VALUE_ENUM_W_DEFAULT( + FuriHalSerialStopBits, js_serial_stop_bit_variants, FuriHalSerialStopBits1); - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_string(arg)) break; + static const JsValueObjectField js_serial_framing_fields[] = { + {"dataBits", &js_serial_data_bits}, + {"parity", &js_serial_parity}, + {"stopBits", &js_serial_stop_bits}, + }; - size_t str_len = 0; - const char* arg_str = mjs_get_string(mjs, &arg, &str_len); - for(size_t i = 0; i < COUNT_OF(serial_channels); i++) { - size_t name_len = strlen(serial_channels[i].name); - if(str_len != name_len) continue; - if(strncmp(arg_str, serial_channels[i].name, str_len) == 0) { - serial_id = serial_channels[i].value; - break; - } - } - if(serial_id == FuriHalSerialIdMax) { - break; - } + static const JsValueDeclaration js_serial_setup_arg_list[] = { + JS_VALUE_ENUM(FuriHalSerialId, js_serial_id_variants), + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_OBJECT_W_DEFAULTS(js_serial_framing_fields), + }; + static const JsValueArguments js_serial_setup_args = JS_VALUE_ARGS(js_serial_setup_arg_list); - arg = mjs_arg(mjs, 1); - if(!mjs_is_number(arg)) break; - baudrate = mjs_get_int32(mjs, arg); + FuriHalSerialId serial_id; + int32_t baudrate; + FuriHalSerialDataBits data_bits = FuriHalSerialDataBits8; + FuriHalSerialParity parity = FuriHalSerialParityNone; + FuriHalSerialStopBits stop_bits = FuriHalSerialStopBits1; + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_serial_setup_args, &serial_id, &baudrate, &data_bits, &parity, &stop_bits); - args_correct = true; - } while(0); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); - if(!args_correct) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); expansion_disable(furi_record_open(RECORD_EXPANSION)); furi_record_close(RECORD_EXPANSION); @@ -97,6 +99,7 @@ static void js_serial_setup(struct mjs* mjs) { if(serial->serial_handle) { serial->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1); furi_hal_serial_init(serial->serial_handle, baudrate); + furi_hal_serial_configure_framing(serial->serial_handle, data_bits, parity, stop_bits); furi_hal_serial_async_rx_start( serial->serial_handle, js_serial_on_async_rx, serial, false); serial->setup_done = true; @@ -122,28 +125,20 @@ static void js_serial_deinit(JsSerialInst* js_serial) { } static void js_serial_end(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); js_serial_deinit(serial); } static void js_serial_write(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); bool args_correct = true; @@ -227,43 +222,20 @@ static size_t js_serial_receive(JsSerialInst* serial, char* buf, size_t len, uin return bytes_read; } +static const JsValueDeclaration js_serial_read_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, INT32_MAX), +}; +static const JsValueArguments js_serial_read_args = JS_VALUE_ARGS(js_serial_read_arg_list); + static void js_serial_read(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - size_t read_len = 0; - uint32_t timeout = FuriWaitForever; - - do { - size_t num_args = mjs_nargs(mjs); - if(num_args == 1) { - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_number(arg)) { - break; - } - read_len = mjs_get_int32(mjs, arg); - } else if(num_args == 2) { - mjs_val_t len_arg = mjs_arg(mjs, 0); - mjs_val_t timeout_arg = mjs_arg(mjs, 1); - if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) { - break; - } - read_len = mjs_get_int32(mjs, len_arg); - timeout = mjs_get_int32(mjs, timeout_arg); - } - } while(0); - - if(read_len == 0) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + int32_t read_len, timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_args, &read_len, &timeout); char* read_buf = malloc(read_len); size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout); @@ -277,37 +249,19 @@ static void js_serial_read(struct mjs* mjs) { } static void js_serial_readln(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - bool args_correct = false; - uint32_t timeout = FuriWaitForever; + static const JsValueDeclaration js_serial_readln_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_serial_readln_args = JS_VALUE_ARGS(js_serial_readln_arg_list); - do { - size_t num_args = mjs_nargs(mjs); - if(num_args > 1) { - break; - } else if(num_args == 1) { - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_number(arg)) { - break; - } - timeout = mjs_get_int32(mjs, arg); - } - args_correct = true; - } while(0); + int32_t timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_readln_args, &timeout); - if(!args_correct) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } FuriString* rx_buf = furi_string_alloc(); size_t bytes_read = 0; char read_char = 0; @@ -334,42 +288,13 @@ static void js_serial_readln(struct mjs* mjs) { } static void js_serial_read_bytes(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - size_t read_len = 0; - uint32_t timeout = FuriWaitForever; - - do { - size_t num_args = mjs_nargs(mjs); - if(num_args == 1) { - mjs_val_t arg = mjs_arg(mjs, 0); - if(!mjs_is_number(arg)) { - break; - } - read_len = mjs_get_int32(mjs, arg); - } else if(num_args == 2) { - mjs_val_t len_arg = mjs_arg(mjs, 0); - mjs_val_t timeout_arg = mjs_arg(mjs, 1); - if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) { - break; - } - read_len = mjs_get_int32(mjs, len_arg); - timeout = mjs_get_int32(mjs, timeout_arg); - } - } while(0); - - if(read_len == 0) { - mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + int32_t read_len, timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_args, &read_len, &timeout); char* read_buf = malloc(read_len); size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout); @@ -398,27 +323,19 @@ static char* js_serial_receive_any(JsSerialInst* serial, size_t* len, uint32_t t } static void js_serial_read_any(struct mjs* mjs) { - mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + JsSerialInst* serial = JS_GET_CONTEXT(mjs); furi_assert(serial); - if(!serial->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - mjs_return(mjs, MJS_UNDEFINED); - return; - } + if(!serial->setup_done) + JS_ERROR_AND_RETURN(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); - uint32_t timeout = FuriWaitForever; + static const JsValueDeclaration js_serial_read_any_arg_list[] = { + JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, INT32_MAX), + }; + static const JsValueArguments js_serial_read_any_args = + JS_VALUE_ARGS(js_serial_read_any_arg_list); - do { - size_t num_args = mjs_nargs(mjs); - if(num_args == 1) { - mjs_val_t timeout_arg = mjs_arg(mjs, 0); - if(!mjs_is_number(timeout_arg)) { - break; - } - timeout = mjs_get_int32(mjs, timeout_arg); - } - } while(0); + int32_t timeout; + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_serial_read_any_args, &timeout); size_t bytes_read = 0; char* read_buf = js_serial_receive_any(serial, &bytes_read, timeout); @@ -662,16 +579,19 @@ static void* js_serial_create(struct mjs* mjs, mjs_val_t* object, JsModules* mod UNUSED(modules); JsSerialInst* js_serial = malloc(sizeof(JsSerialInst)); js_serial->mjs = mjs; + mjs_val_t serial_obj = mjs_mk_object(mjs); - mjs_set(mjs, serial_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_serial)); - mjs_set(mjs, serial_obj, "setup", ~0, MJS_MK_FN(js_serial_setup)); - mjs_set(mjs, serial_obj, "end", ~0, MJS_MK_FN(js_serial_end)); - mjs_set(mjs, serial_obj, "write", ~0, MJS_MK_FN(js_serial_write)); - mjs_set(mjs, serial_obj, "read", ~0, MJS_MK_FN(js_serial_read)); - mjs_set(mjs, serial_obj, "readln", ~0, MJS_MK_FN(js_serial_readln)); - mjs_set(mjs, serial_obj, "readBytes", ~0, MJS_MK_FN(js_serial_read_bytes)); - mjs_set(mjs, serial_obj, "readAny", ~0, MJS_MK_FN(js_serial_read_any)); - mjs_set(mjs, serial_obj, "expect", ~0, MJS_MK_FN(js_serial_expect)); + JS_ASSIGN_MULTI(mjs, serial_obj) { + JS_FIELD(INST_PROP_NAME, mjs_mk_foreign(mjs, js_serial)); + JS_FIELD("setup", MJS_MK_FN(js_serial_setup)); + JS_FIELD("end", MJS_MK_FN(js_serial_end)); + JS_FIELD("write", MJS_MK_FN(js_serial_write)); + JS_FIELD("read", MJS_MK_FN(js_serial_read)); + JS_FIELD("readln", MJS_MK_FN(js_serial_readln)); + JS_FIELD("readBytes", MJS_MK_FN(js_serial_read_bytes)); + JS_FIELD("readAny", MJS_MK_FN(js_serial_read_any)); + JS_FIELD("expect", MJS_MK_FN(js_serial_expect)); + } *object = serial_obj; return js_serial; diff --git a/applications/system/js_app/modules/js_storage.c b/applications/system/js_app/modules/js_storage.c index 1d4053a5f..66d002f33 100644 --- a/applications/system/js_app/modules/js_storage.c +++ b/applications/system/js_app/modules/js_storage.c @@ -1,42 +1,79 @@ #include "../js_modules.h" // IWYU pragma: keep #include -// ---=== file ops ===--- +// ========================== +// Common argument signatures +// ========================== + +static const JsValueDeclaration js_storage_1_int_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeInt32), +}; +static const JsValueArguments js_storage_1_int_args = JS_VALUE_ARGS(js_storage_1_int_arg_list); + +static const JsValueDeclaration js_storage_1_str_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), +}; +static const JsValueArguments js_storage_1_str_args = JS_VALUE_ARGS(js_storage_1_str_arg_list); + +static const JsValueDeclaration js_storage_2_str_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), +}; +static const JsValueArguments js_storage_2_str_args = JS_VALUE_ARGS(js_storage_2_str_arg_list); + +// ====================== +// File object operations +// ====================== static void js_storage_file_close(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_close(file))); } static void js_storage_file_is_open(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_is_open(file))); } static void js_storage_file_read(struct mjs* mjs) { - enum { - ReadModeAscii, - ReadModeBinary, - } read_mode; - JS_ENUM_MAP(read_mode, {"ascii", ReadModeAscii}, {"binary", ReadModeBinary}); + typedef enum { + JsStorageReadModeAscii, + JsStorageReadModeBinary, + } JsStorageReadMode; + static const JsValueEnumVariant js_storage_read_mode_variants[] = { + {"ascii", JsStorageReadModeAscii}, + {"binary", JsStorageReadModeBinary}, + }; + static const JsValueDeclaration js_storage_read_arg_list[] = { + JS_VALUE_ENUM(JsStorageReadMode, js_storage_read_mode_variants), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_storage_read_args = JS_VALUE_ARGS(js_storage_read_arg_list); + + JsStorageReadMode read_mode; int32_t length; - JS_FETCH_ARGS_OR_RETURN( - mjs, JS_EXACTLY, JS_ARG_ENUM(read_mode, "ReadMode"), JS_ARG_INT32(&length)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_read_args, &read_mode, &length); + File* file = JS_GET_CONTEXT(mjs); char buffer[length]; size_t actually_read = storage_file_read(file, buffer, length); - if(read_mode == ReadModeAscii) { + if(read_mode == JsStorageReadModeAscii) { mjs_return(mjs, mjs_mk_string(mjs, buffer, actually_read, true)); - } else if(read_mode == ReadModeBinary) { + } else if(read_mode == JsStorageReadModeBinary) { mjs_return(mjs, mjs_mk_array_buf(mjs, buffer, actually_read)); } } static void js_storage_file_write(struct mjs* mjs) { + static const JsValueDeclaration js_storage_file_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + }; + static const JsValueArguments js_storage_file_write_args = + JS_VALUE_ARGS(js_storage_file_write_arg_list); + mjs_val_t data; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ANY(&data)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_file_write_args, &data); + const void* buf; size_t len; if(mjs_is_string(data)) { @@ -52,52 +89,58 @@ static void js_storage_file_write(struct mjs* mjs) { static void js_storage_file_seek_relative(struct mjs* mjs) { int32_t offset; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_int_args, &offset); File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, false))); } static void js_storage_file_seek_absolute(struct mjs* mjs) { int32_t offset; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&offset)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_int_args, &offset); File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_seek(file, offset, true))); } static void js_storage_file_tell(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_number(mjs, storage_file_tell(file))); } static void js_storage_file_truncate(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_truncate(file))); } static void js_storage_file_size(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_number(mjs, storage_file_size(file))); } static void js_storage_file_eof(struct mjs* mjs) { - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY); // 0 args File* file = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_eof(file))); } static void js_storage_file_copy_to(struct mjs* mjs) { - File* source = JS_GET_CONTEXT(mjs); + static const JsValueDeclaration js_storage_file_write_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeAny), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_storage_file_write_args = + JS_VALUE_ARGS(js_storage_file_write_arg_list); + mjs_val_t dest_obj; int32_t bytes; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_OBJ(&dest_obj), JS_ARG_INT32(&bytes)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_file_write_args, &dest_obj, &bytes); + + File* source = JS_GET_CONTEXT(mjs); File* destination = JS_GET_INST(mjs, dest_obj); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_copy_to_file(source, destination, bytes))); } -// ---=== top-level file ops ===--- +// ========================= +// Top-level file operations +// ========================= // common destructor for file and dir objects static void js_storage_file_destructor(struct mjs* mjs, mjs_val_t obj) { @@ -106,23 +149,33 @@ static void js_storage_file_destructor(struct mjs* mjs, mjs_val_t obj) { } static void js_storage_open_file(struct mjs* mjs) { - const char* path; - FS_AccessMode access_mode; - FS_OpenMode open_mode; - JS_ENUM_MAP(access_mode, {"r", FSAM_READ}, {"w", FSAM_WRITE}, {"rw", FSAM_READ_WRITE}); - JS_ENUM_MAP( - open_mode, + static const JsValueEnumVariant js_storage_fsam_variants[] = { + {"r", FSAM_READ}, + {"w", FSAM_WRITE}, + {"rw", FSAM_READ_WRITE}, + }; + + static const JsValueEnumVariant js_storage_fsom_variants[] = { {"open_existing", FSOM_OPEN_EXISTING}, {"open_always", FSOM_OPEN_ALWAYS}, {"open_append", FSOM_OPEN_APPEND}, {"create_new", FSOM_CREATE_NEW}, - {"create_always", FSOM_CREATE_ALWAYS}); - JS_FETCH_ARGS_OR_RETURN( - mjs, - JS_EXACTLY, - JS_ARG_STR(&path), - JS_ARG_ENUM(access_mode, "AccessMode"), - JS_ARG_ENUM(open_mode, "OpenMode")); + {"create_always", FSOM_CREATE_ALWAYS}, + }; + + static const JsValueDeclaration js_storage_open_file_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_ENUM(FS_AccessMode, js_storage_fsam_variants), + JS_VALUE_ENUM(FS_OpenMode, js_storage_fsom_variants), + }; + static const JsValueArguments js_storage_open_file_args = + JS_VALUE_ARGS(js_storage_open_file_arg_list); + + const char* path; + FS_AccessMode access_mode; + FS_OpenMode open_mode; + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_storage_open_file_args, &path, &access_mode, &open_mode); Storage* storage = JS_GET_CONTEXT(mjs); File* file = storage_file_alloc(storage); @@ -152,16 +205,18 @@ static void js_storage_open_file(struct mjs* mjs) { static void js_storage_file_exists(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_file_exists(storage, path))); } -// ---=== dir ops ===--- +// ==================== +// Directory operations +// ==================== static void js_storage_read_directory(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); File* dir = storage_file_alloc(storage); @@ -200,30 +255,32 @@ static void js_storage_read_directory(struct mjs* mjs) { static void js_storage_directory_exists(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_dir_exists(storage, path))); } static void js_storage_make_directory(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_mkdir(storage, path))); } -// ---=== common ops ===--- +// ================= +// Common operations +// ================= static void js_storage_file_or_dir_exists(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_exists(storage, path))); } static void js_storage_stat(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); FileInfo file_info; uint32_t timestamp; @@ -244,21 +301,21 @@ static void js_storage_stat(struct mjs* mjs) { static void js_storage_remove(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove(storage, path))); } static void js_storage_rmrf(struct mjs* mjs) { const char* path; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &path); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_simply_remove_recursive(storage, path))); } static void js_storage_rename(struct mjs* mjs) { const char *old, *new; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&old), JS_ARG_STR(&new)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &old, &new); Storage* storage = JS_GET_CONTEXT(mjs); FS_Error status = storage_common_rename(storage, old, new); mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK)); @@ -266,7 +323,7 @@ static void js_storage_rename(struct mjs* mjs) { static void js_storage_copy(struct mjs* mjs) { const char *source, *dest; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&source), JS_ARG_STR(&dest)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &source, &dest); Storage* storage = JS_GET_CONTEXT(mjs); FS_Error status = storage_common_copy(storage, source, dest); mjs_return(mjs, mjs_mk_boolean(mjs, status == FSE_OK || status == FSE_EXIST)); @@ -274,7 +331,7 @@ static void js_storage_copy(struct mjs* mjs) { static void js_storage_fs_info(struct mjs* mjs) { const char* fs; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&fs)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_1_str_args, &fs); Storage* storage = JS_GET_CONTEXT(mjs); uint64_t total_space, free_space; if(storage_common_fs_info(storage, fs, &total_space, &free_space) != FSE_OK) { @@ -290,15 +347,19 @@ static void js_storage_fs_info(struct mjs* mjs) { } static void js_storage_next_available_filename(struct mjs* mjs) { + static const JsValueDeclaration js_storage_naf_arg_list[] = { + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeString), + JS_VALUE_SIMPLE(JsValueTypeInt32), + }; + static const JsValueArguments js_storage_naf_args = JS_VALUE_ARGS(js_storage_naf_arg_list); + const char *dir_path, *file_name, *file_ext; int32_t max_len; - JS_FETCH_ARGS_OR_RETURN( - mjs, - JS_EXACTLY, - JS_ARG_STR(&dir_path), - JS_ARG_STR(&file_name), - JS_ARG_STR(&file_ext), - JS_ARG_INT32(&max_len)); + JS_VALUE_PARSE_ARGS_OR_RETURN( + mjs, &js_storage_naf_args, &dir_path, &file_name, &file_ext, &max_len); + Storage* storage = JS_GET_CONTEXT(mjs); FuriString* next_name = furi_string_alloc(); storage_get_next_filename(storage, dir_path, file_name, file_ext, next_name, max_len); @@ -306,23 +367,27 @@ static void js_storage_next_available_filename(struct mjs* mjs) { furi_string_free(next_name); } -// ---=== path ops ===--- +// =============== +// Path operations +// =============== static void js_storage_are_paths_equal(struct mjs* mjs) { const char *path1, *path2; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&path1), JS_ARG_STR(&path2)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &path1, &path2); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_equivalent_path(storage, path1, path2))); } static void js_storage_is_subpath_of(struct mjs* mjs) { const char *parent, *child; - JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_STR(&parent), JS_ARG_STR(&child)); + JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &js_storage_2_str_args, &parent, &child); Storage* storage = JS_GET_CONTEXT(mjs); mjs_return(mjs, mjs_mk_boolean(mjs, storage_common_is_subdir(storage, parent, child))); } -// ---=== module ctor & dtor ===--- +// ================== +// Module ctor & dtor +// ================== static void* js_storage_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { UNUSED(modules); @@ -363,7 +428,9 @@ static void js_storage_destroy(void* data) { furi_record_close(RECORD_STORAGE); } -// ---=== boilerplate ===--- +// =========== +// Boilerplate +// =========== static const JsModuleDescriptor js_storage_desc = { "storage", diff --git a/applications/system/js_app/packages/create-fz-app/package.json b/applications/system/js_app/packages/create-fz-app/package.json index 216423396..7778104e2 100644 --- a/applications/system/js_app/packages/create-fz-app/package.json +++ b/applications/system/js_app/packages/create-fz-app/package.json @@ -1,6 +1,6 @@ { "name": "@flipperdevices/create-fz-app", - "version": "0.1.0", + "version": "0.1.1", "description": "Template package for JS apps Flipper Zero", "bin": "index.js", "type": "module", diff --git a/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml b/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml index 58f20a385..3f753df15 100644 --- a/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml +++ b/applications/system/js_app/packages/create-fz-app/pnpm-lock.yaml @@ -62,8 +62,8 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} eastasianwidth@0.2.0: @@ -240,7 +240,7 @@ snapshots: color-name@1.1.4: {} - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -256,7 +256,7 @@ snapshots: foreground-child@3.3.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 get-caller-file@2.0.5: {} diff --git a/applications/system/js_app/packages/create-fz-app/template/package.json b/applications/system/js_app/packages/create-fz-app/template/package.json index 7acdeccaa..322a8b58b 100644 --- a/applications/system/js_app/packages/create-fz-app/template/package.json +++ b/applications/system/js_app/packages/create-fz-app/template/package.json @@ -6,7 +6,7 @@ "start": "npm run build && node node_modules/@flipperdevices/fz-sdk/sdk.js upload" }, "devDependencies": { - "@flipperdevices/fz-sdk": "^0.1", + "@flipperdevices/fz-sdk": "^0.3", "typescript": "^5.6.3" } } \ No newline at end of file diff --git a/applications/system/js_app/packages/create-fz-app/template/tsconfig.json b/applications/system/js_app/packages/create-fz-app/template/tsconfig.json index c7b83cd5d..1e6fc6018 100644 --- a/applications/system/js_app/packages/create-fz-app/template/tsconfig.json +++ b/applications/system/js_app/packages/create-fz-app/template/tsconfig.json @@ -5,13 +5,14 @@ "module": "CommonJS", "noLib": true, "target": "ES2015", + "types": [], }, "files": [ "./node_modules/@flipperdevices/fz-sdk/global.d.ts", ], "include": [ "./**/*.ts", - "./**/*.js" + "./**/*.js", ], "exclude": [ "./node_modules/**/*", diff --git a/applications/system/js_app/packages/fz-sdk/global.d.ts b/applications/system/js_app/packages/fz-sdk/global.d.ts index d2e73f7de..4c7f217d0 100644 --- a/applications/system/js_app/packages/fz-sdk/global.d.ts +++ b/applications/system/js_app/packages/fz-sdk/global.d.ts @@ -72,7 +72,7 @@ * @brief Checks compatibility between the script and the JS SDK that the * firmware provides * - * @note You're looking at JS SDK v0.1 + * @note You're looking at JS SDK v0.3 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script @@ -92,7 +92,7 @@ declare function sdkCompatibilityStatus(expectedMajor: number, expectedMinor: nu * @brief Checks compatibility between the script and the JS SDK that the * firmware provides in a boolean fashion * - * @note You're looking at JS SDK v0.1 + * @note You're looking at JS SDK v0.3 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script @@ -105,7 +105,7 @@ declare function isSdkCompatible(expectedMajor: number, expectedMinor: number): * @brief Asks the user whether to continue executing the script if the versions * are not compatible. Does nothing if they are. * - * @note You're looking at JS SDK v0.1 + * @note You're looking at JS SDK v0.3 * * @param expectedMajor JS SDK major version expected by the script * @param expectedMinor JS SDK minor version expected by the script @@ -202,6 +202,13 @@ declare function chr(n: number): string | null; */ declare function require(module: string): any; +/** + * @brief Exit JavaScript with given message + * @param message The error message to show to user + * @version Added in JS SDK 0.1 + */ +declare function die(message: string): never; + /** * @brief mJS Foreign Pointer type * diff --git a/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts b/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts index b484ebbf6..cd5ce2b60 100644 --- a/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gpio/index.d.ts @@ -75,6 +75,34 @@ export interface Pin { * @version Added in JS SDK 0.1 */ interrupt(): Contract; + /** + * Determines whether this pin supports PWM. If `false`, all other + * PWM-related methods on this pin will throw an error when called. + * @note On Flipper Zero only pins PA4 and PA7 support PWM + * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + */ + isPwmSupported(): boolean; + /** + * Sets PWM parameters and starts the PWM. Configures the pin with + * `{ direction: "out", outMode: "push_pull" }`. Throws an error if PWM is + * not supported on this pin. + * @param freq Frequency in Hz + * @param duty Duty cycle in % + * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + */ + pwmWrite(freq: number, duty: number): void; + /** + * Determines whether PWM is running. Throws an error if PWM is not + * supported on this pin. + * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + */ + isPwmRunning(): boolean; + /** + * Stops PWM. Does not restore previous pin configuration. Throws an error + * if PWM is not supported on this pin. + * @version Added in JS SDK 0.2, extra feature `"gpio-pwm"` + */ + pwmStop(): void; } /** diff --git a/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts b/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts index 5556e7fbb..7080ad3ae 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/byte_input.d.ts @@ -33,9 +33,10 @@ type Props = { length: number, defaultData: Uint8Array | ArrayBuffer, } -declare class ByteInput extends View { +type Child = never; +declare class ByteInput extends View { input: Contract; } -declare class ByteInputFactory extends ViewFactory { } +declare class ByteInputFactory extends ViewFactory { } declare const factory: ByteInputFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts b/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts index 9bd0c3966..2fffcb873 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/dialog.d.ts @@ -37,9 +37,10 @@ type Props = { center: string, right: string, } -declare class Dialog extends View { +type Child = never; +declare class Dialog extends View { input: Contract<"left" | "center" | "right">; } -declare class DialogFactory extends ViewFactory { } +declare class DialogFactory extends ViewFactory { } declare const factory: DialogFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts b/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts index 49e591426..6a848bd03 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/empty_screen.d.ts @@ -26,7 +26,8 @@ import type { View, ViewFactory } from "."; type Props = {}; -declare class EmptyScreen extends View { } -declare class EmptyScreenFactory extends ViewFactory { } +type Child = never; +declare class EmptyScreen extends View { } +declare class EmptyScreenFactory extends ViewFactory { } declare const factory: EmptyScreenFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts new file mode 100644 index 000000000..8d2b68bce --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/icon.d.ts @@ -0,0 +1,18 @@ +export type BuiltinIcon = "DolphinWait_59x54" | "js_script_10px"; + +export type IconData = symbol & { "__tag__": "icon" }; +// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist. + +/** + * Gets a built-in firmware icon for use in GUI + * @param icon Name of the icon + * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + */ +export declare function getBuiltin(icon: BuiltinIcon): IconData; + +/** + * Loads a .fxbm icon (XBM Flipper sprite, from flipperzero-game-engine) for use in GUI + * @param path Path to the .fxbm file + * @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` + */ +export declare function loadFxbm(path: string): IconData; diff --git a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts index 93a6846c2..969b6934e 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/index.d.ts @@ -26,23 +26,23 @@ * assumes control over the entire viewport and all input events. Different * types of views are available (not all of which are unfortunately currently * implemented in JS): - * | View | Has JS adapter? | - * |----------------------|------------------| - * | `button_menu` | ❌ | - * | `button_panel` | ❌ | - * | `byte_input` | ✅ | - * | `dialog_ex` | ✅ (as `dialog`) | - * | `empty_screen` | ✅ | - * | `file_browser` | ❌ | - * | `loading` | ✅ | - * | `menu` | ❌ | - * | `number_input` | ❌ | - * | `popup` | ❌ | - * | `submenu` | ✅ | - * | `text_box` | ✅ | - * | `text_input` | ✅ | - * | `variable_item_list` | ❌ | - * | `widget` | ❌ | + * | View | Has JS adapter? | + * |----------------------|-----------------------| + * | `button_menu` | ❌ | + * | `button_panel` | ❌ | + * | `byte_input` | ✅ | + * | `dialog_ex` | ✅ (as `dialog`) | + * | `empty_screen` | ✅ | + * | `file_browser` | ✅ (as `file_picker`) | + * | `loading` | ✅ | + * | `menu` | ❌ | + * | `number_input` | ❌ | + * | `popup` | ❌ | + * | `submenu` | ✅ | + * | `text_box` | ✅ | + * | `text_input` | ✅ | + * | `variable_item_list` | ❌ | + * | `widget` | ✅ | * * In JS, each view has its own set of properties (or just "props"). The * programmer can manipulate these properties in two ways: @@ -121,7 +121,7 @@ import type { Contract } from "../event_loop"; type Properties = { [K: string]: any }; -export declare class View { +export declare class View { /** * Assign value to property by name * @param property Name of the property @@ -129,9 +129,26 @@ export declare class View { * @version Added in JS SDK 0.1 */ set

(property: P, value: Props[P]): void; + /** + * Adds a child to the View + * @param child Child to add + * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + */ + addChild(child: C): void; + /** + * Removes all children from the View + * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + */ + resetChildren(): void; + /** + * Removes all previous children from the View and assigns new children + * @param children The list of children to assign + * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + */ + setChildren(children: Child[]): void; } -export declare class ViewFactory> { +export declare class ViewFactory> { /** * Create view instance with default values, can be changed later with set() * @version Added in JS SDK 0.1 @@ -140,9 +157,10 @@ export declare class ViewFactory /** * Create view instance with custom values, can be changed later with set() * @param initial Dictionary of property names to values - * @version Added in JS SDK 0.1 + * @param children Optional list of children to add to the view + * @version Added in JS SDK 0.1; amended in JS SDK 0.2, extra feature `"gui-widget"` */ - makeWith(initial: Partial): V; + makeWith(initial: Partial, children?: Child[]): V; } /** @@ -163,7 +181,7 @@ declare class ViewDispatcher { * View object currently shown * @version Added in JS SDK 0.1 */ - currentView: View; + currentView: View; /** * Sends a number to the custom event handler * @param event number to send @@ -175,7 +193,7 @@ declare class ViewDispatcher { * @param assoc View-ViewDispatcher association as returned by `add` * @version Added in JS SDK 0.1 */ - switchTo(assoc: View): void; + switchTo(assoc: View): void; /** * Sends this ViewDispatcher to the front or back, above or below all other * GUI viewports diff --git a/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts b/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts index b8b10c43a..d636f21ca 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/loading.d.ts @@ -27,7 +27,8 @@ import type { View, ViewFactory } from "."; type Props = {}; -declare class Loading extends View { } -declare class LoadingFactory extends ViewFactory { } +type Child = never; +declare class Loading extends View { } +declare class LoadingFactory extends ViewFactory { } declare const factory: LoadingFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts b/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts index 31e08aab8..e73856bee 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/submenu.d.ts @@ -31,9 +31,10 @@ type Props = { header: string, items: string[], }; -declare class Submenu extends View { +type Child = never; +declare class Submenu extends View { chosen: Contract; } -declare class SubmenuFactory extends ViewFactory { } +declare class SubmenuFactory extends ViewFactory { } declare const factory: SubmenuFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts b/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts index a46ec73fa..32003bd95 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/text_box.d.ts @@ -33,9 +33,10 @@ type Props = { font: "text" | "hex", focus: "start" | "end", } -declare class TextBox extends View { +type Child = never; +declare class TextBox extends View { chosen: Contract; } -declare class TextBoxFactory extends ViewFactory { } +declare class TextBoxFactory extends ViewFactory { } declare const factory: TextBoxFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts b/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts index 5d64b038b..9d0d180ba 100644 --- a/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts +++ b/applications/system/js_app/packages/fz-sdk/gui/text_input.d.ts @@ -37,9 +37,10 @@ type Props = { defaultText: string, defaultTextClear: boolean, } -declare class TextInput extends View { +type Child = never; +declare class TextInput extends View { input: Contract; } -declare class TextInputFactory extends ViewFactory { } +declare class TextInputFactory extends ViewFactory { } declare const factory: TextInputFactory; export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts b/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts new file mode 100644 index 000000000..bf4aab22b --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/gui/widget.d.ts @@ -0,0 +1,70 @@ +/** + * Displays a combination of custom elements on one screen. + * + * Sample screenshot of the view + * + * ```js + * let eventLoop = require("event_loop"); + * let gui = require("gui"); + * let emptyView = require("gui/widget"); + * ``` + * + * This module depends on the `gui` module, which in turn depends on the + * `event_loop` module, so they _must_ be imported in this order. It is also + * recommended to conceptualize these modules first before using this one. + * + * # Example + * For an example refer to the GUI example. + * + * # View props + * This view does not have any props. + * + * # Children + * This view has the elements as its children. + * + * @version Added in JS SDK 0.2, extra feature `"gui-widget"` + * @module + */ + +import type { View, ViewFactory } from "."; +import type { IconData } from "./icon"; +import type { Contract } from "../event_loop"; + +type Position = { x: number, y: number }; +type Size = { w: number, h: number }; +type Alignment = { align: `${"t" | "c" | "b"}${"l" | "m" | "r"}` }; +type Font = { font: "primary" | "secondary" | "keyboard" | "big_numbers" }; +type Text = { text: string }; + +type StringMultilineElement = { element: "string_multiline" } & Position & Alignment & Font & Text; +type StringElement = { element: "string" } & Position & Alignment & Font & Text; +type TextBoxElement = { element: "text_box", stripToDots: boolean } & Position & Size & Alignment & Text; +type TextScrollElement = { element: "text_scroll" } & Position & Size & Text; +type ButtonElement = { element: "button", button: "left" | "center" | "right" } & Text; +type IconElement = { element: "icon", iconData: IconData } & Position; +type RectElement = { element: "rect", radius: number, fill: boolean } & Position & Size; /** @version Amended in JS SDK 0.3, extra feature `"gui-widget-extras"` */ +type CircleElement = { element: "circle", radius: number, fill: boolean } & Position; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */ +type LineElement = { element: "line", x1: number, y1: number, x2: number, y2: number }; /** @version Added in JS SDK 0.3, extra feature `"gui-widget-extras"` */ + +type Element = StringMultilineElement + | StringElement + | TextBoxElement + | TextScrollElement + | ButtonElement + | IconElement + | RectElement + | CircleElement + | LineElement; + +type Props = {}; +type Child = Element; +declare class Widget extends View { + /** + * Event source for buttons. Only gets fired if there's a corresponding + * button element. + */ + button: Contract<"left" | "center" | "right">; +} +declare class WidgetFactory extends ViewFactory { } +declare const factory: WidgetFactory; +export = factory; diff --git a/applications/system/js_app/packages/fz-sdk/package.json b/applications/system/js_app/packages/fz-sdk/package.json index 4d18f3f20..3ab108e48 100644 --- a/applications/system/js_app/packages/fz-sdk/package.json +++ b/applications/system/js_app/packages/fz-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@flipperdevices/fz-sdk", - "version": "0.1.1", + "version": "0.3.0", "description": "Type declarations and documentation for native JS modules available on Flipper Zero", "keywords": [ "flipper", diff --git a/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml b/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml index 45944a854..67d3bde82 100644 --- a/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml +++ b/applications/system/js_app/packages/fz-sdk/pnpm-lock.yaml @@ -8,13 +8,6 @@ importers: .: dependencies: - prompts: - specifier: ^2.4.2 - version: 2.4.2 - serialport: - specifier: ^12.0.0 - version: 12.0.0 - devDependencies: esbuild: specifier: ^0.24.0 version: 0.24.0 @@ -24,6 +17,12 @@ importers: json5: specifier: ^2.2.3 version: 2.2.3 + prompts: + specifier: ^2.4.2 + version: 2.4.2 + serialport: + specifier: ^12.0.0 + version: 12.0.0 typedoc: specifier: ^0.26.10 version: 0.26.10(typescript@5.6.3) diff --git a/applications/system/js_app/packages/fz-sdk/sdk.js b/applications/system/js_app/packages/fz-sdk/sdk.js index 2eecf032d..44663203d 100644 --- a/applications/system/js_app/packages/fz-sdk/sdk.js +++ b/applications/system/js_app/packages/fz-sdk/sdk.js @@ -85,9 +85,21 @@ async function build(config) { async function upload(config) { const appFile = fs.readFileSync(config.input, "utf8"); - const flippers = (await SerialPort.list()).filter(x => x.serialNumber?.startsWith("flip_")); + const serialPorts = await SerialPort.list(); - if (!flippers) { + let flippers = serialPorts + .filter(x => x.serialNumber?.startsWith("flip_")) + .map(x => ({ path: x.path, name: x.serialNumber.replace("flip_", "") })); + + if (!flippers.length) { + // some Windows installations don't report the serial number correctly; + // filter by STM VCP VID:PID instead + flippers = serialPorts + .filter(x => x?.vendorId === "0483" && x?.productId === "5740") + .map(x => ({ path: x.path, name: x.path })); + } + + if (!flippers.length) { console.error("No Flippers found"); process.exit(1); } diff --git a/applications/system/js_app/packages/fz-sdk/serial/index.d.ts b/applications/system/js_app/packages/fz-sdk/serial/index.d.ts index 3c249352e..5064c4213 100644 --- a/applications/system/js_app/packages/fz-sdk/serial/index.d.ts +++ b/applications/system/js_app/packages/fz-sdk/serial/index.d.ts @@ -4,16 +4,33 @@ * @module */ +export interface Framing { + /** + * @note 6 data bits can only be selected when parity is enabled (even or + * odd) + * @note 9 data bits can only be selected when parity is disabled (none) + */ + dataBits: "6" | "7" | "8" | "9"; + parity: "none" | "even" | "odd"; + /** + * @note LPUART only supports whole stop bit lengths (i.e. 1 and 2 but not + * 0.5 and 1.5) + */ + stopBits: "0.5" | "1" | "1.5" | "2"; +} + /** * @brief Initializes the serial port * * Automatically disables Expansion module service to prevent interference. * - * @param port The port to initialize (`"lpuart"` or `"start"`) - * @param baudRate + * @param port The port to initialize (`"lpuart"` or `"usart"`) + * @param baudRate Baud rate + * @param framing See `Framing` type * @version Added in JS SDK 0.1 + * @version Added `framing` parameter in JS SDK 0.3, extra feature `"serial-framing"` */ -export declare function setup(port: "lpuart" | "usart", baudRate: number): void; +export declare function setup(port: "lpuart" | "usart", baudRate: number, framing?: Framing): void; /** * @brief Writes data to the serial port diff --git a/applications/system/js_app/plugin_api/app_api_table_i.h b/applications/system/js_app/plugin_api/app_api_table_i.h index b2debbde8..76556bcdd 100644 --- a/applications/system/js_app/plugin_api/app_api_table_i.h +++ b/applications/system/js_app/plugin_api/app_api_table_i.h @@ -1,4 +1,5 @@ -#include "js_plugin_api.h" +#include "../js_modules.h" + /* * A list of app's private functions and objects to expose for plugins. * It is used to generate a table of symbols for import resolver to use. @@ -8,4 +9,16 @@ static constexpr auto app_api_table = sort(create_array_t( API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)), API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)), API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)), - API_METHOD(js_module_get, void*, (JsModules*, const char*)))); + API_METHOD(js_module_get, void*, (JsModules*, const char*)), + 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, + ...)))); diff --git a/applications/system/js_app/plugin_api/js_plugin_api.h b/applications/system/js_app/plugin_api/js_plugin_api.h deleted file mode 100644 index 421b68576..000000000 --- a/applications/system/js_app/plugin_api/js_plugin_api.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void JsModules; - -bool js_delay_with_flags(struct mjs* mjs, uint32_t time); - -void js_flags_set(struct mjs* mjs, uint32_t flags); - -uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); - -void* js_module_get(JsModules* modules, const char* name); - -#ifdef __cplusplus -} -#endif diff --git a/applications/system/updater/application.fam b/applications/system/updater/application.fam index a693fa6f5..0c56a06ef 100644 --- a/applications/system/updater/application.fam +++ b/applications/system/updater/application.fam @@ -27,7 +27,7 @@ App( provides=["updater_start"], entry_point="updater_srv", stack_size=2 * 1024, - order=10, + order=20, ) App( @@ -35,5 +35,5 @@ App( apptype=FlipperAppType.STARTUP, entry_point="updater_on_system_start", requires=["updater_app"], - order=110, + order=90, ) diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index 56a16bd9d..bad8f0cd6 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -1,12 +1,14 @@ #include #include -#include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -63,8 +65,8 @@ static const CliSubcommand update_cli_subcommands[] = { {.command = "help", .handler = updater_cli_help}, }; -static void updater_cli_ep(Cli* cli, FuriString* args, void* context) { - UNUSED(cli); +static void updater_cli_ep(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); UNUSED(context); FuriString* subcommand; subcommand = furi_string_alloc(); @@ -105,8 +107,8 @@ static void updater_start_app(void* context, uint32_t arg) { void updater_on_system_start(void) { #ifdef SRV_CLI - Cli* cli = (Cli*)furi_record_open(RECORD_CLI); - cli_add_command(cli, "update", CliCommandFlagDefault, updater_cli_ep, NULL); + CliRegistry* registry = furi_record_open(RECORD_CLI); + cli_registry_add_command(registry, "update", CliCommandFlagDefault, updater_cli_ep, NULL); furi_record_close(RECORD_CLI); #else UNUSED(updater_cli_ep); diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_0.png b/assets/dolphin/external/L1_Doom_128x64/frame_0.png new file mode 100644 index 000000000..974fda986 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_1.png b/assets/dolphin/external/L1_Doom_128x64/frame_1.png new file mode 100644 index 000000000..3a9a3a71a Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_10.png b/assets/dolphin/external/L1_Doom_128x64/frame_10.png new file mode 100644 index 000000000..7cf69cc1b Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_11.png b/assets/dolphin/external/L1_Doom_128x64/frame_11.png new file mode 100644 index 000000000..99eb9b2a1 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_12.png b/assets/dolphin/external/L1_Doom_128x64/frame_12.png new file mode 100644 index 000000000..973f97378 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_13.png b/assets/dolphin/external/L1_Doom_128x64/frame_13.png new file mode 100644 index 000000000..1a7e06051 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_14.png b/assets/dolphin/external/L1_Doom_128x64/frame_14.png new file mode 100644 index 000000000..428ac9f1f Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_15.png b/assets/dolphin/external/L1_Doom_128x64/frame_15.png new file mode 100644 index 000000000..aad59f943 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_16.png b/assets/dolphin/external/L1_Doom_128x64/frame_16.png new file mode 100644 index 000000000..4dfc84535 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_17.png b/assets/dolphin/external/L1_Doom_128x64/frame_17.png new file mode 100644 index 000000000..81f4f8beb Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_18.png b/assets/dolphin/external/L1_Doom_128x64/frame_18.png new file mode 100644 index 000000000..78ad1c754 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_19.png b/assets/dolphin/external/L1_Doom_128x64/frame_19.png new file mode 100644 index 000000000..efd1d6b0e Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_2.png b/assets/dolphin/external/L1_Doom_128x64/frame_2.png new file mode 100644 index 000000000..d740f5e33 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_20.png b/assets/dolphin/external/L1_Doom_128x64/frame_20.png new file mode 100644 index 000000000..24693dfe4 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_21.png b/assets/dolphin/external/L1_Doom_128x64/frame_21.png new file mode 100644 index 000000000..f08e98d81 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_21.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_22.png b/assets/dolphin/external/L1_Doom_128x64/frame_22.png new file mode 100644 index 000000000..baa327482 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_22.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_23.png b/assets/dolphin/external/L1_Doom_128x64/frame_23.png new file mode 100644 index 000000000..f1b7fe627 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_23.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_24.png b/assets/dolphin/external/L1_Doom_128x64/frame_24.png new file mode 100644 index 000000000..24693dfe4 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_24.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_25.png b/assets/dolphin/external/L1_Doom_128x64/frame_25.png new file mode 100644 index 000000000..2f12df072 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_25.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_26.png b/assets/dolphin/external/L1_Doom_128x64/frame_26.png new file mode 100644 index 000000000..e90099bd6 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_26.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_27.png b/assets/dolphin/external/L1_Doom_128x64/frame_27.png new file mode 100644 index 000000000..aaf9e6654 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_27.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_28.png b/assets/dolphin/external/L1_Doom_128x64/frame_28.png new file mode 100644 index 000000000..6e4a77679 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_28.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_29.png b/assets/dolphin/external/L1_Doom_128x64/frame_29.png new file mode 100644 index 000000000..06fa8b6e4 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_29.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_3.png b/assets/dolphin/external/L1_Doom_128x64/frame_3.png new file mode 100644 index 000000000..81bff4b92 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_30.png b/assets/dolphin/external/L1_Doom_128x64/frame_30.png new file mode 100644 index 000000000..6d6717c40 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_30.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_31.png b/assets/dolphin/external/L1_Doom_128x64/frame_31.png new file mode 100644 index 000000000..25770b6fe Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_31.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_32.png b/assets/dolphin/external/L1_Doom_128x64/frame_32.png new file mode 100644 index 000000000..24ee19f40 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_32.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_33.png b/assets/dolphin/external/L1_Doom_128x64/frame_33.png new file mode 100644 index 000000000..3581d8d59 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_33.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_34.png b/assets/dolphin/external/L1_Doom_128x64/frame_34.png new file mode 100644 index 000000000..914390376 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_34.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_35.png b/assets/dolphin/external/L1_Doom_128x64/frame_35.png new file mode 100644 index 000000000..6e6ac6a02 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_35.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_36.png b/assets/dolphin/external/L1_Doom_128x64/frame_36.png new file mode 100644 index 000000000..2f37e6371 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_36.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_37.png b/assets/dolphin/external/L1_Doom_128x64/frame_37.png new file mode 100644 index 000000000..e6f374c4f Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_37.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_38.png b/assets/dolphin/external/L1_Doom_128x64/frame_38.png new file mode 100644 index 000000000..189a16f7b Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_38.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_4.png b/assets/dolphin/external/L1_Doom_128x64/frame_4.png new file mode 100644 index 000000000..424f481ff Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_5.png b/assets/dolphin/external/L1_Doom_128x64/frame_5.png new file mode 100644 index 000000000..fa7e6ad10 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_6.png b/assets/dolphin/external/L1_Doom_128x64/frame_6.png new file mode 100644 index 000000000..063f13bae Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_7.png b/assets/dolphin/external/L1_Doom_128x64/frame_7.png new file mode 100644 index 000000000..0995b83bf Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_8.png b/assets/dolphin/external/L1_Doom_128x64/frame_8.png new file mode 100644 index 000000000..f5911b1b8 Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/frame_9.png b/assets/dolphin/external/L1_Doom_128x64/frame_9.png new file mode 100644 index 000000000..57491c2ed Binary files /dev/null and b/assets/dolphin/external/L1_Doom_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Doom_128x64/meta.txt b/assets/dolphin/external/L1_Doom_128x64/meta.txt new file mode 100644 index 000000000..838239623 --- /dev/null +++ b/assets/dolphin/external/L1_Doom_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 15 +Active frames: 24 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 \ No newline at end of file diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_0.png b/assets/dolphin/external/L1_Showtime_128x64/frame_0.png new file mode 100755 index 000000000..7eed9a024 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_1.png b/assets/dolphin/external/L1_Showtime_128x64/frame_1.png new file mode 100755 index 000000000..827730087 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_10.png b/assets/dolphin/external/L1_Showtime_128x64/frame_10.png new file mode 100755 index 000000000..c627bd6f9 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_11.png b/assets/dolphin/external/L1_Showtime_128x64/frame_11.png new file mode 100755 index 000000000..0535101be Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_12.png b/assets/dolphin/external/L1_Showtime_128x64/frame_12.png new file mode 100755 index 000000000..1284019a8 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_13.png b/assets/dolphin/external/L1_Showtime_128x64/frame_13.png new file mode 100755 index 000000000..bc71a08a0 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_14.png b/assets/dolphin/external/L1_Showtime_128x64/frame_14.png new file mode 100755 index 000000000..1444ab836 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_15.png b/assets/dolphin/external/L1_Showtime_128x64/frame_15.png new file mode 100755 index 000000000..0945008a5 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_16.png b/assets/dolphin/external/L1_Showtime_128x64/frame_16.png new file mode 100755 index 000000000..0d1246fc7 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_17.png b/assets/dolphin/external/L1_Showtime_128x64/frame_17.png new file mode 100755 index 000000000..4d0b7227f Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_18.png b/assets/dolphin/external/L1_Showtime_128x64/frame_18.png new file mode 100755 index 000000000..d53d074e6 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_19.png b/assets/dolphin/external/L1_Showtime_128x64/frame_19.png new file mode 100755 index 000000000..0d421d372 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_2.png b/assets/dolphin/external/L1_Showtime_128x64/frame_2.png new file mode 100755 index 000000000..a52e051b9 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_20.png b/assets/dolphin/external/L1_Showtime_128x64/frame_20.png new file mode 100755 index 000000000..a5962bd2c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_21.png b/assets/dolphin/external/L1_Showtime_128x64/frame_21.png new file mode 100755 index 000000000..5113c1095 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_21.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_22.png b/assets/dolphin/external/L1_Showtime_128x64/frame_22.png new file mode 100755 index 000000000..88ba06d37 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_22.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_23.png b/assets/dolphin/external/L1_Showtime_128x64/frame_23.png new file mode 100755 index 000000000..6507bdc6b Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_23.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_24.png b/assets/dolphin/external/L1_Showtime_128x64/frame_24.png new file mode 100755 index 000000000..5d4360a82 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_24.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_25.png b/assets/dolphin/external/L1_Showtime_128x64/frame_25.png new file mode 100755 index 000000000..817c78ab5 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_25.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_26.png b/assets/dolphin/external/L1_Showtime_128x64/frame_26.png new file mode 100755 index 000000000..93f02f48e Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_26.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_27.png b/assets/dolphin/external/L1_Showtime_128x64/frame_27.png new file mode 100755 index 000000000..2b856cc79 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_27.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_28.png b/assets/dolphin/external/L1_Showtime_128x64/frame_28.png new file mode 100755 index 000000000..bcf538072 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_28.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_29.png b/assets/dolphin/external/L1_Showtime_128x64/frame_29.png new file mode 100755 index 000000000..68b32d80c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_29.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_3.png b/assets/dolphin/external/L1_Showtime_128x64/frame_3.png new file mode 100755 index 000000000..e7bf65287 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_30.png b/assets/dolphin/external/L1_Showtime_128x64/frame_30.png new file mode 100755 index 000000000..4d34f9afb Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_30.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_31.png b/assets/dolphin/external/L1_Showtime_128x64/frame_31.png new file mode 100755 index 000000000..274a16074 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_31.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_32.png b/assets/dolphin/external/L1_Showtime_128x64/frame_32.png new file mode 100755 index 000000000..7dbb729ef Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_32.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_33.png b/assets/dolphin/external/L1_Showtime_128x64/frame_33.png new file mode 100755 index 000000000..3cfbe5a98 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_33.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_34.png b/assets/dolphin/external/L1_Showtime_128x64/frame_34.png new file mode 100755 index 000000000..4e64d9db3 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_34.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_35.png b/assets/dolphin/external/L1_Showtime_128x64/frame_35.png new file mode 100755 index 000000000..09fe5c1cd Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_35.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_36.png b/assets/dolphin/external/L1_Showtime_128x64/frame_36.png new file mode 100755 index 000000000..4139bd8b5 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_36.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_37.png b/assets/dolphin/external/L1_Showtime_128x64/frame_37.png new file mode 100755 index 000000000..0384fbdae Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_37.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_38.png b/assets/dolphin/external/L1_Showtime_128x64/frame_38.png new file mode 100755 index 000000000..2632807f9 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_38.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_39.png b/assets/dolphin/external/L1_Showtime_128x64/frame_39.png new file mode 100755 index 000000000..f257489a1 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_39.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_4.png b/assets/dolphin/external/L1_Showtime_128x64/frame_4.png new file mode 100755 index 000000000..8a0c1734e Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_40.png b/assets/dolphin/external/L1_Showtime_128x64/frame_40.png new file mode 100755 index 000000000..cbcff2c89 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_40.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_41.png b/assets/dolphin/external/L1_Showtime_128x64/frame_41.png new file mode 100755 index 000000000..4377a91c6 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_41.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_42.png b/assets/dolphin/external/L1_Showtime_128x64/frame_42.png new file mode 100755 index 000000000..30b446707 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_42.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_43.png b/assets/dolphin/external/L1_Showtime_128x64/frame_43.png new file mode 100755 index 000000000..10d9579a3 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_43.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_44.png b/assets/dolphin/external/L1_Showtime_128x64/frame_44.png new file mode 100755 index 000000000..6d9362c51 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_44.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_45.png b/assets/dolphin/external/L1_Showtime_128x64/frame_45.png new file mode 100755 index 000000000..f834ad6c1 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_45.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_46.png b/assets/dolphin/external/L1_Showtime_128x64/frame_46.png new file mode 100755 index 000000000..4c799effb Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_46.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_47.png b/assets/dolphin/external/L1_Showtime_128x64/frame_47.png new file mode 100755 index 000000000..017549d29 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_47.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_48.png b/assets/dolphin/external/L1_Showtime_128x64/frame_48.png new file mode 100755 index 000000000..28497ac23 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_48.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_49.png b/assets/dolphin/external/L1_Showtime_128x64/frame_49.png new file mode 100755 index 000000000..5a25c32da Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_49.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_5.png b/assets/dolphin/external/L1_Showtime_128x64/frame_5.png new file mode 100755 index 000000000..04ad4360c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_6.png b/assets/dolphin/external/L1_Showtime_128x64/frame_6.png new file mode 100755 index 000000000..86bcbf48c Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_7.png b/assets/dolphin/external/L1_Showtime_128x64/frame_7.png new file mode 100755 index 000000000..3e2a2c739 Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_8.png b/assets/dolphin/external/L1_Showtime_128x64/frame_8.png new file mode 100755 index 000000000..d9babe8dc Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/frame_9.png b/assets/dolphin/external/L1_Showtime_128x64/frame_9.png new file mode 100755 index 000000000..24dcababb Binary files /dev/null and b/assets/dolphin/external/L1_Showtime_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Showtime_128x64/meta.txt b/assets/dolphin/external/L1_Showtime_128x64/meta.txt new file mode 100755 index 000000000..da24febdc --- /dev/null +++ b/assets/dolphin/external/L1_Showtime_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 26 +Active frames: 26 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 41 42 43 44 45 46 47 48 49 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 69 +Y: 47 +Text: SHOWTIME! +AlignH: Left +AlignV: Center +StartFrame: 41 +EndFrame: 44 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 94183bd9a..20eef3364 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -195,18 +195,32 @@ Min butthurt: 0 Max butthurt: 8 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 Name: L3_Intruder_alert_128x64 Min butthurt: 0 Max butthurt: 12 Min level: 3 Max level: 3 -Weight: 4 +Weight: 3 Name: L1_Procrastinating_128x64 Min butthurt: 0 Max butthurt: 8 Min level: 1 Max level: 3 -Weight: 6 +Weight: 3 + +Name: L1_Showtime_128x64 +Min butthurt: 0 +Max butthurt: 10 +Min level: 1 +Max level: 3 +Weight: 4 + +Name: L1_Doom_128x64 +Min butthurt: 0 +Max butthurt: 13 +Min level: 1 +Max level: 3 +Weight: 4 diff --git a/assets/icons/Archive/settings_10px.png b/assets/icons/Archive/settings_10px.png new file mode 100644 index 000000000..2c1e4a262 Binary files /dev/null and b/assets/icons/Archive/settings_10px.png differ diff --git a/assets/protobuf b/assets/protobuf index 6c7c0d55e..1c84fa489 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 6c7c0d55e82cb89223cf4890a540af4cff837fa7 +Subproject commit 1c84fa48919cbb71d1cc65236fc0ee36740e24c6 diff --git a/documentation/FuriHalDebuging.md b/documentation/FuriHalDebugging.md similarity index 100% rename from documentation/FuriHalDebuging.md rename to documentation/FuriHalDebugging.md diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index 9711c6ae1..5d04c8f67 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -43,7 +43,7 @@ To add unit tests for your protocol, follow these steps: 1. Create a file named `test_.irtest` in the [assets](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests/infrared) directory. 2. Fill it with the test data (more on it below). -3. Add the test code to [infrared_test.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/infrared/infrared_test.c). +3. Add the test code to [infrared_test.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/tests/infrared/infrared_test.c). 4. Build and install firmware with resources, install it on your Flipper and run the tests to see if they pass. ##### Test data format diff --git a/documentation/doxygen/Doxyfile.cfg b/documentation/doxygen/Doxyfile.cfg index e01631749..e229209de 100644 --- a/documentation/doxygen/Doxyfile.cfg +++ b/documentation/doxygen/Doxyfile.cfg @@ -759,7 +759,7 @@ GENERATE_BUGLIST = YES # the documentation. # The default value is: YES. -GENERATE_DEPRECATEDLIST= YES +GENERATE_DEPRECATEDLIST= NO # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond @@ -1042,6 +1042,7 @@ EXCLUDE = $(DOXY_SRC_ROOT)/lib/mlib \ $(DOXY_SRC_ROOT)/applications/plugins/dap_link/lib/free-dap \ $(DOXY_SRC_ROOT)/applications/debug \ $(DOXY_SRC_ROOT)/applications/main \ + $(DOXY_SRC_ROOT)/applications/system/js_app/packages \ $(DOXY_SRC_ROOT)/applications/settings \ $(DOXY_SRC_ROOT)/lib/micro-ecc \ $(DOXY_SRC_ROOT)/lib/ReadMe.md \ diff --git a/documentation/doxygen/js.dox b/documentation/doxygen/js.dox index f5c609dd1..fadf0c023 100644 --- a/documentation/doxygen/js.dox +++ b/documentation/doxygen/js.dox @@ -1,30 +1,39 @@ /** @page js JavaScript -This page contains some information on the Flipper Zero scripting engine, which is based on a modified mJS library. +Flipper Zero's built-in JavaScript engine enables you to run lightweight scripts, similar to full-fledged C/C++ apps. Scripts can be shared, copied to a microSD card, and launched directly from the Flipper Zero menu — no precompilation needed. -- [Brief mJS description](https://github.com/cesanta/mjs/blob/master/README.md) -- @subpage js_data_types -- @subpage js_builtin +## Get started with JavaScript -## JavaScript modules +- @subpage js_about_js_engine — Learn about the implementation, advantages and limitations of our JavaScript engine -JS modules use the Flipper app plugin system. Each module is compiled into a `.fal` library file and is located on a microSD card. Here is a list of implemented modules: +- @subpage js_your_first_js_app — Create a simple app and run it using the Flipper Zero UI or CLI -- @subpage js_badusb - BadUSB module -- @subpage js_serial - Serial module -- @subpage js_math - Math module -- @subpage js_notification - Notifications module -- @subpage js_event_loop - Event Loop module -- @subpage js_gpio - GPIO module -- @subpage js_gui - GUI module and its submodules: - - @subpage js_gui__submenu - Submenu view - - @subpage js_gui__loading - Hourglass (Loading) view - - @subpage js_gui__empty_screen - Empty view - - @subpage js_gui__text_input - Keyboard-like text input - - @subpage js_gui__text_box - Simple multiline text box - - @subpage js_gui__dialog - Dialog with up to 3 options +- @subpage js_developing_apps_using_js_sdk — Learn how to install and use the JavaScript SDK for fast app debugging -All modules have corresponding TypeScript declaration files, so you can set up your IDE to show suggestions when writing JS scripts. +- @subpage js_using_js_modules — Learn how you can use JS modules in your apps + +## JavaScript modules {#js_modules} + +- @subpage js_badusb — This module allows you to emulate a standard USB keyboard +- @subpage js_event_loop — The module for easy event-based developing +- @subpage js_flipper — This module allows to query device information +- @subpage js_gpio — This module allows you to control GPIO pins +- @subpage js_gui — This module allows you to use GUI (graphical user interface) +- @subpage js_math — This module contains mathematical methods and constants +- @subpage js_notification — This module allows you to use LED, speaker and vibro for notifications +- @subpage js_serial — The module for interaction with external devices via UART +- @subpage js_storage — The module for accessing the filesystem + +## Examples {#js_examples} + +- [Our examples (GitHub)](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/system/js_app/examples/apps/Scripts) — Pre-installed with the firmware, so you can run them directly from the Flipper Zero menu (**Apps → Scripts**) +- [Featured: Derek Jamison's examples (GitHub)](https://github.com/jamisonderek/flipper-zero-tutorials/tree/main/js) — Come with detailed video guides for most scripts on [his YouTube channel](https://www.youtube.com/@MrDerekJamison) +- Just google "flipper zero javascript examples" + +## Other resources + +- @subpage js_data_types — A list of data types you can use in your JS scripts +- @subpage js_builtin — A list of functions you can use without including any JS modules */ diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 1bac3c4aa..a26f12489 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -57,19 +57,17 @@ Pause script execution by a defined time. ### Modifier keys -Can be combined with a special key command or a single character. -| Command | Notes | -| -------------- | ---------- | -| CONTROL / CTRL | | -| SHIFT | | -| ALT | | -| WINDOWS / GUI | | -| CTRL-ALT | CTRL+ALT | -| CTRL-SHIFT | CTRL+SHIFT | -| ALT-SHIFT | ALT+SHIFT | -| ALT-GUI | ALT+WIN | -| GUI-SHIFT | WIN+SHIFT | -| GUI-CTRL | WIN+CTRL | +The following modifier keys are recognized: +| Command | Notes | +| ------- | ------------ | +| CTRL | | +| CONTROL | Same as CTRL | +| SHIFT | | +| ALT | | +| GUI | | +| WINDOWS | Same as GUI | + +You can chain multiple modifier keys together using hyphens (`-`) or spaces. ## Key hold and release @@ -177,3 +175,18 @@ Example: `ID 1234:abcd Flipper Devices:Flipper Zero` VID and PID are hex codes and are mandatory. Manufacturer and Product are text strings and are optional. + +## Mouse Commands + +Mouse movement and click commands. Mouse click commands support HOLD functionality. + +| Command | Parameters | Notes | +| ------------- | -------------------------------| -------------------------------- | +| LEFTCLICK | None | | +| LEFT_CLICK | None | functionally same as LEFTCLICK | +| RIGHTCLICK | None | | +| RIGHT_CLICK | None | functionally same as RIGHTCLICK | +| MOUSEMOVE | x y: int move mount/direction | | +| MOUSE_MOVE | x y: int move mount/direction | functionally same as MOUSEMOVE | +| MOUSESCROLL | delta: int scroll distance | | +| MOUSE_SCROLL | delta: int scroll distance | functionally same as MOUSESCROLL | diff --git a/documentation/file_formats/SubGhzFileFormats.md b/documentation/file_formats/SubGhzFileFormats.md index c4d63835e..80047faf7 100644 --- a/documentation/file_formats/SubGhzFileFormats.md +++ b/documentation/file_formats/SubGhzFileFormats.md @@ -178,7 +178,7 @@ Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 DE 02 D3 54 D5 4C D2 C Frequency: 433920000 Preset: FuriHalSubGhzPresetCustom Custom_preset_module: CC1101 - Сustom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 + Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00 Protocol: RAW RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ... RAW_Data: -424 205 -412 159 -412 381 -240 181 ... diff --git a/documentation/images/js_first_app_on_cli.jpg b/documentation/images/js_first_app_on_cli.jpg new file mode 100644 index 000000000..a59de6a59 Binary files /dev/null and b/documentation/images/js_first_app_on_cli.jpg differ diff --git a/documentation/images/js_first_app_on_fz.jpg b/documentation/images/js_first_app_on_fz.jpg new file mode 100644 index 000000000..ec1539ad2 Binary files /dev/null and b/documentation/images/js_first_app_on_fz.jpg differ diff --git a/documentation/images/js_sdk_code_completion.jpg b/documentation/images/js_sdk_code_completion.jpg new file mode 100644 index 000000000..9ee9b14f1 Binary files /dev/null and b/documentation/images/js_sdk_code_completion.jpg differ diff --git a/documentation/images/js_sdk_npm_start.jpg b/documentation/images/js_sdk_npm_start.jpg new file mode 100644 index 000000000..4e4ce322f Binary files /dev/null and b/documentation/images/js_sdk_npm_start.jpg differ diff --git a/documentation/images/widget.png b/documentation/images/widget.png new file mode 100644 index 000000000..f4dd1ed5b Binary files /dev/null and b/documentation/images/widget.png differ diff --git a/documentation/js/js_about.md b/documentation/js/js_about.md new file mode 100644 index 000000000..eabcd7d18 --- /dev/null +++ b/documentation/js/js_about.md @@ -0,0 +1,16 @@ +# About the JavaScript engine {#js_about_js_engine} + +> Developing applications for Flipper Zero is now much more accessible with the introduction of JavaScript support. + +Previously, building an app for Flipper Zero required C/C++ skills, setting up a development environment, and studying the code of existing applications and documentation. While embedded developers are very familiar with all of this, we wanted to make it easier for people from all backgrounds to create apps for Flipper Zero. + +Flipper firmware now includes a built-in scripting engine that runs JavaScript, one of the most widely used programming languages. You can create script files, share them with others, and launch them directly from the **Apps/Scripts** menu on your Flipper Zero — no need for compiling on a PC. + +JavaScript support is based on the [mJS scripting engine](https://github.com/cesanta/mjs). Originally designed for microcontrollers, mJS makes efficient use of system resources, requiring less than 50k of flash space and 2k of RAM. We've kept the core features of mJS and also added some useful improvements, such as support for compact binary arrays. + +> [!note] +> mJS has some limitations compared to JavaScript engines built into modern browsers. For details on capabilities and limitations, refer to the [mJS documentation on GitHub](https://github.com/cesanta/mjs). + +JavaScript apps can interact with Flipper Zero's resources, including its GUI, buttons, USB-HID device, GPIO, UART interfaces, and more. Let's go through the steps to create your first JavaScript app for Flipper Zero. + +**Next step:** [Your first JavaScript app](#js_your_first_js_app) diff --git a/documentation/js/js_badusb.md b/documentation/js/js_badusb.md index b21126dfc..79ae53f55 100644 --- a/documentation/js/js_badusb.md +++ b/documentation/js/js_badusb.md @@ -1,33 +1,36 @@ -# js_badusb {#js_badusb} +# BadUSB module {#js_badusb} -# BadUSB module ```js let badusb = require("badusb"); ``` # Methods -## setup +## setup() Start USB HID with optional parameters. Should be called before all other methods. -### Parameters -Configuration object (optional): -- vid, pid (number): VID and PID values, both are mandatory -- mfr_name (string): Manufacturer name (32 ASCII characters max), optional -- prod_name (string): Product name (32 ASCII characters max), optional +**Parameters** -### Examples: +Configuration object *(optional)*: +- vid, pid (number): VID and PID values, both are mandatory +- mfrName (string): Manufacturer name (32 ASCII characters max), optional +- prodName (string): Product name (32 ASCII characters max), optional +- layoutPath (string): Path to keyboard layout file, optional + +**Examples** ```js // Start USB HID with default parameters badusb.setup(); // Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer and product strings not defined badusb.setup({ vid: 0xAAAA, pid: 0xBBBB }); // Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer string = "Flipper Devices", product string = "Flipper Zero" -badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper Devices", prod_name: "Flipper Zero" }); +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfrName: "Flipper Devices", prodName: "Flipper Zero" }); ``` -## isConnected +
+ +## isConnected() Returns USB connection state. -### Example: +**Example** ```js if (badusb.isConnected()) { // Do something @@ -36,15 +39,18 @@ if (badusb.isConnected()) { } ``` -## press +
+ +## press() Press and release a key. -### Parameters +**Parameters** + Key or modifier name, key code. -See a list of key names below. +See a [list of key names below](#js_badusb_keynames). -### Examples: +**Examples** ```js badusb.press("a"); // Press "a" key badusb.press("A"); // SHIFT + "a" @@ -54,58 +60,109 @@ badusb.press(98); // Press key with HID code (dec) 98 (Numpad 0 / Insert) badusb.press(0x47); // Press key with HID code (hex) 0x47 (Scroll lock) ``` -## hold +
+ +## hold() Hold a key. Up to 5 keys (excluding modifiers) can be held simultaneously. -### Parameters -Same as `press` +**Parameters** -### Examples: +Same as `press`. + +**Examples** ```js badusb.hold("a"); // Press and hold "a" key badusb.hold("CTRL", "v"); // Press and hold CTRL + "v" combo ``` -## release +
+ +## release() Release a previously held key. -### Parameters -Same as `press` +**Parameters** -Release all keys if called without parameters +Same as `press`. -### Examples: +Release all keys if called without parameters. + +**Examples** ```js badusb.release(); // Release all keys badusb.release("a"); // Release "a" key ``` -## print +
+ +## print() Print a string. -### Parameters -- A string to print -- (optional) delay between key presses +**Parameters** -### Examples: +- A string to print +- *(optional)* Delay between key presses + +**Examples** ```js badusb.print("Hello, world!"); // print "Hello, world!" badusb.print("Hello, world!", 100); // Add 100ms delay between key presses ``` +
-## println +## println() Same as `print` but ended with "ENTER" press. -### Parameters -- A string to print -- (optional) delay between key presses +**Parameters** -### Examples: +- A string to print +- *(optional)* Delay between key presses + +**Examples** ```js badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" ``` +
-# Key names list +## altPrint() +Prints a string by Alt+Numpad method - works only on Windows! + +**Parameters** + +- A string to print +- *(optional)* delay between key presses + +**Examples** +```js +badusb.altPrint("Hello, world!"); // print "Hello, world!" +badusb.altPrint("Hello, world!", 100); // Add 100ms delay between key presses +``` +
+ +## altPrintln() +Same as `altPrint` but ended with "ENTER" press. + +**Parameters** + +- A string to print +- *(optional)* delay between key presses + +**Examples** +```js +badusb.altPrintln("Hello, world!"); // print "Hello, world!" and press "ENTER" +``` +
+ +## quit() +Releases usb, optional, but allows to interchange with usbdisk. + +**Examples** +```js +badusb.quit(); +usbdisk.start(...) +``` +
+ +# Key names list {#js_badusb_keynames} ## Modifier keys @@ -142,3 +199,4 @@ badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" | TAB | | | MENU | Context menu key | | Fx | F1-F24 keys | +| NUMx | NUM0-NUM9 keys | diff --git a/documentation/js/js_builtin.md b/documentation/js/js_builtin.md index 9c59b9822..2d2f9417a 100644 --- a/documentation/js/js_builtin.md +++ b/documentation/js/js_builtin.md @@ -1,50 +1,315 @@ # Built-in methods {#js_builtin} -## require +## require() Load a module plugin. -### Parameters +**Parameters** - Module name -### Examples: +**Examples** ```js let serial = require("serial"); // Load "serial" module ``` -## delay -### Parameters +
+ +## delay() +**Parameters** - Delay value in ms -### Examples: +**Examples** ```js delay(500); // Delay for 500ms ``` -## print +
+ +## print() Print a message on a screen console. -### Parameters +**Parameters** The following argument types are supported: - String - Number - Bool - undefined -### Examples: +**Examples** ```js print("string1", "string2", 123); ``` +
-## console.log -## console.warn -## console.error -## console.debug +## Console object Same as `print`, but output to serial console only, with corresponding log level. -## to_string +### console.log() + +
+ +### console.warn() + +
+ +### console.error() + +
+ +### console.debug() + +
+ +## load() +Runs a JS file and returns value from it. + +**Parameters** +- The path to the file +- An optional object to use as the global scope while running this file + +**Examples** +```js +load("/ext/apps/Scripts/script.js"); +``` +
+ +## chr() +Convert an ASCII character number to string. + +**Examples** +```js +chr(65); // "A" +``` +
+ +## die() +Exit JavaScript with given message. + +**Examples** +```js +die("Some error occurred"); +``` +
+ +## parseInt() +Convert a string to number with an optional base. + +**Examples** +```js +parseInt("123"); // 123 +parseInt("7b", 16); // 123 +``` +
+ +## Number object + +### Number.toString() Convert a number to string with an optional base. -### Examples: +**Examples** ```js -to_string(123) // "123" -to_string(123, 16) // "0x7b" +let num = 123; +num.toString(); // "123" +num.toString(16); // "0x7b" +``` +
+ +## ArrayBuffer object + +**Fields** + +- byteLength: The length of the buffer in bytes +
+ +### ArrayBuffer.slice() +Creates an `ArrayBuffer` that contains a sub-part of the buffer. + +**Parameters** +- The index to start the new buffer at +- An optional non-inclusive index of where to stop the new buffer + +**Examples** +```js +Uint8Array([1, 2, 3]).buffer.slice(0, 1) // ArrayBuffer([1]) +``` +
+ +## DataView objects +Wrappers around `ArrayBuffer` objects, with dedicated types such as: +- `Uint8Array` +- `Int8Array` +- `Uint16Array` +- `Int16Array` +- `Uint32Array` +- `Int32Array` + +**Fields** + +- byteLength: The length of the buffer in bytes +- length: The length of the buffer in typed elements +- buffer: The underlying `ArrayBuffer` +
+ +## Array object + +**Fields** + +- length: How many elements there are in the array +
+ +### Array.splice() +Removes elements from the array and returns them in a new array. + +**Parameters** +- The index to start taking elements from +- An optional count of how many elements to take + +**Examples** +```js +let arr = [1, 2, 3]; +arr.splice(1); // [2, 3] +arr; // [1] +``` +
+ +### Array.push() +Adds a value to the end of the array. + +**Examples** +```js +let arr = [1, 2]; +arr.push(3); +arr; // [1, 2, 3] +``` +
+ +## String object + +**Fields** + +- length: How many characters there are in the string +
+ +### String.charCodeAt() +Returns the character code at an index in the string. + +**Examples** +```js +"A".charCodeAt(0) // 65 +``` +
+ +### String.at() +Same as `String.charCodeAt()`. +
+ +### String.indexOf() +Return index of first occurrence of substr within the string or `-1` if not found. + +**Parameters** +- Substring to search for +- Optional index to start searching from + +**Examples** +```js +"Example".indexOf("amp") // 2 +``` +
+ +### String.slice() +Return a substring between two indices. + +**Parameters** +- The index to start the new string at +- An optional non-inclusive index of where to stop the new string + +**Examples** +```js +"Example".slice(2) // "ample" +``` +
+ +### String.toUpperCase() +Transforms the string to upper case. + +**Examples** +```js +"Example".toUpperCase() // "EXAMPLE" +``` +
+ +### String.toLowerCase() +Transforms the string to lower case. + +**Examples** +```js +"Example".toLowerCase() // "example" +``` +
+ +## __dirname +Path to the directory containing the current script. + +**Examples** +```js +print(__dirname); // /ext/apps/Scripts +``` +
+ +## __filename +Path to the current script file. + +**Examples** +```js +print(__filename); // /ext/apps/Scripts/path.js +``` +
+ +# SDK compatibility methods {#js_builtin_sdk_compatibility} + +## sdkCompatibilityStatus() +Checks compatibility between the script and the JS SDK that the firmware provides. + +**Returns** +- `"compatible"` if the script and the JS SDK are compatible +- `"firmwareTooOld"` if the expected major version is larger than the version of the firmware, or if the expected minor version is larger than the version of the firmware +- `"firmwareTooNew"` if the expected major version is lower than the version of the firmware + +**Examples** +```js +sdkCompatibilityStatus(0, 3); // "compatible" +``` +
+ +## isSdkCompatible() +Checks compatibility between the script and the JS SDK that the firmware provides in a boolean fashion. + +**Examples** +```js +isSdkCompatible(0, 3); // true +``` +
+ +## checkSdkCompatibility() +Asks the user whether to continue executing the script if the versions are not compatible. Does nothing if they are. + +**Examples** +```js +checkSdkCompatibility(0, 3); +``` +
+ +## doesSdkSupport() +Checks whether all of the specified extra features are supported by the interpreter. + +**Examples** +```js +doesSdkSupport(["gui-widget"]); // true +``` +
+ +## checkSdkFeatures() +Checks whether all of the specified extra features are supported by the interpreter, asking the user if they want to continue running the script if they're not. + +**Examples** +```js +checkSdkFeatures(["gui-widget"]); ``` diff --git a/documentation/js/js_data_types.md b/documentation/js/js_data_types.md index bd3bb1f42..2a94ba5d2 100644 --- a/documentation/js/js_data_types.md +++ b/documentation/js/js_data_types.md @@ -7,7 +7,7 @@ Here is a list of common data types used by mJS. - foreign — C function or data pointer - undefined - null -- object — a data structure with named fields -- array — special type of object, all items have indexes and equal types +- Object — a data structure with named fields +- Array — special type of object, all items have indexes and equal types - ArrayBuffer — raw data buffer - DataView — provides interface for accessing ArrayBuffer contents diff --git a/documentation/js/js_developing_apps_using_js_sdk.md b/documentation/js/js_developing_apps_using_js_sdk.md new file mode 100644 index 000000000..1a764e1df --- /dev/null +++ b/documentation/js/js_developing_apps_using_js_sdk.md @@ -0,0 +1,98 @@ +# Developing apps using JavaScript SDK {#js_developing_apps_using_js_sdk} + +In the [previous guide](#js_your_first_js_app), we learned how to create and run a JavaScript app on Flipper Zero. However, when debugging a script, you often need to repeatedly modify the code and test it on the device. While you can use qFlipper for this, it involves a lot of repetitive steps. Fortunately, there's a more efficient alternative — the Flipper Zero JavaScript SDK, a set of tools that simplify app development in JavaScript. + +Main features of the Flipper Zero JavaScript SDK: + +* [Loading and running an app with a single command](#js_sdk_run_app) +* [Code completion](#js_sdk_code_completion) +* [JS code minifier (compressor)](#js_sdk_js_minifier) + +In this guide, we'll install the JavaScript SDK and learn how to run JavaScript apps on Flipper Zero using it. + +## How to get JavaScript SDK + +The JavaScript SDK for Flipper Zero is distributed as an [NPM package](npmjs.com/package/\@flipperdevices/fz-sdk), so you can install it using a package manager like npm, pnpm, or yarn. You'll also need Node.js, a JavaScript runtime environment required for the NPM package manager to work. + +> [!note] +> In this guide, we'll use **npm**, the default package manager for Node.js. + +Follow these steps: + +1. Install **Node.js + npm** on your PC. Check out this [official Downloads page](https://nodejs.org/en/download/package-manager), select your OS and preferences, and run the provided commands in your terminal. + +2. Open a terminal in the folder where you want to store your project. + +3. Run the `npx @flipperdevices/create-fz-app@latest` command to create a JavaScript app template and include the JavaScript SDK into it. This command will launch an interactive wizard. You'll need to specify the project name and choose a package manager (in our case, **npm**). + +You'll now find a JavaScript app template in your project folder, alongside the JavaScript SDK package, all necessary dependencies and configs. The app code will be in the `index.ts` file. + +Now, let's take a look at the main features of the Flipper Zero JavaScript SDK. + +## Running your app {#js_sdk_run_app} + +To run the application: + +1. Connect your Flipper Zero to your PC via USB. + +2. Open a terminal in your app's folder. + +3. Run the `npm start` command to copy the JS file to Flipper Zero and run it. + +\image html js_sdk_npm_start.jpg width=800 + +You'll see output messages from the `print()` function in the terminal. + +## Updating your app {#js_sdk_update_app} + +After making changes to your app's code, simply run `npm start` again. As long as your Flipper Zero is still connected, the updated app will launch, and the old `.js` file on Flipper Zero will be replaced with the new version. + + +## Other JavaScript SDK features + +As you can see, it's quite easy to launch and update your app with a single command. Now let's explore two more important features of the Flipper Zero JavaScript SDK: **code completion** and **JS minifier**. + + +### Code completion {#js_sdk_code_completion} + +Code completion helps speed up the development process by automatically suggesting code as you type, reducing the need to refer to documentation. + +\image html js_sdk_code_completion.jpg width=800 + +> [!note] +> Code completion works in code editors and IDEs that support Language Server, for example, [VS Code](https://code.visualstudio.com/). + + +### JS minifier {#js_sdk_js_minifier} + +The JS minifier reduces the size of JavaScript files by removing unnecessary characters (like spaces, tabs and line breaks) and shortening variable names. This can make your scripts run a bit faster without changing their logic. + +However, it has a drawback — it can make debugging harder, as error messages in minified files are harder to read in larger applications. For this reason, it's recommended to disable the JS minifier during debugging and it's disabled by default. To enable it, set the `minify` parameter to `true` in the `fz-sdk.config.json5` file in your app folder. This will minify your JavaScript app before loading it onto Flipper Zero. + + +## Differences with normal Flipper JavaScript + +With the Flipper JavaScript SDK, you will be developing in **TypeScript**. This means that you get a better development experience, with more accurate code completion and warnings when variable types are incompatible, but it also means your code will be different from basic Flipper JS. + +Some things to look out for: +- Importing modules: + - Instead of `let module = require("module");` + - You will use `import * as module from "@flipperdevices/fz-sdk/module";` +- Multiple source code files: + - The Flipper JavaScript SDK does not yet support having multiple `.ts` files and importing them + - You can use `load()`, but this will not benefit from TypeScript type checking +- Casting values: + - Some Flipper JavaScript functions will return generic types + - For example `eventLoop.subscribe()` will run your callback with a generic `Item` type + - In some cases you might need to cast these values before using them, you can do this by: + - Inline casting: `item` + - Declare with new type: `let text = item as string;` + +When you upload the script to Flipper with `npm start`, it gets transpiled to normal JavaScript and optionally minified (see below). If you're looking to share your script with others, this is what you should give them to run. + + +## What's next? + +You've learned how to run and debug simple JavaScript apps. But how can you access Flipper Zero's hardware from your JS code? For that, you'll need to use JS modules — which we'll cover in the next guide. + +**Next step:** [Using JavaScript modules](#js_using_js_modules) diff --git a/documentation/js/js_event_loop.md b/documentation/js/js_event_loop.md index 9519478c0..7da3f9341 100644 --- a/documentation/js/js_event_loop.md +++ b/documentation/js/js_event_loop.md @@ -1,28 +1,28 @@ -# js_event_loop {#js_event_loop} - -# Event Loop module -```js -let eventLoop = require("event_loop"); -``` +# Event Loop module {#js_event_loop} The event loop is central to event-based programming in many frameworks, and our JS subsystem is no exception. It is a good idea to familiarize yourself with the event loop first before using any of the advanced modules (e.g. GPIO and GUI). +```js +let eventLoop = require("event_loop"); +``` + ## Conceptualizing the event loop -If you ever wrote JavaScript before, you have definitely seen callbacks. It's -when a function accepts another function (usually an anonymous one) as one of -the arguments, which it will call later on, e.g. when an event happens or when +If you've ever written JavaScript code before, you've definitely seen callbacks. It's +when a function takes another function (usually an anonymous one) as one of +the arguments, which it will call later, e.g. when an event happens or when data becomes ready: ```js setTimeout(function() { console.log("Hello, World!") }, 1000); ``` -Many JavaScript engines employ a queue that the runtime fetches events from as +Many JavaScript engines employ a queue from which the runtime fetches events as they occur, subsequently calling the corresponding callbacks. This is done in a long-running loop, hence the name "event loop". Here's the pseudocode for a typical event loop: -```js + +\code{.js} while(loop_is_running()) { if(event_available_in_queue()) { let event = fetch_event_from_queue(); @@ -34,12 +34,14 @@ while(loop_is_running()) { sleep_until_any_event_becomes_available(); } } -``` +\endcode Most JS runtimes enclose the event loop within themselves, so that most JS -programmers does not even need to be aware of its existence. This is not the +programmers don't even need to be aware of its existence. This is not the case with our JS subsystem. +--- + # Example This is how one would write something similar to the `setTimeout` example above: ```js @@ -84,18 +86,22 @@ Because we have two extra arguments, if we return anything other than an array of length 2, the arguments will be kept as-is for the next call. The first two arguments that get passed to our callback are: - - The subscription manager that lets us `.cancel()` our subscription + - The subscription manager that lets us `.cancel()` our subscription. - The event item, used for events that have extra data. Timer events do not, they just produce `undefined`. +--- + # API reference -## `run` +## run() Runs the event loop until it is stopped with `stop`. -## `subscribe` +
+ +## subscribe() Subscribes a function to an event. -### Parameters +**Parameters** - `contract`: an event source identifier - `callback`: the function to call when the event happens - extra arguments: will be passed as extra arguments to the callback @@ -108,35 +114,45 @@ to `undefined`. The callback may return an array of the same length as the count of the extra arguments to modify them for the next time that the event handler is called. Any other returns values are discarded. -### Returns +**Returns** + A `SubscriptionManager` object: - `SubscriptionManager.cancel()`: unsubscribes the callback from the event -### Warning +**Warning** + Each event source may only have one callback associated with it. -## `stop` +
+ +## stop() Stops the event loop. -## `timer` +
+ +## timer() Produces an event source that fires with a constant interval either once or indefinitely. -### Parameters +**Parameters** - `mode`: either `"oneshot"` or `"periodic"` - `interval`: the timeout (for `"oneshot"`) timers or the period (for `"periodic"` timers) -### Returns +**Returns** + A `Contract` object, as expected by `subscribe`'s first parameter. -## `queue` +
+ +## queue() Produces a queue that can be used to exchange messages. -### Parameters +**Parameters** - `length`: the maximum number of items that the queue may contain -### Returns +**Returns** + A `Queue` object: - `Queue.send(message)`: - `message`: a value of any type that will be placed at the end of the queue diff --git a/documentation/js/js_flipper.md b/documentation/js/js_flipper.md new file mode 100644 index 000000000..a4da23868 --- /dev/null +++ b/documentation/js/js_flipper.md @@ -0,0 +1,52 @@ +# Flipper module {#js_flipper} + +The module contains methods and values to query device information and properties. Call the `require` function to load the module before first using its methods: + +```js +let flipper = require("flipper"); +``` + +# Values + +## firmwareVendor +String representing the firmware installed on the device. +Original firmware reports `"flipperdevices"`. +Do **NOT** use this to check the presence or absence of features, refer to [other ways to check SDK compatibility](#js_builtin_sdk_compatibility). + +## jsSdkVersion +Version of the JavaScript SDK. +Do **NOT** use this to check the presence or absence of features, refer to [other ways to check SDK compatibility](#js_builtin_sdk_compatibility). + +
+ +--- + +# Methods + +## getModel() +Returns the device model. + +**Example** +```js +flipper.getModel(); // "Flipper Zero" +``` + +
+ +## getName() +Returns the name of the virtual dolphin. + +**Example** +```js +flipper.getName(); // "Fur1pp44" +``` + +
+ +## getBatteryCharge() +Returns the battery charge percentage. + +**Example** +```js +flipper.getBatteryCharge(); // 100 +``` diff --git a/documentation/js/js_gpio.md b/documentation/js/js_gpio.md index aa444bacd..d058d7329 100644 --- a/documentation/js/js_gpio.md +++ b/documentation/js/js_gpio.md @@ -1,14 +1,12 @@ -# js_gpio {#js_gpio} +# GPIO module {#js_gpio} + +The module allows you to control GPIO pins of the expansion connector on Flipper Zero. Call the `require` function to load the module before first using its methods. This module depends on the `event_loop` module, so it **must** be imported after `event_loop` is imported: -# GPIO module ```js let eventLoop = require("event_loop"); let gpio = require("gpio"); ``` -This module depends on the `event_loop` module, so it _must_ only be imported -after `event_loop` is imported. - # Example ```js let eventLoop = require("event_loop"); @@ -23,21 +21,26 @@ led.write(false); delay(1000); ``` +--- + # API reference -## `get` +## get() Gets a `Pin` object that can be used to manage a pin. -### Parameters +**Parameters** - `pin`: pin identifier (examples: `"pc3"`, `7`, `"pa6"`, `3`) -### Returns -A `Pin` object +**Returns** -## `Pin` object -### `Pin.init()` -Configures a pin +A `Pin` object. -#### Parameters +
+ +## Pin object +### Pin.init() +Configures a pin. + +**Parameters** - `mode`: `Mode` object: - `direction` (required): either `"in"` or `"out"` - `outMode` (required for `direction: "out"`): either `"open_drain"` or @@ -48,30 +51,71 @@ Configures a pin `"rising"`, `"falling"` or `"both"` - `pull` (optional): either `"up"`, `"down"` or unset -### `Pin.write()` -Writes a digital value to a pin configured with `direction: "out"` +
-#### Parameters +### Pin.write() +Writes a digital value to a pin configured with `direction: "out"`. + +**Parameters** - `value`: boolean logic level to write -### `Pin.read()` +
+ +### Pin.read() Reads a digital value from a pin configured with `direction: "in"` and any -`inMode` except `"analog"` +`inMode` except `"analog"`. -#### Returns -Boolean logic level +**Returns** -### `Pin.readAnalog()` +Boolean logic level. + +
+ +### Pin.readAnalog() Reads an analog voltage level in millivolts from a pin configured with -`direction: "in"` and `inMode: "analog"` +`direction: "in"` and `inMode: "analog"`. -#### Returns -Voltage on pin in millivolts +**Returns** -### `Pin.interrupt()` +Voltage on pin in millivolts. + +
+ +### Pin.interrupt() Attaches an interrupt to a pin configured with `direction: "in"` and -`inMode: "interrupt"` or `"event"` +`inMode: "interrupt"` or `"event"`. + +**Returns** -#### Returns An event loop `Contract` object that identifies the interrupt event source. The event does not produce any extra data. + +### Pin.isPwmSupported() +Determines whether this pin supports PWM. +If `false`, all other PWM-related methods on this pin will throw an error when called. + +**Returns** + +Boolean value. + +### Pin.pwmWrite() +Sets PWM parameters and starts the PWM. +Configures the pin with `{ direction: "out", outMode: "push_pull" }`. +Throws an error if PWM is not supported on this pin. + +**Parameters** + - `freq`: Frequency in Hz + - `duty`: Duty cycle in % + +### Pin.isPwmRunning() +Determines whether PWM is running. +Throws an error if PWM is not supported on this pin. + +**Returns** + +Boolean value. + +### Pin.pwmStop() +Stops PWM. +Does not restore previous pin configuration. +Throws an error if PWM is not supported on this pin. diff --git a/documentation/js/js_gui.md b/documentation/js/js_gui.md index 4d2d2497a..b04255008 100644 --- a/documentation/js/js_gui.md +++ b/documentation/js/js_gui.md @@ -1,13 +1,27 @@ -# js_gui {#js_gui} +# GUI module {#js_gui} + +The module allows you to use GUI (graphical user interface) in concepts off the Flipper Zero firmware. Call the `require` function to load the module before first using its methods. This module depends on the `event_loop` module, so it **must** be imported after the `event_loop` import: -# GUI module ```js let eventLoop = require("event_loop"); let gui = require("gui"); ``` +## Submodules -This module depends on the `event_loop` module, so it _must_ only be imported -after `event_loop` is imported. +GUI module has several submodules: + +- @subpage js_gui__byte_input — Keyboard-like hex input +- @subpage js_gui__dialog — Dialog with up to 3 options +- @subpage js_gui__empty_screen — Just empty screen +- @subpage js_gui__file_picker — Displays a file selection prompt +- @subpage js_gui__icon — Retrieves and loads icons for use in GUI +- @subpage js_gui__loading — Displays an animated hourglass icon +- @subpage js_gui__submenu — Displays a scrollable list of clickable textual entries +- @subpage js_gui__text_box — Simple multiline text box +- @subpage js_gui__text_input — Keyboard-like text input +- @subpage js_gui__widget — Displays a combination of custom elements on one screen + +--- ## Conceptualizing GUI ### Event loop @@ -27,23 +41,23 @@ always access the canvas through a viewport. In Flipper's terminology, a "View" is a fullscreen design element that assumes control over the entire viewport and all input events. Different types of views are available (not all of which are unfortunately currently implemented in JS): -| View | Has JS adapter? | -|----------------------|------------------| -| `button_menu` | ❌ | -| `button_panel` | ❌ | -| `byte_input` | ❌ | -| `dialog_ex` | ✅ (as `dialog`) | -| `empty_screen` | ✅ | -| `file_browser` | ❌ | -| `loading` | ✅ | -| `menu` | ❌ | -| `number_input` | ❌ | -| `popup` | ❌ | -| `submenu` | ✅ | -| `text_box` | ✅ | -| `text_input` | ✅ | -| `variable_item_list` | ❌ | -| `widget` | ❌ | +| View | Has JS adapter? | +|----------------------|-----------------------| +| `button_menu` | ❌ | +| `button_panel` | ❌ | +| `byte_input` | ✅ | +| `dialog_ex` | ✅ (as `dialog`) | +| `empty_screen` | ✅ | +| `file_browser` | ✅ (as `file_picker`) | +| `loading` | ✅ | +| `menu` | ❌ | +| `number_input` | ❌ | +| `popup` | ❌ | +| `submenu` | ✅ | +| `text_box` | ✅ | +| `text_input` | ✅ | +| `variable_item_list` | ❌ | +| `widget` | ✅ | In JS, each view has its own set of properties (or just "props"). The programmer can manipulate these properties in two ways: @@ -69,7 +83,9 @@ a GUI application: | ViewDispatcher | Common UI elements that fit with the overall look of the system | ✅ | | SceneManager | Additional navigation flow management for complex applications | ❌ | -# Example +--- + +## Example An example with three different views using the ViewDispatcher approach: ```js let eventLoop = require("event_loop"); @@ -114,48 +130,101 @@ gui.viewDispatcher.switchTo(views.demos); eventLoop.run(); ``` +--- + # API reference -## `viewDispatcher` +## viewDispatcher The `viewDispatcher` constant holds the `ViewDispatcher` singleton. -### `viewDispatcher.switchTo(view)` -Switches to a view, giving it control over the display and input +
-#### Parameters +### viewDispatcher.switchTo(view) +Switches to a view, giving it control over the display and input. + +**Parameters** - `view`: the `View` to switch to -### `viewDispatcher.sendTo(direction)` +
+ +### viewDispatcher.sendTo(direction) Sends the viewport that the dispatcher manages to the front of the stackup (effectively making it visible), or to the back (effectively making it -invisible) +invisible). -#### Parameters +**Parameters** - `direction`: either `"front"` or `"back"` -### `viewDispatcher.sendCustom(event)` -Sends a custom number to the `custom` event handler +
-#### Parameters +### viewDispatcher.sendCustom(event) +Sends a custom number to the `custom` event handler. + +**Parameters** - `event`: number to send -### `viewDispatcher.custom` +
+ +### viewDispatcher.custom An event loop `Contract` object that identifies the custom event source, -triggered by `ViewDispatcher.sendCustom(event)` +triggered by `ViewDispatcher.sendCustom(event)`. -### `viewDispatcher.navigation` +
+ +### viewDispatcher.navigation An event loop `Contract` object that identifies the navigation event source, -triggered when the back key is pressed +triggered when the back key is pressed. -## `ViewFactory` -When you import a module implementing a view, a `ViewFactory` is instantiated. -For example, in the example above, `loadingView`, `submenuView` and `emptyView` -are view factories. +
-### `ViewFactory.make()` -Creates an instance of a `View` +### viewDispatcher.currentView +The `View` object currently being shown. -### `ViewFactory.make(props)` -Creates an instance of a `View` and assigns initial properties from `props` +
-#### Parameters +## ViewFactory +When you import a module implementing a view, a `ViewFactory` is instantiated. For example, in the example above, `loadingView`, `submenuView` and `emptyView` are view factories. + +
+ +### ViewFactory.make() +Creates an instance of a `View`. + +
+ +### ViewFactory.makeWith(props, children) +Creates an instance of a `View` and assigns initial properties from `props` and optionally a list of children. + +**Parameters** - `props`: simple key-value object, e.g. `{ header: "Header" }` + - `children`: optional array of children, e.g. `[ { element: "button", button: "right", text: "Back" } ]` + +## View +When you call `ViewFactory.make()` or `ViewFactory.makeWith()`, a `View` is instantiated. For example, in the example above, `views.loading`, `views.demos` and `views.empty` are views. + +
+ +### View.set(property, value) +Assign value to property by name. + +**Parameters** + - `property`: name of the property to change + - `value`: value to assign to the property + +
+ +### View.addChild(child) +Adds a child to the `View`. + +**Parameters** + - `child`: the child to add, e.g. `{ element: "button", button: "right", text: "Back" }` + +The format of the `child` parameter depends on the type of View that you're working with. Look in the View documentation. + +### View.resetChildren() +Removes all children from the `View`. + +### View.setChildren(children) +Removes all previous children from the `View` and assigns new children. + +**Parameters** + - `children`: the array of new children, e.g. `[ { element: "button", button: "right", text: "Back" } ]` diff --git a/documentation/js/js_gui__byte_input.md b/documentation/js/js_gui__byte_input.md new file mode 100644 index 000000000..abbf1ccad --- /dev/null +++ b/documentation/js/js_gui__byte_input.md @@ -0,0 +1,32 @@ +# Byte input GUI view {#js_gui__byte_input} + +Displays a hexadecimal keyboard. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let byteInputView = require("gui/byte_input"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they **must** be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +## Example +For an example refer to the `gui.js` example script. + +## View props + +| Prop | Type | Description | +|-------------|--------|--------------------------------------------------| +| `length` | `number` | The length in bytes of the buffer to modify. | +| `header` | `string` | A single line of text that appears above the keyboard. | +| `defaultData` | `string` | Data to show by default. | + +## View events + +| Item | Type | Description | +|-------------|--------|--------------------------------------------------| +| `input` | `ArrayBuffer` | Fires when the user selects the "Save" button. | diff --git a/documentation/js/js_gui__dialog.md b/documentation/js/js_gui__dialog.md index 445e71128..4f26cfa1b 100644 --- a/documentation/js/js_gui__dialog.md +++ b/documentation/js/js_gui__dialog.md @@ -1,6 +1,5 @@ -# js_gui__dialog {#js_gui__dialog} +# Dialog GUI view {#js_gui__dialog} -# Dialog GUI view Displays a dialog with up to three options. Sample screenshot of the view @@ -12,42 +11,24 @@ let dialogView = require("gui/dialog"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. # Example -For an example refer to the `gui.js` example script. +For an example, refer to the `gui.js` example script. # View props -## `header` -Text that appears in bold at the top of the screen -Type: `string` - -## `text` -Text that appears in the middle of the screen - -Type: `string` - -## `left` -Text for the left button. If unset, the left button does not show up. - -Type: `string` - -## `center` -Text for the center button. If unset, the center button does not show up. - -Type: `string` - -## `right` -Text for the right button. If unset, the right button does not show up. - -Type: `string` +| **Prop** | **Type** | **Description** | +|------------|-----------|----------------------------------------------------------------| +| `header` | string | Text that appears in bold at the top of the screen. | +| `text` | string | Text that appears in the middle of the screen. | +| `left` | string | Text for the left button. If unset, the left button does not show up. | +| `center` | string | Text for the center button. If unset, the center button does not show up. | +| `right` | string | Text for the right button. If unset, the right button does not show up. | # View events -## `input` -Fires when the user presses on either of the three possible buttons. The item -contains one of the strings `"left"`, `"center"` or `"right"` depending on the -button. -Item type: `string` +| Item | Type | Description | +|----------|--------|-----------------------------------------------------------------------------| +| `input` | `string`| Fires when the user presses on either of the three possible buttons. The item contains one of the strings `"left"`, `"center"`, or `"right"` depending on the button. | diff --git a/documentation/js/js_gui__empty_screen.md b/documentation/js/js_gui__empty_screen.md index f9fd12553..6ee6ba20e 100644 --- a/documentation/js/js_gui__empty_screen.md +++ b/documentation/js/js_gui__empty_screen.md @@ -1,7 +1,6 @@ -# js_gui__empty_screen {#js_gui__empty_screen} +# Empty Screen GUI view {#js_gui__empty_screen} -# Empty Screen GUI View -Displays nothing. +Displays an empty screen. Sample screenshot of the view @@ -12,11 +11,11 @@ let emptyView = require("gui/empty_screen"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. # Example -For an example refer to the GUI example. +For an example, refer to the GUI example. # View props This view does not have any props. diff --git a/documentation/js/js_gui__file_picker.md b/documentation/js/js_gui__file_picker.md new file mode 100644 index 000000000..a0854f0de --- /dev/null +++ b/documentation/js/js_gui__file_picker.md @@ -0,0 +1,20 @@ +# File Picker GUI prompt {#js_gui__file_picker} + +Allows asking the user to select a file. +It is not GUI view like other JS GUI views, rather just a function that shows a prompt. + +# Example +For an example, refer to the `gui.js` example script. + +# API reference + +## pickFile() +Displays a file picker and returns the selected file, or undefined if cancelled. + +**Parameters** + - `basePath`: the path to start at + - `extension`: the file extension(s) to show (like `.sub`, `.iso|.img`, `*`) + +**Returns** + +A `string` path, or `undefined`. diff --git a/documentation/js/js_gui__icon.md b/documentation/js/js_gui__icon.md new file mode 100644 index 000000000..2c6c4c5b7 --- /dev/null +++ b/documentation/js/js_gui__icon.md @@ -0,0 +1,32 @@ +# GUI Icons {#js_gui__icon} + +Retrieves and loads icons for use with GUI views such as [Dialog](#js_gui__dialog). + +# Example +For an example, refer to the `gui.js` example script. + +# API reference + +## getBuiltin() +Gets a built-in firmware icon by its name. +Not all icons are supported, currently only `"DolphinWait_59x54"` and `"js_script_10px"` are available. + +**Parameters** + - `icon`: name of the icon + +**Returns** + +An `IconData` object. + +
+ +## loadFxbm() +Loads a `.fxbm` icon (XBM Flipper sprite, from `flipperzero-game-engine`) from file. +It will be automatically unloaded when the script exits. + +**Parameters** + - `path`: path to the `.fxbm` file + +**Returns** + +An `IconData` object. diff --git a/documentation/js/js_gui__loading.md b/documentation/js/js_gui__loading.md index 52f1cea49..35ea987c1 100644 --- a/documentation/js/js_gui__loading.md +++ b/documentation/js/js_gui__loading.md @@ -1,8 +1,6 @@ -# js_gui__loading {#js_gui__loading} +# Loading GUI view {#js_gui__loading} -# Loading GUI View -Displays an animated hourglass icon. Suppresses all `navigation` events, making -it impossible for the user to exit the view by pressing the back key. +Displays an animated hourglass icon. Suppresses all `navigation` events, making it impossible for the user to exit the view by pressing the BACK key. Sample screenshot of the view @@ -13,7 +11,7 @@ let loadingView = require("gui/loading"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. # Example diff --git a/documentation/js/js_gui__submenu.md b/documentation/js/js_gui__submenu.md index 28c1e65af..9105330a2 100644 --- a/documentation/js/js_gui__submenu.md +++ b/documentation/js/js_gui__submenu.md @@ -1,6 +1,5 @@ -# js_gui__submenu {#js_gui__submenu} +# Submenu GUI view {#js_gui__submenu} -# Submenu GUI view Displays a scrollable list of clickable textual entries. Sample screenshot of the view @@ -12,26 +11,19 @@ let submenuView = require("gui/submenu"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. -# Example -For an example refer to the GUI example. +## View props -# View props -## `header` -Single line of text that appears above the list +| Property | Type | Description | +|----------|-----------|-------------------------------------------| +| `header` | `string` | A single line of text that appears above the list. | +| `items` | `string[]`| The list of options. | -Type: `string` -## `items` -The list of options +## View events -Type: `string[]` - -# View events -## `chosen` -Fires when an entry has been chosen by the user. The item contains the index of -the entry. - -Item type: `number` +| Item | Type | Description | +|----------|---------|---------------------------------------------------------------| +| `chosen` | `number`| Fires when an entry has been chosen by the user. The item contains the index of the entry. | diff --git a/documentation/js/js_gui__text_box.md b/documentation/js/js_gui__text_box.md index bdad8d8b3..4c94b8c3c 100644 --- a/documentation/js/js_gui__text_box.md +++ b/documentation/js/js_gui__text_box.md @@ -1,6 +1,5 @@ -# js_gui__text_box {#js_gui__text_box} +# Text box GUI view {#js_gui__text_box} -# Text box GUI view Displays a scrollable read-only text field. Sample screenshot of the view @@ -12,14 +11,16 @@ let textBoxView = require("gui/text_box"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. -# Example -For an example refer to the `gui.js` example script. +## Example +For an example, refer to the `gui.js` example script. -# View props -## `text` -Text to show in the text box. +## View props -Type: `string` +| Prop | Type | Description | +|----------|---------|------------------------------------| +| `text` | `string`| Text to show in the text box. | +| `font` | `string`| The font to display the text in (`"text"` or `"hex"`). | +| `focus` | `string`| The initial focus of the text box (`"start"` or `"end"`). | diff --git a/documentation/js/js_gui__text_input.md b/documentation/js/js_gui__text_input.md index 030579e2e..c63297c12 100644 --- a/documentation/js/js_gui__text_input.md +++ b/documentation/js/js_gui__text_input.md @@ -1,6 +1,5 @@ -# js_gui__text_input {#js_gui__text_input} +# Text input GUI view {#js_gui__text_input} -# Text input GUI view Displays a keyboard. Sample screenshot of the view @@ -12,33 +11,24 @@ let textInputView = require("gui/text_input"); ``` This module depends on the `gui` module, which in turn depends on the -`event_loop` module, so they _must_ be imported in this order. It is also +`event_loop` module, so they **must** be imported in this order. It is also recommended to conceptualize these modules first before using this one. -# Example -For an example refer to the `gui.js` example script. +## Example +For an example, refer to the `gui.js` example script. -# View props -## `minLength` -Smallest allowed text length +## View props -Type: `number` +| Prop | Type | Description | +|-------------|--------|--------------------------------------------------| +| `minLength` | `number` | The shortest allowed text length. | +| `maxLength` | `number` | The longest allowed text length.
Default: `32` | +| `header` | `string` | A single line of text that appears above the keyboard. | +| `defaultText` | `string` | Text to show by default. | +| `defaultTextClear` | `boolean` | Whether to clear the default text on next character typed. | -## `maxLength` -Biggest allowed text length +## View events -Type: `number` - -Default: `32` - -## `header` -Single line of text that appears above the keyboard - -Type: `string` - -# View events -## `input` -Fires when the user selects the "save" button and the text matches the length -constrained by `minLength` and `maxLength`. - -Item type: `string` +| Item | Type | Description | +|-------------|--------|--------------------------------------------------| +| `input` | `string` | Fires when the user selects the "Save" button and the text matches the length constrained by `minLength` and `maxLength`. | diff --git a/documentation/js/js_gui__widget.md b/documentation/js/js_gui__widget.md new file mode 100644 index 000000000..9ea3e4dfa --- /dev/null +++ b/documentation/js/js_gui__widget.md @@ -0,0 +1,37 @@ +# Widget GUI view {#js_gui__widget} + +Displays a combination of custom elements on one screen. + +Sample screenshot of the view + +```js +let eventLoop = require("event_loop"); +let gui = require("gui"); +let widgetView = require("gui/widget"); +``` + +This module depends on the `gui` module, which in turn depends on the +`event_loop` module, so they **must** be imported in this order. It is also +recommended to conceptualize these modules first before using this one. + +## Example +For an example, refer to the `gui.js` example script. + +## View props +This view does not have any props. + +## Children +This view has the elements as its children. +Elements are objects with properties to define them, in the form `{ element: "type", ...properties }` (e.g. `{ element: "button", button: "right", text: "Back" }`). + +| **Element Type** | **Properties** | **Description** | +|------------------|----------------|------------------------------------------------| +| `string_multiline` | `x` (number), `y` (number)
`align` ((`"t"`, `"c"`, `"b"`) + (`"l"`, `"m"`, `"r"`))
`font` (`"primary"`, `"secondary"`, `"keyboard"`, `"big_numbers"`)
`text` (string) | String of text that can span multiple lines. | +| `string` | `x` (number), `y` (number)
`align` ((`"t"`, `"c"`, `"b"`) + (`"l"`, `"m"`, `"r"`))
`font` (`"primary"`, `"secondary"`, `"keyboard"`, `"big_numbers"`)
`text` (string) | String of text. | +| `text_box` | `x` (number), `y` (number)
`w` (number), `h` (number)
`align` ((`"t"`, `"c"`, `"b"`) + (`"l"`, `"m"`, `"r"`))
`text` (string)
`stripToDots` (boolean) | Box of with text that can be scrolled vertically. | +| `text_scroll` | `x` (number), `y` (number)
`w` (number), `h` (number)
`text` (string) | Text that can be scrolled vertically. | +| `button` | `text` (string)
`button` (`"left"`, `"center"`, `"right"`) | Button at the bottom of the screen. | +| `icon` | `x` (number), `y` (number)
`iconData` ([IconData](#js_gui__icon)) | Display an icon. | +| `rect` | `x` (number), `y` (number)
`w` (number), `h` (number)
`radius` (number), `fill` (boolean) | Draw a rectangle, optionally rounded and filled. | +| `circle` | `x` (number), `y` (number)
`radius` (number), `fill` (boolean) | Draw a circle, optionally filled. | +| `line` | `x1` (number), `y1` (number)
`x2` (number), `y2` (number) | Draw a line between 2 points. | diff --git a/documentation/js/js_math.md b/documentation/js/js_math.md index ca16a9111..fd7bede17 100644 --- a/documentation/js/js_math.md +++ b/documentation/js/js_math.md @@ -1,9 +1,11 @@ -# js_math {#js_math} +# Math module {#js_math} + +The module contains mathematical methods and constants. Call the `require` function to load the module before first using its methods: -# Math module ```js let math = require("math"); ``` + # Constants ## PI @@ -16,204 +18,248 @@ The number e (Euler's number) = 2.71828182845904523536028747135266250. The smallest number that satisfies the condition: 1.0 + EPSILON != 1.0. EPSILON = 2.2204460492503131e-16. +
+ +--- + # Methods -## abs +## abs() Return the absolute value of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The absolute value of `x`. If `x` is negative (including -0), returns `-x`. Otherwise, returns `x`. The result is therefore always a positive number or 0. -### Example +**Example** ```js math.abs(-5); // 5 ``` -## acos +
+ +## acos() Return the inverse cosine (in radians) of a number. -### Parameters +**Parameters** - x: A number between -1 and 1, inclusive, representing the angle's cosine value -### Returns +**Returns** + The inverse cosine (angle in radians between 0 and π, inclusive) of `x`. If `x` is less than -1 or greater than 1, returns `NaN`. -### Example +**Example** ```js math.acos(-1); // 3.141592653589793 ``` -## acosh +
+ +## acosh() Return the inverse hyperbolic cosine of a number. -### Parameters +**Parameters** - x: A number greater than or equal to 1 -### Returns +**Returns** + The inverse hyperbolic cosine of `x`. -### Example +**Example** ```js math.acosh(1); // 0 ``` -## asin +
+ +## asin() Return the inverse sine (in radians) of a number. -### Parameters +**Parameters** - x: A number between -1 and 1, inclusive, representing the angle's sine value -### Returns +**Returns** + The inverse sine (angle in radians between -𝜋/2 and 𝜋/2, inclusive) of `x`. -### Example +**Example** ```js math.asin(0.5); // 0.5235987755982989 ``` -## asinh +
+ +## asinh() Return the inverse hyperbolic sine of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The inverse hyperbolic sine of `x`. -### Example +**Example** ```js math.asinh(1); // 0.881373587019543 ``` -## atan +
+ +## atan() Return the inverse tangent (in radians) of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The inverse tangent (angle in radians between -𝜋/2 and 𝜋/2, inclusive) of `x`. -### Example +**Example** ```js math.atan(1); // 0.7853981633974483 ``` -## atan2 +
+ +## atan2() Return the angle in the plane (in radians) between the positive x-axis and the ray from (0, 0) to the point (x, y), for math.atan2(y, x). -### Parameters +**Parameters** - y: The y coordinate of the point - x: The x coordinate of the point -### Returns +**Returns** + The angle in radians (between -π and π, inclusive) between the positive x-axis and the ray from (0, 0) to the point (x, y). -### Example +**Example** ```js math.atan2(90, 15); // 1.4056476493802699 ``` -## atanh +
+ +## atanh() The method returns the inverse hyperbolic tangent of a number. -### Parameters +**Parameters** - x: A number between -1 and 1, inclusive -### Returns +**Returns** + The inverse hyperbolic tangent of `x`. -### Example +**Example** ```js math.atanh(0.5); // 0.5493061443340548 ``` -## cbrt +
+ +## cbrt() Return the cube root of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The cube root of `x`. -### Example +**Example** ```js math.cbrt(2); // 1.2599210498948732 ``` -## ceil +
+ +## ceil() Round up and return the smallest integer greater than or equal to a given number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The smallest integer greater than or equal to `x`. It's the same value as `-math.floor(-x)`. -### Example +**Example** ```js math.ceil(-7.004); // -7 math.ceil(7.004); // 8 ``` -## clz32 +
+ +## clz32() Return the number of leading zero bits in the 32-bit binary representation of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The number of leading zero bits in the 32-bit binary representation of `x`. -### Example +**Example** ```js math.clz32(1); // 31 math.clz32(1000); // 22 ``` -## cos +
+ +## cos() Return the cosine of a number in radians. -### Parameters +**Parameters** - x: A number representing an angle in radians -### Returns +**Returns** + The cosine of `x`, between -1 and 1, inclusive. -### Example +**Example** ```js math.cos(math.PI); // -1 ``` -## exp +
+ +## exp() Return e raised to the power of a number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + A nonnegative number representing `e^x`, where `e` is the base of the natural logarithm. -### Example +**Example** ```js math.exp(0); // 1 math.exp(1); // 2.718281828459045 ``` -## floor +
+ +## floor() Round down and return the largest integer less than or equal to a given number. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The largest integer smaller than or equal to `x`. It's the same value as `-math.ceil(-x)`. -### Example +**Example** ```js math.floor(-45.95); // -46 math.floor(-45.05); // -46 @@ -223,137 +269,181 @@ math.floor(45.05); // 45 math.floor(45.95); // 45 ``` -## isEqual -Return true if the difference between numbers `a` and `b` is less than the specified parameter `e`. +
-### Parameters +## log() +Return the natural logarithm of x. + +**Parameters** +- x: A number + +**Returns** + +The natural logarithm of `x`, as in `ln(x)` where `e` is the base of the natural logarithm. + +**Example** +```js +math.log(1); // 0 +math.log(3); // 1.0986122886681098 +``` + +## isEqual() +Return true if the difference between numbers `a` and `b` is less than the specified `tolerance`. + +**Parameters** - a: A number a - b: A number b -- e: An epsilon parameter +- tolerance: How much difference is allowed between the numbers to be considered equal + +**Returns** -### Returns True if the difference between numbers `a` and `b` is less than the specified parameter `e`. Otherwise, false. -### Example +**Example** ```js math.isEqual(1.4, 1.6, 0.2); // false math.isEqual(3.556, 3.555, 0.01); // true ``` -## max +
+ +## max() Return the largest of two numbers given as input parameters. -### Parameters +**Parameters** - a: A number a - b: A number b -### Returns +**Returns** + The largest of the given numbers. -### Example +**Example** ```js math.max(10, 20); // 20 math.max(-10, -20); // -10 ``` -## min +
+ +## min() Return the smallest of two numbers given as input parameters. -### Parameters +**Parameters** - a: A number a - b: A number b -### Returns +**Returns** + The smallest of the given numbers. -### Example +**Example** ```js math.min(10, 20); // 10 math.min(-10, -20); // -20 ``` -## pow +
+ +## pow() Return the value of a base raised to a power. -### Parameters +**Parameters** - base: The base number - exponent: The exponent number -### Returns +**Returns** + A number representing base taken to the power of exponent. -### Example +**Example** ```js math.pow(7, 2); // 49 math.pow(7, 3); // 343 math.pow(2, 10); // 1024 ``` -## random +
+ +## random() Return a floating-point, pseudo-random number that's greater than or equal to 0 and less than 1, with approximately uniform distribution over that range — which you can then scale to your desired range. -### Returns +**Returns** + A floating-point, pseudo-random number between 0 (inclusive) and 1 (exclusive). -### Example +**Example** ```js let num = math.random(); ``` -## sign +
+ +## sign() Return 1 or -1, indicating the sign of the number passed as argument. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + -1 if the number is less than 0, and 1 otherwise. -### Example +**Example** ```js math.sign(3); // 1 math.sign(0); // 1 math.sign(-3); // -1 ``` -## sin +
+ +## sin() Return the sine of a number in radians. -### Parameters +**Parameters** - x: A number representing an angle in radians -### Returns +**Returns** + The sine of `x`, between -1 and 1, inclusive. -### Example +**Example** ```js math.sin(math.PI / 2); // 1 ``` -## sqrt +
+ +## sqrt() Return the square root of a number. -### Parameters +**Parameters** - x: A number greater than or equal to 0 -### Returns +**Returns** + + The square root of `x`, a nonnegative number. If `x` < 0, script will fail with an error. -### Example +**Example** ```js math.sqrt(25); // 5 ``` -## trunc +
+ +## trunc() Return the integer part of a number by removing any fractional digits. -### Parameters +**Parameters** - x: A number -### Returns +**Returns** + The integer part of `x`. -### Example +**Example** ```js math.trunc(-1.123); // -1 math.trunc(0.123); // 0 diff --git a/documentation/js/js_notification.md b/documentation/js/js_notification.md index 100da4414..753f4c9e9 100644 --- a/documentation/js/js_notification.md +++ b/documentation/js/js_notification.md @@ -1,35 +1,38 @@ -# js_notification {#js_notification} +# Notification module {#js_notification} -# Notification module ```js let notify = require("notification"); ``` -# Methods +## Methods -## success -"Success" flipper notification message +### success() +"Success" flipper notification message. -### Examples: +**Example** ```js notify.success(); ``` -## error -"Error" flipper notification message +
-### Examples: +### error() +"Error" flipper notification message. + +**Example** ```js notify.error(); ``` -## blink -Blink notification LED +
-### Parameters +### blink() +Blink notification LED. + +**Parameters** - Blink color (blue/red/green/yellow/cyan/magenta) - Blink type (short/long) -### Examples: +**Examples** ```js notify.blink("red", "short"); // Short blink of red LED notify.blink("green", "short"); // Long blink of green LED diff --git a/documentation/js/js_serial.md b/documentation/js/js_serial.md index 9d7938044..d06c799ac 100644 --- a/documentation/js/js_serial.md +++ b/documentation/js/js_serial.md @@ -1,83 +1,130 @@ -# js_serial {#js_serial} +# Serial module {#js_serial} -# Serial module ```js let serial = require("serial"); ``` # Methods -## setup +## setup() Configure serial port. Should be called before all other methods. -### Parameters -- Serial port name (usart, lpuart) -- Baudrate +**Parameters** + +- Serial port name (`"usart"`, `"lpuart"`) +- Baudrate +- Optional framing configuration object (e.g. `{ dataBits: "8", parity: "even", stopBits: "1" }`): + - `dataBits`: `"6"`, `"7"`, `"8"`, `"9"` + - 6 data bits can only be selected when parity is enabled (even or odd) + - 9 data bits can only be selected when parity is disabled (none) + - `parity`: `"none"`, `"even"`, `"odd"` + - `stopBits`: `"0.5"`, `"1"`, `"1.5"`, `"2"` + - LPUART only supports whole stop bit lengths (i.e. 1 and 2 but not 0.5 and 1.5) + +**Example** -### Examples: ```js // Configure LPUART port with baudrate = 115200 serial.setup("lpuart", 115200); ``` -## write -Write data to serial port +
+ +## write() +Write data to serial port. + +**Parameters** -### Parameters One or more arguments of the following types: - A string - Single number, each number is interpreted as a byte - Array of numbers, each number is interpreted as a byte - ArrayBuffer or DataView -### Examples: +**Example** + ```js serial.write(0x0a); // Write a single byte 0x0A serial.write("Hello, world!"); // Write a string serial.write("Hello, world!", [0x0d, 0x0a]); // Write a string followed by two bytes ``` -## read +
+ +## read() Read a fixed number of characters from serial port. -### Parameters -- Number of bytes to read -- (optional) Timeout value in ms +**Parameters** + +- Number of bytes to read +- *(optional)* Timeout value in ms + +**Returns** -### Returns A sting of received characters or undefined if nothing was received before timeout. -### Examples: +**Example** + ```js serial.read(1); // Read a single byte, without timeout serial.read(10, 5000); // Read 10 bytes, with 5s timeout ``` -## readln -Read from serial port until line break character +
-### Parameters -(optional) Timeout value in ms +## readln() +Read from serial port until line break character. + +**Parameters** + +- *(optional)* Timeout value in ms. + +**Returns** -### Returns A sting of received characters or undefined if nothing was received before timeout. -### Examples: +**Example** + ```js serial.readln(); // Read without timeout serial.readln(5000); // Read with 5s timeout ``` -## readBytes -Read from serial port until line break character +
+ +## readAny() +Read any available amount of data from serial port, can help avoid starving your loop with small reads. + +**Parameters** + +- *(optional)* Timeout value in ms + +**Returns** + +A sting of received characters or undefined if nothing was received before timeout. + +**Example** + +```js +serial.readAny(); // Read without timeout +serial.readAny(5000); // Read with 5s timeout +``` + +
+ +## readBytes() +Read from serial port until line break character. + +**Parameters** -### Parameters - Number of bytes to read -- (optional) Timeout value in ms +- *(optional)* Timeout value in ms + +**Returns** -### Returns ArrayBuffer with received data or undefined if nothing was received before timeout. -### Examples: +**Example** + ```js serial.readBytes(4); // Read 4 bytes, without timeout @@ -85,23 +132,41 @@ serial.readBytes(4); // Read 4 bytes, without timeout serial.readBytes(1, 0); ``` -## expect -Search for a string pattern in received data stream +
+ +## expect() +Search for a string pattern in received data stream. + +**Parameters** -### Parameters - Single argument or array of the following types: - A string - Array of numbers, each number is interpreted as a byte -- (optional) Timeout value in ms +- *(optional)* Timeout value in ms + +**Returns** -### Returns Index of matched pattern in input patterns list, undefined if nothing was found. -### Examples: +**Example** + ```js // Wait for root shell prompt with 1s timeout, returns 0 if it was received before timeout, undefined if not -serial.expect("# ", 1000); +serial.expect("# ", 1000); // Infinitely wait for one of two strings, should return 0 if the first string got matched, 1 if the second one serial.expect([": not found", "Usage: "]); +``` + +
+ +## end() +Deinitializes serial port, allowing multiple initializations per script run. + +**Example** + +```js +serial.end(); +// Configure LPUART port with baudrate = 115200 +serial.setup("lpuart", 115200); ``` \ No newline at end of file diff --git a/documentation/js/js_storage.md b/documentation/js/js_storage.md new file mode 100644 index 000000000..0e435be5b --- /dev/null +++ b/documentation/js/js_storage.md @@ -0,0 +1,475 @@ +# Storage module {#js_storage} + +The module allows you to access files and directories on the Flipper Zero filesystems. Call the `require` function to load the module before first using its methods: + +```js +let storage = require("storage"); +``` + +## Paths + +To work with files and folders, you'll need to specify paths to them. File paths have the following structure: + +``` +/ext/example_subdir_1/example_subdir_2/example_file.txt +\____________________________________/ \__________/ \_/ + dirPath fileName fileExt +``` + +* **dirPath** — directory path starting with `/int/` (small storage in the MCU's flash memory) or `/ext/` (microSD card storage). Specify the sub-directories containing the file using `/` as a separator between directory names. +* **fileName** — file name. +* **fileExt** — file extension (separated from the file name by a period). + +--- + +# Structures + +## FsInfo + +Filesystem information structure. + +**Fields** + +- totalSpace: Total size of the filesystem, in bytes +- freeSpace: Free space in the filesystem, in bytes + +
+ +## FileInfo + +File information structure. + +**Fields** + +- path: Full path (e.g. "/ext/test", returned by `stat`) or file name (e.g. "test", returned by `readDirectory`) +- isDirectory: Returns `true` if path leads to a directory (not a file) +- size: File size in bytes, or 0 in the case of directories +- accessTime: Time of last access as a UNIX timestamp + +--- + +# Classes + +## File + +This class implements methods for working with file. To get an object of the File class, use the `openFile` method. + +
+ +### close() + +Closes the file. After this method is called, all other operations related to this file become unavailable. + +**Returns** + +`true` on success, `false` on failure. + +
+ +### isOpen() + +**Returns** + +`true` if file is currently opened, `false` otherwise. + +
+ +### read() + +Reads bytes from a file opened in read-only or read-write mode. + +**Parameters** + +- mode: The data type to interpret the bytes as: a `string` decoded from ASCII data (`"ascii"`), or an `ArrayBuf` (`"binary"`) +- bytes: How many bytes to read from the file + +**Returns** + +An `ArrayBuf` if the mode is `"binary"`, a `string` if the mode is `ascii`. The number of bytes that was actually read may be fewer than requested. + +
+ +### write() + +Writes bytes to a file opened in write-only or read-write mode. + +**Parameters** + +- data: The data to write: a string that will be ASCII-encoded, or an ArrayBuf + +**Returns** + +The amount of bytes that was actually written. + +
+ +### seekRelative() + +Moves the R/W pointer forward. + +**Parameters** + +- bytes: How many bytes to move the pointer forward by + +**Returns** + +`true` on success, `false` on failure. + +
+ +### seekAbsolute() + +Moves the R/W pointer to an absolute position inside the file. + +**Parameters** + +- bytes: The position inside the file + +**Returns** + +`true` on success, `false` on failure. + +
+ +### tell() + +Gets the absolute position of the R/W pointer in bytes. + +**Returns** + +The absolute current position in the file. + +
+ +### truncate() + +Discards the data after the current position of the R/W pointer in a file opened in either write-only or read-write mode. + +**Returns** + +`true` on success, `false` on failure. + +
+ +### size() + +**Returns** + +The total size of the file in bytes. + +
+ +### eof() + +Detects whether the R/W pointer has reached the end of the file. + +**Returns** + +`true` if end of file reached, `false` otherwise. + +
+ +### copyTo() + +Copies bytes from the R/W pointer in the current file to the R/W pointer in another file. + +**Parameters** + +- dest: The file to copy the bytes into +- bytes: The number of bytes to copy + +**Returns** + +`true` on success, `false` on failure. + +--- + +# Methods + +## openFile() + +Opens a file. + +**Parameters** + +- path: The path to the file +- accessMode: How to access the file (`"r"`, `"w"` or `"rw"`) +- openMode: How to open the file (`"open_existing"`, `"open_always"`, `"open_append"`, `"create_new"` or `"create_always"`) + +**Returns** + +A `File` object on success, `undefined` otherwise. + +
+ +## fileExists() + +Detects whether a file exists. + +**Parameters** + +- path: The path to the file + +**Returns** + +`true` if file exists, `false` otherwise. + +
+ +## arePathsEqual() + +Determines whether the two paths are equivalent. Respects filesystem-defined path equivalence rules. + +**Parameters** + +- path1: The first path for comparison +- path2: The second path for comparison + +**Returns** + +`true` if path1 and path2 are equals, `false` otherwise. + +
+ +## isSubpathOf() + +Determines whether a path is a subpath of another path. Respects filesystem-defined path equivalence rules. + +**Parameters** + +- parentPath: The parent path +- childPath: The child path + +**Returns** + +`true` if path1 and path2 are equals, `false` otherwise. + +
+ +## fileOrDirExists() + +Detects whether a file or a directory exists. + +**Parameters** + +- path: The path to the file or directory + +**Returns** + +`true` if file/directory exists, `false` otherwise. + +**Example** +```js +if (storage.fileOrDirExists("/ext/test_dir")) { + print("Test directory exists"); +} +``` + +
+ +## fsInfo() + +Fetches generic information about a filesystem. + +**Parameters** + +- filesystem: The path to the filesystem (e.g. `"/ext"` or `"/int"`) + +**Returns** + +A `fsInfo` structure or `undefined` on failure. + +**Example** +```js +let fsinfo = storage.fsInfo("/ext"); +if (fsinfo === undefined) { + print("Filesystem access error"); +} else { + print("Free space on the /ext filesystem:", fsinfo.freeSpace); +} +``` + +
+ +## stat() + +Acquires metadata about a file or directory. + +**Parameters** + +- path: The path to the file or directory + +**Returns** + +A `FileInfo` structure or `undefined` on failure. + +**Example** +```js +let finfo = storage.stat("/ext/test_file.txt"); +if (finfo === undefined) { + print("File not found"); +} else { + print("File size:", finfo.size); +} +``` + +
+ +## directoryExists() + +Detects whether a directory exists. + +**Parameters** + +- path: The path to the directory + +**Returns** + +`true` if directory exists, `false` otherwise. + +
+ +## makeDirectory() + +Creates an empty directory. + +**Parameters** + +- path: The path to the new directory + +**Returns** + +`true` on success, `false` on failure. + +
+ +## readDirectory() + +Reads the list of files in a directory. + +**Parameters** + +- path: The path to the directory + +**Returns** + +Array of `FileInfo` structures with directory entries, or `undefined` on failure. + +
+ +## nextAvailableFilename() + +Chooses the next available filename with a numeric suffix in a directory. +``` +/ext/example_dir/example_file123.txt +\______________/ \__________/\_/ \_/ + dirPath fileName | | + | +--- fileExt + +------- suffix selected by this function +``` + +**Parameters** + +- dirPath: The directory to look in +- fileName: The base of the filename (the part before the numeric suffix) +- fileExt: The extension of the filename (the part after the numeric suffix) +- maxLen: The maximum length of the filename with the numeric suffix + +**Returns** + +The base of the filename with the next available numeric suffix, without the extension or the base directory. + +
+ +## copy() + +Copies a file or recursively copies a possibly non-empty directory. + +**Parameters** + +- oldPath: The original path to the file or directory +- newPath: The new path that the copy of the file or directory will be accessible under + +**Returns** + +`true` on success, `false` on failure. + +**Example** +```js +if (storage.copy("/ext/src_file.txt", "/ext/dst_file.txt")) { + print("File copied"); +} +``` + +
+ +## rename() + +Renames or moves a file or directory. + +**Parameters** + +- oldPath: The old path to the file or directory +- newPath: The new path that the file or directory will become accessible + +**Returns** + +`true` on success, `false` on failure. + +**Example** +```js +if (storage.rename("/ext/src_file.txt", "/ext/dst_file.txt")) { + print("File moved"); +} +``` + +
+ +## remove() + +Removes a file or an empty directory. + +**Parameters** + +- path: The path to the file or directory + +**Returns** + +`true` on success, `false` on failure. + +**Example** +```js +let path = "/ext/test_file.txt"; + +file = storage.openFile(path, "w", "create_always"); +file.write("Test"); +file.close(); +print("File created"); + +if (storage.remove(path)) { + print("File removed"); +} +``` + +
+ +## rmrf() + +Removes a file or recursively removes a possibly non-empty directory. + +**Parameters** + +- path: The path to the file or directory + +**Returns** + +`true` on success, `false` on failure. + +**Example** +```js +let path = "/ext/test_dir"; + +if (storage.rmrf(path)) { + print("Directory removed"); +} +``` diff --git a/documentation/js/js_using_js_modules.md b/documentation/js/js_using_js_modules.md new file mode 100644 index 000000000..e7d69ef76 --- /dev/null +++ b/documentation/js/js_using_js_modules.md @@ -0,0 +1,42 @@ +# Using JavaScript modules {#js_using_js_modules} + +In the previous guides, we learned how to write a basic JavaScript app using [built-in functions](#js_builtin). However, the set of built-in functions is limited, so when developing your JS apps, you'll likely want to use external JS modules. These modules offer a wide range of functions (methods) for various tasks. + +For example: +* The `serial` module enables transmitting and receiving data via a serial interface +* The `badusb` module enables USB keyboard emulation and sending key press events via USB +* The `math` module provides mathematical functions + +JS modules are written in C/C++, making them fast and efficient. They come with Flipper Zero firmware and are stored on the microSD card in compiled form as **FAL (Flipper Application File)** files. + +> [!note] +> You can find the implementation of all supported JS modules in the [Flipper Zero firmware repository](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/system/js_app/modules). Also, check out the [docs for JS modules](#js_modules) for more details. + +## How to use JS modules in your app + +Before using any of the JS module methods, you **must** import the module using the `require()` function. This loads the module into RAM, allowing you to access its methods. + +To save RAM and improve performance, avoid loading modules you don't plan to use. Also, all loaded modules will be automatically unloaded from RAM after the app execution ends. + +To load a module, call the `require()` function with the module name in quotes. For example, to load the `notification` module, write this: + +\code{.js} +let notify = require("notification"); +\endcode + +Now you can call methods of the `notification` module using the `notify` variable to access them: + +\code{.js} +let notify = require("notification"); + +notify.success(); +print("success notification"); +\endcode + +## What's next? + +Congratulations, you've completed the **Getting Started** section of our JS docs. You've learned how to run and debug JS apps, and how to use JS modules. Now, we invite you to check out the [main JavaScript page](#js) where you'll find: + +* JavaScript app examples +* Documentation on JS modules +* Additional resources related to JavaScript on Flipper Zero diff --git a/documentation/js/js_your_first_js_app.md b/documentation/js/js_your_first_js_app.md new file mode 100644 index 000000000..ee003ea77 --- /dev/null +++ b/documentation/js/js_your_first_js_app.md @@ -0,0 +1,83 @@ +# Your first JavaScript app {#js_your_first_js_app} + +In this guide, we'll create a simple script that outputs ordinal numbers with a delay and learn how to run it on Flipper Zero in different ways. All you need is your Flipper Zero, a PC, and a USB cable. + +## Step 1. Create the script file + +Create a new text file `first_app.js`. Paste the code below into it and save the file: + +\code{.js} +print("start"); +delay(1000); +print("1"); +delay(500); +print("2"); +delay(500); +print("3"); +delay(500); +print("end"); +\endcode + +What the code does: +* Outputs the text **start**, waits 1 second +* Outputs the numbers **1**, **2** and **3**, with a 0.5-second pause after each number +* Outputs the text **end** + +The `print()` function is used to output text. The string to be output is in the brackets. This is a built-in function, so you don't need to include additional JS modules. You can use this function anywhere in your application. + +Another built-in function, `delay()`, implements delay. The delay time in milliseconds is given in the brackets. Since 1000 milliseconds equals 1 second, a 1-second delay is written as 1000, and a 0.5-second delay as 500. + +> [!note] +> Find the list of built-in functions in [Built-in functions](#js_builtin). + +## Step 2. Copy the file to Flipper Zero + +To copy the JavaScript file to Flipper Zero, follow these steps: +1. Connect your Flipper Zero to your PC via USB. +2. Open the **qFlipper** application. +3. Go to the **File manager** tab and open the path `SD Card/apps/Scripts/`. +4. Drag and drop the file into the qFlipper window. + +> [!note] +> To learn more about qFlipper, visit the [dedicated section in our user documentation](https://docs.flipper.net/qflipper). + +Your script is now ready to run on Flipper Zero. + +## Step 3. Run your script + +You can launch your app in two ways: + +* From the Flipper Zero menu +* Remotely from your PC using the CLI (command-line interface) + +Let's explore them both. + +### How to run a script from Flipper Zero's menu + +1. Go to **Apps → Scripts** in your Flipper Zero's menu. Here, you'll see a list of scripts located in the `SD Card/apps/Scripts/` folder. +2. Select the script you want to run. +3. Press the **OK** button to run the script. + +\image html js_first_app_on_fz.jpg width=500 + +The Flipper Zero screen will display the strings with the specified delay, as defined by the `print()` and `delay()` functions. + +### How to run script using CLI + +The command-line interface (CLI) is a text-based interface that lets you control your Flipper Zero from your computer, including running scripts. Running JavaScript apps via CLI is useful for debugging, as it lets you write and test code remotely, without switching between your PC and the device. + +To run the script via CLI: + +1. Connect your Flipper Zero to your PC via USB. +2. Access the CLI using one of the [recommended methods](https://docs.flipper.net/development/cli#HfXTy). +3. Enter the `js path` command, replacing `path` with the path to the script file on your Flipper Zero: + +\code{.sh} +js /ext/apps/Scripts/first_app.js +\endcode + +\image html js_first_app_on_cli.jpg width=700 + +As you can see, unlike running JavaScript apps from the Flipper Zero UI, all output from the `print()` function is sent to the CLI, not the device screen. + +**Next step:** [Developing apps using JavaScript SDK](#js_developing_apps_using_js_sdk) diff --git a/fbt_options.py b/fbt_options.py index a7705335a..191c4ef1a 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -76,6 +76,7 @@ FIRMWARE_APPS = { "radio_device_cc1101_ext", "unit_tests", "js_app", + "archive", ], } diff --git a/firmware.scons b/firmware.scons index 4c5e05873..e7378f957 100644 --- a/firmware.scons +++ b/firmware.scons @@ -29,6 +29,8 @@ env = ENV.Clone( TARGETS_ROOT=Dir("#/targets"), LINT_SOURCES=[ Dir("applications"), + # Not C code + Dir("!applications/system/js_app/packages"), ], LIBPATH=[ "${LIB_DIST_DIR}", diff --git a/furi/core/check.c b/furi/core/check.c index ba05a675f..ee8ee4af0 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -102,11 +102,13 @@ static void __furi_print_bt_stack_info(void) { static void __furi_print_heap_info(void) { furi_log_puts("\r\n\t heap total: "); - __furi_put_uint32_as_text(xPortGetTotalHeapSize()); + __furi_put_uint32_as_text(configTOTAL_HEAP_SIZE); furi_log_puts("\r\n\t heap free: "); __furi_put_uint32_as_text(xPortGetFreeHeapSize()); + HeapStats_t heap_stats; + vPortGetHeapStats(&heap_stats); furi_log_puts("\r\n\t heap watermark: "); - __furi_put_uint32_as_text(xPortGetMinimumEverFreeHeapSize()); + __furi_put_uint32_as_text(heap_stats.xMinimumEverFreeBytesRemaining); } static void __furi_print_name(bool isr) { diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index fa56150ce..2d6ced025 100644 --- a/furi/core/core_defines.h +++ b/furi/core/core_defines.h @@ -41,6 +41,16 @@ extern "C" { #define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower))) #endif +#ifndef CLAMP_WRAPAROUND +#define CLAMP_WRAPAROUND(x, upper, lower) \ + ({ \ + __typeof__(x) _x = (x); \ + __typeof__(upper) _upper = (upper); \ + __typeof__(lower) _lower = (lower); \ + (_x > _upper) ? _lower : ((_x < _lower) ? _upper : _x); \ + }) +#endif + #ifndef COUNT_OF #define COUNT_OF(x) (sizeof(x) / sizeof(x[0])) #endif diff --git a/furi/core/event_loop.c b/furi/core/event_loop.c index c0998ea90..800fea86c 100644 --- a/furi/core/event_loop.c +++ b/furi/core/event_loop.c @@ -32,14 +32,6 @@ static void furi_event_loop_item_notify(FuriEventLoopItem* instance); static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance); -static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) { - for(; !PendingQueue_empty_p(instance->pending_queue); - PendingQueue_pop_back(NULL, instance->pending_queue)) { - const FuriEventLoopPendingQueueItem* item = PendingQueue_back(instance->pending_queue); - item->callback(item->context); - } -} - static bool furi_event_loop_signal_callback(uint32_t signal, void* arg, void* context) { furi_assert(context); FuriEventLoop* instance = context; @@ -86,6 +78,7 @@ void furi_event_loop_free(FuriEventLoop* instance) { furi_event_loop_process_timer_queue(instance); furi_check(TimerList_empty_p(instance->timer_list)); furi_check(WaitingList_empty_p(instance->waiting_list)); + furi_check(!instance->are_thread_flags_subscribed); FuriEventLoopTree_clear(instance->tree); PendingQueue_clear(instance->pending_queue); @@ -130,12 +123,16 @@ static inline FuriEventLoopProcessStatus furi_event_loop_unsubscribe(instance, item->object); } + instance->current_item = item; + if(item->event & FuriEventLoopEventFlagEdge) { status = furi_event_loop_process_edge_event(item); } else { status = furi_event_loop_process_level_event(item); } + instance->current_item = NULL; + if(item->owner == NULL) { status = FuriEventLoopProcessStatusFreeLater; } @@ -193,6 +190,14 @@ static void furi_event_loop_process_waiting_list(FuriEventLoop* instance) { furi_event_loop_sync_flags(instance); } +static void furi_event_loop_process_pending_callbacks(FuriEventLoop* instance) { + for(; !PendingQueue_empty_p(instance->pending_queue); + PendingQueue_pop_back(NULL, instance->pending_queue)) { + const FuriEventLoopPendingQueueItem* item = PendingQueue_back(instance->pending_queue); + item->callback(item->context); + } +} + static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flags) { if(flags) { xTaskNotifyIndexed( @@ -203,7 +208,6 @@ static void furi_event_loop_restore_flags(FuriEventLoop* instance, uint32_t flag void furi_event_loop_run(FuriEventLoop* instance) { furi_check(instance); furi_check(instance->thread_id == furi_thread_get_current_id()); - FuriThread* thread = furi_thread_get_current(); // Set the default signal callback if none was previously set @@ -213,9 +217,9 @@ void furi_event_loop_run(FuriEventLoop* instance) { furi_event_loop_init_tick(instance); - while(true) { - instance->state = FuriEventLoopStateIdle; + instance->state = FuriEventLoopStateRunning; + while(true) { const TickType_t ticks_to_sleep = MIN(furi_event_loop_get_timer_wait_time(instance), furi_event_loop_get_tick_wait_time(instance)); @@ -224,8 +228,6 @@ void furi_event_loop_run(FuriEventLoop* instance) { BaseType_t ret = xTaskNotifyWaitIndexed( FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, 0, FuriEventLoopFlagAll, &flags, ticks_to_sleep); - instance->state = FuriEventLoopStateProcessing; - if(ret == pdTRUE) { if(flags & FuriEventLoopFlagStop) { instance->state = FuriEventLoopStateStopped; @@ -242,6 +244,10 @@ void furi_event_loop_run(FuriEventLoop* instance) { } else if(flags & FuriEventLoopFlagPending) { furi_event_loop_process_pending_callbacks(instance); + } else if(flags & FuriEventLoopFlagThreadFlag) { + if(instance->are_thread_flags_subscribed) + instance->thread_flags_callback(instance->thread_flags_callback_context); + } else { furi_crash(); } @@ -415,6 +421,24 @@ void furi_event_loop_subscribe_mutex( instance, mutex, &furi_mutex_event_loop_contract, event, callback, context); } +void furi_event_loop_subscribe_thread_flags( + FuriEventLoop* instance, + FuriEventLoopThreadFlagsCallback callback, + void* context) { + furi_check(instance); + furi_check(callback); + furi_check(!instance->are_thread_flags_subscribed); + instance->are_thread_flags_subscribed = true; + instance->thread_flags_callback = callback; + instance->thread_flags_callback_context = context; +} + +void furi_event_loop_unsubscribe_thread_flags(FuriEventLoop* instance) { + furi_check(instance); + furi_check(instance->are_thread_flags_subscribed); + instance->are_thread_flags_subscribed = false; +} + /** * Public generic unsubscription API */ @@ -448,7 +472,7 @@ void furi_event_loop_unsubscribe(FuriEventLoop* instance, FuriEventLoopObject* o WaitingList_unlink(item); } - if(instance->state == FuriEventLoopStateProcessing) { + if(instance->current_item == item) { furi_event_loop_item_free_later(item); } else { furi_event_loop_item_free(item); @@ -537,6 +561,25 @@ static bool furi_event_loop_item_is_waiting(FuriEventLoopItem* instance) { return instance->WaitingList.prev || instance->WaitingList.next; } +void furi_event_loop_thread_flag_callback(FuriThreadId thread_id) { + TaskHandle_t hTask = (TaskHandle_t)thread_id; + BaseType_t yield; + + if(FURI_IS_IRQ_MODE()) { + yield = pdFALSE; + (void)xTaskNotifyIndexedFromISR( + hTask, + FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, + FuriEventLoopFlagThreadFlag, + eSetBits, + &yield); + portYIELD_FROM_ISR(yield); + } else { + (void)xTaskNotifyIndexed( + hTask, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagThreadFlag, eSetBits); + } +} + /* * Internal event loop link API, used by supported primitives */ diff --git a/furi/core/event_loop.h b/furi/core/event_loop.h index d5e8710a6..63f020f78 100644 --- a/furi/core/event_loop.h +++ b/furi/core/event_loop.h @@ -203,6 +203,12 @@ typedef void FuriEventLoopObject; */ typedef void (*FuriEventLoopEventCallback)(FuriEventLoopObject* object, void* context); +/** Callback type for event loop thread flag events + * + * @param context The context that was provided upon subscription + */ +typedef void (*FuriEventLoopThreadFlagsCallback)(void* context); + /** Opaque event flag type */ typedef struct FuriEventFlag FuriEventFlag; @@ -304,6 +310,23 @@ void furi_event_loop_subscribe_mutex( FuriEventLoopEventCallback callback, void* context); +/** Subscribe to thread flag events of the current thread + * + * @param instance The Event Loop instance + * @param callback The callback to call when a flag has been set + * @param context The context for callback + */ +void furi_event_loop_subscribe_thread_flags( + FuriEventLoop* instance, + FuriEventLoopThreadFlagsCallback callback, + void* context); + +/** Unsubscribe from thread flag events of the current thread + * + * @param instance The Event Loop instance + */ +void furi_event_loop_unsubscribe_thread_flags(FuriEventLoop* instance); + /** Unsubscribe from events (common) * * @param instance The Event Loop instance diff --git a/furi/core/event_loop_i.h b/furi/core/event_loop_i.h index 7016e1e1b..c2f04a359 100644 --- a/furi/core/event_loop_i.h +++ b/furi/core/event_loop_i.h @@ -4,12 +4,14 @@ #include "event_loop_link_i.h" #include "event_loop_timer_i.h" #include "event_loop_tick_i.h" +#include "event_loop_thread_flag_interface.h" #include #include #include #include "thread.h" +#include "thread_i.h" struct FuriEventLoopItem { // Source @@ -50,11 +52,12 @@ typedef enum { FuriEventLoopFlagStop = (1 << 1), FuriEventLoopFlagTimer = (1 << 2), FuriEventLoopFlagPending = (1 << 3), + FuriEventLoopFlagThreadFlag = (1 << 4), } FuriEventLoopFlag; #define FuriEventLoopFlagAll \ (FuriEventLoopFlagEvent | FuriEventLoopFlagStop | FuriEventLoopFlagTimer | \ - FuriEventLoopFlagPending) + FuriEventLoopFlagPending | FuriEventLoopFlagThreadFlag) typedef enum { FuriEventLoopProcessStatusComplete, @@ -64,8 +67,7 @@ typedef enum { typedef enum { FuriEventLoopStateStopped, - FuriEventLoopStateIdle, - FuriEventLoopStateProcessing, + FuriEventLoopStateRunning, } FuriEventLoopState; typedef struct { @@ -81,6 +83,7 @@ struct FuriEventLoop { // Poller state volatile FuriEventLoopState state; + volatile FuriEventLoopItem* current_item; // Event handling FuriEventLoopTree_t tree; @@ -94,4 +97,9 @@ struct FuriEventLoop { PendingQueue_t pending_queue; // Tick event FuriEventLoopTick tick; + + // Thread flags callback + bool are_thread_flags_subscribed; + FuriEventLoopThreadFlagsCallback thread_flags_callback; + void* thread_flags_callback_context; }; diff --git a/furi/core/event_loop_thread_flag_interface.h b/furi/core/event_loop_thread_flag_interface.h new file mode 100644 index 000000000..05fcd47de --- /dev/null +++ b/furi/core/event_loop_thread_flag_interface.h @@ -0,0 +1,10 @@ +#pragma once + +#include "thread.h" + +/** + * @brief Notify `FuriEventLoop` that `furi_thread_flags_set` has been called + * + * @param thread_id Thread id + */ +extern void furi_event_loop_thread_flag_callback(FuriThreadId thread_id); diff --git a/furi/core/memmgr.c b/furi/core/memmgr.c index d3ff873ae..a3bbf4556 100644 --- a/furi/core/memmgr.c +++ b/furi/core/memmgr.c @@ -1,6 +1,7 @@ #include "memmgr.h" #include #include +#include extern void* pvPortMalloc(size_t xSize); extern void vPortFree(void* pv); @@ -51,7 +52,7 @@ size_t memmgr_get_free_heap(void) { } size_t memmgr_get_total_heap(void) { - return xPortGetTotalHeapSize(); + return configTOTAL_HEAP_SIZE; } size_t memmgr_get_minimum_free_heap(void) { @@ -106,5 +107,7 @@ void* aligned_malloc(size_t size, size_t alignment) { } void aligned_free(void* p) { - free(((void**)p)[-1]); + if(p) { + free(((void**)p)[-1]); + } } diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 359d0e3db..3ce0558a3 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -1,6 +1,8 @@ /* - * FreeRTOS Kernel V10.2.1 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * FreeRTOS Kernel V11.1.0 + * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -19,10 +21,9 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - * http://www.FreeRTOS.org - * http://aws.amazon.com/freertos + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS * - * 1 tab == 4 spaces! */ /* @@ -31,21 +32,25 @@ * limits memory fragmentation. * * See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the - * memory management pages of http://www.FreeRTOS.org for more information. + * memory management pages of https://www.FreeRTOS.org for more information. */ #include "memmgr_heap.h" #include "check.h" #include +#include #include #include #include #include #include +// -V::562 +// -V::650 + /* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining -all the API functions to use the MPU wrappers. That should only be done when -task.h is included from an application file. */ + * all the API functions to use the MPU wrappers. That should only be done when + * task.h is included from an application file. */ #define MPU_WRAPPERS_INCLUDED_FROM_API_FILE #include @@ -53,8 +58,12 @@ task.h is included from an application file. */ #undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE -#ifdef HEAP_PRINT_DEBUG -#error This feature is broken, logging transport must be replaced with RTT +#if(configSUPPORT_DYNAMIC_ALLOCATION == 0) +#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 +#endif + +#ifndef configHEAP_CLEAR_MEMORY_ON_FREE +#define configHEAP_CLEAR_MEMORY_ON_FREE 0 #endif /* Block sizes must not get too small. */ @@ -63,16 +72,75 @@ task.h is included from an application file. */ /* Assumes 8bit bytes! */ #define heapBITS_PER_BYTE ((size_t)8) +/* Max value that fits in a size_t type. */ +#define heapSIZE_MAX (~((size_t)0)) + +/* Check if multiplying a and b will result in overflow. */ +#define heapMULTIPLY_WILL_OVERFLOW(a, b) (((a) > 0) && ((b) > (heapSIZE_MAX / (a)))) + +/* Check if adding a and b will result in overflow. */ +#define heapADD_WILL_OVERFLOW(a, b) ((a) > (heapSIZE_MAX - (b))) + +/* Check if the subtraction operation ( a - b ) will result in underflow. */ +#define heapSUBTRACT_WILL_UNDERFLOW(a, b) ((a) < (b)) + +/* MSB of the xBlockSize member of an BlockLink_t structure is used to track + * the allocation status of a block. When MSB of the xBlockSize member of + * an BlockLink_t structure is set then the block belongs to the application. + * When the bit is free the block is still part of the free heap space. */ +#define heapBLOCK_ALLOCATED_BITMASK (((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1)) +#define heapBLOCK_SIZE_IS_VALID(xBlockSize) (((xBlockSize) & heapBLOCK_ALLOCATED_BITMASK) == 0) +#define heapBLOCK_IS_ALLOCATED(pxBlock) \ + (((pxBlock->xBlockSize) & heapBLOCK_ALLOCATED_BITMASK) != 0) +#define heapALLOCATE_BLOCK(pxBlock) ((pxBlock->xBlockSize) |= heapBLOCK_ALLOCATED_BITMASK) +#define heapFREE_BLOCK(pxBlock) ((pxBlock->xBlockSize) &= ~heapBLOCK_ALLOCATED_BITMASK) + +/*-----------------------------------------------------------*/ + /* Heap start end symbols provided by linker */ uint8_t* ucHeap = (uint8_t*)&__heap_start__; /* Define the linked list structure. This is used to link free blocks in order -of their memory address. */ + * of their memory address. */ typedef struct A_BLOCK_LINK { - struct A_BLOCK_LINK* pxNextFreeBlock; /*<< The next free block in the list. */ - size_t xBlockSize; /*<< The size of the free block. */ + struct A_BLOCK_LINK* pxNextFreeBlock; /**< The next free block in the list. */ + size_t xBlockSize; /**< The size of the free block. */ } BlockLink_t; +/* Setting configENABLE_HEAP_PROTECTOR to 1 enables heap block pointers + * protection using an application supplied canary value to catch heap + * corruption should a heap buffer overflow occur. + */ +#if(configENABLE_HEAP_PROTECTOR == 1) + +/** + * @brief Application provided function to get a random value to be used as canary. + * + * @param pxHeapCanary [out] Output parameter to return the canary value. + */ +extern void vApplicationGetRandomHeapCanary(portPOINTER_SIZE_TYPE* pxHeapCanary); + +/* Canary value for protecting internal heap pointers. */ +PRIVILEGED_DATA static portPOINTER_SIZE_TYPE xHeapCanary; + +/* Macro to load/store BlockLink_t pointers to memory. By XORing the + * pointers with a random canary value, heap overflows will result + * in randomly unpredictable pointer values which will be caught by + * heapVALIDATE_BLOCK_POINTER assert. */ +#define heapPROTECT_BLOCK_POINTER(pxBlock) \ + ((BlockLink_t*)(((portPOINTER_SIZE_TYPE)(pxBlock)) ^ xHeapCanary)) +#else + +#define heapPROTECT_BLOCK_POINTER(pxBlock) (pxBlock) + +#endif /* configENABLE_HEAP_PROTECTOR */ + +/* Assert that a heap block pointer is within the heap bounds. */ +#define heapVALIDATE_BLOCK_POINTER(pxBlock) \ + configASSERT( \ + ((uint8_t*)(pxBlock) >= &(ucHeap[0])) && \ + ((uint8_t*)(pxBlock) <= &(ucHeap[configTOTAL_HEAP_SIZE - 1]))) + /*-----------------------------------------------------------*/ /* @@ -81,34 +149,31 @@ typedef struct A_BLOCK_LINK { * the block in front it and/or the block behind it if the memory blocks are * adjacent to each other. */ -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert); +static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) PRIVILEGED_FUNCTION; /* * Called automatically to setup the required heap structures the first time * pvPortMalloc() is called. */ -static void prvHeapInit(void); +static void prvHeapInit(void) PRIVILEGED_FUNCTION; /*-----------------------------------------------------------*/ /* The size of the structure placed at the beginning of each allocated memory -block must by correctly byte aligned. */ + * block must by correctly byte aligned. */ static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t)(portBYTE_ALIGNMENT - 1))) & ~((size_t)portBYTE_ALIGNMENT_MASK); /* Create a couple of list links to mark the start and end of the list. */ -static BlockLink_t xStart, *pxEnd = NULL; +PRIVILEGED_DATA static BlockLink_t xStart; +PRIVILEGED_DATA static BlockLink_t* pxEnd = NULL; -/* Keeps track of the number of free bytes remaining, but says nothing about -fragmentation. */ -static size_t xFreeBytesRemaining = 0U; -static size_t xMinimumEverFreeBytesRemaining = 0U; - -/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize -member of an BlockLink_t structure is set then the block belongs to the -application. When the bit is free the block is still part of the free heap -space. */ -static size_t xBlockAllocatedBit = 0; +/* Keeps track of the number of calls to allocate and free memory as well as the + * number of free bytes remaining, but says nothing about fragmentation. */ +PRIVILEGED_DATA static size_t xFreeBytesRemaining = (size_t)0U; +PRIVILEGED_DATA static size_t xMinimumEverFreeBytesRemaining = (size_t)0U; +PRIVILEGED_DATA static size_t xNumberOfSuccessfulAllocations = (size_t)0U; +PRIVILEGED_DATA static size_t xNumberOfSuccessfulFrees = (size_t)0U; /* Furi heap extension */ #include @@ -175,7 +240,7 @@ size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) { puc -= xHeapStructSize; BlockLink_t* pxLink = (void*)puc; - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0 && + if((pxLink->xBlockSize & heapBLOCK_ALLOCATED_BITMASK) && pxLink->pxNextFreeBlock == NULL) { leftovers += data->value; } @@ -220,20 +285,9 @@ static inline void traceFREE(void* pointer, size_t size) { } size_t memmgr_heap_get_max_free_block(void) { - size_t max_free_size = 0; - BlockLink_t* pxBlock; - vTaskSuspendAll(); - - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { - if(pxBlock->xBlockSize > max_free_size) { - max_free_size = pxBlock->xBlockSize; - } - pxBlock = pxBlock->pxNextFreeBlock; - } - - xTaskResumeAll(); - return max_free_size; + HeapStats_t heap_stats; + vPortGetHeapStats(&heap_stats); + return heap_stats.xSizeOfLargestFreeBlockInBytes; } void memmgr_heap_printf_free_blocks(void) { @@ -241,195 +295,126 @@ void memmgr_heap_printf_free_blocks(void) { //can be enabled once we can do printf with a locked scheduler //vTaskSuspendAll(); - pxBlock = xStart.pxNextFreeBlock; - while(pxBlock->pxNextFreeBlock != NULL) { + pxBlock = heapPROTECT_BLOCK_POINTER(xStart.pxNextFreeBlock); + heapVALIDATE_BLOCK_POINTER(pxBlock); + while(pxBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER(NULL)) { printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize); - pxBlock = pxBlock->pxNextFreeBlock; + pxBlock = heapPROTECT_BLOCK_POINTER(pxBlock->pxNextFreeBlock); + heapVALIDATE_BLOCK_POINTER(pxBlock); } //xTaskResumeAll(); } -#ifdef HEAP_PRINT_DEBUG -char* ultoa(unsigned long num, char* str, int radix) { - char temp[33]; // at radix 2 the string is at most 32 + 1 null long. - int temp_loc = 0; - int digit; - int str_loc = 0; - - //construct a backward string of the number. - do { - digit = (unsigned long)num % ((unsigned long)radix); - if(digit < 10) - temp[temp_loc++] = digit + '0'; - else - temp[temp_loc++] = digit - 10 + 'A'; - num = ((unsigned long)num) / ((unsigned long)radix); - } while((unsigned long)num > 0); - - temp_loc--; - - //now reverse the string. - while(temp_loc >= 0) { // while there are still chars - str[str_loc++] = temp[temp_loc--]; - } - str[str_loc] = 0; // add null termination. - - return str; -} - -static void print_heap_init(void) { - char tmp_str[33]; - size_t heap_start = (size_t)&__heap_start__; - size_t heap_end = (size_t)&__heap_end__; - - // {PHStart|heap_start|heap_end} - FURI_CRITICAL_ENTER(); - furi_log_puts("{PHStart|"); - ultoa(heap_start, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - ultoa(heap_end, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} - -static void print_heap_malloc(void* ptr, size_t size) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|m|address|size} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|m|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("|"); - utoa(size, tmp_str, 10); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} - -static void print_heap_free(void* ptr) { - char tmp_str[33]; - const char* name = furi_thread_get_name(furi_thread_get_current_id()); - if(!name) { - name = ""; - } - - // {thread name|f|address} - FURI_CRITICAL_ENTER(); - furi_log_puts("{"); - furi_log_puts(name); - furi_log_puts("|f|0x"); - ultoa((unsigned long)ptr, tmp_str, 16); - furi_log_puts(tmp_str); - furi_log_puts("}\r\n"); - FURI_CRITICAL_EXIT(); -} -#endif /*-----------------------------------------------------------*/ void* pvPortMalloc(size_t xWantedSize) { - BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink; + BlockLink_t* pxBlock; + BlockLink_t* pxPreviousBlock; + BlockLink_t* pxNewBlockLink; void* pvReturn = NULL; - size_t to_wipe = xWantedSize; + size_t xToWipe = xWantedSize; + size_t xAdditionalRequiredSize; + size_t xAllocatedBlockSize = 0; if(FURI_IS_IRQ_MODE()) { furi_crash("memmgt in ISR"); } -#ifdef HEAP_PRINT_DEBUG - BlockLink_t* print_heap_block = NULL; -#endif + if(xWantedSize > 0) { + /* The wanted size must be increased so it can contain a BlockLink_t + * structure in addition to the requested amount of bytes. */ + if(heapADD_WILL_OVERFLOW(xWantedSize, xHeapStructSize) == 0) { + xWantedSize += xHeapStructSize; - /* If this is the first call to malloc then the heap will require - initialisation to setup the list of free blocks. */ - if(pxEnd == NULL) { -#ifdef HEAP_PRINT_DEBUG - print_heap_init(); -#endif + /* Ensure that blocks are always aligned to the required number + * of bytes. */ + if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { + /* Byte alignment required. */ + xAdditionalRequiredSize = + portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK); - vTaskSuspendAll(); - { - prvHeapInit(); - memmgr_heap_init(); + if(heapADD_WILL_OVERFLOW(xWantedSize, xAdditionalRequiredSize) == 0) { + xWantedSize += xAdditionalRequiredSize; + } else { + xWantedSize = 0; + } + } else { + mtCOVERAGE_TEST_MARKER(); + } + } else { + xWantedSize = 0; } - (void)xTaskResumeAll(); } else { mtCOVERAGE_TEST_MARKER(); } vTaskSuspendAll(); { - /* Check the requested block size is not so large that the top bit is - set. The top bit of the block size member of the BlockLink_t structure - is used to determine who owns the block - the application or the - kernel, so it must be free. */ - if((xWantedSize & xBlockAllocatedBit) == 0) { - /* The wanted size is increased so it can contain a BlockLink_t - structure in addition to the requested amount of bytes. */ - if(xWantedSize > 0) { - xWantedSize += xHeapStructSize; - - /* Ensure that blocks are always aligned to the required number - of bytes. */ - if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) { - /* Byte alignment required. */ - xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK)); - configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0); - } else { - mtCOVERAGE_TEST_MARKER(); - } - } else { - mtCOVERAGE_TEST_MARKER(); - } + /* If this is the first call to malloc then the heap will require + * initialisation to setup the list of free blocks. */ + if(pxEnd == NULL) { + prvHeapInit(); + memmgr_heap_init(); + } else { + mtCOVERAGE_TEST_MARKER(); + } + /* Check the block size we are trying to allocate is not so large that the + * top bit is set. The top bit of the block size member of the BlockLink_t + * structure is used to determine who owns the block - the application or + * the kernel, so it must be free. */ + if(heapBLOCK_SIZE_IS_VALID(xWantedSize) != 0) { if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)) { /* Traverse the list from the start (lowest address) block until - one of adequate size is found. */ + * one of adequate size is found. */ pxPreviousBlock = &xStart; - pxBlock = xStart.pxNextFreeBlock; - while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)) { + pxBlock = heapPROTECT_BLOCK_POINTER(xStart.pxNextFreeBlock); + heapVALIDATE_BLOCK_POINTER(pxBlock); + + while((pxBlock->xBlockSize < xWantedSize) && + (pxBlock->pxNextFreeBlock != heapPROTECT_BLOCK_POINTER(NULL))) { pxPreviousBlock = pxBlock; - pxBlock = pxBlock->pxNextFreeBlock; + pxBlock = heapPROTECT_BLOCK_POINTER(pxBlock->pxNextFreeBlock); + heapVALIDATE_BLOCK_POINTER(pxBlock); } /* If the end marker was reached then a block of adequate size - was not found. */ + * was not found. */ if(pxBlock != pxEnd) { /* Return the memory space pointed to - jumping over the - BlockLink_t structure at its start. */ - pvReturn = - (void*)(((uint8_t*)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize); + * BlockLink_t structure at its start. */ + pvReturn = (void*)(((uint8_t*)heapPROTECT_BLOCK_POINTER( + pxPreviousBlock->pxNextFreeBlock)) + + xHeapStructSize); + heapVALIDATE_BLOCK_POINTER(pvReturn); /* This block is being returned for use so must be taken out - of the list of free blocks. */ + * of the list of free blocks. */ pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; /* If the block is larger than required it can be split into - two. */ + * two. */ + configASSERT( + heapSUBTRACT_WILL_UNDERFLOW(pxBlock->xBlockSize, xWantedSize) == 0); + if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) { /* This block is to be split into two. Create a new - block following the number of bytes requested. The void - cast is used to prevent byte alignment warnings from the - compiler. */ + * block following the number of bytes requested. The void + * cast is used to prevent byte alignment warnings from the + * compiler. */ pxNewBlockLink = (void*)(((uint8_t*)pxBlock) + xWantedSize); configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0); /* Calculate the sizes of two blocks split from the - single block. */ + * single block. */ pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; pxBlock->xBlockSize = xWantedSize; /* Insert the new block into the list of free blocks. */ - prvInsertBlockIntoFreeList(pxNewBlockLink); + pxNewBlockLink->pxNextFreeBlock = pxPreviousBlock->pxNextFreeBlock; + pxPreviousBlock->pxNextFreeBlock = + heapPROTECT_BLOCK_POINTER(pxNewBlockLink); } else { mtCOVERAGE_TEST_MARKER(); } @@ -442,14 +427,13 @@ void* pvPortMalloc(size_t xWantedSize) { mtCOVERAGE_TEST_MARKER(); } - /* The block is being returned - it is allocated and owned - by the application and has no "next" block. */ - pxBlock->xBlockSize |= xBlockAllocatedBit; - pxBlock->pxNextFreeBlock = NULL; + xAllocatedBlockSize = pxBlock->xBlockSize; -#ifdef HEAP_PRINT_DEBUG - print_heap_block = pxBlock; -#endif + /* The block is being returned - it is allocated and owned + * by the application and has no "next" block. */ + heapALLOCATE_BLOCK(pxBlock); + pxBlock->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(NULL); + xNumberOfSuccessfulAllocations++; } else { mtCOVERAGE_TEST_MARKER(); } @@ -460,29 +444,27 @@ void* pvPortMalloc(size_t xWantedSize) { mtCOVERAGE_TEST_MARKER(); } - traceMALLOC(pvReturn, xWantedSize); + traceMALLOC(pvReturn, xAllocatedBlockSize); + + /* Prevent compiler warnings when trace macros are not used. */ + (void)xAllocatedBlockSize; } (void)xTaskResumeAll(); -#ifdef HEAP_PRINT_DEBUG - print_heap_malloc(print_heap_block, print_heap_block->xBlockSize & ~xBlockAllocatedBit); -#endif - #if(configUSE_MALLOC_FAILED_HOOK == 1) { if(pvReturn == NULL) { - extern void vApplicationMallocFailedHook(void); vApplicationMallocFailedHook(); } else { mtCOVERAGE_TEST_MARKER(); } } -#endif +#endif /* if ( configUSE_MALLOC_FAILED_HOOK == 1 ) */ configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); furi_check(pvReturn, xWantedSize ? "out of memory" : "malloc(0)"); - pvReturn = memset(pvReturn, 0, to_wipe); + pvReturn = memset(pvReturn, 0, xToWipe); return pvReturn; } /*-----------------------------------------------------------*/ @@ -497,24 +479,30 @@ void vPortFree(void* pv) { if(pv != NULL) { /* The memory being freed will have an BlockLink_t structure immediately - before it. */ + * before it. */ puc -= xHeapStructSize; /* This casting is to keep the compiler from issuing warnings. */ pxLink = (void*)puc; - /* Check the block is actually allocated. */ - configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); - configASSERT(pxLink->pxNextFreeBlock == NULL); + heapVALIDATE_BLOCK_POINTER(pxLink); + configASSERT(heapBLOCK_IS_ALLOCATED(pxLink) != 0); + configASSERT(pxLink->pxNextFreeBlock == heapPROTECT_BLOCK_POINTER(NULL)); - if((pxLink->xBlockSize & xBlockAllocatedBit) != 0) { - if(pxLink->pxNextFreeBlock == NULL) { + if(heapBLOCK_IS_ALLOCATED(pxLink) != 0) { + if(pxLink->pxNextFreeBlock == heapPROTECT_BLOCK_POINTER(NULL)) { /* The block is being returned to the heap - it is no longer - allocated. */ - pxLink->xBlockSize &= ~xBlockAllocatedBit; - -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pxLink); + * allocated. */ + heapFREE_BLOCK(pxLink); +#if(configHEAP_CLEAR_MEMORY_ON_FREE == 1) + { + /* Check for underflow as this can occur if xBlockSize is + * overwritten in a heap block. */ + if(heapSUBTRACT_WILL_UNDERFLOW(pxLink->xBlockSize, xHeapStructSize) == 0) { + (void)memset( + puc + xHeapStructSize, 0, pxLink->xBlockSize - xHeapStructSize); + } + } #endif vTaskSuspendAll(); @@ -527,8 +515,8 @@ void vPortFree(void* pv) { /* Add this block to the list of free blocks. */ xFreeBytesRemaining += pxLink->xBlockSize; traceFREE(pv, pxLink->xBlockSize); - memset(pv, 0, pxLink->xBlockSize - xHeapStructSize); - prvInsertBlockIntoFreeList((BlockLink_t*)pxLink); + prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink)); + xNumberOfSuccessfulFrees++; } (void)xTaskResumeAll(); } else { @@ -537,19 +525,10 @@ void vPortFree(void* pv) { } else { mtCOVERAGE_TEST_MARKER(); } - } else { -#ifdef HEAP_PRINT_DEBUG - print_heap_free(pv); -#endif } } /*-----------------------------------------------------------*/ -size_t xPortGetTotalHeapSize(void) { - return (size_t)&__heap_end__ - (size_t)&__heap_start__; -} -/*-----------------------------------------------------------*/ - size_t xPortGetFreeHeapSize(void) { return xFreeBytesRemaining; } @@ -560,71 +539,98 @@ size_t xPortGetMinimumEverFreeHeapSize(void) { } /*-----------------------------------------------------------*/ +void xPortResetHeapMinimumEverFreeHeapSize(void) { + xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; +} +/*-----------------------------------------------------------*/ + void vPortInitialiseBlocks(void) { /* This just exists to keep the linker quiet. */ } /*-----------------------------------------------------------*/ -static void prvHeapInit(void) { - BlockLink_t* pxFirstFreeBlock; - uint8_t* pucAlignedHeap; - size_t uxAddress; - size_t xTotalHeapSize = (size_t)&__heap_end__ - (size_t)&__heap_start__; +void* pvPortCalloc(size_t xNum, size_t xSize) { + void* pv = NULL; - /* Ensure the heap starts on a correctly aligned boundary. */ - uxAddress = (size_t)ucHeap; + if(heapMULTIPLY_WILL_OVERFLOW(xNum, xSize) == 0) { + pv = pvPortMalloc(xNum * xSize); - if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0) { - uxAddress += (portBYTE_ALIGNMENT - 1); - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - xTotalHeapSize -= uxAddress - (size_t)ucHeap; + if(pv != NULL) { + (void)memset(pv, 0, xNum * xSize); + } } - pucAlignedHeap = (uint8_t*)uxAddress; + return pv; +} +/*-----------------------------------------------------------*/ + +static void prvHeapInit(void) /* PRIVILEGED_FUNCTION */ +{ + BlockLink_t* pxFirstFreeBlock; + portPOINTER_SIZE_TYPE uxStartAddress, uxEndAddress; + size_t xTotalHeapSize = configTOTAL_HEAP_SIZE; + + /* Ensure the heap starts on a correctly aligned boundary. */ + uxStartAddress = (portPOINTER_SIZE_TYPE)ucHeap; + + if((uxStartAddress & portBYTE_ALIGNMENT_MASK) != 0) { + uxStartAddress += (portBYTE_ALIGNMENT - 1); + uxStartAddress &= ~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK); + xTotalHeapSize -= (size_t)(uxStartAddress - (portPOINTER_SIZE_TYPE)ucHeap); + } + +#if(configENABLE_HEAP_PROTECTOR == 1) + { vApplicationGetRandomHeapCanary(&(xHeapCanary)); } +#endif /* xStart is used to hold a pointer to the first item in the list of free - blocks. The void cast is used to prevent compiler warnings. */ - xStart.pxNextFreeBlock = (void*)pucAlignedHeap; + * blocks. The void cast is used to prevent compiler warnings. */ + xStart.pxNextFreeBlock = (void*)heapPROTECT_BLOCK_POINTER(uxStartAddress); xStart.xBlockSize = (size_t)0; /* pxEnd is used to mark the end of the list of free blocks and is inserted - at the end of the heap space. */ - uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize; - uxAddress -= xHeapStructSize; - uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK); - pxEnd = (void*)uxAddress; + * at the end of the heap space. */ + uxEndAddress = uxStartAddress + (portPOINTER_SIZE_TYPE)xTotalHeapSize; + uxEndAddress -= (portPOINTER_SIZE_TYPE)xHeapStructSize; + uxEndAddress &= ~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK); + pxEnd = (BlockLink_t*)uxEndAddress; pxEnd->xBlockSize = 0; - pxEnd->pxNextFreeBlock = NULL; + pxEnd->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(NULL); /* To start with there is a single free block that is sized to take up the - entire heap space, minus the space taken by pxEnd. */ - pxFirstFreeBlock = (void*)pucAlignedHeap; - pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock; - pxFirstFreeBlock->pxNextFreeBlock = pxEnd; + * entire heap space, minus the space taken by pxEnd. */ + pxFirstFreeBlock = (BlockLink_t*)uxStartAddress; + pxFirstFreeBlock->xBlockSize = + (size_t)(uxEndAddress - (portPOINTER_SIZE_TYPE)pxFirstFreeBlock); + pxFirstFreeBlock->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(pxEnd); /* Only one block exists - and it covers the entire usable heap space. */ xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; - - /* Work out the position of the top bit in a size_t variable. */ - xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); } /*-----------------------------------------------------------*/ -static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { +static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) /* PRIVILEGED_FUNCTION */ +{ BlockLink_t* pxIterator; uint8_t* puc; /* Iterate through the list until a block is found that has a higher address - than the block being inserted. */ - for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; - pxIterator = pxIterator->pxNextFreeBlock) { + * than the block being inserted. */ + for(pxIterator = &xStart; + heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock) < pxBlockToInsert; + pxIterator = heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)) { /* Nothing to do here, just iterate to the right position. */ } + if(pxIterator != &xStart) { + heapVALIDATE_BLOCK_POINTER(pxIterator); + } + /* Do the block being inserted, and the block it is being inserted after - make a contiguous block of memory? */ + * make a contiguous block of memory? */ puc = (uint8_t*)pxIterator; + if((puc + pxIterator->xBlockSize) == (uint8_t*)pxBlockToInsert) { pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; pxBlockToInsert = pxIterator; @@ -633,27 +639,98 @@ static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) { } /* Do the block being inserted, and the block it is being inserted before - make a contiguous block of memory? */ + * make a contiguous block of memory? */ puc = (uint8_t*)pxBlockToInsert; - if((puc + pxBlockToInsert->xBlockSize) == (uint8_t*)pxIterator->pxNextFreeBlock) { - if(pxIterator->pxNextFreeBlock != pxEnd) { + + if((puc + pxBlockToInsert->xBlockSize) == + (uint8_t*)heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)) { + if(heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock) != pxEnd) { /* Form one big block from the two blocks. */ - pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; - pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; + pxBlockToInsert->xBlockSize += + heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)->xBlockSize; + pxBlockToInsert->pxNextFreeBlock = + heapPROTECT_BLOCK_POINTER(pxIterator->pxNextFreeBlock)->pxNextFreeBlock; } else { - pxBlockToInsert->pxNextFreeBlock = pxEnd; + pxBlockToInsert->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(pxEnd); } } else { pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; } - /* If the block being inserted plugged a gab, so was merged with the block - before and the block after, then it's pxNextFreeBlock pointer will have - already been set, and should not be set here as that would make it point - to itself. */ + /* If the block being inserted plugged a gap, so was merged with the block + * before and the block after, then it's pxNextFreeBlock pointer will have + * already been set, and should not be set here as that would make it point + * to itself. */ if(pxIterator != pxBlockToInsert) { - pxIterator->pxNextFreeBlock = pxBlockToInsert; + pxIterator->pxNextFreeBlock = heapPROTECT_BLOCK_POINTER(pxBlockToInsert); } else { mtCOVERAGE_TEST_MARKER(); } } +/*-----------------------------------------------------------*/ + +void vPortGetHeapStats(HeapStats_t* pxHeapStats) { + BlockLink_t* pxBlock; + size_t + xBlocks = 0, + xMaxSize = 0, + xMinSize = + portMAX_DELAY; /* portMAX_DELAY used as a portable way of getting the maximum value. */ + + vTaskSuspendAll(); + { + pxBlock = heapPROTECT_BLOCK_POINTER(xStart.pxNextFreeBlock); + + /* pxBlock will be NULL if the heap has not been initialised. The heap + * is initialised automatically when the first allocation is made. */ + if(pxBlock != NULL) { + while(pxBlock != pxEnd) { + /* Increment the number of blocks and record the largest block seen + * so far. */ + xBlocks++; + + if(pxBlock->xBlockSize > xMaxSize) { + xMaxSize = pxBlock->xBlockSize; + } + + if(pxBlock->xBlockSize < xMinSize) { + xMinSize = pxBlock->xBlockSize; + } + + /* Move to the next block in the chain until the last block is + * reached. */ + pxBlock = heapPROTECT_BLOCK_POINTER(pxBlock->pxNextFreeBlock); + } + } + } + (void)xTaskResumeAll(); + + pxHeapStats->xSizeOfLargestFreeBlockInBytes = xMaxSize; + pxHeapStats->xSizeOfSmallestFreeBlockInBytes = xMinSize; + pxHeapStats->xNumberOfFreeBlocks = xBlocks; + + taskENTER_CRITICAL(); + { + pxHeapStats->xAvailableHeapSpaceInBytes = xFreeBytesRemaining; + pxHeapStats->xNumberOfSuccessfulAllocations = xNumberOfSuccessfulAllocations; + pxHeapStats->xNumberOfSuccessfulFrees = xNumberOfSuccessfulFrees; + pxHeapStats->xMinimumEverFreeBytesRemaining = xMinimumEverFreeBytesRemaining; + } + taskEXIT_CRITICAL(); +} +/*-----------------------------------------------------------*/ + +/* + * Reset the state in this file. This state is normally initialized at start up. + * This function must be called by the application before restarting the + * scheduler. + */ +void vPortHeapResetState(void) { + pxEnd = NULL; + + xFreeBytesRemaining = (size_t)0U; + xMinimumEverFreeBytesRemaining = (size_t)0U; + xNumberOfSuccessfulAllocations = (size_t)0U; + xNumberOfSuccessfulFrees = (size_t)0U; +} +/*-----------------------------------------------------------*/ diff --git a/furi/core/record.c b/furi/core/record.c index fa384369a..17c95aa9b 100644 --- a/furi/core/record.c +++ b/furi/core/record.c @@ -80,6 +80,7 @@ bool furi_record_exists(const char* name) { void furi_record_create(const char* name, void* data) { furi_check(furi_record); furi_check(name); + furi_check(data); furi_record_lock(); diff --git a/furi/core/record.h b/furi/core/record.h index a269484f0..1fb20ed6f 100644 --- a/furi/core/record.h +++ b/furi/core/record.h @@ -27,7 +27,7 @@ bool furi_record_exists(const char* name); /** Create record * * @param name record name - * @param data data pointer + * @param data data pointer (not NULL) * @note Thread safe. Create and destroy must be executed from the same * thread. */ diff --git a/furi/core/stream_buffer.c b/furi/core/stream_buffer.c index 783b2d741..902ec931c 100644 --- a/furi/core/stream_buffer.c +++ b/furi/core/stream_buffer.c @@ -54,6 +54,11 @@ bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigg pdTRUE; } +size_t furi_stream_get_trigger_level(FuriStreamBuffer* stream_buffer) { + furi_check(stream_buffer); + return ((StaticStreamBuffer_t*)stream_buffer)->xTriggerLevelBytes; +} + size_t furi_stream_buffer_send( FuriStreamBuffer* stream_buffer, const void* data, diff --git a/furi/core/stream_buffer.h b/furi/core/stream_buffer.h index eef8ee510..deca813c7 100644 --- a/furi/core/stream_buffer.h +++ b/furi/core/stream_buffer.h @@ -54,6 +54,17 @@ void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer); */ bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigger_level); +/** + * @brief Get trigger level for stream buffer. + * A stream buffer's trigger level is the number of bytes that must be in the + * stream buffer before a task that is blocked on the stream buffer to + * wait for data is moved out of the blocked state. + * + * @param stream_buffer The stream buffer instance + * @return The trigger level for the stream buffer + */ +size_t furi_stream_get_trigger_level(FuriStreamBuffer* stream_buffer); + /** * @brief Sends bytes to a stream buffer. The bytes are copied into the stream buffer. * Wakes up task waiting for data to become available if called from ISR. diff --git a/furi/core/string.h b/furi/core/string.h index 84b8c6a24..0d407356b 100644 --- a/furi/core/string.h +++ b/furi/core/string.h @@ -129,12 +129,12 @@ void furi_string_swap(FuriString* string_1, FuriString* string_2); /** Move string_2 content to string_1. * - * Set the string to the other one, and destroy the other one. + * Copy data from one string to another and destroy the source. * - * @param string_1 The FuriString instance 1 - * @param string_2 The FuriString instance 2 + * @param destination The destination FuriString + * @param source The source FuriString */ -void furi_string_move(FuriString* string_1, FuriString* string_2); +void furi_string_move(FuriString* destination, FuriString* source); /** Compute a hash for the string. * diff --git a/furi/core/thread.c b/furi/core/thread.c index fd576ea72..e29a8711e 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -7,6 +7,7 @@ #include "check.h" #include "common_defines.h" #include "string.h" +#include "event_loop_thread_flag_interface.h" #include "log.h" #include @@ -23,12 +24,19 @@ #define THREAD_MAX_STACK_SIZE (UINT16_MAX * sizeof(StackType_t)) -typedef struct FuriThreadStdout FuriThreadStdout; +#define THREAD_STACK_WATERMARK_MIN (256u) -struct FuriThreadStdout { +typedef struct { FuriThreadStdoutWriteCallback write_callback; FuriString* buffer; -}; + void* context; +} FuriThreadStdout; + +typedef struct { + FuriThreadStdinReadCallback read_callback; + FuriString* unread_buffer; // is_service, "Service threads MUST NOT return"); + size_t stack_watermark = furi_thread_get_stack_space(thread); + if(stack_watermark < THREAD_STACK_WATERMARK_MIN) { +#ifdef FURI_DEBUG + furi_crash("Stack watermark is dangerously low"); +#endif + FURI_LOG_E( //-V779 + thread->name ? thread->name : "Thread", + "Stack watermark is too low %zu < " STRINGIFY( + THREAD_STACK_WATERMARK_MIN) ". Increase stack size.", + stack_watermark); + } + if(thread->heap_trace_enabled == true) { furi_delay_ms(33); thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)thread); @@ -136,6 +157,7 @@ static void furi_thread_body(void* context) { static void furi_thread_init_common(FuriThread* thread) { thread->output.buffer = furi_string_alloc(); + thread->input.unread_buffer = furi_string_alloc(); FuriThread* parent = NULL; if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { @@ -245,6 +267,7 @@ void furi_thread_free(FuriThread* thread) { } furi_string_free(thread->output.buffer); + furi_string_free(thread->input.unread_buffer); free(thread); } @@ -479,6 +502,9 @@ uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags) { (void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags); } } + + furi_event_loop_thread_flag_callback(thread_id); + /* Return flags after setting */ return rflags; } @@ -710,13 +736,22 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) { static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size) { if(thread->output.write_callback != NULL) { - thread->output.write_callback(data, size); + thread->output.write_callback(data, size, thread->output.context); } else { furi_log_tx((const uint8_t*)data, size); } return size; } +static size_t + __furi_thread_stdin_read(FuriThread* thread, char* data, size_t size, FuriWait timeout) { + if(thread->input.read_callback != NULL) { + return thread->input.read_callback(data, size, timeout, thread->input.context); + } else { + return 0; + } +} + static int32_t __furi_thread_stdout_flush(FuriThread* thread) { FuriString* buffer = thread->output.buffer; size_t size = furi_string_size(buffer); @@ -727,17 +762,37 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread) { return 0; } -void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { +void furi_thread_get_stdout_callback(FuriThreadStdoutWriteCallback* callback, void** context) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + furi_check(callback); + furi_check(context); + *callback = thread->output.write_callback; + *context = thread->output.context; +} + +void furi_thread_get_stdin_callback(FuriThreadStdinReadCallback* callback, void** context) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + furi_check(callback); + furi_check(context); + *callback = thread->input.read_callback; + *context = thread->input.context; +} + +void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); __furi_thread_stdout_flush(thread); thread->output.write_callback = callback; + thread->output.context = context; } -FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) { +void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context) { FuriThread* thread = furi_thread_get_current(); furi_check(thread); - return thread->output.write_callback; + thread->input.read_callback = callback; + thread->input.context = context; } size_t furi_thread_stdout_write(const char* data, size_t size) { @@ -772,6 +827,31 @@ int32_t furi_thread_stdout_flush(void) { return __furi_thread_stdout_flush(thread); } +size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + + size_t from_buffer = MIN(furi_string_size(thread->input.unread_buffer), size); + size_t from_input = size - from_buffer; + size_t from_input_actual = + __furi_thread_stdin_read(thread, buffer + from_buffer, from_input, timeout); + memcpy(buffer, furi_string_get_cstr(thread->input.unread_buffer), from_buffer); + furi_string_right(thread->input.unread_buffer, from_buffer); + + return from_buffer + from_input_actual; +} + +void furi_thread_stdin_unread(char* buffer, size_t size) { + FuriThread* thread = furi_thread_get_current(); + furi_check(thread); + + FuriString* new_buf = furi_string_alloc(); // there's no furi_string_alloc_set_strn :( + furi_string_set_strn(new_buf, buffer, size); + furi_string_cat(new_buf, thread->input.unread_buffer); + furi_string_free(thread->input.unread_buffer); + thread->input.unread_buffer = new_buf; +} + void furi_thread_suspend(FuriThreadId thread_id) { furi_check(thread_id); diff --git a/furi/core/thread.h b/furi/core/thread.h index ed7aa4553..c1f615d16 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -74,8 +74,23 @@ typedef int32_t (*FuriThreadCallback)(void* context); * * @param[in] data pointer to the data to be written to the standard out * @param[in] size size of the data in bytes + * @param[in] context optional context */ -typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); +typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size, void* context); + +/** + * @brief Standard input callback function pointer type + * + * The function to be used as a standard input callback MUST follow this signature. + * + * @param[out] buffer buffer to read data into + * @param[in] size maximum number of bytes to read into the buffer + * @param[in] timeout how long to wait for (in ticks) before giving up + * @param[in] context optional context + * @returns number of bytes that was actually read into the buffer + */ +typedef size_t ( + *FuriThreadStdinReadCallback)(char* buffer, size_t size, FuriWait timeout, void* context); /** * @brief State change callback function pointer type. @@ -464,17 +479,36 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); /** * @brief Get the standard output callback for the current thead. * - * @return pointer to the standard out callback function + * @param[out] callback where to store the stdout callback + * @param[out] context where to store the context */ -FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void); +void furi_thread_get_stdout_callback(FuriThreadStdoutWriteCallback* callback, void** context); + +/** + * @brief Get the standard input callback for the current thead. + * + * @param[out] callback where to store the stdin callback + * @param[out] context where to store the context + */ +void furi_thread_get_stdin_callback(FuriThreadStdinReadCallback* callback, void** context); /** Set standard output callback for the current thread. * * @param[in] callback pointer to the callback function or NULL to clear + * @param[in] context context to be passed to the callback */ -void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback); +void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context); + +/** Set standard input callback for the current thread. + * + * @param[in] callback pointer to the callback function or NULL to clear + * @param[in] context context to be passed to the callback + */ +void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context); /** Write data to buffered standard output. + * + * @note You can also use the standard C `putc`, `puts`, `printf` and friends. * * @param[in] data pointer to the data to be written * @param[in] size data size in bytes @@ -489,6 +523,29 @@ size_t furi_thread_stdout_write(const char* data, size_t size); */ int32_t furi_thread_stdout_flush(void); +/** Read data from the standard input + * + * @note You can also use the standard C `getc`, `gets` and friends. + * + * @param[in] buffer pointer to the buffer to read data into + * @param[in] size how many bytes to read into the buffer + * @param[in] timeout how long to wait for (in ticks) before giving up + * @return number of bytes that was actually read + */ +size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout); + +/** Puts data back into the standard input buffer + * + * `furi_thread_stdin_read` will return the bytes in the same order that they + * were supplied to this function. + * + * @note You can also use the standard C `ungetc`. + * + * @param[in] buffer pointer to the buffer to get data from + * @param[in] size how many bytes to read from the buffer + */ +void furi_thread_stdin_unread(char* buffer, size_t size); + /** * @brief Suspend a thread. * diff --git a/furi/flipper.c b/furi/flipper.c index 6d6215a9d..bafe2ed0f 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -9,6 +9,8 @@ #define TAG "Flipper" +#define HEAP_CANARY_VALUE 0x8BADF00D + static void flipper_print_version(const char* target, const Version* version) { if(version) { FURI_LOG_I( @@ -67,3 +69,7 @@ void vApplicationGetTimerTaskMemory( *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); *stack_size = configTIMER_TASK_STACK_DEPTH; } + +void vApplicationGetRandomHeapCanary(portPOINTER_SIZE_TYPE* pxHeapCanary) { + *pxHeapCanary = HEAP_CANARY_VALUE; +} diff --git a/lib/SConscript b/lib/SConscript index fb0473f8d..4e6171aba 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -43,6 +43,7 @@ libs = env.BuildModules( "ble_profile", "bit_lib", "datetime", + "ieee754_parse_wrap", ], ) diff --git a/lib/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c index f559a741a..ea90d3114 100644 --- a/lib/ble_profile/extra_profiles/hid_profile.c +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -380,7 +380,10 @@ bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta) #define CONNECTION_INTERVAL_MAX (0x24) static GapConfig template_config = { - .adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, + .adv_service = { + .UUID_Type = UUID_TYPE_16, + .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, + }, .appearance_char = GAP_APPEARANCE_KEYBOARD, .bonding_mode = true, .pairing_method = GapPairingPinCodeVerifyYesNo, diff --git a/lib/ble_profile/extra_services/hid_service.c b/lib/ble_profile/extra_services/hid_service.c index e46d2010c..9f9a0f752 100644 --- a/lib/ble_profile/extra_services/hid_service.c +++ b/lib/ble_profile/extra_services/hid_service.c @@ -10,13 +10,13 @@ #define TAG "BleHid" #define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255) -#define BLE_SVC_HID_REPORT_MAX_LEN (255) -#define BLE_SVC_HID_REPORT_REF_LEN (2) -#define BLE_SVC_HID_INFO_LEN (4) -#define BLE_SVC_HID_CONTROL_POINT_LEN (1) +#define BLE_SVC_HID_REPORT_MAX_LEN (255) +#define BLE_SVC_HID_REPORT_REF_LEN (2) +#define BLE_SVC_HID_INFO_LEN (4) +#define BLE_SVC_HID_CONTROL_POINT_LEN (1) -#define BLE_SVC_HID_INPUT_REPORT_COUNT (3) -#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0) +#define BLE_SVC_HID_INPUT_REPORT_COUNT (3) +#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0) #define BLE_SVC_HID_FEATURE_REPORT_COUNT (0) #define BLE_SVC_HID_REPORT_COUNT \ (BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \ @@ -157,6 +157,7 @@ static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) { hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; // aci_gatt_attribute_modified_event_rp0* attribute_modified; + if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { // Process modification events @@ -274,6 +275,7 @@ bool ble_svc_hid_update_input_report( .data_ptr = data, .data_len = len, }; + return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data); } diff --git a/lib/drivers/bq25896.c b/lib/drivers/bq25896.c index 76aae5e82..a44ff8c39 100644 --- a/lib/drivers/bq25896.c +++ b/lib/drivers/bq25896.c @@ -35,7 +35,7 @@ typedef struct { static bq25896_regs_t bq25896_regs; -bool bq25896_init(FuriHalI2cBusHandle* handle) { +bool bq25896_init(const FuriHalI2cBusHandle* handle) { bool result = true; bq25896_regs.r14.REG_RST = 1; @@ -78,19 +78,19 @@ bool bq25896_init(FuriHalI2cBusHandle* handle) { return result; } -void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim) { +void bq25896_set_boost_lim(const FuriHalI2cBusHandle* handle, BoostLim boost_lim) { bq25896_regs.r0A.BOOST_LIM = boost_lim; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x0A, *(uint8_t*)&bq25896_regs.r0A, BQ25896_I2C_TIMEOUT); } -void bq25896_poweroff(FuriHalI2cBusHandle* handle) { +void bq25896_poweroff(const FuriHalI2cBusHandle* handle) { bq25896_regs.r09.BATFET_DIS = 1; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x09, *(uint8_t*)&bq25896_regs.r09, BQ25896_I2C_TIMEOUT); } -ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle) { +ChrgStat bq25896_get_charge_status(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_mem( handle, BQ25896_ADDRESS, @@ -103,52 +103,52 @@ ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle) { return bq25896_regs.r0B.CHRG_STAT; } -bool bq25896_is_charging(FuriHalI2cBusHandle* handle) { +bool bq25896_is_charging(const FuriHalI2cBusHandle* handle) { // Include precharge, fast charging, and charging termination done as "charging" return bq25896_get_charge_status(handle) != ChrgStatNo; } -bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle) { +bool bq25896_is_charging_done(const FuriHalI2cBusHandle* handle) { return bq25896_get_charge_status(handle) == ChrgStatDone; } -void bq25896_enable_charging(FuriHalI2cBusHandle* handle) { +void bq25896_enable_charging(const FuriHalI2cBusHandle* handle) { bq25896_regs.r03.CHG_CONFIG = 1; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT); } -void bq25896_disable_charging(FuriHalI2cBusHandle* handle) { +void bq25896_disable_charging(const FuriHalI2cBusHandle* handle) { bq25896_regs.r03.CHG_CONFIG = 0; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT); } -void bq25896_enable_otg(FuriHalI2cBusHandle* handle) { +void bq25896_enable_otg(const FuriHalI2cBusHandle* handle) { bq25896_regs.r03.OTG_CONFIG = 1; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT); } -void bq25896_disable_otg(FuriHalI2cBusHandle* handle) { +void bq25896_disable_otg(const FuriHalI2cBusHandle* handle) { bq25896_regs.r03.OTG_CONFIG = 0; furi_hal_i2c_write_reg_8( handle, BQ25896_ADDRESS, 0x03, *(uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT); } -bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle) { +bool bq25896_is_otg_enabled(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x03, (uint8_t*)&bq25896_regs.r03, BQ25896_I2C_TIMEOUT); return bq25896_regs.r03.OTG_CONFIG; } -uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle) { +uint16_t bq25896_get_vreg_voltage(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x06, (uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT); return (uint16_t)bq25896_regs.r06.VREG * 16 + 3840; } -void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) { +void bq25896_set_vreg_voltage(const FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) { if(vreg_voltage < 3840) { // Minimum valid value is 3840 mV vreg_voltage = 3840; @@ -166,13 +166,13 @@ void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage handle, BQ25896_ADDRESS, 0x06, *(uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT); } -bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle) { +bool bq25896_check_otg_fault(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x0C, (uint8_t*)&bq25896_regs.r0C, BQ25896_I2C_TIMEOUT); return bq25896_regs.r0C.BOOST_FAULT; } -uint16_t bq25896_get_vbus_voltage(FuriHalI2cBusHandle* handle) { +uint16_t bq25896_get_vbus_voltage(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x11, (uint8_t*)&bq25896_regs.r11, BQ25896_I2C_TIMEOUT); if(bq25896_regs.r11.VBUS_GD) { @@ -182,25 +182,25 @@ uint16_t bq25896_get_vbus_voltage(FuriHalI2cBusHandle* handle) { } } -uint16_t bq25896_get_vsys_voltage(FuriHalI2cBusHandle* handle) { +uint16_t bq25896_get_vsys_voltage(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x0F, (uint8_t*)&bq25896_regs.r0F, BQ25896_I2C_TIMEOUT); return (uint16_t)bq25896_regs.r0F.SYSV * 20 + 2304; } -uint16_t bq25896_get_vbat_voltage(FuriHalI2cBusHandle* handle) { +uint16_t bq25896_get_vbat_voltage(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x0E, (uint8_t*)&bq25896_regs.r0E, BQ25896_I2C_TIMEOUT); return (uint16_t)bq25896_regs.r0E.BATV * 20 + 2304; } -uint16_t bq25896_get_vbat_current(FuriHalI2cBusHandle* handle) { +uint16_t bq25896_get_vbat_current(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x12, (uint8_t*)&bq25896_regs.r12, BQ25896_I2C_TIMEOUT); return (uint16_t)bq25896_regs.r12.ICHGR * 50; } -uint32_t bq25896_get_ntc_mpct(FuriHalI2cBusHandle* handle) { +uint32_t bq25896_get_ntc_mpct(const FuriHalI2cBusHandle* handle) { furi_hal_i2c_read_reg_8( handle, BQ25896_ADDRESS, 0x10, (uint8_t*)&bq25896_regs.r10, BQ25896_I2C_TIMEOUT); return (uint32_t)bq25896_regs.r10.TSPCT * 465 + 21000; diff --git a/lib/drivers/bq25896.h b/lib/drivers/bq25896.h index d35625ab3..69c19868c 100644 --- a/lib/drivers/bq25896.h +++ b/lib/drivers/bq25896.h @@ -7,61 +7,61 @@ #include /** Initialize Driver */ -bool bq25896_init(FuriHalI2cBusHandle* handle); +bool bq25896_init(const FuriHalI2cBusHandle* handle); /** Set boost lim*/ -void bq25896_set_boost_lim(FuriHalI2cBusHandle* handle, BoostLim boost_lim); +void bq25896_set_boost_lim(const FuriHalI2cBusHandle* handle, BoostLim boost_lim); /** Send device into shipping mode */ -void bq25896_poweroff(FuriHalI2cBusHandle* handle); +void bq25896_poweroff(const FuriHalI2cBusHandle* handle); /** Get charging status */ -ChrgStat bq25896_get_charge_status(FuriHalI2cBusHandle* handle); +ChrgStat bq25896_get_charge_status(const FuriHalI2cBusHandle* handle); /** Is currently charging */ -bool bq25896_is_charging(FuriHalI2cBusHandle* handle); +bool bq25896_is_charging(const FuriHalI2cBusHandle* handle); /** Is charging completed while connected to charger */ -bool bq25896_is_charging_done(FuriHalI2cBusHandle* handle); +bool bq25896_is_charging_done(const FuriHalI2cBusHandle* handle); /** Enable charging */ -void bq25896_enable_charging(FuriHalI2cBusHandle* handle); +void bq25896_enable_charging(const FuriHalI2cBusHandle* handle); /** Disable charging */ -void bq25896_disable_charging(FuriHalI2cBusHandle* handle); +void bq25896_disable_charging(const FuriHalI2cBusHandle* handle); /** Enable otg */ -void bq25896_enable_otg(FuriHalI2cBusHandle* handle); +void bq25896_enable_otg(const FuriHalI2cBusHandle* handle); /** Disable otg */ -void bq25896_disable_otg(FuriHalI2cBusHandle* handle); +void bq25896_disable_otg(const FuriHalI2cBusHandle* handle); /** Is otg enabled */ -bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle); +bool bq25896_is_otg_enabled(const FuriHalI2cBusHandle* handle); /** Get VREG (charging limit) voltage in mV */ -uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle); +uint16_t bq25896_get_vreg_voltage(const FuriHalI2cBusHandle* handle); /** Set VREG (charging limit) voltage in mV * * Valid range: 3840mV - 4208mV, in steps of 16mV */ -void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage); +void bq25896_set_vreg_voltage(const FuriHalI2cBusHandle* handle, uint16_t vreg_voltage); /** Check OTG BOOST Fault status */ -bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle); +bool bq25896_check_otg_fault(const FuriHalI2cBusHandle* handle); /** Get VBUS Voltage in mV */ -uint16_t bq25896_get_vbus_voltage(FuriHalI2cBusHandle* handle); +uint16_t bq25896_get_vbus_voltage(const FuriHalI2cBusHandle* handle); /** Get VSYS Voltage in mV */ -uint16_t bq25896_get_vsys_voltage(FuriHalI2cBusHandle* handle); +uint16_t bq25896_get_vsys_voltage(const FuriHalI2cBusHandle* handle); /** Get VBAT Voltage in mV */ -uint16_t bq25896_get_vbat_voltage(FuriHalI2cBusHandle* handle); +uint16_t bq25896_get_vbat_voltage(const FuriHalI2cBusHandle* handle); /** Get VBAT current in mA */ -uint16_t bq25896_get_vbat_current(FuriHalI2cBusHandle* handle); +uint16_t bq25896_get_vbat_current(const FuriHalI2cBusHandle* handle); /** Get NTC voltage in mpct of REGN */ -uint32_t bq25896_get_ntc_mpct(FuriHalI2cBusHandle* handle); +uint32_t bq25896_get_ntc_mpct(const FuriHalI2cBusHandle* handle); diff --git a/lib/drivers/bq27220.c b/lib/drivers/bq27220.c index d60e287da..127f7c6b9 100644 --- a/lib/drivers/bq27220.c +++ b/lib/drivers/bq27220.c @@ -43,7 +43,7 @@ #endif static inline bool bq27220_read_reg( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, uint8_t* buffer, size_t buffer_size) { @@ -52,7 +52,7 @@ static inline bool bq27220_read_reg( } static inline bool bq27220_write( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* buffer, size_t buffer_size) { @@ -60,11 +60,11 @@ static inline bool bq27220_write( handle, BQ27220_ADDRESS, address, buffer, buffer_size, BQ27220_I2C_TIMEOUT); } -static inline bool bq27220_control(FuriHalI2cBusHandle* handle, uint16_t control) { +static inline bool bq27220_control(const FuriHalI2cBusHandle* handle, uint16_t control) { return bq27220_write(handle, CommandControl, (uint8_t*)&control, 2); } -static uint16_t bq27220_read_word(FuriHalI2cBusHandle* handle, uint8_t address) { +static uint16_t bq27220_read_word(const FuriHalI2cBusHandle* handle, uint8_t address) { uint16_t buf = BQ27220_ERROR; if(!bq27220_read_reg(handle, address, (uint8_t*)&buf, 2)) { @@ -83,7 +83,7 @@ static uint8_t bq27220_get_checksum(uint8_t* data, uint16_t len) { } static bool bq27220_parameter_check( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint16_t address, uint32_t value, size_t size, @@ -163,7 +163,7 @@ static bool bq27220_parameter_check( } static bool bq27220_data_memory_check( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory, bool update) { if(update) { @@ -268,7 +268,7 @@ static bool bq27220_data_memory_check( return result; } -bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { +bool bq27220_init(const FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) { bool result = false; bool reset_and_provisioning_required = false; @@ -365,7 +365,7 @@ bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) return result; } -bool bq27220_reset(FuriHalI2cBusHandle* handle) { +bool bq27220_reset(const FuriHalI2cBusHandle* handle) { bool result = false; do { if(!bq27220_control(handle, Control_RESET)) { @@ -396,7 +396,7 @@ bool bq27220_reset(FuriHalI2cBusHandle* handle) { return result; } -bool bq27220_seal(FuriHalI2cBusHandle* handle) { +bool bq27220_seal(const FuriHalI2cBusHandle* handle) { Bq27220OperationStatus operation_status = {0}; bool result = false; do { @@ -431,7 +431,7 @@ bool bq27220_seal(FuriHalI2cBusHandle* handle) { return result; } -bool bq27220_unseal(FuriHalI2cBusHandle* handle) { +bool bq27220_unseal(const FuriHalI2cBusHandle* handle) { Bq27220OperationStatus operation_status = {0}; bool result = false; do { @@ -465,7 +465,7 @@ bool bq27220_unseal(FuriHalI2cBusHandle* handle) { return result; } -bool bq27220_full_access(FuriHalI2cBusHandle* handle) { +bool bq27220_full_access(const FuriHalI2cBusHandle* handle) { bool result = false; do { @@ -518,29 +518,35 @@ bool bq27220_full_access(FuriHalI2cBusHandle* handle) { return result; } -uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_voltage(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandVoltage); } -int16_t bq27220_get_current(FuriHalI2cBusHandle* handle) { +int16_t bq27220_get_current(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandCurrent); } -bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status) { +bool bq27220_get_control_status( + const FuriHalI2cBusHandle* handle, + Bq27220ControlStatus* control_status) { return bq27220_read_reg(handle, CommandControl, (uint8_t*)control_status, 2); } -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status) { +bool bq27220_get_battery_status( + const FuriHalI2cBusHandle* handle, + Bq27220BatteryStatus* battery_status) { return bq27220_read_reg(handle, CommandBatteryStatus, (uint8_t*)battery_status, 2); } bool bq27220_get_operation_status( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, Bq27220OperationStatus* operation_status) { return bq27220_read_reg(handle, CommandOperationStatus, (uint8_t*)operation_status, 2); } -bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status) { +bool bq27220_get_gauging_status( + const FuriHalI2cBusHandle* handle, + Bq27220GaugingStatus* gauging_status) { // Request gauging data to be loaded to MAC if(!bq27220_control(handle, Control_GAUGING_STATUS)) { FURI_LOG_E(TAG, "DM SelectSubclass for read failed"); @@ -552,26 +558,26 @@ bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatu return bq27220_read_reg(handle, CommandMACData, (uint8_t*)gauging_status, 2); } -uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_temperature(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandTemperature); } -uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_full_charge_capacity(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandFullChargeCapacity); } -uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_design_capacity(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandDesignCapacity); } -uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_remaining_capacity(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandRemainingCapacity); } -uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_state_of_charge(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandStateOfCharge); } -uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle) { +uint16_t bq27220_get_state_of_health(const FuriHalI2cBusHandle* handle) { return bq27220_read_word(handle, CommandStateOfHealth); } diff --git a/lib/drivers/bq27220.h b/lib/drivers/bq27220.h index cdfcb20b1..addbf08f5 100644 --- a/lib/drivers/bq27220.h +++ b/lib/drivers/bq27220.h @@ -136,7 +136,7 @@ typedef struct BQ27220DMData BQ27220DMData; * * @return true on success, false otherwise */ -bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); +bool bq27220_init(const FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory); /** Reset gauge * @@ -144,7 +144,7 @@ bool bq27220_init(FuriHalI2cBusHandle* handle, const BQ27220DMData* data_memory) * * @return true on success, false otherwise */ -bool bq27220_reset(FuriHalI2cBusHandle* handle); +bool bq27220_reset(const FuriHalI2cBusHandle* handle); /** Seal gauge access * @@ -152,7 +152,7 @@ bool bq27220_reset(FuriHalI2cBusHandle* handle); * * @return true on success, false otherwise */ -bool bq27220_seal(FuriHalI2cBusHandle* handle); +bool bq27220_seal(const FuriHalI2cBusHandle* handle); /** Unseal gauge access * @@ -160,7 +160,7 @@ bool bq27220_seal(FuriHalI2cBusHandle* handle); * * @return true on success, false otherwise */ -bool bq27220_unseal(FuriHalI2cBusHandle* handle); +bool bq27220_unseal(const FuriHalI2cBusHandle* handle); /** Get full access * @@ -170,7 +170,7 @@ bool bq27220_unseal(FuriHalI2cBusHandle* handle); * * @return true on success, false otherwise */ -bool bq27220_full_access(FuriHalI2cBusHandle* handle); +bool bq27220_full_access(const FuriHalI2cBusHandle* handle); /** Get battery voltage * @@ -178,7 +178,7 @@ bool bq27220_full_access(FuriHalI2cBusHandle* handle); * * @return voltage in mV or BQ27220_ERROR */ -uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_voltage(const FuriHalI2cBusHandle* handle); /** Get current * @@ -186,7 +186,7 @@ uint16_t bq27220_get_voltage(FuriHalI2cBusHandle* handle); * * @return current in mA or BQ27220_ERROR */ -int16_t bq27220_get_current(FuriHalI2cBusHandle* handle); +int16_t bq27220_get_current(const FuriHalI2cBusHandle* handle); /** Get control status * @@ -195,7 +195,9 @@ int16_t bq27220_get_current(FuriHalI2cBusHandle* handle); * * @return true on success, false otherwise */ -bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatus* control_status); +bool bq27220_get_control_status( + const FuriHalI2cBusHandle* handle, + Bq27220ControlStatus* control_status); /** Get battery status * @@ -204,7 +206,9 @@ bool bq27220_get_control_status(FuriHalI2cBusHandle* handle, Bq27220ControlStatu * * @return true on success, false otherwise */ -bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatus* battery_status); +bool bq27220_get_battery_status( + const FuriHalI2cBusHandle* handle, + Bq27220BatteryStatus* battery_status); /** Get operation status * @@ -214,7 +218,7 @@ bool bq27220_get_battery_status(FuriHalI2cBusHandle* handle, Bq27220BatteryStatu * @return true on success, false otherwise */ bool bq27220_get_operation_status( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, Bq27220OperationStatus* operation_status); /** Get gauging status @@ -224,7 +228,9 @@ bool bq27220_get_operation_status( * * @return true on success, false otherwise */ -bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatus* gauging_status); +bool bq27220_get_gauging_status( + const FuriHalI2cBusHandle* handle, + Bq27220GaugingStatus* gauging_status); /** Get temperature * @@ -232,7 +238,7 @@ bool bq27220_get_gauging_status(FuriHalI2cBusHandle* handle, Bq27220GaugingStatu * * @return temperature in units of 0.1°K */ -uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_temperature(const FuriHalI2cBusHandle* handle); /** Get compensated full charge capacity * @@ -240,7 +246,7 @@ uint16_t bq27220_get_temperature(FuriHalI2cBusHandle* handle); * * @return full charge capacity in mAh or BQ27220_ERROR */ -uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_full_charge_capacity(const FuriHalI2cBusHandle* handle); /** Get design capacity * @@ -248,7 +254,7 @@ uint16_t bq27220_get_full_charge_capacity(FuriHalI2cBusHandle* handle); * * @return design capacity in mAh or BQ27220_ERROR */ -uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_design_capacity(const FuriHalI2cBusHandle* handle); /** Get remaining capacity * @@ -256,7 +262,7 @@ uint16_t bq27220_get_design_capacity(FuriHalI2cBusHandle* handle); * * @return remaining capacity in mAh or BQ27220_ERROR */ -uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_remaining_capacity(const FuriHalI2cBusHandle* handle); /** Get predicted remaining battery capacity * @@ -264,7 +270,7 @@ uint16_t bq27220_get_remaining_capacity(FuriHalI2cBusHandle* handle); * * @return state of charge in percents or BQ27220_ERROR */ -uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_state_of_charge(const FuriHalI2cBusHandle* handle); /** Get ratio of full charge capacity over design capacity * @@ -272,4 +278,4 @@ uint16_t bq27220_get_state_of_charge(FuriHalI2cBusHandle* handle); * * @return state of health in percents or BQ27220_ERROR */ -uint16_t bq27220_get_state_of_health(FuriHalI2cBusHandle* handle); +uint16_t bq27220_get_state_of_health(const FuriHalI2cBusHandle* handle); diff --git a/lib/drivers/cc1101.c b/lib/drivers/cc1101.c index 40b286a9b..ff2f0d610 100644 --- a/lib/drivers/cc1101.c +++ b/lib/drivers/cc1101.c @@ -3,7 +3,8 @@ #include #include -static bool cc1101_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) { +static bool + cc1101_spi_trx(const FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) { FuriHalCortexTimer timer = furi_hal_cortex_timer_get(CC1101_TIMEOUT * 1000); while(furi_hal_gpio_read(handle->miso)) { @@ -16,7 +17,7 @@ static bool cc1101_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx return true; } -CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) { +CC1101Status cc1101_strobe(const FuriHalSpiBusHandle* handle, uint8_t strobe) { uint8_t tx[1] = {strobe}; CC1101Status rx[1] = {0}; rx[0].CHIP_RDYn = 1; @@ -27,7 +28,7 @@ CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) { return rx[0]; } -CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) { +CC1101Status cc1101_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) { uint8_t tx[2] = {reg, data}; CC1101Status rx[2] = {0}; rx[0].CHIP_RDYn = 1; @@ -39,7 +40,7 @@ CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t return rx[1]; } -CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data) { +CC1101Status cc1101_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data) { assert(sizeof(CC1101Status) == 1); uint8_t tx[2] = {reg | CC1101_READ, 0}; CC1101Status rx[2] = {0}; @@ -52,33 +53,36 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* return rx[0]; } -uint8_t cc1101_get_partnumber(FuriHalSpiBusHandle* handle) { +uint8_t cc1101_get_partnumber(const FuriHalSpiBusHandle* handle) { uint8_t partnumber = 0; cc1101_read_reg(handle, CC1101_STATUS_PARTNUM | CC1101_BURST, &partnumber); return partnumber; } -uint8_t cc1101_get_version(FuriHalSpiBusHandle* handle) { +uint8_t cc1101_get_version(const FuriHalSpiBusHandle* handle) { uint8_t version = 0; cc1101_read_reg(handle, CC1101_STATUS_VERSION | CC1101_BURST, &version); return version; } -uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle) { +uint8_t cc1101_get_rssi(const FuriHalSpiBusHandle* handle) { uint8_t rssi = 0; cc1101_read_reg(handle, CC1101_STATUS_RSSI | CC1101_BURST, &rssi); return rssi; } -CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_reset(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SRES); } -CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_get_status(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SNOP); } -bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, uint32_t timeout_us) { +bool cc1101_wait_status_state( + const FuriHalSpiBusHandle* handle, + CC1101State state, + uint32_t timeout_us) { bool result = false; CC1101Status status = {0}; FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout_us); @@ -92,35 +96,35 @@ bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, ui return result; } -CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_shutdown(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SPWD); } -CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_calibrate(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SCAL); } -CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_switch_to_idle(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SIDLE); } -CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_switch_to_rx(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SRX); } -CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_switch_to_tx(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_STX); } -CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_flush_rx(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SFRX); } -CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle) { +CC1101Status cc1101_flush_tx(const FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SFTX); } -uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value) { +uint32_t cc1101_set_frequency(const FuriHalSpiBusHandle* handle, uint32_t value) { uint64_t real_value = (uint64_t)value * CC1101_FDIV / CC1101_QUARTZ; // Sanity check @@ -135,7 +139,7 @@ uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value) { return (uint32_t)real_frequency; } -uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t value) { +uint32_t cc1101_set_intermediate_frequency(const FuriHalSpiBusHandle* handle, uint32_t value) { uint64_t real_value = value * CC1101_IFDIV / CC1101_QUARTZ; assert((real_value & 0xFF) == real_value); @@ -146,7 +150,7 @@ uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t return (uint32_t)real_frequency; } -void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]) { +void cc1101_set_pa_table(const FuriHalSpiBusHandle* handle, const uint8_t value[8]) { uint8_t tx[9] = {CC1101_PATABLE | CC1101_BURST}; //-V1009 CC1101Status rx[9] = {0}; rx[0].CHIP_RDYn = 1; @@ -159,7 +163,7 @@ void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]) { assert((rx[0].CHIP_RDYn | rx[8].CHIP_RDYn) == 0); } -uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size) { +uint8_t cc1101_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size) { uint8_t buff_tx[64]; uint8_t buff_rx[64]; buff_tx[0] = CC1101_FIFO | CC1101_BURST; @@ -170,7 +174,7 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint return size; } -uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size) { +uint8_t cc1101_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size) { uint8_t buff_trx[2]; buff_trx[0] = CC1101_FIFO | CC1101_READ | CC1101_BURST; diff --git a/lib/drivers/cc1101.h b/lib/drivers/cc1101.h index c8c552bec..2828f1cdf 100644 --- a/lib/drivers/cc1101.h +++ b/lib/drivers/cc1101.h @@ -19,7 +19,7 @@ extern "C" { * * @return device status */ -CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe); +CC1101Status cc1101_strobe(const FuriHalSpiBusHandle* handle, uint8_t strobe); /** Write device register * @@ -29,7 +29,7 @@ CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe); * * @return device status */ -CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data); +CC1101Status cc1101_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data); /** Read device register * @@ -39,7 +39,7 @@ CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t * * @return device status */ -CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data); +CC1101Status cc1101_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data); /* High level API */ @@ -49,7 +49,7 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* * * @return CC1101Status structure */ -CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_reset(const FuriHalSpiBusHandle* handle); /** Get status * @@ -57,7 +57,7 @@ CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle); * * @return CC1101Status structure */ -CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_get_status(const FuriHalSpiBusHandle* handle); /** Wait specific chip state * @@ -67,7 +67,10 @@ CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle); * * @return true on success, false otherwise */ -bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, uint32_t timeout_us); +bool cc1101_wait_status_state( + const FuriHalSpiBusHandle* handle, + CC1101State state, + uint32_t timeout_us); /** Enable shutdown mode * @@ -75,7 +78,7 @@ bool cc1101_wait_status_state(FuriHalSpiBusHandle* handle, CC1101State state, ui * * @return CC1101Status structure */ -CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_shutdown(const FuriHalSpiBusHandle* handle); /** Get Partnumber * @@ -83,7 +86,7 @@ CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle); * * @return part number id */ -uint8_t cc1101_get_partnumber(FuriHalSpiBusHandle* handle); +uint8_t cc1101_get_partnumber(const FuriHalSpiBusHandle* handle); /** Get Version * @@ -91,7 +94,7 @@ uint8_t cc1101_get_partnumber(FuriHalSpiBusHandle* handle); * * @return version */ -uint8_t cc1101_get_version(FuriHalSpiBusHandle* handle); +uint8_t cc1101_get_version(const FuriHalSpiBusHandle* handle); /** Get raw RSSI value * @@ -99,7 +102,7 @@ uint8_t cc1101_get_version(FuriHalSpiBusHandle* handle); * * @return rssi value */ -uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle); +uint8_t cc1101_get_rssi(const FuriHalSpiBusHandle* handle); /** Calibrate oscillator * @@ -107,13 +110,13 @@ uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle); * * @return CC1101Status structure */ -CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_calibrate(const FuriHalSpiBusHandle* handle); /** Switch to idle * * @param handle - pointer to FuriHalSpiHandle */ -CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_idle(const FuriHalSpiBusHandle* handle); /** Switch to RX * @@ -121,7 +124,7 @@ CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle); * * @return CC1101Status structure */ -CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_rx(const FuriHalSpiBusHandle* handle); /** Switch to TX * @@ -129,7 +132,7 @@ CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle); * * @return CC1101Status structure */ -CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_tx(const FuriHalSpiBusHandle* handle); /** Flush RX FIFO * @@ -137,13 +140,13 @@ CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle); * * @return CC1101Status structure */ -CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_flush_rx(const FuriHalSpiBusHandle* handle); /** Flush TX FIFO * * @param handle - pointer to FuriHalSpiHandle */ -CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_flush_tx(const FuriHalSpiBusHandle* handle); /** Set Frequency * @@ -152,7 +155,7 @@ CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle); * * @return real frequency that were synthesized */ -uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value); +uint32_t cc1101_set_frequency(const FuriHalSpiBusHandle* handle, uint32_t value); /** Set Intermediate Frequency * @@ -161,14 +164,14 @@ uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value); * * @return real inermediate frequency that were synthesized */ -uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t value); +uint32_t cc1101_set_intermediate_frequency(const FuriHalSpiBusHandle* handle, uint32_t value); /** Set Power Amplifier level table, ramp * * @param handle - pointer to FuriHalSpiHandle * @param value - array of power level values */ -void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]); +void cc1101_set_pa_table(const FuriHalSpiBusHandle* handle, const uint8_t value[8]); /** Write FIFO * @@ -178,7 +181,7 @@ void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]); * * @return size, written bytes count */ -uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size); +uint8_t cc1101_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* data, uint8_t size); /** Read FIFO * @@ -188,7 +191,7 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint * * @return size, read bytes count */ -uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size); +uint8_t cc1101_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* size); #ifdef __cplusplus } diff --git a/lib/drivers/lp5562.c b/lib/drivers/lp5562.c index 30a5b559a..7db9bbce4 100644 --- a/lib/drivers/lp5562.c +++ b/lib/drivers/lp5562.c @@ -3,12 +3,12 @@ #include "lp5562_reg.h" #include -void lp5562_reset(FuriHalI2cBusHandle* handle) { +void lp5562_reset(const FuriHalI2cBusHandle* handle) { Reg0D_Reset reg = {.value = 0xFF}; furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x0D, *(uint8_t*)®, LP5562_I2C_TIMEOUT); } -void lp5562_configure(FuriHalI2cBusHandle* handle) { +void lp5562_configure(const FuriHalI2cBusHandle* handle) { Reg08_Config config = {.INT_CLK_EN = true, .PS_EN = true, .PWM_HF = true}; furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x08, *(uint8_t*)&config, LP5562_I2C_TIMEOUT); @@ -21,14 +21,17 @@ void lp5562_configure(FuriHalI2cBusHandle* handle) { furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x70, *(uint8_t*)&map, LP5562_I2C_TIMEOUT); } -void lp5562_enable(FuriHalI2cBusHandle* handle) { +void lp5562_enable(const FuriHalI2cBusHandle* handle) { Reg00_Enable reg = {.CHIP_EN = true, .LOG_EN = true}; furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x00, *(uint8_t*)®, LP5562_I2C_TIMEOUT); //>488μs delay is required after writing to 0x00 register, otherwise program engine will not work furi_delay_us(500); } -void lp5562_set_channel_current(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value) { +void lp5562_set_channel_current( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + uint8_t value) { uint8_t reg_no; if(channel == LP5562ChannelRed) { reg_no = LP5562_CHANNEL_RED_CURRENT_REGISTER; @@ -44,7 +47,10 @@ void lp5562_set_channel_current(FuriHalI2cBusHandle* handle, LP5562Channel chann furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, reg_no, value, LP5562_I2C_TIMEOUT); } -void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value) { +void lp5562_set_channel_value( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + uint8_t value) { uint8_t reg_no; if(channel == LP5562ChannelRed) { reg_no = LP5562_CHANNEL_RED_VALUE_REGISTER; @@ -60,7 +66,7 @@ void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, reg_no, value, LP5562_I2C_TIMEOUT); } -uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel) { +uint8_t lp5562_get_channel_value(const FuriHalI2cBusHandle* handle, LP5562Channel channel) { uint8_t reg_no; uint8_t value; if(channel == LP5562ChannelRed) { @@ -78,7 +84,10 @@ uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel chan return value; } -void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src) { +void lp5562_set_channel_src( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + LP5562Engine src) { uint8_t reg_val = 0; uint8_t bit_offset = 0; @@ -107,7 +116,7 @@ void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, } void lp5562_execute_program( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint16_t* program) { @@ -155,7 +164,7 @@ void lp5562_execute_program( furi_hal_i2c_write_reg_8(handle, LP5562_ADDRESS, 0x00, enable_reg, LP5562_I2C_TIMEOUT); } -void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng) { +void lp5562_stop_program(const FuriHalI2cBusHandle* handle, LP5562Engine eng) { if((eng < LP5562Engine1) || (eng > LP5562Engine3)) return; uint8_t reg_val = 0; uint8_t bit_offset = 0; @@ -169,7 +178,7 @@ void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng) { } void lp5562_execute_ramp( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint8_t val_start, @@ -213,7 +222,7 @@ void lp5562_execute_ramp( } void lp5562_execute_blink( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint16_t on_time, diff --git a/lib/drivers/lp5562.h b/lib/drivers/lp5562.h index f5ebeeae2..2e54e1ce3 100644 --- a/lib/drivers/lp5562.h +++ b/lib/drivers/lp5562.h @@ -20,39 +20,48 @@ typedef enum { } LP5562Engine; /** Initialize Driver */ -void lp5562_reset(FuriHalI2cBusHandle* handle); +void lp5562_reset(const FuriHalI2cBusHandle* handle); /** Configure Driver */ -void lp5562_configure(FuriHalI2cBusHandle* handle); +void lp5562_configure(const FuriHalI2cBusHandle* handle); /** Enable Driver */ -void lp5562_enable(FuriHalI2cBusHandle* handle); +void lp5562_enable(const FuriHalI2cBusHandle* handle); /** Set channel current */ -void lp5562_set_channel_current(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value); +void lp5562_set_channel_current( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + uint8_t value); /** Set channel PWM value */ -void lp5562_set_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel, uint8_t value); +void lp5562_set_channel_value( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + uint8_t value); /** Get channel PWM value */ -uint8_t lp5562_get_channel_value(FuriHalI2cBusHandle* handle, LP5562Channel channel); +uint8_t lp5562_get_channel_value(const FuriHalI2cBusHandle* handle, LP5562Channel channel); /** Set channel source */ -void lp5562_set_channel_src(FuriHalI2cBusHandle* handle, LP5562Channel channel, LP5562Engine src); +void lp5562_set_channel_src( + const FuriHalI2cBusHandle* handle, + LP5562Channel channel, + LP5562Engine src); /** Execute program sequence */ void lp5562_execute_program( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint16_t* program); /** Stop program sequence */ -void lp5562_stop_program(FuriHalI2cBusHandle* handle, LP5562Engine eng); +void lp5562_stop_program(const FuriHalI2cBusHandle* handle, LP5562Engine eng); /** Execute ramp program sequence */ void lp5562_execute_ramp( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint8_t val_start, @@ -61,7 +70,7 @@ void lp5562_execute_ramp( /** Start blink program sequence */ void lp5562_execute_blink( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, LP5562Engine eng, LP5562Channel ch, uint16_t on_time, diff --git a/lib/drivers/st25r3916.c b/lib/drivers/st25r3916.c index f8dc9a5eb..0721a52c7 100644 --- a/lib/drivers/st25r3916.c +++ b/lib/drivers/st25r3916.c @@ -2,7 +2,7 @@ #include -void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask) { +void st25r3916_mask_irq(const FuriHalSpiBusHandle* handle, uint32_t mask) { furi_assert(handle); uint8_t irq_mask_regs[4] = { @@ -14,7 +14,7 @@ void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask) { st25r3916_write_burst_regs(handle, ST25R3916_REG_IRQ_MASK_MAIN, irq_mask_regs, 4); } -uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle) { +uint32_t st25r3916_get_irq(const FuriHalSpiBusHandle* handle) { furi_assert(handle); uint8_t irq_regs[4] = {}; @@ -32,7 +32,7 @@ uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle) { return irq; } -void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits) { +void st25r3916_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits) { furi_assert(handle); furi_assert(buff); @@ -45,7 +45,7 @@ void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size } bool st25r3916_read_fifo( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t buff_size, size_t* buff_bits) { diff --git a/lib/drivers/st25r3916.h b/lib/drivers/st25r3916.h index 0e77b6317..3eddaa430 100644 --- a/lib/drivers/st25r3916.h +++ b/lib/drivers/st25r3916.h @@ -75,7 +75,7 @@ extern "C" { * @param handle - pointer to FuriHalSpiBusHandle instance * @param mask - mask of interrupts to be disabled */ -void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask); +void st25r3916_mask_irq(const FuriHalSpiBusHandle* handle, uint32_t mask); /** Get st25r3916 interrupts * @@ -83,7 +83,7 @@ void st25r3916_mask_irq(FuriHalSpiBusHandle* handle, uint32_t mask); * * @return received interrupts */ -uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle); +uint32_t st25r3916_get_irq(const FuriHalSpiBusHandle* handle); /** Write FIFO * @@ -91,7 +91,7 @@ uint32_t st25r3916_get_irq(FuriHalSpiBusHandle* handle); * @param buff - buffer to write to FIFO * @param bits - number of bits to write */ -void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits); +void st25r3916_write_fifo(const FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t bits); /** Read FIFO * @@ -103,7 +103,7 @@ void st25r3916_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size * @return true if read success, false otherwise */ bool st25r3916_read_fifo( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t buff_size, size_t* buff_bits); diff --git a/lib/drivers/st25r3916_reg.c b/lib/drivers/st25r3916_reg.c index f7a47d463..cdbf0fd3d 100644 --- a/lib/drivers/st25r3916_reg.c +++ b/lib/drivers/st25r3916_reg.c @@ -28,18 +28,18 @@ (ST25R3916_CMD_LEN + \ ST25R3916_FIFO_DEPTH) /*!< ST25R3916 communication buffer: CMD + FIFO length */ -static void st25r3916_reg_tx_byte(FuriHalSpiBusHandle* handle, uint8_t byte) { +static void st25r3916_reg_tx_byte(const FuriHalSpiBusHandle* handle, uint8_t byte) { uint8_t val = byte; furi_hal_spi_bus_tx(handle, &val, 1, 5); } -void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { +void st25r3916_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { furi_check(handle); st25r3916_read_burst_regs(handle, reg, val, 1); } void st25r3916_read_burst_regs( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg_start, uint8_t* values, uint8_t length) { @@ -59,14 +59,14 @@ void st25r3916_read_burst_regs( furi_hal_gpio_write(handle->cs, true); } -void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { +void st25r3916_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { furi_check(handle); uint8_t reg_val = val; st25r3916_write_burst_regs(handle, reg, ®_val, 1); } void st25r3916_write_burst_regs( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg_start, const uint8_t* values, uint8_t length) { @@ -86,7 +86,10 @@ void st25r3916_write_burst_regs( furi_hal_gpio_write(handle->cs, true); } -void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t length) { +void st25r3916_reg_write_fifo( + const FuriHalSpiBusHandle* handle, + const uint8_t* buff, + size_t length) { furi_check(handle); furi_check(buff); furi_check(length); @@ -98,7 +101,7 @@ void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, furi_hal_gpio_write(handle->cs, true); } -void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { +void st25r3916_reg_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { furi_check(handle); furi_check(buff); furi_check(length); @@ -110,7 +113,10 @@ void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t furi_hal_gpio_write(handle->cs, true); } -void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length) { +void st25r3916_write_pta_mem( + const FuriHalSpiBusHandle* handle, + const uint8_t* values, + size_t length) { furi_check(handle); furi_check(values); furi_check(length); @@ -122,7 +128,7 @@ void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, furi_hal_gpio_write(handle->cs, true); } -void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { +void st25r3916_read_pta_mem(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { furi_check(handle); furi_check(buff); furi_check(length); @@ -136,7 +142,10 @@ void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t l memcpy(buff, tmp_buff + 1, length); } -void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length) { +void st25r3916_write_ptf_mem( + const FuriHalSpiBusHandle* handle, + const uint8_t* values, + size_t length) { furi_check(handle); furi_check(values); @@ -146,7 +155,7 @@ void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, furi_hal_gpio_write(handle->cs, true); } -void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { +void st25r3916_write_pttsn_mem(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length) { furi_check(handle); furi_check(buff); @@ -156,7 +165,7 @@ void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* buff, size_ furi_hal_gpio_write(handle->cs, true); } -void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd) { +void st25r3916_direct_cmd(const FuriHalSpiBusHandle* handle, uint8_t cmd) { furi_check(handle); furi_hal_gpio_write(handle->cs, false); @@ -164,7 +173,7 @@ void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd) { furi_hal_gpio_write(handle->cs, true); } -void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { +void st25r3916_read_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val) { furi_check(handle); furi_hal_gpio_write(handle->cs, false); @@ -174,7 +183,7 @@ void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* furi_hal_gpio_write(handle->cs, true); } -void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { +void st25r3916_write_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val) { furi_check(handle); furi_hal_gpio_write(handle->cs, false); @@ -184,7 +193,7 @@ void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t furi_hal_gpio_write(handle->cs, true); } -void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask) { +void st25r3916_clear_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask) { furi_check(handle); uint8_t reg_val = 0; @@ -195,7 +204,7 @@ void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t } } -void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask) { +void st25r3916_set_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask) { furi_check(handle); uint8_t reg_val = 0; @@ -207,7 +216,7 @@ void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t se } void st25r3916_change_reg_bits( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t value) { @@ -217,7 +226,7 @@ void st25r3916_change_reg_bits( } void st25r3916_modify_reg( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask, uint8_t set_mask) { @@ -233,7 +242,7 @@ void st25r3916_modify_reg( } void st25r3916_change_test_reg_bits( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t value) { @@ -248,7 +257,7 @@ void st25r3916_change_test_reg_bits( } } -bool st25r3916_check_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val) { +bool st25r3916_check_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val) { furi_check(handle); uint8_t reg_val = 0; diff --git a/lib/drivers/st25r3916_reg.h b/lib/drivers/st25r3916_reg.h index 5163c4423..524f93cc7 100644 --- a/lib/drivers/st25r3916_reg.h +++ b/lib/drivers/st25r3916_reg.h @@ -967,7 +967,7 @@ extern "C" { * @param reg - register address * @param val - pointer to the variable to store the read value */ -void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); +void st25r3916_read_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); /** Read multiple registers * @@ -977,7 +977,7 @@ void st25r3916_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); * @param length - number of registers to read */ void st25r3916_read_burst_regs( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg_start, uint8_t* values, uint8_t length); @@ -988,7 +988,7 @@ void st25r3916_read_burst_regs( * @param reg - register address * @param val - value to write */ -void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); +void st25r3916_write_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); /** Write multiple registers * @@ -998,7 +998,7 @@ void st25r3916_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); * @param length - number of registers to write */ void st25r3916_write_burst_regs( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg_start, const uint8_t* values, uint8_t length); @@ -1009,7 +1009,10 @@ void st25r3916_write_burst_regs( * @param buff - buffer to write to FIFO * @param length - number of bytes to write */ -void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, size_t length); +void st25r3916_reg_write_fifo( + const FuriHalSpiBusHandle* handle, + const uint8_t* buff, + size_t length); /** Read fifo register * @@ -1017,7 +1020,7 @@ void st25r3916_reg_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* buff, * @param buff - buffer to store the read values * @param length - number of bytes to read */ -void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length); +void st25r3916_reg_read_fifo(const FuriHalSpiBusHandle* handle, uint8_t* buff, size_t length); /** Write PTA memory register * @@ -1025,7 +1028,10 @@ void st25r3916_reg_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* buff, size_t * @param values - pointer to buffer to write * @param length - number of bytes to write */ -void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length); +void st25r3916_write_pta_mem( + const FuriHalSpiBusHandle* handle, + const uint8_t* values, + size_t length); /** Read PTA memory register * @@ -1033,7 +1039,7 @@ void st25r3916_write_pta_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, * @param values - buffer to store the read values * @param length - number of bytes to read */ -void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); +void st25r3916_read_pta_mem(const FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); /** Write PTF memory register * @@ -1041,7 +1047,10 @@ void st25r3916_read_pta_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t * @param values - pointer to buffer to write * @param length - number of bytes to write */ -void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, size_t length); +void st25r3916_write_ptf_mem( + const FuriHalSpiBusHandle* handle, + const uint8_t* values, + size_t length); /** Read PTTSN memory register * @@ -1049,21 +1058,21 @@ void st25r3916_write_ptf_mem(FuriHalSpiBusHandle* handle, const uint8_t* values, * @param values - pointer to buffer to write * @param length - number of bytes to write */ -void st25r3916_write_pttsn_mem(FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); +void st25r3916_write_pttsn_mem(const FuriHalSpiBusHandle* handle, uint8_t* values, size_t length); /** Send Direct command * * @param handle - pointer to FuriHalSpiBusHandle instance * @param cmd - direct command */ -void st25r3916_direct_cmd(FuriHalSpiBusHandle* handle, uint8_t cmd); +void st25r3916_direct_cmd(const FuriHalSpiBusHandle* handle, uint8_t cmd); /** Read test register * @param handle - pointer to FuriHalSpiBusHandle instance * @param reg - register address * @param val - pointer to the variable to store the read value */ -void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); +void st25r3916_read_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* val); /** Write test register * @@ -1071,7 +1080,7 @@ void st25r3916_read_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* * @param reg - register address * @param val - value to write */ -void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); +void st25r3916_write_test_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t val); /** Clear register bits * @@ -1079,7 +1088,7 @@ void st25r3916_write_test_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t * @param reg - register address * @param clr_mask - bit mask to clear */ -void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask); +void st25r3916_clear_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask); /** Set register bits * @@ -1087,7 +1096,7 @@ void st25r3916_clear_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t * @param reg - register address * @param set_mask - bit mask to set */ -void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask); +void st25r3916_set_reg_bits(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t set_mask); /** Change register bits * @@ -1097,7 +1106,7 @@ void st25r3916_set_reg_bits(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t se * @param value - new register value to write */ void st25r3916_change_reg_bits( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t value); @@ -1110,7 +1119,7 @@ void st25r3916_change_reg_bits( * @param set_mask - bit mask to set */ void st25r3916_modify_reg( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t clr_mask, uint8_t set_mask); @@ -1123,7 +1132,7 @@ void st25r3916_modify_reg( * @param value - new register value to write */ void st25r3916_change_test_reg_bits( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t value); @@ -1137,7 +1146,7 @@ void st25r3916_change_test_reg_bits( * * @return true if register value matches the expected value, false otherwise */ -bool st25r3916_check_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val); +bool st25r3916_check_reg(const FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t mask, uint8_t val); #ifdef __cplusplus } diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index d0c4f52fb..bd8ecdf7e 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -8,11 +8,11 @@ #define TAG "Elf" -#define ELF_NAME_BUFFER_LEN 32 -#define SECTION_OFFSET(e, n) ((e)->section_table + (n) * sizeof(Elf32_Shdr)) -#define IS_FLAGS_SET(v, m) (((v) & (m)) == (m)) +#define ELF_NAME_BUFFER_LEN 32 +#define SECTION_OFFSET(e, n) ((e)->section_table + (n) * sizeof(Elf32_Shdr)) +#define IS_FLAGS_SET(v, m) (((v) & (m)) == (m)) #define RESOLVER_THREAD_YIELD_STEP 30 -#define FAST_RELOCATION_VERSION 1 +#define FAST_RELOCATION_VERSION 1 // #define ELF_DEBUG_LOG 1 @@ -830,9 +830,7 @@ void elf_file_free(ELFFile* elf) { for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) { const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it); - if(itref->value.data) { - aligned_free(itref->value.data); - } + aligned_free(itref->value.data); if(itref->value.fast_rel) { aligned_free(itref->value.fast_rel->data); free(itref->value.fast_rel); diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index 8992247d1..8aebf853f 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -8,6 +8,11 @@ #include "flipper_format_stream.h" #include "flipper_format_stream_i.h" +// permits direct casting between `FlipperFormatOffset` and `StreamOffset` +static_assert((size_t)FlipperFormatOffsetFromCurrent == (size_t)StreamOffsetFromCurrent); +static_assert((size_t)FlipperFormatOffsetFromStart == (size_t)StreamOffsetFromStart); +static_assert((size_t)FlipperFormatOffsetFromEnd == (size_t)StreamOffsetFromEnd); + /********************************** Private **********************************/ struct FlipperFormat { Stream* stream; @@ -127,6 +132,17 @@ bool flipper_format_rewind(FlipperFormat* flipper_format) { return stream_rewind(flipper_format->stream); } +size_t flipper_format_tell(FlipperFormat* flipper_format) { + furi_check(flipper_format); + return stream_tell(flipper_format->stream); +} + +bool flipper_format_seek(FlipperFormat* flipper_format, int32_t offset, FlipperFormatOffset anchor) { + furi_check(flipper_format); + // direct usage of `anchor` made valid by `static_assert`s at the top of this file + return stream_seek(flipper_format->stream, offset, (StreamOffset)anchor); +} + bool flipper_format_seek_to_end(FlipperFormat* flipper_format) { furi_check(flipper_format); return stream_seek(flipper_format->stream, 0, StreamOffsetFromEnd); @@ -403,6 +419,11 @@ bool flipper_format_write_comment_cstr(FlipperFormat* flipper_format, const char return flipper_format_stream_write_comment_cstr(flipper_format->stream, data); } +bool flipper_format_write_empty_line(FlipperFormat* flipper_format) { + furi_check(flipper_format); + return flipper_format_stream_write_eol(flipper_format->stream); +} + bool flipper_format_delete_key(FlipperFormat* flipper_format, const char* key) { furi_check(flipper_format); FlipperStreamWriteData write_data = { diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index 46f78e255..5b13496e1 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -94,6 +94,12 @@ extern "C" { typedef struct FlipperFormat FlipperFormat; +typedef enum { + FlipperFormatOffsetFromCurrent, + FlipperFormatOffsetFromStart, + FlipperFormatOffsetFromEnd, +} FlipperFormatOffset; + /** Allocate FlipperFormat as string. * * @return FlipperFormat* pointer to a FlipperFormat instance @@ -216,6 +222,24 @@ void flipper_format_set_strict_mode(FlipperFormat* flipper_format, bool strict_m */ bool flipper_format_rewind(FlipperFormat* flipper_format); +/** Get the RW pointer position + * + * @param flipper_format Pointer to a FlipperFormat instance + * + * @return RW pointer position + */ +size_t flipper_format_tell(FlipperFormat* flipper_format); + +/** Set the RW pointer position to an arbitrary value + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param offset Offset relative to the anchor point + * @param anchor Anchor point (e.g. start of file) + * + * @return True on success + */ +bool flipper_format_seek(FlipperFormat* flipper_format, int32_t offset, FlipperFormatOffset anchor); + /** Move the RW pointer at the end. Can be useful if you want to add some data * after reading. * @@ -518,6 +542,14 @@ bool flipper_format_write_comment(FlipperFormat* flipper_format, FuriString* dat */ bool flipper_format_write_comment_cstr(FlipperFormat* flipper_format, const char* data); +/** Write empty line (Improves readability for human based parsing) + * + * @param flipper_format Pointer to a FlipperFormat instance + * + * @return True on success + */ +bool flipper_format_write_empty_line(FlipperFormat* flipper_format); + /** Removes the first matching key and its value. Sets the RW pointer to a * position of deleted data. * diff --git a/lib/ibutton/ibutton_worker_modes.c b/lib/ibutton/ibutton_worker_modes.c index 5900b10a2..ff76e784d 100644 --- a/lib/ibutton/ibutton_worker_modes.c +++ b/lib/ibutton/ibutton_worker_modes.c @@ -1,9 +1,10 @@ #include "ibutton_worker_i.h" #include +#include #include -#include +#include #include "ibutton_protocols.h" @@ -75,7 +76,9 @@ void ibutton_worker_mode_idle_stop(iButtonWorker* worker) { void ibutton_worker_mode_read_start(iButtonWorker* worker) { UNUSED(worker); - furi_hal_power_enable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } void ibutton_worker_mode_read_tick(iButtonWorker* worker) { @@ -90,7 +93,9 @@ void ibutton_worker_mode_read_tick(iButtonWorker* worker) { void ibutton_worker_mode_read_stop(iButtonWorker* worker) { UNUSED(worker); - furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } /*********************** EMULATE ***********************/ @@ -120,7 +125,9 @@ void ibutton_worker_mode_emulate_stop(iButtonWorker* worker) { void ibutton_worker_mode_write_common_start(iButtonWorker* worker) { //-V524 UNUSED(worker); - furi_hal_power_enable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, true); + furi_record_close(RECORD_POWER); } void ibutton_worker_mode_write_id_tick(iButtonWorker* worker) { @@ -149,5 +156,7 @@ void ibutton_worker_mode_write_copy_tick(iButtonWorker* worker) { void ibutton_worker_mode_write_common_stop(iButtonWorker* worker) { //-V524 UNUSED(worker); - furi_hal_power_disable_otg(); + Power* power = furi_record_open(RECORD_POWER); + power_enable_otg(power, false); + furi_record_close(RECORD_POWER); } diff --git a/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c index b4dd51ce7..df758283b 100644 --- a/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c +++ b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.c @@ -6,7 +6,7 @@ #include "protocol_ds1971.h" #include "protocol_ds_generic.h" -const iButtonProtocolDallasBase* ibutton_protocols_dallas[] = { +const iButtonProtocolDallasBase* const ibutton_protocols_dallas[] = { [iButtonProtocolDS1990] = &ibutton_protocol_ds1990, [iButtonProtocolDS1992] = &ibutton_protocol_ds1992, [iButtonProtocolDS1996] = &ibutton_protocol_ds1996, diff --git a/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h index 2ba1dd39a..71571d91b 100644 --- a/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h +++ b/lib/ibutton/protocols/dallas/protocol_group_dallas_defs.h @@ -14,4 +14,4 @@ typedef enum { iButtonProtocolDSMax, } iButtonProtocolDallas; -extern const iButtonProtocolDallasBase* ibutton_protocols_dallas[]; +extern const iButtonProtocolDallasBase* const ibutton_protocols_dallas[]; diff --git a/lib/ibutton/protocols/misc/protocol_group_misc_defs.c b/lib/ibutton/protocols/misc/protocol_group_misc_defs.c index 09ae0bdc7..f8cae0463 100644 --- a/lib/ibutton/protocols/misc/protocol_group_misc_defs.c +++ b/lib/ibutton/protocols/misc/protocol_group_misc_defs.c @@ -3,7 +3,7 @@ #include "protocol_cyfral.h" #include "protocol_metakom.h" -const ProtocolBase* ibutton_protocols_misc[] = { +const ProtocolBase* const ibutton_protocols_misc[] = { [iButtonProtocolMiscCyfral] = &ibutton_protocol_misc_cyfral, [iButtonProtocolMiscMetakom] = &ibutton_protocol_misc_metakom, /* Add new misc protocols here */ diff --git a/lib/ibutton/protocols/misc/protocol_group_misc_defs.h b/lib/ibutton/protocols/misc/protocol_group_misc_defs.h index 0a7f92847..cde6b0aa9 100644 --- a/lib/ibutton/protocols/misc/protocol_group_misc_defs.h +++ b/lib/ibutton/protocols/misc/protocol_group_misc_defs.h @@ -8,4 +8,4 @@ typedef enum { iButtonProtocolMiscMax, } iButtonProtocolMisc; -extern const ProtocolBase* ibutton_protocols_misc[]; +extern const ProtocolBase* const ibutton_protocols_misc[]; diff --git a/lib/ibutton/protocols/protocol_group_defs.c b/lib/ibutton/protocols/protocol_group_defs.c index 40a360f0e..ac428f410 100644 --- a/lib/ibutton/protocols/protocol_group_defs.c +++ b/lib/ibutton/protocols/protocol_group_defs.c @@ -3,7 +3,7 @@ #include "dallas/protocol_group_dallas.h" #include "misc/protocol_group_misc.h" -const iButtonProtocolGroupBase* ibutton_protocol_groups[] = { +const iButtonProtocolGroupBase* const ibutton_protocol_groups[] = { [iButtonProtocolGroupDallas] = &ibutton_protocol_group_dallas, [iButtonProtocolGroupMisc] = &ibutton_protocol_group_misc, }; diff --git a/lib/ibutton/protocols/protocol_group_defs.h b/lib/ibutton/protocols/protocol_group_defs.h index 2d41e3cb8..2c00dfab4 100644 --- a/lib/ibutton/protocols/protocol_group_defs.h +++ b/lib/ibutton/protocols/protocol_group_defs.h @@ -8,4 +8,4 @@ typedef enum { iButtonProtocolGroupMax } iButtonProtocolGroup; -extern const iButtonProtocolGroupBase* ibutton_protocol_groups[]; +extern const iButtonProtocolGroupBase* const ibutton_protocol_groups[]; diff --git a/lib/ieee754_parse_wrap/SConscript b/lib/ieee754_parse_wrap/SConscript new file mode 100644 index 000000000..dc60036e0 --- /dev/null +++ b/lib/ieee754_parse_wrap/SConscript @@ -0,0 +1,31 @@ +Import("env") + +wrapped_fn_list = [ + "strtof", + "strtod", +] + +for wrapped_fn in wrapped_fn_list: + env.Append( + LINKFLAGS=[ + "-Wl,--wrap," + wrapped_fn, + ] + ) + +env.Append( + SDK_HEADERS=[ + File("wrappers.h"), + ], + LINT_SOURCES=[ + Dir("."), + ], +) + +libenv = env.Clone(FW_LIB_NAME="ieee754_parse_wrap") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*", ".") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/ieee754_parse_wrap/wrappers.c b/lib/ieee754_parse_wrap/wrappers.c new file mode 100644 index 000000000..33bd38c35 --- /dev/null +++ b/lib/ieee754_parse_wrap/wrappers.c @@ -0,0 +1,14 @@ +#include "wrappers.h" + +// Based on the disassembly, providing NULL as `locale` is fine. +// The default `strtof` and `strtod` provided in the same libc_nano also just +// call these functions, but with an actual locale structure which was taking up +// lots of .data space (364 bytes). + +float __wrap_strtof(const char* in, char** tail) { + return strtof_l(in, tail, NULL); +} + +double __wrap_strtod(const char* in, char** tail) { + return strtod_l(in, tail, NULL); +} diff --git a/lib/ieee754_parse_wrap/wrappers.h b/lib/ieee754_parse_wrap/wrappers.h new file mode 100644 index 000000000..17e7acf83 --- /dev/null +++ b/lib/ieee754_parse_wrap/wrappers.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +float __wrap_strtof(const char* in, char** tail); +double __wrap_strtod(const char* in, char** tail); + +#ifdef __cplusplus +} +#endif diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index aec19e374..d3bcda042 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -499,9 +499,6 @@ static void lfrfid_worker_mode_emulate_process(LFRFIDWorker* worker) { static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { LFRFIDProtocol protocol = worker->protocol; LFRFIDWriteRequest* request = malloc(sizeof(LFRFIDWriteRequest)); - request->write_type = LFRFIDWriteTypeT5577; - - bool can_be_written = protocol_dict_get_write_data(worker->protocols, protocol, request); uint32_t write_start_time = furi_get_tick(); bool too_long = false; @@ -510,63 +507,88 @@ static void lfrfid_worker_mode_write_process(LFRFIDWorker* worker) { size_t data_size = protocol_dict_get_data_size(worker->protocols, protocol); uint8_t* verify_data = malloc(data_size); uint8_t* read_data = malloc(data_size); + protocol_dict_get_data(worker->protocols, protocol, verify_data, data_size); - if(can_be_written) { - while(!lfrfid_worker_check_for_stop(worker)) { - FURI_LOG_D(TAG, "Data write"); - t5577_write(&request->t5577); + while(!lfrfid_worker_check_for_stop(worker)) { + FURI_LOG_D(TAG, "Data write"); + uint16_t skips = 0; + for(size_t i = 0; i < LFRFIDWriteTypeMax; i++) { + memset(request, 0, sizeof(LFRFIDWriteRequest)); + LFRFIDWriteType write_type = i; + request->write_type = write_type; - ProtocolId read_result = PROTOCOL_NO; - LFRFIDWorkerReadState state = lfrfid_worker_read_internal( - worker, - protocol_dict_get_features(worker->protocols, protocol), - LFRFID_WORKER_WRITE_VERIFY_TIME_MS, - &read_result); + protocol_dict_set_data(worker->protocols, protocol, verify_data, data_size); - if(state == LFRFIDWorkerReadOK) { - bool read_success = false; + bool can_be_written = + protocol_dict_get_write_data(worker->protocols, protocol, request); - if(read_result == protocol) { - protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); - - if(memcmp(read_data, verify_data, data_size) == 0) { - read_success = true; - } - } - - if(read_success) { + if(!can_be_written) { + skips++; + if(skips == LFRFIDWriteTypeMax) { if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); + worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx); } break; - } else { - unsuccessful_reads++; + } + continue; + } - if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) { - if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx); - } + memset(read_data, 0, data_size); + + if(request->write_type == LFRFIDWriteTypeT5577) { + t5577_write(&request->t5577); + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + em4305_write(&request->em4305); + } else { + furi_crash("Unknown write type"); + } + } + ProtocolId read_result = PROTOCOL_NO; + LFRFIDWorkerReadState state = lfrfid_worker_read_internal( + worker, + protocol_dict_get_features(worker->protocols, protocol), + LFRFID_WORKER_WRITE_VERIFY_TIME_MS, + &read_result); + + if(state == LFRFIDWorkerReadOK) { + bool read_success = false; + + if(read_result == protocol) { + protocol_dict_get_data(worker->protocols, protocol, read_data, data_size); + + if(memcmp(read_data, verify_data, data_size) == 0) { + read_success = true; + } + } + + if(read_success) { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteOK, worker->cb_ctx); + } + break; + } else { + unsuccessful_reads++; + + if(unsuccessful_reads == LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS) { + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteFobCannotBeWritten, worker->cb_ctx); } } - } else if(state == LFRFIDWorkerReadExit) { - break; } + } else if(state == LFRFIDWorkerReadExit) { + break; + } - if(!too_long && - (furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) { - too_long = true; - if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx); - } + if(!too_long && + (furi_get_tick() - write_start_time) > LFRFID_WORKER_WRITE_TOO_LONG_TIME_MS) { + too_long = true; + if(worker->write_cb) { + worker->write_cb(LFRFIDWorkerWriteTooLongToWrite, worker->cb_ctx); } + } - lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS); - } - } else { - if(worker->write_cb) { - worker->write_cb(LFRFIDWorkerWriteProtocolCannotBeWritten, worker->cb_ctx); - } + lfrfid_worker_delay(worker, LFRFID_WORKER_WRITE_DROP_TIME_MS); } free(request); diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 8ea1f2b49..c6cc57a68 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -20,8 +20,9 @@ #include "protocol_nexwatch.h" #include "protocol_securakey.h" #include "protocol_gproxii.h" +#include "protocol_noralsy.h" -const ProtocolBase* lfrfid_protocols[] = { +const ProtocolBase* const lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, [LFRFIDProtocolEM410032] = &protocol_em4100_32, [LFRFIDProtocolEM410016] = &protocol_em4100_16, @@ -45,4 +46,5 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolNexwatch] = &protocol_nexwatch, [LFRFIDProtocolSecurakey] = &protocol_securakey, [LFRFIDProtocolGProxII] = &protocol_gproxii, + [LFRFIDProtocolNoralsy] = &protocol_noralsy, }; diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index e9c61616e..a33e87990 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -1,6 +1,7 @@ #pragma once #include #include "../tools/t5577.h" +#include "../tools/em4305.h" typedef enum { LFRFIDFeatureASK = 1 << 0, /** ASK Demodulation */ @@ -31,18 +32,24 @@ typedef enum { LFRFIDProtocolNexwatch, LFRFIDProtocolSecurakey, LFRFIDProtocolGProxII, + LFRFIDProtocolNoralsy, + LFRFIDProtocolMax, } LFRFIDProtocol; -extern const ProtocolBase* lfrfid_protocols[]; +extern const ProtocolBase* const lfrfid_protocols[]; typedef enum { LFRFIDWriteTypeT5577, + LFRFIDWriteTypeEM4305, + + LFRFIDWriteTypeMax, } LFRFIDWriteType; typedef struct { LFRFIDWriteType write_type; union { LFRFIDT5577 t5577; + LFRFIDEM4305 em4305; }; } LFRFIDWriteRequest; diff --git a/lib/lfrfid/protocols/protocol_electra.c b/lib/lfrfid/protocols/protocol_electra.c index 014c83d1f..b4e17763f 100644 --- a/lib/lfrfid/protocols/protocol_electra.c +++ b/lib/lfrfid/protocols/protocol_electra.c @@ -407,6 +407,24 @@ bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) { request->t5577.blocks_to_write = 5; result = true; } + if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(64) | (8 << EM4x05_MAXBLOCK_SHIFT)); + uint64_t encoded_data_reversed = 0; + uint64_t encoded_epilogue_reversed = 0; + for(uint8_t i = 0; i < 64; i++) { + encoded_data_reversed = (encoded_data_reversed << 1) | + ((protocol->encoded_base_data >> i) & 1); + encoded_epilogue_reversed = (encoded_epilogue_reversed << 1) | + ((protocol->encoded_epilogue >> i) & 1); + } + request->em4305.word[5] = encoded_data_reversed & 0xFFFFFFFF; + request->em4305.word[6] = encoded_data_reversed >> 32; + request->em4305.word[7] = encoded_epilogue_reversed & 0xFFFFFFFF; + request->em4305.word[8] = encoded_epilogue_reversed >> 32; + request->em4305.mask = 0x01F0; + result = true; + } return result; } diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index 8851a5369..83340895c 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -69,6 +69,19 @@ uint32_t protocol_em4100_get_t5577_bitrate(ProtocolEM4100* proto) { } } +uint32_t protocol_em4100_get_em4305_bitrate(ProtocolEM4100* proto) { + switch(proto->clock_per_bit) { + case 64: + return EM4x05_SET_BITRATE(64); + case 32: + return EM4x05_SET_BITRATE(32); + case 16: + return EM4x05_SET_BITRATE(16); + default: + return EM4x05_SET_BITRATE(64); + } +} + uint16_t protocol_em4100_get_short_time_low(ProtocolEM4100* proto) { return EM_READ_SHORT_TIME_BASE / protocol_em4100_get_time_divisor(proto) - EM_READ_JITTER_TIME_BASE / protocol_em4100_get_time_divisor(proto); @@ -339,6 +352,19 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { request->t5577.block[2] = protocol->encoded_data; request->t5577.blocks_to_write = 3; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_MANCHESTER | protocol_em4100_get_em4305_bitrate(protocol) | + (6 << EM4x05_MAXBLOCK_SHIFT)); + uint64_t encoded_data_reversed = 0; + for(uint8_t i = 0; i < 64; i++) { + encoded_data_reversed = (encoded_data_reversed << 1) | + ((protocol->encoded_data >> i) & 1); + } + request->em4305.word[5] = encoded_data_reversed; + request->em4305.word[6] = encoded_data_reversed >> 32; + request->em4305.mask = 0x70; + result = true; } return result; } diff --git a/lib/lfrfid/protocols/protocol_gallagher.c b/lib/lfrfid/protocols/protocol_gallagher.c index 9ae0cf80a..bbed99706 100644 --- a/lib/lfrfid/protocols/protocol_gallagher.c +++ b/lib/lfrfid/protocols/protocol_gallagher.c @@ -264,6 +264,20 @@ bool protocol_gallagher_write_data(ProtocolGallagher* protocol, void* data) { request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); request->t5577.blocks_to_write = 4; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(32) | (7 << EM4x05_MAXBLOCK_SHIFT)); + uint32_t encoded_data_reversed[3] = {0}; + for(uint8_t i = 0; i < (32 * 3); i++) { + encoded_data_reversed[i / 32] = + (encoded_data_reversed[i / 32] << 1) | + (bit_lib_get_bit(protocol->encoded_data, ((32 * 3) - i)) & 1); + } + request->em4305.word[5] = encoded_data_reversed[2]; + request->em4305.word[6] = encoded_data_reversed[1]; + request->em4305.word[7] = encoded_data_reversed[0]; + request->em4305.mask = 0xF0; + result = true; } return result; } diff --git a/lib/lfrfid/protocols/protocol_nexwatch.c b/lib/lfrfid/protocols/protocol_nexwatch.c index a794a4a8e..08324803f 100644 --- a/lib/lfrfid/protocols/protocol_nexwatch.c +++ b/lib/lfrfid/protocols/protocol_nexwatch.c @@ -21,7 +21,7 @@ typedef struct { uint8_t chk; } ProtocolNexwatchMagic; -ProtocolNexwatchMagic magic_items[] = { +static ProtocolNexwatchMagic magic_items[] = { {0xBE, "Quadrakey", 0}, {0x88, "Nexkey", 0}, {0x86, "Honeywell", 0}}; diff --git a/lib/lfrfid/protocols/protocol_noralsy.c b/lib/lfrfid/protocols/protocol_noralsy.c new file mode 100644 index 000000000..27cf8cb6b --- /dev/null +++ b/lib/lfrfid/protocols/protocol_noralsy.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define NORALSY_CLOCK_PER_BIT (32) + +#define NORALSY_ENCODED_BIT_SIZE (96) +#define NORALSY_ENCODED_BYTE_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8) +#define NORALSY_PREAMBLE_BIT_SIZE (32) +#define NORALSY_PREAMBLE_BYTE_SIZE ((NORALSY_PREAMBLE_BIT_SIZE) / 8) +#define NORALSY_ENCODED_BYTE_FULL_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8) +#define NORALSY_DECODED_DATA_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8) + +#define NORALSY_READ_SHORT_TIME (128) +#define NORALSY_READ_LONG_TIME (256) +#define NORALSY_READ_JITTER_TIME (60) + +#define NORALSY_READ_SHORT_TIME_LOW (NORALSY_READ_SHORT_TIME - NORALSY_READ_JITTER_TIME) +#define NORALSY_READ_SHORT_TIME_HIGH (NORALSY_READ_SHORT_TIME + NORALSY_READ_JITTER_TIME) +#define NORALSY_READ_LONG_TIME_LOW (NORALSY_READ_LONG_TIME - NORALSY_READ_JITTER_TIME) +#define NORALSY_READ_LONG_TIME_HIGH (NORALSY_READ_LONG_TIME + NORALSY_READ_JITTER_TIME) + +#define TAG "NORALSY" + +typedef struct { + uint8_t data[NORALSY_ENCODED_BYTE_SIZE]; + uint8_t encoded_data[NORALSY_ENCODED_BYTE_SIZE]; + + uint8_t encoded_data_index; + bool encoded_polarity; + + ManchesterState decoder_manchester_state; +} ProtocolNoralsy; + +ProtocolNoralsy* protocol_noralsy_alloc(void) { + ProtocolNoralsy* protocol = malloc(sizeof(ProtocolNoralsy)); + return (void*)protocol; +} + +void protocol_noralsy_free(ProtocolNoralsy* protocol) { + free(protocol); +} + +static uint8_t noralsy_chksum(uint8_t* bits, uint8_t len) { + uint8_t sum = 0; + for(uint8_t i = 0; i < len; i += 4) + sum ^= bit_lib_get_bits(bits, i, 4); + return sum & 0x0F; +} + +uint8_t* protocol_noralsy_get_data(ProtocolNoralsy* protocol) { + return protocol->data; +} + +static void protocol_noralsy_decode(ProtocolNoralsy* protocol) { + bit_lib_copy_bits(protocol->data, 0, NORALSY_ENCODED_BIT_SIZE, protocol->encoded_data, 0); +} + +static bool protocol_noralsy_can_be_decoded(ProtocolNoralsy* protocol) { + // check 12 bits preamble + // If necessary, use 0xBB0214FF for 32 bit preamble check + // However, it is not confirmed the 13-16 bit are static. + if(bit_lib_get_bits_16(protocol->encoded_data, 0, 12) != 0b101110110000) return false; + uint8_t calc1 = noralsy_chksum(&protocol->encoded_data[4], 40); + uint8_t calc2 = noralsy_chksum(&protocol->encoded_data[0], 76); + uint8_t chk1 = bit_lib_get_bits(protocol->encoded_data, 72, 4); + uint8_t chk2 = bit_lib_get_bits(protocol->encoded_data, 76, 4); + if(calc1 != chk1 || calc2 != chk2) return false; + + return true; +} + +void protocol_noralsy_decoder_start(ProtocolNoralsy* protocol) { + memset(protocol->encoded_data, 0, NORALSY_ENCODED_BYTE_FULL_SIZE); + manchester_advance( + protocol->decoder_manchester_state, + ManchesterEventReset, + &protocol->decoder_manchester_state, + NULL); +} + +bool protocol_noralsy_decoder_feed(ProtocolNoralsy* protocol, bool level, uint32_t duration) { + bool result = false; + + ManchesterEvent event = ManchesterEventReset; + + if(duration > NORALSY_READ_SHORT_TIME_LOW && duration < NORALSY_READ_SHORT_TIME_HIGH) { + if(!level) { + event = ManchesterEventShortHigh; + } else { + event = ManchesterEventShortLow; + } + } else if(duration > NORALSY_READ_LONG_TIME_LOW && duration < NORALSY_READ_LONG_TIME_HIGH) { + if(!level) { + event = ManchesterEventLongHigh; + } else { + event = ManchesterEventLongLow; + } + } + + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + protocol->decoder_manchester_state, event, &protocol->decoder_manchester_state, &data); + + if(data_ok) { + bit_lib_push_bit(protocol->encoded_data, NORALSY_ENCODED_BYTE_FULL_SIZE, data); + + if(protocol_noralsy_can_be_decoded(protocol)) { + protocol_noralsy_decode(protocol); + result = true; + } + } + } + + return result; +} + +bool protocol_noralsy_encoder_start(ProtocolNoralsy* protocol) { + bit_lib_copy_bits(protocol->encoded_data, 0, NORALSY_ENCODED_BIT_SIZE, protocol->data, 0); + + return true; +} + +LevelDuration protocol_noralsy_encoder_yield(ProtocolNoralsy* protocol) { + bool level = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_data_index); + uint32_t duration = NORALSY_CLOCK_PER_BIT / 2; + + if(protocol->encoded_polarity) { + protocol->encoded_polarity = false; + } else { + level = !level; + + protocol->encoded_polarity = true; + bit_lib_increment_index(protocol->encoded_data_index, NORALSY_ENCODED_BIT_SIZE); + } + + return level_duration_make(level, duration); +} + +bool protocol_noralsy_write_data(ProtocolNoralsy* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + // Correct protocol data by redecoding + protocol_noralsy_encoder_start(protocol); + protocol_noralsy_decode(protocol); + + protocol_noralsy_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = + (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_32 | + (3 << LFRFID_T5577_MAXBLOCK_SHIFT) | LFRFID_T5577_ST_TERMINATOR); + // In fact, base on the current two dump samples from Iceman server, + // Noralsy are usually T5577s with config = 0x00088C6A + // But the `C` and `A` are not explainable by the ATA5577C datasheet + // and they don't affect reading whatsoever. + // So we are mimicing Proxmark's solution here. Leave those nibbles as zero. + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32); + request->t5577.blocks_to_write = 4; + result = true; + } + return result; +} + +static void protocol_noralsy_render_data_internal(ProtocolNoralsy* protocol, FuriString* result) { + UNUSED(protocol); + uint32_t raw2 = bit_lib_get_bits_32(protocol->data, 32, 32); + uint32_t raw3 = bit_lib_get_bits_32(protocol->data, 64, 32); + uint32_t cardid = ((raw2 & 0xFFF00000) >> 20) << 16; + cardid |= (raw2 & 0xFF) << 8; + cardid |= ((raw3 & 0xFF000000) >> 24); + + uint8_t year = (raw2 & 0x000ff000) >> 12; + bool tag_is_gen_z = (year > 0x60); + furi_string_printf( + result, + "Card ID: %07lx\n" + "Year: %s%02x", + cardid, + tag_is_gen_z ? "19" : "20", + year); +} + +void protocol_noralsy_render_data(ProtocolNoralsy* protocol, FuriString* result) { + protocol_noralsy_render_data_internal(protocol, result); +} + +void protocol_noralsy_render_brief_data(ProtocolNoralsy* protocol, FuriString* result) { + protocol_noralsy_render_data_internal(protocol, result); +} + +const ProtocolBase protocol_noralsy = { + .name = "Noralsy", + .manufacturer = "Noralsy", + .data_size = NORALSY_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_noralsy_alloc, + .free = (ProtocolFree)protocol_noralsy_free, + .get_data = (ProtocolGetData)protocol_noralsy_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_noralsy_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_noralsy_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_noralsy_encoder_start, + .yield = (ProtocolEncoderYield)protocol_noralsy_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_noralsy_render_data, + .render_brief_data = (ProtocolRenderData)protocol_noralsy_render_brief_data, + .write_data = (ProtocolWriteData)protocol_noralsy_write_data, +}; diff --git a/lib/lfrfid/protocols/protocol_noralsy.h b/lib/lfrfid/protocols/protocol_noralsy.h new file mode 100644 index 000000000..b1ee51a2e --- /dev/null +++ b/lib/lfrfid/protocols/protocol_noralsy.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_noralsy; diff --git a/lib/lfrfid/protocols/protocol_securakey.c b/lib/lfrfid/protocols/protocol_securakey.c index a4bfeca60..947b68e72 100644 --- a/lib/lfrfid/protocols/protocol_securakey.c +++ b/lib/lfrfid/protocols/protocol_securakey.c @@ -61,17 +61,22 @@ uint8_t* protocol_securakey_get_data(ProtocolSecurakey* protocol) { static bool protocol_securakey_can_be_decoded(ProtocolSecurakey* protocol) { // check 19 bits preamble + format flag if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111000000000) { - protocol->bit_format = 0; - return true; + if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 54, BitLibParityAlways0, 9)) { + protocol->bit_format = 0; + return true; + } } else if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111001011010) { - protocol->bit_format = 26; - return true; + if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 90, BitLibParityAlways0, 9)) { + protocol->bit_format = 26; + return true; + } } else if(bit_lib_get_bits_32(protocol->RKKT_encoded_data, 0, 19) == 0b0111111111001100000) { - protocol->bit_format = 32; - return true; - } else { - return false; + if(bit_lib_test_parity(protocol->RKKT_encoded_data, 2, 90, BitLibParityAlways0, 9)) { + protocol->bit_format = 32; + return true; + } } + return false; } static void protocol_securakey_decode(ProtocolSecurakey* protocol) { diff --git a/lib/lfrfid/protocols/protocol_viking.c b/lib/lfrfid/protocols/protocol_viking.c index 78499a415..7e9009fde 100644 --- a/lib/lfrfid/protocols/protocol_viking.c +++ b/lib/lfrfid/protocols/protocol_viking.c @@ -171,6 +171,19 @@ bool protocol_viking_write_data(ProtocolViking* protocol, void* data) { request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); request->t5577.blocks_to_write = 3; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(32) | (6 << EM4x05_MAXBLOCK_SHIFT)); + uint32_t encoded_data_reversed[2] = {0}; + for(uint8_t i = 0; i < 64; i++) { + encoded_data_reversed[i / 32] = + (encoded_data_reversed[i / 32] << 1) | + (bit_lib_get_bit(protocol->encoded_data, (63 - i)) & 1); + } + request->em4305.word[5] = encoded_data_reversed[1]; + request->em4305.word[6] = encoded_data_reversed[0]; + request->em4305.mask = 0x70; + result = true; } return result; } diff --git a/lib/lfrfid/tools/em4305.c b/lib/lfrfid/tools/em4305.c new file mode 100644 index 000000000..3b072d38d --- /dev/null +++ b/lib/lfrfid/tools/em4305.c @@ -0,0 +1,152 @@ +#include "em4305.h" +#include +#include + +#define TAG "EM4305" + +#define EM4305_TIMING_1 (32) +#define EM4305_TIMING_0_OFF (23) +#define EM4305_TIMING_0_ON (18) + +#define EM4305_FIELD_STOP_OFF_CYCLES (55) +#define EM4305_FIELD_STOP_ON_CYCLES (18) + +#define EM4305_TIMING_POWER_CHECK (1480) +#define EM4305_TIMING_EEPROM_WRITE (9340) + +static bool em4305_line_parity(uint8_t data) { + uint8_t parity = 0; + for(uint8_t i = 0; i < 8; i++) { + parity ^= (data >> i) & 1; + } + return parity; +} + +static uint64_t em4305_prepare_data(uint32_t data) { + uint8_t i, j; + uint64_t data_with_parity = 0; + + // 4 lines of 8 bits of data + // line even parity at bits 8 17 26 35 + // column even parity at bits 36-43 + // bit 44 is always 0 + // final table is 5 lines of 9 bits + + // line parity + for(i = 0; i < 4; i++) { + for(j = 0; j < 8; j++) { + data_with_parity = (data_with_parity << 1) | ((data >> (i * 8 + j)) & 1); + } + data_with_parity = (data_with_parity << 1) | (uint64_t)em4305_line_parity(data >> (i * 8)); + } + + // column parity + for(i = 0; i < 8; i++) { + uint8_t column_parity = 0; + for(j = 0; j < 4; j++) { + column_parity ^= (data >> (j * 8 + i)) & 1; + } + data_with_parity = (data_with_parity << 1) | column_parity; + } + + // bit 44 + data_with_parity = (data_with_parity << 1) | 0; + + return data_with_parity; +} + +static void em4305_start(void) { + furi_hal_rfid_tim_read_start(125000, 0.5); + + // do not ground the antenna + furi_hal_rfid_pin_pull_release(); +} + +static void em4305_stop(void) { + furi_hal_rfid_tim_read_stop(); + furi_hal_rfid_pins_reset(); +} + +static void em4305_write_bit(bool value) { + if(value) { + furi_delay_us(EM4305_TIMING_1 * 8); + } else { + furi_hal_rfid_tim_read_pause(); + furi_delay_us(EM4305_TIMING_0_OFF * 8); + furi_hal_rfid_tim_read_continue(); + furi_delay_us(EM4305_TIMING_0_ON * 8); + } +} + +static void em4305_write_opcode(uint8_t value) { + // 3 bit opcode + for(uint8_t i = 0; i < 3; i++) { + em4305_write_bit((value >> i) & 1); + } + + // parity + bool parity = 0; + for(uint8_t i = 0; i < 3; i++) { + parity ^= (value >> i) & 1; + } + em4305_write_bit(parity); +} + +static void em4305_field_stop() { + furi_hal_rfid_tim_read_pause(); + furi_delay_us(EM4305_FIELD_STOP_OFF_CYCLES * 8); + furi_hal_rfid_tim_read_continue(); + furi_delay_us(EM4305_FIELD_STOP_ON_CYCLES * 8); +} + +static void em4305_write_word(uint8_t address, uint32_t data) { + // parity + uint64_t data_with_parity = em4305_prepare_data(data); + + // power up the tag + furi_delay_us(8000); + + // field stop + em4305_field_stop(); + + // start bit + em4305_write_bit(0); + + // opcode + em4305_write_opcode(EM4x05_OPCODE_WRITE); + + // address + bool address_parity = 0; + for(uint8_t i = 0; i < 4; i++) { + em4305_write_bit((address >> (i)) & 1); + address_parity ^= (address >> (i)) & 1; + } + em4305_write_bit(0); + em4305_write_bit(0); + em4305_write_bit(address_parity); + + // data + for(uint8_t i = 0; i < 45; i++) { + em4305_write_bit((data_with_parity >> (44 - i)) & 1); + } + + // wait for power check and eeprom write + furi_delay_us(EM4305_TIMING_POWER_CHECK); + furi_delay_us(EM4305_TIMING_EEPROM_WRITE); +} + +void em4305_write(LFRFIDEM4305* data) { + furi_check(data); + + em4305_start(); + FURI_CRITICAL_ENTER(); + + for(uint8_t i = 0; i < EM4x05_WORD_COUNT; i++) { + if(data->mask & (1 << i)) { + em4305_write_word(i, data->word[i]); + } + } + + FURI_CRITICAL_EXIT(); + em4305_stop(); +} diff --git a/lib/lfrfid/tools/em4305.h b/lib/lfrfid/tools/em4305.h new file mode 100644 index 000000000..0cec00254 --- /dev/null +++ b/lib/lfrfid/tools/em4305.h @@ -0,0 +1,61 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// EM4305/4205 chip config definitions, thanks proxmark3! +#define EM4x05_GET_BITRATE(x) ((((x) & 0x3F) * 2) + 2) +// Note: only data rates 8, 16, 32, 40(*) and 64 are supported. (*) only with EM4305 330pF +#define EM4x05_SET_BITRATE(x) (((x) - 2) / 2) +#define EM4x05_MODULATION_NRZ (0x00000000) +#define EM4x05_MODULATION_MANCHESTER (0x00000040) +#define EM4x05_MODULATION_BIPHASE (0x00000080) +#define EM4x05_MODULATION_MILLER (0x000000C0) // not supported by all 4x05/4x69 chips +#define EM4x05_MODULATION_PSK1 (0x00000100) // not supported by all 4x05/4x69 chips +#define EM4x05_MODULATION_PSK2 (0x00000140) // not supported by all 4x05/4x69 chips +#define EM4x05_MODULATION_PSK3 (0x00000180) // not supported by all 4x05/4x69 chips +#define EM4x05_MODULATION_FSK1 (0x00000200) // not supported by all 4x05/4x69 chips +#define EM4x05_MODULATION_FSK2 (0x00000240) // not supported by all 4x05/4x69 chips +#define EM4x05_PSK_RF_2 (0) +#define EM4x05_PSK_RF_4 (0x00000400) +#define EM4x05_PSK_RF_8 (0x00000800) +#define EM4x05_MAXBLOCK_SHIFT (14) +#define EM4x05_FIRST_USER_BLOCK (5) +#define EM4x05_SET_NUM_BLOCKS(x) \ + (((x) + 4) << 14) // number of blocks sent during default read mode +#define EM4x05_GET_NUM_BLOCKS(x) ((((x) >> 14) & 0xF) - 4) +#define EM4x05_READ_LOGIN_REQ (1 << 18) +#define EM4x05_READ_HK_LOGIN_REQ (1 << 19) +#define EM4x05_WRITE_LOGIN_REQ (1 << 20) +#define EM4x05_WRITE_HK_LOGIN_REQ (1 << 21) +#define EM4x05_READ_AFTER_WRITE (1 << 22) +#define EM4x05_DISABLE_ALLOWED (1 << 23) +#define EM4x05_READER_TALK_FIRST (1 << 24) +#define EM4x05_INVERT (1 << 25) +#define EM4x05_PIGEON (1 << 26) + +#define EM4x05_WORD_COUNT (16) + +#define EM4x05_OPCODE_LOGIN (0b001) +#define EM4x05_OPCODE_WRITE (0b010) +#define EM4x05_OPCODE_READ (0b100) +#define EM4x05_OPCODE_PROTECT (0b110) +#define EM4x05_OPCODE_DISABLE (0b101) + +typedef struct { + uint32_t word[EM4x05_WORD_COUNT]; /**< Word data to write */ + uint16_t mask; /**< Word mask */ +} LFRFIDEM4305; + +/** Write EM4305 tag data to tag + * + * @param data The data to write (mask is taken from that data) + */ +void em4305_write(LFRFIDEM4305* data); + +#ifdef __cplusplus +} +#endif diff --git a/lib/mbedtls b/lib/mbedtls index edb8fec98..107ea89da 160000 --- a/lib/mbedtls +++ b/lib/mbedtls @@ -1 +1 @@ -Subproject commit edb8fec9882084344a314368ac7fd957a187519c +Subproject commit 107ea89daaefb9867ea9121002fbbdf926780e98 diff --git a/lib/mbedtls.scons b/lib/mbedtls.scons index 77add7696..759f263af 100644 --- a/lib/mbedtls.scons +++ b/lib/mbedtls.scons @@ -6,6 +6,7 @@ env.Append( "#/lib/mbedtls/include", ], SDK_HEADERS=[ + File("mbedtls/include/mbedtls/aes.h"), File("mbedtls/include/mbedtls/des.h"), File("mbedtls/include/mbedtls/sha1.h"), File("mbedtls/include/mbedtls/sha256.h"), @@ -37,6 +38,7 @@ libenv.AppendUnique( # sources = libenv.GlobRecursive("*.c*", "mbedtls/library") # Otherwise, we can just use the files we need: sources = [ + File("mbedtls/library/aes.c"), File("mbedtls/library/bignum.c"), File("mbedtls/library/bignum_core.c"), File("mbedtls/library/ecdsa.c"), diff --git a/lib/mjs/mjs_exec.c b/lib/mjs/mjs_exec.c index 8fdb2d7e5..273c38a34 100644 --- a/lib/mjs/mjs_exec.c +++ b/lib/mjs/mjs_exec.c @@ -584,7 +584,8 @@ static void mjs_apply_(struct mjs* mjs) { if(mjs_is_array(v)) { nargs = mjs_array_length(mjs, v); args = calloc(nargs, sizeof(args[0])); - for(i = 0; i < nargs; i++) args[i] = mjs_array_get(mjs, v, i); + for(i = 0; i < nargs; i++) + args[i] = mjs_array_get(mjs, v, i); } mjs_apply(mjs, &res, func, mjs_arg(mjs, 0), nargs, args); free(args); diff --git a/lib/mjs/mjs_parser.c b/lib/mjs/mjs_parser.c index 212804a86..98f60459d 100644 --- a/lib/mjs/mjs_parser.c +++ b/lib/mjs/mjs_parser.c @@ -47,7 +47,7 @@ static int ptest(struct pstate* p) { return tok; } -static int s_unary_ops[] = { +static const int s_unary_ops[] = { TOK_NOT, TOK_TILDA, TOK_PLUS_PLUS, @@ -56,10 +56,10 @@ static int s_unary_ops[] = { TOK_MINUS, TOK_PLUS, TOK_EOF}; -static int s_comparison_ops[] = {TOK_LT, TOK_LE, TOK_GT, TOK_GE, TOK_EOF}; -static int s_postfix_ops[] = {TOK_PLUS_PLUS, TOK_MINUS_MINUS, TOK_EOF}; -static int s_equality_ops[] = {TOK_EQ, TOK_NE, TOK_EQ_EQ, TOK_NE_NE, TOK_EOF}; -static int s_assign_ops[] = { +static const int s_comparison_ops[] = {TOK_LT, TOK_LE, TOK_GT, TOK_GE, TOK_EOF}; +static const int s_postfix_ops[] = {TOK_PLUS_PLUS, TOK_MINUS_MINUS, TOK_EOF}; +static const int s_equality_ops[] = {TOK_EQ, TOK_NE, TOK_EQ_EQ, TOK_NE_NE, TOK_EOF}; +static const int s_assign_ops[] = { TOK_ASSIGN, TOK_PLUS_ASSIGN, TOK_MINUS_ASSIGN, @@ -74,7 +74,7 @@ static int s_assign_ops[] = { TOK_OR_ASSIGN, TOK_EOF}; -static int findtok(int* toks, int tok) { +static int findtok(int const* toks, int tok) { int i = 0; while(tok != toks[i] && toks[i] != TOK_EOF) i++; diff --git a/lib/mjs/mjs_primitive.c b/lib/mjs/mjs_primitive.c index e73ae892d..20d2a8c66 100644 --- a/lib/mjs/mjs_primitive.c +++ b/lib/mjs/mjs_primitive.c @@ -9,6 +9,8 @@ #include "mjs_string_public.h" #include "mjs_util.h" +#include + mjs_val_t mjs_mk_null(void) { return MJS_NULL; } @@ -94,7 +96,7 @@ int mjs_is_boolean(mjs_val_t v) { } #define MJS_IS_POINTER_LEGIT(n) \ - (((n)&MJS_TAG_MASK) == 0 || ((n)&MJS_TAG_MASK) == (~0 & MJS_TAG_MASK)) + (((n) & MJS_TAG_MASK) == 0 || ((n) & MJS_TAG_MASK) == (~0 & MJS_TAG_MASK)) MJS_PRIVATE mjs_val_t mjs_pointer_to_value(struct mjs* mjs, void* p) { uint64_t n = ((uint64_t)(uintptr_t)p); @@ -165,13 +167,13 @@ MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) { mjs_val_t ret = MJS_UNDEFINED; mjs_val_t base_v = MJS_UNDEFINED; int32_t base = 10; - int32_t num; + double num; /* get number from `this` */ if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_NUMBER, NULL)) { goto clean; } - num = mjs_get_int32(mjs, mjs->vals.this_obj); + num = mjs_get_double(mjs, mjs->vals.this_obj); if(mjs_nargs(mjs) >= 1) { /* get base from arg 0 */ @@ -181,9 +183,20 @@ MJS_PRIVATE void mjs_number_to_string(struct mjs* mjs) { base = mjs_get_int(mjs, base_v); } - char tmp_str[] = "-2147483648"; - itoa(num, tmp_str, base); - ret = mjs_mk_string(mjs, tmp_str, ~0, true); + if(base != 10 || floor(num) == num) { + char tmp_str[] = "-2147483648"; + itoa((int32_t)num, tmp_str, base); + ret = mjs_mk_string(mjs, tmp_str, ~0, true); + } else { + char tmp_str[] = "2.22514337200000e-308"; + snprintf(tmp_str, sizeof(tmp_str), "%.*g", DBL_DIG, num); + size_t len = strlen(tmp_str); + while(len && tmp_str[len - 1] == '0') { + len--; + } + tmp_str[len] = '\0'; + ret = mjs_mk_string(mjs, tmp_str, ~0, true); + } clean: mjs_return(mjs, ret); diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c index 7914c1f7f..2143f0f5f 100644 --- a/lib/nfc/helpers/nfc_data_generator.c +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -392,37 +392,15 @@ static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfCl mf_classic_set_block_read(mfc_data, 0, &mfc_data->block[0]); + // Set every block to 0x00 uint16_t block_num = mf_classic_get_total_block_num(type); - if(type == MfClassicType4k) { - // Set every block to 0x00 - for(uint16_t i = 1; i < block_num; i++) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc_data, i); - } else { - memset(&mfc_data->block[i].data, 0x00, 16); - } - mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); - } - } else if(type == MfClassicType1k) { - // Set every block to 0x00 - for(uint16_t i = 1; i < block_num; i++) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc_data, i); - } else { - memset(&mfc_data->block[i].data, 0x00, 16); - } - mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); - } - } else if(type == MfClassicTypeMini) { - // Set every block to 0x00 - for(uint16_t i = 1; i < block_num; i++) { - if(mf_classic_is_sector_trailer(i)) { - nfc_generate_mf_classic_sector_trailer(mfc_data, i); - } else { - memset(&mfc_data->block[i].data, 0x00, 16); - } - mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); + for(uint16_t i = 1; i < block_num; i++) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc_data, i); + } else { + memset(&mfc_data->block[i].data, 0x00, MF_CLASSIC_BLOCK_SIZE); } + mf_classic_set_block_read(mfc_data, i, &mfc_data->block[i]); } nfc_generate_mf_classic_block_0( diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 720daf238..cf00cbc09 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -118,7 +118,8 @@ NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { blocks[1] = FELICA_BLOCK_INDEX_WCNT; blocks[2] = FELICA_BLOCK_INDEX_MAC_A; FelicaPollerReadCommandResponse* rx_resp; - error = felica_poller_read_blocks(instance, sizeof(blocks), blocks, &rx_resp); + error = felica_poller_read_blocks( + instance, sizeof(blocks), blocks, FELICA_SERVICE_RO_ACCESS, &rx_resp); if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; if(felica_check_mac( @@ -174,7 +175,7 @@ NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { if(error != FelicaErrorNone || tx_resp->SF1 != 0 || tx_resp->SF2 != 0) break; FelicaPollerReadCommandResponse* rx_resp; - error = felica_poller_read_blocks(instance, 1, blocks, &rx_resp); + error = felica_poller_read_blocks(instance, 1, blocks, FELICA_SERVICE_RO_ACCESS, &rx_resp); if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; instance->data->data.fs.state.SF1 = 0; @@ -203,7 +204,8 @@ NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) { } FelicaPollerReadCommandResponse* response; - FelicaError error = felica_poller_read_blocks(instance, block_count, block_list, &response); + FelicaError error = felica_poller_read_blocks( + instance, block_count, block_list, FELICA_SERVICE_RO_ACCESS, &response); if(error == FelicaErrorNone) { block_count = (response->SF1 == 0) ? response->block_count : block_count; uint8_t* data_ptr = diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index b386f4b4b..541442df2 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -56,6 +56,23 @@ typedef struct { */ FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data); +/** + * @brief Performs felica read operation for blocks provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in reading procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[in] service_code Service code for the read operation + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + uint16_t service_code, + FelicaPollerReadCommandResponse** const response_ptr); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index 8ec4b2889..49112debd 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -134,6 +134,7 @@ FelicaError felica_poller_read_blocks( FelicaPoller* instance, const uint8_t block_count, const uint8_t* const block_numbers, + uint16_t service_code, FelicaPollerReadCommandResponse** const response_ptr) { furi_assert(instance); furi_assert(block_count <= 4); @@ -143,7 +144,7 @@ FelicaError felica_poller_read_blocks( felica_poller_prepare_tx_buffer( instance, FELICA_CMD_READ_WITHOUT_ENCRYPTION, - FELICA_SERVICE_RO_ACCESS, + service_code, block_count, block_numbers, 0, diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index df205ba67..df4990a4e 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -70,21 +70,6 @@ FelicaError felica_poller_polling( const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp); -/** - * @brief Performs felica read operation for blocks provided as parameters - * - * @param[in, out] instance pointer to the instance to be used in the transaction. - * @param[in] block_count Amount of blocks involved in reading procedure - * @param[in] block_numbers Array with block indexes according to felica docs - * @param[out] response_ptr Pointer to the response structure - * @return FelicaErrorNone on success, an error code on failure. -*/ -FelicaError felica_poller_read_blocks( - FelicaPoller* instance, - const uint8_t block_count, - const uint8_t* const block_numbers, - FelicaPollerReadCommandResponse** const response_ptr); - /** * @brief Performs felica write operation with data provided as parameters * diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3.c b/lib/nfc/protocols/iso15693_3/iso15693_3.c index e2628b258..802d87f03 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3.c @@ -173,33 +173,35 @@ bool iso15693_3_load(Iso15693_3Data* data, FlipperFormat* ff, uint32_t version) if(flipper_format_key_exist(ff, ISO15693_3_BLOCK_COUNT_KEY) && flipper_format_key_exist(ff, ISO15693_3_BLOCK_SIZE_KEY)) { + data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_MEMORY; + uint32_t block_count; if(!flipper_format_read_uint32(ff, ISO15693_3_BLOCK_COUNT_KEY, &block_count, 1)) break; - data->system_info.block_count = block_count; - data->system_info.flags |= ISO15693_3_SYSINFO_FLAG_MEMORY; if(!flipper_format_read_hex( ff, ISO15693_3_BLOCK_SIZE_KEY, &(data->system_info.block_size), 1)) break; - simple_array_init( - data->block_data, data->system_info.block_size * data->system_info.block_count); - - if(!flipper_format_read_hex( - ff, - ISO15693_3_DATA_CONTENT_KEY, - simple_array_get_data(data->block_data), - simple_array_get_count(data->block_data))) - break; - - if(flipper_format_key_exist(ff, ISO15693_3_SECURITY_STATUS_KEY)) { + if(data->system_info.block_count > 0 && data->system_info.block_size > 0) { + simple_array_init( + data->block_data, + data->system_info.block_size * data->system_info.block_count); simple_array_init(data->block_security, data->system_info.block_count); - const bool security_loaded = has_lock_bits ? - iso15693_3_load_security(data, ff) : - iso15693_3_load_security_legacy(data, ff); - if(!security_loaded) break; + if(!flipper_format_read_hex( + ff, + ISO15693_3_DATA_CONTENT_KEY, + simple_array_get_data(data->block_data), + simple_array_get_count(data->block_data))) + break; + + if(flipper_format_key_exist(ff, ISO15693_3_SECURITY_STATUS_KEY)) { + const bool security_loaded = has_lock_bits ? + iso15693_3_load_security(data, ff) : + iso15693_3_load_security_legacy(data, ff); + if(!security_loaded) break; + } } } @@ -260,22 +262,24 @@ bool iso15693_3_save(const Iso15693_3Data* data, FlipperFormat* ff) { ff, ISO15693_3_BLOCK_SIZE_KEY, &data->system_info.block_size, 1)) break; - if(!flipper_format_write_hex( - ff, - ISO15693_3_DATA_CONTENT_KEY, - simple_array_cget_data(data->block_data), - simple_array_get_count(data->block_data))) - break; + if(data->system_info.block_count > 0 && data->system_info.block_size > 0) { + if(!flipper_format_write_hex( + ff, + ISO15693_3_DATA_CONTENT_KEY, + simple_array_cget_data(data->block_data), + simple_array_get_count(data->block_data))) + break; - if(!flipper_format_write_comment_cstr( - ff, "Block Security Status: 01 = locked, 00 = not locked")) - break; - if(!flipper_format_write_hex( - ff, - ISO15693_3_SECURITY_STATUS_KEY, - simple_array_cget_data(data->block_security), - simple_array_get_count(data->block_security))) - break; + if(!flipper_format_write_comment_cstr( + ff, "Block Security Status: 01 = locked, 00 = not locked")) + break; + if(!flipper_format_write_hex( + ff, + ISO15693_3_SECURITY_STATUS_KEY, + simple_array_cget_data(data->block_security), + simple_array_get_count(data->block_security))) + break; + } } saved = true; } while(false); diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index 6aee84a3f..bc677ce67 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -100,10 +100,12 @@ Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_ break; } - if(system_info->block_count > 0) { - // Read blocks: Optional command + if(system_info->block_count > 0 && system_info->block_size > 0) { simple_array_init( data->block_data, system_info->block_count * system_info->block_size); + simple_array_init(data->block_security, system_info->block_count); + + // Read blocks: Optional command ret = iso15693_3_poller_read_blocks( instance, simple_array_get_data(data->block_data), @@ -115,8 +117,6 @@ Iso15693_3Error iso15693_3_poller_activate(Iso15693_3Poller* instance, Iso15693_ } // Get block security status: Optional command - simple_array_init(data->block_security, system_info->block_count); - ret = iso15693_3_poller_get_blocks_security( instance, simple_array_get_data(data->block_security), system_info->block_count); if(ret != Iso15693_3ErrorNone) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index ef571117a..1f5eea271 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -19,7 +19,7 @@ typedef struct { uint8_t cmd_start_byte; size_t cmd_len_bits; size_t command_num; - MfClassicListenerCommandHandler* handler; + const MfClassicListenerCommandHandler* handler; } MfClassicListenerCmd; static void mf_classic_listener_prepare_emulation(MfClassicListener* instance) { @@ -441,42 +441,42 @@ static MfClassicListenerCommand return command; } -static MfClassicListenerCommandHandler mf_classic_listener_halt_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_halt_handlers[] = { mf_classic_listener_halt_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_auth_key_a_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_auth_key_a_handlers[] = { mf_classic_listener_auth_key_a_handler, mf_classic_listener_auth_second_part_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_auth_key_b_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_auth_key_b_handlers[] = { mf_classic_listener_auth_key_b_handler, mf_classic_listener_auth_second_part_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_read_block_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_read_block_handlers[] = { mf_classic_listener_read_block_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_write_block_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_write_block_handlers[] = { mf_classic_listener_write_block_first_part_handler, mf_classic_listener_write_block_second_part_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_value_dec_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_value_dec_handlers[] = { mf_classic_listener_value_dec_handler, mf_classic_listener_value_data_receive_handler, mf_classic_listener_value_transfer_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_value_inc_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_value_inc_handlers[] = { mf_classic_listener_value_inc_handler, mf_classic_listener_value_data_receive_handler, mf_classic_listener_value_transfer_handler, }; -static MfClassicListenerCommandHandler mf_classic_listener_value_restore_handlers[] = { +static const MfClassicListenerCommandHandler mf_classic_listener_value_restore_handlers[] = { mf_classic_listener_value_restore_handler, mf_classic_listener_value_data_receive_handler, mf_classic_listener_value_transfer_handler, diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.c b/lib/nfc/protocols/mf_desfire/mf_desfire.c index 4d54a2c0e..42ad4634b 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.c @@ -4,6 +4,46 @@ #define MF_DESFIRE_PROTOCOL_NAME "Mifare DESFire" +#define MF_DESFIRE_HW_MINOR_TYPE (0x00) +#define MF_DESFIRE_HW_MINOR_TYPE_MF3ICD40 (0x02) + +#define MF_DESFIRE_HW_MAJOR_TYPE_EV1 (0x01) +#define MF_DESFIRE_HW_MAJOR_TYPE_EV2 (0x12) +#define MF_DESFIRE_HW_MAJOR_TYPE_EV2_XL (0x22) +#define MF_DESFIRE_HW_MAJOR_TYPE_EV3 (0x33) +#define MF_DESFIRE_HW_MAJOR_TYPE_MF3ICD40 (0x00) + +#define MF_DESFIRE_STORAGE_SIZE_2K (0x16) +#define MF_DESFIRE_STORAGE_SIZE_4K (0x18) +#define MF_DESFIRE_STORAGE_SIZE_8K (0x1A) +#define MF_DESFIRE_STORAGE_SIZE_16K (0x1C) +#define MF_DESFIRE_STORAGE_SIZE_32K (0x1E) +#define MF_DESFIRE_STORAGE_SIZE_MF3ICD40 (0xFF) +#define MF_DESFIRE_STORAGE_SIZE_UNKNOWN (0xFF) + +#define MF_DESFIRE_TEST_TYPE_MF3ICD40(major, minor, storage) \ + (((major) == MF_DESFIRE_HW_MAJOR_TYPE_MF3ICD40) && \ + ((minor) == MF_DESFIRE_HW_MINOR_TYPE_MF3ICD40) && \ + ((storage) == MF_DESFIRE_STORAGE_SIZE_MF3ICD40)) + +static const char* mf_desfire_type_strings[] = { + [MfDesfireTypeMF3ICD40] = "(MF3ICD40)", + [MfDesfireTypeEV1] = "EV1", + [MfDesfireTypeEV2] = "EV2", + [MfDesfireTypeEV2XL] = "EV2 XL", + [MfDesfireTypeEV3] = "EV3", + [MfDesfireTypeUnknown] = "UNK", +}; + +static const char* mf_desfire_size_strings[] = { + [MfDesfireSize2k] = "2K", + [MfDesfireSize4k] = "4K", + [MfDesfireSize8k] = "8K", + [MfDesfireSize16k] = "16K", + [MfDesfireSize32k] = "32K", + [MfDesfireSizeUnknown] = "", +}; + const NfcDeviceBase nfc_device_mf_desfire = { .protocol_name = MF_DESFIRE_PROTOCOL_NAME, .alloc = (NfcDeviceAlloc)mf_desfire_alloc, @@ -26,7 +66,7 @@ MfDesfireData* mf_desfire_alloc(void) { data->master_key_versions = simple_array_alloc(&mf_desfire_key_version_array_config); data->application_ids = simple_array_alloc(&mf_desfire_app_id_array_config); data->applications = simple_array_alloc(&mf_desfire_application_array_config); - + data->device_name = furi_string_alloc(); return data; } @@ -38,6 +78,7 @@ void mf_desfire_free(MfDesfireData* data) { simple_array_free(data->application_ids); simple_array_free(data->master_key_versions); iso14443_4a_free(data->iso14443_4a_data); + furi_string_free(data->device_name); free(data); } @@ -228,10 +269,83 @@ bool mf_desfire_is_equal(const MfDesfireData* data, const MfDesfireData* other) simple_array_is_equal(data->applications, other->applications); } +static MfDesfireType mf_desfire_get_type_from_version(const MfDesfireVersion* const version) { + MfDesfireType type = MfDesfireTypeUnknown; + + switch(version->hw_major) { + case MF_DESFIRE_HW_MAJOR_TYPE_EV1: + type = MfDesfireTypeEV1; + break; + case MF_DESFIRE_HW_MAJOR_TYPE_EV2: + type = MfDesfireTypeEV2; + break; + case MF_DESFIRE_HW_MAJOR_TYPE_EV2_XL: + type = MfDesfireTypeEV2XL; + break; + case MF_DESFIRE_HW_MAJOR_TYPE_EV3: + type = MfDesfireTypeEV3; + break; + default: + if(MF_DESFIRE_TEST_TYPE_MF3ICD40(version->hw_major, version->hw_minor, version->hw_storage)) + type = MfDesfireTypeMF3ICD40; + break; + } + + return type; +} + +static MfDesfireSize mf_desfire_get_size_from_version(const MfDesfireVersion* const version) { + MfDesfireSize size = MfDesfireSizeUnknown; + + switch(version->hw_storage) { + case MF_DESFIRE_STORAGE_SIZE_2K: + size = MfDesfireSize2k; + break; + case MF_DESFIRE_STORAGE_SIZE_4K: + size = MfDesfireSize4k; + break; + case MF_DESFIRE_STORAGE_SIZE_8K: + size = MfDesfireSize8k; + break; + case MF_DESFIRE_STORAGE_SIZE_16K: + size = MfDesfireSize16k; + break; + case MF_DESFIRE_STORAGE_SIZE_32K: + size = MfDesfireSize32k; + break; + default: + if(MF_DESFIRE_TEST_TYPE_MF3ICD40(version->hw_major, version->hw_minor, version->hw_storage)) + size = MfDesfireSize4k; + break; + } + + return size; +} + const char* mf_desfire_get_device_name(const MfDesfireData* data, NfcDeviceNameType name_type) { - UNUSED(data); - UNUSED(name_type); - return MF_DESFIRE_PROTOCOL_NAME; + furi_check(data); + + const MfDesfireType type = mf_desfire_get_type_from_version(&data->version); + const MfDesfireSize size = mf_desfire_get_size_from_version(&data->version); + + if(type == MfDesfireTypeUnknown) { + furi_string_printf(data->device_name, "Unknown %s", MF_DESFIRE_PROTOCOL_NAME); + } else if(name_type == NfcDeviceNameTypeFull) { + furi_string_printf( + data->device_name, + "%s %s %s", + MF_DESFIRE_PROTOCOL_NAME, + mf_desfire_type_strings[type], + mf_desfire_size_strings[size]); + } else { + furi_string_printf( + data->device_name, + "%s %s", + mf_desfire_type_strings[type], + mf_desfire_size_strings[size]); + } + + return furi_string_get_cstr(data->device_name); } const uint8_t* mf_desfire_get_uid(const MfDesfireData* data, size_t* uid_len) { diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index dd2009276..ec60b336b 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -29,6 +29,28 @@ extern "C" { #define MF_DESFIRE_APP_ID_SIZE (3) #define MF_DESFIRE_VALUE_SIZE (4) +typedef enum { + MfDesfireTypeMF3ICD40, + MfDesfireTypeEV1, + MfDesfireTypeEV2, + MfDesfireTypeEV2XL, + MfDesfireTypeEV3, + + MfDesfireTypeUnknown, + MfDesfireTypeNum, +} MfDesfireType; + +typedef enum { + MfDesfireSize2k, + MfDesfireSize4k, + MfDesfireSize8k, + MfDesfireSize16k, + MfDesfireSize32k, + + MfDesfireSizeUnknown, + MfDesfireSizeNum, +} MfDesfireSize; + typedef struct { uint8_t hw_vendor; uint8_t hw_type; @@ -75,6 +97,7 @@ typedef enum { MfDesfireFileTypeValue = 2, MfDesfireFileTypeLinearRecord = 3, MfDesfireFileTypeCyclicRecord = 4, + MfDesfireFileTypeTransactionMac = 5, } MfDesfireFileType; typedef enum { @@ -106,6 +129,11 @@ typedef struct { uint32_t max; uint32_t cur; } record; + struct { + uint8_t key_option; + uint8_t key_version; + uint32_t counter_limit; + } transaction_mac; }; } MfDesfireFileSettings; @@ -131,6 +159,7 @@ typedef enum { MfDesfireErrorProtocol, MfDesfireErrorTimeout, MfDesfireErrorAuthentication, + MfDesfireErrorCommandNotSupported, } MfDesfireError; typedef struct { @@ -141,6 +170,7 @@ typedef struct { SimpleArray* master_key_versions; SimpleArray* application_ids; SimpleArray* applications; + FuriString* device_name; } MfDesfireData; extern const NfcDeviceBase nfc_device_mf_desfire; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index d83a91ad1..eba9c4312 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -1,5 +1,7 @@ #include "mf_desfire_i.h" +#include + #define TAG "MfDesfire" #define BITS_IN_BYTE (8U) @@ -47,6 +49,10 @@ #define MF_DESFIRE_FFF_FILE_MAX_KEY "Max" #define MF_DESFIRE_FFF_FILE_CUR_KEY "Cur" +#define MF_DESFIRE_FFF_FILE_KEY_OPTION_KEY "Key Option" +#define MF_DESFIRE_FFF_FILE_KEY_VERSION_KEY "Key Version" +#define MF_DESFIRE_FFF_FILE_COUNTER_LIMIT_KEY "Counter Limit" + bool mf_desfire_version_parse(MfDesfireVersion* data, const BitBuffer* buf) { const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfDesfireVersion); @@ -168,12 +174,19 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer uint32_t cur : 3 * BITS_IN_BYTE; } MfDesfireFileSettingsRecord; + typedef struct FURI_PACKED { + uint8_t key_option; + uint8_t key_version; + uint8_t counter_limit[]; + } MfDesfireFileSettingsTransactionMac; + typedef struct FURI_PACKED { MfDesfireFileSettingsHeader header; union { MfDesfireFileSettingsData data; MfDesfireFileSettingsValue value; MfDesfireFileSettingsRecord record; + MfDesfireFileSettingsTransactionMac transaction_mac; }; } MfDesfireFileSettingsLayout; @@ -182,7 +195,7 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer const size_t data_size = bit_buffer_get_size_bytes(buf); const uint8_t* data_ptr = bit_buffer_get_data(buf); const size_t min_data_size = - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData); + sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsTransactionMac); if(data_size < min_data_size) { FURI_LOG_E( @@ -202,17 +215,11 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer if(file_settings_temp.type == MfDesfireFileTypeStandard || file_settings_temp.type == MfDesfireFileTypeBackup) { - memcpy( - &layout.data, - &data_ptr[sizeof(MfDesfireFileSettingsHeader)], - sizeof(MfDesfireFileSettingsData)); + memcpy(&layout.data, &data_ptr[bytes_processed], sizeof(MfDesfireFileSettingsData)); file_settings_temp.data.size = layout.data.size; bytes_processed += sizeof(MfDesfireFileSettingsData); } else if(file_settings_temp.type == MfDesfireFileTypeValue) { - memcpy( - &layout.value, - &data_ptr[sizeof(MfDesfireFileSettingsHeader)], - sizeof(MfDesfireFileSettingsValue)); + memcpy(&layout.value, &data_ptr[bytes_processed], sizeof(MfDesfireFileSettingsValue)); file_settings_temp.value.lo_limit = layout.value.lo_limit; file_settings_temp.value.hi_limit = layout.value.hi_limit; file_settings_temp.value.limited_credit_value = layout.value.limited_credit_value; @@ -222,13 +229,34 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer file_settings_temp.type == MfDesfireFileTypeLinearRecord || file_settings_temp.type == MfDesfireFileTypeCyclicRecord) { memcpy( - &layout.record, - &data_ptr[sizeof(MfDesfireFileSettingsHeader)], - sizeof(MfDesfireFileSettingsRecord)); + &layout.record, &data_ptr[bytes_processed], sizeof(MfDesfireFileSettingsRecord)); file_settings_temp.record.size = layout.record.size; file_settings_temp.record.max = layout.record.max; file_settings_temp.record.cur = layout.record.cur; bytes_processed += sizeof(MfDesfireFileSettingsRecord); + } else if(file_settings_temp.type == MfDesfireFileTypeTransactionMac) { + const bool has_counter_limit = (layout.header.comm & 0x20) != 0; + memcpy( + &layout.transaction_mac, + &data_ptr[bytes_processed], + sizeof(MfDesfireFileSettingsTransactionMac)); + file_settings_temp.transaction_mac.key_option = layout.transaction_mac.key_option; + file_settings_temp.transaction_mac.key_version = layout.transaction_mac.key_version; + if(!has_counter_limit) { + file_settings_temp.transaction_mac.counter_limit = 0; + } else { + // AES (4b) or LRP (2b) + const size_t counter_limit_size = (layout.transaction_mac.key_option & 0x02) ? 4 : + 2; + memcpy( + &layout.transaction_mac, + &data_ptr[bytes_processed], + sizeof(MfDesfireFileSettingsTransactionMac) + counter_limit_size); + file_settings_temp.transaction_mac.counter_limit = bit_lib_bytes_to_num_be( + layout.transaction_mac.counter_limit, counter_limit_size); + bytes_processed += counter_limit_size; + } + bytes_processed += sizeof(MfDesfireFileSettingsTransactionMac); } else { FURI_LOG_W(TAG, "Unknown file type: %02x", file_settings_temp.type); break; @@ -468,6 +496,21 @@ bool mf_desfire_file_settings_load( furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY); if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1)) break; + } else if(data->type == MfDesfireFileTypeTransactionMac) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_OPTION_KEY); + if(!flipper_format_read_hex( + ff, furi_string_get_cstr(key), &data->transaction_mac.key_option, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_VERSION_KEY); + if(!flipper_format_read_hex( + ff, furi_string_get_cstr(key), &data->transaction_mac.key_version, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COUNTER_LIMIT_KEY); + if(!flipper_format_read_uint32( + ff, furi_string_get_cstr(key), &data->transaction_mac.counter_limit, 1)) + break; } success = true; @@ -716,6 +759,21 @@ bool mf_desfire_file_settings_save( furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_CUR_KEY); if(!flipper_format_write_uint32(ff, furi_string_get_cstr(key), &data->record.cur, 1)) break; + } else if(data->type == MfDesfireFileTypeTransactionMac) { + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_OPTION_KEY); + if(!flipper_format_write_hex( + ff, furi_string_get_cstr(key), &data->transaction_mac.key_option, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_KEY_VERSION_KEY); + if(!flipper_format_write_hex( + ff, furi_string_get_cstr(key), &data->transaction_mac.key_version, 1)) + break; + + furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_COUNTER_LIMIT_KEY); + if(!flipper_format_write_uint32( + ff, furi_string_get_cstr(key), &data->transaction_mac.counter_limit, 1)) + break; } success = true; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index bd8ecfaee..45e5a27f9 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -82,9 +82,12 @@ static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* in FURI_LOG_D(TAG, "Read free memory success"); instance->state = MfDesfirePollerStateReadMasterKeySettings; } else if(instance->error == MfDesfireErrorNotPresent) { - FURI_LOG_D(TAG, "Read free memoty is unsupported"); + FURI_LOG_D(TAG, "Read free memory is not present"); instance->state = MfDesfirePollerStateReadMasterKeySettings; command = NfcCommandReset; + } else if(instance->error == MfDesfireErrorCommandNotSupported) { + FURI_LOG_D(TAG, "Read free memory is unsupported"); + instance->state = MfDesfirePollerStateReadMasterKeySettings; } else { FURI_LOG_E(TAG, "Failed to read free memory"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 1dd6c50e1..8b57fcc4c 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -25,6 +25,8 @@ MfDesfireError mf_desfire_process_status_code(uint8_t status_code) { return MfDesfireErrorNone; case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR: return MfDesfireErrorAuthentication; + case MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE: + return MfDesfireErrorCommandNotSupported; default: return MfDesfireErrorProtocol; } @@ -466,7 +468,7 @@ MfDesfireError mf_desfire_poller_read_file_data_multi( file_type == MfDesfireFileTypeLinearRecord || file_type == MfDesfireFileTypeCyclicRecord) { error = mf_desfire_poller_read_file_records( - instance, file_id, 0, file_settings_cur->data.size, file_data); + instance, file_id, 0, file_settings_cur->record.size, file_data); } } diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index 4ad2ff878..bd32956d6 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -27,65 +27,51 @@ MfPlusError mf_plus_get_type_from_version( MfPlusError error = MfPlusErrorProtocol; - if(mf_plus_data->version.hw_major == 0x02 || mf_plus_data->version.hw_major == 0x82) { + if(mf_plus_data->version.hw_type == 0x02 || mf_plus_data->version.hw_type == 0x82) { error = MfPlusErrorNone; - if(iso14443_4a_data->iso14443_3a_data->sak == 0x10) { - // Mifare Plus 2K SL2 - mf_plus_data->type = MfPlusTypePlus; + // Mifare Plus EV1/EV2 + + // Revision + switch(mf_plus_data->version.hw_major) { + case 0x11: + mf_plus_data->type = MfPlusTypeEV1; + FURI_LOG_D(TAG, "Mifare Plus EV1"); + break; + case 0x22: + mf_plus_data->type = MfPlusTypeEV2; + FURI_LOG_D(TAG, "Mifare Plus EV2"); + break; + default: + mf_plus_data->type = MfPlusTypeUnknown; + FURI_LOG_D(TAG, "Unknown Mifare Plus EV type"); + break; + } + + // Storage size + switch(mf_plus_data->version.hw_storage) { + case 0x16: mf_plus_data->size = MfPlusSize2K; - mf_plus_data->security_level = MfPlusSecurityLevel2; - FURI_LOG_D(TAG, "Mifare Plus 2K SL2"); - } else if(iso14443_4a_data->iso14443_3a_data->sak == 0x11) { - // Mifare Plus 4K SL3 - mf_plus_data->type = MfPlusTypePlus; + FURI_LOG_D(TAG, "2K"); + break; + case 0x18: mf_plus_data->size = MfPlusSize4K; + FURI_LOG_D(TAG, "4K"); + break; + default: + mf_plus_data->size = MfPlusSizeUnknown; + FURI_LOG_D(TAG, "Unknown storage size"); + break; + } + + // Security level + if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) { + // Mifare Plus EV1/2 SL3 mf_plus_data->security_level = MfPlusSecurityLevel3; - FURI_LOG_D(TAG, "Mifare Plus 4K SL3"); + FURI_LOG_D(TAG, "Mifare Plus EV1/2 SL3"); } else { - // Mifare Plus EV1/EV2 - - // Revision - switch(mf_plus_data->version.hw_major) { - case 0x11: - mf_plus_data->type = MfPlusTypeEV1; - FURI_LOG_D(TAG, "Mifare Plus EV1"); - break; - case 0x22: - mf_plus_data->type = MfPlusTypeEV2; - FURI_LOG_D(TAG, "Mifare Plus EV2"); - break; - default: - mf_plus_data->type = MfPlusTypeUnknown; - FURI_LOG_D(TAG, "Unknown Mifare Plus EV type"); - break; - } - - // Storage size - switch(mf_plus_data->version.hw_storage) { - case 0x16: - mf_plus_data->size = MfPlusSize2K; - FURI_LOG_D(TAG, "2K"); - break; - case 0x18: - mf_plus_data->size = MfPlusSize4K; - FURI_LOG_D(TAG, "4K"); - break; - default: - mf_plus_data->size = MfPlusSizeUnknown; - FURI_LOG_D(TAG, "Unknown storage size"); - break; - } - - // Security level - if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) { - // Mifare Plus EV1/2 SL3 - mf_plus_data->security_level = MfPlusSecurityLevel3; - FURI_LOG_D(TAG, "Miare Plus EV1/2 SL3"); - } else { - // Mifare Plus EV1/2 SL1 - mf_plus_data->security_level = MfPlusSecurityLevel1; - FURI_LOG_D(TAG, "Miare Plus EV1/2 SL1"); - } + // Mifare Plus EV1/2 SL1 + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus EV1/2 SL1"); } } @@ -148,6 +134,24 @@ MfPlusError FURI_LOG_D(TAG, "Sak 08 but no known Mifare Plus type"); } + break; + case 0x10: + // Mifare Plus X 2K SL2 + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->size = MfPlusSize2K; + mf_plus_data->security_level = MfPlusSecurityLevel2; + FURI_LOG_D(TAG, "Mifare Plus X 2K SL2"); + error = MfPlusErrorNone; + + break; + case 0x11: + // Mifare Plus X 4K SL2 + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->size = MfPlusSize4K; + mf_plus_data->security_level = MfPlusSecurityLevel2; + FURI_LOG_D(TAG, "Mifare Plus X 4K SL2"); + error = MfPlusErrorNone; + break; case 0x18: if(memcmp( @@ -234,10 +238,22 @@ MfPlusError } MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) { - const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); + bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); if(can_parse) { bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion)); + } else if( + bit_buffer_get_size_bytes(buf) == 8 && + bit_buffer_get_byte(buf, 0) == MF_PLUS_STATUS_ADDITIONAL_FRAME) { + // HACK(-nofl): There are supposed to be three parts to the GetVersion command, + // with the second and third parts fetched by sending the AdditionalFrame + // command. I don't know whether the entire MIFARE Plus line uses status as + // the first byte, so let's just assume we only have the first part of + // the response if it's size 8 and starts with the AF status. The second + // part of the response is the same size and status byte, but so far + // we're only reading one response. + can_parse = true; + bit_buffer_write_bytes_mid(buf, data, 1, bit_buffer_get_size_bytes(buf) - 1); } return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.h b/lib/nfc/protocols/mf_plus/mf_plus_i.h index 1b80030f9..302f5a178 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.h +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.h @@ -4,6 +4,9 @@ #define MF_PLUS_FFF_PICC_PREFIX "PICC" +#define MF_PLUS_STATUS_OPERATION_OK (0x90) +#define MF_PLUS_STATUS_ADDITIONAL_FRAME (0xAF) + MfPlusError mf_plus_get_type_from_version( const Iso14443_4aData* iso14443_4a_data, MfPlusData* mf_plus_data); diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.c b/lib/nfc/protocols/mf_plus/mf_plus_poller.c index 8d1cc1c99..57a26a9cf 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.c @@ -146,7 +146,7 @@ static void mf_plus_poller_set_callback( static NfcCommand mf_plus_poller_run(NfcGenericEvent event, void* context) { furi_assert(context); - furi_assert(event.protocol = NfcProtocolIso14443_4a); + furi_assert(event.protocol == NfcProtocolIso14443_4a); furi_assert(event.event_data); MfPlusPoller* instance = context; @@ -178,7 +178,7 @@ void mf_plus_poller_free(MfPlusPoller* instance) { static bool mf_plus_poller_detect(NfcGenericEvent event, void* context) { furi_assert(context); - furi_assert(event.protocol = NfcProtocolIso14443_4a); + furi_assert(event.protocol == NfcProtocolIso14443_4a); furi_assert(event.event_data); MfPlusPoller* instance = context; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index c769273ec..82e647da8 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -37,7 +37,7 @@ MfUltralightError mf_ultralight_poller_auth_pwd( furi_check(data); uint8_t auth_cmd[5] = {MF_ULTRALIGHT_CMD_PWD_AUTH}; //-V1009 - memccpy(&auth_cmd[1], data->password.data, 0, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE); + memcpy(&auth_cmd[1], data->password.data, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE); bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd)); MfUltralightError ret = MfUltralightErrorNone; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c index 9958dc50d..252c46399 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.c @@ -23,6 +23,7 @@ typedef struct { FuriThreadId thread_id; MfUltralightError error; MfUltralightPollerContextData data; + const MfUltralightPollerAuthContext* auth_context; } MfUltralightPollerContext; typedef MfUltralightError (*MfUltralightPollerCmdHandler)( @@ -250,12 +251,17 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void poller_context->error = mfu_event->data->error; command = NfcCommandStop; } else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) { - mfu_event->data->auth_context.skip_auth = true; - if(mf_ultralight_support_feature( - mfu_poller->feature_set, MfUltralightFeatureSupportAuthenticate)) { - mfu_event->data->auth_context.skip_auth = false; - memset( - mfu_poller->auth_context.tdes_key.data, 0x00, MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + if(poller_context->auth_context != NULL) { + mfu_event->data->auth_context = *poller_context->auth_context; + } else { + mfu_event->data->auth_context.skip_auth = true; + if(mfu_poller->data->type == MfUltralightTypeMfulC) { + mfu_event->data->auth_context.skip_auth = false; + memset( + mfu_poller->auth_context.tdes_key.data, + 0x00, + MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + } } } @@ -266,13 +272,17 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void return command; } -MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data) { +MfUltralightError mf_ultralight_poller_sync_read_card( + Nfc* nfc, + MfUltralightData* data, + const MfUltralightPollerAuthContext* auth_context) { furi_check(nfc); furi_check(data); MfUltralightPollerContext poller_context = {}; poller_context.thread_id = furi_thread_get_current_id(); poller_context.data.data = mf_ultralight_alloc(); + poller_context.auth_context = auth_context; NfcPoller* poller = nfc_poller_alloc(nfc, NfcProtocolMfUltralight); nfc_poller_start(poller, mf_ultralight_poller_read_callback, &poller_context); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h index ac585aad7..3f63203a7 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h @@ -1,6 +1,7 @@ #pragma once #include "mf_ultralight.h" +#include "mf_ultralight_poller.h" #include #ifdef __cplusplus @@ -27,7 +28,10 @@ MfUltralightError mf_ultralight_poller_sync_read_tearing_flag( uint8_t flag_num, MfUltralightTearingFlag* data); -MfUltralightError mf_ultralight_poller_sync_read_card(Nfc* nfc, MfUltralightData* data); +MfUltralightError mf_ultralight_poller_sync_read_card( + Nfc* nfc, + MfUltralightData* data, + const MfUltralightPollerAuthContext* auth_context); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index 6a145445c..3f508a1fb 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -31,7 +31,7 @@ * When implementing a new protocol, add its implementation * here under its own index defined in nfc_protocol.h. */ -const NfcDeviceBase* nfc_devices[NfcProtocolNum] = { +const NfcDeviceBase* const nfc_devices[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_device_iso14443_3a, [NfcProtocolIso14443_3b] = &nfc_device_iso14443_3b, [NfcProtocolIso14443_4a] = &nfc_device_iso14443_4a, diff --git a/lib/nfc/protocols/nfc_device_defs.h b/lib/nfc/protocols/nfc_device_defs.h index f5ba2563f..e5d2707fd 100644 --- a/lib/nfc/protocols/nfc_device_defs.h +++ b/lib/nfc/protocols/nfc_device_defs.h @@ -6,7 +6,7 @@ extern "C" { #endif -extern const NfcDeviceBase* nfc_devices[]; +extern const NfcDeviceBase* const nfc_devices[]; #ifdef __cplusplus } diff --git a/lib/nfc/protocols/nfc_listener_defs.c b/lib/nfc/protocols/nfc_listener_defs.c index 2a6167e9c..c27079f5a 100644 --- a/lib/nfc/protocols/nfc_listener_defs.c +++ b/lib/nfc/protocols/nfc_listener_defs.c @@ -8,7 +8,7 @@ #include #include -const NfcListenerBase* nfc_listeners_api[NfcProtocolNum] = { +const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_listener_iso14443_3a, [NfcProtocolIso14443_3b] = NULL, [NfcProtocolIso14443_4a] = &nfc_listener_iso14443_4a, diff --git a/lib/nfc/protocols/nfc_listener_defs.h b/lib/nfc/protocols/nfc_listener_defs.h index 4d88cc098..7bd4b49b0 100644 --- a/lib/nfc/protocols/nfc_listener_defs.h +++ b/lib/nfc/protocols/nfc_listener_defs.h @@ -7,7 +7,7 @@ extern "C" { #endif -extern const NfcListenerBase* nfc_listeners_api[NfcProtocolNum]; +extern const NfcListenerBase* const nfc_listeners_api[NfcProtocolNum]; #ifdef __cplusplus } diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index c007740b7..f3ad15e7d 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -13,7 +13,7 @@ #include #include -const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { +const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolIso14443_3a] = &nfc_poller_iso14443_3a, [NfcProtocolIso14443_3b] = &nfc_poller_iso14443_3b, [NfcProtocolIso14443_4a] = &nfc_poller_iso14443_4a, diff --git a/lib/nfc/protocols/nfc_poller_defs.h b/lib/nfc/protocols/nfc_poller_defs.h index a406a5f08..0156cc8b2 100644 --- a/lib/nfc/protocols/nfc_poller_defs.h +++ b/lib/nfc/protocols/nfc_poller_defs.h @@ -7,7 +7,7 @@ extern "C" { #endif -extern const NfcPollerBase* nfc_pollers_api[NfcProtocolNum]; +extern const NfcPollerBase* const nfc_pollers_api[NfcProtocolNum]; #ifdef __cplusplus } diff --git a/lib/nfc/protocols/st25tb/st25tb_poller.c b/lib/nfc/protocols/st25tb/st25tb_poller.c index fd6dc4f09..674a439cd 100644 --- a/lib/nfc/protocols/st25tb/st25tb_poller.c +++ b/lib/nfc/protocols/st25tb/st25tb_poller.c @@ -88,7 +88,7 @@ static NfcCommand st25tb_poller_request_mode_handler(St25tbPoller* instance) { St25tbPollerEventDataModeRequest* mode_request_data = &instance->st25tb_event_data.mode_request; - furi_assert(mode_request_data->mode < St25tbPollerModeNum); + furi_check(mode_request_data->mode < St25tbPollerModeNum); if(mode_request_data->mode == St25tbPollerModeRead) { instance->state = St25tbPollerStateRead; diff --git a/lib/print/SConscript b/lib/print/SConscript index 07be8d890..90028cf06 100644 --- a/lib/print/SConscript +++ b/lib/print/SConscript @@ -44,18 +44,24 @@ wrapped_fn_list = [ "vsiprintf", "vsniprintf", # - # Scanf is not implemented 4 now + # standard input + # + "fgetc", + "getc", + "getchar", + "fgets", + "ungetc", + # + # standard input, but unimplemented + # + "gets", + # + # scanf, not implemented for now # # "fscanf", # "scanf", # "sscanf", # "vsprintf", - # "fgetc", - # "fgets", - # "getc", - # "getchar", - # "gets", - # "ungetc", # "vfscanf", # "vscanf", # "vsscanf", diff --git a/lib/print/wrappers.c b/lib/print/wrappers.c index c8d72d192..18df92def 100644 --- a/lib/print/wrappers.c +++ b/lib/print/wrappers.c @@ -51,11 +51,54 @@ int __wrap_snprintf(char* str, size_t size, const char* format, ...) { } int __wrap_fflush(FILE* stream) { - UNUSED(stream); - furi_thread_stdout_flush(); + if(stream == stdout) furi_thread_stdout_flush(); return 0; } +int __wrap_fgetc(FILE* stream) { + if(stream != stdin) return EOF; + char c; + if(furi_thread_stdin_read(&c, 1, FuriWaitForever) == 0) return EOF; + return c; +} + +int __wrap_getc(FILE* stream) { + return __wrap_fgetc(stream); +} + +int __wrap_getchar(void) { + return __wrap_fgetc(stdin); +} + +char* __wrap_fgets(char* str, size_t n, FILE* stream) { + // leave space for the zero terminator + furi_check(n >= 1); + n--; + + if(stream != stdin) { + *str = '\0'; + return str; + } + + // read characters + int c; + do { + c = __wrap_fgetc(stdin); + if(c > 0) *(str++) = c; + } while(c != EOF && c != '\n' && --n); + + // place zero terminator + *str = '\0'; + return str; +} + +int __wrap_ungetc(int ch, FILE* stream) { + char c = ch; + if(stream != stdin) return EOF; + furi_thread_stdin_unread(&c, 1); + return ch; +} + __attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e) { UNUSED(file); UNUSED(line); diff --git a/lib/print/wrappers.h b/lib/print/wrappers.h index 3cec88249..8a4599b41 100644 --- a/lib/print/wrappers.h +++ b/lib/print/wrappers.h @@ -16,6 +16,12 @@ int __wrap_putc(int ch, FILE* stream); int __wrap_snprintf(char* str, size_t size, const char* format, ...); int __wrap_fflush(FILE* stream); +int __wrap_fgetc(FILE* stream); +int __wrap_getc(FILE* stream); +int __wrap_getchar(void); +char* __wrap_fgets(char* str, size_t n, FILE* stream); +int __wrap_ungetc(int ch, FILE* stream); + __attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e); __attribute__((__noreturn__)) void diff --git a/lib/signal_reader/parsers/iso15693/iso15693_parser.c b/lib/signal_reader/parsers/iso15693/iso15693_parser.c index a2c6912e6..e47c734a2 100644 --- a/lib/signal_reader/parsers/iso15693/iso15693_parser.c +++ b/lib/signal_reader/parsers/iso15693/iso15693_parser.c @@ -243,6 +243,8 @@ static Iso15693ParserCommand iso15693_parser_parse_1_out_of_256(Iso15693Parser* instance->parsed_frame, instance->next_byte_part * 4 + j / 2); } } + } else { + instance->zero_found = true; } } instance->next_byte_part = (instance->next_byte_part + 1) % 64; diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 8298bce6b..e90f1508e 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -314,9 +314,10 @@ SubGhzProtocolStatus flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); if(!subghz_protocol_encoder_bin_raw_get_upload(instance)) { - break; res = SubGhzProtocolStatusErrorEncoderGetUpload; + break; } + instance->encoder.is_running = true; res = SubGhzProtocolStatusOk; diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 645f5eee1..bbe3e487f 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -14,12 +14,13 @@ #define TAG "SubGhzProtocolCame" -#define CAME_12_COUNT_BIT 12 -#define CAME_24_COUNT_BIT 24 -#define PRASTEL_COUNT_BIT 25 -#define PRASTEL_NAME "Prastel" -#define AIRFORCE_COUNT_BIT 18 -#define AIRFORCE_NAME "Airforce" +#define CAME_12_COUNT_BIT 12 +#define CAME_24_COUNT_BIT 24 +#define PRASTEL_25_COUNT_BIT 25 +#define PRASTEL_42_COUNT_BIT 42 +#define PRASTEL_NAME "Prastel" +#define AIRFORCE_COUNT_BIT 18 +#define AIRFORCE_NAME "Airforce" static const SubGhzBlockConst subghz_protocol_came_const = { .te_short = 320, @@ -123,6 +124,7 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i switch(instance->generic.data_count_bit) { case CAME_24_COUNT_BIT: + case PRASTEL_42_COUNT_BIT: // CAME 24 Bit = 24320 us header_te = 76; break; @@ -131,7 +133,7 @@ static bool subghz_protocol_encoder_came_get_upload(SubGhzProtocolEncoderCame* i // CAME 12 Bit Original only! and Airforce protocol = 15040 us header_te = 47; break; - case PRASTEL_COUNT_BIT: + case PRASTEL_25_COUNT_BIT: // PRASTEL = 11520 us header_te = 36; break; @@ -174,7 +176,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - if(instance->generic.data_count_bit > PRASTEL_COUNT_BIT) { + if(instance->generic.data_count_bit > PRASTEL_42_COUNT_BIT) { FURI_LOG_E(TAG, "Wrong number of bits in key"); ret = SubGhzProtocolStatusErrorValueBitCount; break; @@ -268,7 +270,8 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat if((instance->decoder.decode_count_bit == subghz_protocol_came_const.min_count_bit_for_found) || (instance->decoder.decode_count_bit == AIRFORCE_COUNT_BIT) || - (instance->decoder.decode_count_bit == PRASTEL_COUNT_BIT) || + (instance->decoder.decode_count_bit == PRASTEL_25_COUNT_BIT) || + (instance->decoder.decode_count_bit == PRASTEL_42_COUNT_BIT) || (instance->decoder.decode_count_bit == CAME_24_COUNT_BIT)) { instance->generic.serial = 0x0; instance->generic.btn = 0x0; @@ -337,7 +340,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - if(instance->generic.data_count_bit > PRASTEL_COUNT_BIT) { + if(instance->generic.data_count_bit > PRASTEL_42_COUNT_BIT) { FURI_LOG_E(TAG, "Wrong number of bits in key"); ret = SubGhzProtocolStatusErrorValueBitCount; break; @@ -350,23 +353,30 @@ void subghz_protocol_decoder_came_get_string(void* context, FuriString* output) furi_assert(context); SubGhzProtocolDecoderCame* instance = context; - uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + uint32_t code_found_lo = instance->generic.data & 0x000003ffffffffff; uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( instance->generic.data, instance->generic.data_count_bit); - uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + uint32_t code_found_reverse_lo = code_found_reverse & 0x000003ffffffffff; + + const char* name = instance->generic.protocol_name; + switch(instance->generic.data_count_bit) { + case PRASTEL_25_COUNT_BIT: + case PRASTEL_42_COUNT_BIT: + name = PRASTEL_NAME; + break; + case AIRFORCE_COUNT_BIT: + name = AIRFORCE_NAME; + break; + } furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%08lX\r\n" "Yek:0x%08lX\r\n", - (instance->generic.data_count_bit == PRASTEL_COUNT_BIT ? - PRASTEL_NAME : - (instance->generic.data_count_bit == AIRFORCE_COUNT_BIT ? - AIRFORCE_NAME : - instance->generic.protocol_name)), + name, instance->generic.data_count_bit, code_found_lo, code_found_reverse_lo); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 0d9c2a088..d7a14cd1c 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -1,6 +1,6 @@ #include "protocol_items.h" // IWYU pragma: keep -const SubGhzProtocol* subghz_protocol_registry_items[] = { +const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_gate_tx, &subghz_protocol_keeloq, &subghz_protocol_star_line, diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index f878ecb01..47cc100b3 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -228,7 +228,7 @@ static bool subghz_protocol_secplus_v1_encode(SubGhzProtocolEncoderSecPlus_v1* i rolling = 0xE6000000; } if(fixed > 0xCFD41B90) { - FURI_LOG_E("TAG", "Encode wrong fixed data"); + FURI_LOG_E(TAG, "Encode wrong fixed data"); return false; } diff --git a/lib/subghz/registry.h b/lib/subghz/registry.h index 8529c1097..8de376b16 100644 --- a/lib/subghz/registry.h +++ b/lib/subghz/registry.h @@ -12,7 +12,7 @@ typedef struct SubGhzProtocolRegistry SubGhzProtocolRegistry; typedef struct SubGhzProtocol SubGhzProtocol; struct SubGhzProtocolRegistry { - const SubGhzProtocol** items; + const SubGhzProtocol* const* items; const size_t size; }; diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 03b8999c4..ad368e2a1 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -12,6 +12,10 @@ env.Append( ], SDK_HEADERS=[ File("api_lock.h"), + File("cli/cli_ansi.h"), + File("cli/cli_command.h"), + File("cli/cli_registry.h"), + File("cli/shell/cli_shell.h"), File("compress.h"), File("manchester_decoder.h"), File("manchester_encoder.h"), @@ -30,6 +34,7 @@ env.Append( File("stream/string_stream.h"), File("stream/buffered_file_stream.h"), File("strint.h"), + File("pipe.h"), File("protocols/protocol_dict.h"), File("pretty_format.h"), File("hex.h"), diff --git a/lib/toolbox/cli/cli_ansi.c b/lib/toolbox/cli/cli_ansi.c new file mode 100644 index 000000000..81167643b --- /dev/null +++ b/lib/toolbox/cli/cli_ansi.c @@ -0,0 +1,123 @@ +#include "cli_ansi.h" + +typedef enum { + CliAnsiParserStateInitial, + CliAnsiParserStateEscape, + CliAnsiParserStateEscapeBrace, + CliAnsiParserStateEscapeBraceOne, + CliAnsiParserStateEscapeBraceOneSemicolon, + CliAnsiParserStateEscapeBraceOneSemicolonModifiers, +} CliAnsiParserState; + +struct CliAnsiParser { + CliAnsiParserState state; + CliModKey modifiers; +}; + +CliAnsiParser* cli_ansi_parser_alloc(void) { + CliAnsiParser* parser = malloc(sizeof(CliAnsiParser)); + return parser; +} + +void cli_ansi_parser_free(CliAnsiParser* parser) { + free(parser); +} + +/** + * @brief Converts a single character representing a special key into the enum + * representation + */ +static CliKey cli_ansi_key_from_mnemonic(char c) { + switch(c) { + case 'A': + return CliKeyUp; + case 'B': + return CliKeyDown; + case 'C': + return CliKeyRight; + case 'D': + return CliKeyLeft; + case 'F': + return CliKeyEnd; + case 'H': + return CliKeyHome; + default: + return CliKeyUnrecognized; + } +} + +#define PARSER_RESET_AND_RETURN(parser, modifiers_val, key_val) \ + do { \ + parser->state = CliAnsiParserStateInitial; \ + return (CliAnsiParserResult){ \ + .is_done = true, \ + .result = (CliKeyCombo){ \ + .modifiers = modifiers_val, \ + .key = key_val, \ + }}; \ + } while(0); + +CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c) { + switch(parser->state) { + case CliAnsiParserStateInitial: + // -> + if(c != CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c); // -V1048 + + // ... + parser->state = CliAnsiParserStateEscape; + break; + + case CliAnsiParserStateEscape: + // -> + if(c == CliKeyEsc) PARSER_RESET_AND_RETURN(parser, CliModKeyNo, c); + + // -> Alt + + if(c != '[') PARSER_RESET_AND_RETURN(parser, CliModKeyAlt, c); + + // [ ... + parser->state = CliAnsiParserStateEscapeBrace; + break; + + case CliAnsiParserStateEscapeBrace: + // [ -> + if(c != '1') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, cli_ansi_key_from_mnemonic(c)); + + // [ 1 ... + parser->state = CliAnsiParserStateEscapeBraceOne; + break; + + case CliAnsiParserStateEscapeBraceOne: + // [ 1 -> error + if(c != ';') PARSER_RESET_AND_RETURN(parser, CliModKeyNo, CliKeyUnrecognized); + + // [ 1 ; ... + parser->state = CliAnsiParserStateEscapeBraceOneSemicolon; + break; + + case CliAnsiParserStateEscapeBraceOneSemicolon: + // [ 1 ; ... + parser->modifiers = (c - '0'); + parser->modifiers &= ~1; + parser->state = CliAnsiParserStateEscapeBraceOneSemicolonModifiers; + break; + + case CliAnsiParserStateEscapeBraceOneSemicolonModifiers: + // [ 1 ; -> + + PARSER_RESET_AND_RETURN(parser, parser->modifiers, cli_ansi_key_from_mnemonic(c)); + } + + return (CliAnsiParserResult){.is_done = false}; +} + +CliAnsiParserResult cli_ansi_parser_feed_timeout(CliAnsiParser* parser) { + CliAnsiParserResult result = {.is_done = false}; + + if(parser->state == CliAnsiParserStateEscape) { + result.is_done = true; + result.result.key = CliKeyEsc; + result.result.modifiers = CliModKeyNo; + } + + parser->state = CliAnsiParserStateInitial; + return result; +} diff --git a/lib/toolbox/cli/cli_ansi.h b/lib/toolbox/cli/cli_ansi.h new file mode 100644 index 000000000..04b6d7759 --- /dev/null +++ b/lib/toolbox/cli/cli_ansi.h @@ -0,0 +1,153 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// text styling + +#define ANSI_RESET "\e[0m" +#define ANSI_BOLD "\e[1m" +#define ANSI_FAINT "\e[2m" +#define ANSI_INVERT "\e[7m" + +#define ANSI_FG_BLACK "\e[30m" +#define ANSI_FG_RED "\e[31m" +#define ANSI_FG_GREEN "\e[32m" +#define ANSI_FG_YELLOW "\e[33m" +#define ANSI_FG_BLUE "\e[34m" +#define ANSI_FG_MAGENTA "\e[35m" +#define ANSI_FG_CYAN "\e[36m" +#define ANSI_FG_WHITE "\e[37m" +#define ANSI_FG_BR_BLACK "\e[90m" +#define ANSI_FG_BR_RED "\e[91m" +#define ANSI_FG_BR_GREEN "\e[92m" +#define ANSI_FG_BR_YELLOW "\e[93m" +#define ANSI_FG_BR_BLUE "\e[94m" +#define ANSI_FG_BR_MAGENTA "\e[95m" +#define ANSI_FG_BR_CYAN "\e[96m" +#define ANSI_FG_BR_WHITE "\e[97m" + +#define ANSI_BG_BLACK "\e[40m" +#define ANSI_BG_RED "\e[41m" +#define ANSI_BG_GREEN "\e[42m" +#define ANSI_BG_YELLOW "\e[43m" +#define ANSI_BG_BLUE "\e[44m" +#define ANSI_BG_MAGENTA "\e[45m" +#define ANSI_BG_CYAN "\e[46m" +#define ANSI_BG_WHITE "\e[47m" +#define ANSI_BG_BR_BLACK "\e[100m" +#define ANSI_BG_BR_RED "\e[101m" +#define ANSI_BG_BR_GREEN "\e[102m" +#define ANSI_BG_BR_YELLOW "\e[103m" +#define ANSI_BG_BR_BLUE "\e[104m" +#define ANSI_BG_BR_MAGENTA "\e[105m" +#define ANSI_BG_BR_CYAN "\e[106m" +#define ANSI_BG_BR_WHITE "\e[107m" + +#define ANSI_FLIPPER_BRAND_ORANGE "\e[38;2;255;130;0m" + +// cursor positioning + +#define ANSI_CURSOR_UP_BY(rows) "\e[" rows "A" +#define ANSI_CURSOR_DOWN_BY(rows) "\e[" rows "B" +#define ANSI_CURSOR_RIGHT_BY(cols) "\e[" cols "C" +#define ANSI_CURSOR_LEFT_BY(cols) "\e[" cols "D" +#define ANSI_CURSOR_DOWN_BY_AND_FIRST_COLUMN(rows) "\e[" rows "E" +#define ANSI_CURSOR_UP_BY_AND_FIRST_COLUMN(rows) "\e[" rows "F" +#define ANSI_CURSOR_HOR_POS(pos) "\e[" pos "G" +#define ANSI_CURSOR_POS(row, col) "\e[" row ";" col "H" + +// erasing + +#define ANSI_ERASE_FROM_CURSOR_TO_END "0" +#define ANSI_ERASE_FROM_START_TO_CURSOR "1" +#define ANSI_ERASE_ENTIRE "2" + +#define ANSI_ERASE_DISPLAY(portion) "\e[" portion "J" +#define ANSI_ERASE_LINE(portion) "\e[" portion "K" +#define ANSI_ERASE_SCROLLBACK_BUFFER ANSI_ERASE_DISPLAY("3") + +// misc + +#define ANSI_INSERT_MODE_ENABLE "\e[4h" +#define ANSI_INSERT_MODE_DISABLE "\e[4l" + +typedef enum { + CliKeyUnrecognized = 0, + + CliKeySOH = 0x01, + CliKeyETX = 0x03, + CliKeyEOT = 0x04, + CliKeyBell = 0x07, + CliKeyBackspace = 0x08, + CliKeyTab = 0x09, + CliKeyLF = 0x0A, + CliKeyFF = 0x0C, + CliKeyCR = 0x0D, + CliKeyETB = 0x17, + CliKeyEsc = 0x1B, + CliKeyUS = 0x1F, + CliKeySpace = 0x20, + CliKeyDEL = 0x7F, + + CliKeySpecial = 0x80, + CliKeyLeft, + CliKeyRight, + CliKeyUp, + CliKeyDown, + CliKeyHome, + CliKeyEnd, +} CliKey; + +typedef enum { + CliModKeyNo = 0, + CliModKeyAlt = 2, + CliModKeyCtrl = 4, + CliModKeyMeta = 8, +} CliModKey; + +typedef struct { + CliModKey modifiers; + CliKey key; +} CliKeyCombo; + +typedef struct CliAnsiParser CliAnsiParser; + +typedef struct { + bool is_done; + CliKeyCombo result; +} CliAnsiParserResult; + +/** + * @brief Allocates an ANSI parser + */ +CliAnsiParser* cli_ansi_parser_alloc(void); + +/** + * @brief Frees an ANSI parser + */ +void cli_ansi_parser_free(CliAnsiParser* parser); + +/** + * @brief Feeds an ANSI parser a character + */ +CliAnsiParserResult cli_ansi_parser_feed(CliAnsiParser* parser, char c); + +/** + * @brief Feeds an ANSI parser a timeout event + * + * As a user of the ANSI parser API, you are responsible for calling this + * function some time after the last character was fed into the parser. The + * recommended timeout is about 10 ms. The exact value does not matter as long + * as it is small enough for the user not notice a delay, but big enough that + * when a terminal is sending an escape sequence, this function does not get + * called in between the characters of the sequence. + */ +CliAnsiParserResult cli_ansi_parser_feed_timeout(CliAnsiParser* parser); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/cli/cli_command.c b/lib/toolbox/cli/cli_command.c new file mode 100644 index 000000000..a3c9ff292 --- /dev/null +++ b/lib/toolbox/cli/cli_command.c @@ -0,0 +1,17 @@ +#include "cli_command.h" +#include "cli_ansi.h" + +bool cli_is_pipe_broken_or_is_etx_next_char(PipeSide* side) { + if(pipe_state(side) == PipeStateBroken) return true; + if(!pipe_bytes_available(side)) return false; + char c = getchar(); + return c == CliKeyETX; +} + +void cli_print_usage(const char* cmd, const char* usage, const char* arg) { + furi_check(cmd); + furi_check(arg); + furi_check(usage); + + printf("%s: illegal option -- %s\r\nusage: %s %s", cmd, arg, cmd, usage); +} diff --git a/lib/toolbox/cli/cli_command.h b/lib/toolbox/cli/cli_command.h new file mode 100644 index 000000000..2d1d851d6 --- /dev/null +++ b/lib/toolbox/cli/cli_command.h @@ -0,0 +1,103 @@ +/** + * @file cli_command.h + * Command metadata and helpers + */ + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLI_PLUGIN_API_VERSION 1 + +typedef enum { + CliCommandFlagDefault = 0, /**< Default */ + CliCommandFlagParallelSafe = (1 << 0), /**< Safe to run in parallel with other apps */ + CliCommandFlagInsomniaSafe = (1 << 1), /**< Safe to run with insomnia mode on */ + CliCommandFlagDontAttachStdio = (1 << 2), /**< Do no attach I/O pipe to thread stdio */ + CliCommandFlagUseShellThread = + (1 + << 3), /**< Don't start a separate thread to run the command in. Incompatible with DontAttachStdio */ + + // internal flags (do not set them yourselves!) + + CliCommandFlagExternal = (1 << 4), /**< The command comes from a .fal file */ +} CliCommandFlag; + +/** + * @brief CLI command execution callback pointer + * + * This callback will be called from a separate thread spawned just for your + * command. The pipe will be installed as the thread's stdio, so you can use + * `printf`, `getchar` and other standard functions to communicate with the + * user. + * + * @param [in] pipe Pipe that can be used to send and receive data. If + * `CliCommandFlagDontAttachStdio` was not set, you can + * also use standard C functions (printf, getc, etc.) to + * access this pipe. + * @param [in] args String with what was passed after the command + * @param [in] context Whatever you provided to `cli_add_command` + */ +typedef void (*CliCommandExecuteCallback)(PipeSide* pipe, FuriString* args, void* context); + +typedef struct { + char* name; + CliCommandExecuteCallback execute_callback; + CliCommandFlag flags; + size_t stack_depth; +} CliCommandDescriptor; + +/** + * @brief Configuration for locating external commands + */ +typedef struct { + const char* search_directory; // +#include + +#define TAG "CliRegistry" + +struct CliRegistry { + CliCommandDict_t commands; + FuriMutex* mutex; +}; + +CliRegistry* cli_registry_alloc(void) { + CliRegistry* registry = malloc(sizeof(CliRegistry)); + CliCommandDict_init(registry->commands); + registry->mutex = furi_mutex_alloc(FuriMutexTypeRecursive); + return registry; +} + +void cli_registry_free(CliRegistry* registry) { + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + furi_mutex_free(registry->mutex); + CliCommandDict_clear(registry->commands); + free(registry); +} + +void cli_registry_add_command( + CliRegistry* registry, + const char* name, + CliCommandFlag flags, + CliCommandExecuteCallback callback, + void* context) { + cli_registry_add_command_ex( + registry, name, flags, callback, context, CLI_BUILTIN_COMMAND_STACK_SIZE); +} + +void cli_registry_add_command_ex( + CliRegistry* registry, + const char* name, + CliCommandFlag flags, + CliCommandExecuteCallback callback, + void* context, + size_t stack_size) { + furi_check(registry); + furi_check(name); + furi_check(callback); + + // the shell always attaches the pipe to the stdio, thus both flags can't be used at once + if(flags & CliCommandFlagUseShellThread) furi_check(!(flags & CliCommandFlagDontAttachStdio)); + + FuriString* name_str; + name_str = furi_string_alloc_set(name); + // command cannot contain spaces + furi_check(furi_string_search_char(name_str, ' ') == FURI_STRING_FAILURE); + + CliRegistryCommand command = { + .context = context, + .execute_callback = callback, + .flags = flags, + .stack_depth = stack_size, + }; + + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + CliCommandDict_set_at(registry->commands, name_str, command); + furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); + + furi_string_free(name_str); +} + +void cli_registry_delete_command(CliRegistry* registry, const char* name) { + furi_check(registry); + FuriString* name_str; + name_str = furi_string_alloc_set(name); + furi_string_trim(name_str); + + size_t name_replace; + do { + name_replace = furi_string_replace(name_str, " ", "_"); + } while(name_replace != FURI_STRING_FAILURE); + + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + CliCommandDict_erase(registry->commands, name_str); + furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); + + furi_string_free(name_str); +} + +bool cli_registry_get_command( + CliRegistry* registry, + FuriString* command, + CliRegistryCommand* result) { + furi_assert(registry); + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + CliRegistryCommand* data = CliCommandDict_get(registry->commands, command); + if(data) *result = *data; + + furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); + + return !!data; +} + +void cli_registry_remove_external_commands(CliRegistry* registry) { + furi_check(registry); + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + + CliCommandDict_t internal_cmds; + CliCommandDict_init(internal_cmds); + for + M_EACH(item, registry->commands, CliCommandDict_t) { + if(!(item->value.flags & CliCommandFlagExternal)) + CliCommandDict_set_at(internal_cmds, item->key, item->value); + } + CliCommandDict_move(registry->commands, internal_cmds); + + furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); +} + +void cli_registry_reload_external_commands( + CliRegistry* registry, + const CliCommandExternalConfig* config) { + furi_check(registry); + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); + FURI_LOG_D(TAG, "Reloading ext commands"); + + cli_registry_remove_external_commands(registry); + + // iterate over files in plugin directory + Storage* storage = furi_record_open(RECORD_STORAGE); + File* plugin_dir = storage_file_alloc(storage); + + if(storage_dir_open(plugin_dir, config->search_directory)) { + char plugin_filename[64]; + FuriString* plugin_name = furi_string_alloc(); + + while(storage_dir_read(plugin_dir, NULL, plugin_filename, sizeof(plugin_filename))) { + FURI_LOG_T(TAG, "Plugin: %s", plugin_filename); + furi_string_set_str(plugin_name, plugin_filename); + + furi_check(furi_string_end_with_str(plugin_name, ".fal")); + furi_string_replace_all_str(plugin_name, ".fal", ""); + furi_check(furi_string_start_with_str(plugin_name, config->fal_prefix)); + furi_string_replace_at(plugin_name, 0, strlen(config->fal_prefix), ""); + + CliRegistryCommand command = { + .context = NULL, + .execute_callback = NULL, + .flags = CliCommandFlagExternal, + }; + CliCommandDict_set_at(registry->commands, plugin_name, command); + } + + furi_string_free(plugin_name); + } + + storage_dir_close(plugin_dir); + storage_file_free(plugin_dir); + furi_record_close(RECORD_STORAGE); + + FURI_LOG_D(TAG, "Done reloading ext commands"); + furi_check(furi_mutex_release(registry->mutex) == FuriStatusOk); +} + +void cli_registry_lock(CliRegistry* registry) { + furi_assert(registry); + furi_check(furi_mutex_acquire(registry->mutex, FuriWaitForever) == FuriStatusOk); +} + +void cli_registry_unlock(CliRegistry* registry) { + furi_assert(registry); + furi_mutex_release(registry->mutex); +} + +CliCommandDict_t* cli_registry_get_commands(CliRegistry* registry) { + furi_assert(registry); + return ®istry->commands; +} diff --git a/lib/toolbox/cli/cli_registry.h b/lib/toolbox/cli/cli_registry.h new file mode 100644 index 000000000..44650e79b --- /dev/null +++ b/lib/toolbox/cli/cli_registry.h @@ -0,0 +1,92 @@ +/** + * @file cli_registry.h + * API for registering commands with a CLI shell + */ + +#pragma once + +#include +#include +#include +#include "cli_command.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CliRegistry CliRegistry; + +/** + * @brief Allocates a `CliRegistry`. + */ +CliRegistry* cli_registry_alloc(void); + +/** + * @brief Frees a `CliRegistry`. + */ +void cli_registry_free(CliRegistry* registry); + +/** + * @brief Registers a command with the registry. Provides less options than the + * `_ex` counterpart. + * + * @param [in] registry Pointer to registry instance + * @param [in] name Command name + * @param [in] flags see CliCommandFlag + * @param [in] callback Callback function + * @param [in] context Custom context + */ +void cli_registry_add_command( + CliRegistry* registry, + const char* name, + CliCommandFlag flags, + CliCommandExecuteCallback callback, + void* context); + +/** + * @brief Registers a command with the registry. Provides more options than the + * non-`_ex` counterpart. + * + * @param [in] registry Pointer to registry instance + * @param [in] name Command name + * @param [in] flags see CliCommandFlag + * @param [in] callback Callback function + * @param [in] context Custom context + * @param [in] stack_size Thread stack size + */ +void cli_registry_add_command_ex( + CliRegistry* registry, + const char* name, + CliCommandFlag flags, + CliCommandExecuteCallback callback, + void* context, + size_t stack_size); + +/** + * @brief Deletes a cli command + * + * @param [in] registry Pointer to registry instance + * @param [in] name Command name + */ +void cli_registry_delete_command(CliRegistry* registry, const char* name); + +/** + * @brief Unregisters all external commands + * + * @param [in] registry Pointer to registry instance + */ +void cli_registry_remove_external_commands(CliRegistry* registry); + +/** + * @brief Reloads the list of externally available commands + * + * @param [in] registry Pointer to registry instance + * @param [in] config See `CliCommandExternalConfig` + */ +void cli_registry_reload_external_commands( + CliRegistry* registry, + const CliCommandExternalConfig* config); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/cli/cli_registry_i.h b/lib/toolbox/cli/cli_registry_i.h new file mode 100644 index 000000000..31995832f --- /dev/null +++ b/lib/toolbox/cli/cli_registry_i.h @@ -0,0 +1,45 @@ +/** + * @file cli_registry_i.h + * Internal API for getting commands registered with the CLI + */ + +#pragma once + +#include +#include +#include "cli_registry.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLI_BUILTIN_COMMAND_STACK_SIZE (4 * 1024U) + +typedef struct { + void* context; // +#include +#include +#include +#include +#include +#include +#include + +#define TAG "CliShell" + +#define ANSI_TIMEOUT_MS 10 + +typedef enum { + CliShellComponentCompletions, + CliShellComponentLine, + CliShellComponentMAX, //pipe); + pipe_attach_to_event_loop(cli_shell->pipe, cli_shell->event_loop); + pipe_set_callback_context(cli_shell->pipe, cli_shell); + pipe_set_data_arrived_callback(cli_shell->pipe, cli_shell_data_available, 0); + pipe_set_broken_callback(cli_shell->pipe, cli_shell_pipe_broken, 0); +} + +static void cli_shell_detach_pipe(CliShell* cli_shell) { + pipe_detach_from_event_loop(cli_shell->pipe); + furi_thread_set_stdin_callback(NULL, NULL); + furi_thread_set_stdout_callback(NULL, NULL); +} + +// ================= +// Built-in commands +// ================= + +void cli_command_reload_external(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + CliShell* shell = context; + furi_check(shell->ext_config); + cli_registry_reload_external_commands(shell->registry, shell->ext_config); + printf("OK!"); +} + +void cli_command_help(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + CliShell* shell = context; + CliRegistry* registry = shell->registry; + + const size_t columns = 3; + + printf("Available commands:\r\n" ANSI_FG_GREEN); + cli_registry_lock(registry); + CliCommandDict_t* commands = cli_registry_get_commands(registry); + size_t commands_count = CliCommandDict_size(*commands); + + CliCommandDict_it_t iterator; + CliCommandDict_it(iterator, *commands); + for(size_t i = 0; i < commands_count; i++) { + const CliCommandDict_itref_t* item = CliCommandDict_cref(iterator); + printf("%-30s", furi_string_get_cstr(item->key)); + CliCommandDict_next(iterator); + + if(i % columns == columns - 1) printf("\r\n"); + } + + if(shell->ext_config) + printf( + ANSI_RESET + "\r\nIf you added a new external command and can't see it above, run `reload_ext_cmds`"); + printf(ANSI_RESET "\r\nFind out more: https://docs.flipper.net/development/cli"); + + cli_registry_unlock(registry); +} + +void cli_command_exit(PipeSide* pipe, FuriString* args, void* context) { + UNUSED(pipe); + UNUSED(args); + CliShell* shell = context; + cli_shell_line_set_about_to_exit(shell->components[CliShellComponentLine]); + furi_event_loop_stop(shell->event_loop); +} + +// ================== +// Internal functions +// ================== + +static int32_t cli_command_thread(void* context) { + CliCommandThreadData* thread_data = context; + if(!(thread_data->command->flags & CliCommandFlagDontAttachStdio)) + pipe_install_as_stdio(thread_data->pipe); + + thread_data->command->execute_callback( + thread_data->pipe, thread_data->args, thread_data->command->context); + + fflush(stdout); + return 0; +} + +void cli_shell_execute_command(CliShell* cli_shell, FuriString* command) { + // split command into command and args + size_t space = furi_string_search_char(command, ' '); + if(space == FURI_STRING_FAILURE) space = furi_string_size(command); + FuriString* command_name = furi_string_alloc_set(command); + furi_string_left(command_name, space); + FuriString* args = furi_string_alloc_set(command); + furi_string_right(args, space + 1); + + PluginManager* plugin_manager = NULL; + Loader* loader = furi_record_open(RECORD_LOADER); + bool loader_locked = false; + CliRegistryCommand command_data; + + do { + // find handler + if(!cli_registry_get_command(cli_shell->registry, command_name, &command_data)) { + printf( + ANSI_FG_RED "could not find command `%s`, try `help`" ANSI_RESET, + furi_string_get_cstr(command_name)); + break; + } + + // load external command + if(command_data.flags & CliCommandFlagExternal) { + const CliCommandExternalConfig* ext_config = cli_shell->ext_config; + plugin_manager = plugin_manager_alloc( + ext_config->appid, CLI_PLUGIN_API_VERSION, firmware_api_interface); + FuriString* path = furi_string_alloc_printf( + "%s/%s%s.fal", + ext_config->search_directory, + ext_config->fal_prefix, + furi_string_get_cstr(command_name)); + uint32_t plugin_cnt_last = plugin_manager_get_count(plugin_manager); + PluginManagerError error = + plugin_manager_load_single(plugin_manager, furi_string_get_cstr(path)); + furi_string_free(path); + + if(error != PluginManagerErrorNone) { + printf(ANSI_FG_RED "failed to load external command" ANSI_RESET); + break; + } + + const CliCommandDescriptor* plugin = + plugin_manager_get_ep(plugin_manager, plugin_cnt_last); + furi_assert(plugin); + furi_check(furi_string_cmp_str(command_name, plugin->name) == 0); + command_data.execute_callback = plugin->execute_callback; + command_data.flags = plugin->flags | CliCommandFlagExternal; + command_data.stack_depth = plugin->stack_depth; + + // external commands have to run in an external thread + furi_check(!(command_data.flags & CliCommandFlagUseShellThread)); + } + + // lock loader + if(!(command_data.flags & CliCommandFlagParallelSafe)) { + loader_locked = loader_lock(loader); + if(!loader_locked) { + printf(ANSI_FG_RED + "this command cannot be run while an application is open" ANSI_RESET); + break; + } + } + + if(command_data.flags & CliCommandFlagUseShellThread) { + // run command in this thread + command_data.execute_callback(cli_shell->pipe, args, command_data.context); + } else { + // run command in separate thread + cli_shell_detach_pipe(cli_shell); + CliCommandThreadData thread_data = { + .command = &command_data, + .pipe = cli_shell->pipe, + .args = args, + }; + FuriThread* thread = furi_thread_alloc_ex( + furi_string_get_cstr(command_name), + command_data.stack_depth, + cli_command_thread, + &thread_data); + furi_thread_start(thread); + furi_thread_join(thread); + furi_thread_free(thread); + cli_shell_install_pipe(cli_shell); + } + } while(0); + + furi_string_free(command_name); + furi_string_free(args); + + // unlock loader + if(loader_locked) loader_unlock(loader); + furi_record_close(RECORD_LOADER); + + // unload external command + if(plugin_manager) plugin_manager_free(plugin_manager); +} + +const char* cli_shell_get_prompt(CliShell* cli_shell) { + return cli_shell->prompt; +} + +// ============== +// Event handlers +// ============== + +static void cli_shell_signal_storage_event(CliShell* cli_shell, CliShellStorageEvent event) { + furi_check(!(furi_event_flag_set(cli_shell->storage.event_flag, event) & FuriFlagError)); +} + +static void cli_shell_storage_event(const void* message, void* context) { + CliShell* cli_shell = context; + const StorageEvent* event = message; + + if(event->type == StorageEventTypeCardMount) { + cli_shell_signal_storage_event(cli_shell, CliShellStorageEventMount); + } else if(event->type == StorageEventTypeCardUnmount) { + cli_shell_signal_storage_event(cli_shell, CliShellStorageEventUnmount); + } +} + +static void cli_shell_storage_internal_event(FuriEventLoopObject* object, void* context) { + CliShell* cli_shell = context; + FuriEventFlag* event_flag = object; + CliShellStorageEvent event = + furi_event_flag_wait(event_flag, FuriFlagWaitAll, FuriFlagWaitAny, 0); + furi_check(!(event & FuriFlagError)); + + if(event & CliShellStorageEventUnmount) { + cli_registry_remove_external_commands(cli_shell->registry); + } else if(event & CliShellStorageEventMount) { + cli_registry_reload_external_commands(cli_shell->registry, cli_shell->ext_config); + } else { + furi_crash(); + } +} + +static void + cli_shell_process_parser_result(CliShell* cli_shell, CliAnsiParserResult parse_result) { + if(!parse_result.is_done) return; + CliKeyCombo key_combo = parse_result.result; + if(key_combo.key == CliKeyUnrecognized) return; + + for(size_t i = 0; i < CliShellComponentMAX; i++) { // -V1008 + CliShellKeyComboSet* set = component_key_combo_sets[i]; + void* component_context = cli_shell->components[i]; + + for(size_t j = 0; j < set->count; j++) { + if(set->records[j].combo.modifiers == key_combo.modifiers && + set->records[j].combo.key == key_combo.key) + if(set->records[j].action(key_combo, component_context)) return; + } + + if(set->fallback) + if(set->fallback(key_combo, component_context)) return; + } +} + +static void cli_shell_pipe_broken(PipeSide* pipe, void* context) { + // allow commands to be processed before we stop the shell + if(pipe_bytes_available(pipe)) return; + + CliShell* cli_shell = context; + furi_event_loop_stop(cli_shell->event_loop); +} + +static void cli_shell_data_available(PipeSide* pipe, void* context) { + UNUSED(pipe); + CliShell* cli_shell = context; + + furi_event_loop_timer_start(cli_shell->ansi_parsing_timer, furi_ms_to_ticks(ANSI_TIMEOUT_MS)); + + // process ANSI escape sequences + int c = getchar(); + furi_assert(c >= 0); + cli_shell_process_parser_result(cli_shell, cli_ansi_parser_feed(cli_shell->ansi_parser, c)); +} + +static void cli_shell_timer_expired(void* context) { + CliShell* cli_shell = context; + cli_shell_process_parser_result( + cli_shell, cli_ansi_parser_feed_timeout(cli_shell->ansi_parser)); +} + +// =========== +// Thread code +// =========== + +static void cli_shell_init(CliShell* shell) { + cli_registry_add_command( + shell->registry, + "help", + CliCommandFlagUseShellThread | CliCommandFlagParallelSafe, + cli_command_help, + shell); + cli_registry_add_command( + shell->registry, + "?", + CliCommandFlagUseShellThread | CliCommandFlagParallelSafe, + cli_command_help, + shell); + cli_registry_add_command( + shell->registry, + "exit", + CliCommandFlagUseShellThread | CliCommandFlagParallelSafe, + cli_command_exit, + shell); + + if(shell->ext_config) { + cli_registry_add_command( + shell->registry, + "reload_ext_cmds", + CliCommandFlagUseShellThread, + cli_command_reload_external, + shell); + cli_registry_reload_external_commands(shell->registry, shell->ext_config); + } + + shell->components[CliShellComponentLine] = cli_shell_line_alloc(shell); + shell->components[CliShellComponentCompletions] = cli_shell_completions_alloc( + shell->registry, shell, shell->components[CliShellComponentLine]); + + shell->ansi_parser = cli_ansi_parser_alloc(); + + shell->event_loop = furi_event_loop_alloc(); + shell->ansi_parsing_timer = furi_event_loop_timer_alloc( + shell->event_loop, cli_shell_timer_expired, FuriEventLoopTimerTypeOnce, shell); + + shell->storage.event_flag = furi_event_flag_alloc(); + furi_event_loop_subscribe_event_flag( + shell->event_loop, + shell->storage.event_flag, + FuriEventLoopEventIn, + cli_shell_storage_internal_event, + shell); + shell->storage.storage = furi_record_open(RECORD_STORAGE); + shell->storage.subscription = furi_pubsub_subscribe( + storage_get_pubsub(shell->storage.storage), cli_shell_storage_event, shell); + + cli_shell_install_pipe(shell); +} + +static void cli_shell_deinit(CliShell* shell) { + furi_pubsub_unsubscribe( + storage_get_pubsub(shell->storage.storage), shell->storage.subscription); + furi_record_close(RECORD_STORAGE); + furi_event_loop_unsubscribe(shell->event_loop, shell->storage.event_flag); + furi_event_flag_free(shell->storage.event_flag); + + cli_shell_completions_free(shell->components[CliShellComponentCompletions]); + cli_shell_line_free(shell->components[CliShellComponentLine]); + + cli_shell_detach_pipe(shell); + furi_event_loop_timer_free(shell->ansi_parsing_timer); + furi_event_loop_free(shell->event_loop); + cli_ansi_parser_free(shell->ansi_parser); +} + +static int32_t cli_shell_thread(void* context) { + CliShell* shell = context; + + // Sometimes, the other side closes the pipe even before our thread is started. Although the + // rest of the code will eventually find this out if this check is removed, there's no point in + // wasting time. + if(pipe_state(shell->pipe) == PipeStateBroken) return 0; + + cli_shell_init(shell); + FURI_LOG_D(TAG, "Started"); + + shell->motd(shell->callback_context); + cli_shell_line_prompt(shell->components[CliShellComponentLine]); + + furi_event_loop_run(shell->event_loop); + + FURI_LOG_D(TAG, "Stopped"); + cli_shell_deinit(shell); + return 0; +} + +// ========== +// Public API +// ========== + +CliShell* cli_shell_alloc( + CliShellMotd motd, + void* context, + PipeSide* pipe, + CliRegistry* registry, + const CliCommandExternalConfig* ext_config) { + furi_check(motd); + furi_check(pipe); + furi_check(registry); + + CliShell* shell = malloc(sizeof(CliShell)); + *shell = (CliShell){ + .motd = motd, + .callback_context = context, + .pipe = pipe, + .registry = registry, + .ext_config = ext_config, + }; + + shell->thread = + furi_thread_alloc_ex("CliShell", CLI_SHELL_STACK_SIZE, cli_shell_thread, shell); + + return shell; +} + +void cli_shell_free(CliShell* shell) { + furi_check(shell); + furi_thread_free(shell->thread); + free(shell); +} + +void cli_shell_start(CliShell* shell) { + furi_check(shell); + furi_thread_start(shell->thread); +} + +void cli_shell_join(CliShell* shell) { + furi_check(shell); + furi_thread_join(shell->thread); +} + +void cli_shell_set_prompt(CliShell* shell, const char* prompt) { + furi_check(shell); + furi_check(furi_thread_get_state(shell->thread) == FuriThreadStateStopped); + shell->prompt = prompt; +} diff --git a/lib/toolbox/cli/shell/cli_shell.h b/lib/toolbox/cli/shell/cli_shell.h new file mode 100644 index 000000000..74f71273e --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include "../cli_registry.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLI_SHELL_STACK_SIZE (4 * 1024U) + +typedef struct CliShell CliShell; + +/** + * Called from the shell thread to print the Message of the Day when the shell + * is started. + */ +typedef void (*CliShellMotd)(void* context); + +/** + * @brief Allocates a shell + * + * @param [in] motd Message of the Day callback + * @param [in] context Callback context + * @param [in] pipe Pipe side to be used by the shell + * @param [in] registry Command registry + * @param [in] ext_config External command configuration. See + * `CliCommandExternalConfig`. May be NULL if support for + * external commands is not required. + * + * @return Shell instance + */ +CliShell* cli_shell_alloc( + CliShellMotd motd, + void* context, + PipeSide* pipe, + CliRegistry* registry, + const CliCommandExternalConfig* ext_config); + +/** + * @brief Frees a shell + * + * @param [in] shell Shell instance + */ +void cli_shell_free(CliShell* shell); + +/** + * @brief Starts a shell + * + * The shell runs in a separate thread. This call is non-blocking. + * + * @param [in] shell Shell instance + */ +void cli_shell_start(CliShell* shell); + +/** + * @brief Joins the shell thread + * + * @warning This call is blocking. + * + * @param [in] shell Shell instance + */ +void cli_shell_join(CliShell* shell); + +/** + * @brief Sets optional text before prompt (`>:`) + * + * @param [in] shell Shell instance + */ +void cli_shell_set_prompt(CliShell* shell, const char* prompt); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/cli/shell/cli_shell_completions.c b/lib/toolbox/cli/shell/cli_shell_completions.c new file mode 100644 index 000000000..6b6634dbb --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell_completions.c @@ -0,0 +1,367 @@ +#include "cli_shell_completions.h" + +ARRAY_DEF(CommandCompletions, FuriString*, FURI_STRING_OPLIST); // -V524 +#define M_OPL_CommandCompletions_t() ARRAY_OPLIST(CommandCompletions) + +struct CliShellCompletions { + CliRegistry* registry; + CliShell* shell; + CliShellLine* line; + CommandCompletions_t variants; + size_t selected; + bool is_displaying; +}; + +#define COMPLETION_COLUMNS 3 +#define COMPLETION_COLUMN_WIDTH "30" +#define COMPLETION_COLUMN_WIDTH_I 30 + +/** + * @brief Update for the completions menu + */ +typedef enum { + CliShellCompletionsActionOpen, + CliShellCompletionsActionClose, + CliShellCompletionsActionUp, + CliShellCompletionsActionDown, + CliShellCompletionsActionLeft, + CliShellCompletionsActionRight, + CliShellCompletionsActionSelect, + CliShellCompletionsActionSelectNoClose, +} CliShellCompletionsAction; + +typedef enum { + CliShellCompletionSegmentTypeCommand, + CliShellCompletionSegmentTypeArguments, +} CliShellCompletionSegmentType; + +typedef struct { + CliShellCompletionSegmentType type; + size_t start; + size_t length; +} CliShellCompletionSegment; + +// ========== +// Public API +// ========== + +CliShellCompletions* + cli_shell_completions_alloc(CliRegistry* registry, CliShell* shell, CliShellLine* line) { + CliShellCompletions* completions = malloc(sizeof(CliShellCompletions)); + + completions->registry = registry; + completions->shell = shell; + completions->line = line; + CommandCompletions_init(completions->variants); + + return completions; +} + +void cli_shell_completions_free(CliShellCompletions* completions) { + CommandCompletions_clear(completions->variants); + free(completions); +} + +// ======= +// Helpers +// ======= + +CliShellCompletionSegment cli_shell_completions_segment(CliShellCompletions* completions) { + furi_assert(completions); + CliShellCompletionSegment segment; + + FuriString* input = furi_string_alloc_set(cli_shell_line_get_editing(completions->line)); + furi_string_left(input, cli_shell_line_get_line_position(completions->line)); + + // find index of first non-space character + size_t first_non_space = 0; + while(1) { + size_t ret = furi_string_search_char(input, ' ', first_non_space); + if(ret == FURI_STRING_FAILURE) break; + if(ret - first_non_space > 1) break; + first_non_space++; + } + + size_t first_space_in_command = furi_string_search_char(input, ' ', first_non_space); + + if(first_space_in_command == FURI_STRING_FAILURE) { + segment.type = CliShellCompletionSegmentTypeCommand; + segment.start = first_non_space; + segment.length = furi_string_size(input) - first_non_space; + } else { + segment.type = CliShellCompletionSegmentTypeArguments; + segment.start = 0; + segment.length = 0; + // support removed, might reimplement in the future + } + + furi_string_free(input); + return segment; +} + +void cli_shell_completions_fill_variants(CliShellCompletions* completions) { + furi_assert(completions); + CommandCompletions_reset(completions->variants); + + CliShellCompletionSegment segment = cli_shell_completions_segment(completions); + FuriString* input = furi_string_alloc_set(cli_shell_line_get_editing(completions->line)); + furi_string_right(input, segment.start); + furi_string_left(input, segment.length); + + if(segment.type == CliShellCompletionSegmentTypeCommand) { + CliRegistry* registry = completions->registry; + cli_registry_lock(registry); + CliCommandDict_t* commands = cli_registry_get_commands(registry); + for + M_EACH(registered_command, *commands, CliCommandDict_t) { + FuriString* command_name = registered_command->key; + if(furi_string_start_with(command_name, input)) { + CommandCompletions_push_back(completions->variants, command_name); + } + } + cli_registry_unlock(registry); + + } else { + // support removed, might reimplement in the future + } + + furi_string_free(input); +} + +static size_t cli_shell_completions_rows_at_column(CliShellCompletions* completions, size_t x) { + size_t completions_size = CommandCompletions_size(completions->variants); + size_t n_full_rows = completions_size / COMPLETION_COLUMNS; + size_t n_cols_in_last_row = completions_size % COMPLETION_COLUMNS; + size_t n_rows_at_x = n_full_rows + ((x >= n_cols_in_last_row) ? 0 : 1); + return n_rows_at_x; +} + +void cli_shell_completions_render( + CliShellCompletions* completions, + CliShellCompletionsAction action) { + furi_assert(completions); + if(action == CliShellCompletionsActionOpen) furi_check(!completions->is_displaying); + if(action == CliShellCompletionsActionClose) furi_check(completions->is_displaying); + + char prompt[64]; + cli_shell_line_format_prompt(completions->line, prompt, sizeof(prompt)); + + if(action == CliShellCompletionsActionOpen) { + cli_shell_completions_fill_variants(completions); + completions->selected = 0; + + if(CommandCompletions_size(completions->variants) == 1) { + cli_shell_completions_render(completions, CliShellCompletionsActionSelectNoClose); + return; + } + + // show completions menu (full re-render) + printf("\n\r"); + size_t position = 0; + for + M_EACH(completion, completions->variants, CommandCompletions_t) { + if(position == completions->selected) printf(ANSI_INVERT); + printf("%-" COMPLETION_COLUMN_WIDTH "s", furi_string_get_cstr(*completion)); + if(position == completions->selected) printf(ANSI_RESET); + if((position % COMPLETION_COLUMNS == COMPLETION_COLUMNS - 1) && + position != CommandCompletions_size(completions->variants)) { + printf("\r\n"); + } + position++; + } + + if(!position) { + printf(ANSI_FG_RED "no completions" ANSI_RESET); + } + + size_t total_rows = (position / COMPLETION_COLUMNS) + 1; + printf( + ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) ANSI_CURSOR_UP_BY("%zu") + ANSI_CURSOR_HOR_POS("%zu"), + total_rows, + strlen(prompt) + cli_shell_line_get_line_position(completions->line) + 1); + + completions->is_displaying = true; + + } else if(action == CliShellCompletionsActionClose) { + // clear completions menu + printf( + ANSI_CURSOR_HOR_POS("%zu") ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END) + ANSI_CURSOR_HOR_POS("%zu"), + strlen(prompt) + furi_string_size(cli_shell_line_get_selected(completions->line)) + 1, + strlen(prompt) + cli_shell_line_get_line_position(completions->line) + 1); + completions->is_displaying = false; + + } else if( + action == CliShellCompletionsActionUp || action == CliShellCompletionsActionDown || + action == CliShellCompletionsActionLeft || action == CliShellCompletionsActionRight) { + if(CommandCompletions_empty_p(completions->variants)) return; + + // move selection + size_t completions_size = CommandCompletions_size(completions->variants); + size_t old_selection = completions->selected; + int n_columns = (completions_size >= COMPLETION_COLUMNS) ? COMPLETION_COLUMNS : + completions_size; + int selection_unclamped = old_selection; + if(action == CliShellCompletionsActionLeft) { + selection_unclamped--; + } else if(action == CliShellCompletionsActionRight) { + selection_unclamped++; + } else { + int selection_x = old_selection % COMPLETION_COLUMNS; + int selection_y_unclamped = old_selection / COMPLETION_COLUMNS; + if(action == CliShellCompletionsActionUp) selection_y_unclamped--; + if(action == CliShellCompletionsActionDown) selection_y_unclamped++; + size_t selection_y = 0; + if(selection_y_unclamped < 0) { + selection_x = CLAMP_WRAPAROUND(selection_x - 1, n_columns - 1, 0); + selection_y = + cli_shell_completions_rows_at_column(completions, selection_x) - 1; // -V537 + } else if( + (size_t)selection_y_unclamped > + cli_shell_completions_rows_at_column(completions, selection_x) - 1) { + selection_x = CLAMP_WRAPAROUND(selection_x + 1, n_columns - 1, 0); + selection_y = 0; + } else { + selection_y = selection_y_unclamped; + } + selection_unclamped = (selection_y * COMPLETION_COLUMNS) + selection_x; + } + size_t new_selection = CLAMP_WRAPAROUND(selection_unclamped, (int)completions_size - 1, 0); + completions->selected = new_selection; + + if(new_selection != old_selection) { + // determine selection coordinates relative to top-left of suggestion menu + size_t old_x = (old_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I; + size_t old_y = old_selection / COMPLETION_COLUMNS; + size_t new_x = (new_selection % COMPLETION_COLUMNS) * COMPLETION_COLUMN_WIDTH_I; + size_t new_y = new_selection / COMPLETION_COLUMNS; + printf("\n\r"); + + // print old selection in normal colors + if(old_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), old_y); + printf(ANSI_CURSOR_HOR_POS("%zu"), old_x + 1); + printf( + "%-" COMPLETION_COLUMN_WIDTH "s", + furi_string_get_cstr( + *CommandCompletions_cget(completions->variants, old_selection))); + if(old_y) printf(ANSI_CURSOR_UP_BY("%zu"), old_y); + printf(ANSI_CURSOR_HOR_POS("1")); + + // print new selection in inverted colors + if(new_y) printf(ANSI_CURSOR_DOWN_BY("%zu"), new_y); + printf(ANSI_CURSOR_HOR_POS("%zu"), new_x + 1); + printf( + ANSI_INVERT "%-" COMPLETION_COLUMN_WIDTH "s" ANSI_RESET, + furi_string_get_cstr( + *CommandCompletions_cget(completions->variants, new_selection))); + + // return cursor + printf(ANSI_CURSOR_UP_BY("%zu"), new_y + 1); + printf( + ANSI_CURSOR_HOR_POS("%zu"), + strlen(prompt) + furi_string_size(cli_shell_line_get_selected(completions->line)) + + 1); + } + + } else if(action == CliShellCompletionsActionSelectNoClose) { + if(!CommandCompletions_size(completions->variants)) return; + // insert selection into prompt + CliShellCompletionSegment segment = cli_shell_completions_segment(completions); + FuriString* input = cli_shell_line_get_selected(completions->line); + FuriString* completion = + *CommandCompletions_cget(completions->variants, completions->selected); + furi_string_replace_at( + input, segment.start, segment.length, furi_string_get_cstr(completion)); + printf( + ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), + strlen(prompt) + 1, + furi_string_get_cstr(input)); + + int position_change = (int)furi_string_size(completion) - (int)segment.length; + cli_shell_line_set_line_position( + completions->line, + MAX(0, (int)cli_shell_line_get_line_position(completions->line) + position_change)); + + } else if(action == CliShellCompletionsActionSelect) { + cli_shell_completions_render(completions, CliShellCompletionsActionSelectNoClose); + cli_shell_completions_render(completions, CliShellCompletionsActionClose); + + } else { + furi_crash(); + } + + fflush(stdout); +} + +// ============== +// Input handlers +// ============== + +static bool hide_if_open_and_continue_handling(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellCompletions* completions = context; + if(completions->is_displaying) + cli_shell_completions_render(completions, CliShellCompletionsActionClose); + return false; // process other home events +} + +static bool key_combo_cr(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellCompletions* completions = context; + if(!completions->is_displaying) return false; + cli_shell_completions_render(completions, CliShellCompletionsActionSelect); + return true; +} + +static bool key_combo_up_down(CliKeyCombo combo, void* context) { + CliShellCompletions* completions = context; + if(!completions->is_displaying) return false; + cli_shell_completions_render( + completions, + (combo.key == CliKeyUp) ? CliShellCompletionsActionUp : CliShellCompletionsActionDown); + return true; +} + +static bool key_combo_left_right(CliKeyCombo combo, void* context) { + CliShellCompletions* completions = context; + if(!completions->is_displaying) return false; + cli_shell_completions_render( + completions, + (combo.key == CliKeyLeft) ? CliShellCompletionsActionLeft : + CliShellCompletionsActionRight); + return true; +} + +static bool key_combo_tab(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellCompletions* completions = context; + cli_shell_completions_render( + completions, + completions->is_displaying ? CliShellCompletionsActionRight : + CliShellCompletionsActionOpen); + return true; +} + +static bool key_combo_esc(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellCompletions* completions = context; + if(!completions->is_displaying) return false; + cli_shell_completions_render(completions, CliShellCompletionsActionClose); + return true; +} + +CliShellKeyComboSet cli_shell_completions_key_combo_set = { + .fallback = hide_if_open_and_continue_handling, + .count = 7, + .records = + { + {{CliModKeyNo, CliKeyCR}, key_combo_cr}, + {{CliModKeyNo, CliKeyUp}, key_combo_up_down}, + {{CliModKeyNo, CliKeyDown}, key_combo_up_down}, + {{CliModKeyNo, CliKeyLeft}, key_combo_left_right}, + {{CliModKeyNo, CliKeyRight}, key_combo_left_right}, + {{CliModKeyNo, CliKeyTab}, key_combo_tab}, + {{CliModKeyNo, CliKeyEsc}, key_combo_esc}, + }, +}; diff --git a/lib/toolbox/cli/shell/cli_shell_completions.h b/lib/toolbox/cli/shell/cli_shell_completions.h new file mode 100644 index 000000000..d49a1982d --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell_completions.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include "cli_shell_i.h" +#include "cli_shell_line.h" +#include "../cli_registry.h" +#include "../cli_registry_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CliShellCompletions CliShellCompletions; + +CliShellCompletions* + cli_shell_completions_alloc(CliRegistry* registry, CliShell* shell, CliShellLine* line); + +void cli_shell_completions_free(CliShellCompletions* completions); + +extern CliShellKeyComboSet cli_shell_completions_key_combo_set; + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/cli/shell/cli_shell_i.h b/lib/toolbox/cli/shell/cli_shell_i.h new file mode 100644 index 000000000..0b676b7de --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell_i.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../cli_ansi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CliShell CliShell; + +/** + * @brief Key combo handler + * @return true if the event was handled, false otherwise + */ +typedef bool (*CliShellKeyComboAction)(CliKeyCombo combo, void* context); + +typedef struct { + CliKeyCombo combo; + CliShellKeyComboAction action; +} CliShellKeyComboRecord; + +typedef struct { + CliShellKeyComboAction fallback; + size_t count; + CliShellKeyComboRecord records[]; +} CliShellKeyComboSet; + +void cli_shell_execute_command(CliShell* cli_shell, FuriString* command); + +const char* cli_shell_get_prompt(CliShell* cli_shell); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/cli/shell/cli_shell_line.c b/lib/toolbox/cli/shell/cli_shell_line.c new file mode 100644 index 000000000..4826ba252 --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell_line.c @@ -0,0 +1,378 @@ +#include "cli_shell_line.h" + +#define HISTORY_DEPTH 10 + +struct CliShellLine { + size_t history_position; + size_t line_position; + FuriString* history[HISTORY_DEPTH]; + size_t history_entries; + CliShell* shell; + bool about_to_exit; +}; + +// ========== +// Public API +// ========== + +CliShellLine* cli_shell_line_alloc(CliShell* shell) { + CliShellLine* line = malloc(sizeof(CliShellLine)); + line->shell = shell; + + line->history[0] = furi_string_alloc(); + line->history_entries = 1; + + return line; +} + +void cli_shell_line_free(CliShellLine* line) { + for(size_t i = 0; i < line->history_entries; i++) + furi_string_free(line->history[i]); + + free(line); +} + +FuriString* cli_shell_line_get_selected(CliShellLine* line) { + return line->history[line->history_position]; +} + +FuriString* cli_shell_line_get_editing(CliShellLine* line) { + return line->history[0]; +} + +void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length) { + UNUSED(line); + const char* prompt = cli_shell_get_prompt(line->shell); + snprintf(buf, length - 1, "%s>: ", prompt ? prompt : ""); +} + +size_t cli_shell_line_prompt_length(CliShellLine* line) { + char buffer[128]; + cli_shell_line_format_prompt(line, buffer, sizeof(buffer)); + return strlen(buffer); +} + +void cli_shell_line_prompt(CliShellLine* line) { + char buffer[32]; + cli_shell_line_format_prompt(line, buffer, sizeof(buffer)); + printf("\r\n%s", buffer); + fflush(stdout); +} + +void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line) { + if(line->history_position > 0) { + FuriString* source = cli_shell_line_get_selected(line); + FuriString* destination = cli_shell_line_get_editing(line); + furi_string_set(destination, source); + line->history_position = 0; + } +} + +void cli_shell_line_set_about_to_exit(CliShellLine* line) { + line->about_to_exit = true; +} + +size_t cli_shell_line_get_line_position(CliShellLine* line) { + return line->line_position; +} + +void cli_shell_line_set_line_position(CliShellLine* line, size_t position) { + line->line_position = position; +} + +// ======= +// Helpers +// ======= + +typedef enum { + CliCharClassWord, + CliCharClassSpace, + CliCharClassOther, +} CliCharClass; + +typedef enum { + CliSkipDirectionLeft, + CliSkipDirectionRight, +} CliSkipDirection; + +CliCharClass cli_shell_line_char_class(char c) { + if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { + return CliCharClassWord; + } else if(c == ' ') { + return CliCharClassSpace; + } else { + return CliCharClassOther; + } +} + +size_t + cli_shell_line_skip_run(FuriString* string, size_t original_pos, CliSkipDirection direction) { + if(furi_string_size(string) == 0) return original_pos; + if(direction == CliSkipDirectionLeft && original_pos == 0) return original_pos; + if(direction == CliSkipDirectionRight && original_pos == furi_string_size(string)) + return original_pos; + + int8_t look_offset = (direction == CliSkipDirectionLeft) ? -1 : 0; + int8_t increment = (direction == CliSkipDirectionLeft) ? -1 : 1; + int32_t position = original_pos; + CliCharClass start_class = + cli_shell_line_char_class(furi_string_get_char(string, position + look_offset)); + + while(true) { + position += increment; + if(position < 0) break; + if(position >= (int32_t)furi_string_size(string)) break; + if(cli_shell_line_char_class(furi_string_get_char(string, position + look_offset)) != + start_class) + break; + } + + return MAX(0, position); +} + +// ============== +// Input handlers +// ============== + +static bool cli_shell_line_input_ctrl_c(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // reset input + furi_string_reset(cli_shell_line_get_editing(line)); + line->line_position = 0; + line->history_position = 0; + printf("^C"); + cli_shell_line_prompt(line); + return true; +} + +static bool cli_shell_line_input_cr(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + + FuriString* command = cli_shell_line_get_selected(line); + furi_string_trim(command); + if(furi_string_empty(command)) { + cli_shell_line_prompt(line); + return true; + } + + FuriString* command_copy = furi_string_alloc_set(command); + + if(line->history_position == 0) { + for(size_t i = 1; i < line->history_entries; i++) { + if(furi_string_cmp(line->history[i], command) == 0) { + line->history_position = i; + command = cli_shell_line_get_selected(line); + furi_string_trim(command); + break; + } + } + } + + // move selected command to the front + if(line->history_position > 0) { + size_t pos = line->history_position; + size_t len = line->history_entries; + memmove( + &line->history[pos], &line->history[pos + 1], (len - pos - 1) * sizeof(FuriString*)); + furi_string_move(line->history[0], command); + line->history_entries--; + } + + // insert empty command + if(line->history_entries == HISTORY_DEPTH) { + furi_string_free(line->history[HISTORY_DEPTH - 1]); + line->history_entries--; + } + memmove(&line->history[1], &line->history[0], line->history_entries * sizeof(FuriString*)); + line->history[0] = furi_string_alloc(); + line->history_entries++; + line->line_position = 0; + line->history_position = 0; + + // execute command + printf("\r\n"); + cli_shell_execute_command(line->shell, command_copy); + furi_string_free(command_copy); + if(!line->about_to_exit) cli_shell_line_prompt(line); + return true; +} + +static bool cli_shell_line_input_up_down(CliKeyCombo combo, void* context) { + CliShellLine* line = context; + // go up and down in history + int increment = (combo.key == CliKeyUp) ? 1 : -1; + size_t new_pos = + CLAMP((int)line->history_position + increment, (int)line->history_entries - 1, 0); + + // print prompt with selected command + if(new_pos != line->history_position) { + char prompt[64]; + cli_shell_line_format_prompt(line, prompt, sizeof(prompt)); + line->history_position = new_pos; + FuriString* command = cli_shell_line_get_selected(line); + printf( + ANSI_CURSOR_HOR_POS("1") "%s%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), + prompt, + furi_string_get_cstr(command)); + fflush(stdout); + line->line_position = furi_string_size(command); + } + return true; +} + +static bool cli_shell_line_input_left_right(CliKeyCombo combo, void* context) { + CliShellLine* line = context; + // go left and right in the current line + FuriString* command = cli_shell_line_get_selected(line); + int increment = (combo.key == CliKeyRight) ? 1 : -1; + size_t new_pos = + CLAMP((int)line->line_position + increment, (int)furi_string_size(command), 0); + + // move cursor + if(new_pos != line->line_position) { + line->line_position = new_pos; + printf("%s", (increment == 1) ? ANSI_CURSOR_RIGHT_BY("1") : ANSI_CURSOR_LEFT_BY("1")); + fflush(stdout); + } + return true; +} + +static bool cli_shell_line_input_home(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // go to the start + line->line_position = 0; + printf(ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + 1); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_end(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // go to the end + line->line_position = furi_string_size(cli_shell_line_get_selected(line)); + printf( + ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + line->line_position + 1); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_bksp(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // erase one character + cli_shell_line_ensure_not_overwriting_history(line); + FuriString* editing_line = cli_shell_line_get_editing(line); + if(line->line_position == 0) { + putc(CliKeyBell, stdout); + fflush(stdout); + return true; + } + line->line_position--; + furi_string_replace_at(editing_line, line->line_position, 1, ""); + + // move cursor, print the rest of the line, restore cursor + printf( + ANSI_CURSOR_LEFT_BY("1") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END), + furi_string_get_cstr(editing_line) + line->line_position); + size_t left_by = furi_string_size(editing_line) - line->line_position; + if(left_by) // apparently LEFT_BY("0") still shifts left by one ._ . + printf(ANSI_CURSOR_LEFT_BY("%zu"), left_by); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_ctrl_l(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // clear screen + FuriString* command = cli_shell_line_get_selected(line); + char prompt[64]; + cli_shell_line_format_prompt(line, prompt, sizeof(prompt)); + printf( + ANSI_ERASE_DISPLAY(ANSI_ERASE_ENTIRE) ANSI_ERASE_SCROLLBACK_BUFFER ANSI_CURSOR_POS( + "1", "1") "%s%s" ANSI_CURSOR_HOR_POS("%zu"), + prompt, + furi_string_get_cstr(command), + strlen(prompt) + line->line_position + 1 /* 1-based column indexing */); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_ctrl_left_right(CliKeyCombo combo, void* context) { + CliShellLine* line = context; + // skip run of similar chars to the left or right + FuriString* selected_line = cli_shell_line_get_selected(line); + CliSkipDirection direction = (combo.key == CliKeyLeft) ? CliSkipDirectionLeft : + CliSkipDirectionRight; + line->line_position = cli_shell_line_skip_run(selected_line, line->line_position, direction); + printf( + ANSI_CURSOR_HOR_POS("%zu"), cli_shell_line_prompt_length(line) + line->line_position + 1); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_ctrl_bksp(CliKeyCombo combo, void* context) { + UNUSED(combo); + CliShellLine* line = context; + // delete run of similar chars to the left + cli_shell_line_ensure_not_overwriting_history(line); + FuriString* selected_line = cli_shell_line_get_selected(line); + size_t run_start = + cli_shell_line_skip_run(selected_line, line->line_position, CliSkipDirectionLeft); + furi_string_replace_at(selected_line, run_start, line->line_position - run_start, ""); + line->line_position = run_start; + printf( + ANSI_CURSOR_HOR_POS("%zu") "%s" ANSI_ERASE_LINE(ANSI_ERASE_FROM_CURSOR_TO_END) + ANSI_CURSOR_HOR_POS("%zu"), + cli_shell_line_prompt_length(line) + line->line_position + 1, + furi_string_get_cstr(selected_line) + run_start, + cli_shell_line_prompt_length(line) + run_start + 1); + fflush(stdout); + return true; +} + +static bool cli_shell_line_input_normal(CliKeyCombo combo, void* context) { + CliShellLine* line = context; + if(combo.modifiers != CliModKeyNo) return false; + if(combo.key < CliKeySpace || combo.key >= CliKeyDEL) return false; + // insert character + cli_shell_line_ensure_not_overwriting_history(line); + FuriString* editing_line = cli_shell_line_get_editing(line); + if(line->line_position == furi_string_size(editing_line)) { + furi_string_push_back(editing_line, combo.key); + printf("%c", combo.key); + } else { + const char in_str[2] = {combo.key, 0}; + furi_string_replace_at(editing_line, line->line_position, 0, in_str); + printf(ANSI_INSERT_MODE_ENABLE "%c" ANSI_INSERT_MODE_DISABLE, combo.key); + } + fflush(stdout); + line->line_position++; + return true; +} + +CliShellKeyComboSet cli_shell_line_key_combo_set = { + .fallback = cli_shell_line_input_normal, + .count = 14, + .records = + { + {{CliModKeyNo, CliKeyETX}, cli_shell_line_input_ctrl_c}, + {{CliModKeyNo, CliKeyCR}, cli_shell_line_input_cr}, + {{CliModKeyNo, CliKeyUp}, cli_shell_line_input_up_down}, + {{CliModKeyNo, CliKeyDown}, cli_shell_line_input_up_down}, + {{CliModKeyNo, CliKeyLeft}, cli_shell_line_input_left_right}, + {{CliModKeyNo, CliKeyRight}, cli_shell_line_input_left_right}, + {{CliModKeyNo, CliKeyHome}, cli_shell_line_input_home}, + {{CliModKeyNo, CliKeyEnd}, cli_shell_line_input_end}, + {{CliModKeyNo, CliKeyBackspace}, cli_shell_line_input_bksp}, + {{CliModKeyNo, CliKeyDEL}, cli_shell_line_input_bksp}, + {{CliModKeyNo, CliKeyFF}, cli_shell_line_input_ctrl_l}, + {{CliModKeyCtrl, CliKeyLeft}, cli_shell_line_input_ctrl_left_right}, + {{CliModKeyCtrl, CliKeyRight}, cli_shell_line_input_ctrl_left_right}, + {{CliModKeyNo, CliKeyETB}, cli_shell_line_input_ctrl_bksp}, + }, +}; diff --git a/lib/toolbox/cli/shell/cli_shell_line.h b/lib/toolbox/cli/shell/cli_shell_line.h new file mode 100644 index 000000000..e40a12bd6 --- /dev/null +++ b/lib/toolbox/cli/shell/cli_shell_line.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "cli_shell_i.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CliShellLine CliShellLine; + +CliShellLine* cli_shell_line_alloc(CliShell* shell); + +void cli_shell_line_free(CliShellLine* line); + +FuriString* cli_shell_line_get_selected(CliShellLine* line); + +FuriString* cli_shell_line_get_editing(CliShellLine* line); + +size_t cli_shell_line_prompt_length(CliShellLine* line); + +void cli_shell_line_format_prompt(CliShellLine* line, char* buf, size_t length); + +void cli_shell_line_prompt(CliShellLine* line); + +size_t cli_shell_line_get_line_position(CliShellLine* line); + +void cli_shell_line_set_line_position(CliShellLine* line, size_t position); + +/** + * @brief If a line from history has been selected, moves it into the active line + */ +void cli_shell_line_ensure_not_overwriting_history(CliShellLine* line); + +void cli_shell_line_set_about_to_exit(CliShellLine* line); + +extern CliShellKeyComboSet cli_shell_line_key_combo_set; + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/pipe.c b/lib/toolbox/pipe.c new file mode 100644 index 000000000..59b2f63f4 --- /dev/null +++ b/lib/toolbox/pipe.c @@ -0,0 +1,272 @@ +#include "pipe.h" +#include + +#define PIPE_DEFAULT_STATE_CHECK_PERIOD furi_ms_to_ticks(100) + +/** + * Data shared between both sides. + */ +typedef struct { + FuriSemaphore* instance_count; // role; +} + +PipeState pipe_state(PipeSide* pipe) { + furi_check(pipe); + uint32_t count = furi_semaphore_get_count(pipe->shared->instance_count); + return (count == 1) ? PipeStateOpen : PipeStateBroken; +} + +void pipe_free(PipeSide* pipe) { + furi_check(pipe); + furi_check(!pipe->event_loop); + + furi_mutex_acquire(pipe->shared->state_transition, FuriWaitForever); + FuriStatus status = furi_semaphore_acquire(pipe->shared->instance_count, 0); + + if(status == FuriStatusOk) { + // the other side is still intact + furi_mutex_release(pipe->shared->state_transition); + free(pipe); + } else { + // the other side is gone too + furi_stream_buffer_free(pipe->sending); + furi_stream_buffer_free(pipe->receiving); + furi_semaphore_free(pipe->shared->instance_count); + furi_mutex_free(pipe->shared->state_transition); + free(pipe->shared); + free(pipe); + } +} + +static void pipe_stdout_cb(const char* data, size_t size, void* context) { + furi_assert(context); + PipeSide* pipe = context; + pipe_send(pipe, data, size); +} + +static size_t pipe_stdin_cb(char* data, size_t size, FuriWait timeout, void* context) { + UNUSED(timeout); + furi_assert(context); + PipeSide* pipe = context; + return pipe_receive(pipe, data, size); +} + +void pipe_install_as_stdio(PipeSide* pipe) { + furi_check(pipe); + furi_thread_set_stdout_callback(pipe_stdout_cb, pipe); + furi_thread_set_stdin_callback(pipe_stdin_cb, pipe); +} + +void pipe_set_state_check_period(PipeSide* pipe, FuriWait check_period) { + furi_check(pipe); + pipe->state_check_period = check_period; +} + +size_t pipe_receive(PipeSide* pipe, void* data, size_t length) { + furi_check(pipe); + + size_t received = 0; + while(length) { + size_t received_this_time = + furi_stream_buffer_receive(pipe->receiving, data, length, pipe->state_check_period); + if(!received_this_time && pipe_state(pipe) == PipeStateBroken) break; + + received += received_this_time; + length -= received_this_time; + data += received_this_time; + } + + return received; +} + +size_t pipe_send(PipeSide* pipe, const void* data, size_t length) { + furi_check(pipe); + + size_t sent = 0; + while(length) { + size_t sent_this_time = + furi_stream_buffer_send(pipe->sending, data, length, pipe->state_check_period); + if(!sent_this_time && pipe_state(pipe) == PipeStateBroken) break; + + sent += sent_this_time; + length -= sent_this_time; + data += sent_this_time; + } + + return sent; +} + +size_t pipe_bytes_available(PipeSide* pipe) { + furi_check(pipe); + return furi_stream_buffer_bytes_available(pipe->receiving); +} + +size_t pipe_spaces_available(PipeSide* pipe) { + furi_check(pipe); + return furi_stream_buffer_spaces_available(pipe->sending); +} + +static void pipe_receiving_buffer_callback(FuriEventLoopObject* buffer, void* context) { + UNUSED(buffer); + PipeSide* pipe = context; + furi_assert(pipe); + if(pipe->on_data_arrived) pipe->on_data_arrived(pipe, pipe->callback_context); +} + +static void pipe_sending_buffer_callback(FuriEventLoopObject* buffer, void* context) { + UNUSED(buffer); + PipeSide* pipe = context; + furi_assert(pipe); + if(pipe->on_space_freed) pipe->on_space_freed(pipe, pipe->callback_context); +} + +static void pipe_semaphore_callback(FuriEventLoopObject* semaphore, void* context) { + UNUSED(semaphore); + PipeSide* pipe = context; + furi_assert(pipe); + if(pipe->on_pipe_broken) pipe->on_pipe_broken(pipe, pipe->callback_context); +} + +void pipe_attach_to_event_loop(PipeSide* pipe, FuriEventLoop* event_loop) { + furi_check(pipe); + furi_check(event_loop); + furi_check(!pipe->event_loop); + + pipe->event_loop = event_loop; +} + +void pipe_detach_from_event_loop(PipeSide* pipe) { + furi_check(pipe); + furi_check(pipe->event_loop); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->receiving); + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->sending); + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->shared->instance_count); + + pipe->event_loop = NULL; +} + +void pipe_set_callback_context(PipeSide* pipe, void* context) { + furi_check(pipe); + pipe->callback_context = context; +} + +void pipe_set_data_arrived_callback( + PipeSide* pipe, + PipeSideDataArrivedCallback callback, + FuriEventLoopEvent event) { + furi_check(pipe); + furi_check(pipe->event_loop); + furi_check((event & FuriEventLoopEventMask) == 0); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->receiving); + pipe->on_data_arrived = callback; + if(callback) + furi_event_loop_subscribe_stream_buffer( + pipe->event_loop, + pipe->receiving, + FuriEventLoopEventIn | event, + pipe_receiving_buffer_callback, + pipe); +} + +void pipe_set_space_freed_callback( + PipeSide* pipe, + PipeSideSpaceFreedCallback callback, + FuriEventLoopEvent event) { + furi_check(pipe); + furi_check(pipe->event_loop); + furi_check((event & FuriEventLoopEventMask) == 0); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->sending); + pipe->on_space_freed = callback; + if(callback) + furi_event_loop_subscribe_stream_buffer( + pipe->event_loop, + pipe->sending, + FuriEventLoopEventOut | event, + pipe_sending_buffer_callback, + pipe); +} + +void pipe_set_broken_callback( + PipeSide* pipe, + PipeSideBrokenCallback callback, + FuriEventLoopEvent event) { + furi_check(pipe); + furi_check(pipe->event_loop); + furi_check((event & FuriEventLoopEventMask) == 0); + + furi_event_loop_maybe_unsubscribe(pipe->event_loop, pipe->shared->instance_count); + pipe->on_pipe_broken = callback; + if(callback) + furi_event_loop_subscribe_semaphore( + pipe->event_loop, + pipe->shared->instance_count, + FuriEventLoopEventOut | event, + pipe_semaphore_callback, + pipe); +} diff --git a/lib/toolbox/pipe.h b/lib/toolbox/pipe.h new file mode 100644 index 000000000..b071975f7 --- /dev/null +++ b/lib/toolbox/pipe.h @@ -0,0 +1,315 @@ +/** + * @file pipe.h + * Pipe convenience module + * + * Pipes are used to send bytes between two threads in both directions. The two + * threads are referred to as Alice and Bob and their abilities regarding what + * they can do with the pipe are equal. + * + * It is also possible to use both sides of the pipe within one thread. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief The role of a pipe side + * + * Both roles are equal, as they can both read and write the data. This status + * might be helpful in determining the role of a thread w.r.t. another thread in + * an application that builds on the pipe. + */ +typedef enum { + PipeRoleAlice, + PipeRoleBob, +} PipeRole; + +/** + * @brief The state of a pipe + * + * - `PipeStateOpen`: Both pipe sides are in place, meaning data that is sent + * down the pipe _might_ be read by the peer, and new data sent by the peer + * _might_ arrive. + * - `PipeStateBroken`: The other side of the pipe has been freed, meaning + * data that is written will never reach its destination, and no new data + * will appear in the buffer. + * + * A broken pipe can never become open again, because there's no way to connect + * a side of a pipe to another side of a pipe. + */ +typedef enum { + PipeStateOpen, + PipeStateBroken, +} PipeState; + +typedef struct PipeSide PipeSide; + +typedef struct { + PipeSide* alices_side; + PipeSide* bobs_side; +} PipeSideBundle; + +typedef struct { + size_t capacity; + size_t trigger_level; +} PipeSideReceiveSettings; + +/** + * @brief Allocates two connected sides of one pipe. + * + * Creating a pair of sides using this function is the only way to connect two + * pipe sides together. Two unrelated orphaned sides may never be connected back + * together. + * + * The capacity and trigger level for both directions are the same when the pipe + * is created using this function. Use `pipe_alloc_ex` if you want more + * control. + * + * @param capacity Maximum number of bytes buffered in one direction + * @param trigger_level Number of bytes that need to be available in the buffer + * in order for a blocked thread to unblock + * @returns Bundle with both sides of the pipe + */ +PipeSideBundle pipe_alloc(size_t capacity, size_t trigger_level); + +/** + * @brief Allocates two connected sides of one pipe. + * + * Creating a pair of sides using this function is the only way to connect two + * pipe sides together. Two unrelated orphaned sides may never be connected back + * together. + * + * The capacity and trigger level may be different for the two directions when + * the pipe is created using this function. Use `pipe_alloc` if you don't + * need control this fine. + * + * @param alice `capacity` and `trigger_level` settings for Alice's receiving + * buffer + * @param bob `capacity` and `trigger_level` settings for Bob's receiving buffer + * @returns Bundle with both sides of the pipe + */ +PipeSideBundle pipe_alloc_ex(PipeSideReceiveSettings alice, PipeSideReceiveSettings bob); + +/** + * @brief Gets the role of a pipe side. + * + * The roles (Alice and Bob) are equal, as both can send and receive data. This + * status might be helpful in determining the role of a thread w.r.t. another + * thread. + * + * @param [in] pipe Pipe side to query + * @returns Role of provided pipe side + */ +PipeRole pipe_role(PipeSide* pipe); + +/** + * @brief Gets the state of a pipe. + * + * When the state is `PipeStateOpen`, both sides are active and may send or + * receive data. When the state is `PipeStateBroken`, only one side is active + * (the one that this method has been called on). If you find yourself in that + * state, the data that you send will never be heard by anyone, and the data you + * receive are leftovers in the buffer. + * + * @param [in] pipe Pipe side to query + * @returns State of the pipe + */ +PipeState pipe_state(PipeSide* pipe); + +/** + * @brief Frees a side of a pipe. + * + * When only one of the sides is freed, the pipe is transitioned from the "Open" + * state into the "Broken" state. When both sides are freed, the underlying data + * structures are freed too. + * + * @param [in] pipe Pipe side to free + */ +void pipe_free(PipeSide* pipe); + +/** + * @brief Connects the pipe to the `stdin` and `stdout` of the current thread. + * + * After performing this operation, you can use `getc`, `puts`, etc. to send and + * receive data to and from the pipe. If the pipe becomes broken, C stdlib calls + * will return `EOF` wherever possible. + * + * You can disconnect the pipe by manually calling + * `furi_thread_set_stdout_callback` and `furi_thread_set_stdin_callback` with + * `NULL`. + * + * @param [in] pipe Pipe side to connect to the stdio + */ +void pipe_install_as_stdio(PipeSide* pipe); + +/** + * @brief Sets the state check period for `send` and `receive` operations + * + * @note This value is set to 100 ms when the pipe is created + * + * `send` and `receive` will check the state of the pipe if exactly 0 bytes were + * sent or received during any given `check_period`. Read the documentation for + * `pipe_send` and `pipe_receive` for more info. + * + * @param [in] pipe Pipe side to set the check period of + * @param [in] check_period Period in ticks + */ +void pipe_set_state_check_period(PipeSide* pipe, FuriWait check_period); + +/** + * @brief Receives data from the pipe. + * + * This function will try to receive all of the requested bytes from the pipe. + * If at some point during the operation the pipe becomes broken, this function + * will return prematurely, in which case the return value will be less than the + * requested `length`. + * + * @param [in] pipe The pipe side to read data out of + * @param [out] data The buffer to fill with data + * @param length Maximum length of data to read + * @returns The number of bytes actually written into the provided buffer + */ +size_t pipe_receive(PipeSide* pipe, void* data, size_t length); + +/** + * @brief Sends data into the pipe. + * + * This function will try to send all of the requested bytes to the pipe. + * If at some point during the operation the pipe becomes broken, this function + * will return prematurely, in which case the return value will be less than the + * requested `length`. + * + * @param [in] pipe The pipe side to send data into + * @param [out] data The buffer to get data from + * @param length Maximum length of data to send + * @returns The number of bytes actually read from the provided buffer + */ +size_t pipe_send(PipeSide* pipe, const void* data, size_t length); + +/** + * @brief Determines how many bytes there are in the pipe available to be read. + * + * @param [in] pipe Pipe side to query + * @returns Number of bytes available to be read out from that side of the pipe + */ +size_t pipe_bytes_available(PipeSide* pipe); + +/** + * @brief Determines how many space there is in the pipe for data to be written + * into. + * + * @param [in] pipe Pipe side to query + * @returns Number of bytes available to be written into that side of the pipe + */ +size_t pipe_spaces_available(PipeSide* pipe); + +/** + * @brief Attaches a `PipeSide` to a `FuriEventLoop`, allowing to attach + * callbacks to the PipeSide. + * + * @param [in] pipe Pipe side to attach to the event loop + * @param [in] event_loop Event loop to attach the pipe side to + */ +void pipe_attach_to_event_loop(PipeSide* pipe, FuriEventLoop* event_loop); + +/** + * @brief Detaches a `PipeSide` from the `FuriEventLoop` that it was previously + * attached to. + * + * @param [in] pipe Pipe side to detach to the event loop + */ +void pipe_detach_from_event_loop(PipeSide* pipe); + +/** + * @brief Callback for when data arrives to a `PipeSide`. + * + * @param [in] pipe Pipe side that called the callback + * @param [inout] context Custom context + */ +typedef void (*PipeSideDataArrivedCallback)(PipeSide* pipe, void* context); + +/** + * @brief Callback for when data is read out of the opposite `PipeSide`. + * + * @param [in] pipe Pipe side that called the callback + * @param [inout] context Custom context + */ +typedef void (*PipeSideSpaceFreedCallback)(PipeSide* pipe, void* context); + +/** + * @brief Callback for when the opposite `PipeSide` is freed, making the pipe + * broken. + * + * @param [in] pipe Pipe side that called the callback + * @param [inout] context Custom context + */ +typedef void (*PipeSideBrokenCallback)(PipeSide* pipe, void* context); + +/** + * @brief Sets the custom context for all callbacks. + * + * @param [in] pipe Pipe side to set the context of + * @param [inout] context Custom context that will be passed to callbacks + */ +void pipe_set_callback_context(PipeSide* pipe, void* context); + +/** + * @brief Sets the callback for when data arrives. + * + * @param [in] pipe Pipe side to assign the callback to + * @param [in] callback Callback to assign to the pipe side. Set to NULL to + * unsubscribe. + * @param [in] event Additional event loop flags (e.g. `Edge`, `Once`, etc.). + * Non-flag values of the enum are not allowed. + * + * @warning Attach the pipe side to an event loop first using + * `pipe_attach_to_event_loop`. + */ +void pipe_set_data_arrived_callback( + PipeSide* pipe, + PipeSideDataArrivedCallback callback, + FuriEventLoopEvent event); + +/** + * @brief Sets the callback for when data is read out of the opposite `PipeSide`. + * + * @param [in] pipe Pipe side to assign the callback to + * @param [in] callback Callback to assign to the pipe side. Set to NULL to + * unsubscribe. + * @param [in] event Additional event loop flags (e.g. `Edge`, `Once`, etc.). + * Non-flag values of the enum are not allowed. + * + * @warning Attach the pipe side to an event loop first using + * `pipe_attach_to_event_loop`. + */ +void pipe_set_space_freed_callback( + PipeSide* pipe, + PipeSideSpaceFreedCallback callback, + FuriEventLoopEvent event); + +/** + * @brief Sets the callback for when the opposite `PipeSide` is freed, making + * the pipe broken. + * + * @param [in] pipe Pipe side to assign the callback to + * @param [in] callback Callback to assign to the pipe side. Set to NULL to + * unsubscribe. + * @param [in] event Additional event loop flags (e.g. `Edge`, `Once`, etc.). + * Non-flag values of the enum are not allowed. + * + * @warning Attach the pipe side to an event loop first using + * `pipe_attach_to_event_loop`. + */ +void pipe_set_broken_callback( + PipeSide* pipe, + PipeSideBrokenCallback callback, + FuriEventLoopEvent event); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/pretty_format.c b/lib/toolbox/pretty_format.c index f8319b69d..496738c4d 100644 --- a/lib/toolbox/pretty_format.c +++ b/lib/toolbox/pretty_format.c @@ -36,11 +36,17 @@ void pretty_format_bytes_hex_canonical( } const size_t begin_idx = i; - const size_t end_idx = MIN(i + num_places, data_size); + const size_t wrap_idx = i + num_places; + const size_t end_idx = MIN(wrap_idx, data_size); for(size_t j = begin_idx; j < end_idx; j++) { furi_string_cat_printf(result, "%02X ", data[j]); } + if(end_idx < wrap_idx) { + for(size_t j = end_idx; j < wrap_idx; j++) { + furi_string_cat_printf(result, " "); + } + } furi_string_push_back(result, '|'); diff --git a/lib/toolbox/protocols/protocol_dict.c b/lib/toolbox/protocols/protocol_dict.c index 5680be18d..5cc46d6eb 100644 --- a/lib/toolbox/protocols/protocol_dict.c +++ b/lib/toolbox/protocols/protocol_dict.c @@ -2,12 +2,12 @@ #include "protocol_dict.h" struct ProtocolDict { - const ProtocolBase** base; + const ProtocolBase* const* base; size_t count; void* data[]; }; -ProtocolDict* protocol_dict_alloc(const ProtocolBase** protocols, size_t count) { +ProtocolDict* protocol_dict_alloc(const ProtocolBase* const* protocols, size_t count) { furi_check(protocols); ProtocolDict* dict = malloc(sizeof(ProtocolDict) + (sizeof(void*) * count)); diff --git a/lib/toolbox/protocols/protocol_dict.h b/lib/toolbox/protocols/protocol_dict.h index 543b3ead2..a7e02a988 100644 --- a/lib/toolbox/protocols/protocol_dict.h +++ b/lib/toolbox/protocols/protocol_dict.h @@ -12,7 +12,7 @@ typedef int32_t ProtocolId; #define PROTOCOL_NO (-1) #define PROTOCOL_ALL_FEATURES (0xFFFFFFFF) -ProtocolDict* protocol_dict_alloc(const ProtocolBase** protocols, size_t protocol_count); +ProtocolDict* protocol_dict_alloc(const ProtocolBase* const* protocols, size_t protocol_count); void protocol_dict_free(ProtocolDict* dict); diff --git a/lib/toolbox/settings_helpers/submenu_based.c b/lib/toolbox/settings_helpers/submenu_based.c new file mode 100644 index 000000000..ce785dd73 --- /dev/null +++ b/lib/toolbox/settings_helpers/submenu_based.c @@ -0,0 +1,103 @@ +#include "submenu_based.h" +#include + +struct SubmenuSettingsHelper { + const SubmenuSettingsHelperDescriptor* descriptor; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + Submenu* submenu; + uint32_t submenu_view_id; + uint32_t main_scene_id; +}; + +SubmenuSettingsHelper* + submenu_settings_helpers_alloc(const SubmenuSettingsHelperDescriptor* descriptor) { + furi_check(descriptor); + SubmenuSettingsHelper* helper = malloc(sizeof(SubmenuSettingsHelper)); + helper->descriptor = descriptor; + return helper; +} + +void submenu_settings_helpers_assign_objects( + SubmenuSettingsHelper* helper, + ViewDispatcher* view_dispatcher, + SceneManager* scene_manager, + Submenu* submenu, + uint32_t submenu_view_id, + uint32_t main_scene_id) { + furi_check(helper); + furi_check(view_dispatcher); + furi_check(scene_manager); + furi_check(submenu); + helper->view_dispatcher = view_dispatcher; + helper->scene_manager = scene_manager; + helper->submenu = submenu; + helper->submenu_view_id = submenu_view_id; + helper->main_scene_id = main_scene_id; +} + +void submenu_settings_helpers_free(SubmenuSettingsHelper* helper) { + free(helper); +} + +bool submenu_settings_helpers_app_start(SubmenuSettingsHelper* helper, void* arg) { + furi_check(helper); + if(!arg) return false; + + const char* option = arg; + for(size_t i = 0; i < helper->descriptor->options_cnt; i++) { + if(strcmp(helper->descriptor->options[i].name, option) == 0) { + scene_manager_next_scene( + helper->scene_manager, helper->descriptor->options[i].scene_id); + return true; + } + } + + return false; +} + +static void + submenu_settings_helpers_callback(void* context, InputType input_type, uint32_t index) { + SubmenuSettingsHelper* helper = context; + if(input_type == InputTypeShort) { + view_dispatcher_send_custom_event(helper->view_dispatcher, index); + } else if(input_type == InputTypeLong) { + archive_favorites_handle_setting_pin_unpin( + helper->descriptor->app_name, helper->descriptor->options[index].name); + } +} + +void submenu_settings_helpers_scene_enter(SubmenuSettingsHelper* helper) { + furi_check(helper); + for(size_t i = 0; i < helper->descriptor->options_cnt; i++) { + submenu_add_item_ex( + helper->submenu, + helper->descriptor->options[i].name, + i, + submenu_settings_helpers_callback, + helper); + } + + submenu_set_selected_item( + helper->submenu, + scene_manager_get_scene_state(helper->scene_manager, helper->main_scene_id)); + view_dispatcher_switch_to_view(helper->view_dispatcher, helper->submenu_view_id); +} + +bool submenu_settings_helpers_scene_event(SubmenuSettingsHelper* helper, SceneManagerEvent event) { + furi_check(helper); + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_next_scene( + helper->scene_manager, helper->descriptor->options[event.event].scene_id); + scene_manager_set_scene_state(helper->scene_manager, helper->main_scene_id, event.event); + return true; + } + + return false; +} + +void submenu_settings_helpers_scene_exit(SubmenuSettingsHelper* helper) { + furi_check(helper); + submenu_reset(helper->submenu); +} diff --git a/lib/toolbox/settings_helpers/submenu_based.h b/lib/toolbox/settings_helpers/submenu_based.h new file mode 100644 index 000000000..6929cb04a --- /dev/null +++ b/lib/toolbox/settings_helpers/submenu_based.h @@ -0,0 +1,89 @@ +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*SubmenuSettingsHelpherCallback)(void* context, uint32_t index); + +typedef struct { + const char* name; + uint32_t scene_id; +} SubmenuSettingsHelperOption; + +typedef struct { + const char* app_name; + size_t options_cnt; + SubmenuSettingsHelperOption options[]; +} SubmenuSettingsHelperDescriptor; + +typedef struct SubmenuSettingsHelper SubmenuSettingsHelper; + +/** + * @brief Allocates a submenu-based settings helper + * @param descriptor settings descriptor + */ +SubmenuSettingsHelper* + submenu_settings_helpers_alloc(const SubmenuSettingsHelperDescriptor* descriptor); + +/** + * @brief Assigns dynamic objects to the submenu-based settings helper + * @param helper helper object + * @param view_dispatcher ViewDispatcher + * @param scene_manager SceneManager + * @param submenu Submenu + * @param submenu_view_id Submenu view id in the ViewDispatcher + * @param main_scene_id Main scene id in the SceneManager + */ +void submenu_settings_helpers_assign_objects( + SubmenuSettingsHelper* helper, + ViewDispatcher* view_dispatcher, + SceneManager* scene_manager, + Submenu* submenu, + uint32_t submenu_view_id, + uint32_t main_scene_id); + +/** + * @brief Frees a submenu-based settings helper + * @param helper helper object + */ +void submenu_settings_helpers_free(SubmenuSettingsHelper* helper); + +/** + * @brief App start callback for the submenu-based settings helper + * + * If an argument containing one of the options was provided, launches the + * corresponding scene. + * + * @param helper helper object + * @param arg app argument, may be NULL + * @returns true if a setting name was provided in the argument, false if normal + * app operation shall commence + */ +bool submenu_settings_helpers_app_start(SubmenuSettingsHelper* helper, void* arg); + +/** + * @brief Main scene enter callback for the submenu-based settings helper + * @param helper helper object + */ +void submenu_settings_helpers_scene_enter(SubmenuSettingsHelper* helper); + +/** + * @brief Main scene event callback for the submenu-based settings helper + * @param helper helper object + * @param event event data + * @returns true if the event was consumed, false otherwise + */ +bool submenu_settings_helpers_scene_event(SubmenuSettingsHelper* helper, SceneManagerEvent event); + +/** + * @brief Main scene exit callback for the submenu-based settings helper + * @param helper helper object + */ +void submenu_settings_helpers_scene_exit(SubmenuSettingsHelper* helper); + +#ifdef __cplusplus +} +#endif diff --git a/scripts/debug/flipperapps.py b/scripts/debug/flipperapps.py index 81aa43c34..6d2d4c0a9 100644 --- a/scripts/debug/flipperapps.py +++ b/scripts/debug/flipperapps.py @@ -7,6 +7,33 @@ import zlib import gdb +class bcolors: + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + + +LOG_PREFIX = "[FURI]" + + +def error(line): + print(f"{bcolors.FAIL}{LOG_PREFIX} {line}{bcolors.ENDC}") + + +def warning(line): + print(f"{bcolors.WARNING}{LOG_PREFIX} {line}{bcolors.ENDC}") + + +def info(line): + print(f"{bcolors.OKGREEN}{LOG_PREFIX} {line}{bcolors.ENDC}") + + def get_file_crc32(filename): with open(filename, "rb") as f: return zlib.crc32(f.read()) @@ -39,12 +66,12 @@ class AppState: def is_debug_available(self) -> bool: have_debug_info = bool(self.debug_link_elf and self.debug_link_crc) if not have_debug_info: - print("No debug info available for this app") + warning("No debug info available for this app") return False debug_elf_path = self.get_original_elf_path() debug_elf_crc32 = get_file_crc32(debug_elf_path) if self.debug_link_crc != debug_elf_crc32: - print( + warning( f"Debug info ({debug_elf_path}) CRC mismatch: {self.debug_link_crc:08x} != {debug_elf_crc32:08x}, rebuild app" ) return False @@ -52,7 +79,7 @@ class AppState: def get_gdb_load_command(self) -> str: load_path = self.get_original_elf_path() - print(f"Loading debug information from {load_path}") + info(f"Loading debug information from {load_path}") load_command = ( f"add-symbol-file -readnow {load_path} 0x{self.text_address:08x} " ) @@ -121,12 +148,12 @@ class SetFapDebugElfRoot(gdb.Command): AppState.DEBUG_ELF_ROOT = arg try: global helper - print(f"Set '{arg}' as debug info lookup path for Flipper external apps") + info(f"Set '{arg}' as debug info lookup path for Flipper external apps") helper.attach_to_fw() gdb.events.stop.connect(helper.handle_stop) gdb.events.gdb_exiting.connect(helper.handle_exit) except gdb.error as e: - print(f"Support for Flipper external apps debug is not available: {e}") + error(f"Support for Flipper external apps debug is not available: {e}") class FlipperAppStateHelper: @@ -148,13 +175,29 @@ class FlipperAppStateHelper: gdb.execute(command) return True except gdb.error as e: - print(f"Failed to execute GDB command '{command}': {e}") + error(f"Failed to execute GDB command '{command}': {e}") return False + def _get_crash_message(self): + message = self.app_check_message.value() + if message == 1: + return "furi_assert failed" + elif message == 2: + return "furi_check failed" + else: + return message + def _sync_apps(self) -> None: + crash_message = self._get_crash_message() + if crash_message: + crash_message = f"! System crashed: {crash_message} !" + error("!" * len(crash_message)) + error(crash_message) + error("!" * len(crash_message)) + self.set_debug_mode(True) if not (app_list := self.app_list_ptr.value()): - print("Reset app loader state") + info("Reset app loader state") for app in self._current_apps: self._exec_gdb_command(app.get_gdb_unload_command()) self._current_apps = [] @@ -167,22 +210,23 @@ class FlipperAppStateHelper: for app in self._current_apps.copy(): if app.entry_address not in loaded_apps: - print(f"Application {app.name} is no longer loaded") + warning(f"Application {app.name} is no longer loaded") if not self._exec_gdb_command(app.get_gdb_unload_command()): - print(f"Failed to unload debug info for {app.name}") + error(f"Failed to unload debug info for {app.name}") self._current_apps.remove(app) for entry_point, app in loaded_apps.items(): if entry_point not in set(app.entry_address for app in self._current_apps): new_app_state = AppState.from_gdb(app) - print(f"New application loaded. Adding debug info") + warning(f"New application loaded. Adding debug info") if self._exec_gdb_command(new_app_state.get_gdb_load_command()): self._current_apps.append(new_app_state) else: - print(f"Failed to load debug info for {new_app_state}") + error(f"Failed to load debug info for {new_app_state}") def attach_to_fw(self) -> None: - print("Attaching to Flipper firmware") + info("Attaching to Flipper firmware") + self.app_check_message = gdb.lookup_global_symbol("__furi_check_message") self.app_list_ptr = gdb.lookup_global_symbol( "flipper_application_loaded_app_list" ) @@ -200,10 +244,10 @@ class FlipperAppStateHelper: try: gdb.execute(f"set variable furi_hal_debug_gdb_session_active = {int(mode)}") except gdb.error as e: - print(f"Failed to set debug mode: {e}") + error(f"Failed to set debug mode: {e}") # Init additional 'fap-set-debug-elf-root' command and set up hooks SetFapDebugElfRoot() helper = FlipperAppStateHelper() -print("Support for Flipper external apps debug is loaded") +info("Support for Flipper external apps debug is loaded") diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index b5b5d6e12..4da23b3dd 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -158,7 +158,7 @@ class AppManager: f"App {kw.get('appid')} cannot have fal_embedded set" ) - if apptype in AppBuildset.dist_app_types: + if apptype in AppBuildset.DIST_APP_TYPES: # For distributing .fap's resources, there's "fap_file_assets" for app_property in ("resources",): if kw.get(app_property): @@ -258,14 +258,12 @@ class AppBuildset: FlipperAppType.DEBUG: True, FlipperAppType.MENUEXTERNAL: False, } - - @classmethod - @property - def dist_app_types(cls): - """Applications that are installed on SD card""" - return list( - entry[0] for entry in cls.EXTERNAL_APP_TYPES_MAP.items() if entry[1] - ) + DIST_APP_TYPES = list( + # Applications that are installed on SD card + entry[0] + for entry in EXTERNAL_APP_TYPES_MAP.items() + if entry[1] + ) @staticmethod def print_writer(message): diff --git a/scripts/fbt/sdk/collector.py b/scripts/fbt/sdk/collector.py index 5615f105e..14653eb44 100644 --- a/scripts/fbt/sdk/collector.py +++ b/scripts/fbt/sdk/collector.py @@ -113,7 +113,7 @@ def stringify_descr(type_descr): # Hack if isinstance(type_descr.ptr_to, FunctionType): return stringify_descr(type_descr.ptr_to) - return f"{stringify_descr(type_descr.ptr_to)}*" + return f"{stringify_descr(type_descr.ptr_to)}*{' const' if type_descr.const else ''}" elif isinstance(type_descr, Type): return ( f"{'const ' if type_descr.const else ''}" diff --git a/scripts/fbt_tools/fbt_apps.py b/scripts/fbt_tools/fbt_apps.py index 3a17a79a3..c48e4ba80 100644 --- a/scripts/fbt_tools/fbt_apps.py +++ b/scripts/fbt_tools/fbt_apps.py @@ -150,11 +150,22 @@ def DumpApplicationConfig(target, source, env): print(fg.boldgreen("Firmware modules configuration:")) for apptype in FlipperAppType: app_sublist = env["APPBUILD"].get_apps_of_type(apptype) - if app_sublist: + # Print a warning if any apps in the list have same .order value + unique_order_values = set(app.order for app in app_sublist) + if len(app_sublist) != len(unique_order_values) and max(unique_order_values): + print( + fg.red(f"{apptype.value}: ") + + fg.yellow( + "Duplicate .order values in group:\n\t" + + ", ".join(f"{app.appid} ({app.order})" for app in app_sublist) + ) + ) + elif app_sublist: print( fg.green(f"{apptype.value}:\n\t"), ", ".join(app.appid for app in app_sublist), ) + if incompatible_ext_apps := env["APPBUILD"].get_incompatible_ext_apps(): print( fg.blue("Incompatible apps (skipped):\n\t"), diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py index 40af5cebc..0182cf45f 100644 --- a/scripts/flipper/storage.py +++ b/scripts/flipper/storage.py @@ -109,6 +109,8 @@ class FlipperStorage: def start(self): self.port.open() + time.sleep(0.5) + self.read.until(self.CLI_PROMPT) self.port.reset_input_buffer() # Send a command with a known syntax to make sure the buffer is flushed self.send("device_info\r") diff --git a/scripts/flipper/utils/cdc.py b/scripts/flipper/utils/cdc.py index ee1125f77..00b20d6fb 100644 --- a/scripts/flipper/utils/cdc.py +++ b/scripts/flipper/utils/cdc.py @@ -1,3 +1,4 @@ +import os import serial.tools.list_ports as list_ports @@ -15,3 +16,8 @@ def resolve_port(logger, portname: str = "auto"): logger.error("Failed to find connected Flipper") elif len(flippers) > 1: logger.error("More than one Flipper is attached") + env_path = os.environ.get("FLIPPER_PATH") + if env_path: + if os.path.exists(env_path): + logger.info(f"Using FLIPPER_PATH from environment: {env_path}") + return env_path diff --git a/scripts/get_env.py b/scripts/get_env.py index e8f6b3b77..6d6b3cce5 100755 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -34,7 +34,11 @@ def get_commit_json(event): commit_url = event["pull_request"]["base"]["repo"]["commits_url"].replace( "{/sha}", f"/{event['pull_request']['head']['sha']}" ) - with urllib.request.urlopen(commit_url, context=context) as commit_file: + request = urllib.request.Request(commit_url) + if "GH_TOKEN" in os.environ: + request.add_header("Authorization", "Bearer %s" % (os.environ["GH_TOKEN"])) + + with urllib.request.urlopen(request, context=context) as commit_file: commit_json = json.loads(commit_file.read().decode("utf-8")) return commit_json diff --git a/scripts/lint.py b/scripts/lint.py index 8530209be..896cd3812 100755 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -35,21 +35,25 @@ class Main(App): self.parser_format.set_defaults(func=self.format) @staticmethod - def _filter_lint_directories(dirnames: list[str]): + def _filter_lint_directories( + dirpath: str, dirnames: list[str], excludes: tuple[str] + ): # Skipping 3rd-party code - usually resides in subfolder "lib" if "lib" in dirnames: dirnames.remove("lib") - # Skipping hidden folders + # Skipping hidden and excluded folders for dirname in dirnames.copy(): if dirname.startswith("."): dirnames.remove(dirname) + if os.path.join(dirpath, dirname).startswith(excludes): + dirnames.remove(dirname) - def _check_folders(self, folders: list): + def _check_folders(self, folders: list, excludes: tuple[str]): show_message = False pattern = re.compile(SOURCE_CODE_DIR_PATTERN) for folder in folders: for dirpath, dirnames, filenames in os.walk(folder): - self._filter_lint_directories(dirnames) + self._filter_lint_directories(dirpath, dirnames, excludes) for dirname in dirnames: if not pattern.match(dirname): @@ -61,11 +65,11 @@ class Main(App): "Folders are not renamed automatically, please fix it by yourself" ) - def _find_sources(self, folders: list): + def _find_sources(self, folders: list, excludes: tuple[str]): output = [] for folder in folders: for dirpath, dirnames, filenames in os.walk(folder): - self._filter_lint_directories(dirnames) + self._filter_lint_directories(dirpath, dirnames, excludes) for filename in filenames: ext = os.path.splitext(filename.lower())[1] @@ -168,14 +172,20 @@ class Main(App): def _perform(self, dry_run: bool): result = 0 - sources = self._find_sources(self.args.input) + excludes = [] + for folder in self.args.input.copy(): + if folder.startswith("!"): + excludes.append(folder.removeprefix("!")) + self.args.input.remove(folder) + excludes = tuple(excludes) + sources = self._find_sources(self.args.input, excludes) if not self._format_sources(sources, dry_run=dry_run): result |= 0b001 if not self._apply_file_naming_convention(sources, dry_run=dry_run): result |= 0b010 if not self._apply_file_permissions(sources, dry_run=dry_run): result |= 0b100 - self._check_folders(self.args.input) + self._check_folders(self.args.input, excludes) return result def check(self): diff --git a/scripts/power.py b/scripts/power.py index 50bb2d4f7..b13e63bd1 100755 --- a/scripts/power.py +++ b/scripts/power.py @@ -3,6 +3,8 @@ import time from typing import Optional +from serial.serialutil import SerialException + from flipper.app import App from flipper.storage import FlipperStorage from flipper.utils.cdc import resolve_port @@ -32,11 +34,11 @@ class Main(App): def _get_flipper(self, retry_count: Optional[int] = 1): port = None - self.logger.info(f"Attempting to find flipper with {retry_count} attempts.") - for i in range(retry_count): time.sleep(1) - self.logger.info(f"Attempting to find flipper #{i}.") + self.logger.info( + f"Attempting to find flipper (Attempt {i + 1}/{retry_count})." + ) if port := resolve_port(self.logger, self.args.port): self.logger.info(f"Found flipper at {port}") @@ -47,8 +49,16 @@ class Main(App): return None flipper = FlipperStorage(port) - flipper.start() - return flipper + for i in range(retry_count): + try: + flipper.start() + self.logger.info("Flipper successfully started.") + return flipper + except IOError as e: + self.logger.info( + f"Failed to start flipper (Attempt {i + 1}/{retry_count}): {e}" + ) + time.sleep(1) def power_off(self): if not (flipper := self._get_flipper(retry_count=10)): diff --git a/scripts/serial_cli_perf.py b/scripts/serial_cli_perf.py new file mode 100644 index 000000000..3d612e6de --- /dev/null +++ b/scripts/serial_cli_perf.py @@ -0,0 +1,71 @@ +import argparse +import logging +from serial import Serial +from random import randint +from time import time + +from flipper.utils.cdc import resolve_port + + +def main(): + logger = logging.getLogger() + parser = argparse.ArgumentParser() + parser.add_argument("-p", "--port", help="CDC Port", default="auto") + parser.add_argument( + "-l", "--length", type=int, help="Number of bytes to send", default=1024**2 + ) + args = parser.parse_args() + + if not (port := resolve_port(logger, args.port)): + logger.error("Is Flipper connected via USB and not in DFU mode?") + return 1 + port = Serial(port, 230400) + port.timeout = 2 + + port.read_until(b">: ") + port.write(b"echo\r") + port.read_until(b">: ") + + print(f"Transferring {args.length} bytes. Hang tight...") + + start_time = time() + + bytes_to_send = args.length + block_size = 1024 + success = True + while bytes_to_send: + actual_size = min(block_size, bytes_to_send) + # can't use 0x03 because that's ASCII ETX, or Ctrl+C + # block = bytes([randint(4, 255) for _ in range(actual_size)]) + block = bytes([4 + (i // 64) for i in range(actual_size)]) + + port.write(block) + return_block = port.read(actual_size) + + if return_block != block: + with open("block.bin", "wb") as f: + f.write(block) + with open("return_block.bin", "wb") as f: + f.write(return_block) + + logger.error( + "Incorrect block received. Saved to `block.bin' and `return_block.bin'." + ) + logger.error(f"{bytes_to_send} bytes left. Aborting.") + success = False + break + + bytes_to_send -= actual_size + + end_time = time() + delta = end_time - start_time + speed = args.length / delta + if success: + print(f"Speed: {speed/1024:.2f} KiB/s") + + port.write(b"\x03") # Ctrl+C + port.close() + + +if __name__ == "__main__": + main() diff --git a/scripts/testops.py b/scripts/testops.py index 4ae10c7f4..119453448 100644 --- a/scripts/testops.py +++ b/scripts/testops.py @@ -1,17 +1,21 @@ #!/usr/bin/env python3 - import re -import sys import time +from datetime import datetime from typing import Optional +from serial.serialutil import SerialException + from flipper.app import App from flipper.storage import FlipperStorage from flipper.utils.cdc import resolve_port class Main(App): - # this is basic use without sub-commands, simply to reboot flipper / power it off, not meant as a full CLI wrapper + def __init__(self, no_exit=False): + super().__init__(no_exit) + self.test_results = None + def init(self): self.parser.add_argument("-p", "--port", help="CDC Port", default="auto") self.parser.add_argument( @@ -32,25 +36,34 @@ class Main(App): def _get_flipper(self, retry_count: Optional[int] = 1): port = None - self.logger.info(f"Attempting to find flipper with {retry_count} attempts.") - for i in range(retry_count): - self.logger.info(f"Attempt to find flipper #{i}.") + time.sleep(1) + self.logger.info( + f"Attempting to find flipper (Attempt {i + 1}/{retry_count})." + ) if port := resolve_port(self.logger, self.args.port): self.logger.info(f"Found flipper at {port}") - time.sleep(1) break - time.sleep(1) - if not port: - self.logger.info(f"Failed to find flipper {port}") + self.logger.info(f"Failed to find flipper") return None flipper = FlipperStorage(port) - flipper.start() - return flipper + for i in range(retry_count): + try: + flipper.start() + self.logger.info("Flipper successfully started.") + return flipper + except IOError as e: + self.logger.info( + f"Failed to start flipper (Attempt {i + 1}/{retry_count}): {e}" + ) + time.sleep(1) + + self.logger.error("Flipper failed to start after all retries.") + return None def await_flipper(self): if not (flipper := self._get_flipper(retry_count=self.args.timeout)): @@ -67,64 +80,108 @@ class Main(App): self.logger.info("Running unit tests") flipper.send("unit_tests" + "\r") self.logger.info("Waiting for unit tests to complete") - data = flipper.read.until(">: ") - self.logger.info("Parsing result") - - lines = data.decode().split("\r\n") - - tests_re = r"Failed tests: \d{0,}" - time_re = r"Consumed: \d{0,}" - leak_re = r"Leaked: \d{0,}" - status_re = r"Status: \w{3,}" - - tests_pattern = re.compile(tests_re) - time_pattern = re.compile(time_re) - leak_pattern = re.compile(leak_re) - status_pattern = re.compile(status_re) tests, elapsed_time, leak, status = None, None, None, None total = 0 + all_required_found = False - for line in lines: - self.logger.info(line) - if "()" in line: - total += 1 + full_output = [] - if not tests: - tests = re.match(tests_pattern, line) - if not elapsed_time: - elapsed_time = re.match(time_pattern, line) - if not leak: - leak = re.match(leak_pattern, line) - if not status: - status = re.match(status_pattern, line) + tests_pattern = re.compile(r"Failed tests: \d{0,}") + time_pattern = re.compile(r"Consumed: \d{0,}") + leak_pattern = re.compile(r"Leaked: \d{0,}") + status_pattern = re.compile(r"Status: \w{3,}") - if None in (tests, elapsed_time, leak, status): - self.logger.error( - f"Failed to parse output: {tests} {elapsed_time} {leak} {status}" + try: + while not all_required_found: + try: + line = flipper.read.until("\r\n", cut_eol=True).decode() + self.logger.info(line) + if "command not found," in line: + self.logger.error(f"Command not found: {line}") + return 1 + + if "()" in line: + total += 1 + self.logger.debug(f"Test completed: {line}") + + if not tests: + tests = tests_pattern.match(line) + if not elapsed_time: + elapsed_time = time_pattern.match(line) + if not leak: + leak = leak_pattern.match(line) + if not status: + status = status_pattern.match(line) + + pattern = re.compile( + r"(\[-]|\[\\]|\[\|]|\[/-]|\[[^\]]*\]|\x1b\[\d+D)" + ) + line_to_append = pattern.sub("", line) + pattern = re.compile(r"\[3D[^\]]*") + line_to_append = pattern.sub("", line_to_append) + line_to_append = f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')} {line_to_append}" + + full_output.append(line_to_append) + + if tests and elapsed_time and leak and status: + all_required_found = True + try: + remaining = flipper.read.until(">: ", cut_eol=True).decode() + if remaining.strip(): + full_output.append(remaining) + except: + pass + break + + except Exception as e: + self.logger.error(f"Error reading output: {e}") + raise + + if None in (tests, elapsed_time, leak, status): + raise RuntimeError( + f"Failed to parse output: {tests} {elapsed_time} {leak} {status}" + ) + + leak = int(re.findall(r"[- ]\d+", leak.group(0))[0]) + status = re.findall(r"\w+", status.group(0))[1] + tests = int(re.findall(r"\d+", tests.group(0))[0]) + elapsed_time = int(re.findall(r"\d+", elapsed_time.group(0))[0]) + + test_results = { + "full_output": "\n".join(full_output), + "total_tests": total, + "failed_tests": tests, + "elapsed_time_ms": elapsed_time, + "memory_leak_bytes": leak, + "status": status, + } + + self.test_results = test_results + + output_file = "unit_tests_output.txt" + with open(output_file, "w") as f: + f.write(test_results["full_output"]) + + print( + f"::notice:: Total tests: {total} Failed tests: {tests} Status: {status} Elapsed time: {elapsed_time / 1000} s Memory leak: {leak} bytes" ) - sys.exit(1) - leak = int(re.findall(r"[- ]\d+", leak.group(0))[0]) - status = re.findall(r"\w+", status.group(0))[1] - tests = int(re.findall(r"\d+", tests.group(0))[0]) - elapsed_time = int(re.findall(r"\d+", elapsed_time.group(0))[0]) + if tests > 0 or status != "PASSED": + self.logger.error(f"Got {tests} failed tests.") + self.logger.error(f"Leaked (not failing on this stat): {leak}") + self.logger.error(f"Status: {status}") + self.logger.error(f"Time: {elapsed_time / 1000} seconds") + return 1 - if tests > 0 or status != "PASSED": - self.logger.error(f"Got {tests} failed tests.") - self.logger.error(f"Leaked (not failing on this stat): {leak}") - self.logger.error(f"Status: {status}") - self.logger.error(f"Time: {elapsed_time/1000} seconds") + self.logger.info(f"Leaked (not failing on this stat): {leak}") + self.logger.info( + f"Tests ran successfully! Time elapsed {elapsed_time / 1000} seconds. Passed {total} tests." + ) + return 0 + + finally: flipper.stop() - return 1 - - self.logger.info(f"Leaked (not failing on this stat): {leak}") - self.logger.info( - f"Tests ran successfully! Time elapsed {elapsed_time/1000} seconds. Passed {total} tests." - ) - - flipper.stop() - return 0 if __name__ == "__main__": diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index 025f8341f..85a5ab724 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -16,15 +16,15 @@ $toolchain_dist_temp_path = "$download_dir\$toolchain_dist_folder" try { if (Test-Path -LiteralPath "$toolchain_target_path") { - Write-Host -NoNewline "Removing old Windows toolchain.." - Remove-Item -LiteralPath "$toolchain_target_path" -Force -Recurse - Write-Host "done!" + Write-Host -NoNewline "Removing old Windows toolchain.." + Remove-Item -LiteralPath "$toolchain_target_path" -Force -Recurse + Write-Host "done!" } if (Test-path -LiteralPath "$toolchain_target_path\..\current") { - Write-Host -NoNewline "Unlinking 'current'.." + Write-Host -NoNewline "Unlinking 'current'.." Remove-Item -LiteralPath "$toolchain_target_path\..\current" -Force - Write-Host "done!" + Write-Host "done!" } if (!(Test-Path -LiteralPath "$toolchain_zip_temp_path" -PathType Leaf)) { @@ -46,7 +46,8 @@ if (Test-Path -LiteralPath "$toolchain_dist_temp_path") { Write-Host -NoNewline "Extracting Windows toolchain.." # This is faster than Expand-Archive Add-Type -Assembly "System.IO.Compression.Filesystem" -[System.IO.Compression.ZipFile]::ExtractToDirectory("$toolchain_zip_temp_path", "$download_dir") +Add-Type -Assembly "System.Text.Encoding" +[System.IO.Compression.ZipFile]::ExtractToDirectory("$toolchain_zip_temp_path", "$download_dir", [System.Text.Encoding]::UTF8) # Expand-Archive -LiteralPath "$toolchain_zip_temp_path" -DestinationPath "$download_dir" Write-Host -NoNewline "moving.." diff --git a/scripts/ufbt/project_template/.vscode/settings.json b/scripts/ufbt/project_template/.vscode/settings.json index d304752a9..b2ee0ca3d 100644 --- a/scripts/ufbt/project_template/.vscode/settings.json +++ b/scripts/ufbt/project_template/.vscode/settings.json @@ -19,6 +19,7 @@ "[python]": { "editor.defaultFormatter": "ms-python.black-formatter" }, + "clangd.checkUpdates": false, "clangd.path": "@UFBT_TOOLCHAIN_CLANGD@", "clangd.arguments": [ "--query-driver=**/arm-none-eabi-*", diff --git a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml index 143847c4a..613590d47 100644 --- a/scripts/ufbt/project_template/app_template/.github/workflows/build.yml +++ b/scripts/ufbt/project_template/app_template/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: with: sdk-channel: ${{ matrix.sdk-channel }} - name: Upload app artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: # See ufbt action docs for other output variables name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }} diff --git a/scripts/update.py b/scripts/update.py index 2b6620744..1c877c23e 100755 --- a/scripts/update.py +++ b/scripts/update.py @@ -34,7 +34,7 @@ class Main(App): FLASH_BASE = 0x8000000 FLASH_PAGE_SIZE = 4 * 1024 - MIN_GAP_PAGES = 2 + MIN_GAP_PAGES = 0 # Update stage file larger than that is not loadable without fix # https://github.com/flipperdevices/flipperzero-firmware/pull/3676 diff --git a/site_scons/cc.scons b/site_scons/cc.scons index c5d99b896..9960d350c 100644 --- a/site_scons/cc.scons +++ b/site_scons/cc.scons @@ -31,6 +31,7 @@ ENV.AppendUnique( "-Wundef", "-fdata-sections", "-ffunction-sections", + "-Wa,-gdwarf-sections", "-fsingle-precision-constant", "-fno-math-errno", # Generates .su files with stack usage information diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index b5d51a0dd..23c9edb63 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.1,, +Version,+,86.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -57,6 +57,7 @@ Header,+,lib/flipper_application/plugins/plugin_manager.h,, Header,+,lib/flipper_format/flipper_format.h,, Header,+,lib/flipper_format/flipper_format_i.h,, Header,+,lib/flipper_format/flipper_format_stream.h,, +Header,+,lib/ieee754_parse_wrap/wrappers.h,, Header,+,lib/libusb_stm32/inc/hid_usage_button.h,, Header,+,lib/libusb_stm32/inc/hid_usage_consumer.h,, Header,+,lib/libusb_stm32/inc/hid_usage_desktop.h,, @@ -84,6 +85,7 @@ Header,+,lib/libusb_stm32/inc/usb_hid.h,, Header,+,lib/libusb_stm32/inc/usb_std.h,, Header,+,lib/libusb_stm32/inc/usb_tmc.h,, Header,+,lib/libusb_stm32/inc/usbd_core.h,, +Header,+,lib/mbedtls/include/mbedtls/aes.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, Header,+,lib/mbedtls/include/mbedtls/ecdh.h,, Header,+,lib/mbedtls/include/mbedtls/ecdsa.h,, @@ -150,6 +152,10 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/bit_buffer.h,, +Header,+,lib/toolbox/cli/cli_ansi.h,, +Header,+,lib/toolbox/cli/cli_command.h,, +Header,+,lib/toolbox/cli/cli_registry.h,, +Header,+,lib/toolbox/cli/shell/cli_shell.h,, Header,+,lib/toolbox/compress.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, @@ -161,6 +167,7 @@ Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5_calc.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/pipe.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, @@ -371,11 +378,18 @@ Function,-,__utoa,char*,"unsigned, char*, int" Function,+,__wrap___assert,void,"const char*, int, const char*" Function,+,__wrap___assert_func,void,"const char*, int, const char*, const char*" Function,+,__wrap_fflush,int,FILE* +Function,+,__wrap_fgetc,int,FILE* +Function,+,__wrap_fgets,char*,"char*, size_t, FILE*" +Function,+,__wrap_getc,int,FILE* +Function,+,__wrap_getchar,int, Function,+,__wrap_printf,int,"const char*, ..." Function,+,__wrap_putc,int,"int, FILE*" Function,+,__wrap_putchar,int,int Function,+,__wrap_puts,int,const char* Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_strtod,double,"const char*, char**" +Function,+,__wrap_strtof,float,"const char*, char**" +Function,+,__wrap_ungetc,int,"int, FILE*" Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." Function,-,_asniprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." @@ -768,18 +782,26 @@ Function,-,ceill,long double,long double Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* -Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*" -Function,+,cli_cmd_interrupt_received,_Bool,Cli* -Function,+,cli_delete_command,void,"Cli*, const char*" -Function,+,cli_getc,char,Cli* -Function,+,cli_is_connected,_Bool,Cli* -Function,+,cli_nl,void,Cli* +Function,+,cli_ansi_parser_alloc,CliAnsiParser*, +Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char" +Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser* +Function,+,cli_ansi_parser_free,void,CliAnsiParser* +Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide* Function,+,cli_print_usage,void,"const char*, const char*, const char*" -Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t" -Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" -Function,+,cli_session_close,void,Cli* -Function,+,cli_session_open,void,"Cli*, void*" -Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" +Function,+,cli_registry_add_command,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*" +Function,+,cli_registry_add_command_ex,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*, size_t" +Function,+,cli_registry_alloc,CliRegistry*, +Function,+,cli_registry_delete_command,void,"CliRegistry*, const char*" +Function,+,cli_registry_free,void,CliRegistry* +Function,+,cli_registry_reload_external_commands,void,"CliRegistry*, const CliCommandExternalConfig*" +Function,+,cli_registry_remove_external_commands,void,CliRegistry* +Function,+,cli_shell_alloc,CliShell*,"CliShellMotd, void*, PipeSide*, CliRegistry*, const CliCommandExternalConfig*" +Function,+,cli_shell_free,void,CliShell* +Function,+,cli_shell_join,void,CliShell* +Function,+,cli_shell_set_prompt,void,"CliShell*, const char*" +Function,+,cli_shell_start,void,CliShell* +Function,+,cli_vcp_disable,void,CliVcp* +Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* @@ -1031,6 +1053,7 @@ Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t" Function,+,flipper_format_rewind,_Bool,FlipperFormat* +Function,+,flipper_format_seek,_Bool,"FlipperFormat*, int32_t, FlipperFormatOffset" Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool" Function,+,flipper_format_stream_delete_key_and_write,_Bool,"Stream*, FlipperStreamWriteData*, _Bool" @@ -1039,6 +1062,7 @@ Function,+,flipper_format_stream_read_value_line,_Bool,"Stream*, const char*, Fl Function,+,flipper_format_stream_write_comment_cstr,_Bool,"Stream*, const char*" Function,+,flipper_format_stream_write_value_line,_Bool,"Stream*, FlipperStreamWriteData*" Function,+,flipper_format_string_alloc,FlipperFormat*, +Function,+,flipper_format_tell,size_t,FlipperFormat* Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" @@ -1049,6 +1073,7 @@ Function,+,flipper_format_update_uint32,_Bool,"FlipperFormat*, const char*, cons Function,+,flipper_format_write_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_write_comment,_Bool,"FlipperFormat*, FuriString*" Function,+,flipper_format_write_comment_cstr,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_write_empty_line,_Bool,FlipperFormat* Function,+,flipper_format_write_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_write_header,_Bool,"FlipperFormat*, FuriString*, const uint32_t" Function,+,flipper_format_write_header_cstr,_Bool,"FlipperFormat*, const char*, const uint32_t" @@ -1125,6 +1150,7 @@ Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMes Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_stream_buffer,void,"FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" +Function,+,furi_event_loop_subscribe_thread_flags,void,"FuriEventLoop*, FuriEventLoopThreadFlagsCallback, void*" Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*" Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer* @@ -1135,6 +1161,7 @@ Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer* Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t" Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer* Function,+,furi_event_loop_unsubscribe,void,"FuriEventLoop*, FuriEventLoopObject*" +Function,+,furi_event_loop_unsubscribe_thread_flags,void,FuriEventLoop* Function,+,furi_get_tick,uint32_t, Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* @@ -1277,23 +1304,23 @@ Function,+,furi_hal_hid_u2f_get_request,uint32_t,uint8_t* Function,+,furi_hal_hid_u2f_is_connected,_Bool, Function,+,furi_hal_hid_u2f_send_response,void,"uint8_t*, uint8_t" Function,+,furi_hal_hid_u2f_set_callback,void,"HidU2fCallback, void*" -Function,+,furi_hal_i2c_acquire,void,FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_acquire,void,const FuriHalI2cBusHandle* Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, -Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" -Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" -Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" -Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_rx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" -Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_tx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" -Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" -Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" +Function,+,furi_hal_i2c_is_device_ready,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_read_reg_16,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" +Function,+,furi_hal_i2c_read_reg_8,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" +Function,+,furi_hal_i2c_release,void,const FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_rx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_rx_ext,_Bool,"const FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx_ext,_Bool,"const FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_16,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_8,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_info_get,void,"PropertyValueCallback, char, void*" Function,+,furi_hal_info_get_api_version,void,"uint16_t*, uint16_t*" Function,-,furi_hal_init,void, @@ -1427,6 +1454,7 @@ Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_configure_framing,void,"FuriHalSerialHandle*, FuriHalSerialDataBits, FuriHalSerialParity, FuriHalSerialStopBits" Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId Function,+,furi_hal_serial_control_deinit,void, Function,+,furi_hal_serial_control_init,void, @@ -1458,20 +1486,20 @@ Function,+,furi_hal_speaker_release,void, Function,+,furi_hal_speaker_set_volume,void,float Function,+,furi_hal_speaker_start,void,"float, float" Function,+,furi_hal_speaker_stop,void, -Function,+,furi_hal_spi_acquire,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_acquire,void,const FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_deinit,void,FuriHalSpiBus* -Function,+,furi_hal_spi_bus_handle_deinit,void,FuriHalSpiBusHandle* -Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_handle_deinit,void,const FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_handle_init,void,const FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* -Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_trx_dma,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_rx,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx_dma,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_tx,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t" Function,-,furi_hal_spi_config_deinit_early,void, Function,-,furi_hal_spi_config_init,void, Function,-,furi_hal_spi_config_init_early,void, Function,-,furi_hal_spi_dma_init,void, -Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_release,void,const FuriHalSpiBusHandle* Function,+,furi_hal_switch,void,void* Function,+,furi_hal_usb_ccid_insert_smartcard,void, Function,+,furi_hal_usb_ccid_remove_smartcard,void, @@ -1484,7 +1512,7 @@ Function,+,furi_hal_usb_is_locked,_Bool, Function,+,furi_hal_usb_lock,void, Function,+,furi_hal_usb_reinit,void, Function,+,furi_hal_usb_set_config,_Bool,"FuriHalUsbInterface*, void*" -Function,-,furi_hal_usb_set_state_callback,void,"FuriHalUsbStateCallback, void*" +Function,+,furi_hal_usb_set_state_callback,void,"FuriHalUsbStateCallback, void*" Function,+,furi_hal_usb_unlock,void, Function,+,furi_hal_version_do_i_belong_here,_Bool, Function,+,furi_hal_version_get_ble_local_device_name_ptr,const char*, @@ -1574,6 +1602,7 @@ Function,+,furi_stream_buffer_receive,size_t,"FuriStreamBuffer*, void*, size_t, Function,+,furi_stream_buffer_reset,FuriStatus,FuriStreamBuffer* Function,+,furi_stream_buffer_send,size_t,"FuriStreamBuffer*, const void*, size_t, uint32_t" Function,+,furi_stream_buffer_spaces_available,size_t,FuriStreamBuffer* +Function,+,furi_stream_get_trigger_level,size_t,FuriStreamBuffer* Function,+,furi_stream_set_trigger_level,_Bool,"FuriStreamBuffer*, size_t" Function,+,furi_string_alloc,FuriString*, Function,+,furi_string_alloc_move,FuriString*,FuriString* @@ -1654,7 +1683,8 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* -Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, +Function,+,furi_thread_get_stdin_callback,void,"FuriThreadStdinReadCallback*, void**" +Function,+,furi_thread_get_stdout_callback,void,"FuriThreadStdoutWriteCallback*, void**" Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_list_alloc,FuriThreadList*, @@ -1674,9 +1704,12 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" -Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback +Function,+,furi_thread_set_stdin_callback,void,"FuriThreadStdinReadCallback, void*" +Function,+,furi_thread_set_stdout_callback,void,"FuriThreadStdoutWriteCallback, void*" Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* +Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" +Function,+,furi_thread_stdin_unread,void,"char*, size_t" Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId @@ -1717,7 +1750,7 @@ Function,-,getchar,int, Function,-,getchar_unlocked,int, Function,-,getenv,char*,const char* Function,-,gets,char*,char* -Function,-,getsubopt,int,"char**, char**, char**" +Function,-,getsubopt,int,"char**, char* const*, char**" Function,-,getw,int,FILE* Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" @@ -1828,6 +1861,9 @@ Function,-,llrintl,long long int,long double Function,-,llround,long long int,double Function,-,llroundf,long long int,float Function,-,llroundl,long long int,long double +Function,+,loader_clear_launch_queue,void,Loader* +Function,+,loader_enqueue_launch,void,"Loader*, const char*, const char*, LoaderDeferredLaunchFlag" +Function,+,loader_get_application_launch_path,_Bool,"Loader*, FuriString*" Function,+,loader_get_application_name,_Bool,"Loader*, FuriString*" Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_is_locked,_Bool,Loader* @@ -1879,6 +1915,21 @@ Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Boo Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* Function,+,manchester_encoder_reset,void,ManchesterEncoderState* Function,+,maxim_crc8,uint8_t,"const uint8_t*, const uint8_t, const uint8_t" +Function,-,mbedtls_aes_crypt_cbc,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb128,int,"mbedtls_aes_context*, int, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb8,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ctr,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ecb,int,"mbedtls_aes_context*, int, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_aes_crypt_ofb,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_xts,int,"mbedtls_aes_xts_context*, int, size_t, const unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_free,void,mbedtls_aes_context* +Function,-,mbedtls_aes_init,void,mbedtls_aes_context* +Function,-,mbedtls_aes_setkey_dec,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_setkey_enc,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_free,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_init,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_setkey_dec,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_setkey_enc,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" Function,-,mbedtls_des3_crypt_cbc,int,"mbedtls_des3_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" Function,-,mbedtls_des3_crypt_ecb,int,"mbedtls_des3_context*, const unsigned char[8], unsigned char[8]" Function,-,mbedtls_des3_free,void,mbedtls_des3_context* @@ -1902,6 +1953,7 @@ Function,-,mbedtls_ecdh_can_do,int,mbedtls_ecp_group_id Function,-,mbedtls_ecdh_compute_shared,int,"mbedtls_ecp_group*, mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecdh_free,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_gen_public,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_get_grp_id,mbedtls_ecp_group_id,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_get_params,int,"mbedtls_ecdh_context*, const mbedtls_ecp_keypair*, mbedtls_ecdh_side" Function,-,mbedtls_ecdh_init,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_make_params,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" @@ -1942,7 +1994,9 @@ Function,-,mbedtls_ecp_group_init,void,mbedtls_ecp_group* Function,-,mbedtls_ecp_group_load,int,"mbedtls_ecp_group*, mbedtls_ecp_group_id" Function,-,mbedtls_ecp_grp_id_list,const mbedtls_ecp_group_id*, Function,-,mbedtls_ecp_is_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_keypair_calc_public,int,"mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_keypair_free,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_keypair_get_group_id,mbedtls_ecp_group_id,const mbedtls_ecp_keypair* Function,-,mbedtls_ecp_keypair_init,void,mbedtls_ecp_keypair* Function,-,mbedtls_ecp_mul,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_mul_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecp_restart_ctx*" @@ -1955,6 +2009,7 @@ Function,-,mbedtls_ecp_point_read_binary,int,"const mbedtls_ecp_group*, mbedtls_ Function,-,mbedtls_ecp_point_read_string,int,"mbedtls_ecp_point*, int, const char*, const char*" Function,-,mbedtls_ecp_point_write_binary,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_read_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_set_public_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const mbedtls_ecp_point*" Function,-,mbedtls_ecp_set_zero,int,mbedtls_ecp_point* Function,-,mbedtls_ecp_tls_read_group,int,"mbedtls_ecp_group*, const unsigned char**, size_t" Function,-,mbedtls_ecp_tls_read_group_id,int,"mbedtls_ecp_group_id*, const unsigned char**, size_t" @@ -1962,6 +2017,10 @@ Function,-,mbedtls_ecp_tls_read_point,int,"const mbedtls_ecp_group*, mbedtls_ecp Function,-,mbedtls_ecp_tls_write_group,int,"const mbedtls_ecp_group*, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_tls_write_point,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_write_key,int,"mbedtls_ecp_keypair*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_key_ext,int,"const mbedtls_ecp_keypair*, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_public_key,int,"const mbedtls_ecp_keypair*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_internal_aes_decrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_internal_aes_encrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" Function,-,mbedtls_internal_md5_process,int,"mbedtls_md5_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha256_process,int,"mbedtls_sha256_context*, const unsigned char[64]" @@ -2280,6 +2339,23 @@ Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" Function,-,pclose,int,FILE* Function,-,perror,void,const char* +Function,+,pipe_alloc,PipeSideBundle,"size_t, size_t" +Function,+,pipe_alloc_ex,PipeSideBundle,"PipeSideReceiveSettings, PipeSideReceiveSettings" +Function,+,pipe_attach_to_event_loop,void,"PipeSide*, FuriEventLoop*" +Function,+,pipe_bytes_available,size_t,PipeSide* +Function,+,pipe_detach_from_event_loop,void,PipeSide* +Function,+,pipe_free,void,PipeSide* +Function,+,pipe_install_as_stdio,void,PipeSide* +Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t" +Function,+,pipe_role,PipeRole,PipeSide* +Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t" +Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" +Function,+,pipe_set_callback_context,void,"PipeSide*, void*" +Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" +Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" +Function,+,pipe_set_state_check_period,void,"PipeSide*, FuriWait" +Function,+,pipe_spaces_available,size_t,PipeSide* +Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" Function,+,plugin_manager_free,void,PluginManager* Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" @@ -2305,9 +2381,11 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* +Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" Function,+,powf,float,"float, float" @@ -2315,7 +2393,7 @@ Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." -Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" +Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase* const*, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" Function,+,protocol_dict_decoders_feed_by_feature,ProtocolId,"ProtocolDict*, uint32_t, _Bool, uint32_t" Function,+,protocol_dict_decoders_feed_by_id,ProtocolId,"ProtocolDict*, size_t, _Bool, uint32_t" @@ -2416,6 +2494,7 @@ Function,-,scalbnl,long double,"long double, int" Function,-,scanf,int,"const char*, ..." Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" Function,+,scene_manager_free,void,SceneManager* +Function,+,scene_manager_get_current_scene,uint32_t,SceneManager* Function,+,scene_manager_get_scene_state,uint32_t,"const SceneManager*, uint32_t" Function,+,scene_manager_handle_back_event,_Bool,SceneManager* Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t" @@ -2477,29 +2556,29 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." -Function,+,st25r3916_change_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_change_test_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_check_reg,_Bool,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_clear_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_direct_cmd,void,"FuriHalSpiBusHandle*, uint8_t" -Function,+,st25r3916_get_irq,uint32_t,FuriHalSpiBusHandle* -Function,+,st25r3916_mask_irq,void,"FuriHalSpiBusHandle*, uint32_t" -Function,+,st25r3916_modify_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_read_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" -Function,+,st25r3916_read_fifo,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" -Function,+,st25r3916_read_pta_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_read_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,st25r3916_read_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,st25r3916_reg_read_fifo,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_reg_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_set_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_write_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" -Function,+,st25r3916_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_pta_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_ptf_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_change_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_change_test_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_check_reg,_Bool,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_clear_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_direct_cmd,void,"const FuriHalSpiBusHandle*, uint8_t" +Function,+,st25r3916_get_irq,uint32_t,const FuriHalSpiBusHandle* +Function,+,st25r3916_mask_irq,void,"const FuriHalSpiBusHandle*, uint32_t" +Function,+,st25r3916_modify_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_read_burst_regs,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" +Function,+,st25r3916_read_fifo,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" +Function,+,st25r3916_read_pta_mem,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_read_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_read_test_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_reg_read_fifo,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_reg_write_fifo,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_set_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_burst_regs,void,"const FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" +Function,+,st25r3916_write_fifo,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pta_mem,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_ptf_mem,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pttsn_mem,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_write_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_test_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_equivalent_path,_Bool,"Storage*, const char*, const char*" Function,+,storage_common_exists,_Bool,"Storage*, const char*" @@ -2648,6 +2727,7 @@ Function,-,strverscmp,int,"const char*, const char*" Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" +Function,+,submenu_add_item_ex,void,"Submenu*, const char*, uint32_t, SubmenuItemCallbackEx, void*" Function,+,submenu_alloc,Submenu*, Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu* @@ -2841,8 +2921,10 @@ Function,-,vsscanf,int,"const char*, const char*, __gnuc_va_list" Function,-,wcstombs,size_t,"char*, const wchar_t*, size_t" Function,-,wctomb,int,"char*, wchar_t" Function,+,widget_add_button_element,void,"Widget*, GuiButtonType, const char*, ButtonCallback, void*" -Function,+,widget_add_frame_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_circle_element,void,"Widget*, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_icon_element,void,"Widget*, uint8_t, uint8_t, const Icon*" +Function,+,widget_add_line_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_rect_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_string_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_string_multiline_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_text_box_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" @@ -2871,22 +2953,21 @@ Variable,-,__stdio_exit_handler,void (*)(), Variable,+,_ctype_,const char[], Variable,+,_impure_data,_reent, Variable,+,_impure_ptr,_reent*, -Variable,-,_sys_errlist,const char*[], +Variable,-,_sys_errlist,const char* const[], Variable,-,_sys_nerr,int, Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, -Variable,-,ble_profile_serial,const FuriHalBleProfileTemplate*, -Variable,+,cli_vcp,CliSession, +Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const, Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink, -Variable,+,firmware_api_interface,const ElfApiInterface*, +Variable,+,firmware_api_interface,const ElfApiInterface* const, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, -Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, -Variable,+,furi_hal_i2c_handle_power,FuriHalI2cBusHandle, +Variable,+,furi_hal_i2c_handle_external,const FuriHalI2cBusHandle, +Variable,+,furi_hal_i2c_handle_power,const FuriHalI2cBusHandle, Variable,+,furi_hal_spi_bus_d,FuriHalSpiBus, -Variable,+,furi_hal_spi_bus_handle_display,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_external,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_sd_fast,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_sd_slow,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_display,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_external,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_fast,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_slow,const FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_r,FuriHalSpiBus, Variable,+,furi_hal_spi_preset_1edge_low_16m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_1edge_low_2m,const LL_SPI_InitTypeDef, diff --git a/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h index 9a0d04cb6..23a88c215 100644 --- a/targets/f18/furi_hal/furi_hal_resources.h +++ b/targets/f18/furi_hal/furi_hal_resources.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -40,6 +41,7 @@ typedef struct { const GpioPin* pin; const char* name; const FuriHalAdcChannel channel; + const FuriHalPwmOutputId pwm_output; const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/targets/f18/furi_hal/furi_hal_spi_config.c b/targets/f18/furi_hal/furi_hal_spi_config.c index 8957bfe3a..a7393d3f0 100644 --- a/targets/f18/furi_hal/furi_hal_spi_config.c +++ b/targets/f18/furi_hal/furi_hal_spi_config.c @@ -143,7 +143,7 @@ FuriHalSpiBus furi_hal_spi_bus_d = { /* SPI Bus Handles */ inline static void furi_hal_spi_bus_r_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -189,7 +189,7 @@ inline static void furi_hal_spi_bus_r_handle_event_callback( } inline static void furi_hal_spi_bus_nfc_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -255,12 +255,12 @@ inline static void furi_hal_spi_bus_nfc_handle_event_callback( } static void furi_hal_spi_bus_handle_external_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { .bus = &furi_hal_spi_bus_r, .callback = furi_hal_spi_bus_handle_external_event_callback, .miso = &gpio_ext_pa6, @@ -270,7 +270,7 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { }; inline static void furi_hal_spi_bus_d_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -311,12 +311,12 @@ inline static void furi_hal_spi_bus_d_handle_event_callback( } static void furi_hal_spi_bus_handle_display_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_4m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_display_event_callback, .miso = &gpio_spi_d_miso, @@ -326,12 +326,12 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { }; static void furi_hal_spi_bus_handle_sd_fast_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_16m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_sd_fast_event_callback, .miso = &gpio_spi_d_miso, @@ -341,12 +341,12 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { }; static void furi_hal_spi_bus_handle_sd_slow_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_sd_slow_event_callback, .miso = &gpio_spi_d_miso, diff --git a/targets/f18/furi_hal/furi_hal_spi_config.h b/targets/f18/furi_hal/furi_hal_spi_config.h index da39fbfa6..2ff8b41b1 100644 --- a/targets/f18/furi_hal/furi_hal_spi_config.h +++ b/targets/f18/furi_hal/furi_hal_spi_config.h @@ -39,16 +39,16 @@ extern FuriHalSpiBus furi_hal_spi_bus_d; * Bus pins are floating on inactive state, CS high after initialization * */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_external; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_external; /** ST7567(Display) on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_display; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_display; /** SdCard in fast mode on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast; /** SdCard in slow mode on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow; #ifdef __cplusplus } diff --git a/targets/f18/target.json b/targets/f18/target.json index 3452c6707..1a8306596 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -36,7 +36,8 @@ "flipper18", "bit_lib", "toolbox", - "datetime" + "datetime", + "ieee754_parse_wrap" ], "excluded_sources": [ "furi_hal_infrared.c", diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index ee81f76a9..d142a6374 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,78.1,, +Version,+,86.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -61,6 +61,7 @@ Header,+,lib/flipper_format/flipper_format_stream.h,, Header,+,lib/ibutton/ibutton_key.h,, Header,+,lib/ibutton/ibutton_protocols.h,, Header,+,lib/ibutton/ibutton_worker.h,, +Header,+,lib/ieee754_parse_wrap/wrappers.h,, Header,+,lib/infrared/encoder_decoder/infrared.h,, Header,+,lib/infrared/worker/infrared_transmit.h,, Header,+,lib/infrared/worker/infrared_worker.h,, @@ -96,6 +97,7 @@ Header,+,lib/libusb_stm32/inc/usb_hid.h,, Header,+,lib/libusb_stm32/inc/usb_std.h,, Header,+,lib/libusb_stm32/inc/usb_tmc.h,, Header,+,lib/libusb_stm32/inc/usbd_core.h,, +Header,+,lib/mbedtls/include/mbedtls/aes.h,, Header,+,lib/mbedtls/include/mbedtls/des.h,, Header,+,lib/mbedtls/include/mbedtls/ecdh.h,, Header,+,lib/mbedtls/include/mbedtls/ecdsa.h,, @@ -222,6 +224,10 @@ Header,+,lib/subghz/transmitter.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/bit_buffer.h,, +Header,+,lib/toolbox/cli/cli_ansi.h,, +Header,+,lib/toolbox/cli/cli_command.h,, +Header,+,lib/toolbox/cli/cli_registry.h,, +Header,+,lib/toolbox/cli/shell/cli_shell.h,, Header,+,lib/toolbox/compress.h,, Header,+,lib/toolbox/crc32_calc.h,, Header,+,lib/toolbox/dir_walk.h,, @@ -233,6 +239,7 @@ Header,+,lib/toolbox/manchester_encoder.h,, Header,+,lib/toolbox/md5_calc.h,, Header,+,lib/toolbox/name_generator.h,, Header,+,lib/toolbox/path.h,, +Header,+,lib/toolbox/pipe.h,, Header,+,lib/toolbox/pretty_format.h,, Header,+,lib/toolbox/protocols/protocol_dict.h,, Header,+,lib/toolbox/pulse_protocols/pulse_glue.h,, @@ -448,11 +455,18 @@ Function,-,__utoa,char*,"unsigned, char*, int" Function,+,__wrap___assert,void,"const char*, int, const char*" Function,+,__wrap___assert_func,void,"const char*, int, const char*, const char*" Function,+,__wrap_fflush,int,FILE* +Function,+,__wrap_fgetc,int,FILE* +Function,+,__wrap_fgets,char*,"char*, size_t, FILE*" +Function,+,__wrap_getc,int,FILE* +Function,+,__wrap_getchar,int, Function,+,__wrap_printf,int,"const char*, ..." Function,+,__wrap_putc,int,"int, FILE*" Function,+,__wrap_putchar,int,int Function,+,__wrap_puts,int,const char* Function,+,__wrap_snprintf,int,"char*, size_t, const char*, ..." +Function,+,__wrap_strtod,double,"const char*, char**" +Function,+,__wrap_strtof,float,"const char*, char**" +Function,+,__wrap_ungetc,int,"int, FILE*" Function,+,__wrap_vsnprintf,int,"char*, size_t, const char*, va_list" Function,-,_asiprintf_r,int,"_reent*, char**, const char*, ..." Function,-,_asniprintf_r,char*,"_reent*, char*, size_t*, const char*, ..." @@ -845,18 +859,26 @@ Function,-,ceill,long double,long double Function,-,cfree,void,void* Function,-,clearerr,void,FILE* Function,-,clearerr_unlocked,void,FILE* -Function,+,cli_add_command,void,"Cli*, const char*, CliCommandFlag, CliCallback, void*" -Function,+,cli_cmd_interrupt_received,_Bool,Cli* -Function,+,cli_delete_command,void,"Cli*, const char*" -Function,+,cli_getc,char,Cli* -Function,+,cli_is_connected,_Bool,Cli* -Function,+,cli_nl,void,Cli* +Function,+,cli_ansi_parser_alloc,CliAnsiParser*, +Function,+,cli_ansi_parser_feed,CliAnsiParserResult,"CliAnsiParser*, char" +Function,+,cli_ansi_parser_feed_timeout,CliAnsiParserResult,CliAnsiParser* +Function,+,cli_ansi_parser_free,void,CliAnsiParser* +Function,+,cli_is_pipe_broken_or_is_etx_next_char,_Bool,PipeSide* Function,+,cli_print_usage,void,"const char*, const char*, const char*" -Function,+,cli_read,size_t,"Cli*, uint8_t*, size_t" -Function,+,cli_read_timeout,size_t,"Cli*, uint8_t*, size_t, uint32_t" -Function,+,cli_session_close,void,Cli* -Function,+,cli_session_open,void,"Cli*, void*" -Function,+,cli_write,void,"Cli*, const uint8_t*, size_t" +Function,+,cli_registry_add_command,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*" +Function,+,cli_registry_add_command_ex,void,"CliRegistry*, const char*, CliCommandFlag, CliCommandExecuteCallback, void*, size_t" +Function,+,cli_registry_alloc,CliRegistry*, +Function,+,cli_registry_delete_command,void,"CliRegistry*, const char*" +Function,+,cli_registry_free,void,CliRegistry* +Function,+,cli_registry_reload_external_commands,void,"CliRegistry*, const CliCommandExternalConfig*" +Function,+,cli_registry_remove_external_commands,void,CliRegistry* +Function,+,cli_shell_alloc,CliShell*,"CliShellMotd, void*, PipeSide*, CliRegistry*, const CliCommandExternalConfig*" +Function,+,cli_shell_free,void,CliShell* +Function,+,cli_shell_join,void,CliShell* +Function,+,cli_shell_set_prompt,void,"CliShell*, const char*" +Function,+,cli_shell_start,void,CliShell* +Function,+,cli_vcp_disable,void,CliVcp* +Function,+,cli_vcp_enable,void,CliVcp* Function,+,composite_api_resolver_add,void,"CompositeApiResolver*, const ElfApiInterface*" Function,+,composite_api_resolver_alloc,CompositeApiResolver*, Function,+,composite_api_resolver_free,void,CompositeApiResolver* @@ -993,6 +1015,7 @@ Function,+,elements_string_fit_width,void,"Canvas*, FuriString*, size_t" Function,+,elements_text_box,void,"Canvas*, int32_t, int32_t, size_t, size_t, Align, Align, const char*, _Bool" Function,+,elf_resolve_from_hashtable,_Bool,"const ElfApiInterface*, uint32_t, Elf32_Addr*" Function,+,elf_symbolname_hash,uint32_t,const char* +Function,+,em4305_write,void,LFRFIDEM4305* Function,+,empty_screen_alloc,EmptyScreen*, Function,+,empty_screen_free,void,EmptyScreen* Function,+,empty_screen_get_view,View*,EmptyScreen* @@ -1041,6 +1064,7 @@ Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*" Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*" Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t" Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*" +Function,+,felica_poller_read_blocks,FelicaError,"FelicaPoller*, const uint8_t, const uint8_t* const, uint16_t, FelicaPollerReadCommandResponse** const" Function,+,felica_poller_sync_read,FelicaError,"Nfc*, FelicaData*, const FelicaCardKey*" Function,+,felica_reset,void,FelicaData* Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" @@ -1141,6 +1165,7 @@ Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, FuriString*" Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t" Function,+,flipper_format_rewind,_Bool,FlipperFormat* +Function,+,flipper_format_seek,_Bool,"FlipperFormat*, int32_t, FlipperFormatOffset" Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat* Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool" Function,+,flipper_format_stream_delete_key_and_write,_Bool,"Stream*, FlipperStreamWriteData*, _Bool" @@ -1149,6 +1174,7 @@ Function,+,flipper_format_stream_read_value_line,_Bool,"Stream*, const char*, Fl Function,+,flipper_format_stream_write_comment_cstr,_Bool,"Stream*, const char*" Function,+,flipper_format_stream_write_value_line,_Bool,"Stream*, FlipperStreamWriteData*" Function,+,flipper_format_string_alloc,FlipperFormat*, +Function,+,flipper_format_tell,size_t,FlipperFormat* Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t" @@ -1159,6 +1185,7 @@ Function,+,flipper_format_update_uint32,_Bool,"FlipperFormat*, const char*, cons Function,+,flipper_format_write_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t" Function,+,flipper_format_write_comment,_Bool,"FlipperFormat*, FuriString*" Function,+,flipper_format_write_comment_cstr,_Bool,"FlipperFormat*, const char*" +Function,+,flipper_format_write_empty_line,_Bool,FlipperFormat* Function,+,flipper_format_write_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t" Function,+,flipper_format_write_header,_Bool,"FlipperFormat*, FuriString*, const uint32_t" Function,+,flipper_format_write_header_cstr,_Bool,"FlipperFormat*, const char*, const uint32_t" @@ -1235,6 +1262,7 @@ Function,+,furi_event_loop_subscribe_message_queue,void,"FuriEventLoop*, FuriMes Function,+,furi_event_loop_subscribe_mutex,void,"FuriEventLoop*, FuriMutex*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_semaphore,void,"FuriEventLoop*, FuriSemaphore*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" Function,+,furi_event_loop_subscribe_stream_buffer,void,"FuriEventLoop*, FuriStreamBuffer*, FuriEventLoopEvent, FuriEventLoopEventCallback, void*" +Function,+,furi_event_loop_subscribe_thread_flags,void,"FuriEventLoop*, FuriEventLoopThreadFlagsCallback, void*" Function,+,furi_event_loop_tick_set,void,"FuriEventLoop*, uint32_t, FuriEventLoopTickCallback, void*" Function,+,furi_event_loop_timer_alloc,FuriEventLoopTimer*,"FuriEventLoop*, FuriEventLoopTimerCallback, FuriEventLoopTimerType, void*" Function,+,furi_event_loop_timer_free,void,FuriEventLoopTimer* @@ -1245,6 +1273,7 @@ Function,+,furi_event_loop_timer_restart,void,FuriEventLoopTimer* Function,+,furi_event_loop_timer_start,void,"FuriEventLoopTimer*, uint32_t" Function,+,furi_event_loop_timer_stop,void,FuriEventLoopTimer* Function,+,furi_event_loop_unsubscribe,void,"FuriEventLoop*, FuriEventLoopObject*" +Function,+,furi_event_loop_unsubscribe_thread_flags,void,FuriEventLoop* Function,+,furi_get_tick,uint32_t, Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* @@ -1387,23 +1416,23 @@ Function,+,furi_hal_hid_u2f_get_request,uint32_t,uint8_t* Function,+,furi_hal_hid_u2f_is_connected,_Bool, Function,+,furi_hal_hid_u2f_send_response,void,"uint8_t*, uint8_t" Function,+,furi_hal_hid_u2f_set_callback,void,"HidU2fCallback, void*" -Function,+,furi_hal_i2c_acquire,void,FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_acquire,void,const FuriHalI2cBusHandle* Function,-,furi_hal_i2c_deinit_early,void, Function,-,furi_hal_i2c_init,void, Function,-,furi_hal_i2c_init_early,void, -Function,+,furi_hal_i2c_is_device_ready,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint32_t" -Function,+,furi_hal_i2c_read_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_read_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" -Function,+,furi_hal_i2c_read_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" -Function,+,furi_hal_i2c_release,void,FuriHalI2cBusHandle* -Function,+,furi_hal_i2c_rx,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_rx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" -Function,+,furi_hal_i2c_trx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_tx,_Bool,"FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_tx_ext,_Bool,"FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" -Function,+,furi_hal_i2c_write_mem,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" -Function,+,furi_hal_i2c_write_reg_16,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" -Function,+,furi_hal_i2c_write_reg_8,_Bool,"FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" +Function,+,furi_hal_i2c_is_device_ready,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint32_t" +Function,+,furi_hal_i2c_read_mem,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_read_reg_16,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t*, uint32_t" +Function,+,furi_hal_i2c_read_reg_8,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t*, uint32_t" +Function,+,furi_hal_i2c_release,void,const FuriHalI2cBusHandle* +Function,+,furi_hal_i2c_rx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_rx_ext,_Bool,"const FuriHalI2cBusHandle*, uint16_t, _Bool, uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_trx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx,_Bool,"const FuriHalI2cBusHandle*, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_tx_ext,_Bool,"const FuriHalI2cBusHandle*, uint16_t, _Bool, const uint8_t*, size_t, FuriHalI2cBegin, FuriHalI2cEnd, uint32_t" +Function,+,furi_hal_i2c_write_mem,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_16,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint16_t, uint32_t" +Function,+,furi_hal_i2c_write_reg_8,_Bool,"const FuriHalI2cBusHandle*, uint8_t, uint8_t, uint8_t, uint32_t" Function,+,furi_hal_ibutton_emulate_set_next,void,uint32_t Function,+,furi_hal_ibutton_emulate_start,void,"uint32_t, FuriHalIbuttonEmulateCallback, void*" Function,+,furi_hal_ibutton_emulate_stop,void, @@ -1616,6 +1645,7 @@ Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* +Function,+,furi_hal_serial_configure_framing,void,"FuriHalSerialHandle*, FuriHalSerialDataBits, FuriHalSerialParity, FuriHalSerialStopBits" Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId Function,+,furi_hal_serial_control_deinit,void, Function,+,furi_hal_serial_control_init,void, @@ -1647,20 +1677,20 @@ Function,+,furi_hal_speaker_release,void, Function,+,furi_hal_speaker_set_volume,void,float Function,+,furi_hal_speaker_start,void,"float, float" Function,+,furi_hal_speaker_stop,void, -Function,+,furi_hal_spi_acquire,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_acquire,void,const FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_deinit,void,FuriHalSpiBus* -Function,+,furi_hal_spi_bus_handle_deinit,void,FuriHalSpiBusHandle* -Function,+,furi_hal_spi_bus_handle_init,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_handle_deinit,void,const FuriHalSpiBusHandle* +Function,+,furi_hal_spi_bus_handle_init,void,const FuriHalSpiBusHandle* Function,+,furi_hal_spi_bus_init,void,FuriHalSpiBus* -Function,+,furi_hal_spi_bus_rx,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_trx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_trx_dma,_Bool,"FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,furi_hal_spi_bus_tx,_Bool,"FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_rx,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_trx_dma,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,furi_hal_spi_bus_tx,_Bool,"const FuriHalSpiBusHandle*, const uint8_t*, size_t, uint32_t" Function,-,furi_hal_spi_config_deinit_early,void, Function,-,furi_hal_spi_config_init,void, Function,-,furi_hal_spi_config_init_early,void, Function,-,furi_hal_spi_dma_init,void, -Function,+,furi_hal_spi_release,void,FuriHalSpiBusHandle* +Function,+,furi_hal_spi_release,void,const FuriHalSpiBusHandle* Function,-,furi_hal_subghz_dump_state,void, Function,+,furi_hal_subghz_flush_rx,void, Function,+,furi_hal_subghz_flush_tx,void, @@ -1703,7 +1733,7 @@ Function,+,furi_hal_usb_is_locked,_Bool, Function,+,furi_hal_usb_lock,void, Function,+,furi_hal_usb_reinit,void, Function,+,furi_hal_usb_set_config,_Bool,"FuriHalUsbInterface*, void*" -Function,-,furi_hal_usb_set_state_callback,void,"FuriHalUsbStateCallback, void*" +Function,+,furi_hal_usb_set_state_callback,void,"FuriHalUsbStateCallback, void*" Function,+,furi_hal_usb_unlock,void, Function,+,furi_hal_version_do_i_belong_here,_Bool, Function,+,furi_hal_version_get_ble_local_device_name_ptr,const char*, @@ -1793,6 +1823,7 @@ Function,+,furi_stream_buffer_receive,size_t,"FuriStreamBuffer*, void*, size_t, Function,+,furi_stream_buffer_reset,FuriStatus,FuriStreamBuffer* Function,+,furi_stream_buffer_send,size_t,"FuriStreamBuffer*, const void*, size_t, uint32_t" Function,+,furi_stream_buffer_spaces_available,size_t,FuriStreamBuffer* +Function,+,furi_stream_get_trigger_level,size_t,FuriStreamBuffer* Function,+,furi_stream_set_trigger_level,_Bool,"FuriStreamBuffer*, size_t" Function,+,furi_string_alloc,FuriString*, Function,+,furi_string_alloc_move,FuriString*,FuriString* @@ -1873,7 +1904,8 @@ Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_signal_callback,FuriThreadSignalCallback,const FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* -Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, +Function,+,furi_thread_get_stdin_callback,void,"FuriThreadStdinReadCallback*, void**" +Function,+,furi_thread_get_stdout_callback,void,"FuriThreadStdoutWriteCallback*, void**" Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_list_alloc,FuriThreadList*, @@ -1893,9 +1925,12 @@ Function,+,furi_thread_set_signal_callback,void,"FuriThread*, FuriThreadSignalCa Function,+,furi_thread_set_stack_size,void,"FuriThread*, size_t" Function,+,furi_thread_set_state_callback,void,"FuriThread*, FuriThreadStateCallback" Function,+,furi_thread_set_state_context,void,"FuriThread*, void*" -Function,+,furi_thread_set_stdout_callback,void,FuriThreadStdoutWriteCallback +Function,+,furi_thread_set_stdin_callback,void,"FuriThreadStdinReadCallback, void*" +Function,+,furi_thread_set_stdout_callback,void,"FuriThreadStdoutWriteCallback, void*" Function,+,furi_thread_signal,_Bool,"const FuriThread*, uint32_t, void*" Function,+,furi_thread_start,void,FuriThread* +Function,+,furi_thread_stdin_read,size_t,"char*, size_t, FuriWait" +Function,+,furi_thread_stdin_unread,void,"char*, size_t" Function,+,furi_thread_stdout_flush,int32_t, Function,+,furi_thread_stdout_write,size_t,"const char*, size_t" Function,+,furi_thread_suspend,void,FuriThreadId @@ -1936,7 +1971,7 @@ Function,-,getchar,int, Function,-,getchar_unlocked,int, Function,-,getenv,char*,const char* Function,-,gets,char*,char* -Function,-,getsubopt,int,"char**, char**, char**" +Function,-,getsubopt,int,"char**, char* const*, char**" Function,-,getw,int,FILE* Function,+,gui_add_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, void*" Function,+,gui_add_view_port,void,"Gui*, ViewPort*, GuiLayer" @@ -2254,6 +2289,9 @@ Function,-,llrintl,long long int,long double Function,-,llround,long long int,double Function,-,llroundf,long long int,float Function,-,llroundl,long long int,long double +Function,+,loader_clear_launch_queue,void,Loader* +Function,+,loader_enqueue_launch,void,"Loader*, const char*, const char*, LoaderDeferredLaunchFlag" +Function,+,loader_get_application_launch_path,_Bool,"Loader*, FuriString*" Function,+,loader_get_application_name,_Bool,"Loader*, FuriString*" Function,+,loader_get_pubsub,FuriPubSub*,Loader* Function,+,loader_is_locked,_Bool,Loader* @@ -2305,6 +2343,21 @@ Function,+,manchester_encoder_advance,_Bool,"ManchesterEncoderState*, const _Boo Function,+,manchester_encoder_finish,ManchesterEncoderResult,ManchesterEncoderState* Function,+,manchester_encoder_reset,void,ManchesterEncoderState* Function,+,maxim_crc8,uint8_t,"const uint8_t*, const uint8_t, const uint8_t" +Function,-,mbedtls_aes_crypt_cbc,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb128,int,"mbedtls_aes_context*, int, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_cfb8,int,"mbedtls_aes_context*, int, size_t, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ctr,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_ecb,int,"mbedtls_aes_context*, int, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_aes_crypt_ofb,int,"mbedtls_aes_context*, size_t, size_t*, unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_crypt_xts,int,"mbedtls_aes_xts_context*, int, size_t, const unsigned char[16], const unsigned char*, unsigned char*" +Function,-,mbedtls_aes_free,void,mbedtls_aes_context* +Function,-,mbedtls_aes_init,void,mbedtls_aes_context* +Function,-,mbedtls_aes_setkey_dec,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_setkey_enc,int,"mbedtls_aes_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_free,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_init,void,mbedtls_aes_xts_context* +Function,-,mbedtls_aes_xts_setkey_dec,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" +Function,-,mbedtls_aes_xts_setkey_enc,int,"mbedtls_aes_xts_context*, const unsigned char*, unsigned int" Function,-,mbedtls_des3_crypt_cbc,int,"mbedtls_des3_context*, int, size_t, unsigned char[8], const unsigned char*, unsigned char*" Function,-,mbedtls_des3_crypt_ecb,int,"mbedtls_des3_context*, const unsigned char[8], unsigned char[8]" Function,-,mbedtls_des3_free,void,mbedtls_des3_context* @@ -2328,6 +2381,7 @@ Function,-,mbedtls_ecdh_can_do,int,mbedtls_ecp_group_id Function,-,mbedtls_ecdh_compute_shared,int,"mbedtls_ecp_group*, mbedtls_mpi*, const mbedtls_ecp_point*, const mbedtls_mpi*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecdh_free,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_gen_public,int,"mbedtls_ecp_group*, mbedtls_mpi*, mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" +Function,-,mbedtls_ecdh_get_grp_id,mbedtls_ecp_group_id,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_get_params,int,"mbedtls_ecdh_context*, const mbedtls_ecp_keypair*, mbedtls_ecdh_side" Function,-,mbedtls_ecdh_init,void,mbedtls_ecdh_context* Function,-,mbedtls_ecdh_make_params,int,"mbedtls_ecdh_context*, size_t*, unsigned char*, size_t, int (*)(void*, unsigned char*, size_t), void*" @@ -2368,7 +2422,9 @@ Function,-,mbedtls_ecp_group_init,void,mbedtls_ecp_group* Function,-,mbedtls_ecp_group_load,int,"mbedtls_ecp_group*, mbedtls_ecp_group_id" Function,-,mbedtls_ecp_grp_id_list,const mbedtls_ecp_group_id*, Function,-,mbedtls_ecp_is_zero,int,mbedtls_ecp_point* +Function,-,mbedtls_ecp_keypair_calc_public,int,"mbedtls_ecp_keypair*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_keypair_free,void,mbedtls_ecp_keypair* +Function,-,mbedtls_ecp_keypair_get_group_id,mbedtls_ecp_group_id,const mbedtls_ecp_keypair* Function,-,mbedtls_ecp_keypair_init,void,mbedtls_ecp_keypair* Function,-,mbedtls_ecp_mul,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*" Function,-,mbedtls_ecp_mul_restartable,int,"mbedtls_ecp_group*, mbedtls_ecp_point*, const mbedtls_mpi*, const mbedtls_ecp_point*, int (*)(void*, unsigned char*, size_t), void*, mbedtls_ecp_restart_ctx*" @@ -2381,6 +2437,7 @@ Function,-,mbedtls_ecp_point_read_binary,int,"const mbedtls_ecp_group*, mbedtls_ Function,-,mbedtls_ecp_point_read_string,int,"mbedtls_ecp_point*, int, const char*, const char*" Function,-,mbedtls_ecp_point_write_binary,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_read_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const unsigned char*, size_t" +Function,-,mbedtls_ecp_set_public_key,int,"mbedtls_ecp_group_id, mbedtls_ecp_keypair*, const mbedtls_ecp_point*" Function,-,mbedtls_ecp_set_zero,int,mbedtls_ecp_point* Function,-,mbedtls_ecp_tls_read_group,int,"mbedtls_ecp_group*, const unsigned char**, size_t" Function,-,mbedtls_ecp_tls_read_group_id,int,"mbedtls_ecp_group_id*, const unsigned char**, size_t" @@ -2388,6 +2445,10 @@ Function,-,mbedtls_ecp_tls_read_point,int,"const mbedtls_ecp_group*, mbedtls_ecp Function,-,mbedtls_ecp_tls_write_group,int,"const mbedtls_ecp_group*, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_tls_write_point,int,"const mbedtls_ecp_group*, const mbedtls_ecp_point*, int, size_t*, unsigned char*, size_t" Function,-,mbedtls_ecp_write_key,int,"mbedtls_ecp_keypair*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_key_ext,int,"const mbedtls_ecp_keypair*, size_t*, unsigned char*, size_t" +Function,-,mbedtls_ecp_write_public_key,int,"const mbedtls_ecp_keypair*, int, size_t*, unsigned char*, size_t" +Function,-,mbedtls_internal_aes_decrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" +Function,-,mbedtls_internal_aes_encrypt,int,"mbedtls_aes_context*, const unsigned char[16], unsigned char[16]" Function,-,mbedtls_internal_md5_process,int,"mbedtls_md5_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha1_process,int,"mbedtls_sha1_context*, const unsigned char[64]" Function,-,mbedtls_internal_sha256_process,int,"mbedtls_sha256_context*, const unsigned char[64]" @@ -2615,7 +2676,7 @@ Function,+,mf_ultralight_3des_decrypt,void,"mbedtls_des3_context*, const uint8_t Function,+,mf_ultralight_3des_encrypt,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*" Function,+,mf_ultralight_3des_get_key,const uint8_t*,const MfUltralightData* Function,+,mf_ultralight_3des_key_valid,_Bool,const MfUltralightData* -Function,+,mf_ultralight_3des_shift_data,void,uint8_t* +Function,+,mf_ultralight_3des_shift_data,void,uint8_t* const Function,+,mf_ultralight_alloc,MfUltralightData*, Function,+,mf_ultralight_copy,void,"MfUltralightData*, const MfUltralightData*" Function,+,mf_ultralight_detect_protocol,_Bool,const Iso14443_3aData* @@ -2644,7 +2705,7 @@ Function,+,mf_ultralight_poller_read_page_from_sector,MfUltralightError,"MfUltra Function,+,mf_ultralight_poller_read_signature,MfUltralightError,"MfUltralightPoller*, MfUltralightSignature*" Function,+,mf_ultralight_poller_read_tearing_flag,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightTearingFlag*" Function,+,mf_ultralight_poller_read_version,MfUltralightError,"MfUltralightPoller*, MfUltralightVersion*" -Function,+,mf_ultralight_poller_sync_read_card,MfUltralightError,"Nfc*, MfUltralightData*" +Function,+,mf_ultralight_poller_sync_read_card,MfUltralightError,"Nfc*, MfUltralightData*, const MfUltralightPollerAuthContext*" Function,+,mf_ultralight_poller_sync_read_counter,MfUltralightError,"Nfc*, uint8_t, MfUltralightCounter*" Function,+,mf_ultralight_poller_sync_read_page,MfUltralightError,"Nfc*, uint16_t, MfUltralightPage*" Function,+,mf_ultralight_poller_sync_read_signature,MfUltralightError,"Nfc*, MfUltralightSignature*" @@ -2916,6 +2977,23 @@ Function,+,pb_skip_field,_Bool,"pb_istream_t*, pb_wire_type_t" Function,+,pb_write,_Bool,"pb_ostream_t*, const pb_byte_t*, size_t" Function,-,pclose,int,FILE* Function,-,perror,void,const char* +Function,+,pipe_alloc,PipeSideBundle,"size_t, size_t" +Function,+,pipe_alloc_ex,PipeSideBundle,"PipeSideReceiveSettings, PipeSideReceiveSettings" +Function,+,pipe_attach_to_event_loop,void,"PipeSide*, FuriEventLoop*" +Function,+,pipe_bytes_available,size_t,PipeSide* +Function,+,pipe_detach_from_event_loop,void,PipeSide* +Function,+,pipe_free,void,PipeSide* +Function,+,pipe_install_as_stdio,void,PipeSide* +Function,+,pipe_receive,size_t,"PipeSide*, void*, size_t" +Function,+,pipe_role,PipeRole,PipeSide* +Function,+,pipe_send,size_t,"PipeSide*, const void*, size_t" +Function,+,pipe_set_broken_callback,void,"PipeSide*, PipeSideBrokenCallback, FuriEventLoopEvent" +Function,+,pipe_set_callback_context,void,"PipeSide*, void*" +Function,+,pipe_set_data_arrived_callback,void,"PipeSide*, PipeSideDataArrivedCallback, FuriEventLoopEvent" +Function,+,pipe_set_space_freed_callback,void,"PipeSide*, PipeSideSpaceFreedCallback, FuriEventLoopEvent" +Function,+,pipe_set_state_check_period,void,"PipeSide*, FuriWait" +Function,+,pipe_spaces_available,size_t,PipeSide* +Function,+,pipe_state,PipeState,PipeSide* Function,+,plugin_manager_alloc,PluginManager*,"const char*, uint32_t, const ElfApiInterface*" Function,+,plugin_manager_free,void,PluginManager* Function,+,plugin_manager_get,const FlipperAppPluginDescriptor*,"PluginManager*, uint32_t" @@ -2941,9 +3019,11 @@ Function,-,pow,double,"double, double" Function,-,pow10,double,double Function,-,pow10f,float,float Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool" +Function,+,power_enable_otg,void,"Power*, _Bool" Function,+,power_get_info,void,"Power*, PowerInfo*" Function,+,power_get_pubsub,FuriPubSub*,Power* Function,+,power_is_battery_healthy,_Bool,Power* +Function,+,power_is_otg_enabled,_Bool,Power* Function,+,power_off,void,Power* Function,+,power_reboot,void,"Power*, PowerBootMode" Function,+,powf,float,"float, float" @@ -2951,7 +3031,7 @@ Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." -Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" +Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase* const*, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" Function,+,protocol_dict_decoders_feed_by_feature,ProtocolId,"ProtocolDict*, uint32_t, _Bool, uint32_t" Function,+,protocol_dict_decoders_feed_by_id,ProtocolId,"ProtocolDict*, size_t, _Bool, uint32_t" @@ -3052,6 +3132,7 @@ Function,-,scalbnl,long double,"long double, int" Function,-,scanf,int,"const char*, ..." Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*" Function,+,scene_manager_free,void,SceneManager* +Function,+,scene_manager_get_current_scene,uint32_t,SceneManager* Function,+,scene_manager_get_scene_state,uint32_t,"const SceneManager*, uint32_t" Function,+,scene_manager_handle_back_event,_Bool,SceneManager* Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t" @@ -3138,29 +3219,29 @@ Function,+,srand,void,unsigned Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." -Function,+,st25r3916_change_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_change_test_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_check_reg,_Bool,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_clear_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_direct_cmd,void,"FuriHalSpiBusHandle*, uint8_t" -Function,+,st25r3916_get_irq,uint32_t,FuriHalSpiBusHandle* -Function,+,st25r3916_mask_irq,void,"FuriHalSpiBusHandle*, uint32_t" -Function,+,st25r3916_modify_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" -Function,+,st25r3916_read_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" -Function,+,st25r3916_read_fifo,_Bool,"FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" -Function,+,st25r3916_read_pta_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_read_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,st25r3916_read_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t*" -Function,+,st25r3916_reg_read_fifo,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_reg_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_set_reg_bits,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_write_burst_regs,void,"FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" -Function,+,st25r3916_write_fifo,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_pta_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_ptf_mem,void,"FuriHalSpiBusHandle*, const uint8_t*, size_t" -Function,+,st25r3916_write_pttsn_mem,void,"FuriHalSpiBusHandle*, uint8_t*, size_t" -Function,+,st25r3916_write_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" -Function,+,st25r3916_write_test_reg,void,"FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_change_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_change_test_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_check_reg,_Bool,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_clear_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_direct_cmd,void,"const FuriHalSpiBusHandle*, uint8_t" +Function,+,st25r3916_get_irq,uint32_t,const FuriHalSpiBusHandle* +Function,+,st25r3916_mask_irq,void,"const FuriHalSpiBusHandle*, uint32_t" +Function,+,st25r3916_modify_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t, uint8_t" +Function,+,st25r3916_read_burst_regs,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*, uint8_t" +Function,+,st25r3916_read_fifo,_Bool,"const FuriHalSpiBusHandle*, uint8_t*, size_t, size_t*" +Function,+,st25r3916_read_pta_mem,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_read_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_read_test_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t*" +Function,+,st25r3916_reg_read_fifo,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_reg_write_fifo,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_set_reg_bits,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_burst_regs,void,"const FuriHalSpiBusHandle*, uint8_t, const uint8_t*, uint8_t" +Function,+,st25r3916_write_fifo,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pta_mem,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_ptf_mem,void,"const FuriHalSpiBusHandle*, const uint8_t*, size_t" +Function,+,st25r3916_write_pttsn_mem,void,"const FuriHalSpiBusHandle*, uint8_t*, size_t" +Function,+,st25r3916_write_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" +Function,+,st25r3916_write_test_reg,void,"const FuriHalSpiBusHandle*, uint8_t, uint8_t" Function,+,st25tb_alloc,St25tbData*, Function,+,st25tb_copy,void,"St25tbData*, const St25tbData*" Function,+,st25tb_free,void,St25tbData* @@ -3388,13 +3469,13 @@ Function,+,subghz_file_encoder_worker_get_level_duration,LevelDuration,void* Function,+,subghz_file_encoder_worker_is_running,_Bool,SubGhzFileEncoderWorker* Function,+,subghz_file_encoder_worker_start,_Bool,"SubGhzFileEncoderWorker*, const char*, const char*" Function,+,subghz_file_encoder_worker_stop,void,SubGhzFileEncoderWorker* -Function,-,subghz_keystore_alloc,SubGhzKeystore*, -Function,-,subghz_keystore_free,void,SubGhzKeystore* -Function,-,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* -Function,-,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*" -Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*" -Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" -Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" +Function,+,subghz_keystore_alloc,SubGhzKeystore*, +Function,+,subghz_keystore_free,void,SubGhzKeystore* +Function,+,subghz_keystore_get_data,SubGhzKeyArray_t*,SubGhzKeystore* +Function,+,subghz_keystore_load,_Bool,"SubGhzKeystore*, const char*" +Function,+,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, uint8_t*" +Function,+,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t" +Function,+,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*" Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t" Function,+,subghz_protocol_blocks_add_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_blocks_add_to_128_bit,void,"SubGhzBlockDecoder*, uint8_t, uint64_t*" @@ -3495,6 +3576,7 @@ Function,+,subghz_worker_set_pair_callback,void,"SubGhzWorker*, SubGhzWorkerPair Function,+,subghz_worker_start,void,SubGhzWorker* Function,+,subghz_worker_stop,void,SubGhzWorker* Function,+,submenu_add_item,void,"Submenu*, const char*, uint32_t, SubmenuItemCallback, void*" +Function,+,submenu_add_item_ex,void,"Submenu*, const char*, uint32_t, SubmenuItemCallbackEx, void*" Function,+,submenu_alloc,Submenu*, Function,+,submenu_change_item_label,void,"Submenu*, uint32_t, const char*" Function,+,submenu_free,void,Submenu* @@ -3691,8 +3773,10 @@ Function,-,vsscanf,int,"const char*, const char*, __gnuc_va_list" Function,-,wcstombs,size_t,"char*, const wchar_t*, size_t" Function,-,wctomb,int,"char*, wchar_t" Function,+,widget_add_button_element,void,"Widget*, GuiButtonType, const char*, ButtonCallback, void*" -Function,+,widget_add_frame_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_circle_element,void,"Widget*, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_icon_element,void,"Widget*, uint8_t, uint8_t, const Icon*" +Function,+,widget_add_line_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t" +Function,+,widget_add_rect_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, _Bool" Function,+,widget_add_string_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_string_multiline_element,void,"Widget*, uint8_t, uint8_t, Align, Align, Font, const char*" Function,+,widget_add_text_box_element,void,"Widget*, uint8_t, uint8_t, uint8_t, uint8_t, Align, Align, const char*, _Bool" @@ -3721,24 +3805,23 @@ Variable,-,__stdio_exit_handler,void (*)(), Variable,+,_ctype_,const char[], Variable,+,_impure_data,_reent, Variable,+,_impure_ptr,_reent*, -Variable,-,_sys_errlist,const char*[], +Variable,-,_sys_errlist,const char* const[], Variable,-,_sys_nerr,int, Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, -Variable,-,ble_profile_serial,const FuriHalBleProfileTemplate*, -Variable,+,cli_vcp,CliSession, +Variable,+,ble_profile_serial,const FuriHalBleProfileTemplate* const, Variable,+,compress_config_heatshrink_default,const CompressConfigHeatshrink, -Variable,+,firmware_api_interface,const ElfApiInterface*, +Variable,+,firmware_api_interface,const ElfApiInterface* const, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, Variable,+,furi_hal_i2c_bus_power,FuriHalI2cBus, -Variable,+,furi_hal_i2c_handle_external,FuriHalI2cBusHandle, -Variable,+,furi_hal_i2c_handle_power,FuriHalI2cBusHandle, +Variable,+,furi_hal_i2c_handle_external,const FuriHalI2cBusHandle, +Variable,+,furi_hal_i2c_handle_power,const FuriHalI2cBusHandle, Variable,+,furi_hal_spi_bus_d,FuriHalSpiBus, -Variable,+,furi_hal_spi_bus_handle_display,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_external,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_nfc,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_sd_fast,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_sd_slow,FuriHalSpiBusHandle, -Variable,+,furi_hal_spi_bus_handle_subghz,FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_display,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_external,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_nfc,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_fast,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_sd_slow,const FuriHalSpiBusHandle, +Variable,+,furi_hal_spi_bus_handle_subghz,const FuriHalSpiBusHandle, Variable,+,furi_hal_spi_bus_r,FuriHalSpiBus, Variable,+,furi_hal_spi_preset_1edge_low_16m,const LL_SPI_InitTypeDef, Variable,+,furi_hal_spi_preset_1edge_low_2m,const LL_SPI_InitTypeDef, @@ -3796,7 +3879,7 @@ Variable,+,gpio_usb_dp,const GpioPin, Variable,+,gpio_vibro,const GpioPin, Variable,+,input_pins,const InputPin[], Variable,+,input_pins_count,const size_t, -Variable,+,lfrfid_protocols,const ProtocolBase*[], +Variable,+,lfrfid_protocols,const ProtocolBase* const[], Variable,+,message_blink_set_color_blue,const NotificationMessage, Variable,+,message_blink_set_color_cyan,const NotificationMessage, Variable,+,message_blink_set_color_green,const NotificationMessage, diff --git a/targets/f7/application_ext.ld b/targets/f7/application_ext.ld index b6496290a..456947db1 100644 --- a/targets/f7/application_ext.ld +++ b/targets/f7/application_ext.ld @@ -33,10 +33,56 @@ SECTIONS { *(COMMON) } - .ARM.attributes : { - *(.ARM.attributes) - *(.ARM.attributes.*) - } + /* Default debug-related rules from ld */ + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1. */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions. */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2. */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2. */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions. */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3. */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF 5. */ + .debug_addr 0 : { *(.debug_addr) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_macro 0 : { *(.debug_macro) } + .debug_names 0 : { *(.debug_names) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } + .debug_sup 0 : { *(.debug_sup) } + .ARM.attributes 0 : { KEEP (*(.ARM.attributes)) KEEP (*(.gnu.attributes)) } + .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) } + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } /DISCARD/ : { *(.comment) diff --git a/targets/f7/ble_glue/ble_event_thread.c b/targets/f7/ble_glue/ble_event_thread.c index c6bc56846..3d1fdd196 100644 --- a/targets/f7/ble_glue/ble_event_thread.c +++ b/targets/f7/ble_glue/ble_event_thread.c @@ -90,7 +90,7 @@ void ble_event_thread_stop(void) { void ble_event_thread_start(void) { furi_check(event_thread == NULL); - event_thread = furi_thread_alloc_ex("BleEventWorker", 1024, ble_event_thread, NULL); + event_thread = furi_thread_alloc_ex("BleEventWorker", 1280, ble_event_thread, NULL); furi_thread_set_priority(event_thread, FuriThreadPriorityHigh); furi_thread_start(event_thread); } diff --git a/targets/f7/ble_glue/furi_ble/gatt.c b/targets/f7/ble_glue/furi_ble/gatt.c index e40786583..b8ab094f9 100644 --- a/targets/f7/ble_glue/furi_ble/gatt.c +++ b/targets/f7/ble_glue/furi_ble/gatt.c @@ -7,6 +7,12 @@ #define GATT_MIN_READ_KEY_SIZE (10) +#ifdef BLE_GATT_STRICT +#define ble_gatt_strict_crash(message) furi_crash(message) +#else +#define ble_gatt_strict_crash(message) +#endif + void ble_gatt_characteristic_init( uint16_t svc_handle, const BleGattCharacteristicParams* char_descriptor, @@ -42,6 +48,7 @@ void ble_gatt_characteristic_init( &char_instance->handle); if(status) { FURI_LOG_E(TAG, "Failed to add %s char: %d", char_descriptor->name, status); + ble_gatt_strict_crash("Failed to add characteristic"); } char_instance->descriptor_handle = 0; @@ -68,6 +75,7 @@ void ble_gatt_characteristic_init( &char_instance->descriptor_handle); if(status) { FURI_LOG_E(TAG, "Failed to add %s char descriptor: %d", char_descriptor->name, status); + ble_gatt_strict_crash("Failed to add characteristic descriptor"); } if(release_data) { free((void*)char_data); @@ -82,6 +90,7 @@ void ble_gatt_characteristic_delete( if(status) { FURI_LOG_E( TAG, "Failed to delete %s char: %d", char_instance->characteristic->name, status); + ble_gatt_strict_crash("Failed to delete characteristic"); } free((void*)char_instance->characteristic); } @@ -111,14 +120,27 @@ bool ble_gatt_characteristic_update( release_data = char_descriptor->data.callback.fn(context, &char_data, &char_data_size); } - tBleStatus result = aci_gatt_update_char_value( - svc_handle, char_instance->handle, 0, char_data_size, char_data); - if(result) { - FURI_LOG_E(TAG, "Failed updating %s characteristic: %d", char_descriptor->name, result); - } + tBleStatus result; + size_t retries_left = 1000; + do { + retries_left--; + result = aci_gatt_update_char_value( + svc_handle, char_instance->handle, 0, char_data_size, char_data); + if(result == BLE_STATUS_INSUFFICIENT_RESOURCES) { + FURI_LOG_W(TAG, "Insufficient resources for %s characteristic", char_descriptor->name); + furi_delay_ms(1); + } + } while(result == BLE_STATUS_INSUFFICIENT_RESOURCES && retries_left); + if(release_data) { free((void*)char_data); } + + if(result != BLE_STATUS_SUCCESS) { + FURI_LOG_E(TAG, "Failed updating %s characteristic: %d", char_descriptor->name, result); + ble_gatt_strict_crash("Failed to update characteristic"); + } + return result != BLE_STATUS_SUCCESS; } @@ -132,6 +154,7 @@ bool ble_gatt_service_add( Service_UUID_Type, Service_UUID, Service_Type, Max_Attribute_Records, Service_Handle); if(result) { FURI_LOG_E(TAG, "Failed to add service: %x", result); + ble_gatt_strict_crash("Failed to add service"); } return result == BLE_STATUS_SUCCESS; @@ -141,6 +164,7 @@ bool ble_gatt_service_delete(uint16_t svc_handle) { tBleStatus result = aci_gatt_del_service(svc_handle); if(result) { FURI_LOG_E(TAG, "Failed to delete service: %x", result); + ble_gatt_strict_crash("Failed to delete service"); } return result == BLE_STATUS_SUCCESS; diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 732440ccf..70c9d1c6f 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -23,6 +23,8 @@ typedef struct { uint16_t connection_handle; uint8_t adv_svc_uuid_len; uint8_t adv_svc_uuid[20]; + uint8_t mfg_data_len; + uint8_t mfg_data[23]; char* adv_name; } GapSvc; @@ -198,8 +200,10 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { gap->service.connection_handle = event->Connection_Handle; gap_verify_connection_parameters(gap); - // Start pairing by sending security request - aci_gap_slave_security_req(event->Connection_Handle); + if(gap->config->pairing_method != GapPairingNone) { + // Start pairing by sending security request + aci_gap_slave_security_req(event->Connection_Handle); + } } break; default: @@ -321,6 +325,14 @@ static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) { gap->service.adv_svc_uuid_len += uid_len; } +static void set_manufacturer_data(uint8_t* mfg_data, uint8_t mfg_data_len) { + furi_check(mfg_data_len <= sizeof(gap->service.mfg_data) - 2); + gap->service.mfg_data[0] = mfg_data_len + 1; + gap->service.mfg_data[1] = AD_TYPE_MANUFACTURER_SPECIFIC_DATA; + memcpy(&gap->service.mfg_data[gap->service.mfg_data_len], mfg_data, mfg_data_len); + gap->service.mfg_data_len += mfg_data_len; +} + static void gap_init_svc(Gap* gap) { tBleStatus status; uint32_t srd_bd_addr[2]; @@ -440,6 +452,11 @@ static void gap_advertise_start(GapState new_state) { FURI_LOG_D(TAG, "set_non_discoverable success"); } } + + if(gap->service.mfg_data_len > 0) { + hci_le_set_scan_response_data(gap->service.mfg_data_len, gap->service.mfg_data); + } + // Configure advertising status = aci_gap_set_discoverable( ADV_IND, @@ -550,11 +567,26 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->is_secure = false; gap->negotiation_round = 0; - uint8_t adv_service_uid[2]; - gap->service.adv_svc_uuid_len = 1; - adv_service_uid[0] = gap->config->adv_service_uuid & 0xff; - adv_service_uid[1] = gap->config->adv_service_uuid >> 8; - set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); + if(gap->config->mfg_data_len > 0) { + // Offset by 2 for length + AD_TYPE_MANUFACTURER_SPECIFIC_DATA + gap->service.mfg_data_len = 2; + set_manufacturer_data(gap->config->mfg_data, gap->config->mfg_data_len); + } + + if(gap->config->adv_service.UUID_Type == UUID_TYPE_16) { + uint8_t adv_service_uid[2]; + gap->service.adv_svc_uuid_len = 1; + adv_service_uid[0] = gap->config->adv_service.Service_UUID_16 & 0xff; + adv_service_uid[1] = gap->config->adv_service.Service_UUID_16 >> 8; + set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid)); + } else if(gap->config->adv_service.UUID_Type == UUID_TYPE_128) { + gap->service.adv_svc_uuid_len = 1; + set_advertisment_service_uid( + gap->config->adv_service.Service_UUID_128, + sizeof(gap->config->adv_service.Service_UUID_128)); + } else { + furi_crash("Invalid UUID type"); + } // Set callback gap->on_event_cb = on_event_cb; diff --git a/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h index a90d07304..b2bf0ffc1 100644 --- a/targets/f7/ble_glue/gap.h +++ b/targets/f7/ble_glue/gap.h @@ -68,7 +68,13 @@ typedef struct { } GapConnectionParamsRequest; typedef struct { - uint16_t adv_service_uuid; + struct { + uint8_t UUID_Type; + uint16_t Service_UUID_16; + uint8_t Service_UUID_128[16]; + } adv_service; + uint8_t mfg_data[23]; + uint8_t mfg_data_len; uint16_t appearance_char; bool bonding_mode; GapPairing pairing_method; diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c index 1d414889f..9bba0d897 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.c +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -6,6 +6,7 @@ #include #include #include +#include typedef struct { FuriHalBleProfileBase base; @@ -46,8 +47,12 @@ static void ble_profile_serial_stop(FuriHalBleProfileBase* profile) { // Up to 45 ms #define CONNECTION_INTERVAL_MAX (0x24) -static GapConfig serial_template_config = { - .adv_service_uuid = 0x3080, +static const GapConfig serial_template_config = { + .adv_service = + { + .UUID_Type = UUID_TYPE_16, + .Service_UUID_16 = 0x3080, + }, .appearance_char = 0x8600, .bonding_mode = true, .pairing_method = GapPairingPinCodeShow, @@ -71,7 +76,8 @@ static void config->adv_name, furi_hal_version_get_ble_local_device_name_ptr(), FURI_HAL_VERSION_DEVICE_NAME_LENGTH); - config->adv_service_uuid |= furi_hal_version_get_hw_color(); + config->adv_service.UUID_Type = UUID_TYPE_16; + config->adv_service.Service_UUID_16 |= furi_hal_version_get_hw_color(); } static const FuriHalBleProfileTemplate profile_callbacks = { @@ -80,7 +86,7 @@ static const FuriHalBleProfileTemplate profile_callbacks = { .get_gap_config = ble_profile_serial_get_config, }; -const FuriHalBleProfileTemplate* ble_profile_serial = &profile_callbacks; +const FuriHalBleProfileTemplate* const ble_profile_serial = &profile_callbacks; void ble_profile_serial_set_event_callback( FuriHalBleProfileBase* profile, diff --git a/targets/f7/ble_glue/profiles/serial_profile.h b/targets/f7/ble_glue/profiles/serial_profile.h index e07eaef03..7938bfc33 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.h +++ b/targets/f7/ble_glue/profiles/serial_profile.h @@ -19,7 +19,7 @@ typedef enum { typedef SerialServiceEventCallback FuriHalBtSerialCallback; /** Serial profile descriptor */ -extern const FuriHalBleProfileTemplate* ble_profile_serial; +extern const FuriHalBleProfileTemplate* const ble_profile_serial; /** Send data through BLE * diff --git a/targets/f7/fatfs/user_diskio.c b/targets/f7/fatfs/user_diskio.c index 85e5cad4f..963cb595f 100644 --- a/targets/f7/fatfs/user_diskio.c +++ b/targets/f7/fatfs/user_diskio.c @@ -9,7 +9,7 @@ static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count); static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff); -Diskio_drvTypeDef sd_fatfs_driver = { +const Diskio_drvTypeDef sd_fatfs_driver = { driver_initialize, driver_status, driver_read, diff --git a/targets/f7/fatfs/user_diskio.h b/targets/f7/fatfs/user_diskio.h index 009a17d4b..2505de704 100644 --- a/targets/f7/fatfs/user_diskio.h +++ b/targets/f7/fatfs/user_diskio.h @@ -6,7 +6,7 @@ extern "C" { #include "fatfs/ff_gen_drv.h" -extern Diskio_drvTypeDef sd_fatfs_driver; +extern const Diskio_drvTypeDef sd_fatfs_driver; #ifdef __cplusplus } diff --git a/targets/f7/furi_hal/furi_hal_gpio.c b/targets/f7/furi_hal/furi_hal_gpio.c index a020e9d7c..0104dd898 100644 --- a/targets/f7/furi_hal/furi_hal_gpio.c +++ b/targets/f7/furi_hal/furi_hal_gpio.c @@ -258,85 +258,85 @@ FURI_ALWAYS_INLINE static void furi_hal_gpio_int_call(uint16_t pin_num) { /* Interrupt handlers */ void EXTI0_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_0)) { - furi_hal_gpio_int_call(0); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_0); + furi_hal_gpio_int_call(0); } } void EXTI1_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_1)) { - furi_hal_gpio_int_call(1); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_1); + furi_hal_gpio_int_call(1); } } void EXTI2_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_2)) { - furi_hal_gpio_int_call(2); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_2); + furi_hal_gpio_int_call(2); } } void EXTI3_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_3)) { - furi_hal_gpio_int_call(3); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_3); + furi_hal_gpio_int_call(3); } } void EXTI4_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_4)) { - furi_hal_gpio_int_call(4); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_4); + furi_hal_gpio_int_call(4); } } void EXTI9_5_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_5)) { - furi_hal_gpio_int_call(5); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_5); + furi_hal_gpio_int_call(5); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_6)) { - furi_hal_gpio_int_call(6); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_6); + furi_hal_gpio_int_call(6); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_7)) { - furi_hal_gpio_int_call(7); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_7); + furi_hal_gpio_int_call(7); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_8)) { - furi_hal_gpio_int_call(8); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_8); + furi_hal_gpio_int_call(8); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_9)) { - furi_hal_gpio_int_call(9); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_9); + furi_hal_gpio_int_call(9); } } void EXTI15_10_IRQHandler(void) { if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_10)) { - furi_hal_gpio_int_call(10); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_10); + furi_hal_gpio_int_call(10); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_11)) { - furi_hal_gpio_int_call(11); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_11); + furi_hal_gpio_int_call(11); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_12)) { - furi_hal_gpio_int_call(12); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_12); + furi_hal_gpio_int_call(12); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_13)) { - furi_hal_gpio_int_call(13); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_13); + furi_hal_gpio_int_call(13); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_14)) { - furi_hal_gpio_int_call(14); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_14); + furi_hal_gpio_int_call(14); } if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_15)) { - furi_hal_gpio_int_call(15); LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_15); + furi_hal_gpio_int_call(15); } } diff --git a/targets/f7/furi_hal/furi_hal_i2c.c b/targets/f7/furi_hal/furi_hal_i2c.c index 71e1a5814..7eb9b4928 100644 --- a/targets/f7/furi_hal/furi_hal_i2c.c +++ b/targets/f7/furi_hal/furi_hal_i2c.c @@ -22,7 +22,7 @@ void furi_hal_i2c_init(void) { FURI_LOG_I(TAG, "Init OK"); } -void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle) { +void furi_hal_i2c_acquire(const FuriHalI2cBusHandle* handle) { furi_hal_power_insomnia_enter(); // Lock bus access handle->bus->callback(handle->bus, FuriHalI2cBusEventLock); @@ -36,7 +36,7 @@ void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle) { handle->callback(handle, FuriHalI2cBusHandleEventActivate); } -void furi_hal_i2c_release(FuriHalI2cBusHandle* handle) { +void furi_hal_i2c_release(const FuriHalI2cBusHandle* handle) { // Ensure that current handle is our handle furi_check(handle->bus->current_handle == handle); // Deactivate handle @@ -196,7 +196,7 @@ static bool furi_hal_i2c_transaction( } bool furi_hal_i2c_rx_ext( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint16_t address, bool ten_bit, uint8_t* data, @@ -213,7 +213,7 @@ bool furi_hal_i2c_rx_ext( } bool furi_hal_i2c_tx_ext( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint16_t address, bool ten_bit, const uint8_t* data, @@ -230,7 +230,7 @@ bool furi_hal_i2c_tx_ext( } bool furi_hal_i2c_tx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* data, size_t size, @@ -242,7 +242,7 @@ bool furi_hal_i2c_tx( } bool furi_hal_i2c_rx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, uint8_t* data, size_t size, @@ -254,7 +254,7 @@ bool furi_hal_i2c_rx( } bool furi_hal_i2c_trx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* tx_data, size_t tx_size, @@ -281,7 +281,10 @@ bool furi_hal_i2c_trx( timeout); } -bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint32_t timeout) { +bool furi_hal_i2c_is_device_ready( + const FuriHalI2cBusHandle* handle, + uint8_t i2c_addr, + uint32_t timeout) { furi_check(handle); furi_check(handle->bus->current_handle == handle); furi_check(timeout > 0); @@ -314,7 +317,7 @@ bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, } bool furi_hal_i2c_read_reg_8( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint8_t* data, @@ -325,7 +328,7 @@ bool furi_hal_i2c_read_reg_8( } bool furi_hal_i2c_read_reg_16( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint16_t* data, @@ -340,7 +343,7 @@ bool furi_hal_i2c_read_reg_16( } bool furi_hal_i2c_read_mem( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, uint8_t* data, @@ -352,7 +355,7 @@ bool furi_hal_i2c_read_mem( } bool furi_hal_i2c_write_reg_8( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint8_t data, @@ -368,7 +371,7 @@ bool furi_hal_i2c_write_reg_8( } bool furi_hal_i2c_write_reg_16( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint16_t data, @@ -385,7 +388,7 @@ bool furi_hal_i2c_write_reg_16( } bool furi_hal_i2c_write_mem( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, const uint8_t* data, diff --git a/targets/f7/furi_hal/furi_hal_i2c_config.c b/targets/f7/furi_hal/furi_hal_i2c_config.c index f9d88abb3..b10c53d32 100644 --- a/targets/f7/furi_hal/furi_hal_i2c_config.c +++ b/targets/f7/furi_hal/furi_hal_i2c_config.c @@ -74,7 +74,7 @@ FuriHalI2cBus furi_hal_i2c_bus_external = { }; void furi_hal_i2c_bus_handle_power_event( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, FuriHalI2cBusHandleEvent event) { if(event == FuriHalI2cBusHandleEventActivate) { furi_hal_gpio_init_ex( @@ -120,13 +120,13 @@ void furi_hal_i2c_bus_handle_power_event( } } -FuriHalI2cBusHandle furi_hal_i2c_handle_power = { +const FuriHalI2cBusHandle furi_hal_i2c_handle_power = { .bus = &furi_hal_i2c_bus_power, .callback = furi_hal_i2c_bus_handle_power_event, }; void furi_hal_i2c_bus_handle_external_event( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, FuriHalI2cBusHandleEvent event) { if(event == FuriHalI2cBusHandleEventActivate) { furi_hal_gpio_init_ex( @@ -160,7 +160,7 @@ void furi_hal_i2c_bus_handle_external_event( } } -FuriHalI2cBusHandle furi_hal_i2c_handle_external = { +const FuriHalI2cBusHandle furi_hal_i2c_handle_external = { .bus = &furi_hal_i2c_bus_external, .callback = furi_hal_i2c_bus_handle_external_event, }; diff --git a/targets/f7/furi_hal/furi_hal_i2c_config.h b/targets/f7/furi_hal/furi_hal_i2c_config.h index a8fb91835..28bd09a0a 100644 --- a/targets/f7/furi_hal/furi_hal_i2c_config.h +++ b/targets/f7/furi_hal/furi_hal_i2c_config.h @@ -17,14 +17,14 @@ extern FuriHalI2cBus furi_hal_i2c_bus_external; * Pins: PA9(SCL) / PA10(SDA), float on release * Params: 400khz */ -extern FuriHalI2cBusHandle furi_hal_i2c_handle_power; +extern const FuriHalI2cBusHandle furi_hal_i2c_handle_power; /** Handle for external i2c bus * Bus: furi_hal_i2c_bus_external * Pins: PC0(SCL) / PC1(SDA), float on release * Params: 100khz */ -extern FuriHalI2cBusHandle furi_hal_i2c_handle_external; +extern const FuriHalI2cBusHandle furi_hal_i2c_handle_external; #ifdef __cplusplus } diff --git a/targets/f7/furi_hal/furi_hal_i2c_types.h b/targets/f7/furi_hal/furi_hal_i2c_types.h index 13f361054..0a35137b0 100644 --- a/targets/f7/furi_hal/furi_hal_i2c_types.h +++ b/targets/f7/furi_hal/furi_hal_i2c_types.h @@ -25,7 +25,7 @@ typedef void (*FuriHalI2cBusEventCallback)(FuriHalI2cBus* bus, FuriHalI2cBusEven /** FuriHal i2c bus */ struct FuriHalI2cBus { I2C_TypeDef* i2c; - FuriHalI2cBusHandle* current_handle; + const FuriHalI2cBusHandle* current_handle; FuriHalI2cBusEventCallback callback; }; @@ -37,7 +37,7 @@ typedef enum { /** FuriHal i2c handle event callback */ typedef void (*FuriHalI2cBusHandleEventCallback)( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, FuriHalI2cBusHandleEvent event); /** FuriHal i2c handle */ diff --git a/targets/f7/furi_hal/furi_hal_nfc.c b/targets/f7/furi_hal/furi_hal_nfc.c index ee7a04e45..d8dc0c618 100644 --- a/targets/f7/furi_hal/furi_hal_nfc.c +++ b/targets/f7/furi_hal/furi_hal_nfc.c @@ -8,7 +8,7 @@ #define TAG "FuriHalNfc" -const FuriHalNfcTechBase* furi_hal_nfc_tech[FuriHalNfcTechNum] = { +const FuriHalNfcTechBase* const furi_hal_nfc_tech[FuriHalNfcTechNum] = { [FuriHalNfcTechIso14443a] = &furi_hal_nfc_iso14443a, [FuriHalNfcTechIso14443b] = &furi_hal_nfc_iso14443b, [FuriHalNfcTechIso15693] = &furi_hal_nfc_iso15693, @@ -18,7 +18,7 @@ const FuriHalNfcTechBase* furi_hal_nfc_tech[FuriHalNfcTechNum] = { FuriHalNfc furi_hal_nfc; -static FuriHalNfcError furi_hal_nfc_turn_on_osc(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_turn_on_osc(const FuriHalSpiBusHandle* handle) { FuriHalNfcError error = FuriHalNfcErrorNone; furi_hal_nfc_event_start(); @@ -53,7 +53,7 @@ FuriHalNfcError furi_hal_nfc_is_hal_ready(void) { error = furi_hal_nfc_acquire(); if(error != FuriHalNfcErrorNone) break; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; uint8_t chip_id = 0; st25r3916_read_reg(handle, ST25R3916_REG_IC_IDENTITY, &chip_id); if((chip_id & ST25R3916_REG_IC_IDENTITY_ic_type_mask) != @@ -83,7 +83,7 @@ FuriHalNfcError furi_hal_nfc_init(void) { furi_hal_nfc_low_power_mode_start(); } - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; // Set default state st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT); // Increase IO driver strength of MISO and IRQ @@ -282,7 +282,7 @@ FuriHalNfcError furi_hal_nfc_release(void) { FuriHalNfcError furi_hal_nfc_low_power_mode_start(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); st25r3916_clear_reg_bits( @@ -300,7 +300,7 @@ FuriHalNfcError furi_hal_nfc_low_power_mode_start(void) { FuriHalNfcError furi_hal_nfc_low_power_mode_stop(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; do { furi_hal_nfc_init_gpio_isr(); @@ -318,7 +318,7 @@ FuriHalNfcError furi_hal_nfc_low_power_mode_stop(void) { return error; } -static FuriHalNfcError furi_hal_nfc_poller_init_common(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_poller_init_common(const FuriHalSpiBusHandle* handle) { // Disable wake up st25r3916_clear_reg_bits(handle, ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu); // Enable correlator @@ -339,7 +339,7 @@ static FuriHalNfcError furi_hal_nfc_poller_init_common(FuriHalSpiBusHandle* hand return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_listener_init_common(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_listener_init_common(const FuriHalSpiBusHandle* handle) { UNUSED(handle); // No common listener configuration return FuriHalNfcErrorNone; @@ -349,7 +349,7 @@ FuriHalNfcError furi_hal_nfc_set_mode(FuriHalNfcMode mode, FuriHalNfcTech tech) furi_check(mode < FuriHalNfcModeNum); furi_check(tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; FuriHalNfcError error = FuriHalNfcErrorNone; @@ -375,7 +375,7 @@ FuriHalNfcError furi_hal_nfc_set_mode(FuriHalNfcMode mode, FuriHalNfcTech tech) FuriHalNfcError furi_hal_nfc_reset_mode(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); @@ -415,7 +415,7 @@ FuriHalNfcError furi_hal_nfc_reset_mode(void) { FuriHalNfcError furi_hal_nfc_field_detect_start(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_write_reg( handle, @@ -429,7 +429,7 @@ FuriHalNfcError furi_hal_nfc_field_detect_start(void) { FuriHalNfcError furi_hal_nfc_field_detect_stop(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_clear_reg_bits( handle, @@ -441,7 +441,7 @@ FuriHalNfcError furi_hal_nfc_field_detect_stop(void) { bool furi_hal_nfc_field_is_present(void) { bool is_present = false; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; if(st25r3916_check_reg( handle, @@ -456,7 +456,7 @@ bool furi_hal_nfc_field_is_present(void) { FuriHalNfcError furi_hal_nfc_poller_field_on(void) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; if(!st25r3916_check_reg( handle, @@ -476,7 +476,7 @@ FuriHalNfcError furi_hal_nfc_poller_field_on(void) { } FuriHalNfcError furi_hal_nfc_poller_tx_common( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { furi_check(tx_data); @@ -508,7 +508,7 @@ FuriHalNfcError furi_hal_nfc_poller_tx_common( } FuriHalNfcError furi_hal_nfc_common_fifo_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { FuriHalNfcError err = FuriHalNfcErrorNone; @@ -523,7 +523,7 @@ FuriHalNfcError furi_hal_nfc_common_fifo_tx( FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits) { furi_check(furi_hal_nfc.mode == FuriHalNfcModePoller); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.tx(handle, tx_data, tx_bits); } @@ -531,7 +531,7 @@ FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits) { FuriHalNfcError furi_hal_nfc_poller_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { furi_check(furi_hal_nfc.mode == FuriHalNfcModePoller); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.rx(handle, rx_data, rx_data_size, rx_bits); } @@ -556,12 +556,12 @@ FuriHalNfcError furi_hal_nfc_listener_tx(const uint8_t* tx_data, size_t tx_bits) furi_check(furi_hal_nfc.mode == FuriHalNfcModeListener); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.tx(handle, tx_data, tx_bits); } FuriHalNfcError furi_hal_nfc_common_fifo_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { @@ -581,13 +581,13 @@ FuriHalNfcError furi_hal_nfc_listener_rx(uint8_t* rx_data, size_t rx_data_size, furi_check(furi_hal_nfc.mode == FuriHalNfcModeListener); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.rx( handle, rx_data, rx_data_size, rx_bits); } FuriHalNfcError furi_hal_nfc_trx_reset(void) { - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP); @@ -598,7 +598,7 @@ FuriHalNfcError furi_hal_nfc_listener_sleep(void) { furi_check(furi_hal_nfc.mode == FuriHalNfcModeListener); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.sleep(handle); } @@ -607,13 +607,13 @@ FuriHalNfcError furi_hal_nfc_listener_idle(void) { furi_check(furi_hal_nfc.mode == FuriHalNfcModeListener); furi_check(furi_hal_nfc.tech < FuriHalNfcTechNum); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.idle(handle); } FuriHalNfcError furi_hal_nfc_listener_enable_rx(void) { - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA); diff --git a/targets/f7/furi_hal/furi_hal_nfc_event.c b/targets/f7/furi_hal/furi_hal_nfc_event.c index 9bcd2f1fe..56681b4fe 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_event.c +++ b/targets/f7/furi_hal/furi_hal_nfc_event.c @@ -48,7 +48,7 @@ FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) { if(event_flag != (unsigned)FuriFlagErrorTimeout) { if(event_flag & FuriHalNfcEventInternalTypeIrq) { furi_thread_flags_clear(FuriHalNfcEventInternalTypeIrq); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; uint32_t irq = furi_hal_nfc_get_irq(handle); if(irq & ST25R3916_IRQ_MASK_OSC) { event |= FuriHalNfcEventOscOn; @@ -101,7 +101,7 @@ FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) { } bool furi_hal_nfc_event_wait_for_specific_irq( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint32_t mask, uint32_t timeout_ms) { furi_check(furi_hal_nfc_event); diff --git a/targets/f7/furi_hal/furi_hal_nfc_felica.c b/targets/f7/furi_hal/furi_hal_nfc_felica.c index 6c3f55525..1267b7b13 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_felica.c +++ b/targets/f7/furi_hal/furi_hal_nfc_felica.c @@ -18,7 +18,7 @@ typedef struct { } FuriHalFelicaPtMemory; #pragma pack(pop) -static FuriHalNfcError furi_hal_nfc_felica_poller_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_felica_poller_init(const FuriHalSpiBusHandle* handle) { // Enable Felica mode, AM modulation st25r3916_change_reg_bits( handle, @@ -61,13 +61,13 @@ static FuriHalNfcError furi_hal_nfc_felica_poller_init(FuriHalSpiBusHandle* hand return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_felica_listener_init(const FuriHalSpiBusHandle* handle) { furi_assert(handle); st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT); st25r3916_write_reg( @@ -141,7 +141,7 @@ static FuriHalNfcError furi_hal_nfc_felica_listener_init(FuriHalSpiBusHandle* ha return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_felica_listener_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_felica_listener_deinit(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; } @@ -154,19 +154,19 @@ static FuriHalNfcEvent furi_hal_nfc_felica_listener_wait_event(uint32_t timeout_ } FuriHalNfcError furi_hal_nfc_felica_listener_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { furi_hal_nfc_common_fifo_tx(handle, tx_data, tx_bits); return FuriHalNfcErrorNone; } -FuriHalNfcError furi_hal_nfc_felica_listener_sleep(FuriHalSpiBusHandle* handle) { +FuriHalNfcError furi_hal_nfc_felica_listener_sleep(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; } -FuriHalNfcError furi_hal_nfc_felica_listener_idle(FuriHalSpiBusHandle* handle) { +FuriHalNfcError furi_hal_nfc_felica_listener_idle(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; } @@ -182,7 +182,7 @@ FuriHalNfcError furi_hal_nfc_felica_listener_set_sensf_res_data( furi_check(idm_len == FURI_HAL_FELICA_IDM_PMM_LENGTH); furi_check(pmm_len == FURI_HAL_FELICA_IDM_PMM_LENGTH); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; // Write PT Memory FuriHalFelicaPtMemory pt; pt.system_code = sys_code; diff --git a/targets/f7/furi_hal/furi_hal_nfc_i.h b/targets/f7/furi_hal/furi_hal_nfc_i.h index 084196451..5b0f8e68d 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_i.h +++ b/targets/f7/furi_hal/furi_hal_nfc_i.h @@ -104,7 +104,7 @@ void furi_hal_nfc_timers_deinit(void); * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. * @returns bitmask of zero or more occurred interrupts. */ -uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle); +uint32_t furi_hal_nfc_get_irq(const FuriHalSpiBusHandle* handle); /** * @brief Wait until a specified type of interrupt occurs. @@ -115,7 +115,7 @@ uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle); * @returns true if specified interrupt(s) have occured within timeout, false otherwise. */ bool furi_hal_nfc_event_wait_for_specific_irq( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint32_t mask, uint32_t timeout_ms); @@ -137,7 +137,7 @@ FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms); * @param[in,out] handle pointer to the SPI handle associated with the NFC chip. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ -FuriHalNfcError furi_hal_nfc_common_listener_rx_start(FuriHalSpiBusHandle* handle); +FuriHalNfcError furi_hal_nfc_common_listener_rx_start(const FuriHalSpiBusHandle* handle); /** * @brief Transmit data using on-chip FIFO. @@ -150,7 +150,7 @@ FuriHalNfcError furi_hal_nfc_common_listener_rx_start(FuriHalSpiBusHandle* handl * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError furi_hal_nfc_common_fifo_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits); @@ -166,7 +166,7 @@ FuriHalNfcError furi_hal_nfc_common_fifo_tx( * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError furi_hal_nfc_common_fifo_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); @@ -182,7 +182,7 @@ FuriHalNfcError furi_hal_nfc_common_fifo_rx( * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError furi_hal_nfc_poller_tx_common( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits); diff --git a/targets/f7/furi_hal/furi_hal_nfc_irq.c b/targets/f7/furi_hal/furi_hal_nfc_irq.c index 90373955f..50a0139fd 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_irq.c +++ b/targets/f7/furi_hal/furi_hal_nfc_irq.c @@ -8,7 +8,7 @@ static void furi_hal_nfc_int_callback(void* context) { furi_hal_nfc_event_set(FuriHalNfcEventInternalTypeIrq); } -uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle) { +uint32_t furi_hal_nfc_get_irq(const FuriHalSpiBusHandle* handle) { uint32_t irq = 0; while(furi_hal_gpio_read_port_pin(gpio_nfc_irq_rfid_pull.port, gpio_nfc_irq_rfid_pull.pin)) { irq |= st25r3916_get_irq(handle); diff --git a/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c b/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c index 1ef23dfa4..be7a17264 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c +++ b/targets/f7/furi_hal/furi_hal_nfc_iso14443a.c @@ -13,7 +13,7 @@ static Iso14443_3aSignal* iso14443_3a_signal = NULL; -static FuriHalNfcError furi_hal_nfc_iso14443a_common_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443a_common_init(const FuriHalSpiBusHandle* handle) { // Common NFC-A settings, 106 kbps // 1st stage zero = 600kHz, 3rd stage zero = 200 kHz @@ -40,7 +40,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443a_common_init(FuriHalSpiBusHandle* h return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_iso14443a_poller_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443a_poller_init(const FuriHalSpiBusHandle* handle) { // Enable ISO14443A mode, OOK modulation st25r3916_change_reg_bits( handle, @@ -57,7 +57,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443a_poller_init(FuriHalSpiBusHandle* h return furi_hal_nfc_iso14443a_common_init(handle); } -static FuriHalNfcError furi_hal_nfc_iso14443a_poller_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443a_poller_deinit(const FuriHalSpiBusHandle* handle) { st25r3916_change_reg_bits( handle, ST25R3916_REG_ISO14443A_NFC, @@ -67,7 +67,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443a_poller_deinit(FuriHalSpiBusHandle* return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_iso14443a_listener_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443a_listener_init(const FuriHalSpiBusHandle* handle) { furi_check(iso14443_3a_signal == NULL); iso14443_3a_signal = iso14443_3a_signal_alloc(&gpio_spi_r_mosi); @@ -105,7 +105,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443a_listener_init(FuriHalSpiBusHandle* return furi_hal_nfc_iso14443a_common_init(handle); } -static FuriHalNfcError furi_hal_nfc_iso14443a_listener_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443a_listener_deinit(const FuriHalSpiBusHandle* handle) { UNUSED(handle); if(iso14443_3a_signal) { @@ -118,7 +118,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443a_listener_deinit(FuriHalSpiBusHandl static FuriHalNfcEvent furi_hal_nfc_iso14443_3a_listener_wait_event(uint32_t timeout_ms) { FuriHalNfcEvent event = furi_hal_nfc_wait_event_common(timeout_ms); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; if(event & FuriHalNfcEventListenerActive) { st25r3916_set_reg_bits( @@ -131,7 +131,7 @@ static FuriHalNfcEvent furi_hal_nfc_iso14443_3a_listener_wait_event(uint32_t tim FuriHalNfcError furi_hal_nfc_iso14443a_poller_trx_short_frame(FuriHalNfcaShortFrame frame) { FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; // Disable crc check st25r3916_set_reg_bits(handle, ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx); @@ -185,7 +185,7 @@ FuriHalNfcError furi_check(tx_data); FuriHalNfcError err = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; // Prepare tx st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO); @@ -220,7 +220,7 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_set_col_res_data( FuriHalNfcError error = FuriHalNfcErrorNone; - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; // Set 4 or 7 bytes UID if(uid_len == 4) { @@ -255,7 +255,7 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_set_col_res_data( } FuriHalNfcError furi_hal_nfc_iso4443a_listener_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { FuriHalNfcError error = FuriHalNfcErrorNone; @@ -284,7 +284,7 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( furi_check(iso14443_3a_signal); - FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; + const FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc; st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSPARENT_MODE); // Reconfigure gpio for Transparent mode @@ -303,7 +303,7 @@ FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity( return FuriHalNfcErrorNone; } -FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_sleep(FuriHalSpiBusHandle* handle) { +FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_sleep(const FuriHalSpiBusHandle* handle) { // Enable auto collision resolution st25r3916_clear_reg_bits( handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); @@ -313,7 +313,7 @@ FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_sleep(FuriHalSpiBusHandle* han return FuriHalNfcErrorNone; } -FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_idle(FuriHalSpiBusHandle* handle) { +FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_idle(const FuriHalSpiBusHandle* handle) { // Enable auto collision resolution st25r3916_clear_reg_bits( handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a); diff --git a/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c b/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c index bb1a63515..315223e2f 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c +++ b/targets/f7/furi_hal/furi_hal_nfc_iso14443b.c @@ -1,7 +1,7 @@ #include "furi_hal_nfc_i.h" #include "furi_hal_nfc_tech_i.h" -static FuriHalNfcError furi_hal_nfc_iso14443b_common_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443b_common_init(const FuriHalSpiBusHandle* handle) { // Common NFC-B settings, 106kbps // 1st stage zero = 60kHz, 3rd stage zero = 200 kHz @@ -40,7 +40,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443b_common_init(FuriHalSpiBusHandle* h return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_iso14443b_poller_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443b_poller_init(const FuriHalSpiBusHandle* handle) { // Enable ISO14443B mode, AM modulation st25r3916_change_reg_bits( handle, @@ -84,7 +84,7 @@ static FuriHalNfcError furi_hal_nfc_iso14443b_poller_init(FuriHalSpiBusHandle* h return furi_hal_nfc_iso14443b_common_init(handle); } -static FuriHalNfcError furi_hal_nfc_iso14443b_poller_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso14443b_poller_deinit(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; } diff --git a/targets/f7/furi_hal/furi_hal_nfc_iso15693.c b/targets/f7/furi_hal/furi_hal_nfc_iso15693.c index 3245b67cc..d35b160f4 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_iso15693.c +++ b/targets/f7/furi_hal/furi_hal_nfc_iso15693.c @@ -74,7 +74,7 @@ static void furi_hal_nfc_iso15693_poller_free(FuriHalNfcIso15693Poller* instance free(instance); } -static FuriHalNfcError furi_hal_nfc_iso15693_common_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso15693_common_init(const FuriHalSpiBusHandle* handle) { // Common NFC-V settings, 26.48 kbps // 1st stage zero = 12 kHz, 3rd stage zero = 80 kHz, low-pass = 600 kHz @@ -112,7 +112,7 @@ static FuriHalNfcError furi_hal_nfc_iso15693_common_init(FuriHalSpiBusHandle* ha return FuriHalNfcErrorNone; } -static FuriHalNfcError furi_hal_nfc_iso15693_poller_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso15693_poller_init(const FuriHalSpiBusHandle* handle) { furi_check(furi_hal_nfc_iso15693_poller == NULL); furi_hal_nfc_iso15693_poller = furi_hal_nfc_iso15693_poller_alloc(); @@ -141,7 +141,7 @@ static FuriHalNfcError furi_hal_nfc_iso15693_poller_init(FuriHalSpiBusHandle* ha return furi_hal_nfc_iso15693_common_init(handle); } -static FuriHalNfcError furi_hal_nfc_iso15693_poller_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso15693_poller_deinit(const FuriHalSpiBusHandle* handle) { UNUSED(handle); furi_check(furi_hal_nfc_iso15693_poller); @@ -238,7 +238,7 @@ static FuriHalNfcError iso15693_3_poller_decode_frame( } static FuriHalNfcError furi_hal_nfc_iso15693_poller_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { FuriHalNfcIso15693Poller* instance = furi_hal_nfc_iso15693_poller; @@ -252,7 +252,7 @@ static FuriHalNfcError furi_hal_nfc_iso15693_poller_tx( } static FuriHalNfcError furi_hal_nfc_iso15693_poller_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { @@ -284,14 +284,16 @@ static FuriHalNfcError furi_hal_nfc_iso15693_poller_rx( return error; } -static void furi_hal_nfc_iso15693_listener_transparent_mode_enter(FuriHalSpiBusHandle* handle) { +static void + furi_hal_nfc_iso15693_listener_transparent_mode_enter(const FuriHalSpiBusHandle* handle) { st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSPARENT_MODE); furi_hal_spi_bus_handle_deinit(handle); furi_hal_nfc_deinit_gpio_isr(); } -static void furi_hal_nfc_iso15693_listener_transparent_mode_exit(FuriHalSpiBusHandle* handle) { +static void + furi_hal_nfc_iso15693_listener_transparent_mode_exit(const FuriHalSpiBusHandle* handle) { // Configure gpio back to SPI and exit transparent mode furi_hal_nfc_init_gpio_isr(); furi_hal_spi_bus_handle_init(handle); @@ -299,7 +301,7 @@ static void furi_hal_nfc_iso15693_listener_transparent_mode_exit(FuriHalSpiBusHa st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA); } -static FuriHalNfcError furi_hal_nfc_iso15693_listener_init(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso15693_listener_init(const FuriHalSpiBusHandle* handle) { furi_check(furi_hal_nfc_iso15693_listener == NULL); furi_hal_nfc_iso15693_listener = furi_hal_nfc_iso15693_listener_alloc(); @@ -328,7 +330,7 @@ static FuriHalNfcError furi_hal_nfc_iso15693_listener_init(FuriHalSpiBusHandle* return error; } -static FuriHalNfcError furi_hal_nfc_iso15693_listener_deinit(FuriHalSpiBusHandle* handle) { +static FuriHalNfcError furi_hal_nfc_iso15693_listener_deinit(const FuriHalSpiBusHandle* handle) { furi_check(furi_hal_nfc_iso15693_listener); furi_hal_nfc_iso15693_listener_transparent_mode_exit(handle); @@ -387,7 +389,7 @@ static FuriHalNfcEvent furi_hal_nfc_iso15693_wait_event(uint32_t timeout_ms) { } static FuriHalNfcError furi_hal_nfc_iso15693_listener_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits) { UNUSED(handle); @@ -407,7 +409,7 @@ FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof(void) { } static FuriHalNfcError furi_hal_nfc_iso15693_listener_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) { @@ -425,7 +427,7 @@ static FuriHalNfcError furi_hal_nfc_iso15693_listener_rx( return FuriHalNfcErrorNone; } -FuriHalNfcError furi_hal_nfc_iso15693_listener_sleep(FuriHalSpiBusHandle* handle) { +FuriHalNfcError furi_hal_nfc_iso15693_listener_sleep(const FuriHalSpiBusHandle* handle) { UNUSED(handle); return FuriHalNfcErrorNone; diff --git a/targets/f7/furi_hal/furi_hal_nfc_tech_i.h b/targets/f7/furi_hal/furi_hal_nfc_tech_i.h index e36dc852e..4a62f67c9 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_tech_i.h +++ b/targets/f7/furi_hal/furi_hal_nfc_tech_i.h @@ -25,7 +25,7 @@ extern "C" { * @param[in,out] handle pointer to the NFC chip SPI handle. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ -typedef FuriHalNfcError (*FuriHalNfcChipConfig)(FuriHalSpiBusHandle* handle); +typedef FuriHalNfcError (*FuriHalNfcChipConfig)(const FuriHalSpiBusHandle* handle); /** * @brief Transmit data using technology-specific framing and timings. @@ -36,7 +36,7 @@ typedef FuriHalNfcError (*FuriHalNfcChipConfig)(FuriHalSpiBusHandle* handle); * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ typedef FuriHalNfcError ( - *FuriHalNfcTx)(FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits); + *FuriHalNfcTx)(const FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits); /** * @brief Receive data using technology-specific framing and timings. @@ -48,7 +48,7 @@ typedef FuriHalNfcError ( * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ typedef FuriHalNfcError (*FuriHalNfcRx)( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); @@ -69,7 +69,7 @@ typedef FuriHalNfcEvent (*FuriHalNfcWaitEvent)(uint32_t timeout_ms); * @param[in,out] handle pointer to the NFC chip SPI handle. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ -typedef FuriHalNfcError (*FuriHalNfcSleep)(FuriHalSpiBusHandle* handle); +typedef FuriHalNfcError (*FuriHalNfcSleep)(const FuriHalSpiBusHandle* handle); /** * @brief Go to idle in listener mode. @@ -79,7 +79,7 @@ typedef FuriHalNfcError (*FuriHalNfcSleep)(FuriHalSpiBusHandle* handle); * @param[in,out] handle pointer to the NFC chip SPI handle. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ -typedef FuriHalNfcError (*FuriHalNfcIdle)(FuriHalSpiBusHandle* handle); +typedef FuriHalNfcError (*FuriHalNfcIdle)(const FuriHalSpiBusHandle* handle); /** * @brief Technology-specific compenstaion values for pollers. @@ -160,7 +160,7 @@ extern const FuriHalNfcTechBase furi_hal_nfc_felica; * This variable is defined in furi_hal_nfc.c. It will need to be modified * in case when a new technology is to be added. */ -extern const FuriHalNfcTechBase* furi_hal_nfc_tech[]; +extern const FuriHalNfcTechBase* const furi_hal_nfc_tech[]; #ifdef __cplusplus } diff --git a/targets/f7/furi_hal/furi_hal_pwm.h b/targets/f7/furi_hal/furi_hal_pwm.h index 16acca05e..30a5467d2 100644 --- a/targets/f7/furi_hal/furi_hal_pwm.h +++ b/targets/f7/furi_hal/furi_hal_pwm.h @@ -12,6 +12,7 @@ extern "C" { #include typedef enum { + FuriHalPwmOutputIdNone, FuriHalPwmOutputIdTim1PA7, FuriHalPwmOutputIdLptim2PA4, } FuriHalPwmOutputId; diff --git a/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c index 123ebc420..a6ba0b083 100644 --- a/targets/f7/furi_hal/furi_hal_resources.c +++ b/targets/f7/furi_hal/furi_hal_resources.c @@ -73,6 +73,7 @@ const GpioPinRecord gpio_pins[] = { {.pin = &gpio_ext_pa7, .name = "PA7", .channel = FuriHalAdcChannel12, + .pwm_output = FuriHalPwmOutputIdTim1PA7, .number = 2, .debug = false}, {.pin = &gpio_ext_pa6, @@ -83,6 +84,7 @@ const GpioPinRecord gpio_pins[] = { {.pin = &gpio_ext_pa4, .name = "PA4", .channel = FuriHalAdcChannel9, + .pwm_output = FuriHalPwmOutputIdLptim2PA4, .number = 4, .debug = false}, {.pin = &gpio_ext_pb3, diff --git a/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h index ec8794cc1..3e8501b86 100644 --- a/targets/f7/furi_hal/furi_hal_resources.h +++ b/targets/f7/furi_hal/furi_hal_resources.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -40,6 +41,7 @@ typedef struct { const GpioPin* pin; const char* name; const FuriHalAdcChannel channel; + const FuriHalPwmOutputId pwm_output; const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/targets/f7/furi_hal/furi_hal_sd.c b/targets/f7/furi_hal/furi_hal_sd.c index eca5b6da9..152192736 100644 --- a/targets/f7/furi_hal/furi_hal_sd.c +++ b/targets/f7/furi_hal/furi_hal_sd.c @@ -204,7 +204,7 @@ typedef struct { } SD_CardInfo; /** Pointer to currently used SPI Handle */ -FuriHalSpiBusHandle* furi_hal_sd_spi_handle = NULL; +const FuriHalSpiBusHandle* furi_hal_sd_spi_handle = NULL; static inline void sd_spi_select_card(void) { furi_hal_gpio_write(furi_hal_sd_spi_handle->cs, false); diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 5ddb0785f..8ad9794a8 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -120,7 +120,7 @@ static void furi_hal_serial_usart_irq_callback(void* context) { } if(USART1->ISR & USART_ISR_PE) { USART1->ICR = USART_ICR_PECF; - event |= FuriHalSerialRxEventFrameError; + event |= FuriHalSerialRxEventParityError; } if(furi_hal_serial[FuriHalSerialIdUsart].buffer_rx_ptr == NULL) { @@ -321,7 +321,7 @@ static void furi_hal_serial_lpuart_irq_callback(void* context) { } if(LPUART1->ISR & USART_ISR_PE) { LPUART1->ICR = USART_ICR_PECF; - event |= FuriHalSerialRxEventFrameError; + event |= FuriHalSerialRxEventParityError; } if(furi_hal_serial[FuriHalSerialIdLpuart].buffer_rx_ptr == NULL) { @@ -605,6 +605,92 @@ void furi_hal_serial_set_br(FuriHalSerialHandle* handle, uint32_t baud) { } } +// Avoid duplicating look-up tables between USART and LPUART +static_assert(LL_LPUART_DATAWIDTH_7B == LL_USART_DATAWIDTH_7B); +static_assert(LL_LPUART_DATAWIDTH_8B == LL_USART_DATAWIDTH_8B); +static_assert(LL_LPUART_DATAWIDTH_9B == LL_USART_DATAWIDTH_9B); +static_assert(LL_LPUART_PARITY_NONE == LL_USART_PARITY_NONE); +static_assert(LL_LPUART_PARITY_EVEN == LL_USART_PARITY_EVEN); +static_assert(LL_LPUART_PARITY_ODD == LL_USART_PARITY_ODD); +static_assert(LL_LPUART_STOPBITS_1 == LL_USART_STOPBITS_1); +static_assert(LL_LPUART_STOPBITS_2 == LL_USART_STOPBITS_2); + +static const uint32_t serial_data_bits_lut[] = { + [FuriHalSerialDataBits7] = LL_USART_DATAWIDTH_7B, + [FuriHalSerialDataBits8] = LL_USART_DATAWIDTH_8B, + [FuriHalSerialDataBits9] = LL_USART_DATAWIDTH_9B, +}; + +static const uint32_t serial_parity_lut[] = { + [FuriHalSerialParityNone] = LL_USART_PARITY_NONE, + [FuriHalSerialParityEven] = LL_USART_PARITY_EVEN, + [FuriHalSerialParityOdd] = LL_USART_PARITY_ODD, +}; + +static const uint32_t serial_stop_bits_lut[] = { + [FuriHalSerialStopBits0_5] = LL_USART_STOPBITS_0_5, + [FuriHalSerialStopBits1] = LL_USART_STOPBITS_1, + [FuriHalSerialStopBits1_5] = LL_USART_STOPBITS_1_5, + [FuriHalSerialStopBits2] = LL_USART_STOPBITS_2, +}; + +static void furi_hal_serial_usart_configure_framing( + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { + LL_USART_SetDataWidth(USART1, serial_data_bits_lut[data_bits]); + LL_USART_SetParity(USART1, serial_parity_lut[parity]); + LL_USART_SetStopBitsLength(USART1, serial_stop_bits_lut[stop_bits]); +} + +static void furi_hal_serial_lpuart_configure_framing( + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { + LL_LPUART_SetDataWidth(LPUART1, serial_data_bits_lut[data_bits]); + LL_LPUART_SetParity(LPUART1, serial_parity_lut[parity]); + // Unsupported non-whole stop bit numbers have been furi_check'ed away + LL_LPUART_SetStopBitsLength(LPUART1, serial_stop_bits_lut[stop_bits]); +} + +void furi_hal_serial_configure_framing( + FuriHalSerialHandle* handle, + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits) { + furi_check(handle); + + // Unsupported combinations + if(data_bits == FuriHalSerialDataBits9) furi_check(parity == FuriHalSerialParityNone); + if(data_bits == FuriHalSerialDataBits6) furi_check(parity != FuriHalSerialParityNone); + + // Extend data word to account for parity bit + if(parity != FuriHalSerialParityNone) data_bits++; + + if(handle->id == FuriHalSerialIdUsart) { + if(LL_USART_IsEnabled(USART1)) { + // Wait for transfer complete flag + while(!LL_USART_IsActiveFlag_TC(USART1)) + ; + LL_USART_Disable(USART1); + furi_hal_serial_usart_configure_framing(data_bits, parity, stop_bits); + LL_USART_Enable(USART1); + } + } else if(handle->id == FuriHalSerialIdLpuart) { + // Unsupported configurations + furi_check(stop_bits == FuriHalSerialStopBits1 || stop_bits == FuriHalSerialStopBits2); + + if(LL_LPUART_IsEnabled(LPUART1)) { + // Wait for transfer complete flag + while(!LL_LPUART_IsActiveFlag_TC(LPUART1)) + ; + LL_LPUART_Disable(LPUART1); + furi_hal_serial_lpuart_configure_framing(data_bits, parity, stop_bits); + LL_LPUART_Enable(LPUART1); + } + } +} + void furi_hal_serial_deinit(FuriHalSerialHandle* handle) { furi_check(handle); furi_hal_serial_async_rx_configure(handle, NULL, NULL); diff --git a/targets/f7/furi_hal/furi_hal_serial.h b/targets/f7/furi_hal/furi_hal_serial.h index 00010d83c..ca8860a60 100644 --- a/targets/f7/furi_hal/furi_hal_serial.h +++ b/targets/f7/furi_hal/furi_hal_serial.h @@ -16,7 +16,9 @@ extern "C" { /** Initialize Serial * - * Configures GPIO, configures and enables transceiver. + * Configures GPIO, configures and enables transceiver. Default framing settings + * are used: 8 data bits, no parity, 1 stop bit. Override them with + * `furi_hal_serial_configure_framing`. * * @param handle Serial handle * @param baud baud rate @@ -64,6 +66,20 @@ bool furi_hal_serial_is_baud_rate_supported(FuriHalSerialHandle* handle, uint32_ */ void furi_hal_serial_set_br(FuriHalSerialHandle* handle, uint32_t baud); +/** + * @brief Configures framing of a serial interface + * + * @param handle Serial handle + * @param data_bits Data bits + * @param parity Parity + * @param stop_bits Stop bits + */ +void furi_hal_serial_configure_framing( + FuriHalSerialHandle* handle, + FuriHalSerialDataBits data_bits, + FuriHalSerialParity parity, + FuriHalSerialStopBits stop_bits); + /** Transmits data in semi-blocking mode * * Fills transmission pipe with data, returns as soon as all bytes from buffer @@ -93,6 +109,7 @@ typedef enum { FuriHalSerialRxEventFrameError = (1 << 2), /**< Framing Error: incorrect frame detected */ FuriHalSerialRxEventNoiseError = (1 << 3), /**< Noise Error: noise on the line detected */ FuriHalSerialRxEventOverrunError = (1 << 4), /**< Overrun Error: no space for received data */ + FuriHalSerialRxEventParityError = (1 << 5), /**< Parity Error: incorrect parity bit received */ } FuriHalSerialRxEvent; /** Receive callback @@ -172,7 +189,7 @@ typedef void (*FuriHalSerialDmaRxCallback)( void* context); /** - * @brief Enable an input/output directon + * @brief Enable an input/output direction * * Takes over the respective pin by reconfiguring it to * the appropriate alternative function. @@ -185,7 +202,7 @@ void furi_hal_serial_enable_direction( FuriHalSerialDirection direction); /** - * @brief Disable an input/output directon + * @brief Disable an input/output direction * * Releases the respective pin by reconfiguring it to * initial state, making possible its use for other purposes. diff --git a/targets/f7/furi_hal/furi_hal_serial_types.h b/targets/f7/furi_hal/furi_hal_serial_types.h index 9f10102e1..a427765dd 100644 --- a/targets/f7/furi_hal/furi_hal_serial_types.h +++ b/targets/f7/furi_hal/furi_hal_serial_types.h @@ -19,4 +19,39 @@ typedef enum { FuriHalSerialDirectionMax, } FuriHalSerialDirection; +/** + * @brief Actual data bits, i.e. not including start/stop and parity bits + * @note 6 data bits are only permitted when parity is enabled + * @note 9 data bits are only permitted when parity is disabled + */ +typedef enum { + FuriHalSerialDataBits6, + FuriHalSerialDataBits7, + FuriHalSerialDataBits8, + FuriHalSerialDataBits9, + + FuriHalSerialDataBitsMax, +} FuriHalSerialDataBits; + +typedef enum { + FuriHalSerialParityNone, + FuriHalSerialParityEven, + FuriHalSerialParityOdd, + + FuriHalSerialParityMax, +} FuriHalSerialParity; + +/** + * @brief Stop bit length + * @note LPUART only supports whole stop bit lengths (i.e. 1 and 2, but not 0.5 and 1.5) + */ +typedef enum { + FuriHalSerialStopBits0_5, + FuriHalSerialStopBits1, + FuriHalSerialStopBits1_5, + FuriHalSerialStopBits2, + + FuriHalSerialStopBits2Max, +} FuriHalSerialStopBits; + typedef struct FuriHalSerialHandle FuriHalSerialHandle; diff --git a/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c index 2a7cb7c25..9997d278d 100644 --- a/targets/f7/furi_hal/furi_hal_spi.c +++ b/targets/f7/furi_hal/furi_hal_spi.c @@ -38,17 +38,17 @@ void furi_hal_spi_bus_deinit(FuriHalSpiBus* bus) { bus->callback(bus, FuriHalSpiBusEventDeinit); } -void furi_hal_spi_bus_handle_init(FuriHalSpiBusHandle* handle) { +void furi_hal_spi_bus_handle_init(const FuriHalSpiBusHandle* handle) { furi_check(handle); handle->callback(handle, FuriHalSpiBusHandleEventInit); } -void furi_hal_spi_bus_handle_deinit(FuriHalSpiBusHandle* handle) { +void furi_hal_spi_bus_handle_deinit(const FuriHalSpiBusHandle* handle) { furi_check(handle); handle->callback(handle, FuriHalSpiBusHandleEventDeinit); } -void furi_hal_spi_acquire(FuriHalSpiBusHandle* handle) { +void furi_hal_spi_acquire(const FuriHalSpiBusHandle* handle) { furi_check(handle); furi_hal_power_insomnia_enter(); @@ -62,7 +62,7 @@ void furi_hal_spi_acquire(FuriHalSpiBusHandle* handle) { handle->callback(handle, FuriHalSpiBusHandleEventActivate); } -void furi_hal_spi_release(FuriHalSpiBusHandle* handle) { +void furi_hal_spi_release(const FuriHalSpiBusHandle* handle) { furi_check(handle); furi_check(handle->bus->current_handle == handle); @@ -77,7 +77,7 @@ void furi_hal_spi_release(FuriHalSpiBusHandle* handle) { furi_hal_power_insomnia_exit(); } -static void furi_hal_spi_bus_end_txrx(FuriHalSpiBusHandle* handle, uint32_t timeout) { +static void furi_hal_spi_bus_end_txrx(const FuriHalSpiBusHandle* handle, uint32_t timeout) { UNUSED(timeout); // FIXME while(LL_SPI_GetTxFIFOLevel(handle->bus->spi) != LL_SPI_TX_FIFO_EMPTY) ; @@ -89,7 +89,7 @@ static void furi_hal_spi_bus_end_txrx(FuriHalSpiBusHandle* handle, uint32_t time } bool furi_hal_spi_bus_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* buffer, size_t size, uint32_t timeout) { @@ -102,7 +102,7 @@ bool furi_hal_spi_bus_rx( } bool furi_hal_spi_bus_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* buffer, size_t size, uint32_t timeout) { @@ -128,7 +128,7 @@ bool furi_hal_spi_bus_tx( } bool furi_hal_spi_bus_trx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, @@ -192,7 +192,7 @@ static void spi_dma_isr(void* context) { } bool furi_hal_spi_bus_trx_dma( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, diff --git a/targets/f7/furi_hal/furi_hal_spi_config.c b/targets/f7/furi_hal/furi_hal_spi_config.c index 8a694961a..ece0c05f7 100644 --- a/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/targets/f7/furi_hal/furi_hal_spi_config.c @@ -147,7 +147,7 @@ FuriHalSpiBus furi_hal_spi_bus_d = { /* SPI Bus Handles */ inline static void furi_hal_spi_bus_r_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -193,7 +193,7 @@ inline static void furi_hal_spi_bus_r_handle_event_callback( } inline static void furi_hal_spi_bus_external_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -239,7 +239,7 @@ inline static void furi_hal_spi_bus_external_handle_event_callback( } inline static void furi_hal_spi_bus_nfc_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -305,12 +305,12 @@ inline static void furi_hal_spi_bus_nfc_handle_event_callback( } static void furi_hal_spi_bus_handle_subghz_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_8m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { .bus = &furi_hal_spi_bus_r, .callback = furi_hal_spi_bus_handle_subghz_event_callback, .miso = &gpio_spi_r_miso, @@ -320,12 +320,12 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz = { }; static void furi_hal_spi_bus_handle_nfc_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_nfc_handle_event_callback(handle, event, &furi_hal_spi_preset_2edge_low_8m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { .bus = &furi_hal_spi_bus_r, .callback = furi_hal_spi_bus_handle_nfc_event_callback, .miso = &gpio_spi_r_miso, @@ -335,13 +335,13 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { }; static void furi_hal_spi_bus_handle_external_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_external_handle_event_callback( handle, event, &furi_hal_spi_preset_1edge_low_2m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { .bus = &furi_hal_spi_bus_r, .callback = furi_hal_spi_bus_handle_external_event_callback, .miso = &gpio_ext_pa6, @@ -351,7 +351,7 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { }; inline static void furi_hal_spi_bus_d_handle_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, const LL_SPI_InitTypeDef* preset) { if(event == FuriHalSpiBusHandleEventInit) { @@ -392,12 +392,12 @@ inline static void furi_hal_spi_bus_d_handle_event_callback( } static void furi_hal_spi_bus_handle_display_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_4m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_display_event_callback, .miso = &gpio_spi_d_miso, @@ -407,12 +407,12 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = { }; static void furi_hal_spi_bus_handle_sd_fast_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_16m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_sd_fast_event_callback, .miso = &gpio_spi_d_miso, @@ -422,12 +422,12 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = { }; static void furi_hal_spi_bus_handle_sd_slow_event_callback( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); } -FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = { +const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = { .bus = &furi_hal_spi_bus_d, .callback = furi_hal_spi_bus_handle_sd_slow_event_callback, .miso = &gpio_spi_d_miso, diff --git a/targets/f7/furi_hal/furi_hal_spi_config.h b/targets/f7/furi_hal/furi_hal_spi_config.h index eab633a19..e90cd7061 100644 --- a/targets/f7/furi_hal/furi_hal_spi_config.h +++ b/targets/f7/furi_hal/furi_hal_spi_config.h @@ -28,10 +28,10 @@ extern FuriHalSpiBus furi_hal_spi_bus_r; extern FuriHalSpiBus furi_hal_spi_bus_d; /** CC1101 on `furi_hal_spi_bus_r` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_subghz; /** ST25R3916 on `furi_hal_spi_bus_r` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc; /** External on `furi_hal_spi_bus_r` * Preset: `furi_hal_spi_preset_1edge_low_2m` @@ -45,16 +45,16 @@ extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc; * Bus pins are floating on inactive state, CS high after initialization * */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_external; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_external; /** ST7567(Display) on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_display; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_display; /** SdCard in fast mode on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast; /** SdCard in slow mode on `furi_hal_spi_bus_d` */ -extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow; +extern const FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow; #ifdef __cplusplus } diff --git a/targets/f7/furi_hal/furi_hal_spi_types.h b/targets/f7/furi_hal/furi_hal_spi_types.h index ecc18d50d..9bf138ac0 100644 --- a/targets/f7/furi_hal/furi_hal_spi_types.h +++ b/targets/f7/furi_hal/furi_hal_spi_types.h @@ -31,7 +31,7 @@ typedef void (*FuriHalSpiBusEventCallback)(FuriHalSpiBus* bus, FuriHalSpiBusEven struct FuriHalSpiBus { SPI_TypeDef* spi; FuriHalSpiBusEventCallback callback; - FuriHalSpiBusHandle* current_handle; + const FuriHalSpiBusHandle* current_handle; }; /** FuriHal spi handle states */ @@ -44,7 +44,7 @@ typedef enum { /** FuriHal spi handle event callback */ typedef void (*FuriHalSpiBusHandleEventCallback)( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event); /** FuriHal spi handle */ diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c index cfedb5e76..f9c1d3a42 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -392,10 +392,11 @@ static void cdc_on_suspend(usbd_device* dev); static usbd_respond cdc_ep_config(usbd_device* dev, uint8_t cfg); static usbd_respond cdc_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback); + static usbd_device* usb_dev; -static FuriHalUsbInterface* cdc_if_cur = NULL; -static bool connected = false; -static CdcCallbacks* callbacks[IF_NUM_MAX] = {NULL}; +static volatile FuriHalUsbInterface* cdc_if_cur = NULL; +static volatile bool connected = false; +static volatile CdcCallbacks* callbacks[IF_NUM_MAX] = {NULL}; static void* cb_ctx[IF_NUM_MAX]; FuriHalUsbInterface usb_cdc_single = { @@ -506,8 +507,10 @@ uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num) { void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { if(if_num == 0) { usbd_ep_write(usb_dev, CDC0_TXD_EP, buf, len); - } else { + } else if(if_num == 1) { usbd_ep_write(usb_dev, CDC1_TXD_EP, buf, len); + } else { + furi_crash(); } } @@ -515,8 +518,10 @@ int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { int32_t len = 0; if(if_num == 0) { len = usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); - } else { + } else if(if_num == 1) { len = usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); + } else { + furi_crash(); } return (len < 0) ? 0 : len; } diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.h b/targets/f7/furi_hal/furi_hal_usb_cdc.h index 89b68991b..50d456698 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.h +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.h @@ -9,11 +9,21 @@ extern "C" { #endif +typedef enum { + CdcStateDisconnected, + CdcStateConnected, +} CdcState; + +typedef enum { + CdcCtrlLineDTR = (1 << 0), + CdcCtrlLineRTS = (1 << 1), +} CdcCtrlLine; + typedef struct { void (*tx_ep_callback)(void* context); void (*rx_ep_callback)(void* context); - void (*state_callback)(void* context, uint8_t state); - void (*ctrl_line_callback)(void* context, uint8_t state); + void (*state_callback)(void* context, CdcState state); + void (*ctrl_line_callback)(void* context, CdcCtrlLine ctrl_lines); void (*config_callback)(void* context, struct usb_cdc_line_coding* config); } CdcCallbacks; diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 357019ea2..8d34925ec 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -11,13 +11,16 @@ #endif /* CMSIS_device_header */ #include CMSIS_device_header +#include #define configENABLE_FPU 1 #define configENABLE_MPU 0 #define configUSE_PREEMPTION 1 #define configSUPPORT_STATIC_ALLOCATION 1 -#define configSUPPORT_DYNAMIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configENABLE_HEAP_PROTECTOR 1 +#define configHEAP_CLEAR_MEMORY_ON_FREE 1 #define configUSE_MALLOC_FAILED_HOOK 0 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 @@ -30,7 +33,7 @@ #define configUSE_POSIX_ERRNO 1 /* Heap size determined automatically by linker */ -// #define configTOTAL_HEAP_SIZE ((size_t)0) +#define configTOTAL_HEAP_SIZE ((uint32_t) & __heap_end__ - (uint32_t) & __heap_start__) #define configMAX_TASK_NAME_LEN (32) #define configGENERATE_RUN_TIME_STATS 1 @@ -146,7 +149,7 @@ standard names. */ /* Normal assert() semantics without relying on the provision of an assert.h header file. */ -#ifdef DEBUG +#ifdef FURI_DEBUG #define configASSERT(x) \ if((x) == 0) { \ furi_crash("FreeRTOS Assert"); \ diff --git a/targets/f7/inc/stm32wb55_linker.h b/targets/f7/inc/stm32wb55_linker.h index 4b56a11be..e978dff35 100644 --- a/targets/f7/inc/stm32wb55_linker.h +++ b/targets/f7/inc/stm32wb55_linker.h @@ -9,25 +9,28 @@ #ifdef __cplusplus extern "C" { +typedef const char linker_symbol_t; +#else +typedef const void linker_symbol_t; #endif -extern const void _stack_end; /**< end of stack */ -extern const void _stack_size; /**< stack size */ +extern linker_symbol_t _stack_end; /**< end of stack */ +extern linker_symbol_t _stack_size; /**< stack size */ -extern const void _sidata; /**< data initial value start */ -extern const void _sdata; /**< data start */ -extern const void _edata; /**< data end */ +extern linker_symbol_t _sidata; /**< data initial value start */ +extern linker_symbol_t _sdata; /**< data start */ +extern linker_symbol_t _edata; /**< data end */ -extern const void _sbss; /**< bss start */ -extern const void _ebss; /**< bss end */ +extern linker_symbol_t _sbss; /**< bss start */ +extern linker_symbol_t _ebss; /**< bss end */ -extern const void _sMB_MEM2; /**< RAM2a start */ -extern const void _eMB_MEM2; /**< RAM2a end */ +extern linker_symbol_t _sMB_MEM2; /**< RAM2a start */ +extern linker_symbol_t _eMB_MEM2; /**< RAM2a end */ -extern const void __heap_start__; /**< RAM1 Heap start */ -extern const void __heap_end__; /**< RAM1 Heap end */ +extern linker_symbol_t __heap_start__; /**< RAM1 Heap start */ +extern linker_symbol_t __heap_end__; /**< RAM1 Heap end */ -extern const void __free_flash_start__; /**< Free Flash space start */ +extern linker_symbol_t __free_flash_start__; /**< Free Flash space start */ #ifdef __cplusplus } diff --git a/targets/f7/stm32wb55xx_flash.ld b/targets/f7/stm32wb55xx_flash.ld index 524da6fc3..c31aee863 100644 --- a/targets/f7/stm32wb55xx_flash.ld +++ b/targets/f7/stm32wb55xx_flash.ld @@ -3,7 +3,7 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _stack_end = 0x20030000; /* end of RAM */ /* Generate a link error if heap and stack don't fit into RAM */ -_stack_size = 0x200; /* required amount of stack */ +_stack_size = 0x400; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K @@ -131,4 +131,55 @@ SECTIONS { MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM2A ._sram2a_free : { . = ALIGN(4); __sram2a_free__ = .; } >RAM2A ._sram2b_start : { . = ALIGN(4); __sram2b_start__ = .; } >RAM2B + + /* Default debug-related rules from ld */ + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1. */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions. */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2. */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2. */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions. */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3. */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF 5. */ + .debug_addr 0 : { *(.debug_addr) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_macro 0 : { *(.debug_macro) } + .debug_names 0 : { *(.debug_names) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } + .debug_sup 0 : { *(.debug_sup) } + .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) } + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } } diff --git a/targets/f7/stm32wb55xx_ram_fw.ld b/targets/f7/stm32wb55xx_ram_fw.ld index f0e8ad678..c3f5f8a6a 100644 --- a/targets/f7/stm32wb55xx_ram_fw.ld +++ b/targets/f7/stm32wb55xx_ram_fw.ld @@ -3,7 +3,7 @@ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _stack_end = 0x20030000; /* end of RAM */ /* Generate a link error if heap and stack don't fit into RAM */ -_stack_size = 0x200; /* required amount of stack */ +_stack_size = 0x400; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K @@ -129,4 +129,55 @@ SECTIONS { MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM2A ._sram2a_free : { . = ALIGN(4); __sram2a_free__ = .; } >RAM2A ._sram2b_start : { . = ALIGN(4); __sram2b_start__ = .; } >RAM2B + + /* Default debug-related rules from ld */ + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1. */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions. */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2. */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2. */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions. */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3. */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF 5. */ + .debug_addr 0 : { *(.debug_addr) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_macro 0 : { *(.debug_macro) } + .debug_names 0 : { *(.debug_names) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } + .debug_sup 0 : { *(.debug_sup) } + .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) } + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } } diff --git a/targets/f7/target.json b/targets/f7/target.json index f5b3cf3b6..911aa0822 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -50,6 +50,7 @@ "flipper7", "bit_lib", "toolbox", - "datetime" + "datetime", + "ieee754_parse_wrap" ] } diff --git a/targets/furi_hal_include/furi_hal_i2c.h b/targets/furi_hal_include/furi_hal_i2c.h index 7d69cd74d..fe9f0949c 100644 --- a/targets/furi_hal_include/furi_hal_i2c.h +++ b/targets/furi_hal_include/furi_hal_i2c.h @@ -55,14 +55,14 @@ void furi_hal_i2c_init(void); * * @param handle Pointer to FuriHalI2cBusHandle instance */ -void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle); +void furi_hal_i2c_acquire(const FuriHalI2cBusHandle* handle); /** Release I2C bus handle * * @param handle Pointer to FuriHalI2cBusHandle instance acquired in * `furi_hal_i2c_acquire` */ -void furi_hal_i2c_release(FuriHalI2cBusHandle* handle); +void furi_hal_i2c_release(const FuriHalI2cBusHandle* handle); /** Perform I2C TX transfer * @@ -75,7 +75,7 @@ void furi_hal_i2c_release(FuriHalI2cBusHandle* handle); * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_tx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* data, size_t size, @@ -96,7 +96,7 @@ bool furi_hal_i2c_tx( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_tx_ext( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint16_t address, bool ten_bit, const uint8_t* data, @@ -116,7 +116,7 @@ bool furi_hal_i2c_tx_ext( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_rx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, uint8_t* data, size_t size, @@ -136,7 +136,7 @@ bool furi_hal_i2c_rx( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_rx_ext( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint16_t address, bool ten_bit, uint8_t* data, @@ -158,7 +158,7 @@ bool furi_hal_i2c_rx_ext( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_trx( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t address, const uint8_t* tx_data, size_t tx_size, @@ -174,7 +174,10 @@ bool furi_hal_i2c_trx( * * @return true if device present and is ready, false otherwise */ -bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint32_t timeout); +bool furi_hal_i2c_is_device_ready( + const FuriHalI2cBusHandle* handle, + uint8_t i2c_addr, + uint32_t timeout); /** Perform I2C device register read (8-bit) * @@ -187,7 +190,7 @@ bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_read_reg_8( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint8_t* data, @@ -204,7 +207,7 @@ bool furi_hal_i2c_read_reg_8( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_read_reg_16( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint16_t* data, @@ -222,7 +225,7 @@ bool furi_hal_i2c_read_reg_16( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_read_mem( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, uint8_t* data, @@ -240,7 +243,7 @@ bool furi_hal_i2c_read_mem( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_write_reg_8( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint8_t data, @@ -257,7 +260,7 @@ bool furi_hal_i2c_write_reg_8( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_write_reg_16( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t reg_addr, uint16_t data, @@ -275,7 +278,7 @@ bool furi_hal_i2c_write_reg_16( * @return true on successful transfer, false otherwise */ bool furi_hal_i2c_write_mem( - FuriHalI2cBusHandle* handle, + const FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint8_t mem_addr, const uint8_t* data, diff --git a/targets/furi_hal_include/furi_hal_infrared.h b/targets/furi_hal_include/furi_hal_infrared.h index 29f7101c1..36eaf122d 100644 --- a/targets/furi_hal_include/furi_hal_infrared.h +++ b/targets/furi_hal_include/furi_hal_infrared.h @@ -13,7 +13,7 @@ extern "C" { #endif -#define INFRARED_MAX_FREQUENCY 56000 +#define INFRARED_MAX_FREQUENCY 1000000 #define INFRARED_MIN_FREQUENCY 10000 typedef enum { diff --git a/targets/furi_hal_include/furi_hal_power.h b/targets/furi_hal_include/furi_hal_power.h index 06598df86..f5b6ac71b 100644 --- a/targets/furi_hal_include/furi_hal_power.h +++ b/targets/furi_hal_include/furi_hal_power.h @@ -105,10 +105,14 @@ void furi_hal_power_off(void); FURI_NORETURN void furi_hal_power_reset(void); /** OTG enable + * + * @warning this is low level control, use power service instead */ bool furi_hal_power_enable_otg(void); /** OTG disable + * + * @warning this is low level control, use power service instead */ void furi_hal_power_disable_otg(void); diff --git a/targets/furi_hal_include/furi_hal_spi.h b/targets/furi_hal_include/furi_hal_spi.h index d497dff5c..41f3abdaa 100644 --- a/targets/furi_hal_include/furi_hal_spi.h +++ b/targets/furi_hal_include/furi_hal_spi.h @@ -35,13 +35,13 @@ void furi_hal_spi_bus_deinit(FuriHalSpiBus* bus); * * @param handle pointer to FuriHalSpiBusHandle instance */ -void furi_hal_spi_bus_handle_init(FuriHalSpiBusHandle* handle); +void furi_hal_spi_bus_handle_init(const FuriHalSpiBusHandle* handle); /** Deinitialize SPI Bus Handle * * @param handle pointer to FuriHalSpiBusHandle instance */ -void furi_hal_spi_bus_handle_deinit(FuriHalSpiBusHandle* handle); +void furi_hal_spi_bus_handle_deinit(const FuriHalSpiBusHandle* handle); /** Acquire SPI bus * @@ -49,7 +49,7 @@ void furi_hal_spi_bus_handle_deinit(FuriHalSpiBusHandle* handle); * * @param handle pointer to FuriHalSpiBusHandle instance */ -void furi_hal_spi_acquire(FuriHalSpiBusHandle* handle); +void furi_hal_spi_acquire(const FuriHalSpiBusHandle* handle); /** Release SPI bus * @@ -57,7 +57,7 @@ void furi_hal_spi_acquire(FuriHalSpiBusHandle* handle); * * @param handle pointer to FuriHalSpiBusHandle instance */ -void furi_hal_spi_release(FuriHalSpiBusHandle* handle); +void furi_hal_spi_release(const FuriHalSpiBusHandle* handle); /** SPI Receive * @@ -69,7 +69,7 @@ void furi_hal_spi_release(FuriHalSpiBusHandle* handle); * @return true on sucess */ bool furi_hal_spi_bus_rx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* buffer, size_t size, uint32_t timeout); @@ -84,7 +84,7 @@ bool furi_hal_spi_bus_rx( * @return true on success */ bool furi_hal_spi_bus_tx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* buffer, size_t size, uint32_t timeout); @@ -100,7 +100,7 @@ bool furi_hal_spi_bus_tx( * @return true on success */ bool furi_hal_spi_bus_trx( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, @@ -117,7 +117,7 @@ bool furi_hal_spi_bus_trx( * @return true on success */ bool furi_hal_spi_bus_trx_dma( - FuriHalSpiBusHandle* handle, + const FuriHalSpiBusHandle* handle, uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size,