mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-21 05:04:46 -07:00
Merge remote-tracking branch 'origin/ulcdict' into dev
This commit is contained in:
102
.github/CODEOWNERS
vendored
102
.github/CODEOWNERS
vendored
@@ -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
|
||||
|
||||
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -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:
|
||||
|
||||
1
.github/workflows/build_compact.yml
vendored
1
.github/workflows/build_compact.yml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
env:
|
||||
FBT_TOOLCHAIN_PATH: /runner/_work
|
||||
FBT_GIT_SUBMODULE_SHALLOW: 1
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
compact:
|
||||
|
||||
15
.github/workflows/docs.yml
vendored
15
.github/workflows/docs.yml
vendored
@@ -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' }}
|
||||
|
||||
1
.github/workflows/merge_report.yml
vendored
1
.github/workflows/merge_report.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
|
||||
env:
|
||||
FBT_TOOLCHAIN_PATH: /runner/_work
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
merge_report:
|
||||
|
||||
1
.github/workflows/pvs_studio.yml
vendored
1
.github/workflows/pvs_studio.yml
vendored
@@ -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:
|
||||
|
||||
54
.github/workflows/unit_tests.yml
vendored
54
.github/workflows/unit_tests.yml
vendored
@@ -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
|
||||
|
||||
56
.github/workflows/updater_test.yml
vendored
56
.github/workflows/updater_test.yml
vendored
@@ -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
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -67,3 +67,7 @@ PVS-Studio.log
|
||||
|
||||
# JS packages
|
||||
node_modules/
|
||||
|
||||
# cli_perf script output in case of errors
|
||||
/block.bin
|
||||
/return_block.bin
|
||||
|
||||
1
.vscode/example/settings.json.tmpl
vendored
1
.vscode/example/settings.json.tmpl
vendored
@@ -12,6 +12,7 @@
|
||||
"SConstruct": "python",
|
||||
"*.fam": "python"
|
||||
},
|
||||
"clangd.checkUpdates": false,
|
||||
"clangd.path": "${workspaceFolder}/toolchain/current/bin/clangd@FBT_PLATFORM_EXECUTABLE_EXT@",
|
||||
"clangd.arguments": [
|
||||
"--query-driver=**/arm-none-eabi-*",
|
||||
|
||||
15
SConstruct
15
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",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stdarg.h>
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
void AccessorApp::run(void) {
|
||||
AccessorEvent event;
|
||||
@@ -35,16 +36,18 @@ AccessorApp::AccessorApp()
|
||||
: text_store{0} {
|
||||
notification = static_cast<NotificationApp*>(furi_record_open(RECORD_NOTIFICATION));
|
||||
expansion = static_cast<Expansion*>(furi_record_open(RECORD_EXPANSION));
|
||||
power = static_cast<Power*>(furi_record_open(RECORD_POWER));
|
||||
onewire_host = onewire_host_alloc(&gpio_ibutton);
|
||||
expansion_disable(expansion);
|
||||
furi_hal_power_enable_otg();
|
||||
power_enable_otg(power, true);
|
||||
}
|
||||
|
||||
AccessorApp::~AccessorApp() {
|
||||
furi_hal_power_disable_otg();
|
||||
power_enable_otg(power, false);
|
||||
expansion_enable(expansion);
|
||||
furi_record_close(RECORD_EXPANSION);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_POWER);
|
||||
onewire_host_free(onewire_host);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <one_wire/one_wire_host.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <expansion/expansion.h>
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
class AccessorApp {
|
||||
public:
|
||||
@@ -53,4 +54,5 @@ private:
|
||||
|
||||
NotificationApp* notification;
|
||||
Expansion* expansion;
|
||||
Power* power;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,5 @@ App(
|
||||
entry_point="accessor_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=40,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ App(
|
||||
"power",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=130,
|
||||
fap_category="Debug",
|
||||
fap_libs=["assets"],
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="blink_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -13,6 +13,5 @@ App(
|
||||
"bt_debug",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=110,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -10,6 +10,5 @@ App(
|
||||
"ccid_test",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -24,8 +24,49 @@ typedef enum {
|
||||
CrashTestSubmenuAssertMessage,
|
||||
CrashTestSubmenuCrash,
|
||||
CrashTestSubmenuHalt,
|
||||
CrashTestSubmenuHeapUnderflow,
|
||||
CrashTestSubmenuHeapOverflow,
|
||||
} CrashTestSubmenu;
|
||||
|
||||
static void crash_test_corrupt_heap_underflow(void) {
|
||||
const size_t block_size = 1000;
|
||||
const size_t underflow_size = 123;
|
||||
uint8_t* block = malloc(block_size);
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wstringop-overflow" // that's what we want!
|
||||
memset(block - underflow_size, 0xDD, underflow_size); // -V769
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
free(block); // should crash here (if compiled with DEBUG=1)
|
||||
|
||||
// If we got here, the heap wasn't able to detect our corruption and crash
|
||||
furi_crash("Test failed, should've crashed with \"FreeRTOS Assert\" error");
|
||||
}
|
||||
|
||||
static void crash_test_corrupt_heap_overflow(void) {
|
||||
const size_t block_size = 1000;
|
||||
const size_t overflow_size = 123;
|
||||
uint8_t* block1 = malloc(block_size);
|
||||
uint8_t* block2 = malloc(block_size);
|
||||
memset(block2, 12, 34); // simulate use to avoid optimization // -V597 // -V1086
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wstringop-overflow" // that's what we want!
|
||||
memset(block1 + block_size, 0xDD, overflow_size); // -V769 // -V512
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
uint8_t* block3 = malloc(block_size);
|
||||
memset(block3, 12, 34); // simulate use to avoid optimization // -V597 // -V1086
|
||||
|
||||
free(block3); // should crash here (if compiled with DEBUG=1)
|
||||
free(block2);
|
||||
free(block1);
|
||||
|
||||
// If we got here, the heap wasn't able to detect our corruption and crash
|
||||
furi_crash("Test failed, should've crashed with \"FreeRTOS Assert\" error");
|
||||
}
|
||||
|
||||
static void crash_test_submenu_callback(void* context, uint32_t index) {
|
||||
CrashTest* instance = (CrashTest*)context;
|
||||
UNUSED(instance);
|
||||
@@ -49,6 +90,12 @@ static void crash_test_submenu_callback(void* context, uint32_t index) {
|
||||
case CrashTestSubmenuHalt:
|
||||
furi_halt("Crash test: furi_halt");
|
||||
break;
|
||||
case CrashTestSubmenuHeapUnderflow:
|
||||
crash_test_corrupt_heap_underflow();
|
||||
break;
|
||||
case CrashTestSubmenuHeapOverflow:
|
||||
crash_test_corrupt_heap_overflow();
|
||||
break;
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
@@ -94,6 +141,18 @@ CrashTest* crash_test_alloc(void) {
|
||||
instance->submenu, "Crash", CrashTestSubmenuCrash, crash_test_submenu_callback, instance);
|
||||
submenu_add_item(
|
||||
instance->submenu, "Halt", CrashTestSubmenuHalt, crash_test_submenu_callback, instance);
|
||||
submenu_add_item(
|
||||
instance->submenu,
|
||||
"Heap underflow",
|
||||
CrashTestSubmenuHeapUnderflow,
|
||||
crash_test_submenu_callback,
|
||||
instance);
|
||||
submenu_add_item(
|
||||
instance->submenu,
|
||||
"Heap overflow",
|
||||
CrashTestSubmenuHeapOverflow,
|
||||
crash_test_submenu_callback,
|
||||
instance);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="direct_draw_app",
|
||||
requires=["gui", "input"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -6,6 +6,5 @@ App(
|
||||
requires=["gui"],
|
||||
fap_libs=["u8g2"],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="event_loop_blink_test_app",
|
||||
requires=["input"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
requires=["expansion_start"],
|
||||
fap_libs=["assets"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
fap_file_assets="assets",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="file_browser_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=150,
|
||||
fap_category="Debug",
|
||||
fap_icon_assets="icons",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="keypad_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=30,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -11,6 +11,5 @@ App(
|
||||
"lfrfid_debug",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=100,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
8
applications/debug/loader_chaining_a/application.fam
Normal file
8
applications/debug/loader_chaining_a/application.fam
Normal file
@@ -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",
|
||||
)
|
||||
164
applications/debug/loader_chaining_a/loader_chaining_a.c
Normal file
164
applications/debug/loader_chaining_a/loader_chaining_a.c
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <loader/loader.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#define TAG "LoaderChainingA"
|
||||
#define CHAINING_TEST_B "/ext/apps/Debug/loader_chaining_b.fap"
|
||||
#define NONEXISTENT_APP "Some nonexistent app"
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
|
||||
Loader* loader;
|
||||
|
||||
DialogsApp* dialogs;
|
||||
} LoaderChainingA;
|
||||
|
||||
typedef enum {
|
||||
LoaderChainingASubmenuLaunchB,
|
||||
LoaderChainingASubmenuLaunchBThenA,
|
||||
LoaderChainingASubmenuLaunchNonexistentSilent,
|
||||
LoaderChainingASubmenuLaunchNonexistentGui,
|
||||
LoaderChainingASubmenuLaunchNonexistentGuiThenA,
|
||||
} LoaderChainingASubmenu;
|
||||
|
||||
static void loader_chaining_a_submenu_callback(void* context, uint32_t index) {
|
||||
LoaderChainingA* app = context;
|
||||
|
||||
FuriString* self_path = furi_string_alloc();
|
||||
furi_check(loader_get_application_launch_path(app->loader, self_path));
|
||||
|
||||
switch(index) {
|
||||
case LoaderChainingASubmenuLaunchB:
|
||||
loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchBThenA:
|
||||
loader_enqueue_launch(app->loader, CHAINING_TEST_B, "Hello", LoaderDeferredLaunchFlagGui);
|
||||
loader_enqueue_launch(
|
||||
app->loader,
|
||||
furi_string_get_cstr(self_path),
|
||||
"Hello to you from the future",
|
||||
LoaderDeferredLaunchFlagGui);
|
||||
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchNonexistentSilent:
|
||||
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagNone);
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchNonexistentGui:
|
||||
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui);
|
||||
break;
|
||||
|
||||
case LoaderChainingASubmenuLaunchNonexistentGuiThenA:
|
||||
loader_enqueue_launch(app->loader, NONEXISTENT_APP, NULL, LoaderDeferredLaunchFlagGui);
|
||||
loader_enqueue_launch(
|
||||
app->loader,
|
||||
furi_string_get_cstr(self_path),
|
||||
"Hello to you from the future",
|
||||
LoaderDeferredLaunchFlagGui);
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_free(self_path);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
}
|
||||
|
||||
static bool loader_chaining_a_nav_callback(void* context) {
|
||||
LoaderChainingA* app = context;
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
}
|
||||
|
||||
LoaderChainingA* loader_chaining_a_alloc(void) {
|
||||
LoaderChainingA* app = malloc(sizeof(LoaderChainingA));
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->loader = furi_record_open(RECORD_LOADER);
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->submenu = submenu_alloc();
|
||||
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Launch B",
|
||||
LoaderChainingASubmenuLaunchB,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Launch B, then A",
|
||||
LoaderChainingASubmenuLaunchBThenA,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Trigger error: silent",
|
||||
LoaderChainingASubmenuLaunchNonexistentSilent,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Trigger error: GUI",
|
||||
LoaderChainingASubmenuLaunchNonexistentGui,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Error, then launch A",
|
||||
LoaderChainingASubmenuLaunchNonexistentGuiThenA,
|
||||
loader_chaining_a_submenu_callback,
|
||||
app);
|
||||
|
||||
view_dispatcher_add_view(app->view_dispatcher, 0, submenu_get_view(app->submenu));
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, loader_chaining_a_nav_callback);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void loader_chaining_a_free(LoaderChainingA* app) {
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
furi_record_close(RECORD_LOADER);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, 0);
|
||||
submenu_free(app->submenu);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t chaining_test_app_a(const char* arg) {
|
||||
LoaderChainingA* app = loader_chaining_a_alloc();
|
||||
|
||||
if(arg) {
|
||||
if(strlen(arg)) {
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
FuriString* text;
|
||||
|
||||
dialog_message_set_header(message, "Hi, I am A", 64, 0, AlignCenter, AlignTop);
|
||||
text = furi_string_alloc_printf("Me from the past says:\n%s", arg);
|
||||
dialog_message_set_buttons(message, NULL, "ok!", NULL);
|
||||
|
||||
dialog_message_set_text(
|
||||
message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter);
|
||||
dialog_message_show(app->dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_string_free(text);
|
||||
}
|
||||
}
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
loader_chaining_a_free(app);
|
||||
return 0;
|
||||
}
|
||||
8
applications/debug/loader_chaining_b/application.fam
Normal file
8
applications/debug/loader_chaining_b/application.fam
Normal file
@@ -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",
|
||||
)
|
||||
27
applications/debug/loader_chaining_b/loader_chaining_b.c
Normal file
27
applications/debug/loader_chaining_b/loader_chaining_b.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <furi.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
int32_t chaining_test_app_b(const char* arg) {
|
||||
if(!arg) return 0;
|
||||
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_header(message, "Hi, I am B", 64, 0, AlignCenter, AlignTop);
|
||||
FuriString* text = furi_string_alloc_printf("And A told me:\n%s", arg);
|
||||
dialog_message_set_text(message, furi_string_get_cstr(text), 64, 32, AlignCenter, AlignCenter);
|
||||
dialog_message_set_buttons(message, "Just quit", NULL, "Launch A");
|
||||
DialogMessageButton result = dialog_message_show(dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_string_free(text);
|
||||
|
||||
if(result == DialogMessageButtonRight)
|
||||
loader_enqueue_launch(
|
||||
loader, "/ext/apps/Debug/loader_chaining_a.fap", NULL, LoaderDeferredLaunchFlagGui);
|
||||
|
||||
furi_record_close(RECORD_LOADER);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
return 0;
|
||||
}
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="locale_test_app",
|
||||
requires=["gui", "locale"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="rpc_debug_app",
|
||||
requires=["gui", "rpc_start", "notification"],
|
||||
stack_size=2 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ App(
|
||||
entry_point="speaker_debug_app",
|
||||
requires=["gui", "notification"],
|
||||
stack_size=2 * 1024,
|
||||
order=10,
|
||||
fap_category="Debug",
|
||||
fap_libs=["music_worker"],
|
||||
)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <furi.h>
|
||||
#include <notification/notification.h>
|
||||
#include <music_worker/music_worker.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
|
||||
#define TAG "SpeakerDebug"
|
||||
|
||||
@@ -19,14 +21,14 @@ typedef struct {
|
||||
typedef struct {
|
||||
MusicWorker* music_worker;
|
||||
FuriMessageQueue* message_queue;
|
||||
Cli* cli;
|
||||
CliRegistry* cli_registry;
|
||||
} SpeakerDebugApp;
|
||||
|
||||
static SpeakerDebugApp* speaker_app_alloc(void) {
|
||||
SpeakerDebugApp* app = (SpeakerDebugApp*)malloc(sizeof(SpeakerDebugApp));
|
||||
app->music_worker = music_worker_alloc();
|
||||
app->message_queue = furi_message_queue_alloc(8, sizeof(SpeakerDebugAppMessage));
|
||||
app->cli = furi_record_open(RECORD_CLI);
|
||||
app->cli_registry = furi_record_open(RECORD_CLI);
|
||||
return app;
|
||||
}
|
||||
|
||||
@@ -37,8 +39,8 @@ static void speaker_app_free(SpeakerDebugApp* app) {
|
||||
free(app);
|
||||
}
|
||||
|
||||
static void speaker_app_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
static void speaker_app_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(pipe);
|
||||
|
||||
SpeakerDebugApp* app = (SpeakerDebugApp*)context;
|
||||
SpeakerDebugAppMessage message;
|
||||
@@ -95,7 +97,8 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
|
||||
return;
|
||||
}
|
||||
|
||||
cli_add_command(app->cli, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app);
|
||||
cli_registry_add_command(
|
||||
app->cli_registry, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app);
|
||||
|
||||
SpeakerDebugAppMessage message;
|
||||
FuriStatus status;
|
||||
@@ -110,7 +113,7 @@ static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
cli_delete_command(app->cli, CLI_COMMAND);
|
||||
cli_registry_delete_command(app->cli_registry, CLI_COMMAND);
|
||||
}
|
||||
|
||||
int32_t speaker_debug_app(void* arg) {
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
entry_point="subghz_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=50,
|
||||
fap_icon="subghz_test_10px.png",
|
||||
fap_category="Debug",
|
||||
fap_icon_assets="images",
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="text_box_element_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="text_box_view_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="uart_echo_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
#define LINES_ON_SCREEN 6
|
||||
#define COLUMNS_ON_SCREEN 21
|
||||
#define DEFAULT_BAUD_RATE 230400
|
||||
#define DEFAULT_DATA_BITS FuriHalSerialDataBits8
|
||||
#define DEFAULT_PARITY FuriHalSerialParityNone
|
||||
#define DEFAULT_STOP_BITS FuriHalSerialStopBits1
|
||||
|
||||
typedef struct UartDumpModel UartDumpModel;
|
||||
|
||||
@@ -49,11 +52,12 @@ typedef enum {
|
||||
WorkerEventRxOverrunError = (1 << 4),
|
||||
WorkerEventRxFramingError = (1 << 5),
|
||||
WorkerEventRxNoiseError = (1 << 6),
|
||||
WorkerEventRxParityError = (1 << 7),
|
||||
} WorkerEventFlags;
|
||||
|
||||
#define WORKER_EVENTS_MASK \
|
||||
(WorkerEventStop | WorkerEventRxData | WorkerEventRxIdle | WorkerEventRxOverrunError | \
|
||||
WorkerEventRxFramingError | WorkerEventRxNoiseError)
|
||||
WorkerEventRxFramingError | WorkerEventRxNoiseError | WorkerEventRxParityError)
|
||||
|
||||
const NotificationSequence sequence_notification = {
|
||||
&message_display_backlight_on,
|
||||
@@ -62,6 +66,13 @@ const NotificationSequence sequence_notification = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sequence_error = {
|
||||
&message_display_backlight_on,
|
||||
&message_red_255,
|
||||
&message_delay_10,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void uart_echo_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
UartDumpModel* model = _model;
|
||||
|
||||
@@ -133,6 +144,9 @@ static void
|
||||
if(event & FuriHalSerialRxEventOverrunError) {
|
||||
flag |= WorkerEventRxOverrunError;
|
||||
}
|
||||
if(event & FuriHalSerialRxEventParityError) {
|
||||
flag |= WorkerEventRxParityError;
|
||||
}
|
||||
|
||||
furi_thread_flags_set(furi_thread_get_id(app->worker_thread), flag);
|
||||
}
|
||||
@@ -227,13 +241,21 @@ static int32_t uart_echo_worker(void* context) {
|
||||
if(events & WorkerEventRxNoiseError) {
|
||||
furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect NE\r\n", 13);
|
||||
}
|
||||
if(events & WorkerEventRxParityError) {
|
||||
furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect PE\r\n", 13);
|
||||
}
|
||||
notification_message(app->notification, &sequence_error);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
|
||||
static UartEchoApp* uart_echo_app_alloc(
|
||||
uint32_t baudrate,
|
||||
FuriHalSerialDataBits data_bits,
|
||||
FuriHalSerialParity parity,
|
||||
FuriHalSerialStopBits stop_bits) {
|
||||
UartEchoApp* app = malloc(sizeof(UartEchoApp));
|
||||
|
||||
app->rx_stream = furi_stream_buffer_alloc(2048, 1);
|
||||
@@ -275,6 +297,7 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
|
||||
app->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart);
|
||||
furi_check(app->serial_handle);
|
||||
furi_hal_serial_init(app->serial_handle, baudrate);
|
||||
furi_hal_serial_configure_framing(app->serial_handle, data_bits, parity, stop_bits);
|
||||
|
||||
furi_hal_serial_async_rx_start(app->serial_handle, uart_echo_on_irq_cb, app, true);
|
||||
|
||||
@@ -318,19 +341,76 @@ static void uart_echo_app_free(UartEchoApp* app) {
|
||||
free(app);
|
||||
}
|
||||
|
||||
// silences "same-assignment" false positives in the arg parser below
|
||||
// -V::1048
|
||||
|
||||
int32_t uart_echo_app(void* p) {
|
||||
uint32_t baudrate = DEFAULT_BAUD_RATE;
|
||||
FuriHalSerialDataBits data_bits = DEFAULT_DATA_BITS;
|
||||
FuriHalSerialParity parity = DEFAULT_PARITY;
|
||||
FuriHalSerialStopBits stop_bits = DEFAULT_STOP_BITS;
|
||||
|
||||
if(p) {
|
||||
const char* baudrate_str = p;
|
||||
if(strint_to_uint32(baudrate_str, NULL, &baudrate, 10) != StrintParseNoError) {
|
||||
FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str);
|
||||
baudrate = DEFAULT_BAUD_RATE;
|
||||
// parse argument
|
||||
char* parse_ptr = p;
|
||||
bool parse_success = false;
|
||||
|
||||
do {
|
||||
if(strint_to_uint32(parse_ptr, &parse_ptr, &baudrate, 10) != StrintParseNoError) break;
|
||||
|
||||
if(*(parse_ptr++) != '_') break;
|
||||
|
||||
uint16_t data_bits_int;
|
||||
if(strint_to_uint16(parse_ptr, &parse_ptr, &data_bits_int, 10) != StrintParseNoError)
|
||||
break;
|
||||
if(data_bits_int == 6)
|
||||
data_bits = FuriHalSerialDataBits6;
|
||||
else if(data_bits_int == 7)
|
||||
data_bits = FuriHalSerialDataBits7;
|
||||
else if(data_bits_int == 8)
|
||||
data_bits = FuriHalSerialDataBits8;
|
||||
else if(data_bits_int == 9)
|
||||
data_bits = FuriHalSerialDataBits9;
|
||||
else
|
||||
break;
|
||||
|
||||
char parity_char = *(parse_ptr++);
|
||||
if(parity_char == 'N')
|
||||
parity = FuriHalSerialParityNone;
|
||||
else if(parity_char == 'E')
|
||||
parity = FuriHalSerialParityEven;
|
||||
else if(parity_char == 'O')
|
||||
parity = FuriHalSerialParityOdd;
|
||||
else
|
||||
break;
|
||||
|
||||
uint16_t stop_bits_int;
|
||||
if(strint_to_uint16(parse_ptr, &parse_ptr, &stop_bits_int, 10) != StrintParseNoError)
|
||||
break;
|
||||
if(stop_bits_int == 5)
|
||||
stop_bits = FuriHalSerialStopBits0_5;
|
||||
else if(stop_bits_int == 1)
|
||||
stop_bits = FuriHalSerialStopBits1;
|
||||
else if(stop_bits_int == 15)
|
||||
stop_bits = FuriHalSerialStopBits1_5;
|
||||
else if(stop_bits_int == 2)
|
||||
stop_bits = FuriHalSerialStopBits2;
|
||||
else
|
||||
break;
|
||||
|
||||
parse_success = true;
|
||||
} while(0);
|
||||
|
||||
if(!parse_success) {
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Couldn't parse baud rate and framing (%s). Applying defaults (%d_8N1)",
|
||||
(const char*)p,
|
||||
DEFAULT_BAUD_RATE);
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Using baudrate: %lu", baudrate);
|
||||
|
||||
UartEchoApp* app = uart_echo_app_alloc(baudrate);
|
||||
UartEchoApp* app = uart_echo_app_alloc(baudrate, data_bits, parity, stop_bits);
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
uart_echo_app_free(app);
|
||||
return 0;
|
||||
|
||||
@@ -4,10 +4,10 @@ App(
|
||||
entry_point="unit_tests_on_system_start",
|
||||
sources=["unit_tests.c", "test_runner.c", "unit_test_api_table.cpp"],
|
||||
cdefines=["APP_UNIT_TESTS"],
|
||||
requires=["system_settings", "subghz_start"],
|
||||
requires=["system_settings", "cli_subghz"],
|
||||
provides=["delay_test"],
|
||||
resources="resources",
|
||||
order=100,
|
||||
order=30,
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -18,7 +18,7 @@ App(
|
||||
entry_point="delay_test_app",
|
||||
stack_size=1 * 1024,
|
||||
requires=["unit_tests"],
|
||||
order=110,
|
||||
order=30,
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
#include "tests/test_api.h"
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <loader/loader.h>
|
||||
#include <storage/storage.h>
|
||||
#include <notification/notification_messages.h>
|
||||
@@ -25,7 +26,7 @@ struct TestRunner {
|
||||
NotificationApp* notification;
|
||||
|
||||
// Temporary used things
|
||||
Cli* cli;
|
||||
PipeSide* pipe;
|
||||
FuriString* args;
|
||||
|
||||
// ELF related stuff
|
||||
@@ -38,14 +39,14 @@ struct TestRunner {
|
||||
int minunit_status;
|
||||
};
|
||||
|
||||
TestRunner* test_runner_alloc(Cli* cli, FuriString* args) {
|
||||
TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args) {
|
||||
TestRunner* instance = malloc(sizeof(TestRunner));
|
||||
|
||||
instance->storage = furi_record_open(RECORD_STORAGE);
|
||||
instance->loader = furi_record_open(RECORD_LOADER);
|
||||
instance->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
instance->cli = cli;
|
||||
instance->pipe = pipe;
|
||||
instance->args = args;
|
||||
|
||||
instance->composite_resolver = composite_api_resolver_alloc();
|
||||
@@ -147,7 +148,7 @@ static void test_runner_run_internal(TestRunner* instance) {
|
||||
}
|
||||
|
||||
while(true) {
|
||||
if(cli_cmd_interrupt_received(instance->cli)) {
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(instance->pipe)) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
typedef struct TestRunner TestRunner;
|
||||
typedef struct Cli Cli;
|
||||
|
||||
TestRunner* test_runner_alloc(Cli* cli, FuriString* args);
|
||||
TestRunner* test_runner_alloc(PipeSide* pipe, FuriString* args);
|
||||
|
||||
void test_runner_free(TestRunner* isntance);
|
||||
void test_runner_free(TestRunner* instance);
|
||||
|
||||
void test_runner_run(TestRunner* isntance);
|
||||
void test_runner_run(TestRunner* instance);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
|
||||
111
applications/debug/unit_tests/tests/furi/furi_stdio_test.c
Normal file
111
applications/debug/unit_tests/tests/furi/furi_stdio_test.c
Normal file
@@ -0,0 +1,111 @@
|
||||
#include <furi.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TAG "JsUnitTests"
|
||||
|
||||
#define JS_SCRIPT_PATH(name) EXT_PATH("unit_tests/js/" name ".js")
|
||||
|
||||
typedef enum {
|
||||
@@ -73,7 +76,311 @@ MU_TEST(js_test_storage) {
|
||||
js_test_run(JS_SCRIPT_PATH("storage"));
|
||||
}
|
||||
|
||||
static void js_value_test_compatibility_matrix(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
JsValueTypeFunction,
|
||||
JsValueTypeRawPointer,
|
||||
JsValueTypeInt32,
|
||||
JsValueTypeDouble,
|
||||
JsValueTypeString,
|
||||
JsValueTypeBool,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_foreign(mjs, (void*)0xDEADBEEF),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
mjs_mk_number(mjs, 123.456),
|
||||
mjs_mk_string(mjs, "test", ~0, false),
|
||||
mjs_mk_boolean(mjs, true),
|
||||
};
|
||||
|
||||
// for proper matrix formatting and better readability
|
||||
#define YES true
|
||||
#define NO_ false
|
||||
static const bool success_matrix[COUNT_OF(types)][COUNT_OF(values)] = {
|
||||
// types:
|
||||
{YES, YES, YES, YES, YES, YES, YES}, // any
|
||||
{NO_, NO_, YES, NO_, NO_, NO_, NO_}, // array
|
||||
{NO_, NO_, YES, YES, NO_, NO_, NO_}, // obj
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, NO_}, // fn
|
||||
{NO_, YES, NO_, NO_, NO_, NO_, NO_}, // ptr
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // int32
|
||||
{NO_, NO_, NO_, NO_, YES, NO_, NO_}, // double
|
||||
{NO_, NO_, NO_, NO_, NO_, YES, NO_}, // str
|
||||
{NO_, NO_, NO_, NO_, NO_, NO_, YES}, // bool
|
||||
//
|
||||
//und ptr arr obj num str bool <- values
|
||||
};
|
||||
#undef NO_
|
||||
#undef YES
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
for(size_t j = 0; j < COUNT_OF(values); j++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
// we only care about the status, not the result. double has the largest size out of
|
||||
// all the results
|
||||
uint8_t result[sizeof(double)];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[j],
|
||||
result);
|
||||
if((status == JsValueParseStatusOk) != success_matrix[i][j]) {
|
||||
FURI_LOG_E(TAG, "type %zu, value %zu", i, j);
|
||||
mu_fail("see serial logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_literal(struct mjs* mjs) {
|
||||
static const JsValueType types[] = {
|
||||
JsValueTypeAny,
|
||||
JsValueTypeAnyArray,
|
||||
JsValueTypeAnyObject,
|
||||
};
|
||||
|
||||
mjs_val_t values[] = {
|
||||
mjs_mk_undefined(),
|
||||
mjs_mk_array(mjs),
|
||||
mjs_mk_object(mjs),
|
||||
};
|
||||
|
||||
mu_assert_int_eq(COUNT_OF(types), COUNT_OF(values));
|
||||
for(size_t i = 0; i < COUNT_OF(types); i++) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = types[i],
|
||||
.n_children = 0,
|
||||
};
|
||||
mjs_val_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&values[i],
|
||||
&result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert(result == values[i], "wrong result");
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitive(
|
||||
struct mjs* mjs,
|
||||
JsValueType type,
|
||||
const void* c_value,
|
||||
size_t c_value_size,
|
||||
mjs_val_t js_val) {
|
||||
const JsValueDeclaration declaration = {
|
||||
.type = type,
|
||||
.n_children = 0,
|
||||
};
|
||||
uint8_t result[c_value_size];
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&declaration),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&js_val,
|
||||
result);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
if(type == JsValueTypeString) {
|
||||
const char* result_str = *(const char**)&result;
|
||||
mu_assert_string_eq(c_value, result_str);
|
||||
} else {
|
||||
mu_assert_mem_eq(c_value, result, c_value_size);
|
||||
}
|
||||
}
|
||||
|
||||
static void js_value_test_primitives(struct mjs* mjs) {
|
||||
int32_t i32 = 123;
|
||||
js_value_test_primitive(mjs, JsValueTypeInt32, &i32, sizeof(i32), mjs_mk_number(mjs, i32));
|
||||
|
||||
double dbl = 123.456;
|
||||
js_value_test_primitive(mjs, JsValueTypeDouble, &dbl, sizeof(dbl), mjs_mk_number(mjs, dbl));
|
||||
|
||||
const char* str = "test";
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeString, str, strlen(str) + 1, mjs_mk_string(mjs, str, ~0, false));
|
||||
|
||||
bool boolean = true;
|
||||
js_value_test_primitive(
|
||||
mjs, JsValueTypeBool, &boolean, sizeof(boolean), mjs_mk_boolean(mjs, boolean));
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
js_value_test_enum(struct mjs* mjs, const JsValueDeclaration* decl, const char* value) {
|
||||
mjs_val_t str = mjs_mk_string(mjs, value, ~0, false);
|
||||
uint32_t result;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs, JS_VALUE_PARSE_SOURCE_VALUE(decl), JsValueParseFlagNone, &status, &str, &result);
|
||||
if(status != JsValueParseStatusOk) return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void js_value_test_enums(struct mjs* mjs) {
|
||||
static const JsValueEnumVariant enum_1_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_1 = JS_VALUE_ENUM(uint32_t, enum_1_variants);
|
||||
|
||||
static const JsValueEnumVariant enum_2_variants[] = {
|
||||
{"read", 4},
|
||||
{"write", 8},
|
||||
};
|
||||
static const JsValueDeclaration enum_2 = JS_VALUE_ENUM(uint32_t, enum_2_variants);
|
||||
|
||||
mu_assert_int_eq(1, js_value_test_enum(mjs, &enum_1, "variant 1"));
|
||||
mu_assert_int_eq(2, js_value_test_enum(mjs, &enum_1, "variant 2"));
|
||||
mu_assert_int_eq(3, js_value_test_enum(mjs, &enum_1, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_1, "not a thing"));
|
||||
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 1"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 2"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "variant 3"));
|
||||
mu_assert_int_eq(0, js_value_test_enum(mjs, &enum_2, "not a thing"));
|
||||
mu_assert_int_eq(4, js_value_test_enum(mjs, &enum_2, "read"));
|
||||
mu_assert_int_eq(8, js_value_test_enum(mjs, &enum_2, "write"));
|
||||
}
|
||||
|
||||
static void js_value_test_object(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl = JS_VALUE_SIMPLE(JsValueTypeInt32);
|
||||
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueEnumVariant enum_variants[] = {
|
||||
{"variant 1", 1},
|
||||
{"variant 2", 2},
|
||||
{"variant 3", 3},
|
||||
};
|
||||
static const JsValueDeclaration enum_decl = JS_VALUE_ENUM(uint32_t, enum_variants);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
{"enum", &enum_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_number(mjs, 123));
|
||||
JS_FIELD("enum", mjs_mk_string(mjs, "variant 2", ~0, false));
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
uint32_t result_enum;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str,
|
||||
&result_enum);
|
||||
mu_assert_int_eq(JsValueParseStatusOk, status);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
mu_assert_int_eq(2, result_enum);
|
||||
}
|
||||
|
||||
static void js_value_test_default(struct mjs* mjs) {
|
||||
static const JsValueDeclaration int_decl =
|
||||
JS_VALUE_SIMPLE_W_DEFAULT(JsValueTypeInt32, int32_val, 123);
|
||||
static const JsValueDeclaration str_decl = JS_VALUE_SIMPLE(JsValueTypeString);
|
||||
|
||||
static const JsValueObjectField fields[] = {
|
||||
{"int", &int_decl},
|
||||
{"str", &str_decl},
|
||||
};
|
||||
static const JsValueDeclaration object_decl = JS_VALUE_OBJECT(fields);
|
||||
|
||||
mjs_val_t object = mjs_mk_object(mjs);
|
||||
JS_ASSIGN_MULTI(mjs, object) {
|
||||
JS_FIELD("str", mjs_mk_string(mjs, "Helloooo!", ~0, false));
|
||||
JS_FIELD("int", mjs_mk_undefined());
|
||||
}
|
||||
|
||||
const char* result_str;
|
||||
int32_t result_int;
|
||||
JsValueParseStatus status;
|
||||
JS_VALUE_PARSE(
|
||||
mjs,
|
||||
JS_VALUE_PARSE_SOURCE_VALUE(&object_decl),
|
||||
JsValueParseFlagNone,
|
||||
&status,
|
||||
&object,
|
||||
&result_int,
|
||||
&result_str);
|
||||
mu_assert_string_eq("Helloooo!", result_str);
|
||||
mu_assert_int_eq(123, result_int);
|
||||
}
|
||||
|
||||
static void js_value_test_args_fn(struct mjs* mjs) {
|
||||
static const JsValueDeclaration arg_list[] = {
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
JS_VALUE_SIMPLE(JsValueTypeInt32),
|
||||
};
|
||||
static const JsValueArguments args = JS_VALUE_ARGS(arg_list);
|
||||
|
||||
int32_t a, b, c;
|
||||
JS_VALUE_PARSE_ARGS_OR_RETURN(mjs, &args, &a, &b, &c);
|
||||
|
||||
mu_assert_int_eq(123, a);
|
||||
mu_assert_int_eq(456, b);
|
||||
mu_assert_int_eq(-420, c);
|
||||
}
|
||||
|
||||
static void js_value_test_args(struct mjs* mjs) {
|
||||
mjs_val_t function = MJS_MK_FN(js_value_test_args_fn);
|
||||
|
||||
mjs_val_t result;
|
||||
mjs_val_t args[] = {
|
||||
mjs_mk_number(mjs, 123),
|
||||
mjs_mk_number(mjs, 456),
|
||||
mjs_mk_number(mjs, -420),
|
||||
};
|
||||
mu_assert_int_eq(
|
||||
MJS_OK, mjs_apply(mjs, &result, function, MJS_UNDEFINED, COUNT_OF(args), args));
|
||||
}
|
||||
|
||||
MU_TEST(js_value_test) {
|
||||
struct mjs* mjs = mjs_create(NULL);
|
||||
|
||||
js_value_test_compatibility_matrix(mjs);
|
||||
js_value_test_literal(mjs);
|
||||
js_value_test_primitives(mjs);
|
||||
js_value_test_enums(mjs);
|
||||
js_value_test_object(mjs);
|
||||
js_value_test_default(mjs);
|
||||
js_value_test_args(mjs);
|
||||
|
||||
mjs_destroy(mjs);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_js) {
|
||||
MU_RUN_TEST(js_value_test);
|
||||
MU_RUN_TEST(js_test_basic);
|
||||
MU_RUN_TEST(js_test_math);
|
||||
MU_RUN_TEST(js_test_event_loop);
|
||||
|
||||
@@ -396,6 +396,8 @@ void minunit_printf_warning(const char* format, ...);
|
||||
return; \
|
||||
} else { minunit_print_progress(); })
|
||||
|
||||
//-V:mu_assert_string_eq:526, 547
|
||||
|
||||
#define mu_assert_string_eq(expected, result) \
|
||||
MU__SAFE_BLOCK( \
|
||||
const char* minunit_tmp_e = expected; const char* minunit_tmp_r = result; \
|
||||
@@ -416,6 +418,8 @@ void minunit_printf_warning(const char* format, ...);
|
||||
return; \
|
||||
} else { minunit_print_progress(); })
|
||||
|
||||
//-V:mu_assert_mem_eq:526
|
||||
|
||||
#define mu_assert_mem_eq(expected, result, size) \
|
||||
MU__SAFE_BLOCK( \
|
||||
const void* minunit_tmp_e = expected; const void* minunit_tmp_r = result; \
|
||||
|
||||
@@ -262,7 +262,7 @@ static void mf_ultralight_reader_test(const char* path) {
|
||||
nfc_listener_start(mfu_listener, NULL, NULL);
|
||||
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||
|
||||
nfc_listener_stop(mfu_listener);
|
||||
@@ -315,7 +315,7 @@ MU_TEST(ntag_213_locked_reader) {
|
||||
nfc_listener_start(mfu_listener, NULL, NULL);
|
||||
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||
|
||||
nfc_listener_stop(mfu_listener);
|
||||
@@ -353,7 +353,7 @@ static void mf_ultralight_write(void) {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
// Initial read
|
||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||
|
||||
mu_assert(
|
||||
@@ -371,7 +371,7 @@ static void mf_ultralight_write(void) {
|
||||
}
|
||||
|
||||
// Verification read
|
||||
error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||
error = mf_ultralight_poller_sync_read_card(poller, mfu_data, NULL);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||
|
||||
nfc_listener_stop(mfu_listener);
|
||||
|
||||
142
applications/debug/unit_tests/tests/pipe/pipe_test.c
Normal file
142
applications/debug/unit_tests/tests/pipe/pipe_test.c
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "../test.h" // IWYU pragma: keep
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/toolbox/pipe.h>
|
||||
|
||||
#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)
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <rpc/rpc.h>
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <cli/cli.h>
|
||||
#include <storage/storage.h>
|
||||
#include <loader/loader.h>
|
||||
#include <storage/filesystem_api_defines.h>
|
||||
|
||||
@@ -521,11 +521,6 @@ MU_TEST(test_storage_data_path) {
|
||||
// check that appsdata folder exists
|
||||
mu_check(storage_dir_exists(storage, APPS_DATA_PATH));
|
||||
|
||||
// check that cli folder exists
|
||||
mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli")));
|
||||
|
||||
storage_simply_remove(storage, APPSDATA_APP_PATH("cli"));
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <flipper.pb.h>
|
||||
#include <applications/system/js_app/js_thread.h>
|
||||
#include <applications/system/js_app/js_value.h>
|
||||
|
||||
static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
API_METHOD(resource_manifest_reader_alloc, ResourceManifestReader*, (Storage*)),
|
||||
@@ -38,4 +39,16 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
|
||||
JsThread*,
|
||||
(const char* script_path, JsThreadCallback callback, void* context)),
|
||||
API_METHOD(js_thread_stop, void, (JsThread * worker)),
|
||||
API_METHOD(js_value_buffer_size, size_t, (const JsValueParseDeclaration declaration)),
|
||||
API_METHOD(
|
||||
js_value_parse,
|
||||
JsValueParseStatus,
|
||||
(struct mjs * mjs,
|
||||
const JsValueParseDeclaration declaration,
|
||||
JsValueParseFlag flags,
|
||||
mjs_val_t* buffer,
|
||||
size_t buf_size,
|
||||
mjs_val_t* source,
|
||||
size_t n_c_vals,
|
||||
...)),
|
||||
API_VARIABLE(PB_Main_msg, PB_Main_msg_t)));
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
#include <toolbox/cli/cli_registry.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
|
||||
#include "test_runner.h"
|
||||
|
||||
void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
void unit_tests_cli(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
TestRunner* test_runner = test_runner_alloc(cli, args);
|
||||
TestRunner* test_runner = test_runner_alloc(pipe, args);
|
||||
test_runner_run(test_runner);
|
||||
test_runner_free(test_runner);
|
||||
}
|
||||
|
||||
void unit_tests_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
|
||||
CliRegistry* registry = furi_record_open(RECORD_CLI);
|
||||
cli_registry_add_command(
|
||||
registry, "unit_tests", CliCommandFlagParallelSafe, unit_tests_cli, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="usb_mouse_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=60,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="usb_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=50,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -5,6 +5,5 @@ App(
|
||||
entry_point="vibro_test_app",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_category="Debug",
|
||||
)
|
||||
|
||||
@@ -85,7 +85,7 @@ typedef struct {
|
||||
volatile SubGhzDeviceCC1101ExtState state;
|
||||
volatile SubGhzDeviceCC1101ExtRegulation regulation;
|
||||
const GpioPin* async_mirror_pin;
|
||||
FuriHalSpiBusHandle* spi_bus_handle;
|
||||
const FuriHalSpiBusHandle* spi_bus_handle;
|
||||
const GpioPin* g0_pin;
|
||||
SubGhzDeviceCC1101ExtAsyncTx async_tx;
|
||||
SubGhzDeviceCC1101ExtAsyncRx async_rx;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <one_wire/maxim_crc.h>
|
||||
#include <one_wire/one_wire_host.h>
|
||||
|
||||
#include <furi_hal_power.h>
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
#define UPDATE_PERIOD_MS 1000UL
|
||||
#define TEXT_STORE_SIZE 64U
|
||||
@@ -76,6 +76,7 @@ typedef struct {
|
||||
FuriThread* reader_thread;
|
||||
FuriMessageQueue* event_queue;
|
||||
OneWireHost* onewire;
|
||||
Power* power;
|
||||
float temp_celsius;
|
||||
bool has_device;
|
||||
} ExampleThermoContext;
|
||||
@@ -273,7 +274,7 @@ static void example_thermo_input_callback(InputEvent* event, void* ctx) {
|
||||
/* Starts the reader thread and handles the input */
|
||||
static void example_thermo_run(ExampleThermoContext* context) {
|
||||
/* Enable power on external pins */
|
||||
furi_hal_power_enable_otg();
|
||||
power_enable_otg(context->power, true);
|
||||
|
||||
/* Configure the hardware in host mode */
|
||||
onewire_host_start(context->onewire);
|
||||
@@ -309,7 +310,7 @@ static void example_thermo_run(ExampleThermoContext* context) {
|
||||
onewire_host_stop(context->onewire);
|
||||
|
||||
/* Disable power on external pins */
|
||||
furi_hal_power_disable_otg();
|
||||
power_enable_otg(context->power, false);
|
||||
}
|
||||
|
||||
/******************** Initialisation & startup *****************************/
|
||||
@@ -334,6 +335,8 @@ static ExampleThermoContext* example_thermo_context_alloc(void) {
|
||||
|
||||
context->onewire = onewire_host_alloc(&THERMO_GPIO_PIN);
|
||||
|
||||
context->power = furi_record_open(RECORD_POWER);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -348,6 +351,7 @@ static void example_thermo_context_free(ExampleThermoContext* context) {
|
||||
view_port_free(context->view_port);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_POWER);
|
||||
}
|
||||
|
||||
/* The application's entry point. Execution starts from here. */
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -7,5 +7,5 @@ App(
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
icon="A_FileManager_14",
|
||||
order=0,
|
||||
order=10,
|
||||
)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "archive_apps.h"
|
||||
#include "archive_browser.h"
|
||||
|
||||
static const char* known_apps[] = {
|
||||
static const char* const known_apps[] = {
|
||||
[ArchiveAppTypeU2f] = "u2f",
|
||||
[ArchiveAppTypeSetting] = "setting",
|
||||
};
|
||||
|
||||
ArchiveAppTypeEnum archive_get_app_type(const char* path) {
|
||||
@@ -36,6 +37,8 @@ bool archive_app_is_available(void* context, const char* path) {
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return file_exists;
|
||||
} else if(app == ArchiveAppTypeSetting) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -53,6 +56,9 @@ bool archive_app_read_dir(void* context, const char* path) {
|
||||
if(app == ArchiveAppTypeU2f) {
|
||||
archive_add_app_item(browser, "/app:u2f/U2F Token");
|
||||
return true;
|
||||
} else if(app == ArchiveAppTypeSetting) {
|
||||
archive_add_app_item(browser, path);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -75,6 +81,8 @@ void archive_app_delete_file(void* context, const char* path) {
|
||||
if(archive_is_favorite("/app:u2f/U2F Token")) {
|
||||
archive_favorites_delete("/app:u2f/U2F Token");
|
||||
}
|
||||
} else if(app == ArchiveAppTypeSetting) {
|
||||
// can't delete a setting!
|
||||
}
|
||||
|
||||
if(res) {
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
|
||||
typedef enum {
|
||||
ArchiveAppTypeU2f,
|
||||
ArchiveAppTypeSetting,
|
||||
ArchiveAppTypeUnknown,
|
||||
ArchiveAppsTotal,
|
||||
} ArchiveAppTypeEnum;
|
||||
|
||||
static const ArchiveFileTypeEnum app_file_types[] = {
|
||||
[ArchiveAppTypeU2f] = ArchiveFileTypeU2f,
|
||||
[ArchiveAppTypeSetting] = ArchiveFileTypeSetting,
|
||||
[ArchiveAppTypeUnknown] = ArchiveFileTypeUnknown,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#define TAB_DEFAULT ArchiveTabFavorites // Start tab
|
||||
#define FILE_LIST_BUF_LEN 50
|
||||
|
||||
static const char* tab_default_paths[] = {
|
||||
static const char* const tab_default_paths[] = {
|
||||
[ArchiveTabFavorites] = "/app:favorites",
|
||||
[ArchiveTabIButton] = EXT_PATH("ibutton"),
|
||||
[ArchiveTabNFC] = EXT_PATH("nfc"),
|
||||
@@ -20,7 +20,7 @@ static const char* tab_default_paths[] = {
|
||||
[ArchiveTabBrowser] = STORAGE_EXT_PATH_PREFIX,
|
||||
};
|
||||
|
||||
static const char* known_ext[] = {
|
||||
static const char* const known_ext[] = {
|
||||
[ArchiveFileTypeIButton] = ".ibtn",
|
||||
[ArchiveFileTypeNFC] = ".nfc",
|
||||
[ArchiveFileTypeSubGhz] = ".sub",
|
||||
@@ -34,6 +34,7 @@ static const char* known_ext[] = {
|
||||
[ArchiveFileTypeFolder] = "?",
|
||||
[ArchiveFileTypeUnknown] = "*",
|
||||
[ArchiveFileTypeAppOrJs] = ".fap|.js",
|
||||
[ArchiveFileTypeSetting] = "?",
|
||||
};
|
||||
|
||||
static const ArchiveFileTypeEnum known_type[] = {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
#include "archive_favorites.h"
|
||||
#include "archive_files.h"
|
||||
#include "archive_apps.h"
|
||||
#include "archive_browser.h"
|
||||
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#define ARCHIVE_FAV_FILE_BUF_LEN 32
|
||||
|
||||
static bool archive_favorites_read_line(File* file, FuriString* str_result) {
|
||||
@@ -337,3 +338,46 @@ void archive_favorites_save(void* context) {
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting) {
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
|
||||
FuriString* setting_path = furi_string_alloc_set_str(app_name);
|
||||
if(setting) {
|
||||
furi_string_push_back(setting_path, '/');
|
||||
furi_string_cat_str(setting_path, setting);
|
||||
}
|
||||
const char* setting_path_str = furi_string_get_cstr(setting_path);
|
||||
|
||||
bool is_favorite = archive_is_favorite("/app:setting/%s", setting_path_str);
|
||||
dialog_message_set_header(
|
||||
message,
|
||||
is_favorite ? "Unpin This Setting?" : "Pin This Setting?",
|
||||
64,
|
||||
0,
|
||||
AlignCenter,
|
||||
AlignTop);
|
||||
dialog_message_set_text(
|
||||
message,
|
||||
is_favorite ? "It will no longer be\naccessible from the\nFavorites menu" :
|
||||
"It will be accessible from the\nFavorites menu",
|
||||
64,
|
||||
32,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
dialog_message_set_buttons(
|
||||
message, is_favorite ? "Unpin" : "Go back", NULL, is_favorite ? "Keep pinned" : "Pin");
|
||||
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
DialogMessageButton button = dialog_message_show(dialogs, message);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
|
||||
if(is_favorite && button == DialogMessageButtonLeft) {
|
||||
archive_favorites_delete("/app:setting/%s", setting_path_str);
|
||||
} else if(!is_favorite && button == DialogMessageButtonRight) {
|
||||
archive_file_append(ARCHIVE_FAV_PATH, "/app:setting/%s\n", setting_path_str);
|
||||
}
|
||||
|
||||
furi_string_free(setting_path);
|
||||
dialog_message_free(message);
|
||||
}
|
||||
|
||||
@@ -12,3 +12,13 @@ bool archive_is_favorite(const char* format, ...) _ATTRIBUTE((__format__(__print
|
||||
bool archive_favorites_rename(const char* src, const char* dst);
|
||||
void archive_add_to_favorites(const char* file_path);
|
||||
void archive_favorites_save(void* context);
|
||||
|
||||
/**
|
||||
* Intended to be called by settings apps to handle long presses, as well as
|
||||
* internally from within the archive
|
||||
*
|
||||
* @param app_name name of the referring application
|
||||
* @param setting name of the setting, which will be both displayed to the user
|
||||
* and passed to the application as an argument upon recall
|
||||
*/
|
||||
void archive_favorites_handle_setting_pin_unpin(const char* app_name, const char* setting);
|
||||
|
||||
@@ -20,6 +20,7 @@ typedef enum {
|
||||
ArchiveFileTypeFolder,
|
||||
ArchiveFileTypeUnknown,
|
||||
ArchiveFileTypeAppOrJs,
|
||||
ArchiveFileTypeSetting,
|
||||
ArchiveFileTypeLoading,
|
||||
} ArchiveFileTypeEnum;
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ static void archive_loader_callback(const void* message, void* context) {
|
||||
const LoaderEvent* event = message;
|
||||
ArchiveApp* archive = (ArchiveApp*)context;
|
||||
|
||||
if(event->type == LoaderEventTypeApplicationStopped) {
|
||||
if(event->type == LoaderEventTypeNoMoreAppsInQueue) {
|
||||
view_dispatcher_send_custom_event(
|
||||
archive->view_dispatcher, ArchiveBrowserEventListRefresh);
|
||||
}
|
||||
@@ -52,20 +52,37 @@ static void archive_run_in_app(ArchiveBrowserView* browser, ArchiveFile_t* selec
|
||||
UNUSED(browser);
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
|
||||
const char* app_name = archive_get_flipper_app_name(selected->type);
|
||||
|
||||
if(app_name) {
|
||||
if(selected->is_app) {
|
||||
char* param = strrchr(furi_string_get_cstr(selected->path), '/');
|
||||
if(param != NULL) {
|
||||
param++;
|
||||
}
|
||||
loader_start_with_gui_error(loader, app_name, param);
|
||||
if(selected->type == ArchiveFileTypeSetting) {
|
||||
FuriString* app_name = furi_string_alloc_set(selected->path);
|
||||
furi_string_right(app_name, furi_string_search_char(app_name, '/', 1) + 1);
|
||||
size_t slash = furi_string_search_char(app_name, '/', 1);
|
||||
if(slash != FURI_STRING_FAILURE) {
|
||||
furi_string_left(app_name, slash);
|
||||
FuriString* app_args =
|
||||
furi_string_alloc_set_str(furi_string_get_cstr(app_name) + slash + 1);
|
||||
loader_start_with_gui_error(
|
||||
loader, furi_string_get_cstr(app_name), furi_string_get_cstr(app_args));
|
||||
furi_string_free(app_args);
|
||||
} else {
|
||||
loader_start_with_gui_error(loader, app_name, furi_string_get_cstr(selected->path));
|
||||
loader_start_with_gui_error(loader, furi_string_get_cstr(app_name), NULL);
|
||||
}
|
||||
furi_string_free(app_name);
|
||||
} else {
|
||||
loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL);
|
||||
const char* app_name = archive_get_flipper_app_name(selected->type);
|
||||
if(app_name) {
|
||||
if(selected->is_app) {
|
||||
char* param = strrchr(furi_string_get_cstr(selected->path), '/');
|
||||
if(param != NULL) {
|
||||
param++;
|
||||
}
|
||||
loader_start_with_gui_error(loader, app_name, param);
|
||||
} else {
|
||||
loader_start_with_gui_error(
|
||||
loader, app_name, furi_string_get_cstr(selected->path));
|
||||
}
|
||||
} else {
|
||||
loader_start_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_LOADER);
|
||||
|
||||
@@ -28,6 +28,7 @@ static const Icon* ArchiveItemIcons[] = {
|
||||
[ArchiveFileTypeInfrared] = &I_ir_10px,
|
||||
[ArchiveFileTypeBadUsb] = &I_badusb_10px,
|
||||
[ArchiveFileTypeU2f] = &I_u2f_10px,
|
||||
[ArchiveFileTypeSetting] = &I_settings_10px,
|
||||
[ArchiveFileTypeUpdateManifest] = &I_update_10px,
|
||||
[ArchiveFileTypeFolder] = &I_dir_10px,
|
||||
[ArchiveFileTypeUnknown] = &I_unknown_10px,
|
||||
|
||||
@@ -37,6 +37,31 @@ bool hid_usb_kb_release(void* inst, uint16_t button) {
|
||||
return furi_hal_hid_kb_release(button);
|
||||
}
|
||||
|
||||
bool hid_usb_mouse_press(void* inst, uint8_t button) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_mouse_press(button);
|
||||
}
|
||||
|
||||
bool hid_usb_mouse_release(void* inst, uint8_t button) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_mouse_release(button);
|
||||
}
|
||||
|
||||
bool hid_usb_mouse_scroll(void* inst, int8_t delta) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_mouse_scroll(delta);
|
||||
}
|
||||
|
||||
bool hid_usb_mouse_move(void* inst, int8_t dx, int8_t dy) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_mouse_move(dx, dy);
|
||||
}
|
||||
|
||||
bool hid_usb_mouse_release_all(void* inst) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_mouse_release(0);
|
||||
}
|
||||
|
||||
bool hid_usb_consumer_press(void* inst, uint16_t button) {
|
||||
UNUSED(inst);
|
||||
return furi_hal_hid_consumer_key_press(button);
|
||||
@@ -51,6 +76,7 @@ bool hid_usb_release_all(void* inst) {
|
||||
UNUSED(inst);
|
||||
bool state = furi_hal_hid_kb_release_all();
|
||||
state &= furi_hal_hid_consumer_key_release_all();
|
||||
state &= hid_usb_mouse_release_all(inst);
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -67,6 +93,10 @@ static const BadUsbHidApi hid_api_usb = {
|
||||
|
||||
.kb_press = hid_usb_kb_press,
|
||||
.kb_release = hid_usb_kb_release,
|
||||
.mouse_press = hid_usb_mouse_press,
|
||||
.mouse_release = hid_usb_mouse_release,
|
||||
.mouse_scroll = hid_usb_mouse_scroll,
|
||||
.mouse_move = hid_usb_mouse_move,
|
||||
.consumer_press = hid_usb_consumer_press,
|
||||
.consumer_release = hid_usb_consumer_release,
|
||||
.release_all = hid_usb_release_all,
|
||||
@@ -157,6 +187,27 @@ bool hid_ble_kb_release(void* inst, uint16_t button) {
|
||||
return ble_profile_hid_kb_release(ble_hid->profile, button);
|
||||
}
|
||||
|
||||
bool hid_ble_mouse_press(void* inst, uint8_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_mouse_press(ble_hid->profile, button);
|
||||
}
|
||||
bool hid_ble_mouse_release(void* inst, uint8_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_mouse_release(ble_hid->profile, button);
|
||||
}
|
||||
bool hid_ble_mouse_scroll(void* inst, int8_t delta) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_mouse_scroll(ble_hid->profile, delta);
|
||||
}
|
||||
bool hid_ble_mouse_move(void* inst, int8_t dx, int8_t dy) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
return ble_profile_hid_mouse_move(ble_hid->profile, dx, dy);
|
||||
}
|
||||
|
||||
bool hid_ble_consumer_press(void* inst, uint16_t button) {
|
||||
BleHidInstance* ble_hid = inst;
|
||||
furi_assert(ble_hid);
|
||||
@@ -174,6 +225,7 @@ bool hid_ble_release_all(void* inst) {
|
||||
furi_assert(ble_hid);
|
||||
bool state = ble_profile_hid_kb_release_all(ble_hid->profile);
|
||||
state &= ble_profile_hid_consumer_key_release_all(ble_hid->profile);
|
||||
state &= ble_profile_hid_mouse_release_all(ble_hid->profile);
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -191,6 +243,10 @@ static const BadUsbHidApi hid_api_ble = {
|
||||
|
||||
.kb_press = hid_ble_kb_press,
|
||||
.kb_release = hid_ble_kb_release,
|
||||
.mouse_press = hid_ble_mouse_press,
|
||||
.mouse_release = hid_ble_mouse_release,
|
||||
.mouse_scroll = hid_ble_mouse_scroll,
|
||||
.mouse_move = hid_ble_mouse_move,
|
||||
.consumer_press = hid_ble_consumer_press,
|
||||
.consumer_release = hid_ble_consumer_release,
|
||||
.release_all = hid_ble_release_all,
|
||||
|
||||
@@ -20,6 +20,10 @@ typedef struct {
|
||||
|
||||
bool (*kb_press)(void* inst, uint16_t button);
|
||||
bool (*kb_release)(void* inst, uint16_t button);
|
||||
bool (*mouse_press)(void* inst, uint8_t button);
|
||||
bool (*mouse_release)(void* inst, uint8_t button);
|
||||
bool (*mouse_scroll)(void* inst, int8_t delta);
|
||||
bool (*mouse_move)(void* inst, int8_t dx, int8_t dy);
|
||||
bool (*consumer_press)(void* inst, uint16_t button);
|
||||
bool (*consumer_release)(void* inst, uint16_t button);
|
||||
bool (*release_all)(void* inst);
|
||||
|
||||
@@ -40,11 +40,8 @@ static const uint8_t numpad_keys[10] = {
|
||||
};
|
||||
|
||||
uint32_t ducky_get_command_len(const char* line) {
|
||||
uint32_t len = strlen(line);
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
if(line[i] == ' ') return i;
|
||||
}
|
||||
return 0;
|
||||
char* first_space = strchr(line, ' ');
|
||||
return first_space ? (first_space - line) : 0;
|
||||
}
|
||||
|
||||
bool ducky_is_line_end(const char chr) {
|
||||
@@ -180,29 +177,46 @@ static bool ducky_string_next(BadUsbScript* bad_usb) {
|
||||
|
||||
static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) {
|
||||
uint32_t line_len = furi_string_size(line);
|
||||
const char* line_tmp = furi_string_get_cstr(line);
|
||||
const char* line_cstr = furi_string_get_cstr(line);
|
||||
|
||||
if(line_len == 0) {
|
||||
return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
|
||||
}
|
||||
FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
|
||||
FURI_LOG_D(WORKER_TAG, "line:%s", line_cstr);
|
||||
|
||||
// Ducky Lang Functions
|
||||
int32_t cmd_result = ducky_execute_cmd(bad_usb, line_tmp);
|
||||
int32_t cmd_result = ducky_execute_cmd(bad_usb, line_cstr);
|
||||
if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) {
|
||||
return cmd_result;
|
||||
}
|
||||
|
||||
// Special keys + modifiers
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
|
||||
if(key == HID_KEYBOARD_NONE) {
|
||||
return ducky_error(bad_usb, "No keycode defined for %s", line_tmp);
|
||||
// Mouse Keys
|
||||
uint16_t key = ducky_get_mouse_keycode_by_name(line_cstr);
|
||||
if(key != HID_MOUSE_INVALID) {
|
||||
bad_usb->hid->mouse_press(bad_usb->hid_inst, key);
|
||||
bad_usb->hid->mouse_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
if((key & 0xFF00) != 0) {
|
||||
// It's a modifier key
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
key |= ducky_get_keycode(bad_usb, line_tmp, true);
|
||||
|
||||
// Parse chain of modifiers linked by spaces and hyphens
|
||||
uint16_t modifiers = 0;
|
||||
while(1) {
|
||||
key = ducky_get_next_modifier_keycode_by_name(&line_cstr);
|
||||
if(key == HID_KEYBOARD_NONE) break;
|
||||
|
||||
modifiers |= key;
|
||||
char next_char = *line_cstr;
|
||||
if(next_char == ' ' || next_char == '-') line_cstr++;
|
||||
}
|
||||
|
||||
// Main key
|
||||
char next_char = *line_cstr;
|
||||
uint16_t main_key = ducky_get_keycode_by_name(line_cstr);
|
||||
if(!main_key && next_char) main_key = BADUSB_ASCII_TO_KEY(bad_usb, next_char);
|
||||
key = modifiers | main_key;
|
||||
|
||||
if(key == 0 && next_char) ducky_error(bad_usb, "No keycode defined for %s", line_cstr);
|
||||
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <furi_hal.h>
|
||||
#include <lib/toolbox/strint.h>
|
||||
#include "ducky_script.h"
|
||||
#include "ducky_script_i.h"
|
||||
|
||||
@@ -124,34 +125,58 @@ static int32_t ducky_fnc_altstring(BadUsbScript* bad_usb, const char* line, int3
|
||||
|
||||
static int32_t ducky_fnc_hold(BadUsbScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line, true);
|
||||
if(key == HID_KEYBOARD_NONE) {
|
||||
return ducky_error(bad_usb, "No keycode defined for %s", line);
|
||||
}
|
||||
bad_usb->key_hold_nb++;
|
||||
|
||||
if(bad_usb->key_hold_nb > (HID_KB_MAX_KEYS - 1)) {
|
||||
return ducky_error(bad_usb, "Too many keys are hold");
|
||||
return ducky_error(bad_usb, "Too many keys are held");
|
||||
}
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
|
||||
// Handle Mouse Keys here
|
||||
uint16_t key = ducky_get_mouse_keycode_by_name(line);
|
||||
if(key != HID_MOUSE_NONE) {
|
||||
bad_usb->key_hold_nb++;
|
||||
bad_usb->hid->mouse_press(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle Keyboard keys here
|
||||
key = ducky_get_keycode(bad_usb, line, true);
|
||||
if(key != HID_KEYBOARD_NONE) {
|
||||
bad_usb->key_hold_nb++;
|
||||
bad_usb->hid->kb_press(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// keyboard and mouse were none
|
||||
return ducky_error(bad_usb, "Unknown keycode for %s", line);
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_release(BadUsbScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[ducky_get_command_len(line) + 1];
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line, true);
|
||||
if(key == HID_KEYBOARD_NONE) {
|
||||
return ducky_error(bad_usb, "No keycode defined for %s", line);
|
||||
}
|
||||
|
||||
if(bad_usb->key_hold_nb == 0) {
|
||||
return ducky_error(bad_usb, "No keys are hold");
|
||||
return ducky_error(bad_usb, "No keys are held");
|
||||
}
|
||||
bad_usb->key_hold_nb--;
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
|
||||
// Handle Mouse Keys here
|
||||
uint16_t key = ducky_get_mouse_keycode_by_name(line);
|
||||
if(key != HID_MOUSE_NONE) {
|
||||
bad_usb->key_hold_nb--;
|
||||
bad_usb->hid->mouse_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//Handle Keyboard Keys here
|
||||
key = ducky_get_keycode(bad_usb, line, true);
|
||||
if(key != HID_KEYBOARD_NONE) {
|
||||
bad_usb->key_hold_nb--;
|
||||
bad_usb->hid->kb_release(bad_usb->hid_inst, key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// keyboard and mouse were none
|
||||
return ducky_error(bad_usb, "No keycode defined for %s", line);
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_media(BadUsbScript* bad_usb, const char* line, int32_t param) {
|
||||
@@ -191,6 +216,43 @@ static int32_t ducky_fnc_waitforbutton(BadUsbScript* bad_usb, const char* line,
|
||||
return SCRIPT_STATE_WAIT_FOR_BTN;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_mouse_scroll(BadUsbScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[strcspn(line, " ") + 1];
|
||||
int32_t mouse_scroll_dist = 0;
|
||||
|
||||
if(strint_to_int32(line, NULL, &mouse_scroll_dist, 10) != StrintParseNoError) {
|
||||
return ducky_error(bad_usb, "Invalid Number %s", line);
|
||||
}
|
||||
|
||||
bad_usb->hid->mouse_scroll(bad_usb->hid_inst, mouse_scroll_dist);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_fnc_mouse_move(BadUsbScript* bad_usb, const char* line, int32_t param) {
|
||||
UNUSED(param);
|
||||
|
||||
line = &line[strcspn(line, " ") + 1];
|
||||
int32_t mouse_move_x = 0;
|
||||
int32_t mouse_move_y = 0;
|
||||
|
||||
if(strint_to_int32(line, NULL, &mouse_move_x, 10) != StrintParseNoError) {
|
||||
return ducky_error(bad_usb, "Invalid Number %s", line);
|
||||
}
|
||||
|
||||
line = &line[strcspn(line, " ") + 1];
|
||||
|
||||
if(strint_to_int32(line, NULL, &mouse_move_y, 10) != StrintParseNoError) {
|
||||
return ducky_error(bad_usb, "Invalid Number %s", line);
|
||||
}
|
||||
|
||||
bad_usb->hid->mouse_move(bad_usb->hid_inst, mouse_move_x, mouse_move_y);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const DuckyCmd ducky_commands[] = {
|
||||
{"REM", NULL, -1},
|
||||
{"ID", NULL, -1},
|
||||
@@ -213,6 +275,10 @@ static const DuckyCmd ducky_commands[] = {
|
||||
{"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1},
|
||||
{"MEDIA", ducky_fnc_media, -1},
|
||||
{"GLOBE", ducky_fnc_globe, -1},
|
||||
{"MOUSEMOVE", ducky_fnc_mouse_move, -1},
|
||||
{"MOUSE_MOVE", ducky_fnc_mouse_move, -1},
|
||||
{"MOUSESCROLL", ducky_fnc_mouse_scroll, -1},
|
||||
{"MOUSE_SCROLL", ducky_fnc_mouse_scroll, -1},
|
||||
};
|
||||
|
||||
#define TAG "BadUsb"
|
||||
|
||||
@@ -18,6 +18,9 @@ extern "C" {
|
||||
|
||||
#define FILE_BUFFER_LEN 16
|
||||
|
||||
#define HID_MOUSE_INVALID 0
|
||||
#define HID_MOUSE_NONE 0
|
||||
|
||||
struct BadUsbScript {
|
||||
FuriHalUsbHidConfig hid_cfg;
|
||||
const BadUsbHidApi* hid;
|
||||
@@ -51,10 +54,14 @@ uint32_t ducky_get_command_len(const char* line);
|
||||
|
||||
bool ducky_is_line_end(const char chr);
|
||||
|
||||
uint16_t ducky_get_next_modifier_keycode_by_name(const char** param);
|
||||
|
||||
uint16_t ducky_get_keycode_by_name(const char* param);
|
||||
|
||||
uint16_t ducky_get_media_keycode_by_name(const char* param);
|
||||
|
||||
uint8_t ducky_get_mouse_keycode_by_name(const char* param);
|
||||
|
||||
bool ducky_get_number(const char* param, uint32_t* val);
|
||||
|
||||
void ducky_numlock_on(BadUsbScript* bad_usb);
|
||||
|
||||
@@ -6,21 +6,16 @@ typedef struct {
|
||||
uint16_t keycode;
|
||||
} DuckyKey;
|
||||
|
||||
static const DuckyKey ducky_keys[] = {
|
||||
{"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT},
|
||||
{"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
|
||||
{"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
|
||||
{"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL},
|
||||
|
||||
static const DuckyKey ducky_modifier_keys[] = {
|
||||
{"CTRL", KEY_MOD_LEFT_CTRL},
|
||||
{"CONTROL", KEY_MOD_LEFT_CTRL},
|
||||
{"SHIFT", KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT", KEY_MOD_LEFT_ALT},
|
||||
{"GUI", KEY_MOD_LEFT_GUI},
|
||||
{"WINDOWS", KEY_MOD_LEFT_GUI},
|
||||
};
|
||||
|
||||
static const DuckyKey ducky_keys[] = {
|
||||
{"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
|
||||
{"DOWN", HID_KEYBOARD_DOWN_ARROW},
|
||||
{"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
|
||||
@@ -108,6 +103,34 @@ static const DuckyKey ducky_media_keys[] = {
|
||||
{"BRIGHT_DOWN", HID_CONSUMER_BRIGHTNESS_DECREMENT},
|
||||
};
|
||||
|
||||
static const DuckyKey ducky_mouse_keys[] = {
|
||||
{"LEFTCLICK", HID_MOUSE_BTN_LEFT},
|
||||
{"LEFT_CLICK", HID_MOUSE_BTN_LEFT},
|
||||
{"RIGHTCLICK", HID_MOUSE_BTN_RIGHT},
|
||||
{"RIGHT_CLICK", HID_MOUSE_BTN_RIGHT},
|
||||
{"MIDDLECLICK", HID_MOUSE_BTN_WHEEL},
|
||||
{"MIDDLE_CLICK", HID_MOUSE_BTN_WHEEL},
|
||||
{"WHEELCLICK", HID_MOUSE_BTN_WHEEL},
|
||||
{"WHEEL_CLICK", HID_MOUSE_BTN_WHEEL},
|
||||
};
|
||||
|
||||
uint16_t ducky_get_next_modifier_keycode_by_name(const char** param) {
|
||||
const char* input_str = *param;
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(ducky_modifier_keys); i++) {
|
||||
size_t key_cmd_len = strlen(ducky_modifier_keys[i].name);
|
||||
if((strncmp(input_str, ducky_modifier_keys[i].name, key_cmd_len) == 0)) {
|
||||
char next_char_after_key = input_str[key_cmd_len];
|
||||
if(ducky_is_line_end(next_char_after_key) || (next_char_after_key == '-')) {
|
||||
*param = &input_str[key_cmd_len];
|
||||
return ducky_modifier_keys[i].keycode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HID_KEYBOARD_NONE;
|
||||
}
|
||||
|
||||
uint16_t ducky_get_keycode_by_name(const char* param) {
|
||||
for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) {
|
||||
size_t key_cmd_len = strlen(ducky_keys[i].name);
|
||||
@@ -131,3 +154,15 @@ uint16_t ducky_get_media_keycode_by_name(const char* param) {
|
||||
|
||||
return HID_CONSUMER_UNASSIGNED;
|
||||
}
|
||||
|
||||
uint8_t ducky_get_mouse_keycode_by_name(const char* param) {
|
||||
for(size_t i = 0; i < COUNT_OF(ducky_mouse_keys); i++) {
|
||||
size_t key_cmd_len = strlen(ducky_mouse_keys[i].name);
|
||||
if((strncmp(param, ducky_mouse_keys[i].name, key_cmd_len) == 0) &&
|
||||
(ducky_is_line_end(param[key_cmd_len]))) {
|
||||
return ducky_mouse_keys[i].keycode;
|
||||
}
|
||||
}
|
||||
|
||||
return HID_MOUSE_INVALID;
|
||||
}
|
||||
|
||||
46
applications/main/bad_usb/resources/badusb/test_mouse.txt
Normal file
46
applications/main/bad_usb/resources/badusb/test_mouse.txt
Normal file
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "scenes/gpio_scene.h"
|
||||
#include "gpio_custom_event.h"
|
||||
#include "usb_uart_bridge.h"
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
@@ -27,6 +28,7 @@ struct GpioApp {
|
||||
SceneManager* scene_manager;
|
||||
Widget* widget;
|
||||
DialogEx* dialog;
|
||||
Power* power;
|
||||
|
||||
VariableItemList* var_item_list;
|
||||
VariableItem* var_item_flow;
|
||||
|
||||
@@ -60,7 +60,7 @@ void gpio_scene_start_on_enter(void* context) {
|
||||
GpioOtgSettingsNum,
|
||||
gpio_scene_start_var_list_change_callback,
|
||||
app);
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
if(power_is_otg_enabled(app->power)) {
|
||||
variable_item_set_current_value_index(item, GpioOtgOn);
|
||||
variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOn]);
|
||||
} else {
|
||||
@@ -80,9 +80,9 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GpioStartEventOtgOn) {
|
||||
furi_hal_power_enable_otg();
|
||||
power_enable_otg(app->power, true);
|
||||
} else if(event.event == GpioStartEventOtgOff) {
|
||||
furi_hal_power_disable_otg();
|
||||
power_enable_otg(app->power, false);
|
||||
} else if(event.event == GpioStartEventManualControl) {
|
||||
scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemTest);
|
||||
scene_manager_next_scene(app->scene_manager, GpioSceneTest);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "usb_uart_bridge.h"
|
||||
#include "usb_cdc.h"
|
||||
#include <cli/cli_vcp.h>
|
||||
#include <cli/cli.h>
|
||||
#include <toolbox/api_lock.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_usb_cdc.h>
|
||||
@@ -61,6 +60,8 @@ struct UsbUartBridge {
|
||||
|
||||
FuriApiLock cfg_lock;
|
||||
|
||||
CliVcp* cli_vcp;
|
||||
|
||||
uint8_t rx_buf[USB_CDC_PKT_LEN];
|
||||
};
|
||||
|
||||
@@ -106,15 +107,11 @@ static void usb_uart_on_irq_rx_dma_cb(
|
||||
static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
|
||||
furi_hal_usb_unlock();
|
||||
if(vcp_ch == 0) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_close(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
cli_vcp_disable(usb_uart->cli_vcp);
|
||||
furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
|
||||
} else {
|
||||
furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true);
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
furi_record_close(RECORD_CLI);
|
||||
cli_vcp_enable(usb_uart->cli_vcp);
|
||||
}
|
||||
furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart);
|
||||
}
|
||||
@@ -123,9 +120,7 @@ static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) {
|
||||
UNUSED(usb_uart);
|
||||
furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL);
|
||||
if(vcp_ch != 0) {
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_close(cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
cli_vcp_disable(usb_uart->cli_vcp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,13 +172,15 @@ static int32_t usb_uart_worker(void* context) {
|
||||
|
||||
memcpy(&usb_uart->cfg, &usb_uart->cfg_new, sizeof(UsbUartConfig));
|
||||
|
||||
usb_uart->cli_vcp = furi_record_open(RECORD_CLI_VCP);
|
||||
|
||||
usb_uart->rx_stream = furi_stream_buffer_alloc(USB_UART_RX_BUF_SIZE, 1);
|
||||
|
||||
usb_uart->tx_sem = furi_semaphore_alloc(1, 1);
|
||||
usb_uart->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
usb_uart->tx_thread =
|
||||
furi_thread_alloc_ex("UsbUartTxWorker", 512, usb_uart_tx_thread, usb_uart);
|
||||
furi_thread_alloc_ex("UsbUartTxWorker", 768, usb_uart_tx_thread, usb_uart);
|
||||
|
||||
usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch);
|
||||
usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch);
|
||||
@@ -288,8 +285,6 @@ static int32_t usb_uart_worker(void* context) {
|
||||
usb_uart_update_ctrl_lines(usb_uart);
|
||||
}
|
||||
}
|
||||
usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch);
|
||||
usb_uart_serial_deinit(usb_uart);
|
||||
|
||||
furi_hal_gpio_init(USB_USART_DE_RE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
@@ -302,15 +297,18 @@ static int32_t usb_uart_worker(void* context) {
|
||||
furi_thread_join(usb_uart->tx_thread);
|
||||
furi_thread_free(usb_uart->tx_thread);
|
||||
|
||||
usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch);
|
||||
usb_uart_serial_deinit(usb_uart);
|
||||
|
||||
furi_stream_buffer_free(usb_uart->rx_stream);
|
||||
furi_mutex_free(usb_uart->usb_mutex);
|
||||
furi_semaphore_free(usb_uart->tx_sem);
|
||||
|
||||
furi_hal_usb_unlock();
|
||||
furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_session_open(cli, &cli_vcp);
|
||||
furi_record_close(RECORD_CLI);
|
||||
cli_vcp_enable(usb_uart->cli_vcp);
|
||||
|
||||
furi_record_close(RECORD_CLI_VCP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/pipe.h>
|
||||
|
||||
#include <ibutton/ibutton_key.h>
|
||||
#include <ibutton/ibutton_worker.h>
|
||||
#include <ibutton/ibutton_protocols.h>
|
||||
|
||||
static void ibutton_cli(Cli* cli, FuriString* args, void* context);
|
||||
|
||||
// app cli function
|
||||
void ibutton_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "ikey", CliCommandFlagDefault, ibutton_cli, cli);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(ibutton_cli);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ibutton_cli_print_usage(void) {
|
||||
printf("Usage:\r\n");
|
||||
printf("ikey read\r\n");
|
||||
@@ -92,7 +80,7 @@ static void ibutton_cli_worker_read_cb(void* context) {
|
||||
furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE);
|
||||
}
|
||||
|
||||
static void ibutton_cli_read(Cli* cli) {
|
||||
static void ibutton_cli_read(PipeSide* pipe) {
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
@@ -113,7 +101,7 @@ static void ibutton_cli_read(Cli* cli) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
|
||||
ibutton_worker_stop(worker);
|
||||
@@ -138,7 +126,7 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult
|
||||
furi_event_flag_set(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE);
|
||||
}
|
||||
|
||||
void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
void ibutton_cli_write(PipeSide* pipe, FuriString* args) {
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
@@ -181,7 +169,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
}
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
@@ -195,7 +183,7 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
furi_event_flag_free(write_context.event);
|
||||
}
|
||||
|
||||
void ibutton_cli_emulate(Cli* cli, FuriString* args) {
|
||||
void ibutton_cli_emulate(PipeSide* pipe, FuriString* args) {
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
@@ -214,7 +202,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) {
|
||||
|
||||
ibutton_worker_emulate_start(worker, key);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(100);
|
||||
};
|
||||
|
||||
@@ -228,8 +216,7 @@ void ibutton_cli_emulate(Cli* cli, FuriString* args) {
|
||||
ibutton_protocols_free(protocols);
|
||||
}
|
||||
|
||||
void ibutton_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -241,14 +228,16 @@ void ibutton_cli(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(cmd, "read") == 0) {
|
||||
ibutton_cli_read(cli);
|
||||
ibutton_cli_read(pipe);
|
||||
} else if(furi_string_cmp_str(cmd, "write") == 0) {
|
||||
ibutton_cli_write(cli, args);
|
||||
ibutton_cli_write(pipe, args);
|
||||
} else if(furi_string_cmp_str(cmd, "emulate") == 0) {
|
||||
ibutton_cli_emulate(cli, args);
|
||||
ibutton_cli_emulate(pipe, args);
|
||||
} else {
|
||||
ibutton_cli_print_usage();
|
||||
}
|
||||
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(ikey, execute, CliCommandFlagDefault, 1024, CLI_APPID);
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "infrared_app_i.h"
|
||||
|
||||
#include <furi_hal_power.h>
|
||||
#include <power/power_service/power.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <toolbox/path.h>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -2,26 +2,61 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <m-dict.h>
|
||||
#include <m-array.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "infrared_signal.h"
|
||||
|
||||
ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST);
|
||||
|
||||
typedef struct {
|
||||
uint32_t index;
|
||||
uint32_t count;
|
||||
size_t index;
|
||||
SignalPositionArray_t signals;
|
||||
} InfraredBruteForceRecord;
|
||||
|
||||
static inline void ir_bf_record_init(InfraredBruteForceRecord* record) {
|
||||
record->index = 0;
|
||||
SignalPositionArray_init(record->signals);
|
||||
}
|
||||
#define IR_BF_RECORD_INIT(r) (ir_bf_record_init(&(r)))
|
||||
|
||||
static inline void
|
||||
ir_bf_record_init_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) {
|
||||
dest->index = src->index;
|
||||
SignalPositionArray_init_set(dest->signals, src->signals);
|
||||
}
|
||||
#define IR_BF_RECORD_INIT_SET(d, s) (ir_bf_record_init_set(&(d), &(s)))
|
||||
|
||||
static inline void
|
||||
ir_bf_record_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) {
|
||||
dest->index = src->index;
|
||||
SignalPositionArray_set(dest->signals, src->signals);
|
||||
}
|
||||
#define IR_BF_RECORD_SET(d, s) (ir_bf_record_set(&(d), &(s)))
|
||||
|
||||
static inline void ir_bf_record_clear(InfraredBruteForceRecord* record) {
|
||||
SignalPositionArray_clear(record->signals);
|
||||
}
|
||||
#define IR_BF_RECORD_CLEAR(r) (ir_bf_record_clear(&(r)))
|
||||
|
||||
#define IR_BF_RECORD_OPLIST \
|
||||
(INIT(IR_BF_RECORD_INIT), \
|
||||
INIT_SET(IR_BF_RECORD_INIT_SET), \
|
||||
SET(IR_BF_RECORD_SET), \
|
||||
CLEAR(IR_BF_RECORD_CLEAR))
|
||||
|
||||
DICT_DEF2(
|
||||
InfraredBruteForceRecordDict,
|
||||
FuriString*,
|
||||
FURI_STRING_OPLIST,
|
||||
InfraredBruteForceRecord,
|
||||
M_POD_OPLIST);
|
||||
IR_BF_RECORD_OPLIST);
|
||||
|
||||
struct InfraredBruteForce {
|
||||
FlipperFormat* ff;
|
||||
const char* db_filename;
|
||||
FuriString* current_record_name;
|
||||
InfraredBruteForceRecord current_record;
|
||||
InfraredSignal* current_signal;
|
||||
InfraredBruteForceRecordDict_t records;
|
||||
bool is_started;
|
||||
@@ -39,6 +74,7 @@ InfraredBruteForce* infrared_brute_force_alloc(void) {
|
||||
}
|
||||
|
||||
void infrared_brute_force_free(InfraredBruteForce* brute_force) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(!brute_force->is_started);
|
||||
InfraredBruteForceRecordDict_clear(brute_force->records);
|
||||
furi_string_free(brute_force->current_record_name);
|
||||
@@ -46,11 +82,13 @@ void infrared_brute_force_free(InfraredBruteForce* brute_force) {
|
||||
}
|
||||
|
||||
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(!brute_force->is_started);
|
||||
brute_force->db_filename = db_filename;
|
||||
}
|
||||
|
||||
InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(!brute_force->is_started);
|
||||
furi_assert(brute_force->db_filename);
|
||||
InfraredErrorCode error = InfraredErrorCodeNone;
|
||||
@@ -66,19 +104,19 @@ InfraredErrorCode infrared_brute_force_calculate_messages(InfraredBruteForce* br
|
||||
break;
|
||||
}
|
||||
|
||||
bool signals_valid = false;
|
||||
bool signal_valid = false;
|
||||
while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) {
|
||||
size_t signal_start = flipper_format_tell(ff);
|
||||
error = infrared_signal_read_body(signal, ff);
|
||||
signals_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
|
||||
if(!signals_valid) break;
|
||||
signal_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
|
||||
if(!signal_valid) break;
|
||||
|
||||
InfraredBruteForceRecord* record =
|
||||
InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
|
||||
if(record) { //-V547
|
||||
++(record->count);
|
||||
}
|
||||
furi_assert(record);
|
||||
SignalPositionArray_push_back(record->signals, signal_start);
|
||||
}
|
||||
if(!signals_valid) break;
|
||||
if(!signal_valid) break;
|
||||
} while(false);
|
||||
|
||||
infrared_signal_free(signal);
|
||||
@@ -93,6 +131,7 @@ bool infrared_brute_force_start(
|
||||
InfraredBruteForce* brute_force,
|
||||
uint32_t index,
|
||||
uint32_t* record_count) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(!brute_force->is_started);
|
||||
bool success = false;
|
||||
*record_count = 0;
|
||||
@@ -103,9 +142,10 @@ bool infrared_brute_force_start(
|
||||
InfraredBruteForceRecordDict_next(it)) {
|
||||
const InfraredBruteForceRecordDict_itref_t* record = InfraredBruteForceRecordDict_cref(it);
|
||||
if(record->value.index == index) {
|
||||
*record_count = record->value.count;
|
||||
*record_count = SignalPositionArray_size(record->value.signals);
|
||||
if(*record_count) {
|
||||
furi_string_set(brute_force->current_record_name, record->key);
|
||||
brute_force->current_record = record->value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -124,10 +164,12 @@ bool infrared_brute_force_start(
|
||||
}
|
||||
|
||||
bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) {
|
||||
furi_check(brute_force);
|
||||
return brute_force->is_started;
|
||||
}
|
||||
|
||||
void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(brute_force->is_started);
|
||||
furi_string_reset(brute_force->current_record_name);
|
||||
infrared_signal_free(brute_force->current_signal);
|
||||
@@ -138,25 +180,32 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
|
||||
bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(brute_force->is_started);
|
||||
|
||||
const bool success = infrared_signal_search_by_name_and_read(
|
||||
brute_force->current_signal,
|
||||
brute_force->ff,
|
||||
furi_string_get_cstr(brute_force->current_record_name)) ==
|
||||
InfraredErrorCodeNone;
|
||||
if(success) {
|
||||
infrared_signal_transmit(brute_force->current_signal);
|
||||
}
|
||||
return success;
|
||||
if(signal_index >= SignalPositionArray_size(brute_force->current_record.signals)) return false;
|
||||
|
||||
size_t signal_start =
|
||||
*SignalPositionArray_cget(brute_force->current_record.signals, signal_index);
|
||||
if(!flipper_format_seek(brute_force->ff, signal_start, FlipperFormatOffsetFromStart))
|
||||
return false;
|
||||
|
||||
if(INFRARED_ERROR_PRESENT(
|
||||
infrared_signal_read_body(brute_force->current_signal, brute_force->ff)))
|
||||
return false;
|
||||
|
||||
infrared_signal_transmit(brute_force->current_signal);
|
||||
return true;
|
||||
}
|
||||
|
||||
void infrared_brute_force_add_record(
|
||||
InfraredBruteForce* brute_force,
|
||||
uint32_t index,
|
||||
const char* name) {
|
||||
InfraredBruteForceRecord value = {.index = index, .count = 0};
|
||||
InfraredBruteForceRecord value;
|
||||
ir_bf_record_init(&value);
|
||||
value.index = index;
|
||||
FuriString* key;
|
||||
key = furi_string_alloc_set(name);
|
||||
InfraredBruteForceRecordDict_set_at(brute_force->records, key, value);
|
||||
|
||||
@@ -78,18 +78,16 @@ bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force);
|
||||
void infrared_brute_force_stop(InfraredBruteForce* brute_force);
|
||||
|
||||
/**
|
||||
* @brief Send the next signal from the chosen category.
|
||||
*
|
||||
* This function is called repeatedly until no more signals are left
|
||||
* in the chosen signal category.
|
||||
*
|
||||
* @warning Transmission must be started first by calling infrared_brute_force_start()
|
||||
* before calling this function.
|
||||
*
|
||||
* @param[in,out] brute_force pointer to the instance to be used.
|
||||
* @returns true if the next signal existed and could be transmitted, false otherwise.
|
||||
* @brief Send an arbitrary signal from the chosen category.
|
||||
*
|
||||
* @param[in] brute_force pointer to the instance
|
||||
* @param signal_index the index of the signal within the category, must be
|
||||
* between 0 and `record_count` as told by
|
||||
* `infrared_brute_force_start`
|
||||
*
|
||||
* @returns true on success, false otherwise
|
||||
*/
|
||||
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force);
|
||||
bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index);
|
||||
|
||||
/**
|
||||
* @brief Add a signal category to an InfraredBruteForce instance's dictionary.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include <cli/cli.h>
|
||||
#include <cli/cli_i.h>
|
||||
#include <cli/cli_main_commands.h>
|
||||
#include <infrared.h>
|
||||
#include <infrared_worker.h>
|
||||
#include <furi_hal_infrared.h>
|
||||
#include <flipper_format.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/strint.h>
|
||||
#include <toolbox/pipe.h>
|
||||
#include <m-dict.h>
|
||||
|
||||
#include "infrared_signal.h"
|
||||
@@ -19,14 +19,14 @@
|
||||
|
||||
DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST)
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_process_decode(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args);
|
||||
static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args);
|
||||
static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args);
|
||||
static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args);
|
||||
static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args);
|
||||
|
||||
static const struct {
|
||||
const char* cmd;
|
||||
void (*process_function)(Cli* cli, FuriString* args);
|
||||
void (*process_function)(PipeSide* pipe, FuriString* args);
|
||||
} infrared_cli_commands[] = {
|
||||
{.cmd = "rx", .process_function = infrared_cli_start_ir_rx},
|
||||
{.cmd = "tx", .process_function = infrared_cli_start_ir_tx},
|
||||
@@ -38,7 +38,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
|
||||
furi_assert(received_signal);
|
||||
char buf[100];
|
||||
size_t buf_cnt;
|
||||
Cli* cli = (Cli*)context;
|
||||
PipeSide* pipe = (PipeSide*)context;
|
||||
|
||||
if(infrared_worker_signal_is_decoded(received_signal)) {
|
||||
const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal);
|
||||
@@ -52,20 +52,20 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
|
||||
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
|
||||
message->command,
|
||||
message->repeat ? " R" : "");
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
} else {
|
||||
const uint32_t* timings;
|
||||
size_t timings_cnt;
|
||||
infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
|
||||
|
||||
buf_cnt = snprintf(buf, sizeof(buf), "RAW, %zu samples:\r\n", timings_cnt);
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
for(size_t i = 0; i < timings_cnt; ++i) {
|
||||
buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]);
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
}
|
||||
buf_cnt = snprintf(buf, sizeof(buf), "\r\n");
|
||||
cli_write(cli, (uint8_t*)buf, buf_cnt);
|
||||
pipe_send(pipe, buf, buf_cnt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,9 +124,7 @@ static void infrared_cli_print_usage(void) {
|
||||
infrared_cli_print_universal_remotes();
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
|
||||
static void infrared_cli_start_ir_rx(PipeSide* pipe, FuriString* args) {
|
||||
bool enable_decoding = true;
|
||||
|
||||
if(!furi_string_empty(args)) {
|
||||
@@ -142,10 +140,10 @@ static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
InfraredWorker* worker = infrared_worker_alloc();
|
||||
infrared_worker_rx_enable_signal_decoding(worker, enable_decoding);
|
||||
infrared_worker_rx_start(worker);
|
||||
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
|
||||
infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, pipe);
|
||||
|
||||
printf("Receiving %s INFRARED...\r\nPress Ctrl+C to abort\r\n", enable_decoding ? "" : "RAW");
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
furi_delay_ms(50);
|
||||
}
|
||||
|
||||
@@ -214,8 +212,8 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) {
|
||||
return infrared_signal_is_valid(signal);
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void infrared_cli_start_ir_tx(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
const char* str = furi_string_get_cstr(args);
|
||||
InfraredSignal* signal = infrared_signal_alloc();
|
||||
|
||||
@@ -335,8 +333,8 @@ static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* o
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void infrared_cli_process_decode(Cli* cli, FuriString* args) {
|
||||
UNUSED(cli);
|
||||
static void infrared_cli_process_decode(PipeSide* pipe, FuriString* args) {
|
||||
UNUSED(pipe);
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage);
|
||||
FlipperFormat* output_file = NULL;
|
||||
@@ -455,8 +453,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void
|
||||
infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) {
|
||||
static void infrared_cli_brute_force_signals(
|
||||
PipeSide* pipe,
|
||||
FuriString* remote_name,
|
||||
FuriString* signal_name) {
|
||||
InfraredBruteForce* brute_force = infrared_brute_force_alloc();
|
||||
FuriString* remote_path = furi_string_alloc_printf(
|
||||
"%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name));
|
||||
@@ -475,25 +475,24 @@ static void
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t record_count;
|
||||
uint32_t signal_count, current_signal = 0;
|
||||
bool running = infrared_brute_force_start(
|
||||
brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &record_count);
|
||||
brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &signal_count);
|
||||
|
||||
if(record_count <= 0) {
|
||||
if(signal_count <= 0) {
|
||||
printf("Invalid signal name.\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Sending %lu signal(s)...\r\n", record_count);
|
||||
printf("Sending %lu signal(s)...\r\n", signal_count);
|
||||
printf("Press Ctrl-C to stop.\r\n");
|
||||
|
||||
int records_sent = 0;
|
||||
while(running) {
|
||||
running = infrared_brute_force_send_next(brute_force);
|
||||
running = infrared_brute_force_send(brute_force, current_signal);
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
if(cli_is_pipe_broken_or_is_etx_next_char(pipe)) break;
|
||||
|
||||
printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100));
|
||||
printf("\r%d%% complete.", (int)((float)current_signal++ / (float)signal_count * 100));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -505,7 +504,7 @@ static void
|
||||
infrared_brute_force_free(brute_force);
|
||||
}
|
||||
|
||||
static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
|
||||
static void infrared_cli_process_universal(PipeSide* pipe, FuriString* args) {
|
||||
FuriString* arg1 = furi_string_alloc();
|
||||
FuriString* arg2 = furi_string_alloc();
|
||||
|
||||
@@ -520,14 +519,14 @@ static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
|
||||
} else if(furi_string_equal_str(arg1, "list")) {
|
||||
infrared_cli_list_remote_signals(arg2);
|
||||
} else {
|
||||
infrared_cli_brute_force_signals(cli, arg1, arg2);
|
||||
infrared_cli_brute_force_signals(pipe, arg1, arg2);
|
||||
}
|
||||
|
||||
furi_string_free(arg1);
|
||||
furi_string_free(arg2);
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
|
||||
static void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
if(furi_hal_infrared_is_busy()) {
|
||||
printf("INFRARED is busy. Exiting.");
|
||||
@@ -547,19 +546,12 @@ static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
|
||||
}
|
||||
|
||||
if(i < COUNT_OF(infrared_cli_commands)) {
|
||||
infrared_cli_commands[i].process_function(cli, args);
|
||||
infrared_cli_commands[i].process_function(pipe, args);
|
||||
} else {
|
||||
infrared_cli_print_usage();
|
||||
}
|
||||
|
||||
furi_string_free(command);
|
||||
}
|
||||
void infrared_on_system_start(void) {
|
||||
#ifdef SRV_CLI
|
||||
Cli* cli = (Cli*)furi_record_open(RECORD_CLI);
|
||||
cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL);
|
||||
furi_record_close(RECORD_CLI);
|
||||
#else
|
||||
UNUSED(infrared_cli_start_ir);
|
||||
#endif
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(ir, execute, CliCommandFlagDefault, 2048, CLI_APPID);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
enum InfraredCustomEventType {
|
||||
typedef enum {
|
||||
// Reserve first 100 events for button types and indexes, starting from 0
|
||||
InfraredCustomEventTypeReserved = 100,
|
||||
InfraredCustomEventTypeMenuSelected,
|
||||
@@ -13,7 +13,7 @@ enum InfraredCustomEventType {
|
||||
InfraredCustomEventTypeTextEditDone,
|
||||
InfraredCustomEventTypePopupClosed,
|
||||
InfraredCustomEventTypeButtonSelected,
|
||||
InfraredCustomEventTypeBackPressed,
|
||||
InfraredCustomEventTypePopupInput,
|
||||
InfraredCustomEventTypeTaskFinished,
|
||||
|
||||
InfraredCustomEventTypeRpcLoadFile,
|
||||
@@ -21,11 +21,13 @@ enum InfraredCustomEventType {
|
||||
InfraredCustomEventTypeRpcButtonPressName,
|
||||
InfraredCustomEventTypeRpcButtonPressIndex,
|
||||
InfraredCustomEventTypeRpcButtonRelease,
|
||||
InfraredCustomEventTypeRpcButtonPressReleaseName,
|
||||
InfraredCustomEventTypeRpcButtonPressReleaseIndex,
|
||||
InfraredCustomEventTypeRpcSessionClose,
|
||||
|
||||
InfraredCustomEventTypeGpioTxPinChanged,
|
||||
InfraredCustomEventTypeGpioOtgChanged,
|
||||
};
|
||||
} InfraredCustomEventType;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef union {
|
||||
|
||||
@@ -1896,3 +1896,42 @@ type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 9024 4481 655 551 655 1653 654 550 656 1652 655 1652 656 550 656 550 656 550 656 550 656 551 655 1652 655 551 655 1652 655 550 656 551 655 1654 654 550 656 550 656 551 655 1652 655 550 656 551 654 550 656 1652 655 1651 657 550 655 551 655 550 656 1654 654 550 656 1653 654 551 654 551 655 1651 656 551 655 19984 655 550 656 551 655 550 656 551 655 550 655 551 655 551 655 550 656 1654 653 1653 655 1653 655 550 655 551 655 550 656 551 655 551 655 550 656 551 655 551 655 551 655 551 655 551 655 550 656 550 656 550 656 551 655 551 655 551 655 1652 656 550 656 551 655 550 656 39996 8999 4479 656 551 655 1652 656 550 656 1653 655 1653 655 550 656 551 655 550 656 551 655 551 655 1652 655 551 655 1652 655 550 656 551 655 1653 655 551 655 550 656 550 656 1652 655 551 654 551 655 551 655 1652 655 1652 656 551 655 551 655 552 654 551 655 1653 655 1653 655 551 655 549 656 1653 655 552 654 19984 655 1652 655 551 655 550 656 1652 656 551 655 551 655 551 655 1652 655 1652 655 551 656 1652 656 1653 655 1653 655 551 655 1652 655 551 655 551 655 551 654 551 654 551 655 551 655 1653 655 550 656 551 655 1652 656 1653 654 551 655 551 655 551 655 550 655 550 656 551 655
|
||||
#
|
||||
# Model: Fujitsu ASTG12LVCC
|
||||
#
|
||||
name: Off
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 3258 1573 427 404 426 404 425 1180 428 403 427 1183 425 402 427 402 428 402 427 1180 428 1181 427 404 426 403 427 402 428 1181 427 1181 427 402 427 405 425 402 427 402 427 403 427 402 428 402 428 403 426 401 429 403 427 402 428 403 427 403 427 1180 428 401 428 404 425 401 428 402 427 402 427 402 427 402 428 1180 427 401 428 403 427 402 427 401 428 1180 427 401 428 402 427 402 428 402 427 401 428 403 427 1180 427 402 427 1180 427 1180 427 1177 429 1179 427 1179 427 1178 428
|
||||
#
|
||||
name: Dh
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 39677 99167 3233 1570 425 405 425 404 425 1184 424 405 425 1182 426 405 424 404 426 404 425 1181 427 1182 426 403 427 403 426 404 426 1183 425 1183 425 403 426 404 426 406 424 404 425 405 425 402 427 405 425 404 425 403 426 404 426 404 425 402 427 405 424 1182 425 402 427 404 426 403 426 404 425 404 426 404 425 404 425 1183 424 406 423 404 426 403 426 404 425 1181 427 1182 426 1181 426 1181 426 1181 425 1182 425 1181 426 1182 426 403 426 404 425 1182 426 404 425 404 425 405 425 404 426 403 426 403 427 404 426 404 426 1182 426 1182 426 403 426 404 426 1182 426 405 424 404 426 403 426 1182 426 405 425 403 426 1182 426 404 426 1183 425 403 426 403 426 404 425 403 426 405 425 403 426 1182 425 1182 425 403 427 404 425 1181 426 403 427 403 426 404 425 406 424 404 426 404 425 404 426 404 425 404 426 404 426 404 426 404 426 404 425 404 426 404 426 403 426 403 427 404 425 402 427 405 425 403 426 404 425 404 425 404 425 405 425 404 425 404 425 404 426 403 426 402 427 403 427 403 426 1182 425 404 426 404 425 403 426 1182 425 403 426 1181 426 403 427 403 426 404 425 405 425
|
||||
#
|
||||
name: Cool_hi
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 39674 99137 3228 1573 422 407 421 408 422 1185 423 408 422 1187 421 410 419 409 421 408 421 1186 422 1187 421 408 421 408 422 409 421 1187 420 1186 422 408 421 410 419 407 424 408 421 407 421 410 420 409 421 408 422 408 421 408 422 407 422 410 419 408 422 1187 420 408 421 408 422 408 421 408 421 408 421 408 420 409 421 1187 444 382 422 408 422 408 421 408 445 1162 419 1187 420 1184 423 1185 421 1184 423 1186 421 1186 421 1187 422 409 419 409 420 1186 422 407 420 409 422 409 420 407 422 407 422 411 419 406 421 409 422 1185 446 1162 420 409 421 409 421 1189 418 407 421 408 422 407 422 409 420 409 421 408 420 412 417 1187 421 407 422 408 420 410 421 408 421 409 421 409 445 384 420 410 421 407 421 407 422 409 421 1187 420 409 419 409 421 408 422 408 421 410 419 409 420 410 419 410 420 407 422 409 420 408 421 407 422 408 421 408 421 410 419 409 420 407 423 407 422 409 421 410 419 411 418 408 421 408 422 410 420 407 421 409 419 409 421 409 419 408 422 407 422 407 422 409 420 1188 419 409 421 409 420 409 419 1189 419 1186 421 1188 419 1187 420 408 421 407 422 1188 419
|
||||
#
|
||||
name: Cool_lo
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 39689 99188 3229 1576 421 407 422 409 420 1188 419 409 421 1187 422 408 422 409 419 410 419 1187 422 1186 423 410 419 409 421 409 420 1187 420 1188 420 410 447 382 420 409 422 410 446 383 422 409 420 407 422 410 395 435 420 408 422 407 422 410 420 409 445 1162 420 410 420 409 420 410 420 409 421 410 419 409 421 409 419 1189 420 407 422 409 395 437 419 410 418 1186 422 1186 423 1187 420 1185 422 1188 420 1184 421 1188 419 1188 419 408 420 410 419 1186 421 408 420 410 419 410 419 409 419 411 418 409 421 410 419 409 420 1187 445 1163 419 412 417 409 420 1188 419 409 419 410 420 409 444 1164 418 1187 419 1189 419 409 419 1187 420 408 422 409 419 410 420 409 419 410 419 434 393 411 420 409 421 408 421 409 419 409 421 1188 418 410 419 410 420 410 418 412 417 409 445 385 419 409 420 410 420 408 419 409 421 410 419 411 419 408 446 382 421 409 420 409 420 410 418 409 420 409 419 410 419 412 442 384 419 411 416 412 419 409 420 410 419 410 419 410 418 410 420 409 420 409 420 434 394 1187 419 412 417 410 418 410 420 1188 419 1187 420 1188 419 410 419 1188 419 411 418 434 396
|
||||
#
|
||||
name: Heat_hi
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 39692 99118 3226 1571 423 406 423 405 423 1185 421 405 424 1183 423 406 422 408 422 405 423 1184 446 1160 422 409 420 406 424 405 424 1185 422 1183 424 404 425 406 422 408 421 406 423 407 422 406 423 407 421 406 424 407 422 407 422 404 425 407 421 409 420 1185 422 406 423 408 421 404 424 405 424 407 447 383 421 406 424 1184 447 380 424 406 422 409 421 407 423 1183 447 1159 424 1185 422 1185 421 1184 422 1185 422 1185 421 1185 423 407 423 406 423 1185 423 406 424 406 423 408 446 381 424 406 423 408 421 406 424 406 423 1185 423 1184 422 407 423 407 422 1187 421 408 421 407 423 407 423 405 424 1185 422 1186 421 1184 423 407 422 407 422 1186 422 407 422 406 423 408 422 405 423 408 447 383 420 409 421 406 423 407 423 1184 423 407 423 407 422 408 421 408 423 406 424 406 422 409 422 406 423 408 421 408 421 406 422 406 424 407 422 406 423 409 421 407 422 408 423 406 423 406 423 409 446 382 447 384 420 407 423 405 424 406 423 406 423 407 423 407 422 406 423 405 422 407 424 406 422 1185 422 406 423 407 422 1183 423 1184 422 407 422 1185 423 1186 421 1184 424 407 422 1185 422
|
||||
#
|
||||
name: Heat_lo
|
||||
type: raw
|
||||
frequency: 38000
|
||||
duty_cycle: 0.330000
|
||||
data: 39670 99106 3227 1570 424 406 422 407 422 1183 424 405 424 1184 448 380 423 407 421 406 424 1183 424 1185 421 406 423 404 424 405 424 1184 423 1182 425 407 448 380 423 407 422 405 423 406 422 406 424 405 423 407 421 406 423 407 422 405 424 405 423 406 423 1184 422 408 421 408 422 405 424 406 421 407 422 406 423 405 423 1183 424 406 423 405 423 405 423 405 423 1186 421 1184 422 1184 422 1185 422 1184 447 1159 423 1184 422 1184 422 408 421 407 423 1184 421 407 448 381 422 405 423 409 421 406 422 406 422 407 422 406 423 1183 423 1185 422 406 423 405 424 1184 423 408 421 405 424 405 424 1184 422 1185 422 1184 422 407 423 408 420 409 420 1185 447 382 423 405 423 408 421 406 423 407 422 406 423 406 423 408 421 406 423 1183 424 407 422 406 424 405 424 406 423 407 423 406 423 408 422 407 422 405 424 408 421 407 422 407 422 406 423 406 423 407 422 406 423 406 422 408 421 407 422 408 421 407 422 406 423 408 422 406 423 405 423 409 422 406 422 406 423 406 423 407 422 407 423 405 424 1184 423 407 421 406 424 1184 423 1184 422 407 423 1183 423 405 424 1184 423 409 420 407 422
|
||||
#
|
||||
|
||||
@@ -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
|
||||
#
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,15 +2,30 @@
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void infrared_scene_universal_common_item_callback(void* context, uint32_t index) {
|
||||
#pragma pack(push, 1)
|
||||
typedef union {
|
||||
uint32_t packed_value;
|
||||
struct {
|
||||
bool is_paused;
|
||||
uint8_t padding;
|
||||
uint16_t signal_index;
|
||||
};
|
||||
} InfraredSceneState;
|
||||
#pragma pack(pop)
|
||||
|
||||
void infrared_scene_universal_common_item_callback(void* context, uint32_t index, InputType type) {
|
||||
InfraredApp* infrared = context;
|
||||
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
|
||||
if(type == InputTypeShort) {
|
||||
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
|
||||
}
|
||||
}
|
||||
|
||||
static void infrared_scene_universal_common_progress_back_callback(void* context) {
|
||||
static void infrared_scene_universal_common_progress_input_callback(
|
||||
void* context,
|
||||
InfraredProgressViewInput input) {
|
||||
InfraredApp* infrared = context;
|
||||
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1);
|
||||
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypePopupInput, input);
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
|
||||
}
|
||||
|
||||
@@ -19,8 +34,8 @@ static void
|
||||
ViewStack* view_stack = infrared->view_stack;
|
||||
InfraredProgressView* progress = infrared->progress;
|
||||
infrared_progress_view_set_progress_total(progress, record_count);
|
||||
infrared_progress_view_set_back_callback(
|
||||
progress, infrared_scene_universal_common_progress_back_callback, infrared);
|
||||
infrared_progress_view_set_input_callback(
|
||||
progress, infrared_scene_universal_common_progress_input_callback, infrared);
|
||||
view_stack_add_view(view_stack, infrared_progress_view_get_view(progress));
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
|
||||
}
|
||||
@@ -51,29 +66,111 @@ void infrared_scene_universal_common_on_enter(void* context) {
|
||||
infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback);
|
||||
}
|
||||
|
||||
static void infrared_scene_universal_common_handle_popup_input(
|
||||
InfraredApp* infrared,
|
||||
InfraredProgressViewInput input) {
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager);
|
||||
switch(input) {
|
||||
case InfraredProgressViewInputStop: {
|
||||
infrared_brute_force_stop(brute_force);
|
||||
infrared_scene_universal_common_hide_popup(infrared);
|
||||
break;
|
||||
}
|
||||
|
||||
case InfraredProgressViewInputPause: {
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
|
||||
infrared_progress_view_set_paused(infrared->progress, true);
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
scene_state.is_paused = true;
|
||||
if(scene_state.signal_index)
|
||||
scene_state.signal_index--; // when running, the state stores the next index
|
||||
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
|
||||
break;
|
||||
}
|
||||
|
||||
case InfraredProgressViewInputResume: {
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
|
||||
infrared_progress_view_set_paused(infrared->progress, false);
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
scene_state.is_paused = false;
|
||||
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
|
||||
break;
|
||||
}
|
||||
|
||||
case InfraredProgressViewInputNextSignal: {
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
scene_state.signal_index++;
|
||||
if(infrared_progress_view_set_progress(infrared->progress, scene_state.signal_index + 1))
|
||||
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
|
||||
break;
|
||||
}
|
||||
|
||||
case InfraredProgressViewInputPreviousSignal: {
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
if(scene_state.signal_index) {
|
||||
scene_state.signal_index--;
|
||||
if(infrared_progress_view_set_progress(
|
||||
infrared->progress, scene_state.signal_index + 1))
|
||||
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case InfraredProgressViewInputSendSingle: {
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
|
||||
infrared_brute_force_send(infrared->brute_force, scene_state.signal_index);
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager);
|
||||
bool consumed = false;
|
||||
|
||||
if(infrared_brute_force_is_started(brute_force)) {
|
||||
if(event.type == SceneManagerEventTypeTick) {
|
||||
bool success = infrared_brute_force_send_next(brute_force);
|
||||
if(success) {
|
||||
success = infrared_progress_view_increase_progress(infrared->progress);
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
|
||||
if(!scene_state.is_paused) {
|
||||
bool success = infrared_brute_force_send(brute_force, scene_state.signal_index);
|
||||
if(success) {
|
||||
success = infrared_progress_view_set_progress(
|
||||
infrared->progress, scene_state.signal_index + 1);
|
||||
scene_state.signal_index++;
|
||||
scene_manager_set_scene_state(
|
||||
scene_manager, scene_id, scene_state.packed_value);
|
||||
}
|
||||
if(!success) {
|
||||
infrared_brute_force_stop(brute_force);
|
||||
infrared_scene_universal_common_hide_popup(infrared);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
if(!success) {
|
||||
infrared_brute_force_stop(brute_force);
|
||||
infrared_scene_universal_common_hide_popup(infrared);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) {
|
||||
infrared_brute_force_stop(brute_force);
|
||||
infrared_scene_universal_common_hide_popup(infrared);
|
||||
uint16_t event_type;
|
||||
int16_t event_value;
|
||||
infrared_custom_event_unpack(event.event, &event_type, &event_value);
|
||||
if(event_type == InfraredCustomEventTypePopupInput) {
|
||||
infrared_scene_universal_common_handle_popup_input(infrared, event_value);
|
||||
consumed = true;
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
} else {
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
@@ -87,6 +184,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e
|
||||
if(event_type == InfraredCustomEventTypeButtonSelected) {
|
||||
uint32_t record_count;
|
||||
if(infrared_brute_force_start(brute_force, event_value, &record_count)) {
|
||||
scene_manager_set_scene_state(infrared->scene_manager, scene_id, 0);
|
||||
dolphin_deed(DolphinDeedIrSend);
|
||||
infrared_scene_universal_common_show_popup(infrared, record_count);
|
||||
} else {
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
void infrared_scene_universal_common_on_enter(void* context);
|
||||
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event);
|
||||
void infrared_scene_universal_common_on_exit(void* context);
|
||||
void infrared_scene_universal_common_item_callback(void* context, uint32_t index);
|
||||
void infrared_scene_universal_common_item_callback(void* context, uint32_t index, InputType type);
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -118,7 +118,7 @@ void infrared_scene_universal_ac_on_enter(void* context) {
|
||||
button_panel_add_icon(button_panel, 0, 60, &I_cool_30x51);
|
||||
button_panel_add_icon(button_panel, 34, 60, &I_heat_30x51);
|
||||
|
||||
button_panel_add_label(button_panel, 4, 10, FontPrimary, "AC remote");
|
||||
button_panel_add_label(button_panel, 24, 10, FontPrimary, "AC");
|
||||
|
||||
infrared_scene_universal_common_on_enter(context);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ void infrared_scene_universal_audio_on_enter(void* context) {
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Vol_up");
|
||||
|
||||
button_panel_add_label(button_panel, 1, 10, FontPrimary, "Mus. remote");
|
||||
button_panel_add_label(button_panel, 1, 10, FontPrimary, "Audio player");
|
||||
button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30);
|
||||
|
||||
infrared_scene_universal_common_on_enter(context);
|
||||
|
||||
@@ -63,7 +63,7 @@ void infrared_scene_universal_projector_on_enter(void* context) {
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Vol_dn");
|
||||
|
||||
button_panel_add_label(button_panel, 3, 11, FontPrimary, "Proj. remote");
|
||||
button_panel_add_label(button_panel, 10, 11, FontPrimary, "Projector");
|
||||
button_panel_add_icon(button_panel, 17, 72, &I_vol_ac_text_30x30);
|
||||
|
||||
infrared_scene_universal_common_on_enter(context);
|
||||
|
||||
@@ -91,7 +91,7 @@ void infrared_scene_universal_tv_on_enter(void* context) {
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "Ch_prev");
|
||||
|
||||
button_panel_add_label(button_panel, 5, 10, FontPrimary, "TV remote");
|
||||
button_panel_add_label(button_panel, 25, 10, FontPrimary, "TV");
|
||||
|
||||
infrared_scene_universal_common_on_enter(context);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user