diff --git a/.github/workflows/amap_analyse.yml b/.github/workflows/amap_analyse.yml index cfb1eab1..1340e4cd 100644 --- a/.github/workflows/amap_analyse.yml +++ b/.github/workflows/amap_analyse.yml @@ -11,6 +11,7 @@ on: env: TARGETS: f7 + FBT_TOOLCHAIN_PATH: /opt jobs: amap_analyse: @@ -39,7 +40,7 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} @@ -78,7 +79,7 @@ jobs: - name: 'Upload report to DB' run: | - FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh + source scripts/toolchain/fbtenv.sh get_size() { SECTION="$1"; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed5ee6bd..2de0e57c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,7 @@ on: env: TARGETS: f7 DEFAULT_TARGET: f7 + FBT_TOOLCHAIN_PATH: /runner/_work jobs: main: @@ -24,7 +25,7 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} @@ -35,6 +36,7 @@ jobs: mkdir artifacts - name: 'Get commit details' + id: names run: | if [[ ${{ github.event_name }} == 'pull_request' ]]; then TYPE="pull" @@ -45,14 +47,6 @@ jobs: fi python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - - name: 'Generate suffixes for comment' - id: names - run: | - echo "::set-output name=branch_name::${BRANCH_NAME}" - echo "::set-output name=commit_sha::${COMMIT_SHA}" - echo "::set-output name=default_target::${DEFAULT_TARGET}" - echo "::set-output name=suffix::${SUFFIX}" - - name: 'Bundle scripts' if: ${{ !github.event.pull_request.head.repo.fork }} run: | @@ -62,7 +56,7 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ + ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ copro_dist updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} done @@ -143,7 +137,7 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 submodules: true @@ -164,6 +158,6 @@ jobs: run: | set -e for TARGET in ${TARGETS}; do - FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ + ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ updater_package DEBUG=0 COMPACT=1 done diff --git a/.github/workflows/check_submodules.yml b/.github/workflows/check_submodules.yml index eba4affc..2eb2027c 100644 --- a/.github/workflows/check_submodules.yml +++ b/.github/workflows/check_submodules.yml @@ -20,7 +20,7 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} @@ -36,12 +36,12 @@ jobs: BRANCHES=$(git branch -r --contains "$SUBMODULE_HASH"); COMMITS_IN_BRANCH="$(git rev-list --count dev)"; if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then - echo "::set-output name=fails::error"; + echo "name=fails::error" >> $GITHUB_OUTPUT echo "::error::Error: Too low commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)"; exit 1; fi if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then - echo "::set-output name=fails::error"; + echo "name=fails::error" >> $GITHUB_OUTPUT echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH"; exit 1; fi diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml index 23dc6c69..a6fd5127 100644 --- a/.github/workflows/lint_c.yml +++ b/.github/workflows/lint_c.yml @@ -11,6 +11,8 @@ on: env: TARGETS: f7 + FBT_TOOLCHAIN_PATH: /runner/_work + SET_GH_OUTPUT: 1 jobs: lint_c_cpp: @@ -23,14 +25,14 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - name: 'Check code formatting' id: syntax_check - run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/runner/_work ./fbt lint + run: ./fbt lint - name: Report code formatting errors if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index c2f09211..66c36064 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -9,6 +9,10 @@ on: - '*' pull_request: +env: + FBT_TOOLCHAIN_PATH: /runner/_work + SET_GH_OUTPUT: 1 + jobs: lint_python: runs-on: [self-hosted,FlipperZeroShell] @@ -20,10 +24,10 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - name: 'Check code formatting' - run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/runner/_work ./fbt lint_py + run: ./fbt lint_py diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml new file mode 100644 index 00000000..13fab094 --- /dev/null +++ b/.github/workflows/merge_report.yml @@ -0,0 +1,45 @@ +name: 'Check FL ticket in PR name' + +on: + push: + branches: + - dev + +env: + FBT_TOOLCHAIN_PATH: /runner/_work + +jobs: + merge_report: + runs-on: [self-hosted,FlipperZeroShell] + steps: + - name: 'Decontaminate previous build leftovers' + run: | + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + fi + + - name: 'Checkout code' + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: 'Get commit details' + run: | + if [[ ${{ github.event_name }} == 'pull_request' ]]; then + TYPE="pull" + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + TYPE="tag" + else + TYPE="other" + fi + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" + + - name: 'Check ticket and report' + run: | + source scripts/toolchain/fbtenv.sh + python3 -m pip install slack_sdk + python3 scripts/merge_report_qa.py \ + ${{ secrets.QA_REPORT_SLACK_TOKEN }} \ + ${{ secrets.QA_REPORT_SLACK_CHANNEL }} + diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 9de493a4..5bb04afc 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -12,6 +12,7 @@ on: env: TARGETS: f7 DEFAULT_TARGET: f7 + FBT_TOOLCHAIN_PATH: /runner/_work jobs: analyse_c_cpp: @@ -25,12 +26,13 @@ jobs: fi - name: 'Checkout code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - name: 'Get commit details' + id: names run: | if [[ ${{ github.event_name }} == 'pull_request' ]]; then TYPE="pull" @@ -41,15 +43,6 @@ jobs: fi python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - - name: 'Generate suffixes for comment' - if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} - id: names - run: | - echo "::set-output name=branch_name::${BRANCH_NAME}" - echo "::set-output name=commit_sha::${COMMIT_SHA}" - echo "::set-output name=default_target::${DEFAULT_TARGET}" - echo "::set-output name=suffix::${SUFFIX}" - - name: 'Make reports directory' run: | rm -rf reports/ @@ -57,11 +50,11 @@ jobs: - name: 'Generate compile_comands.json' run: | - FBT_TOOLCHAIN_PATH=/runner/_work ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking _fap_icons + ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking _fap_icons - name: 'Static code analysis' run: | - FBT_TOOLCHAIN_PATH=/runner/_work source scripts/toolchain/fbtenv.sh + source scripts/toolchain/fbtenv.sh pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} pvs-studio-analyzer analyze \ @.pvsoptions \ diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 1ca4a9c0..eb687b6c 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -6,13 +6,20 @@ on: env: TARGETS: f7 DEFAULT_TARGET: f7 + FBT_TOOLCHAIN_PATH: /opt jobs: run_units_on_test_bench: runs-on: [self-hosted, FlipperZeroTest] steps: + - name: 'Decontaminate previous build leftovers' + run: | + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + fi + - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} @@ -22,35 +29,81 @@ jobs: run: | echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT + - name: 'Flashing target firmware' + id: first_full_flash + run: | + ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + + - name: 'Validating updater' + id: second_full_flash + if: success() + run: | + ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + - name: 'Flash unit tests firmware' id: flashing + if: success() run: | - FBT_TOOLCHAIN_PATH=/opt ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 - name: 'Wait for flipper to finish updating' id: connect if: steps.flashing.outcome == 'success' run: | - . scripts/toolchain/fbtenv.sh - ./scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - - - name: 'Format flipper SD card' - id: format - if: steps.connect.outcome == 'success' - run: | - . scripts/toolchain/fbtenv.sh - ./scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} - name: 'Copy assets and unit tests data to flipper' id: copy - if: steps.format.outcome == 'success' + if: steps.connect.outcome == 'success' run: | - . scripts/toolchain/fbtenv.sh - ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/resources /ext - ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/unit_tests /ext/unit_tests + source scripts/toolchain/fbtenv.sh + python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send assets/unit_tests /ext/unit_tests - name: 'Run units and validate results' if: steps.copy.outcome == 'success' run: | - . scripts/toolchain/fbtenv.sh - ./scripts/testing/units.py ${{steps.device.outputs.flipper}} + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/units.py ${{steps.device.outputs.flipper}} + + - name: 'Get last release tag' + id: release_tag + if: always() + run: | + echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT + + - name: 'Decontaminate previous build leftovers' + if: always() + run: | + if [ -d .git ]; then + git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)" + fi + + - name: 'Checkout latest release' + uses: actions/checkout@v3 + if: always() + with: + fetch-depth: 0 + ref: ${{ steps.release_tag.outputs.tag }} + + - name: 'Flash last release' + if: always() + run: | + ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 + + - name: 'Wait for flipper to finish updating' + if: always() + run: | + source scripts/toolchain/fbtenv.sh + python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} + + - name: 'Format flipper SD card' + id: format + if: always() + run: | + source scripts/toolchain/fbtenv.sh + python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json index 9baaf97b..c16c3ab4 100644 --- a/.vscode/example/tasks.json +++ b/.vscode/example/tasks.json @@ -128,6 +128,38 @@ "group": "build", "type": "shell", "command": "./fbt COMPACT=1 DEBUG=0 launch_app APPSRC=${relativeFileDirname}" + }, + { + "label": "[Debug] Launch App on Flipper with Serial Console", + "dependsOrder": "sequence", + "group": "build", + "dependsOn": [ + "[Debug] Launch App on Flipper", + "Serial Console" + ] + }, + { + // Press Ctrl+] to quit + "label": "Serial Console", + "type": "shell", + "command": "./fbt cli", + "group": "none", + "isBackground": true, + "options": { + "env": { + "FBT_NO_SYNC": "0" + } + }, + "presentation": { + "reveal": "always", + "revealProblems": "never", + "showReuseMessage": false, + "panel": "dedicated", + "focus": true, + "echo": true, + "close": true, + "group": "Logger" + } } ] -} \ No newline at end of file +} diff --git a/applications/debug/locale_test/application.fam b/applications/debug/locale_test/application.fam new file mode 100644 index 00000000..e46eeff5 --- /dev/null +++ b/applications/debug/locale_test/application.fam @@ -0,0 +1,11 @@ +App( + appid="locale_test", + name="Locale Test", + apptype=FlipperAppType.DEBUG, + entry_point="locale_test_app", + cdefines=["APP_LOCALE"], + requires=["gui", "locale"], + stack_size=2 * 1024, + order=70, + fap_category="Debug", +) diff --git a/applications/debug/locale_test/locale_test.c b/applications/debug/locale_test/locale_test.c new file mode 100644 index 00000000..003df55d --- /dev/null +++ b/applications/debug/locale_test/locale_test.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include +#include + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + View* view; +} LocaleTestApp; + +static void locale_test_view_draw_callback(Canvas* canvas, void* _model) { + UNUSED(_model); + + // Prepare canvas + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + + FuriString* tmp_string = furi_string_alloc(); + + float temp = 25.3f; + LocaleMeasurementUnits units = locale_get_measurement_unit(); + if(units == LocaleMeasurementUnitsMetric) { + furi_string_printf(tmp_string, "Temp: %5.1fC", (double)temp); + } else { + temp = locale_celsius_to_fahrenheit(temp); + furi_string_printf(tmp_string, "Temp: %5.1fF", (double)temp); + } + canvas_draw_str(canvas, 0, 10, furi_string_get_cstr(tmp_string)); + + FuriHalRtcDateTime datetime; + furi_hal_rtc_get_datetime(&datetime); + + locale_format_time(tmp_string, &datetime, locale_get_time_format(), false); + canvas_draw_str(canvas, 0, 25, furi_string_get_cstr(tmp_string)); + + locale_format_date(tmp_string, &datetime, locale_get_date_format(), "/"); + canvas_draw_str(canvas, 0, 40, furi_string_get_cstr(tmp_string)); + + furi_string_free(tmp_string); +} + +static bool locale_test_view_input_callback(InputEvent* event, void* context) { + UNUSED(event); + UNUSED(context); + return false; +} + +static uint32_t locale_test_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +static LocaleTestApp* locale_test_alloc() { + LocaleTestApp* app = malloc(sizeof(LocaleTestApp)); + + // Gui + app->gui = furi_record_open(RECORD_GUI); + + // View dispatcher + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Views + app->view = view_alloc(); + view_set_draw_callback(app->view, locale_test_view_draw_callback); + view_set_input_callback(app->view, locale_test_view_input_callback); + + view_set_previous_callback(app->view, locale_test_exit); + view_dispatcher_add_view(app->view_dispatcher, 0, app->view); + view_dispatcher_switch_to_view(app->view_dispatcher, 0); + + return app; +} + +static void locale_test_free(LocaleTestApp* app) { + furi_assert(app); + + // Free views + view_dispatcher_remove_view(app->view_dispatcher, 0); + + view_free(app->view); + view_dispatcher_free(app->view_dispatcher); + + // Close gui record + furi_record_close(RECORD_GUI); + app->gui = NULL; + + // Free rest + free(app); +} + +int32_t locale_test_app(void* p) { + UNUSED(p); + LocaleTestApp* app = locale_test_alloc(); + view_dispatcher_run(app->view_dispatcher); + locale_test_free(app); + return 0; +} diff --git a/applications/debug/unit_tests/bt/bt_test.c b/applications/debug/unit_tests/bt/bt_test.c new file mode 100644 index 00000000..2cbfd684 --- /dev/null +++ b/applications/debug/unit_tests/bt/bt_test.c @@ -0,0 +1,110 @@ +#include +#include +#include "../minunit.h" + +#include +#include + +#define BT_TEST_KEY_STORAGE_FILE_PATH EXT_PATH("unit_tests/bt_test.keys") +#define BT_TEST_NVM_RAM_BUFF_SIZE (507 * 4) // The same as in ble NVM storage + +typedef struct { + Storage* storage; + BtKeysStorage* bt_keys_storage; + uint8_t* nvm_ram_buff_dut; + uint8_t* nvm_ram_buff_ref; +} BtTest; + +BtTest* bt_test = NULL; + +void bt_test_alloc() { + bt_test = malloc(sizeof(BtTest)); + bt_test->storage = furi_record_open(RECORD_STORAGE); + bt_test->nvm_ram_buff_dut = malloc(BT_TEST_NVM_RAM_BUFF_SIZE); + bt_test->nvm_ram_buff_ref = malloc(BT_TEST_NVM_RAM_BUFF_SIZE); + bt_test->bt_keys_storage = bt_keys_storage_alloc(BT_TEST_KEY_STORAGE_FILE_PATH); + bt_keys_storage_set_ram_params( + bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, BT_TEST_NVM_RAM_BUFF_SIZE); +} + +void bt_test_free() { + furi_assert(bt_test); + free(bt_test->nvm_ram_buff_ref); + free(bt_test->nvm_ram_buff_dut); + bt_keys_storage_free(bt_test->bt_keys_storage); + furi_record_close(RECORD_STORAGE); + free(bt_test); + bt_test = NULL; +} + +static void bt_test_keys_storage_profile() { + // Emulate nvm change on initial connection + const int nvm_change_size_on_connection = 88; + for(size_t i = 0; i < nvm_change_size_on_connection; i++) { + bt_test->nvm_ram_buff_dut[i] = rand(); + bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i]; + } + // Emulate update storage on initial connect + mu_assert( + bt_keys_storage_update( + bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection), + "Failed to update key storage on initial connect"); + memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE); + mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM"); + mu_assert( + memcmp( + bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection) == + 0, + "Wrong buffer loaded"); + + const int nvm_disconnect_update_offset = 84; + const int nvm_disconnect_update_size = 324; + const int nvm_total_size = nvm_change_size_on_connection - + (nvm_change_size_on_connection - nvm_disconnect_update_offset) + + nvm_disconnect_update_size; + // Emulate update storage on initial disconnect + for(size_t i = nvm_disconnect_update_offset; + i < nvm_disconnect_update_offset + nvm_disconnect_update_size; + i++) { + bt_test->nvm_ram_buff_dut[i] = rand(); + bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i]; + } + mu_assert( + bt_keys_storage_update( + bt_test->bt_keys_storage, + &bt_test->nvm_ram_buff_dut[nvm_disconnect_update_offset], + nvm_disconnect_update_size), + "Failed to update key storage on initial disconnect"); + memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE); + mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM"); + mu_assert( + memcmp(bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_total_size) == 0, + "Wrong buffer loaded"); +} + +static void bt_test_keys_remove_test_file() { + mu_assert( + storage_simply_remove(bt_test->storage, BT_TEST_KEY_STORAGE_FILE_PATH), + "Can't remove test file"); +} + +MU_TEST(bt_test_keys_storage_serial_profile) { + furi_assert(bt_test); + + bt_test_keys_remove_test_file(); + bt_test_keys_storage_profile(); + bt_test_keys_remove_test_file(); +} + +MU_TEST_SUITE(test_bt) { + bt_test_alloc(); + + MU_RUN_TEST(bt_test_keys_storage_serial_profile); + + bt_test_free(); +} + +int run_minunit_test_bt() { + MU_RUN_SUITE(test_bt); + return MU_EXIT_CODE; +} diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 4218482c..e9e7b35f 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include @@ -102,7 +102,10 @@ static bool nfc_test_digital_signal_test_encode( do { // Read test data - if(!nfc_test_read_signal_from_file(file_name)) break; + if(!nfc_test_read_signal_from_file(file_name)) { + FURI_LOG_E(TAG, "Failed to read signal from file"); + break; + } // Encode signal FURI_CRITICAL_ENTER(); diff --git a/applications/debug/unit_tests/subghz/subghz_test.c b/applications/debug/unit_tests/subghz/subghz_test.c index fe834c60..d954ddd9 100644 --- a/applications/debug/unit_tests/subghz/subghz_test.c +++ b/applications/debug/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo") #define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s") #define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub") -#define TEST_RANDOM_COUNT_PARSE 244 +#define TEST_RANDOM_COUNT_PARSE 253 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -587,6 +587,13 @@ MU_TEST(subghz_decoder_ansonic_test) { "Test decoder " SUBGHZ_PROTOCOL_ANSONIC_NAME " error\r\n"); } +MU_TEST(subghz_decoder_smc5326_test) { + mu_assert( + subghz_decoder_test( + EXT_PATH("unit_tests/subghz/smc5326_raw.sub"), SUBGHZ_PROTOCOL_SMC5326_NAME), + "Test decoder " SUBGHZ_PROTOCOL_SMC5326_NAME " error\r\n"); +} + //test encoders MU_TEST(subghz_encoder_princeton_test) { mu_assert( @@ -714,6 +721,12 @@ MU_TEST(subghz_encoder_ansonic_test) { "Test encoder " SUBGHZ_PROTOCOL_ANSONIC_NAME " error\r\n"); } +MU_TEST(subghz_encoder_smc5326_test) { + mu_assert( + subghz_encoder_test(EXT_PATH("unit_tests/subghz/smc5326.sub")), + "Test encoder " SUBGHZ_PROTOCOL_SMC5326_NAME " error\r\n"); +} + MU_TEST(subghz_random_test) { mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); } @@ -757,6 +770,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_decoder_intertechno_v3_test); MU_RUN_TEST(subghz_decoder_clemsa_test); MU_RUN_TEST(subghz_decoder_ansonic_test); + MU_RUN_TEST(subghz_decoder_smc5326_test); MU_RUN_TEST(subghz_encoder_princeton_test); MU_RUN_TEST(subghz_encoder_came_test); @@ -779,6 +793,7 @@ MU_TEST_SUITE(subghz) { MU_RUN_TEST(subghz_encoder_intertechno_v3_test); MU_RUN_TEST(subghz_encoder_clemsa_test); MU_RUN_TEST(subghz_encoder_ansonic_test); + MU_RUN_TEST(subghz_encoder_smc5326_test); MU_RUN_TEST(subghz_random_test); subghz_test_deinit(); diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 65fa23c0..36c2b4ae 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -24,6 +24,7 @@ int run_minunit_test_protocol_dict(); int run_minunit_test_lfrfid_protocols(); int run_minunit_test_nfc(); int run_minunit_test_bit_lib(); +int run_minunit_test_bt(); typedef int (*UnitTestEntry)(); @@ -49,6 +50,7 @@ const UnitTest unit_tests[] = { {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict}, {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols}, {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, + {.name = "bt", .entry = run_minunit_test_bt}, }; void minunit_print_progress() { diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 04f4dcc3..2f469354 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -143,6 +143,8 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { break; case ArchiveBrowserEventFileMenuDelete: if(archive_get_tab(browser) != ArchiveTabFavorites) { + scene_manager_set_scene_state( + archive->scene_manager, ArchiveAppSceneBrowser, SCENE_STATE_NEED_REFRESH); scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneDelete); } consumed = true; diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 65be4213..2aca3c02 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -5,6 +5,9 @@ #include "archive_browser_view.h" #include "../helpers/archive_browser.h" +#define SCROLL_INTERVAL (333) +#define SCROLL_DELAY (2) + static const char* ArchiveTabNames[] = { [ArchiveTabFavorites] = "Favorites", [ArchiveTabIButton] = "iButton", @@ -146,13 +149,18 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { furi_string_set(str_buf, "---"); } - elements_string_fit_width( - canvas, str_buf, (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset); + size_t scroll_counter = model->scroll_counter; if(model->item_idx == idx) { archive_draw_frame(canvas, i, scrollbar, model->move_fav); + if(scroll_counter < SCROLL_DELAY) { + scroll_counter = 0; + } else { + scroll_counter -= SCROLL_DELAY; + } } else { canvas_set_color(canvas, ColorBlack); + scroll_counter = 0; } if(custom_icon_data) { @@ -162,8 +170,15 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { canvas_draw_icon( canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]); } - canvas_draw_str( - canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buf)); + + elements_scrollable_text_line( + canvas, + 15 + x_offset, + 24 + i * FRAME_HEIGHT, + ((scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset), + str_buf, + scroll_counter, + (model->item_idx != idx)); furi_string_free(str_buf); } @@ -329,6 +344,7 @@ static bool archive_view_input(InputEvent* event, void* context) { if(move_fav_mode) { browser->callback(ArchiveBrowserEventFavMoveUp, browser->context); } + model->scroll_counter = 0; } else if(event->key == InputKeyDown) { model->item_idx = (model->item_idx + 1) % model->item_cnt; if(is_file_list_load_required(model)) { @@ -338,6 +354,7 @@ static bool archive_view_input(InputEvent* event, void* context) { if(move_fav_mode) { browser->callback(ArchiveBrowserEventFavMoveDown, browser->context); } + model->scroll_counter = 0; } }, true); @@ -377,6 +394,27 @@ static bool archive_view_input(InputEvent* event, void* context) { return true; } +static void browser_scroll_timer(void* context) { + furi_assert(context); + ArchiveBrowserView* browser = context; + with_view_model( + browser->view, ArchiveBrowserViewModel * model, { model->scroll_counter++; }, true); +} + +static void browser_view_enter(void* context) { + furi_assert(context); + ArchiveBrowserView* browser = context; + with_view_model( + browser->view, ArchiveBrowserViewModel * model, { model->scroll_counter = 0; }, true); + furi_timer_start(browser->scroll_timer, SCROLL_INTERVAL); +} + +static void browser_view_exit(void* context) { + furi_assert(context); + ArchiveBrowserView* browser = context; + furi_timer_stop(browser->scroll_timer); +} + ArchiveBrowserView* browser_alloc() { ArchiveBrowserView* browser = malloc(sizeof(ArchiveBrowserView)); browser->view = view_alloc(); @@ -384,6 +422,10 @@ ArchiveBrowserView* browser_alloc() { view_set_context(browser->view, browser); view_set_draw_callback(browser->view, archive_view_render); view_set_input_callback(browser->view, archive_view_input); + view_set_enter_callback(browser->view, browser_view_enter); + view_set_exit_callback(browser->view, browser_view_exit); + + browser->scroll_timer = furi_timer_alloc(browser_scroll_timer, FuriTimerTypePeriodic, browser); browser->path = furi_string_alloc_set(archive_get_default_path(TAB_DEFAULT)); @@ -402,6 +444,8 @@ ArchiveBrowserView* browser_alloc() { void browser_free(ArchiveBrowserView* browser) { furi_assert(browser); + furi_timer_free(browser->scroll_timer); + if(browser->worker_running) { file_browser_worker_free(browser->worker); } diff --git a/applications/main/archive/views/archive_browser_view.h b/applications/main/archive/views/archive_browser_view.h index 308af4e4..915b5307 100644 --- a/applications/main/archive/views/archive_browser_view.h +++ b/applications/main/archive/views/archive_browser_view.h @@ -81,6 +81,7 @@ struct ArchiveBrowserView { FuriString* path; InputKey last_tab_switch_dir; bool is_root; + FuriTimer* scroll_timer; }; typedef struct { @@ -97,6 +98,7 @@ typedef struct { int32_t item_idx; int32_t array_offset; int32_t list_offset; + size_t scroll_counter; } ArchiveBrowserViewModel; void archive_browser_set_callback( diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 0dd071bc..6e6dc4dc 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -46,6 +46,7 @@ Nfc* nfc_alloc() { // Nfc device nfc->dev = nfc_device_alloc(); + furi_string_set(nfc->dev->folder, NFC_APP_FOLDER); // Open GUI record nfc->gui = furi_record_open(RECORD_GUI); diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index 57eefbf6..f7e48990 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -27,6 +27,7 @@ #include #include #include +#include #include "views/dict_attack.h" #include "views/detect_reader.h" @@ -43,6 +44,7 @@ ARRAY_DEF(MfClassicUserKeys, char*, M_PTR_OPLIST); #define NFC_TEXT_STORE_SIZE 128 +#define NFC_APP_FOLDER ANY_PATH("nfc") typedef enum { NfcRpcStateIdle, @@ -50,9 +52,6 @@ typedef enum { NfcRpcStateEmulated, } NfcRpcState; -// Forward declaration due to circular dependency -typedef struct NfcGenerator NfcGenerator; - struct Nfc { NfcWorker* worker; ViewDispatcher* view_dispatcher; diff --git a/applications/main/nfc/scenes/nfc_scene_generate_info.c b/applications/main/nfc/scenes/nfc_scene_generate_info.c index 66900767..7b84ae43 100644 --- a/applications/main/nfc/scenes/nfc_scene_generate_info.c +++ b/applications/main/nfc/scenes/nfc_scene_generate_info.c @@ -1,5 +1,5 @@ #include "../nfc_i.h" -#include "../helpers/nfc_generators.h" +#include "lib/nfc/helpers/nfc_generators.h" void nfc_scene_generate_info_dialog_callback(DialogExResult result, void* context) { Nfc* nfc = context; @@ -39,7 +39,12 @@ bool nfc_scene_generate_info_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultRight) { - scene_manager_next_scene(nfc->scene_manager, nfc->generator->next_scene); + // Switch either to NfcSceneMfClassicMenu or NfcSceneMfUltralightMenu + if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareClassic) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicMenu); + } else if(nfc->dev->dev_data.protocol == NfcDeviceProtocolMifareUl) { + scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightMenu); + } consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index 3e08aeb3..cadf2eb6 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -1,5 +1,5 @@ #include "../nfc_i.h" -#include "../helpers/nfc_generators.h" +#include "lib/nfc/helpers/nfc_generators.h" enum SubmenuIndex { SubmenuIndexNFCA4, diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index bd6451f2..abf70539 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -28,6 +28,13 @@ typedef enum { SubGhzHopperStateRSSITimeOut, } SubGhzHopperState; +/** SubGhzSpeakerState state */ +typedef enum { + SubGhzSpeakerStateDisable, + SubGhzSpeakerStateShutdown, + SubGhzSpeakerStateEnable, +} SubGhzSpeakerState; + /** SubGhzRxKeyState state */ typedef enum { SubGhzRxKeyStateIDLE, diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 2e5ba096..b270dd48 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -259,6 +259,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { case SubGhzCustomEventViewReadRAWSendStop: subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { + subghz_speaker_unmute(subghz); subghz_tx_stop(subghz); subghz_sleep(subghz); } @@ -376,10 +377,12 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, false); subghz_protocol_raw_save_to_file_pause( (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, true); + subghz_speaker_mute(subghz); } else { subghz_read_raw_add_data_rssi(subghz->subghz_read_raw, rssi, true); subghz_protocol_raw_save_to_file_pause( (SubGhzProtocolDecoderRAW*)subghz->txrx->decoder_result, false); + subghz_speaker_unmute(subghz); } } diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index fd42829b..b49aac92 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -5,6 +5,7 @@ enum SubGhzSettingIndex { SubGhzSettingIndexFrequency, SubGhzSettingIndexHopping, SubGhzSettingIndexModulation, + SubGhzSettingIndexSound, SubGhzSettingIndexLock, SubGhzSettingIndexRAWThesholdRSSI, }; @@ -48,6 +49,16 @@ const uint32_t hopping_value[HOPPING_COUNT] = { SubGhzHopperStateRunnig, }; +#define SPEAKER_COUNT 2 +const char* const speaker_text[SPEAKER_COUNT] = { + "OFF", + "ON", +}; +const uint32_t speaker_value[SPEAKER_COUNT] = { + SubGhzSpeakerStateShutdown, + SubGhzSpeakerStateEnable, +}; + uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) { furi_assert(context); SubGhz* subghz = context; @@ -167,6 +178,14 @@ static void subghz_scene_receiver_config_set_hopping_running(VariableItem* item) subghz->txrx->hopper_state = hopping_value[index]; } +static void subghz_scene_receiver_config_set_speaker(VariableItem* item) { + SubGhz* subghz = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, speaker_text[index]); + subghz->txrx->speaker_state = speaker_value[index]; +} + static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -235,6 +254,16 @@ void subghz_scene_receiver_config_on_enter(void* context) { variable_item_set_current_value_text( item, subghz_setting_get_preset_name(subghz->setting, value_index)); + item = variable_item_list_add( + subghz->variable_item_list, + "Sound:", + SPEAKER_COUNT, + subghz_scene_receiver_config_set_speaker, + subghz); + value_index = value_index_uint32(subghz->txrx->speaker_state, speaker_value, SPEAKER_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, speaker_text[value_index]); + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerSet) { variable_item_list_add(subghz->variable_item_list, "Lock Keyboard", 1, NULL, NULL); diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index df5a7652..b7564ab5 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -177,6 +177,7 @@ SubGhz* subghz_alloc() { subghz->txrx->txrx_state = SubGhzTxRxStateSleep; subghz->txrx->hopper_state = SubGhzHopperStateOFF; + subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; subghz->txrx->raw_threshold_rssi = SUBGHZ_RAW_TRESHOLD_MIN; subghz->txrx->history = subghz_history_alloc(); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 736bcf36..0bcd7006 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -86,6 +86,7 @@ uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency) { uint32_t value = furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_subghz_flush_rx(); + subghz_speaker_on(subghz); furi_hal_subghz_rx(); furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, subghz->txrx->worker); @@ -104,6 +105,7 @@ static bool subghz_tx(SubGhz* subghz, uint32_t frequency) { furi_hal_subghz_set_frequency_and_path(frequency); furi_hal_gpio_write(&gpio_cc1101_g0, false); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); + subghz_speaker_on(subghz); bool ret = furi_hal_subghz_tx(); subghz->txrx->txrx_state = SubGhzTxRxStateTx; return ret; @@ -119,11 +121,13 @@ void subghz_idle(SubGhz* subghz) { void subghz_rx_end(SubGhz* subghz) { furi_assert(subghz); furi_assert(subghz->txrx->txrx_state == SubGhzTxRxStateRx); + if(subghz_worker_is_running(subghz->txrx->worker)) { subghz_worker_stop(subghz->txrx->worker); furi_hal_subghz_stop_async_rx(); } furi_hal_subghz_idle(); + subghz_speaker_off(subghz); subghz->txrx->txrx_state = SubGhzTxRxStateIDLE; } @@ -212,6 +216,7 @@ void subghz_tx_stop(SubGhz* subghz) { subghz, subghz->txrx->fff_data, furi_string_get_cstr(subghz->file_path)); } subghz_idle(subghz); + subghz_speaker_off(subghz); notification_message(subghz->notifications, &sequence_reset_red); } @@ -585,3 +590,40 @@ void subghz_hopper_update(SubGhz* subghz) { subghz_rx(subghz, subghz->txrx->preset->frequency); } } + +void subghz_speaker_on(SubGhz* subghz) { + if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_acquire(30)) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } else { + subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; + } + } +} + +void subghz_speaker_off(SubGhz* subghz) { + if(subghz->txrx->speaker_state != SubGhzSpeakerStateDisable) { + if(furi_hal_speaker_is_mine()) { + furi_hal_subghz_set_async_mirror_pin(NULL); + furi_hal_speaker_release(); + if(subghz->txrx->speaker_state == SubGhzSpeakerStateShutdown) + subghz->txrx->speaker_state = SubGhzSpeakerStateDisable; + } + } +} + +void subghz_speaker_mute(SubGhz* subghz) { + if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_is_mine()) { + furi_hal_subghz_set_async_mirror_pin(NULL); + } + } +} + +void subghz_speaker_unmute(SubGhz* subghz) { + if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) { + if(furi_hal_speaker_is_mine()) { + furi_hal_subghz_set_async_mirror_pin(&gpio_speaker); + } + } +} diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 23436515..cd33da44 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -53,6 +53,7 @@ struct SubGhzTxRx { uint16_t idx_menu_chosen; SubGhzTxRxState txrx_state; SubGhzHopperState hopper_state; + SubGhzSpeakerState speaker_state; uint8_t hopper_timeout; uint8_t hopper_idx_frequency; SubGhzRxKeyState rx_key_state; @@ -131,3 +132,7 @@ void subghz_file_name_clear(SubGhz* subghz); bool subghz_path_is_file(FuriString* path); uint32_t subghz_random_serial(void); void subghz_hopper_update(SubGhz* subghz); +void subghz_speaker_on(SubGhz* subghz); +void subghz_speaker_off(SubGhz* subghz); +void subghz_speaker_mute(SubGhz* subghz); +void subghz_speaker_unmute(SubGhz* subghz); diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 7aa69922..999eef6a 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -8,7 +8,7 @@ #include #define FRAME_HEIGHT 12 -#define MAX_LEN_PX 100 +#define MAX_LEN_PX 111 #define MENU_ITEMS 4u #define UNLOCK_CNT 3 @@ -186,7 +186,7 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { size_t idx = CLAMP((uint16_t)(i + model->list_offset), model->history_item, 0); item_menu = SubGhzReceiverMenuItemArray_get(model->history->data, idx); furi_string_set(str_buff, item_menu->item_str); - elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); + elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 7 : MAX_LEN_PX); if(model->idx == idx) { subghz_view_receiver_draw_frame(canvas, i, scrollbar); } else { diff --git a/applications/plugins/clock/application.fam b/applications/plugins/clock/application.fam new file mode 100644 index 00000000..590f5dfe --- /dev/null +++ b/applications/plugins/clock/application.fam @@ -0,0 +1,10 @@ +App( + appid="clock", + name="Clock", + apptype=FlipperAppType.PLUGIN, + entry_point="clock_app", + requires=["gui"], + stack_size=2 * 1024, + fap_icon="clock.png", + fap_category="Tools", +) diff --git a/applications/plugins/clock/clock.png b/applications/plugins/clock/clock.png new file mode 100644 index 00000000..0d96df10 Binary files /dev/null and b/applications/plugins/clock/clock.png differ diff --git a/applications/plugins/clock/clock_app.c b/applications/plugins/clock/clock_app.c new file mode 100644 index 00000000..9d87ff95 --- /dev/null +++ b/applications/plugins/clock/clock_app.c @@ -0,0 +1,136 @@ +#include +#include + +#include +#include + +typedef enum { + ClockEventTypeTick, + ClockEventTypeKey, +} ClockEventType; + +typedef struct { + ClockEventType type; + InputEvent input; +} ClockEvent; + +typedef struct { + FuriString* buffer; + FuriHalRtcDateTime datetime; + LocaleTimeFormat timeformat; + LocaleDateFormat dateformat; +} ClockData; + +typedef struct { + FuriMutex* mutex; + FuriMessageQueue* queue; + ClockData* data; +} Clock; + +static void clock_input_callback(InputEvent* input_event, FuriMessageQueue* queue) { + furi_assert(queue); + ClockEvent event = {.type = ClockEventTypeKey, .input = *input_event}; + furi_message_queue_put(queue, &event, FuriWaitForever); +} + +static void clock_render_callback(Canvas* canvas, void* ctx) { + Clock* clock = ctx; + if(furi_mutex_acquire(clock->mutex, 200) != FuriStatusOk) { + return; + } + + ClockData* data = clock->data; + + canvas_set_font(canvas, FontBigNumbers); + locale_format_time(data->buffer, &data->datetime, data->timeformat, true); + canvas_draw_str_aligned( + canvas, 64, 28, AlignCenter, AlignCenter, furi_string_get_cstr(data->buffer)); + + // Special case to cover missing glyphs in FontBigNumbers + if(data->timeformat == LocaleTimeFormat12h) { + size_t time_width = canvas_string_width(canvas, furi_string_get_cstr(data->buffer)); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, + 64 + (time_width / 2) - 10, + 31, + AlignLeft, + AlignCenter, + (data->datetime.hour > 12) ? "PM" : "AM"); + } + + canvas_set_font(canvas, FontSecondary); + locale_format_date(data->buffer, &data->datetime, data->dateformat, "/"); + canvas_draw_str_aligned( + canvas, 64, 42, AlignCenter, AlignTop, furi_string_get_cstr(data->buffer)); + + furi_mutex_release(clock->mutex); +} + +static void clock_tick(void* ctx) { + furi_assert(ctx); + FuriMessageQueue* queue = ctx; + ClockEvent event = {.type = ClockEventTypeTick}; + // It's OK to loose this event if system overloaded + furi_message_queue_put(queue, &event, 0); +} + +int32_t clock_app(void* p) { + UNUSED(p); + Clock* clock = malloc(sizeof(Clock)); + clock->data = malloc(sizeof(ClockData)); + clock->data->buffer = furi_string_alloc(); + + clock->queue = furi_message_queue_alloc(8, sizeof(ClockEvent)); + clock->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + furi_hal_rtc_get_datetime(&clock->data->datetime); + clock->data->timeformat = locale_get_time_format(); + clock->data->dateformat = locale_get_date_format(); + + // Set ViewPort callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, clock_render_callback, clock); + view_port_input_callback_set(view_port, clock_input_callback, clock->queue); + + FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, clock->queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + furi_timer_start(timer, 100); + + // Main loop + ClockEvent event; + for(bool processing = true; processing;) { + furi_check(furi_message_queue_get(clock->queue, &event, FuriWaitForever) == FuriStatusOk); + furi_mutex_acquire(clock->mutex, FuriWaitForever); + if(event.type == ClockEventTypeKey) { + if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) { + processing = false; + } + } else if(event.type == ClockEventTypeTick) { + furi_hal_rtc_get_datetime(&clock->data->datetime); + } + + furi_mutex_release(clock->mutex); + view_port_update(view_port); + } + + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_record_close(RECORD_GUI); + + furi_message_queue_free(clock->queue); + furi_mutex_free(clock->mutex); + + furi_string_free(clock->data->buffer); + + free(clock->data); + free(clock); + + return 0; +} \ No newline at end of file diff --git a/applications/plugins/hid_app/hid.c b/applications/plugins/hid_app/hid.c index 2a617fde..1d2235e0 100644 --- a/applications/plugins/hid_app/hid.c +++ b/applications/plugins/hid_app/hid.c @@ -9,10 +9,10 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexKeynote, HidSubmenuIndexKeyboard, HidSubmenuIndexMedia, - BtHidSubmenuIndexTikTok, + HidSubmenuIndexTikTok, HidSubmenuIndexMouse, + HidSubmenuIndexMouseJiggler, }; -typedef enum { ConnTypeSubmenuIndexBluetooth, ConnTypeSubmenuIndexUsb } ConnTypeDebugSubmenuIndex; static void hid_submenu_callback(void* context, uint32_t index) { furi_assert(context); @@ -29,9 +29,12 @@ static void hid_submenu_callback(void* context, uint32_t index) { } else if(index == HidSubmenuIndexMouse) { app->view_id = HidViewMouse; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouse); - } else if(index == BtHidSubmenuIndexTikTok) { + } else if(index == HidSubmenuIndexTikTok) { app->view_id = BtHidViewTikTok; view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewTikTok); + } else if(index == HidSubmenuIndexMouseJiggler) { + app->view_id = HidViewMouseJiggler; + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); } } @@ -48,6 +51,7 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con hid_keyboard_set_connected_status(hid->hid_keyboard, connected); hid_media_set_connected_status(hid->hid_media, connected); hid_mouse_set_connected_status(hid->hid_mouse, connected); + hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected); hid_tiktok_set_connected_status(hid->hid_tiktok, connected); } @@ -104,10 +108,16 @@ Hid* hid_alloc(HidTransport transport) { submenu_add_item( app->device_type_submenu, "TikTok Controller", - BtHidSubmenuIndexTikTok, + HidSubmenuIndexTikTok, hid_submenu_callback, app); } + submenu_add_item( + app->device_type_submenu, + "Mouse Jiggler", + HidSubmenuIndexMouseJiggler, + hid_submenu_callback, + app); view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); view_dispatcher_add_view( app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); @@ -160,6 +170,15 @@ Hid* hid_app_alloc_view(void* context) { view_dispatcher_add_view( app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); + // Mouse jiggler view + app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); + view_set_previous_callback( + hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_exit_confirm_view); + view_dispatcher_add_view( + app->view_dispatcher, + HidViewMouseJiggler, + hid_mouse_jiggler_get_view(app->hid_mouse_jiggler)); + return app; } @@ -182,6 +201,8 @@ void hid_free(Hid* app) { hid_media_free(app->hid_media); view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse); hid_mouse_free(app->hid_mouse); + view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler); + hid_mouse_jiggler_free(app->hid_mouse_jiggler); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); hid_tiktok_free(app->hid_tiktok); view_dispatcher_free(app->view_dispatcher); @@ -346,9 +367,17 @@ int32_t hid_ble_app(void* p) { Hid* app = hid_alloc(HidTransportBle); app = hid_app_alloc_view(app); + bt_disconnect(app->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_storage_path(app->bt, HID_BT_KEYS_STORAGE_PATH); + if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { - FURI_LOG_E(TAG, "Failed to switch profile"); + FURI_LOG_E(TAG, "Failed to switch to HID profile"); } + furi_hal_bt_start_advertising(); bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); @@ -357,7 +386,17 @@ int32_t hid_ble_app(void* p) { view_dispatcher_run(app->view_dispatcher); bt_set_status_changed_callback(app->bt, NULL, NULL); - bt_set_profile(app->bt, BtProfileSerial); + + bt_disconnect(app->bt); + + // Wait 2nd core to update nvm storage + furi_delay_ms(200); + + bt_keys_storage_set_default_path(app->bt); + + if(!bt_set_profile(app->bt, BtProfileSerial)) { + FURI_LOG_E(TAG, "Failed to switch to Serial profile"); + } hid_free(app); diff --git a/applications/plugins/hid_app/hid.h b/applications/plugins/hid_app/hid.h index 81ebcf56..fe32a199 100644 --- a/applications/plugins/hid_app/hid.h +++ b/applications/plugins/hid_app/hid.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -19,8 +20,11 @@ #include "views/hid_keyboard.h" #include "views/hid_media.h" #include "views/hid_mouse.h" +#include "views/hid_mouse_jiggler.h" #include "views/hid_tiktok.h" +#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys") + typedef enum { HidTransportUsb, HidTransportBle, @@ -39,6 +43,7 @@ struct Hid { HidKeyboard* hid_keyboard; HidMedia* hid_media; HidMouse* hid_mouse; + HidMouseJiggler* hid_mouse_jiggler; HidTikTok* hid_tiktok; HidTransport transport; diff --git a/applications/plugins/hid_app/views.h b/applications/plugins/hid_app/views.h index 68a827ad..2a44832e 100644 --- a/applications/plugins/hid_app/views.h +++ b/applications/plugins/hid_app/views.h @@ -4,6 +4,7 @@ typedef enum { HidViewKeyboard, HidViewMedia, HidViewMouse, + HidViewMouseJiggler, BtHidViewTikTok, HidViewExitConfirm, } HidView; \ No newline at end of file diff --git a/applications/plugins/hid_app/views/hid_mouse_jiggler.c b/applications/plugins/hid_app/views/hid_mouse_jiggler.c new file mode 100644 index 00000000..a2b07c7a --- /dev/null +++ b/applications/plugins/hid_app/views/hid_mouse_jiggler.c @@ -0,0 +1,149 @@ +#include "hid_mouse_jiggler.h" +#include +#include "../hid.h" + +#include "hid_icons.h" + +#define TAG "HidMouseJiggler" + +struct HidMouseJiggler { + View* view; + Hid* hid; + FuriTimer* timer; +}; + +typedef struct { + bool connected; + bool running; + uint8_t counter; +} HidMouseJigglerModel; + +static void hid_mouse_jiggler_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + HidMouseJigglerModel* model = context; + + // Header + if(model->connected) { + canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15); + } + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Jiggler"); + + canvas_set_font(canvas, FontPrimary); + elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto jiggle"); + canvas_set_font(canvas, FontSecondary); + + // Ok + canvas_draw_icon(canvas, 63, 25, &I_Space_65x18); + if(model->running) { + elements_slightly_rounded_box(canvas, 66, 27, 60, 13); + canvas_set_color(canvas, ColorWhite); + } + canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9); + if(model->running) { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop"); + } else { + elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start"); + } + canvas_set_color(canvas, ColorBlack); + + // Back + canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); + elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit"); +} + +static void hid_mouse_jiggler_timer_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { + if(model->running) { + model->counter++; + hid_hal_mouse_move( + hid_mouse_jiggler->hid, + (model->counter % 2 == 0) ? MOUSE_MOVE_SHORT : -MOUSE_MOVE_SHORT, + 0); + } + }, + false); +} + +static void hid_mouse_jiggler_enter_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + + furi_timer_start(hid_mouse_jiggler->timer, 500); +} + +static void hid_mouse_jiggler_exit_callback(void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + furi_timer_stop(hid_mouse_jiggler->timer); +} + +static bool hid_mouse_jiggler_input_callback(InputEvent* event, void* context) { + furi_assert(context); + HidMouseJiggler* hid_mouse_jiggler = context; + + bool consumed = false; + + if(event->key == InputKeyOk) { + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { model->running = !model->running; }, + true); + consumed = true; + } + + return consumed; +} + +HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* hid) { + HidMouseJiggler* hid_mouse_jiggler = malloc(sizeof(HidMouseJiggler)); + + hid_mouse_jiggler->view = view_alloc(); + view_set_context(hid_mouse_jiggler->view, hid_mouse_jiggler); + view_allocate_model( + hid_mouse_jiggler->view, ViewModelTypeLocking, sizeof(HidMouseJigglerModel)); + view_set_draw_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_draw_callback); + view_set_input_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_input_callback); + view_set_enter_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_enter_callback); + view_set_exit_callback(hid_mouse_jiggler->view, hid_mouse_jiggler_exit_callback); + + hid_mouse_jiggler->hid = hid; + + hid_mouse_jiggler->timer = furi_timer_alloc( + hid_mouse_jiggler_timer_callback, FuriTimerTypePeriodic, hid_mouse_jiggler); + + return hid_mouse_jiggler; +} + +void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + + furi_timer_stop(hid_mouse_jiggler->timer); + furi_timer_free(hid_mouse_jiggler->timer); + + view_free(hid_mouse_jiggler->view); + + free(hid_mouse_jiggler); +} + +View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler) { + furi_assert(hid_mouse_jiggler); + return hid_mouse_jiggler->view; +} + +void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected) { + furi_assert(hid_mouse_jiggler); + with_view_model( + hid_mouse_jiggler->view, + HidMouseJigglerModel * model, + { model->connected = connected; }, + true); +} diff --git a/applications/plugins/hid_app/views/hid_mouse_jiggler.h b/applications/plugins/hid_app/views/hid_mouse_jiggler.h new file mode 100644 index 00000000..0813b435 --- /dev/null +++ b/applications/plugins/hid_app/views/hid_mouse_jiggler.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#define MOUSE_MOVE_SHORT 5 +#define MOUSE_MOVE_LONG 20 + +typedef struct Hid Hid; +typedef struct HidMouseJiggler HidMouseJiggler; + +HidMouseJiggler* hid_mouse_jiggler_alloc(Hid* bt_hid); + +void hid_mouse_jiggler_free(HidMouseJiggler* hid_mouse_jiggler); + +View* hid_mouse_jiggler_get_view(HidMouseJiggler* hid_mouse_jiggler); + +void hid_mouse_jiggler_set_connected_status(HidMouseJiggler* hid_mouse_jiggler, bool connected); diff --git a/applications/plugins/music_player/music_player_worker.c b/applications/plugins/music_player/music_player_worker.c index 0d683f4a..60fd33a1 100644 --- a/applications/plugins/music_player/music_player_worker.c +++ b/applications/plugins/music_player/music_player_worker.c @@ -47,47 +47,51 @@ static int32_t music_player_worker_thread_callback(void* context) { NoteBlockArray_it_t it; NoteBlockArray_it(it, instance->notes); + if(furi_hal_speaker_acquire(1000)) { + while(instance->should_work) { + if(NoteBlockArray_end_p(it)) { + NoteBlockArray_it(it, instance->notes); + furi_delay_ms(10); + } else { + NoteBlock* note_block = NoteBlockArray_ref(it); - while(instance->should_work) { - if(NoteBlockArray_end_p(it)) { - NoteBlockArray_it(it, instance->notes); - furi_delay_ms(10); - } else { - NoteBlock* note_block = NoteBlockArray_ref(it); + float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE; + float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); + float duration = 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / + note_block->duration; + uint32_t dots = note_block->dots; + while(dots > 0) { + duration += duration / 2; + dots--; + } + uint32_t next_tick = furi_get_tick() + duration; + float volume = instance->volume; - float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE; - float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); - float duration = - 60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / note_block->duration; - uint32_t dots = note_block->dots; - while(dots > 0) { - duration += duration / 2; - dots--; + if(instance->callback) { + instance->callback( + note_block->semitone, + note_block->dots, + note_block->duration, + 0.0, + instance->callback_context); + } + + furi_hal_speaker_stop(); + furi_hal_speaker_start(frequency, volume); + while(instance->should_work && furi_get_tick() < next_tick) { + volume *= 0.9945679; + furi_hal_speaker_set_volume(volume); + furi_delay_ms(2); + } + NoteBlockArray_next(it); } - uint32_t next_tick = furi_get_tick() + duration; - float volume = instance->volume; - - if(instance->callback) { - instance->callback( - note_block->semitone, - note_block->dots, - note_block->duration, - 0.0, - instance->callback_context); - } - - furi_hal_speaker_stop(); - furi_hal_speaker_start(frequency, volume); - while(instance->should_work && furi_get_tick() < next_tick) { - volume *= 0.9945679; - furi_hal_speaker_set_volume(volume); - furi_delay_ms(2); - } - NoteBlockArray_next(it); } - } - furi_hal_speaker_stop(); + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } else { + FURI_LOG_E(TAG, "Speaker system is busy with another process."); + } return 0; } diff --git a/applications/plugins/nfc_magic/nfc_magic.c b/applications/plugins/nfc_magic/nfc_magic.c index 38eecba6..e4e0ffde 100644 --- a/applications/plugins/nfc_magic/nfc_magic.c +++ b/applications/plugins/nfc_magic/nfc_magic.c @@ -49,6 +49,7 @@ NfcMagic* nfc_magic_alloc() { // Nfc device nfc_magic->nfc_dev = nfc_device_alloc(); + furi_string_set(nfc_magic->nfc_dev->folder, NFC_APP_FOLDER); // Open GUI record nfc_magic->gui = furi_record_open(RECORD_GUI); diff --git a/applications/plugins/nfc_magic/nfc_magic_i.h b/applications/plugins/nfc_magic/nfc_magic_i.h index 01b30082..378912e5 100644 --- a/applications/plugins/nfc_magic/nfc_magic_i.h +++ b/applications/plugins/nfc_magic/nfc_magic_i.h @@ -27,6 +27,8 @@ #include #include "nfc_magic_icons.h" +#define NFC_APP_FOLDER ANY_PATH("nfc") + enum NfcMagicCustomEvent { // Reserve first 100 events for button types and indexes, starting from 0 NfcMagicCustomEventReserved = 100, diff --git a/applications/plugins/weather_station/helpers/weather_station_types.h b/applications/plugins/weather_station/helpers/weather_station_types.h index a23540e3..d512251f 100644 --- a/applications/plugins/weather_station/helpers/weather_station_types.h +++ b/applications/plugins/weather_station/helpers/weather_station_types.h @@ -3,7 +3,7 @@ #include #include -#define WS_VERSION_APP "0.5" +#define WS_VERSION_APP "0.6.1" #define WS_DEVELOPED "SkorP" #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware" diff --git a/applications/plugins/weather_station/images/Humid_8x13.png b/applications/plugins/weather_station/images/Humid_8x13.png new file mode 100644 index 00000000..6d8c71b0 Binary files /dev/null and b/applications/plugins/weather_station/images/Humid_8x13.png differ diff --git a/applications/plugins/weather_station/images/Timer_11x11.png b/applications/plugins/weather_station/images/Timer_11x11.png new file mode 100644 index 00000000..21ad47f4 Binary files /dev/null and b/applications/plugins/weather_station/images/Timer_11x11.png differ diff --git a/applications/plugins/weather_station/protocols/ambient_weather.c b/applications/plugins/weather_station/protocols/ambient_weather.c index 5ae22b79..e3c85f40 100644 --- a/applications/plugins/weather_station/protocols/ambient_weather.c +++ b/applications/plugins/weather_station/protocols/ambient_weather.c @@ -134,8 +134,8 @@ static void ws_protocol_ambient_weather_remote_controller(WSBlockGeneric* instan instance->id = (instance->data >> 32) & 0xFF; instance->battery_low = (instance->data >> 31) & 1; instance->channel = ((instance->data >> 28) & 0x07) + 1; - instance->temp = ws_block_generic_fahrenheit_to_celsius( - ((float)((instance->data >> 16) & 0x0FFF) - 400.0f) / 10.0f); + instance->temp = + locale_fahrenheit_to_celsius(((float)((instance->data >> 16) & 0x0FFF) - 400.0f) / 10.0f); instance->humidity = (instance->data >> 8) & 0xFF; instance->btn = WS_NO_BTN; diff --git a/applications/plugins/weather_station/protocols/infactory.c b/applications/plugins/weather_station/protocols/infactory.c index 2d444d98..53a656d7 100644 --- a/applications/plugins/weather_station/protocols/infactory.c +++ b/applications/plugins/weather_station/protocols/infactory.c @@ -143,8 +143,8 @@ static void ws_protocol_infactory_remote_controller(WSBlockGeneric* instance) { instance->id = instance->data >> 32; instance->battery_low = (instance->data >> 26) & 1; instance->btn = WS_NO_BTN; - instance->temp = ws_block_generic_fahrenheit_to_celsius( - ((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f); + instance->temp = + locale_fahrenheit_to_celsius(((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f); instance->humidity = (((instance->data >> 8) & 0x0F) * 10) + ((instance->data >> 4) & 0x0F); // BCD, 'A0'=100%rH instance->channel = instance->data & 0x03; diff --git a/applications/plugins/weather_station/protocols/nexus_th.c b/applications/plugins/weather_station/protocols/nexus_th.c index 7d4a77ae..38f2fe89 100644 --- a/applications/plugins/weather_station/protocols/nexus_th.c +++ b/applications/plugins/weather_station/protocols/nexus_th.c @@ -135,6 +135,10 @@ static void ws_protocol_nexus_th_remote_controller(WSBlockGeneric* instance) { } instance->humidity = instance->data & 0xFF; + if(instance->humidity > 95) + instance->humidity = 95; + else if(instance->humidity < 20) + instance->humidity = 20; } void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration) { diff --git a/applications/plugins/weather_station/protocols/oregon_v1.c b/applications/plugins/weather_station/protocols/oregon_v1.c new file mode 100644 index 00000000..d1cc4c7a --- /dev/null +++ b/applications/plugins/weather_station/protocols/oregon_v1.c @@ -0,0 +1,331 @@ +#include "oregon_v1.h" +#include + +#define TAG "WSProtocolOregon_V1" + +/* + * Help + * https://github.dev/merbanan/rtl_433/blob/bb1be7f186ac0fdb7dc5d77693847d96fb95281e/src/devices/oregon_scientific_v1.c + * + * OSv1 protocol. + * + * MC with nominal bit width of 2930 us. + * Pulses are somewhat longer than nominal half-bit width, 1748 us / 3216 us, + * Gaps are somewhat shorter than nominal half-bit width, 1176 us / 2640 us. + * After 12 preamble bits there is 4200 us gap, 5780 us pulse, 5200 us gap. + * And next 32 bit data + * + * Care must be taken with the gap after the sync pulse since it + * is outside of the normal clocking. Because of this a data stream + * beginning with a 0 will have data in this gap. + * + * + * Data is in reverse order of bits + * RevBit(data32bit)=> tib23atad + * + * tib23atad => xxxxxxxx | busuTTTT | ttttzzzz | ccuuiiii + * + * - i: ID + * - x: CRC; + * - u: unknown; + * - b: battery low; flag to indicate low battery voltage + * - s: temperature sign + * - T: BCD, Temperature; in °C * 10 + * - t: BCD, Temperature; in °C * 1 + * - z: BCD, Temperature; in °C * 0.1 + * - c: Channel 00=CH1, 01=CH2, 10=CH3 + * + */ + +#define OREGON_V1_HEADER_OK 0xFF + +static const SubGhzBlockConst ws_protocol_oregon_v1_const = { + .te_short = 1465, + .te_long = 2930, + .te_delta = 350, + .min_count_bit_for_found = 32, +}; + +struct WSProtocolDecoderOregon_V1 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_state; + uint16_t header_count; + uint8_t first_bit; +}; + +struct WSProtocolEncoderOregon_V1 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Oregon_V1DecoderStepReset = 0, + Oregon_V1DecoderStepFoundPreamble, + Oregon_V1DecoderStepParse, +} Oregon_V1DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_oregon_v1_decoder = { + .alloc = ws_protocol_decoder_oregon_v1_alloc, + .free = ws_protocol_decoder_oregon_v1_free, + + .feed = ws_protocol_decoder_oregon_v1_feed, + .reset = ws_protocol_decoder_oregon_v1_reset, + + .get_hash_data = ws_protocol_decoder_oregon_v1_get_hash_data, + .serialize = ws_protocol_decoder_oregon_v1_serialize, + .deserialize = ws_protocol_decoder_oregon_v1_deserialize, + .get_string = ws_protocol_decoder_oregon_v1_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_oregon_v1_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_oregon_v1 = { + .name = WS_PROTOCOL_OREGON_V1_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_oregon_v1_decoder, + .encoder = &ws_protocol_oregon_v1_encoder, +}; + +void* ws_protocol_decoder_oregon_v1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderOregon_V1* instance = malloc(sizeof(WSProtocolDecoderOregon_V1)); + instance->base.protocol = &ws_protocol_oregon_v1; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_oregon_v1_free(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + free(instance); +} + +void ws_protocol_decoder_oregon_v1_reset(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + instance->decoder.parser_step = Oregon_V1DecoderStepReset; +} + +static bool ws_protocol_oregon_v1_check(WSProtocolDecoderOregon_V1* instance) { + if(!instance->decoder.decode_data) return false; + uint64_t data = subghz_protocol_blocks_reverse_key(instance->decoder.decode_data, 32); + uint16_t crc = (data & 0xff) + ((data >> 8) & 0xff) + ((data >> 16) & 0xff); + crc = (crc & 0xff) + ((crc >> 8) & 0xff); + return (crc == ((data >> 24) & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_oregon_v1_remote_controller(WSBlockGeneric* instance) { + uint64_t data = subghz_protocol_blocks_reverse_key(instance->data, 32); + + instance->id = data & 0xFF; + instance->channel = ((data >> 6) & 0x03) + 1; + + float temp_raw = + ((data >> 8) & 0x0F) * 0.1f + ((data >> 12) & 0x0F) + ((data >> 16) & 0x0F) * 10.0f; + if(!((data >> 21) & 1)) { + instance->temp = temp_raw; + } else { + instance->temp = -temp_raw; + } + + instance->battery_low = !(instance->data >> 23) & 1; + + instance->btn = WS_NO_BTN; + instance->humidity = WS_NO_HUMIDITY; +} + +void ws_protocol_decoder_oregon_v1_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case Oregon_V1DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + instance->decoder.parser_step = Oregon_V1DecoderStepFoundPreamble; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case Oregon_V1DecoderStepFoundPreamble: + if(level) { + //keep high levels, if they suit our durations + if((DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) || + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short * 4) < + ws_protocol_oregon_v1_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } else if( + //checking low levels + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + // Found header + instance->header_count++; + } else if( + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short * 3) < + ws_protocol_oregon_v1_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + // check header + if(instance->header_count > 7) { + instance->header_count = OREGON_V1_HEADER_OK; + } + } else if( + (instance->header_count == OREGON_V1_HEADER_OK) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short * 4) < + ws_protocol_oregon_v1_const.te_delta)) { + //found all the necessary patterns + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); + instance->decoder.parser_step = Oregon_V1DecoderStepParse; + if(duration < ws_protocol_oregon_v1_const.te_short * 4) { + instance->first_bit = 1; + } else { + instance->first_bit = 0; + } + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + break; + case Oregon_V1DecoderStepParse: + if(level) { + if(DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_long) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_long) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventLongLow; + } else if(duration >= ((uint32_t)ws_protocol_oregon_v1_const.te_long * 2)) { + if(instance->decoder.decode_count_bit == + ws_protocol_oregon_v1_const.min_count_bit_for_found) { + if(instance->first_bit) { + instance->decoder.decode_data = ~instance->decoder.decode_data | (1 << 31); + } + if(ws_protocol_oregon_v1_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_oregon_v1_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_state, event, &instance->manchester_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + instance->decoder.decode_count_bit++; + } + } + + break; + } +} + +uint8_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_oregon_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_oregon_v1_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/oregon_v1.h b/applications/plugins/weather_station/protocols/oregon_v1.h new file mode 100644 index 00000000..c9aa5af4 --- /dev/null +++ b/applications/plugins/weather_station/protocols/oregon_v1.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_OREGON_V1_NAME "Oregon-v1" + +typedef struct WSProtocolDecoderOregon_V1 WSProtocolDecoderOregon_V1; +typedef struct WSProtocolEncoderOregon_V1 WSProtocolEncoderOregon_V1; + +extern const SubGhzProtocolDecoder ws_protocol_oregon_v1_decoder; +extern const SubGhzProtocolEncoder ws_protocol_oregon_v1_encoder; +extern const SubGhzProtocol ws_protocol_oregon_v1; + +/** + * Allocate WSProtocolDecoderOregon_V1. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderOregon_V1* pointer to a WSProtocolDecoderOregon_V1 instance + */ +void* ws_protocol_decoder_oregon_v1_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + */ +void ws_protocol_decoder_oregon_v1_free(void* context); + +/** + * Reset decoder WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + */ +void ws_protocol_decoder_oregon_v1_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_oregon_v1_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_oregon_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param output Resulting text + */ +void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/protocol_items.c b/applications/plugins/weather_station/protocols/protocol_items.c index 9ad8ce1a..99c8344f 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.c +++ b/applications/plugins/weather_station/protocols/protocol_items.c @@ -13,6 +13,8 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_acurite_592txr, &ws_protocol_ambient_weather, &ws_protocol_auriol_th, + &ws_protocol_oregon_v1, + &ws_protocol_tx_8300, }; const SubGhzProtocolRegistry weather_station_protocol_registry = { diff --git a/applications/plugins/weather_station/protocols/protocol_items.h b/applications/plugins/weather_station/protocols/protocol_items.h index 4fef8944..9d5d096f 100644 --- a/applications/plugins/weather_station/protocols/protocol_items.h +++ b/applications/plugins/weather_station/protocols/protocol_items.h @@ -13,5 +13,7 @@ #include "acurite_592txr.h" #include "ambient_weather.h" #include "auriol_hg0601a.h" +#include "oregon_v1.h" +#include "tx_8300.h" extern const SubGhzProtocolRegistry weather_station_protocol_registry; diff --git a/applications/plugins/weather_station/protocols/tx_8300.c b/applications/plugins/weather_station/protocols/tx_8300.c new file mode 100644 index 00000000..ee0412ba --- /dev/null +++ b/applications/plugins/weather_station/protocols/tx_8300.c @@ -0,0 +1,293 @@ +#include "tx_8300.h" + +#define TAG "WSProtocolTX_8300" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/ambientweather_tx8300.c + * + * Ambient Weather TX-8300 (also sold as TFA 30.3211.02). + * 1970us pulse with variable gap (third pulse 3920 us). + * Above 79% humidity, gap after third pulse is 5848 us. + * - Bit 1 : 1970us pulse with 3888 us gap + * - Bit 0 : 1970us pulse with 1936 us gap + * 74 bit (2 bit preamble and 72 bit data => 9 bytes => 18 nibbles) + * The preamble seems to be a repeat counter (00, and 01 seen), + * the first 4 bytes are data, + * the second 4 bytes the same data inverted, + * the last byte is a checksum. + * Preamble format (2 bits): + * [1 bit (0)] [1 bit rolling count] + * Payload format (32 bits): + * HHHHhhhh ??CCNIII IIIITTTT ttttuuuu + * - H = First BCD digit humidity (the MSB might be distorted by the demod) + * - h = Second BCD digit humidity, invalid humidity seems to be 0x0e + * - ? = Likely battery flag, 2 bits + * - C = Channel, 2 bits + * - N = Negative temperature sign bit + * - I = ID, 7-bit + * - T = First BCD digit temperature + * - t = Second BCD digit temperature + * - u = Third BCD digit temperature + * The Checksum seems to covers the 4 data bytes and is something like Fletcher-8. + **/ + +#define TX_8300_PACKAGE_SIZE 32 + +static const SubGhzBlockConst ws_protocol_tx_8300_const = { + .te_short = 1940, + .te_long = 3880, + .te_delta = 250, + .min_count_bit_for_found = 72, +}; + +struct WSProtocolDecoderTX_8300 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + uint32_t package_1; + uint32_t package_2; +}; + +struct WSProtocolEncoderTX_8300 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + TX_8300DecoderStepReset = 0, + TX_8300DecoderStepCheckPreambule, + TX_8300DecoderStepSaveDuration, + TX_8300DecoderStepCheckDuration, +} TX_8300DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_tx_8300_decoder = { + .alloc = ws_protocol_decoder_tx_8300_alloc, + .free = ws_protocol_decoder_tx_8300_free, + + .feed = ws_protocol_decoder_tx_8300_feed, + .reset = ws_protocol_decoder_tx_8300_reset, + + .get_hash_data = ws_protocol_decoder_tx_8300_get_hash_data, + .serialize = ws_protocol_decoder_tx_8300_serialize, + .deserialize = ws_protocol_decoder_tx_8300_deserialize, + .get_string = ws_protocol_decoder_tx_8300_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_tx_8300_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_tx_8300 = { + .name = WS_PROTOCOL_TX_8300_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_tx_8300_decoder, + .encoder = &ws_protocol_tx_8300_encoder, +}; + +void* ws_protocol_decoder_tx_8300_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderTX_8300* instance = malloc(sizeof(WSProtocolDecoderTX_8300)); + instance->base.protocol = &ws_protocol_tx_8300; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_tx_8300_free(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + free(instance); +} + +void ws_protocol_decoder_tx_8300_reset(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + instance->decoder.parser_step = TX_8300DecoderStepReset; +} + +static bool ws_protocol_tx_8300_check_crc(WSProtocolDecoderTX_8300* instance) { + if(!instance->package_2) return false; + if(instance->package_1 != ~instance->package_2) return false; + + uint16_t x = 0; + uint16_t y = 0; + for(int i = 0; i < 32; i += 4) { + x += (instance->package_1 >> i) & 0x0F; + y += (instance->package_1 >> i) & 0x05; + } + uint8_t crc = (~x & 0xF) << 4 | (~y & 0xF); + return (crc == ((instance->decoder.decode_data) & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_tx_8300_remote_controller(WSBlockGeneric* instance) { + instance->humidity = (((instance->data >> 28) & 0x0F) * 10) + ((instance->data >> 24) & 0x0F); + instance->btn = WS_NO_BTN; + if(!((instance->data >> 22) & 0x03)) + instance->battery_low = 0; + else + instance->battery_low = 1; + instance->channel = (instance->data >> 20) & 0x03; + instance->id = (instance->data >> 12) & 0x7F; + + float temp_raw = ((instance->data >> 8) & 0x0F) * 10.0f + ((instance->data >> 4) & 0x0F) + + (instance->data & 0x0F) * 0.1f; + if(!((instance->data >> 19) & 1)) { + instance->temp = temp_raw; + } else { + instance->temp = -temp_raw; + } +} + +void ws_protocol_decoder_tx_8300_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + + switch(instance->decoder.parser_step) { + case TX_8300DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 2) < + ws_protocol_tx_8300_const.te_delta)) { + instance->decoder.parser_step = TX_8300DecoderStepCheckPreambule; + } + break; + + case TX_8300DecoderStepCheckPreambule: + if((!level) && ((DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 2) < + ws_protocol_tx_8300_const.te_delta) || + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 3) < + ws_protocol_tx_8300_const.te_delta))) { + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + instance->package_1 = 0; + instance->package_2 = 0; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + + case TX_8300DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = TX_8300DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + + case TX_8300DecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_tx_8300_const.te_short * 5)) { + //Found syncPostfix + if((instance->decoder.decode_count_bit == + ws_protocol_tx_8300_const.min_count_bit_for_found) && + ws_protocol_tx_8300_check_crc(instance)) { + instance->generic.data = instance->package_1; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_tx_8300_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + instance->decoder.parser_step = TX_8300DecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_long) < + ws_protocol_tx_8300_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + + if(instance->decoder.decode_count_bit == TX_8300_PACKAGE_SIZE) { + instance->package_1 = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } else if(instance->decoder.decode_count_bit == TX_8300_PACKAGE_SIZE * 2) { + instance->package_2 = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_tx_8300_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool ws_protocol_decoder_tx_8300_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +bool ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + bool ret = false; + do { + if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + if(instance->generic.data_count_bit != ws_protocol_tx_8300_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + ret = true; + } while(false); + return ret; +} + +void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/applications/plugins/weather_station/protocols/tx_8300.h b/applications/plugins/weather_station/protocols/tx_8300.h new file mode 100644 index 00000000..ec198e80 --- /dev/null +++ b/applications/plugins/weather_station/protocols/tx_8300.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_TX_8300_NAME "TX8300" + +typedef struct WSProtocolDecoderTX_8300 WSProtocolDecoderTX_8300; +typedef struct WSProtocolEncoderTX_8300 WSProtocolEncoderTX_8300; + +extern const SubGhzProtocolDecoder ws_protocol_tx_8300_decoder; +extern const SubGhzProtocolEncoder ws_protocol_tx_8300_encoder; +extern const SubGhzProtocol ws_protocol_tx_8300; + +/** + * Allocate WSProtocolDecoderTX_8300. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderTX_8300* pointer to a WSProtocolDecoderTX_8300 instance + */ +void* ws_protocol_decoder_tx_8300_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + */ +void ws_protocol_decoder_tx_8300_free(void* context); + +/** + * Reset decoder WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + */ +void ws_protocol_decoder_tx_8300_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_tx_8300_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_tx_8300_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool ws_protocol_decoder_tx_8300_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param output Resulting text + */ +void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output); diff --git a/applications/plugins/weather_station/protocols/ws_generic.c b/applications/plugins/weather_station/protocols/ws_generic.c index 17453109..dcacda2e 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.c +++ b/applications/plugins/weather_station/protocols/ws_generic.c @@ -99,6 +99,17 @@ bool ws_block_generic_serialize( break; } + //DATE AGE set + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + + temp_data = curr_ts; + if(!flipper_format_write_uint32(flipper_format, "Ts", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add timestamp"); + break; + } + temp_data = instance->channel; if(!flipper_format_write_uint32(flipper_format, "Ch", &temp_data, 1)) { FURI_LOG_E(TAG, "Unable to add Channel"); @@ -168,6 +179,12 @@ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipp } instance->humidity = (uint8_t)temp_data; + if(!flipper_format_read_uint32(flipper_format, "Ts", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing timestamp"); + break; + } + instance->timestamp = (uint32_t)temp_data; + if(!flipper_format_read_uint32(flipper_format, "Ch", (uint32_t*)&temp_data, 1)) { FURI_LOG_E(TAG, "Missing Channel"); break; @@ -192,7 +209,3 @@ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipp return res; } - -float ws_block_generic_fahrenheit_to_celsius(float fahrenheit) { - return (fahrenheit - 32.0f) / 1.8f; -} \ No newline at end of file diff --git a/applications/plugins/weather_station/protocols/ws_generic.h b/applications/plugins/weather_station/protocols/ws_generic.h index b2a84df8..8e6e061a 100644 --- a/applications/plugins/weather_station/protocols/ws_generic.h +++ b/applications/plugins/weather_station/protocols/ws_generic.h @@ -8,6 +8,7 @@ #include "furi.h" #include "furi_hal.h" #include +#include #ifdef __cplusplus extern "C" { @@ -29,6 +30,7 @@ struct WSBlockGeneric { uint8_t data_count_bit; uint8_t battery_low; uint8_t humidity; + uint32_t timestamp; uint8_t channel; uint8_t btn; float temp; @@ -61,8 +63,6 @@ bool ws_block_generic_serialize( */ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format); -float ws_block_generic_fahrenheit_to_celsius(float fahrenheit); - #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/applications/plugins/weather_station/views/weather_station_receiver.c b/applications/plugins/weather_station/views/weather_station_receiver.c index 61b15260..124065e3 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver.c +++ b/applications/plugins/weather_station/views/weather_station_receiver.c @@ -8,7 +8,7 @@ #include #define FRAME_HEIGHT 12 -#define MAX_LEN_PX 100 +#define MAX_LEN_PX 112 #define MENU_ITEMS 4u #define UNLOCK_CNT 3 @@ -189,7 +189,7 @@ void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) { canvas_set_color(canvas, ColorBlack); } canvas_draw_icon(canvas, 4, 2 + i * FRAME_HEIGHT, ReceiverItemIcons[item_menu->type]); - canvas_draw_str(canvas, 15, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); + canvas_draw_str(canvas, 14, 9 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buff)); furi_string_reset(str_buff); } if(scrollbar) { diff --git a/applications/plugins/weather_station/views/weather_station_receiver_info.c b/applications/plugins/weather_station/views/weather_station_receiver_info.c index 49b447f1..55d239aa 100644 --- a/applications/plugins/weather_station/views/weather_station_receiver_info.c +++ b/applications/plugins/weather_station/views/weather_station_receiver_info.c @@ -9,9 +9,11 @@ struct WSReceiverInfo { View* view; + FuriTimer* timer; }; typedef struct { + uint32_t curr_ts; FuriString* protocol_name; WSBlockGeneric* generic; } WSReceiverInfoModel; @@ -28,6 +30,10 @@ void ws_view_receiver_info_update(WSReceiverInfo* ws_receiver_info, FlipperForma flipper_format_read_string(fff, "Protocol", model->protocol_name); ws_block_generic_deserialize(model->generic, fff); + + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + model->curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); }, true); } @@ -44,46 +50,102 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { "%s %db", furi_string_get_cstr(model->protocol_name), model->generic->data_count_bit); - canvas_draw_str(canvas, 5, 8, buffer); + canvas_draw_str(canvas, 0, 8, buffer); if(model->generic->channel != WS_NO_CHANNEL) { snprintf(buffer, sizeof(buffer), "Ch: %01d", model->generic->channel); - canvas_draw_str(canvas, 105, 8, buffer); + canvas_draw_str(canvas, 106, 8, buffer); } if(model->generic->id != WS_NO_ID) { snprintf(buffer, sizeof(buffer), "Sn: 0x%02lX", model->generic->id); - canvas_draw_str(canvas, 5, 20, buffer); + canvas_draw_str(canvas, 0, 20, buffer); } if(model->generic->btn != WS_NO_BTN) { snprintf(buffer, sizeof(buffer), "Btn: %01d", model->generic->btn); - canvas_draw_str(canvas, 62, 20, buffer); + canvas_draw_str(canvas, 57, 20, buffer); } if(model->generic->battery_low != WS_NO_BATT) { snprintf( buffer, sizeof(buffer), "Batt: %s", (!model->generic->battery_low ? "ok" : "low")); - canvas_draw_str(canvas, 90, 20, buffer); + canvas_draw_str_aligned(canvas, 126, 17, AlignRight, AlignCenter, buffer); } snprintf(buffer, sizeof(buffer), "Data: 0x%llX", model->generic->data); - canvas_draw_str(canvas, 5, 32, buffer); + canvas_draw_str(canvas, 0, 32, buffer); - elements_bold_rounded_frame(canvas, 2, 37, 123, 25); + elements_bold_rounded_frame(canvas, 0, 38, 127, 25); canvas_set_font(canvas, FontPrimary); if(model->generic->temp != WS_NO_TEMPERATURE) { - canvas_draw_icon(canvas, 18, 42, &I_Therm_7x16); - snprintf(buffer, sizeof(buffer), "%3.1f C", (double)model->generic->temp); - canvas_draw_str_aligned(canvas, 63, 46, AlignRight, AlignTop, buffer); - canvas_draw_circle(canvas, 55, 45, 1); + canvas_draw_icon(canvas, 6, 43, &I_Therm_7x16); + + uint8_t temp_x1 = 0; + uint8_t temp_x2 = 0; + if(furi_hal_rtc_get_locale_units() == FuriHalRtcLocaleUnitsMetric) { + snprintf(buffer, sizeof(buffer), "%3.1f C", (double)model->generic->temp); + if(model->generic->temp < -9.0f) { + temp_x1 = 49; + temp_x2 = 40; + } else { + temp_x1 = 47; + temp_x2 = 38; + } + } else { + snprintf( + buffer, + sizeof(buffer), + "%3.1f F", + (double)locale_celsius_to_fahrenheit(model->generic->temp)); + if((model->generic->temp < -27.77f) || (model->generic->temp > 37.77f)) { + temp_x1 = 50; + temp_x2 = 42; + } else { + temp_x1 = 48; + temp_x2 = 40; + } + } + + canvas_draw_str_aligned(canvas, temp_x1, 47, AlignRight, AlignTop, buffer); + canvas_draw_circle(canvas, temp_x2, 46, 1); } if(model->generic->humidity != WS_NO_HUMIDITY) { - canvas_draw_icon(canvas, 75, 42, &I_Humid_10x15); + canvas_draw_icon(canvas, 53, 44, &I_Humid_8x13); snprintf(buffer, sizeof(buffer), "%d%%", model->generic->humidity); - canvas_draw_str(canvas, 91, 54, buffer); + canvas_draw_str(canvas, 64, 55, buffer); + } + + if((int)model->generic->timestamp > 0 && model->curr_ts) { + int ts_diff = (int)model->curr_ts - (int)model->generic->timestamp; + + canvas_draw_icon(canvas, 91, 46, &I_Timer_11x11); + + if(ts_diff > 60) { + int tmp_sec = ts_diff; + int cnt_min = 1; + for(int i = 1; tmp_sec > 60; i++) { + tmp_sec = tmp_sec - 60; + cnt_min = i; + } + + if(model->curr_ts % 2 == 0) { + canvas_draw_str_aligned(canvas, 105, 51, AlignLeft, AlignCenter, "Old"); + } else { + if(cnt_min >= 59) { + canvas_draw_str_aligned(canvas, 105, 51, AlignLeft, AlignCenter, "Old"); + } else { + snprintf(buffer, sizeof(buffer), "%dm", cnt_min); + canvas_draw_str_aligned(canvas, 114, 51, AlignCenter, AlignCenter, buffer); + } + } + + } else { + snprintf(buffer, sizeof(buffer), "%d", ts_diff); + canvas_draw_str_aligned(canvas, 112, 51, AlignCenter, AlignCenter, buffer); + } } } @@ -98,14 +160,19 @@ bool ws_view_receiver_info_input(InputEvent* event, void* context) { return true; } -void ws_view_receiver_info_enter(void* context) { - furi_assert(context); -} - -void ws_view_receiver_info_exit(void* context) { +static void ws_view_receiver_info_enter(void* context) { furi_assert(context); WSReceiverInfo* ws_receiver_info = context; + furi_timer_start(ws_receiver_info->timer, 1000); +} + +static void ws_view_receiver_info_exit(void* context) { + furi_assert(context); + WSReceiverInfo* ws_receiver_info = context; + + furi_timer_stop(ws_receiver_info->timer); + with_view_model( ws_receiver_info->view, WSReceiverInfoModel * model, @@ -113,6 +180,20 @@ void ws_view_receiver_info_exit(void* context) { false); } +static void ws_view_receiver_info_timer(void* context) { + WSReceiverInfo* ws_receiver_info = context; + // Force redraw + with_view_model( + ws_receiver_info->view, + WSReceiverInfoModel * model, + { + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + model->curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + }, + true); +} + WSReceiverInfo* ws_view_receiver_info_alloc() { WSReceiverInfo* ws_receiver_info = malloc(sizeof(WSReceiverInfo)); @@ -135,12 +216,17 @@ WSReceiverInfo* ws_view_receiver_info_alloc() { }, true); + ws_receiver_info->timer = + furi_timer_alloc(ws_view_receiver_info_timer, FuriTimerTypePeriodic, ws_receiver_info); + return ws_receiver_info; } void ws_view_receiver_info_free(WSReceiverInfo* ws_receiver_info) { furi_assert(ws_receiver_info); + furi_timer_free(ws_receiver_info->timer); + with_view_model( ws_receiver_info->view, WSReceiverInfoModel * model, diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 62b5ab10..024cb6e5 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -117,6 +117,8 @@ Bt* bt_alloc() { if(!bt_settings_load(&bt->bt_settings)) { bt_settings_save(&bt->bt_settings); } + // Keys storage + bt->keys_storage = bt_keys_storage_alloc(BT_KEYS_STORAGE_PATH); // Alloc queue bt->message_queue = furi_message_queue_alloc(8, sizeof(BtMessage)); @@ -285,8 +287,10 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) { furi_assert(context); Bt* bt = context; - FURI_LOG_I(TAG, "Changed addr start: %p, size changed: %d", addr, size); - BtMessage message = {.type = BtMessageTypeKeysStorageUpdated}; + BtMessage message = { + .type = BtMessageTypeKeysStorageUpdated, + .data.key_storage_data.start_address = addr, + .data.key_storage_data.size = size}; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } @@ -331,6 +335,8 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { furi_profile = FuriHalBtProfileSerial; } + bt_keys_storage_load(bt->keys_storage); + if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) { FURI_LOG_I(TAG, "Bt App started"); if(bt->bt_settings.enabled) { @@ -358,6 +364,7 @@ static void bt_change_profile(Bt* bt, BtMessage* message) { static void bt_close_connection(Bt* bt) { bt_close_rpc_connection(bt); + furi_hal_bt_stop_advertising(); furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT); } @@ -372,8 +379,8 @@ int32_t bt_srv(void* p) { return 0; } - // Read keys - if(!bt_keys_storage_load(bt)) { + // Load keys + if(!bt_keys_storage_load(bt->keys_storage)) { FURI_LOG_W(TAG, "Failed to load bonding keys"); } @@ -418,13 +425,16 @@ int32_t bt_srv(void* p) { // Display PIN code bt_pin_code_show(bt, message.data.pin_code); } else if(message.type == BtMessageTypeKeysStorageUpdated) { - bt_keys_storage_save(bt); + bt_keys_storage_update( + bt->keys_storage, + message.data.key_storage_data.start_address, + message.data.key_storage_data.size); } else if(message.type == BtMessageTypeSetProfile) { bt_change_profile(bt, &message); } else if(message.type == BtMessageTypeDisconnect) { bt_close_connection(bt); } else if(message.type == BtMessageTypeForgetBondedDevices) { - bt_keys_storage_delete(bt); + bt_keys_storage_delete(bt->keys_storage); } } return 0; diff --git a/applications/services/bt/bt_service/bt.h b/applications/services/bt/bt_service/bt.h index 6e4e1b82..ca47936d 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -56,6 +56,19 @@ void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, vo */ void bt_forget_bonded_devices(Bt* bt); +/** Set keys storage file path + * + * @param bt Bt instance + * @param keys_storage_path Path to file with saved keys + */ +void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path); + +/** Set default keys storage file path + * + * @param bt Bt instance + */ +void bt_keys_storage_set_default_path(Bt* bt); + #ifdef __cplusplus } #endif diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c index 3de896d5..e3cf78cc 100644 --- a/applications/services/bt/bt_service/bt_api.c +++ b/applications/services/bt/bt_service/bt_api.c @@ -39,3 +39,18 @@ void bt_forget_bonded_devices(Bt* bt) { furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } + +void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path) { + furi_assert(bt); + furi_assert(bt->keys_storage); + furi_assert(keys_storage_path); + + bt_keys_storage_set_file_path(bt->keys_storage, keys_storage_path); +} + +void bt_keys_storage_set_default_path(Bt* bt) { + furi_assert(bt); + furi_assert(bt->keys_storage); + + bt_keys_storage_set_file_path(bt->keys_storage, BT_KEYS_STORAGE_PATH); +} diff --git a/applications/services/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h index 5769243e..c8a0e996 100644 --- a/applications/services/bt/bt_service/bt_i.h +++ b/applications/services/bt/bt_service/bt_i.h @@ -13,8 +13,14 @@ #include #include #include +#include #include +#include + +#include "bt_keys_filename.h" + +#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME) #define BT_API_UNLOCK_EVENT (1UL << 0) @@ -29,10 +35,16 @@ typedef enum { BtMessageTypeForgetBondedDevices, } BtMessageType; +typedef struct { + uint8_t* start_address; + uint16_t size; +} BtKeyStorageUpdateData; + typedef union { uint32_t pin_code; uint8_t battery_level; BtProfile profile; + BtKeyStorageUpdateData key_storage_data; } BtMessageData; typedef struct { @@ -46,6 +58,7 @@ struct Bt { uint16_t bt_keys_size; uint16_t max_packet_size; BtSettings bt_settings; + BtKeysStorage* keys_storage; BtStatus status; BtProfile profile; FuriMessageQueue* message_queue; diff --git a/applications/services/bt/bt_service/bt_keys_storage.c b/applications/services/bt/bt_service/bt_keys_storage.c index 91d97d67..7cff9994 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.c +++ b/applications/services/bt/bt_service/bt_keys_storage.c @@ -1,49 +1,24 @@ #include "bt_keys_storage.h" #include +#include #include #include -#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME) #define BT_KEYS_STORAGE_VERSION (0) #define BT_KEYS_STORAGE_MAGIC (0x18) -bool bt_keys_storage_load(Bt* bt) { - furi_assert(bt); - bool file_loaded = false; +#define TAG "BtKeyStorage" - furi_hal_bt_get_key_storage_buff(&bt->bt_keys_addr_start, &bt->bt_keys_size); - furi_hal_bt_nvm_sram_sem_acquire(); - file_loaded = saved_struct_load( - BT_KEYS_STORAGE_PATH, - bt->bt_keys_addr_start, - bt->bt_keys_size, - BT_KEYS_STORAGE_MAGIC, - BT_KEYS_STORAGE_VERSION); - furi_hal_bt_nvm_sram_sem_release(); +struct BtKeysStorage { + uint8_t* nvm_sram_buff; + uint16_t nvm_sram_buff_size; + FuriString* file_path; +}; - return file_loaded; -} +bool bt_keys_storage_delete(BtKeysStorage* instance) { + furi_assert(instance); -bool bt_keys_storage_save(Bt* bt) { - furi_assert(bt); - furi_assert(bt->bt_keys_addr_start); - bool file_saved = false; - - furi_hal_bt_nvm_sram_sem_acquire(); - file_saved = saved_struct_save( - BT_KEYS_STORAGE_PATH, - bt->bt_keys_addr_start, - bt->bt_keys_size, - BT_KEYS_STORAGE_MAGIC, - BT_KEYS_STORAGE_VERSION); - furi_hal_bt_nvm_sram_sem_release(); - - return file_saved; -} - -bool bt_keys_storage_delete(Bt* bt) { - furi_assert(bt); bool delete_succeed = false; bool bt_is_active = furi_hal_bt_is_active(); @@ -55,3 +30,117 @@ bool bt_keys_storage_delete(Bt* bt) { return delete_succeed; } + +BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path) { + furi_assert(keys_storage_path); + + BtKeysStorage* instance = malloc(sizeof(BtKeysStorage)); + // Set default nvm ram parameters + furi_hal_bt_get_key_storage_buff(&instance->nvm_sram_buff, &instance->nvm_sram_buff_size); + // Set key storage file + instance->file_path = furi_string_alloc(); + furi_string_set_str(instance->file_path, keys_storage_path); + + return instance; +} + +void bt_keys_storage_free(BtKeysStorage* instance) { + furi_assert(instance); + + furi_string_free(instance->file_path); + free(instance); +} + +void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path) { + furi_assert(instance); + furi_assert(path); + + furi_string_set_str(instance->file_path, path); +} + +void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size) { + furi_assert(instance); + furi_assert(buff); + + instance->nvm_sram_buff = buff; + instance->nvm_sram_buff_size = size; +} + +bool bt_keys_storage_load(BtKeysStorage* instance) { + furi_assert(instance); + + bool loaded = false; + do { + // Get payload size + size_t payload_size = 0; + if(!saved_struct_get_payload_size( + furi_string_get_cstr(instance->file_path), + BT_KEYS_STORAGE_MAGIC, + BT_KEYS_STORAGE_VERSION, + &payload_size)) { + FURI_LOG_E(TAG, "Failed to read payload size"); + break; + } + + if(payload_size > instance->nvm_sram_buff_size) { + FURI_LOG_E(TAG, "Saved data doesn't fit ram buffer"); + break; + } + + // Load saved data to ram + furi_hal_bt_nvm_sram_sem_acquire(); + bool data_loaded = saved_struct_load( + furi_string_get_cstr(instance->file_path), + instance->nvm_sram_buff, + payload_size, + BT_KEYS_STORAGE_MAGIC, + BT_KEYS_STORAGE_VERSION); + furi_hal_bt_nvm_sram_sem_release(); + if(!data_loaded) { + FURI_LOG_E(TAG, "Failed to load struct"); + break; + } + + loaded = true; + } while(false); + + return loaded; +} + +bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size) { + furi_assert(instance); + furi_assert(start_addr); + + bool updated = false; + + FURI_LOG_I( + TAG, + "Base address: %p. Start update address: %p. Size changed: %ld", + (void*)instance->nvm_sram_buff, + start_addr, + size); + + do { + size_t new_size = start_addr - instance->nvm_sram_buff + size; + if(new_size > instance->nvm_sram_buff_size) { + FURI_LOG_E(TAG, "NVM RAM buffer overflow"); + break; + } + + furi_hal_bt_nvm_sram_sem_acquire(); + bool data_updated = saved_struct_save( + furi_string_get_cstr(instance->file_path), + instance->nvm_sram_buff, + new_size, + BT_KEYS_STORAGE_MAGIC, + BT_KEYS_STORAGE_VERSION); + furi_hal_bt_nvm_sram_sem_release(); + if(!data_updated) { + FURI_LOG_E(TAG, "Failed to update key storage"); + break; + } + updated = true; + } while(false); + + return updated; +} diff --git a/applications/services/bt/bt_service/bt_keys_storage.h b/applications/services/bt/bt_service/bt_keys_storage.h index b82b1035..cb808ca3 100644 --- a/applications/services/bt/bt_service/bt_keys_storage.h +++ b/applications/services/bt/bt_service/bt_keys_storage.h @@ -1,10 +1,20 @@ #pragma once -#include "bt_i.h" -#include "bt_keys_filename.h" +#include +#include -bool bt_keys_storage_load(Bt* bt); +typedef struct BtKeysStorage BtKeysStorage; -bool bt_keys_storage_save(Bt* bt); +BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path); -bool bt_keys_storage_delete(Bt* bt); +void bt_keys_storage_free(BtKeysStorage* instance); + +void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path); + +void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size); + +bool bt_keys_storage_load(BtKeysStorage* instance); + +bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size); + +bool bt_keys_storage_delete(BtKeysStorage* instance); diff --git a/applications/services/desktop/animations/animation_manager.c b/applications/services/desktop/animations/animation_manager.c index 36c5b397..9c22d131 100644 --- a/applications/services/desktop/animations/animation_manager.c +++ b/applications/services/desktop/animations/animation_manager.c @@ -52,6 +52,7 @@ struct AnimationManager { FuriString* freezed_animation_name; int32_t freezed_animation_time_left; ViewStack* view_stack; + bool dummy_mode; }; static StorageAnimation* @@ -93,6 +94,12 @@ void animation_manager_set_interact_callback( animation_manager->interact_callback = callback; } +void animation_manager_set_dummy_mode_state(AnimationManager* animation_manager, bool enabled) { + furi_assert(animation_manager); + animation_manager->dummy_mode = enabled; + animation_manager_start_new_idle(animation_manager); +} + static void animation_manager_check_blocking_callback(const void* message, void* context) { const StorageEvent* storage_event = message; @@ -363,7 +370,9 @@ static bool animation_manager_is_valid_idle_animation( static StorageAnimation* animation_manager_select_idle_animation(AnimationManager* animation_manager) { - UNUSED(animation_manager); + if(animation_manager->dummy_mode) { + return animation_storage_find_animation(HARDCODED_ANIMATION_NAME); + } StorageAnimationList_t animation_list; StorageAnimationList_init(animation_list); animation_storage_fill_animation_list(&animation_list); diff --git a/applications/services/desktop/animations/animation_manager.h b/applications/services/desktop/animations/animation_manager.h index 234d20de..a3dcc882 100644 --- a/applications/services/desktop/animations/animation_manager.h +++ b/applications/services/desktop/animations/animation_manager.h @@ -157,3 +157,11 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma * @animation_manager instance */ void animation_manager_load_and_continue_animation(AnimationManager* animation_manager); + +/** + * Enable or disable dummy mode backgrounds of animation manager. + * + * @animation_manager instance + * @enabled bool + */ +void animation_manager_set_dummy_mode_state(AnimationManager* animation_manager, bool enabled); diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index b45a9d62..848f5cb6 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -144,6 +144,7 @@ void desktop_unlock(Desktop* desktop) { void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { view_port_enabled_set(desktop->dummy_mode_icon_viewport, enabled); desktop_main_set_dummy_mode_state(desktop->main_view, enabled); + animation_manager_set_dummy_mode_state(desktop->animation_manager, enabled); desktop->settings.dummy_mode = enabled; DESKTOP_SETTINGS_SAVE(&desktop->settings); } @@ -330,6 +331,8 @@ int32_t desktop_srv(void* p) { view_port_enabled_set(desktop->dummy_mode_icon_viewport, desktop->settings.dummy_mode); desktop_main_set_dummy_mode_state(desktop->main_view, desktop->settings.dummy_mode); + animation_manager_set_dummy_mode_state( + desktop->animation_manager, desktop->settings.dummy_mode); scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index 4f01ad5b..befcf399 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -12,6 +12,10 @@ #define TAG "DesktopSrv" +#define MUSIC_PLAYER_APP EXT_PATH("/apps/Misc/music_player.fap") +#define SNAKE_GAME_APP EXT_PATH("/apps/Games/snake_game.fap") +#define CLOCK_APP EXT_PATH("/apps/Tools/clock.fap") + static void desktop_scene_main_new_idle_animation_callback(void* context) { furi_assert(context); Desktop* desktop = context; @@ -60,6 +64,19 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl } #endif +static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* path) { + do { + LoaderStatus status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, path); + if(status == LoaderStatusOk) break; + FURI_LOG_E(TAG, "loader_start failed: %d", status); + + status = loader_start(desktop->loader, "Passport", NULL); + if(status != LoaderStatusOk) { + FURI_LOG_E(TAG, "loader_start failed: %d", status); + } + } while(false); +} + void desktop_scene_main_callback(DesktopEvent event, void* context) { Desktop* desktop = (Desktop*)context; view_dispatcher_send_custom_event(desktop->view_dispatcher, event); @@ -181,12 +198,16 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { } break; } - case DesktopMainEventOpenGameMenu: { - LoaderStatus status = loader_start( - desktop->loader, FAP_LOADER_APP_NAME, EXT_PATH("/apps/Games/snake_game.fap")); - if(status != LoaderStatusOk) { - FURI_LOG_E(TAG, "loader_start failed: %d", status); - } + case DesktopMainEventOpenGame: { + desktop_scene_main_open_app_or_profile(desktop, SNAKE_GAME_APP); + break; + } + case DesktopMainEventOpenClock: { + desktop_scene_main_open_app_or_profile(desktop, CLOCK_APP); + break; + } + case DesktopMainEventOpenMusicPlayer: { + desktop_scene_main_open_app_or_profile(desktop, MUSIC_PLAYER_APP); break; } case DesktopLockedEventUpdate: diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index a7e610ff..666d179b 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -10,7 +10,9 @@ typedef enum { DesktopMainEventOpenPassport, DesktopMainEventOpenPowerOff, - DesktopMainEventOpenGameMenu, + DesktopMainEventOpenGame, + DesktopMainEventOpenClock, + DesktopMainEventOpenMusicPlayer, DesktopLockedEventUnlocked, DesktopLockedEventUpdate, diff --git a/applications/services/desktop/views/desktop_view_main.c b/applications/services/desktop/views/desktop_view_main.c index 0edc0b70..cbf4a20f 100644 --- a/applications/services/desktop/views/desktop_view_main.c +++ b/applications/services/desktop/views/desktop_view_main.c @@ -72,13 +72,13 @@ bool desktop_main_input_callback(InputEvent* event, void* context) { } else { if(event->type == InputTypeShort) { if(event->key == InputKeyOk) { - main_view->callback(DesktopMainEventOpenGameMenu, main_view->context); + main_view->callback(DesktopMainEventOpenGame, main_view->context); } else if(event->key == InputKeyUp) { main_view->callback(DesktopMainEventOpenLockMenu, main_view->context); } else if(event->key == InputKeyDown) { - main_view->callback(DesktopMainEventOpenPassport, main_view->context); + main_view->callback(DesktopMainEventOpenMusicPlayer, main_view->context); } else if(event->key == InputKeyLeft) { - main_view->callback(DesktopMainEventOpenPassport, main_view->context); + main_view->callback(DesktopMainEventOpenClock, main_view->context); } // Right key is handled by animation manager } diff --git a/applications/services/dialogs/view_holder.c b/applications/services/dialogs/view_holder.c index d2ae7750..7ab0a8e1 100644 --- a/applications/services/dialogs/view_holder.c +++ b/applications/services/dialogs/view_holder.c @@ -50,6 +50,10 @@ void view_holder_free(ViewHolder* view_holder) { void view_holder_set_view(ViewHolder* view_holder, View* view) { furi_assert(view_holder); if(view_holder->view) { + if(view_holder->view->exit_callback) { + view_holder->view->exit_callback(view_holder->view->context); + } + view_set_update_callback(view_holder->view, NULL); view_set_update_callback_context(view_holder->view, NULL); } @@ -59,6 +63,10 @@ void view_holder_set_view(ViewHolder* view_holder, View* view) { if(view_holder->view) { view_set_update_callback(view_holder->view, view_holder_update); view_set_update_callback_context(view_holder->view, view_holder); + + if(view_holder->view->enter_callback) { + view_holder->view->enter_callback(view_holder->view->context); + } } } diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index 0f7cf73f..6b796ed5 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -547,6 +547,53 @@ void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width } } +void elements_scrollable_text_line( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + FuriString* string, + size_t scroll, + bool ellipsis) { + FuriString* line = furi_string_alloc_set(string); + + size_t len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + if(len_px > width) { + if(ellipsis) { + width -= canvas_string_width(canvas, "..."); + } + + // Calculate scroll size + size_t scroll_size = furi_string_size(line); + size_t right_width = 0; + for(size_t i = scroll_size; i > 0; i--) { + right_width += canvas_glyph_width(canvas, furi_string_get_char(line, i)); + if(right_width > width) break; + scroll_size--; + if(!scroll_size) break; + } + // Ensure that we have something to scroll + if(scroll_size) { + scroll_size += 3; + scroll = scroll % scroll_size; + furi_string_right(line, scroll); + } + + len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + while(len_px > width) { + furi_string_left(line, furi_string_size(line) - 1); + len_px = canvas_string_width(canvas, furi_string_get_cstr(line)); + } + + if(ellipsis) { + furi_string_cat(line, "..."); + } + } + + canvas_draw_str(canvas, x, y, furi_string_get_cstr(line)); + furi_string_free(line); +} + void elements_text_box( Canvas* canvas, uint8_t x, diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 9f155402..162f0d41 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -192,6 +192,25 @@ void elements_bubble_str( */ void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width); +/** Draw scrollable text line + * + * @param canvas The canvas + * @param[in] x X coordinate + * @param[in] y Y coordinate + * @param[in] width The width + * @param string The string + * @param[in] scroll The scroll counter: 0 - no scroll, any other number - scroll. Just count up, everything else will be calculated on the inside. + * @param[in] ellipsis The ellipsis flag: true to add ellipse + */ +void elements_scrollable_text_line( + Canvas* canvas, + uint8_t x, + uint8_t y, + uint8_t width, + FuriString* string, + size_t scroll, + bool ellipsis); + /** Draw text box element * * @param canvas Canvas instance diff --git a/applications/services/gui/modules/button_menu.c b/applications/services/gui/modules/button_menu.c index ff12a931..60bd160c 100644 --- a/applications/services/gui/modules/button_menu.c +++ b/applications/services/gui/modules/button_menu.c @@ -30,7 +30,7 @@ struct ButtonMenu { typedef struct { ButtonMenuItemArray_t items; - uint8_t position; + size_t position; const char* header; } ButtonMenuModel; @@ -102,11 +102,9 @@ static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { ButtonMenuModel* model = (ButtonMenuModel*)_model; canvas_set_font(canvas, FontSecondary); - uint8_t item_position = 0; - int8_t active_screen = model->position / BUTTONS_PER_SCREEN; - size_t items_size = ButtonMenuItemArray_size(model->items); - int8_t max_screen = ((int16_t)items_size - 1) / BUTTONS_PER_SCREEN; - ButtonMenuItemArray_it_t it; + const size_t active_screen = model->position / BUTTONS_PER_SCREEN; + const size_t items_size = ButtonMenuItemArray_size(model->items); + const size_t max_screen = items_size ? (items_size - 1) / BUTTONS_PER_SCREEN : 0; if(active_screen > 0) { canvas_draw_icon(canvas, 28, 1, &I_InfraredArrowUp_4x8); @@ -125,6 +123,9 @@ static void button_menu_view_draw_callback(Canvas* canvas, void* _model) { furi_string_free(disp_str); } + size_t item_position = 0; + ButtonMenuItemArray_it_t it; + for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it); ButtonMenuItemArray_next(it), ++item_position) { if(active_screen == (item_position / BUTTONS_PER_SCREEN)) { @@ -195,14 +196,14 @@ static void button_menu_process_ok(ButtonMenu* button_menu, InputType type) { if(item) { if(item->type == ButtonMenuItemTypeControl) { if(type == InputTypeShort) { - if(item && item->callback) { + if(item->callback) { item->callback(item->callback_context, item->index, type); } } } if(item->type == ButtonMenuItemTypeCommon) { if((type == InputTypePress) || (type == InputTypeRelease)) { - if(item && item->callback) { + if(item->callback) { item->callback(item->callback_context, item->index, type); } } @@ -341,7 +342,7 @@ void button_menu_set_selected_item(ButtonMenu* button_menu, uint32_t index) { button_menu->view, ButtonMenuModel * model, { - uint8_t item_position = 0; + size_t item_position = 0; ButtonMenuItemArray_it_t it; for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it); ButtonMenuItemArray_next(it), ++item_position) { diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index a5daa91e..57e0018e 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -19,6 +19,9 @@ #define CUSTOM_ICON_MAX_SIZE 32 +#define SCROLL_INTERVAL (333) +#define SCROLL_DELAY (2) + typedef enum { BrowserItemTypeLoading, BrowserItemTypeBack, @@ -95,6 +98,7 @@ struct FileBrowser { void* item_context; FuriString* result_path; + FuriTimer* scroll_timer; }; typedef struct { @@ -110,6 +114,7 @@ typedef struct { const Icon* file_icon; bool hide_ext; + size_t scroll_counter; } FileBrowserModel; static const Icon* BrowserItemIcons[] = { @@ -129,6 +134,27 @@ static void browser_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last); static void browser_long_load_cb(void* context); +static void file_browser_scroll_timer_callback(void* context) { + furi_assert(context); + FileBrowser* browser = context; + with_view_model( + browser->view, FileBrowserModel * model, { model->scroll_counter++; }, true); +} + +static void file_browser_view_enter_callback(void* context) { + furi_assert(context); + FileBrowser* browser = context; + with_view_model( + browser->view, FileBrowserModel * model, { model->scroll_counter = 0; }, true); + furi_timer_start(browser->scroll_timer, SCROLL_INTERVAL); +} + +static void file_browser_view_exit_callback(void* context) { + furi_assert(context); + FileBrowser* browser = context; + furi_timer_stop(browser->scroll_timer); +} + FileBrowser* file_browser_alloc(FuriString* result_path) { furi_assert(result_path); FileBrowser* browser = malloc(sizeof(FileBrowser)); @@ -137,6 +163,11 @@ FileBrowser* file_browser_alloc(FuriString* result_path) { view_set_context(browser->view, browser); view_set_draw_callback(browser->view, file_browser_view_draw_callback); view_set_input_callback(browser->view, file_browser_view_input_callback); + view_set_enter_callback(browser->view, file_browser_view_enter_callback); + view_set_exit_callback(browser->view, file_browser_view_exit_callback); + + browser->scroll_timer = + furi_timer_alloc(file_browser_scroll_timer_callback, FuriTimerTypePeriodic, browser); browser->result_path = result_path; @@ -149,6 +180,8 @@ FileBrowser* file_browser_alloc(FuriString* result_path) { void file_browser_free(FileBrowser* browser) { furi_assert(browser); + furi_timer_free(browser->scroll_timer); + with_view_model( browser->view, FileBrowserModel * model, { items_array_clear(model->items); }, false); @@ -468,13 +501,17 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { furi_string_set(filename, ". ."); } - elements_string_fit_width( - canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX)); - + size_t scroll_counter = model->scroll_counter; if(model->item_idx == idx) { browser_draw_frame(canvas, i, show_scrollbar); + if(scroll_counter < SCROLL_DELAY) { + scroll_counter = 0; + } else { + scroll_counter -= SCROLL_DELAY; + } } else { canvas_set_color(canvas, ColorBlack); + scroll_counter = 0; } if(custom_icon_data) { @@ -487,8 +524,14 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { canvas_draw_icon( canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, BrowserItemIcons[item_type]); } - canvas_draw_str( - canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, furi_string_get_cstr(filename)); + elements_scrollable_text_line( + canvas, + 15, + Y_OFFSET + 9 + i * FRAME_HEIGHT, + (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX), + filename, + scroll_counter, + (model->item_idx != idx)); } if(show_scrollbar) { @@ -543,6 +586,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { file_browser_worker_load( browser->worker, load_offset, ITEM_LIST_LEN_MAX); } + model->scroll_counter = 0; } else if(event->key == InputKeyDown) { model->item_idx = (model->item_idx + 1) % model->item_cnt; if(browser_is_list_load_required(model)) { @@ -554,6 +598,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { file_browser_worker_load( browser->worker, load_offset, ITEM_LIST_LEN_MAX); } + model->scroll_counter = 0; } }, true); diff --git a/applications/services/gui/modules/submenu.c b/applications/services/gui/modules/submenu.c index 94959877..b7152a3d 100644 --- a/applications/services/gui/modules/submenu.c +++ b/applications/services/gui/modules/submenu.c @@ -20,8 +20,8 @@ ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST); typedef struct { SubmenuItemArray_t items; const char* header; - uint8_t position; - uint8_t window_position; + size_t position; + size_t window_position; } SubmenuModel; static void submenu_process_up(Submenu* submenu); @@ -36,19 +36,19 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) { canvas_clear(canvas); - uint8_t position = 0; - SubmenuItemArray_it_t it; - if(model->header) { canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 4, 11, model->header); } canvas_set_font(canvas, FontSecondary); + + size_t position = 0; + SubmenuItemArray_it_t it; for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); SubmenuItemArray_next(it)) { - uint8_t item_position = position - model->window_position; - uint8_t items_on_screen = model->header ? 3 : 4; + const size_t item_position = position - model->window_position; + const size_t items_on_screen = model->header ? 3 : 4; uint8_t y_offset = model->header ? 16 : 0; if(item_position < items_on_screen) { @@ -198,7 +198,7 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { submenu->view, SubmenuModel * model, { - uint32_t position = 0; + size_t position = 0; SubmenuItemArray_it_t it; for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it); SubmenuItemArray_next(it)) { @@ -208,7 +208,9 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { position++; } - if(position >= SubmenuItemArray_size(model->items)) { + const size_t items_size = SubmenuItemArray_size(model->items); + + if(position >= items_size) { position = 0; } @@ -219,16 +221,12 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) { model->window_position -= 1; } - uint8_t items_on_screen = model->header ? 3 : 4; + const size_t items_on_screen = model->header ? 3 : 4; - if(SubmenuItemArray_size(model->items) <= items_on_screen) { + if(items_size <= items_on_screen) { model->window_position = 0; - } else { - if(model->window_position >= - (SubmenuItemArray_size(model->items) - items_on_screen)) { - model->window_position = - (SubmenuItemArray_size(model->items) - items_on_screen); - } + } else if(model->window_position >= items_size - items_on_screen) { + model->window_position = items_size - items_on_screen; } }, true); @@ -239,16 +237,18 @@ void submenu_process_up(Submenu* submenu) { submenu->view, SubmenuModel * model, { - uint8_t items_on_screen = model->header ? 3 : 4; + const size_t items_on_screen = model->header ? 3 : 4; + const size_t items_size = SubmenuItemArray_size(model->items); + if(model->position > 0) { model->position--; - if(((model->position - model->window_position) < 1) && - model->window_position > 0) { + if((model->position - model->window_position < 1) && + (model->window_position > 0)) { model->window_position--; } } else { - model->position = SubmenuItemArray_size(model->items) - 1; - if(model->position > (items_on_screen - 1)) { + model->position = items_size - 1; + if(model->position > items_on_screen - 1) { model->window_position = model->position - (items_on_screen - 1); } } @@ -261,12 +261,13 @@ void submenu_process_down(Submenu* submenu) { submenu->view, SubmenuModel * model, { - uint8_t items_on_screen = model->header ? 3 : 4; - if(model->position < (SubmenuItemArray_size(model->items) - 1)) { + const size_t items_on_screen = model->header ? 3 : 4; + const size_t items_size = SubmenuItemArray_size(model->items); + + if(model->position < items_size - 1) { model->position++; - if((model->position - model->window_position) > (items_on_screen - 2) && - model->window_position < - (SubmenuItemArray_size(model->items) - items_on_screen)) { + if((model->position - model->window_position > items_on_screen - 2) && + (model->window_position < items_size - items_on_screen)) { model->window_position++; } } else { @@ -284,7 +285,8 @@ void submenu_process_ok(Submenu* submenu) { submenu->view, SubmenuModel * model, { - if(model->position < (SubmenuItemArray_size(model->items))) { + const size_t items_size = SubmenuItemArray_size(model->items); + if(model->position < items_size) { item = SubmenuItemArray_get(model->items, model->position); } }, diff --git a/applications/services/locale/application.fam b/applications/services/locale/application.fam new file mode 100644 index 00000000..c762d02d --- /dev/null +++ b/applications/services/locale/application.fam @@ -0,0 +1,9 @@ +App( + appid="locale", + name="LocaleSrv", + apptype=FlipperAppType.STARTUP, + entry_point="locale_on_system_start", + cdefines=["SRV_LOCALE"], + order=90, + sdk_headers=["locale.h"], +) diff --git a/applications/services/locale/locale.c b/applications/services/locale/locale.c new file mode 100644 index 00000000..e8b6a9fc --- /dev/null +++ b/applications/services/locale/locale.c @@ -0,0 +1,112 @@ +#include "locale.h" + +#define TAG "LocaleSrv" + +LocaleMeasurementUnits locale_get_measurement_unit(void) { + return (LocaleMeasurementUnits)furi_hal_rtc_get_locale_units(); +} + +void locale_set_measurement_unit(LocaleMeasurementUnits format) { + furi_hal_rtc_set_locale_units((FuriHalRtcLocaleUnits)format); +} + +LocaleTimeFormat locale_get_time_format(void) { + return (LocaleTimeFormat)furi_hal_rtc_get_locale_timeformat(); +} + +void locale_set_time_format(LocaleTimeFormat format) { + furi_hal_rtc_set_locale_timeformat((FuriHalRtcLocaleTimeFormat)format); +} + +LocaleDateFormat locale_get_date_format(void) { + return (LocaleDateFormat)furi_hal_rtc_get_locale_dateformat(); +} + +void locale_set_date_format(LocaleDateFormat format) { + furi_hal_rtc_set_locale_dateformat((FuriHalRtcLocaleDateFormat)format); +} + +float locale_fahrenheit_to_celsius(float temp_f) { + return (temp_f - 32.f) / 1.8f; +} + +float locale_celsius_to_fahrenheit(float temp_c) { + return (temp_c * 1.8f + 32.f); +} + +void locale_format_time( + FuriString* out_str, + const FuriHalRtcDateTime* datetime, + const LocaleTimeFormat format, + const bool show_seconds) { + furi_assert(out_str); + furi_assert(datetime); + + uint8_t hours = datetime->hour; + uint8_t am_pm = 0; + if(format == LocaleTimeFormat12h) { + if(hours > 12) { + hours -= 12; + am_pm = 2; + } else { + am_pm = 1; + } + if(hours == 0) { + hours = 12; + } + } + + if(show_seconds) { + furi_string_printf(out_str, "%02u:%02u:%02u", hours, datetime->minute, datetime->second); + } else { + furi_string_printf(out_str, "%02u:%02u", hours, datetime->minute); + } + + if(am_pm > 0) { + furi_string_cat_printf(out_str, " %s", (am_pm == 1) ? ("AM") : ("PM")); + } +} + +void locale_format_date( + FuriString* out_str, + const FuriHalRtcDateTime* datetime, + const LocaleDateFormat format, + const char* separator) { + furi_assert(out_str); + furi_assert(datetime); + furi_assert(separator); + + if(format == LocaleDateFormatDMY) { + furi_string_printf( + out_str, + "%02u%s%02u%s%04u", + datetime->day, + separator, + datetime->month, + separator, + datetime->year); + } else if(format == LocaleDateFormatMDY) { + furi_string_printf( + out_str, + "%02u%s%02u%s%04u", + datetime->month, + separator, + datetime->day, + separator, + datetime->year); + } else { + furi_string_printf( + out_str, + "%04u%s%02u%s%02u", + datetime->year, + separator, + datetime->month, + separator, + datetime->day); + } +} + +int32_t locale_on_system_start(void* p) { + UNUSED(p); + return 0; +} diff --git a/applications/services/locale/locale.h b/applications/services/locale/locale.h new file mode 100644 index 00000000..5f2a4d87 --- /dev/null +++ b/applications/services/locale/locale.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + LocaleMeasurementUnitsMetric = 0, /**< Metric measurement units */ + LocaleMeasurementUnitsImperial = 1, /**< Imperial measurement units */ +} LocaleMeasurementUnits; + +typedef enum { + LocaleTimeFormat24h = 0, /**< 24-hour format */ + LocaleTimeFormat12h = 1, /**< 12-hour format */ +} LocaleTimeFormat; + +typedef enum { + LocaleDateFormatDMY = 0, /**< Day/Month/Year */ + LocaleDateFormatMDY = 1, /**< Month/Day/Year */ + LocaleDateFormatYMD = 2, /**< Year/Month/Day */ +} LocaleDateFormat; + +/** Get Locale measurement units + * + * @return The locale measurement units. + */ +LocaleMeasurementUnits locale_get_measurement_unit(); + +/** Set locale measurement units + * + * @param[in] format The locale measurements units + */ +void locale_set_measurement_unit(LocaleMeasurementUnits format); + +/** Convert Fahrenheit to Celsius + * + * @param[in] temp_f The Temperature in Fahrenheit + * + * @return The Temperature in Celsius + */ +float locale_fahrenheit_to_celsius(float temp_f); + +/** Convert Celsius to Fahrenheit + * + * @param[in] temp_c The Temperature in Celsius + * + * @return The Temperature in Fahrenheit + */ +float locale_celsius_to_fahrenheit(float temp_c); + +/** Get Locale time format + * + * @return The locale time format. + */ +LocaleTimeFormat locale_get_time_format(); + +/** Set Locale Time Format + * + * @param[in] format The Locale Time Format + */ +void locale_set_time_format(LocaleTimeFormat format); + +/** Format time to furi string + * + * @param[out] out_str The FuriString to store formatted time + * @param[in] datetime Pointer to the datetime + * @param[in] format The Locale Time Format + * @param[in] show_seconds The show seconds flag + */ +void locale_format_time( + FuriString* out_str, + const FuriHalRtcDateTime* datetime, + const LocaleTimeFormat format, + const bool show_seconds); + +/** Get Locale DateFormat + * + * @return The Locale DateFormat. + */ +LocaleDateFormat locale_get_date_format(); + +/** Set Locale DateFormat + * + * @param[in] format The Locale DateFormat + */ +void locale_set_date_format(LocaleDateFormat format); + +/** Format date to furi string + * + * @param[out] out_str The FuriString to store formatted date + * @param[in] datetime Pointer to the datetime + * @param[in] format The format + * @param[in] separator The separator + */ +void locale_format_date( + FuriString* out_str, + const FuriHalRtcDateTime* datetime, + const LocaleDateFormat format, + const char* separator); + +#ifdef __cplusplus +} +#endif diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 6091f0aa..b6579f54 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -150,11 +150,16 @@ void notification_vibro_off() { } void notification_sound_on(float freq, float volume) { - furi_hal_speaker_start(freq, volume); + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { + furi_hal_speaker_start(freq, volume); + } } void notification_sound_off() { - furi_hal_speaker_stop(); + if(furi_hal_speaker_is_mine()) { + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } } // display timer diff --git a/applications/settings/system/application.fam b/applications/settings/system/application.fam index 0fc456b2..69a8f123 100644 --- a/applications/settings/system/application.fam +++ b/applications/settings/system/application.fam @@ -3,7 +3,7 @@ App( name="System", apptype=FlipperAppType.SETTINGS, entry_point="system_settings_app", - requires=["gui"], + requires=["gui", "locale"], stack_size=1 * 1024, order=70, ) diff --git a/applications/settings/system/system_settings.c b/applications/settings/system/system_settings.c index dfce11a2..5eade211 100644 --- a/applications/settings/system/system_settings.c +++ b/applications/settings/system/system_settings.c @@ -1,6 +1,7 @@ #include "system_settings.h" #include #include +#include const char* const log_level_text[] = { "Default", @@ -70,6 +71,59 @@ static void heap_trace_mode_changed(VariableItem* item) { furi_hal_rtc_set_heap_track_mode(heap_trace_mode_value[index]); } +const char* const mesurement_units_text[] = { + "Metric", + "Imperial", +}; + +const uint32_t mesurement_units_value[] = { + LocaleMeasurementUnitsMetric, + LocaleMeasurementUnitsImperial, +}; + +static void mesurement_units_changed(VariableItem* item) { + // SystemSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, mesurement_units_text[index]); + locale_set_measurement_unit(mesurement_units_value[index]); +} + +const char* const time_format_text[] = { + "24h", + "12h", +}; + +const uint32_t time_format_value[] = { + LocaleTimeFormat24h, + LocaleTimeFormat12h, +}; + +static void time_format_changed(VariableItem* item) { + // SystemSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, time_format_text[index]); + locale_set_time_format(time_format_value[index]); +} + +const char* const date_format_text[] = { + "D/M/Y", + "M/D/Y", + "Y/M/D", +}; + +const uint32_t date_format_value[] = { + LocaleDateFormatDMY, + LocaleDateFormatMDY, + LocaleDateFormatYMD, +}; + +static void date_format_changed(VariableItem* item) { + // SystemSettings* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, date_format_text[index]); + locale_set_date_format(date_format_value[index]); +} + static uint32_t system_settings_exit(void* context) { UNUSED(context); return VIEW_NONE; @@ -91,6 +145,31 @@ SystemSettings* system_settings_alloc() { uint8_t value_index; app->var_item_list = variable_item_list_alloc(); + item = variable_item_list_add( + app->var_item_list, + "Units", + COUNT_OF(mesurement_units_text), + mesurement_units_changed, + app); + value_index = value_index_uint32( + locale_get_measurement_unit(), mesurement_units_value, COUNT_OF(mesurement_units_value)); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, mesurement_units_text[value_index]); + + item = variable_item_list_add( + app->var_item_list, "Time Format", COUNT_OF(time_format_text), time_format_changed, app); + value_index = value_index_uint32( + locale_get_time_format(), time_format_value, COUNT_OF(time_format_value)); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, time_format_text[value_index]); + + item = variable_item_list_add( + app->var_item_list, "Date Format", COUNT_OF(date_format_text), date_format_changed, app); + value_index = value_index_uint32( + locale_get_date_format(), date_format_value, COUNT_OF(date_format_value)); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, date_format_text[value_index]); + item = variable_item_list_add( app->var_item_list, "Log Level", COUNT_OF(log_level_text), log_level_changed, app); value_index = value_index_uint32( diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png new file mode 100644 index 00000000..909cc330 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png new file mode 100644 index 00000000..fdc1805d Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png new file mode 100644 index 00000000..3a43c6b8 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png new file mode 100644 index 00000000..20ea12eb Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png new file mode 100644 index 00000000..a05abd80 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png new file mode 100644 index 00000000..fc7e0364 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png new file mode 100644 index 00000000..06f08ae6 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png new file mode 100644 index 00000000..bcc7f28a Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png new file mode 100644 index 00000000..7d945dd0 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png new file mode 100644 index 00000000..c0b1a4fe Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png new file mode 100644 index 00000000..19c8181d Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png new file mode 100644 index 00000000..c0b31695 Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png new file mode 100644 index 00000000..929a207e Binary files /dev/null and b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt b/assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt new file mode 100644 index 00000000..a2c73339 --- /dev/null +++ b/assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 10 +Active frames: 18 +Frames order: 0 1 2 1 0 1 2 1 0 1 2 3 4 5 6 5 4 7 2 8 9 10 11 10 9 10 11 12 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 11 +Y: 19 +Text: HAPPY\nHOLIDAYS! +AlignH: Right +AlignV: Center +StartFrame: 22 +EndFrame: 27 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 7f03d659..362fba6a 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -36,10 +36,10 @@ Min level: 1 Max level: 1 Weight: 3 -Name: L2_Wake_up_128x64 +Name: L1_Happy_holidays_128x64 Min butthurt: 0 -Max butthurt: 12 -Min level: 2 +Max butthurt: 14 +Min level: 1 Max level: 3 Weight: 4 @@ -92,6 +92,13 @@ Min level: 1 Max level: 3 Weight: 3 +Name: L2_Wake_up_128x64 +Min butthurt: 0 +Max butthurt: 12 +Min level: 2 +Max level: 3 +Weight: 4 + Name: L2_Furippa2_128x64 Min butthurt: 0 Max butthurt: 6 diff --git a/assets/resources/nfc/assets/mf_classic_dict.nfc b/assets/resources/nfc/assets/mf_classic_dict.nfc index f4cdf595..0925888f 100644 --- a/assets/resources/nfc/assets/mf_classic_dict.nfc +++ b/assets/resources/nfc/assets/mf_classic_dict.nfc @@ -244,6 +244,7 @@ FEE470A4CB58 1F1A0A111B5B 1F1FFE000000 2031D1E57A3B +# HID Key B 204752454154 21A600056CB0 22729A9BD40F @@ -292,6 +293,7 @@ FEE470A4CB58 45635EF66EF3 476242304C53 484558414354 +# HID Key A 484944204953 484A57696F4A 48734389EDC3 @@ -1226,6 +1228,7 @@ C41514DEFC07 ABFEDC124578 046154274C11 5429D67E1F57 +# SMARTair Key B E7316853E731 CD7FFFF81C4A F253C30568C4 @@ -1308,4 +1311,4 @@ CE99FBC8BD26 # PIK Comfort Moscow keys (ISBC Mifare Plus SE 1K) 009FB42D98ED -002E626E2820 \ No newline at end of file +002E626E2820 diff --git a/assets/resources/picopass/assets/iclass_elite_dict.txt b/assets/resources/picopass/assets/iclass_elite_dict.txt new file mode 100644 index 00000000..46808ef6 --- /dev/null +++ b/assets/resources/picopass/assets/iclass_elite_dict.txt @@ -0,0 +1,47 @@ + +## From https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/iclass_default_keys.dic + +# AA1 +AEA684A6DAB23278 +# key1/Kc from PicoPass 2k documentation +7665544332211000 +# SAGEM +0123456789ABCDEF +# from loclass demo file. +5b7c62c491c11b39 +# Kd from PicoPass 2k documentation +F0E1D2C3B4A59687 +# PicoPass Default Exchange Key +5CBCF1DA45D5FB4F +# From HID multiclassSE reader +31ad7ebd2f282168 +# From pastebin: https://pastebin.com/uHqpjiuU +6EFD46EFCBB3C875 +E033CA419AEE43F9 + +# iCopy-x DRM keys +# iCL tags +2020666666668888 +# iCS tags reversed from the SOs +6666202066668888 + +# default picopass KD / Page 0 / Book 1 +FDCB5A52EA8F3090 +237FF9079863DF44 +5ADC25FB27181D32 +83B881F2936B2E49 +43644E61EE866BA5 +897034143D016080 +82D17B44C0122963 +4895CA7DE65E2025 +DADAD4C57BE271B7 +E41E9EDEF5719ABF +293D275EC3AF9C7F +C3C169251B8A70FB +F41DAF58B20C8B91 +28877A609EC0DD2B +66584C91EE80D5E5 +C1B74D7478053AE2 + +# default iCLASS RFIDeas +6B65797374726B72 diff --git a/assets/unit_tests/subghz/smc5326.sub b/assets/unit_tests/subghz/smc5326.sub new file mode 100644 index 00000000..eab7aac9 --- /dev/null +++ b/assets/unit_tests/subghz/smc5326.sub @@ -0,0 +1,8 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: SMC5326 +Bit: 25 +Key: 00 00 00 00 01 7D 55 80 +TE: 210 diff --git a/assets/unit_tests/subghz/smc5326_raw.sub b/assets/unit_tests/subghz/smc5326_raw.sub new file mode 100644 index 00000000..f2e3edbf --- /dev/null +++ b/assets/unit_tests/subghz/smc5326_raw.sub @@ -0,0 +1,7 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 2442 -312 275 -972 949 -310 941 -322 923 -342 921 -352 923 -334 281 -954 945 -350 279 -958 907 -354 289 -980 909 -352 281 -962 907 -330 311 -964 913 -350 317 -930 933 -344 921 -352 893 -330 311 -954 943 -318 315 -958 909 -324 947 -7854 953 -322 289 -948 939 -354 927 -332 911 -324 943 -344 917 -318 317 -964 905 -344 303 -942 947 -312 319 -960 913 -348 281 -958 941 -322 295 -978 905 -350 279 -962 931 -328 947 -324 939 -346 267 -964 935 -348 283 -938 953 -318 931 -7868 935 -346 269 -968 953 -310 941 -322 921 -330 935 -342 931 -318 311 -962 939 -290 337 -950 909 -352 317 -924 943 -324 313 -938 941 -318 317 -932 939 -344 301 -938 933 -350 921 -322 959 -310 301 -942 933 -352 317 -926 957 -314 919 -7868 943 -314 317 -958 909 -322 951 -344 919 -352 921 -324 937 -326 281 -964 941 -318 317 -930 939 -344 301 -938 933 -352 281 -962 953 -314 317 -922 933 -330 315 -954 943 -318 921 -342 943 -320 291 -980 909 -354 281 -962 943 -296 967 -7836 943 -332 309 -950 935 -318 929 -340 943 -320 921 -344 921 -354 283 -960 943 -296 309 -964 945 -318 279 -964 941 -322 333 -944 939 -314 279 -992 903 -342 319 -932 933 -330 931 -340 929 -348 281 -964 935 -334 281 -970 927 -346 921 -7862 951 -314 319 -922 953 -320 923 -346 921 -320 965 -298 943 -324 313 -942 941 -320 317 -930 941 -344 303 -940 945 -312 321 -940 953 -314 303 -960 933 -348 287 -962 911 -352 917 -350 905 -324 333 -918 971 -322 317 -924 945 -324 937 -7872 919 -324 317 -942 941 -318 933 -330 943 -324 943 -310 951 -318 317 -930 939 -344 301 -938 933 -352 317 -926 953 -314 319 -924 939 -324 331 -950 907 -354 315 -926 945 -324 939 -312 953 -318 317 -930 937 -344 301 -940 947 -348 909 -7864 949 -310 319 -956 915 -350 919 -348 905 -322 963 -296 935 -348 317 -922 951 -322 295 -976 939 -314 281 -996 915 -326 307 -940 959 -310 301 -966 935 -346 285 -958 915 -348 921 -348 903 -354 303 -948 911 -350 315 -926 945 -324 941 -7874 943 -290 319 -942 973 -318 929 -314 937 -328 941 -324 939 -310 303 -962 933 -352 285 -962 949 -314 319 -924 951 -320 293 -948 941 -354 283 -962 943 -294 309 -966 943 -320 931 -328 943 -326 311 -940 939 -320 309 -958 933 -338 943 -7840 933 -352 277 -964 941 -322 923 -344 923 -350 931 -310 955 -320 291 -974 907 -350 281 -958 963 -298 313 -956 945 -314 311 -960 937 -312 311 -966 909 -324 319 -944 941 -354 929 -298 945 -324 315 -940 +RAW_Data: 943 -354 281 -964 905 -330 933 -7868 951 -324 315 -938 943 -354 893 -330 943 -324 943 -344 919 -318 317 -962 903 -344 301 -974 903 -350 317 -932 931 -342 269 -972 949 -346 285 -938 955 -310 301 -964 935 -348 921 -320 921 -344 301 -940 935 -350 317 -930 929 -318 937 -7872 939 -344 301 -940 947 -346 917 -322 921 -344 923 -352 927 -334 281 -970 925 -334 277 -982 943 -318 317 -932 931 -344 301 -936 935 -350 281 -960 957 -312 303 -960 935 -346 907 -322 929 -344 301 -942 935 -350 317 -924 955 -312 951 -7858 919 -342 309 -940 949 -348 909 -322 923 -344 923 -352 923 -336 317 -924 945 -312 311 -966 921 -340 317 -924 947 -350 281 -958 941 -322 291 -976 905 -350 279 -960 935 -342 943 -320 919 -330 311 -958 943 -320 315 -932 935 -344 919 -7866 957 -312 303 -964 917 -342 945 -320 923 -344 923 -354 929 -298 315 -956 941 -318 315 -960 911 -324 317 -942 939 -354 281 -964 941 -294 311 -968 943 -318 317 -932 937 -330 931 -350 919 -348 283 -960 917 -350 317 -922 939 -322 965 -7864 921 -324 329 -950 909 -354 923 -336 913 -322 947 -344 919 -354 281 -962 941 -294 311 -960 935 -354 281 -962 939 -294 311 -964 937 -354 281 -964 941 -296 309 -964 939 -318 931 -330 945 -324 315 -940 939 -354 281 -964 909 -344 921 -7862 963 -304 307 -976 933 -320 929 -328 941 -324 939 -348 915 -320 317 -930 939 -344 301 -940 965 -320 319 -926 953 -312 303 -960 933 -312 321 -960 913 -348 319 -924 943 -320 959 -310 921 -354 319 -924 943 -324 311 -938 941 -318 957 -7862 943 -318 317 -932 933 -344 925 -352 897 -332 943 -324 943 -346 267 -966 951 -310 321 -960 911 -350 281 -958 949 -320 291 -978 937 -316 279 -964 949 -326 309 -944 943 -314 959 -318 933 -336 317 -934 933 -344 267 -964 937 -350 905 -7896 943 -318 319 -926 955 -314 919 -350 935 -324 941 -294 967 -312 303 -962 933 -348 285 -960 917 -348 317 -922 941 -322 329 -950 907 -354 315 -926 943 -326 313 -940 941 -352 893 -332 949 -324 315 -938 941 -352 283 -962 943 -310 925 -7890 931 -344 269 -968 949 -310 943 -320 923 -350 937 -310 955 -318 317 -930 935 -344 301 -942 947 -346 285 -958 915 -346 317 -924 951 -322 295 -982 905 -352 317 -924 945 -324 941 -346 917 -318 317 -962 905 -330 311 -956 937 -352 897 -7878 939 -354 283 -960 941 -294 965 -312 953 -318 385 -201512 165 -198 265 -526 229 -298 755 -164 61687 -17310 131 -1056 99 -296 195 -296 65 -66 1617 diff --git a/assets/unit_tests/subghz/test_random_raw.sub b/assets/unit_tests/subghz/test_random_raw.sub index a6a3c986..900b2620 100644 --- a/assets/unit_tests/subghz/test_random_raw.sub +++ b/assets/unit_tests/subghz/test_random_raw.sub @@ -165,4 +165,6 @@ RAW_Data: 1125 -536 1145 -19444 567 -542 1151 -1086 581 -534 1133 -1084 583 -530 RAW_Data: 317 -144 57 -486 53 -282 115 -585 97 -72 229 -174 257 -440 225 -86 173 -518 243 -167 95 -259 137 -96 694 -58 227 -80 279 -287 71 -72 301 -72 121 -106 51 -84 57 -58 199 -260 143 -288 219 -174 113 -681 115 -172 403 -58 113 -116 113 -432 171 -202 55 -108 95 -212 113 -72 527 -166 95 -212 195 -108 603 -142 239 -296 173 -346 373 -287 53 -80 79 -72 95 -238 95 -312 167 -618 143 -288 95 -72 95 -72 141 -210 55 -258 143 -328 305 -58 87 -86 315 -116 195 -218 85 -290 285 -220 215 -189 201 -58 57 -645 119 -96 71 -144 119 -406 143 -72 191 -72 631 -268 344 -56 115 -260 315 -140 455 -518 57 -58 171 -144 488 -86 219 -232 257 -144 85 -174 171 -260 115 -56 87 -166 197 -58 83 -56 85 -288 113 -410 115 -172 163 -202 113 -58 201 -144 201 -86 143 -264 167 -212 113 -116 139 -72 181 -287 343 -430 201 -260 201 -462 143 -192 301 -230 191 -454 187 -144 315 -164 143 -477 165 -58 201 -114 143 -490 115 -86 201 -58 113 -88 85 -58 203 -198 375 -86 171 -346 95 -88 257 -170 81 -56 143 -172 335 -230 173 -202 133 -471 187 -264 215 -86 115 -198 159 -72 179 -112 195 -116 449 -216 93 -96 167 -216 71 -216 71 -166 235 -86 447 -102 101 -226 195 -213 71 -144 215 -144 215 -261 241 -136 269 -142 263 -311 215 -172 201 -144 265 -168 71 -404 259 -86 85 -230 115 -650 143 -202 749 -512 248 -316 201 -154 71 -96 95 -360 105 -56 57 -432 95 -288 95 -286 95 -96 166 -144 93 -144 167 -150 904 -162 95 -526 287 -244 95 -240 383 -120 167 -394 430 -854 95 -72 143 -194 227 -120 167 -264 405 -144 143 -72 143 -72 141 -120 187 -86 143 -164 170 -96 143 -58 143 -86 402 -166 153 -120 95 -96 69 -96 71 -359 404 -338 71 -225 93 -74 97 -54 161 -114 319 -288 113 -116 459 -202 115 -114 115 -116 143 -86 57 -56 87 -114 85 -375 113 -58 311 -240 203 -288 95 -72 119 -383 213 -384 115 -86 171 -58 53 -104 401 -58 115 -86 373 -116 143 -144 161 -216 406 -72 263 -96 215 -72 95 -94 167 -96 191 -240 95 -94 214 -120 403 -116 200 -114 57 -172 220 -120 137 -364 334 -392 115 -260 199 -116 373 -188 95 -110 143 -172 87 -114 172 -230 57 -316 201 -56 249 -485 171 -202 87 -86 85 -144 345 -86 171 -58 259 -58 295 -120 95 -120 71 -192 635 -118 167 -96 375 -72 119 -120 261 -144 167 -96 95 -96 923 -215 71 -433 71 -477 RAW_Data: 191 -240 85 -72 637 -408 213 -510 261 -168 143 -126 79 -106 167 -72 117 -218 251 -168 119 -96 215 -182 191 -238 517 -116 201 -144 255 -154 97 -94 215 -72 95 -120 71 -288 261 -106 434 -96 606 -232 229 -432 85 -174 343 -58 329 -156 55 -116 259 -144 488 -56 307 -339 115 -202 334 -88 113 -86 57 -174 143 -144 401 -376 85 -240 267 -82 95 -216 137 -158 85 -144 143 -58 221 -308 295 -114 87 -114 301 -120 358 -517 71 -262 191 -144 57 -140 165 -407 53 -262 217 -120 238 -358 119 -357 71 -72 119 -96 428 -72 95 -72 167 -72 93 -240 335 -96 357 -240 173 -230 143 -114 87 -200 143 -232 287 -150 97 -288 71 -72 93 -288 115 -58 143 -230 109 -264 71 -72 119 -72 238 -242 97 -78 163 -86 115 -518 79 -560 205 -449 969 -144 507 -86 231 -114 345 -58 979 -110 85 -288 287 -404 229 -202 57 -274 233 -86 115 -202 632 -230 85 -312 369 -392 460 -450 75 -280 85 -202 201 -86 229 -174 143 -144 233 -528 115 -212 127 -202 287 -172 403 -172 139 -128 165 -138 261 -392 143 -480 142 -189 291 -80 53 -283 167 -140 113 -1008 191 -144 119 -120 71 -193 241 -462 201 -58 143 -344 539 -316 113 -174 85 -116 113 -250 239 -168 405 -168 239 -158 85 -144 115 -86 57 -86 341 -144 171 -202 85 -202 115 -114 719 -88 55 -318 257 -56 254 -86 171 -116 459 -174 171 -329 95 -134 85 -314 431 -306 77 -316 401 -86 173 -404 281 -1073 488 -94 217 -78 101 -98 214 -120 215 -340 403 -535 143 -564 115 -116 199 -58 85 -174 315 -58 335 -136 55 -260 143 -144 229 -460 143 -58 143 -144 171 -202 115 -374 291 -130 339 -82 143 -58 171 -58 201 -86 85 -174 1022 -56 85 -82 255 -240 103 -202 431 -278 95 -216 119 -72 71 -96 71 -559 57 -144 171 -88 113 -86 231 -414 131 -192 237 -360 95 -168 145 -168 213 -120 167 -96 143 -110 57 -86 259 -56 87 -777 295 -96 57 -86 173 -86 171 -404 143 -172 231 -200 57 -441 55 -58 173 -56 87 -86 171 -72 287 -72 119 -262 119 -144 71 -72 121 -310 71 -302 113 -54 193 -80 307 -58 257 -232 143 -56 143 -116 219 -72 695 -70 71 -460 85 -232 719 -363 57 -402 604 -230 287 -138 83 -172 259 -58 171 -174 55 -88 489 -114 143 -116 171 -116 143 -58 199 -144 145 -343 374 -186 235 -140 77 -86 143 -202 143 -144 113 -144 143 -58 732 -96 263 -264 71 -206 95 -168 215 -144 271 -80 139 -88 85 -414 75 -100 RAW_Data: 285 -96 627 -362 53 -84 201 -374 113 -202 115 -202 421 -316 85 -58 139 -224 87 -86 229 -58 243 -178 267 -288 95 -336 171 -96 213 -288 71 -405 95 -96 95 -384 95 -72 213 -72 95 -96 95 -272 87 -1083 85 -58 113 -88 257 -116 143 -292 175 -318 95 -120 95 -144 95 -72 71 -216 368 -116 373 -172 115 -58 85 -116 143 -86 85 -144 201 -86 201 -202 257 -144 201 -174 113 -144 115 -144 257 -202 585 -364 173 -138 287 -422 431 -86 85 -96 869 -186 95 -52 115 -86 115 -58 55 -276 365 -86 85 -489 171 -140 577 -106 718 -144 391 -232 195 -82 143 -172 109 -120 167 -96 280 -216 145 -240 215 -186 163 -96 141 -172 159 -603 257 -108 629 -192 119 -80 87 -172 57 -144 286 -86 57 -230 344 -58 113 -537 75 -96 537 -86 403 -196 167 -264 119 -238 119 -120 167 -96 95 -478 95 -120 167 -216 1085 -96 358 -72 263 -72 69 -120 143 -96 71 -96 191 -362 55 -144 57 -260 113 -58 85 -174 55 -88 257 -86 231 -194 55 -58 115 -56 55 -339 55 -58 374 -172 139 -82 419 -98 119 -261 71 -72 71 -240 713 -86 143 -218 295 -72 53 -56 431 -58 317 -144 161 -144 373 -144 173 -144 57 -114 85 -116 195 -72 708 -172 115 -86 191 -96 506 -120 71 -174 85 -58 363 -114 317 -230 316 -200 87 -114 57 -230 115 -315 173 -280 694 -212 453 -256 143 -202 113 -540 352 -116 257 -116 457 -56 109 -58 143 -230 259 -144 259 -525 119 -408 247 -112 389 -72 431 -96 137 -236 97 -474 201 -298 71 -82 55 -116 55 -112 199 -174 191 -86 143 -144 115 -114 317 -86 85 -230 87 -114 259 -84 107 -130 143 -94 153 -86 135 -94 215 -72 239 -94 435 -96 263 -142 166 -334 87 -194 179 -96 115 -284 135 -56 57 -144 463 -204 143 -316 201 -58 403 -86 141 -288 85 -202 139 -397 171 -174 305 -202 85 -144 373 -253 161 -492 181 -191 95 -216 315 -191 71 -166 97 -126 337 -96 71 -96 189 -168 295 -84 197 -86 259 -345 137 -144 167 -796 115 -344 455 -72 119 -96 119 -550 209 -88 85 -86 143 -340 167 -260 143 -537 85 -226 51 -537 57 -260 315 -461 51 -84 199 -358 383 -96 143 -257 115 -86 173 -86 201 -144 143 -316 85 -86 479 -88 85 -72 71 -104 115 -116 267 -72 137 -144 143 -116 85 -86 373 -288 115 -200 87 -114 259 -114 259 -462 143 -144 171 -86 57 -58 137 -144 57 -634 343 -72 205 -86 143 -258 57 -232 113 -230 461 -58 185 -74 537 -86 -RAW_Data: 535 -142 57 -58 55 -116 115 -432 85 -172 259 -192 167 -120 117 -72 119 -240 334 -72 71 -267 285 -144 119 -374 85 -88 85 -114 143 -202 229 -58 143 -202 115 -202 171 -86 71 -144 87 -56 173 -373 143 -116 113 -462 169 -80 215 -148 115 -336 85 -230 163 -432 85 -374 639 -174 85 -58 57 -82 295 -352 269 -532 414 -322 95 -287 263 -268 115 -56 259 -76 85 -282 401 -305 516 -114 115 -202 171 -86 451 -110 85 -346 201 -274 149 -202 85 -364 366 -258 57 -114 259 -172 142 -144 85 -116 85 -480 171 -144 57 -352 115 -116 535 -404 315 -202 163 -158 517 -316 215 -98 85 -346 85 -144 87 -86 257 -82 167 -58 85 -116 113 -894 233 -186 77 -266 147 -72 71 -82 57 -86 171 -58 57 -86 201 -364 143 -202 115 -114 85 -88 113 -86 87 -230 57 -76 613 -72 85 -96 209 -346 458 -58 547 -490 201 -315 315 -116 75 -168 359 -335 95 -384 93 -120 71 -312 251 -366 233 -96 189 -240 263 -192 271 -58 115 -58 229 -346 459 -174 113 -144 173 -144 218 -224 57 -116 215 -72 103 -202 513 -210 433 -116 113 -174 650 -273 147 -450 375 -86 115 -172 536 -84 85 -230 85 -58 195 -468 287 -110 551 -214 167 -311 213 -250 85 -58 85 -355 113 -230 115 -144 117 -288 195 -202 57 -376 123 -144 236 -168 553 -284 119 -72 143 -188 161 -120 93 -312 335 -58 55 -260 105 -244 143 -120 381 -268 173 -268 635 -168 453 -318 71 -167 71 -406 191 -172 215 -408 119 -144 93 -120 97 -130 143 -192 308 -122 147 -550 313 -96 139 -162 167 -96 431 -80 83 -112 201 -86 287 -86 229 -116 57 -288 113 -174 143 -116 113 -144 115 -518 57 -230 57 -172 231 -86 113 -314 183 -144 119 -72 165 -446 81 -86 135 -190 143 -96 71 -72 411 -96 143 -120 69 -216 349 -72 95 -96 517 -646 163 -86 113 -116 171 -116 143 -116 113 -287 259 -114 517 -168 141 -116 105 -72 95 -96 311 -118 159 -310 191 -54 143 -258 115 -450 219 -54 339 -372 239 -72 167 -174 113 -58 57 -144 259 -172 143 -336 113 -174 85 -230 83 -668 85 -202 113 -144 57 -116 373 -316 719 -288 115 -58 75 -120 139 -144 229 -144 57 -144 171 -192 391 -202 403 -58 315 -188 259 -56 115 -144 85 -404 57 -58 105 -102 429 -406 81 -172 57 -144 287 -230 287 -220 317 -458 283 -58 113 -86 269 -72 281 -58 85 -202 113 -52 421 -58 229 -480 259 -58 143 -660 155 -638 123 -86 57 -86 143 -346 143 -144 57 -144 \ No newline at end of file +RAW_Data: 535 -142 57 -58 55 -116 115 -432 85 -172 259 -192 167 -120 117 -72 119 -240 334 -72 71 -267 285 -144 119 -374 85 -88 85 -114 143 -202 229 -58 143 -202 115 -202 171 -86 71 -144 87 -56 173 -373 143 -116 113 -462 169 -80 215 -148 115 -336 85 -230 163 -432 85 -374 639 -174 85 -58 57 -82 295 -352 269 -532 414 -322 95 -287 263 -268 115 -56 259 -76 85 -282 401 -305 516 -114 115 -202 171 -86 451 -110 85 -346 201 -274 149 -202 85 -364 366 -258 57 -114 259 -172 142 -144 85 -116 85 -480 171 -144 57 -352 115 -116 535 -404 315 -202 163 -158 517 -316 215 -98 85 -346 85 -144 87 -86 257 -82 167 -58 85 -116 113 -894 233 -186 77 -266 147 -72 71 -82 57 -86 171 -58 57 -86 201 -364 143 -202 115 -114 85 -88 113 -86 87 -230 57 -76 613 -72 85 -96 209 -346 458 -58 547 -490 201 -315 315 -116 75 -168 359 -335 95 -384 93 -120 71 -312 251 -366 233 -96 189 -240 263 -192 271 -58 115 -58 229 -346 459 -174 113 -144 173 -144 218 -224 57 -116 215 -72 103 -202 513 -210 433 -116 113 -174 650 -273 147 -450 375 -86 115 -172 536 -84 85 -230 85 -58 195 -468 287 -110 551 -214 167 -311 213 -250 85 -58 85 -355 113 -230 115 -144 117 -288 195 -202 57 -376 123 -144 236 -168 553 -284 119 -72 143 -188 161 -120 93 -312 335 -58 55 -260 105 -244 143 -120 381 -268 173 -268 635 -168 453 -318 71 -167 71 -406 191 -172 215 -408 119 -144 93 -120 97 -130 143 -192 308 -122 147 -550 313 -96 139 -162 167 -96 431 -80 83 -112 201 -86 287 -86 229 -116 57 -288 113 -174 143 -116 113 -144 115 -518 57 -230 57 -172 231 -86 113 -314 183 -144 119 -72 165 -446 81 -86 135 -190 143 -96 71 -72 411 -96 143 -120 69 -216 349 -72 95 -96 517 -646 163 -86 113 -116 171 -116 143 -116 113 -287 259 -114 517 -168 141 -116 105 -72 95 -96 311 -118 159 -310 191 -54 143 -258 115 -450 219 -54 339 -372 239 -72 167 -174 113 -58 57 -144 259 -172 143 -336 113 -174 85 -230 83 -668 85 -202 113 -144 57 -116 373 -316 719 -288 115 -58 75 -120 139 -144 229 -144 57 -144 171 -192 391 -202 403 -58 315 -188 259 -56 115 -144 85 -404 57 -58 105 -102 429 -406 81 -172 57 -144 287 -230 287 -220 317 -458 283 -58 113 -86 269 -72 281 -58 85 -202 113 -52 421 -58 229 -480 259 -58 143 -660 155 -638 123 -86 57 -86 143 -346 143 -144 57 -144 +RAW_Data: 2442 -312 275 -972 949 -310 941 -322 923 -342 921 -352 923 -334 281 -954 945 -350 279 -958 907 -354 289 -980 909 -352 281 -962 907 -330 311 -964 913 -350 317 -930 933 -344 921 -352 893 -330 311 -954 943 -318 315 -958 909 -324 947 -7854 953 -322 289 -948 939 -354 927 -332 911 -324 943 -344 917 -318 317 -964 905 -344 303 -942 947 -312 319 -960 913 -348 281 -958 941 -322 295 -978 905 -350 279 -962 931 -328 947 -324 939 -346 267 -964 935 -348 283 -938 953 -318 931 -7868 935 -346 269 -968 953 -310 941 -322 921 -330 935 -342 931 -318 311 -962 939 -290 337 -950 909 -352 317 -924 943 -324 313 -938 941 -318 317 -932 939 -344 301 -938 933 -350 921 -322 959 -310 301 -942 933 -352 317 -926 957 -314 919 -7868 943 -314 317 -958 909 -322 951 -344 919 -352 921 -324 937 -326 281 -964 941 -318 317 -930 939 -344 301 -938 933 -352 281 -962 953 -314 317 -922 933 -330 315 -954 943 -318 921 -342 943 -320 291 -980 909 -354 281 -962 943 -296 967 -7836 943 -332 309 -950 935 -318 929 -340 943 -320 921 -344 921 -354 283 -960 943 -296 309 -964 945 -318 279 -964 941 -322 333 -944 939 -314 279 -992 903 -342 319 -932 933 -330 931 -340 929 -348 281 -964 935 -334 281 -970 927 -346 921 -7862 951 -314 319 -922 953 -320 923 -346 921 -320 965 -298 943 -324 313 -942 941 -320 317 -930 941 -344 303 -940 945 -312 321 -940 953 -314 303 -960 933 -348 287 -962 911 -352 917 -350 905 -324 333 -918 971 -322 317 -924 945 -324 937 -7872 919 -324 317 -942 941 -318 933 -330 943 -324 943 -310 951 -318 317 -930 939 -344 301 -938 933 -352 317 -926 953 -314 319 -924 939 -324 331 -950 907 -354 315 -926 945 -324 939 -312 953 -318 317 -930 937 -344 301 -940 947 -348 909 -7864 949 -310 319 -956 915 -350 919 -348 905 -322 963 -296 935 -348 317 -922 951 -322 295 -976 939 -314 281 -996 915 -326 307 -940 959 -310 301 -966 935 -346 285 -958 915 -348 921 -348 903 -354 303 -948 911 -350 315 -926 945 -324 941 -7874 943 -290 319 -942 973 -318 929 -314 937 -328 941 -324 939 -310 303 -962 933 -352 285 -962 949 -314 319 -924 951 -320 293 -948 941 -354 283 -962 943 -294 309 -966 943 -320 931 -328 943 -326 311 -940 939 -320 309 -958 933 -338 943 -7840 933 -352 277 -964 941 -322 923 -344 923 -350 931 -310 955 -320 291 -974 907 -350 281 -958 963 -298 313 -956 945 -314 311 -960 937 -312 311 -966 909 -324 319 -944 941 -354 929 -298 945 -324 315 -940 +RAW_Data: 943 -354 281 -964 905 -330 933 -7868 951 -324 315 -938 943 -354 893 -330 943 -324 943 -344 919 -318 317 -962 903 -344 301 -974 903 -350 317 -932 931 -342 269 -972 949 -346 285 -938 955 -310 301 -964 935 -348 921 -320 921 -344 301 -940 935 -350 317 -930 929 -318 937 -7872 939 -344 301 -940 947 -346 917 -322 921 -344 923 -352 927 -334 281 -970 925 -334 277 -982 943 -318 317 -932 931 -344 301 -936 935 -350 281 -960 957 -312 303 -960 935 -346 907 -322 929 -344 301 -942 935 -350 317 -924 955 -312 951 -7858 919 -342 309 -940 949 -348 909 -322 923 -344 923 -352 923 -336 317 -924 945 -312 311 -966 921 -340 317 -924 947 -350 281 -958 941 -322 291 -976 905 -350 279 -960 935 -342 943 -320 919 -330 311 -958 943 -320 315 -932 935 -344 919 -7866 957 -312 303 -964 917 -342 945 -320 923 -344 923 -354 929 -298 315 -956 941 -318 315 -960 911 -324 317 -942 939 -354 281 -964 941 -294 311 -968 943 -318 317 -932 937 -330 931 -350 919 -348 283 -960 917 -350 317 -922 939 -322 965 -7864 921 -324 329 -950 909 -354 923 -336 913 -322 947 -344 919 -354 281 -962 941 -294 311 -960 935 -354 281 -962 939 -294 311 -964 937 -354 281 -964 941 -296 309 -964 939 -318 931 -330 945 -324 315 -940 939 -354 281 -964 909 -344 921 -7862 963 -304 307 -976 933 -320 929 -328 941 -324 939 -348 915 -320 317 -930 939 -344 301 -940 965 -320 319 -926 953 -312 303 -960 933 -312 321 -960 913 -348 319 -924 943 -320 959 -310 921 -354 319 -924 943 -324 311 -938 941 -318 957 -7862 943 -318 317 -932 933 -344 925 -352 897 -332 943 -324 943 -346 267 -966 951 -310 321 -960 911 -350 281 -958 949 -320 291 -978 937 -316 279 -964 949 -326 309 -944 943 -314 959 -318 933 -336 317 -934 933 -344 267 -964 937 -350 905 -7896 943 -318 319 -926 955 -314 919 -350 935 -324 941 -294 967 -312 303 -962 933 -348 285 -960 917 -348 317 -922 941 -322 329 -950 907 -354 315 -926 943 -326 313 -940 941 -352 893 -332 949 -324 315 -938 941 -352 283 -962 943 -310 925 -7890 931 -344 269 -968 949 -310 943 -320 923 -350 937 -310 955 -318 317 -930 935 -344 301 -942 947 -346 285 -958 915 -346 317 -924 951 -322 295 -982 905 -352 317 -924 945 -324 941 -346 917 -318 317 -962 905 -330 311 -956 937 -352 897 -7878 939 -354 283 -960 941 -294 965 -312 953 -318 385 -201512 165 -198 265 -526 229 -298 755 -164 61687 -17310 131 -1056 99 -296 195 -296 65 -66 1617 diff --git a/documentation/file_formats/InfraredFileFormats.md b/documentation/file_formats/InfraredFileFormats.md new file mode 100644 index 00000000..a6d6c276 --- /dev/null +++ b/documentation/file_formats/InfraredFileFormats.md @@ -0,0 +1,111 @@ +# Infrared Flipper File Formats + +## Infrared Remote File Format +### Example + + Filetype: IR signals file + Version: 1 + # + name: Button_1 + type: parsed + protocol: NECext + address: EE 87 00 00 + command: 5D A0 00 00 + # + name: Button_2 + type: raw + frequency: 38000 + duty_cycle: 0.330000 + data: 504 3432 502 483 500 484 510 502 502 482 501 485 509 1452 504 1458 509 1452 504 481 501 474 509 3420 503 + # + name: Button_3 + type: parsed + protocol: SIRC + address: 01 00 00 00 + command: 15 00 00 00 + +### Description +Filename extension: `.ir` + +This file format is used to store an infrared remote that consists of an arbitrary number of buttons. +Each button is separated from others by a comment character (`#`) for better readability. + +Known protocols are represented in the `parsed` form, whereas non-recognised signals may be saved and re-transmitted as `raw` data. + +#### Version history: +1. Initial version. + +#### Format fields +| Name | Use | Type | Description | +| ---------- | ------- | ------ |------------ | +| name | both | string | Name of the button. Only printable ASCII characters are allowed. | +| type | both | string | Type of the signal. Must be `parsed` or `raw`. | +| protocol | parsed | string | Name of the infrared protocol. Refer to `ir` console command for the complete list of supported protocols. | +| address | parsed | hex | Payload address. Must be 4 bytes long. | +| command | parsed | hex | Payload command. Must be 4 bytes long. | +| frequency | raw | uint32 | Carrier frequency, in Hertz, usually 38000 Hz. | +| duty_cycle | raw | float | Carrier duty cycle, usually 0.33. | +| data | raw | uint32 | Raw signal timings, in microseconds between logic level changes. Individual elements must be space-separated. Maximum timings amount is 1024. | + +## Infrared Library File Format +### Examples +- [TV Universal Library](/assets/resources/infrared/assets/tv.ir) +- [A/C Universal Library](/assets/resources/infrared/assets/ac.ir) +- [Audio Universal Library](/assets/resources/infrared/assets/audio.ir) + +### Description +Filename extension: `.ir` + +This file format is used to store universal remote libraries. It is identical to the previous format, differing only in the `Filetype` field.\ +It also has predefined button names for each universal library type, so that the universal remote application could understand them. +See [Universal Remotes](/documentation/UniversalRemotes.md) for more information. + +### Version history: +1. Initial version. + +## Infrared Test File Format +### Examples +See [Infrared Unit Tests](/assets/unit_tests/infrared/) for various examples. +### Description +Filename extension: `.irtest` + +This file format is used to store technical test data that is too large to keep directly in the firmware. +It is mostly similar to the two previous formats, with the main difference being the addition of the parsed signal arrays. + +Each infrared protocol must have corresponding unit tests complete with an `.irtest` file. + +Known protocols are represented in the `parsed_array` form, whereas raw data has the `raw` type.\ +Note: a single parsed signal must be represented as an array of size 1. + +### Version history: +1. Initial version. + +#### Format fields +| Name | Use | Type | Description | +| ---------- | ------------ | ------ |------------ | +| name | both | string | Name of the signal. Only printable ASCII characters are allowed. | +| type | both | string | Type of the signal. Must be `parsed_array` or `raw`. | +| count | parsed_array | uint32 | The number of parsed signals in an array. Must be at least 1. | +| protocol | parsed_array | string | Same as in previous formats. | +| address | parsed_array | hex | Ditto. | +| command | parsed_array | hex | Ditto. | +| repeat | parsed_array | bool | Indicates whether the signal is a repeated button press. | +| frequency | raw | uint32 | Same as in previous formats. | +| duty_cycle | raw | float | Ditto. | +| data | raw | uint32 | Ditto. | + +#### Signal names +The signal names in an `.irtest` file folow a convention ``, where the name is one of: +- decoder_input +- decoder_expected +- encoder_decoder_input, + +and the number is a sequential integer: 1, 2, 3...etc, which produces names like `decoder_input1`, `encoder_decoder_input3`, and so on. + +| Name | Type | Description | +| --------------------- | ------------ | ----------- | +| decoder_input | raw | A raw signal contaning the decoder input. Is also used as the expected encoder output. | +| decoder_expected | parsed_array | An array of parsed signals containing the expected decoder output. Is also used as the encoder input. | +| encoder_decoder_input | parsed_array | An array of parsed signals containing both the encoder-decoder input and expected output. | + +See [Unit Tests](/documentation/UnitTests.md#infrared) for more info. diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md new file mode 100644 index 00000000..90dcaea1 --- /dev/null +++ b/documentation/file_formats/NfcFileFormats.md @@ -0,0 +1,255 @@ +# NFC Flipper File Formats + +## NFC-A (UID) + Header + +### Example + + Filetype: Flipper NFC device + Version: 3 + # Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card + Device type: UID + # UID, ATQA and SAK are common for all formats + UID: 04 85 92 8A A0 61 81 + ATQA: 00 44 + SAK: 00 + +### Description + +This file format is used to store the UID, SAK and ATQA of a NFC-A device. It does not store any internal data, so it can be used for multiple different card types. Also used as a header for other formats. + +Version differences: + + 1. Initial version, deprecated + 2. LSB ATQA (e.g. 4400 instead of 0044) + 3. MSB ATQA (current version) + +UID can be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long. + +## Mifare Ultralight/NTAG + +### Example + + Filetype: Flipper NFC device + Version: 3 + # Nfc device type can be UID, Mifare Ultralight, Mifare Classic + Device type: NTAG216 + # UID, ATQA and SAK are common for all formats + UID: 04 85 90 54 12 98 23 + ATQA: 00 44 + SAK: 00 + # Mifare Ultralight specific data + Data format version: 1 + Signature: 1B 84 EB 70 BD 4C BD 1B 1D E4 98 0B 18 58 BD 7C 72 85 B4 E4 7B 38 8E 96 CF 88 6B EE A3 43 AD 90 + Mifare version: 00 04 04 02 01 00 13 03 + Counter 0: 0 + Tearing 0: 00 + Counter 1: 0 + Tearing 1: 00 + Counter 2: 0 + Tearing 2: 00 + Pages total: 231 + Pages read: 231 + Page 0: 04 85 92 9B + Page 1: 8A A0 61 81 + Page 2: CA 48 0F 00 + ... + Page 224: 00 00 00 00 + Page 225: 00 00 00 00 + Page 226: 00 00 7F BD + Page 227: 04 00 00 E2 + Page 228: 00 05 00 00 + Page 229: 00 00 00 00 + Page 230: 00 00 00 00 + Failed authentication attempts: 0 + +### Description + +This file format is used to store the UID, SAK and ATQA of a Mifare Ultralight/NTAG device. It also stores the internal data of the card, the signature, the version, and the counters. The data is stored in pages, just like on the card itself. + +The "Signature" field contains the reply of the tag to the READ_SIG command. More on that can be found here: (page 31) + +The "Mifare version" field is not related to the file format version, but to the Mifare Ultralight version. It contains the responce of the tag to the GET_VERSION command. More on that can be found here: (page 21) + +Other fields are the direct representation of the card's internal state, more on them can be found in the same datasheet. + +Version differences: + + 1. Current version + +## Mifare Classic + +### Example + + Filetype: Flipper NFC device + Version: 3 + # Nfc device type can be UID, Mifare Ultralight, Mifare Classic + Device type: Mifare Classic + # UID, ATQA and SAK are common for all formats + UID: BA E2 7C 9D + ATQA: 00 02 + SAK: 18 + # Mifare Classic specific data + Mifare Classic type: 4K + Data format version: 2 + # Mifare Classic blocks, '??' means unknown data + Block 0: BA E2 7C 9D B9 18 02 00 46 44 53 37 30 56 30 31 + Block 1: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 3: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF + Block 4: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 5: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 6: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 7: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF + ... + Block 238: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 239: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF + Block 240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 241: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 242: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 243: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 244: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 245: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 246: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 247: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 248: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 249: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 251: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 252: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 253: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 254: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + Block 255: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF + +### Description + +This file format is used to store the NFC-A and Mifare Classic specific data of a Mifare Classic card. Aside from the NFC-A data, it stores the card type (1K/4K) and the internal data of the card. The data is stored in blocks, there is no sector grouping. If the block's data is unknown, it is represented by '??'. Otherwise, the data is represented as a hex string. + +Version differences: + + 1. Initial version, has Key A and Key B masks instead of marking unknown data with '??'. + +Example: + + ... + Data format version: 1 + # Key map is the bit mask indicating valid key in each sector + Key A map: 000000000000FFFF + Key B map: 000000000000FFFF + # Mifare Classic blocks + ... + + 2. Current version + +## Mifare DESFire + +### Example + + Filetype: Flipper NFC device + Version: 3 + # Nfc device type can be UID, Mifare Ultralight, Mifare Classic + Device type: Mifare DESFire + # UID, ATQA and SAK are common for all formats + UID: 04 2F 19 0A CD 66 80 + ATQA: 03 44 + SAK: 20 + # Mifare DESFire specific data + PICC Version: 04 01 01 12 00 1A 05 04 01 01 02 01 1A 05 04 2F 19 0A CD 66 80 CE ED D4 51 80 31 19 + PICC Free Memory: 7520 + PICC Change Key ID: 00 + PICC Config Changeable: true + PICC Free Create Delete: true + PICC Free Directory List: true + PICC Key Changeable: true + PICC Max Keys: 01 + PICC Key 0 Version: 00 + Application Count: 1 + Application IDs: 56 34 12 + Application 563412 Change Key ID: 00 + Application 563412 Config Changeable: true + Application 563412 Free Create Delete: true + Application 563412 Free Directory List: true + Application 563412 Key Changeable: true + Application 563412 Max Keys: 0E + Application 563412 Key 0 Version: 00 + Application 563412 Key 1 Version: 00 + Application 563412 Key 2 Version: 00 + Application 563412 Key 3 Version: 00 + Application 563412 Key 4 Version: 00 + Application 563412 Key 5 Version: 00 + Application 563412 Key 6 Version: 00 + Application 563412 Key 7 Version: 00 + Application 563412 Key 8 Version: 00 + Application 563412 Key 9 Version: 00 + Application 563412 Key 10 Version: 00 + Application 563412 Key 11 Version: 00 + Application 563412 Key 12 Version: 00 + Application 563412 Key 13 Version: 00 + Application 563412 File IDs: 01 + Application 563412 File 1 Type: 00 + Application 563412 File 1 Communication Settings: 00 + Application 563412 File 1 Access Rights: EE EE + Application 563412 File 1 Size: 256 + Application 563412 File 1: 13 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +### Description + +This file format is used to store the NFC-A and Mifare DESFire specific data of a Mifare DESFire card. Aside from the NFC-A data, it stores the card type (DESFire) and the internal data of the card. The data is stored per-application, and per-file. Here, the card was written using those pm3 commands: + + hf mfdes createapp --aid 123456 --fid 2345 --dfname astra + hf mfdes createfile --aid 123456 --fid 01 --isofid 0001 --size 000100 + hf mfdes write --aid 123456 --fid 01 -d 1337 + +Version differences: + None, there are no versions yet. + +## Mifare Classic Dictionary + +### Example + + # Key dictionary from https://github.com/ikarus23/MifareClassicTool.git + + # More well known keys! + # Standard keys + FFFFFFFFFFFF + A0A1A2A3A4A5 + D3F7D3F7D3F7 + 000000000000 + + # Keys from mfoc + B0B1B2B3B4B5 + 4D3A99C351DD + 1A982C7E459A + AABBCCDDEEFF + 714C5C886E97 + 587EE5F9350F + A0478CC39091 + 533CB6C723F6 + 8FD0A4F256E9 + ... + +### Description + +This file contains a list of Mifare Classic keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well. + +## EMV resources + +### Example + + Filetype: Flipper EMV resources + Version: 1 + # EMV currency code: currency name + 0997: USN + 0994: XSU + 0990: CLF + 0986: BRL + 0985: PLN + 0984: BOV + ... + +### Description + +This file stores a list of EMV currency codes, country codes, or AIDs and their names. Each line contains a hex value and a name separated by a colon and a space. + +Version differences: + + 1. Initial version diff --git a/fbt_options.py b/fbt_options.py index 7a805c99..4850389a 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -81,7 +81,6 @@ FIRMWARE_APPS = { "basic_services", "updater_app", "unit_tests", - "nfc", ], } diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 80aaf7fa..8503a838 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,10.1,, +Version,+,11.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -29,6 +29,7 @@ Header,+,applications/services/gui/view_dispatcher.h,, Header,+,applications/services/gui/view_stack.h,, Header,+,applications/services/input/input.h,, Header,+,applications/services/loader/loader.h,, +Header,+,applications/services/locale/locale.h,, Header,+,applications/services/notification/notification.h,, Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, @@ -561,6 +562,8 @@ Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* +Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" +Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* @@ -751,6 +754,7 @@ Function,+,elements_multiline_text,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,elements_multiline_text_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*" Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const char*" Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float" +Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool" Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t" Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t" Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t" @@ -1265,6 +1269,9 @@ Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime* Function,+,furi_hal_rtc_get_fault_data,uint32_t, Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode, +Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat, +Function,+,furi_hal_rtc_get_locale_timeformat,FuriHalRtcLocaleTimeFormat, +Function,+,furi_hal_rtc_get_locale_units,FuriHalRtcLocaleUnits, Function,+,furi_hal_rtc_get_log_level,uint8_t, Function,+,furi_hal_rtc_get_pin_fails,uint32_t, Function,+,furi_hal_rtc_get_register,uint32_t,FuriHalRtcRegister @@ -1278,11 +1285,18 @@ Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime* Function,+,furi_hal_rtc_set_fault_data,void,uint32_t Function,+,furi_hal_rtc_set_flag,void,FuriHalRtcFlag Function,+,furi_hal_rtc_set_heap_track_mode,void,FuriHalRtcHeapTrackMode +Function,+,furi_hal_rtc_set_locale_dateformat,void,FuriHalRtcLocaleDateFormat +Function,+,furi_hal_rtc_set_locale_timeformat,void,FuriHalRtcLocaleTimeFormat +Function,+,furi_hal_rtc_set_locale_units,void,FuriHalRtcLocaleUnits Function,+,furi_hal_rtc_set_log_level,void,uint8_t Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* +Function,+,furi_hal_speaker_acquire,_Bool,uint32_t +Function,-,furi_hal_speaker_deinit,void, Function,-,furi_hal_speaker_init,void, +Function,+,furi_hal_speaker_is_mine,_Bool, +Function,+,furi_hal_speaker_release,void, Function,+,furi_hal_speaker_set_volume,void,float Function,+,furi_hal_speaker_start,void,"float, float" Function,+,furi_hal_speaker_stop,void, @@ -1316,6 +1330,7 @@ Function,+,furi_hal_subghz_read_packet,void,"uint8_t*, uint8_t*" Function,+,furi_hal_subghz_reset,void, Function,+,furi_hal_subghz_rx,void, Function,+,furi_hal_subghz_rx_pipe_not_empty,_Bool, +Function,+,furi_hal_subghz_set_async_mirror_pin,void,const GpioPin* Function,+,furi_hal_subghz_set_frequency,uint32_t,uint32_t Function,+,furi_hal_subghz_set_frequency_and_path,uint32_t,uint32_t Function,+,furi_hal_subghz_set_path,void,FuriHalSubGhzPath @@ -1731,6 +1746,16 @@ Function,+,loader_update_menu,void, Function,+,loading_alloc,Loading*, Function,+,loading_free,void,Loading* Function,+,loading_get_view,View*,Loading* +Function,+,locale_celsius_to_fahrenheit,float,float +Function,+,locale_fahrenheit_to_celsius,float,float +Function,+,locale_format_date,void,"FuriString*, const FuriHalRtcDateTime*, const LocaleDateFormat, const char*" +Function,+,locale_format_time,void,"FuriString*, const FuriHalRtcDateTime*, const LocaleTimeFormat, const _Bool" +Function,+,locale_get_date_format,LocaleDateFormat, +Function,+,locale_get_measurement_unit,LocaleMeasurementUnits, +Function,+,locale_get_time_format,LocaleTimeFormat, +Function,+,locale_set_date_format,void,LocaleDateFormat +Function,+,locale_set_measurement_unit,void,LocaleMeasurementUnits +Function,+,locale_set_time_format,void,LocaleTimeFormat Function,-,localtime,tm*,const time_t* Function,-,localtime_r,tm*,"const time_t*, tm*" Function,-,log,double,double @@ -2298,6 +2323,7 @@ Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcApp Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t" Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*" Function,-,rpmatch,int,const char* +Function,+,saved_struct_get_payload_size,_Bool,"const char*, uint8_t, uint8_t, size_t*" Function,+,saved_struct_load,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t" Function,-,scalbln,double,"double, long int" diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index c38cbfec..e5fa8c76 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -29,12 +29,15 @@ typedef struct { uint8_t log_level : 4; uint8_t log_reserved : 4; uint8_t flags; - uint8_t boot_mode : 4; - uint8_t heap_track_mode : 2; - uint16_t reserved : 10; -} DeveloperReg; + FuriHalRtcBootMode boot_mode : 4; + FuriHalRtcHeapTrackMode heap_track_mode : 2; + FuriHalRtcLocaleUnits locale_units : 1; + FuriHalRtcLocaleTimeFormat locale_timeformat : 1; + FuriHalRtcLocaleDateFormat locale_dateformat : 2; + uint8_t reserved : 6; +} SystemReg; -_Static_assert(sizeof(DeveloperReg) == 4, "DeveloperReg size mismatch"); +_Static_assert(sizeof(SystemReg) == 4, "SystemReg size mismatch"); #define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 #define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) @@ -172,7 +175,7 @@ void furi_hal_rtc_set_register(FuriHalRtcRegister reg, uint32_t value) { void furi_hal_rtc_set_log_level(uint8_t level) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; data->log_level = level; furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); furi_log_set_level(level); @@ -180,13 +183,13 @@ void furi_hal_rtc_set_log_level(uint8_t level) { uint8_t furi_hal_rtc_get_log_level() { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; return data->log_level; } void furi_hal_rtc_set_flag(FuriHalRtcFlag flag) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; data->flags |= flag; furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); @@ -197,7 +200,7 @@ void furi_hal_rtc_set_flag(FuriHalRtcFlag flag) { void furi_hal_rtc_reset_flag(FuriHalRtcFlag flag) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; data->flags &= ~flag; furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); @@ -208,34 +211,73 @@ void furi_hal_rtc_reset_flag(FuriHalRtcFlag flag) { bool furi_hal_rtc_is_flag_set(FuriHalRtcFlag flag) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; return data->flags & flag; } void furi_hal_rtc_set_boot_mode(FuriHalRtcBootMode mode) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; data->boot_mode = mode; furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); } FuriHalRtcBootMode furi_hal_rtc_get_boot_mode() { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; - return (FuriHalRtcBootMode)data->boot_mode; + SystemReg* data = (SystemReg*)&data_reg; + return data->boot_mode; } void furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackMode mode) { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; + SystemReg* data = (SystemReg*)&data_reg; data->heap_track_mode = mode; furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); } FuriHalRtcHeapTrackMode furi_hal_rtc_get_heap_track_mode() { uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); - DeveloperReg* data = (DeveloperReg*)&data_reg; - return (FuriHalRtcHeapTrackMode)data->heap_track_mode; + SystemReg* data = (SystemReg*)&data_reg; + return data->heap_track_mode; +} + +void furi_hal_rtc_set_locale_units(FuriHalRtcLocaleUnits value) { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + data->locale_units = value; + furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); +} + +FuriHalRtcLocaleUnits furi_hal_rtc_get_locale_units() { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + return data->locale_units; +} + +void furi_hal_rtc_set_locale_timeformat(FuriHalRtcLocaleTimeFormat value) { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + data->locale_timeformat = value; + furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); +} + +FuriHalRtcLocaleTimeFormat furi_hal_rtc_get_locale_timeformat() { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + return data->locale_timeformat; +} + +void furi_hal_rtc_set_locale_dateformat(FuriHalRtcLocaleDateFormat value) { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + data->locale_dateformat = value; + furi_hal_rtc_set_register(FuriHalRtcRegisterSystem, data_reg); +} + +FuriHalRtcLocaleDateFormat furi_hal_rtc_get_locale_dateformat() { + uint32_t data_reg = furi_hal_rtc_get_register(FuriHalRtcRegisterSystem); + SystemReg* data = (SystemReg*)&data_reg; + return data->locale_dateformat; } void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime) { diff --git a/firmware/targets/f7/furi_hal/furi_hal_speaker.c b/firmware/targets/f7/furi_hal/furi_hal_speaker.c index 03a7f094..c4a0bdd1 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_speaker.c +++ b/firmware/targets/f7/furi_hal/furi_hal_speaker.c @@ -1,23 +1,66 @@ #include #include #include +#include #include +#include + +#define TAG "FuriHalSpeaker" #define FURI_HAL_SPEAKER_TIMER TIM16 #define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1 #define FURI_HAL_SPEAKER_PRESCALER 500 #define FURI_HAL_SPEAKER_MAX_VOLUME 60 +static FuriMutex* furi_hal_speaker_mutex = NULL; + // #define FURI_HAL_SPEAKER_NEW_VOLUME void furi_hal_speaker_init() { + furi_assert(furi_hal_speaker_mutex == NULL); + furi_hal_speaker_mutex = furi_mutex_alloc(FuriMutexTypeNormal); FURI_CRITICAL_ENTER(); LL_TIM_DeInit(FURI_HAL_SPEAKER_TIMER); FURI_CRITICAL_EXIT(); + FURI_LOG_I(TAG, "Init OK"); +} - furi_hal_gpio_init_ex( - &gpio_speaker, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); +void furi_hal_speaker_deinit() { + furi_check(furi_hal_speaker_mutex != NULL); + LL_TIM_DeInit(FURI_HAL_SPEAKER_TIMER); + furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_mutex_free(furi_hal_speaker_mutex); + furi_hal_speaker_mutex = NULL; +} + +bool furi_hal_speaker_acquire(uint32_t timeout) { + furi_check(!FURI_IS_IRQ_MODE()); + + if(furi_mutex_acquire(furi_hal_speaker_mutex, timeout) == FuriStatusOk) { + furi_hal_power_insomnia_enter(); + furi_hal_gpio_init_ex( + &gpio_speaker, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); + return true; + } else { + return false; + } +} + +void furi_hal_speaker_release() { + furi_check(!FURI_IS_IRQ_MODE()); + furi_check(furi_hal_speaker_is_mine()); + + furi_hal_speaker_stop(); + furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_power_insomnia_exit(); + + furi_check(furi_mutex_release(furi_hal_speaker_mutex) == FuriStatusOk); +} + +bool furi_hal_speaker_is_mine() { + return (FURI_IS_IRQ_MODE()) || + (furi_mutex_get_owner(furi_hal_speaker_mutex) == furi_thread_get_current_id()); } static inline uint32_t furi_hal_speaker_calculate_autoreload(float frequency) { @@ -54,6 +97,8 @@ static inline uint32_t furi_hal_speaker_calculate_compare(float volume) { } void furi_hal_speaker_start(float frequency, float volume) { + furi_check(furi_hal_speaker_is_mine()); + if(volume <= 0) { furi_hal_speaker_stop(); return; @@ -75,6 +120,7 @@ void furi_hal_speaker_start(float frequency, float volume) { } void furi_hal_speaker_set_volume(float volume) { + furi_check(furi_hal_speaker_is_mine()); if(volume <= 0) { furi_hal_speaker_stop(); return; @@ -88,6 +134,7 @@ void furi_hal_speaker_set_volume(float volume) { } void furi_hal_speaker_stop() { + furi_check(furi_hal_speaker_is_mine()); LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER); LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER); } diff --git a/firmware/targets/f7/furi_hal/furi_hal_subghz.c b/firmware/targets/f7/furi_hal/furi_hal_subghz.c index 726b2d7f..3441fd96 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_subghz.c +++ b/firmware/targets/f7/furi_hal/furi_hal_subghz.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -17,39 +16,26 @@ #define TAG "FuriHalSubGhz" -/* - * Uncomment define to enable duplication of - * IO GO0 CC1101 to an external comb. - * Debug pin can be assigned - * gpio_ext_pc0 - * gpio_ext_pc1 - * gpio_ext_pc3 - * gpio_ext_pb2 - * gpio_ext_pb3 - * gpio_ext_pa4 - * gpio_ext_pa6 - * gpio_ext_pa7 - * Attention this setting switches pin to output. - * Make sure it is not connected directly to power or ground - */ - -//#define SUBGHZ_DEBUG_CC1101_PIN gpio_ext_pa7 -#ifdef SUBGHZ_DEBUG_CC1101_PIN -uint32_t subghz_debug_gpio_buff[2]; -#endif +static uint32_t furi_hal_subghz_debug_gpio_buff[2]; typedef struct { volatile SubGhzState state; volatile SubGhzRegulation regulation; volatile FuriHalSubGhzPreset preset; + const GpioPin* async_mirror_pin; } FuriHalSubGhz; volatile FuriHalSubGhz furi_hal_subghz = { .state = SubGhzStateInit, .regulation = SubGhzRegulationTxRx, .preset = FuriHalSubGhzPresetIDLE, + .async_mirror_pin = NULL, }; +void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin) { + furi_hal_subghz.async_mirror_pin = pin; +} + void furi_hal_subghz_init() { furi_assert(furi_hal_subghz.state == SubGhzStateInit); furi_hal_subghz.state = SubGhzStateIdle; @@ -372,6 +358,29 @@ void furi_hal_subghz_set_path(FuriHalSubGhzPath path) { furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); } +static bool furi_hal_subghz_start_debug() { + bool ret = false; + if(furi_hal_subghz.async_mirror_pin != NULL) { + furi_hal_gpio_init( + furi_hal_subghz.async_mirror_pin, + GpioModeOutputPushPull, + GpioPullNo, + GpioSpeedVeryHigh); + ret = true; + } + return ret; +} + +static bool furi_hal_subghz_stop_debug() { + bool ret = false; + if(furi_hal_subghz.async_mirror_pin != NULL) { + furi_hal_gpio_init( + furi_hal_subghz.async_mirror_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + ret = true; + } + return ret; +} + volatile uint32_t furi_hal_subghz_capture_delta_duration = 0; volatile FuriHalSubGhzCaptureCallback furi_hal_subghz_capture_callback = NULL; volatile void* furi_hal_subghz_capture_callback_context = NULL; @@ -382,9 +391,9 @@ static void furi_hal_subghz_capture_ISR() { LL_TIM_ClearFlag_CC1(TIM2); furi_hal_subghz_capture_delta_duration = LL_TIM_IC_GetCaptureCH1(TIM2); if(furi_hal_subghz_capture_callback) { -#ifdef SUBGHZ_DEBUG_CC1101_PIN - furi_hal_gpio_write(&SUBGHZ_DEBUG_CC1101_PIN, false); -#endif + if(furi_hal_subghz.async_mirror_pin != NULL) + furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, false); + furi_hal_subghz_capture_callback( true, furi_hal_subghz_capture_delta_duration, @@ -395,9 +404,9 @@ static void furi_hal_subghz_capture_ISR() { if(LL_TIM_IsActiveFlag_CC2(TIM2)) { LL_TIM_ClearFlag_CC2(TIM2); if(furi_hal_subghz_capture_callback) { -#ifdef SUBGHZ_DEBUG_CC1101_PIN - furi_hal_gpio_write(&SUBGHZ_DEBUG_CC1101_PIN, true); -#endif + if(furi_hal_subghz.async_mirror_pin != NULL) + furi_hal_gpio_write(furi_hal_subghz.async_mirror_pin, true); + furi_hal_subghz_capture_callback( false, LL_TIM_IC_GetCaptureCH2(TIM2) - furi_hal_subghz_capture_delta_duration, @@ -459,10 +468,8 @@ void furi_hal_subghz_start_async_rx(FuriHalSubGhzCaptureCallback callback, void* LL_TIM_SetCounter(TIM2, 0); LL_TIM_EnableCounter(TIM2); -#ifdef SUBGHZ_DEBUG_CC1101_PIN - furi_hal_gpio_init( - &SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); -#endif + // Start debug + furi_hal_subghz_start_debug(); // Switch to RX furi_hal_subghz_rx(); @@ -478,9 +485,8 @@ void furi_hal_subghz_stop_async_rx() { FURI_CRITICAL_ENTER(); LL_TIM_DeInit(TIM2); -#ifdef SUBGHZ_DEBUG_CC1101_PIN - furi_hal_gpio_init(&SUBGHZ_DEBUG_CC1101_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); -#endif + // Stop debug + furi_hal_subghz_stop_debug(); FURI_CRITICAL_EXIT(); furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL); @@ -673,30 +679,27 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* LL_TIM_SetCounter(TIM2, 0); LL_TIM_EnableCounter(TIM2); -#ifdef SUBGHZ_DEBUG_CC1101_PIN - furi_hal_gpio_init( - &SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + // Start debug + if(furi_hal_subghz_start_debug()) { + const GpioPin* gpio = furi_hal_subghz.async_mirror_pin; + furi_hal_subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; + furi_hal_subghz_debug_gpio_buff[1] = gpio->pin; - const GpioPin* gpio = &SUBGHZ_DEBUG_CC1101_PIN; - subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER; - subghz_debug_gpio_buff[1] = gpio->pin; - - dma_config.MemoryOrM2MDstAddress = (uint32_t)subghz_debug_gpio_buff; - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); - dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - dma_config.Mode = LL_DMA_MODE_CIRCULAR; - dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - dma_config.NbData = 2; - dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; - dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; - LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); - LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2); - LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); - -#endif + dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_debug_gpio_buff; + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); + dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + dma_config.Mode = LL_DMA_MODE_CIRCULAR; + dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + dma_config.NbData = 2; + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; + LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config); + LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 2); + LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); + } return true; } @@ -730,10 +733,10 @@ void furi_hal_subghz_stop_async_tx() { // Deinitialize GPIO furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); -#ifdef SUBGHZ_DEBUG_CC1101_PIN - LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); - furi_hal_gpio_init(&SUBGHZ_DEBUG_CC1101_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); -#endif + // Stop debug + if(furi_hal_subghz_stop_debug()) { + LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2); + } FURI_CRITICAL_EXIT(); diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index 5ce12227..fe095e74 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -59,53 +59,194 @@ typedef enum { FuriHalRtcRegisterMAX, /**< Service value, do not use */ } FuriHalRtcRegister; +typedef enum { + FuriHalRtcLocaleUnitsMetric = 0, /**< Metric measurement units */ + FuriHalRtcLocaleUnitsImperial = 1, /**< Imperial measurement units */ +} FuriHalRtcLocaleUnits; + +typedef enum { + FuriHalRtcLocaleTimeFormat24h = 0, /**< 24-hour format */ + FuriHalRtcLocaleTimeFormat12h = 1, /**< 12-hour format */ +} FuriHalRtcLocaleTimeFormat; + +typedef enum { + FuriHalRtcLocaleDateFormatDMY = 0, /**< Day/Month/Year */ + FuriHalRtcLocaleDateFormatMDY = 1, /**< Month/Day/Year */ + FuriHalRtcLocaleDateFormatYMD = 2, /**< Year/Month/Day */ +} FuriHalRtcLocaleDateFormat; + /** Early initialization */ void furi_hal_rtc_init_early(); -/** Early deinitialization */ +/** Early de-initialization */ void furi_hal_rtc_deinit_early(); /** Initialize RTC subsystem */ void furi_hal_rtc_init(); +/** Get RTC register content + * + * @param[in] reg The register identifier + * + * @return content of the register + */ uint32_t furi_hal_rtc_get_register(FuriHalRtcRegister reg); +/** Set register content + * + * @param[in] reg The register identifier + * @param[in] value The value to store into register + */ void furi_hal_rtc_set_register(FuriHalRtcRegister reg, uint32_t value); +/** Set Log Level value + * + * @param[in] level The level to store + */ void furi_hal_rtc_set_log_level(uint8_t level); +/** Get Log Level value + * + * @return The Log Level value + */ uint8_t furi_hal_rtc_get_log_level(); +/** Set RTC Flag + * + * @param[in] flag The flag to set + */ void furi_hal_rtc_set_flag(FuriHalRtcFlag flag); +/** Reset RTC Flag + * + * @param[in] flag The flag to reset + */ void furi_hal_rtc_reset_flag(FuriHalRtcFlag flag); +/** Check if RTC Flag is set + * + * @param[in] flag The flag to check + * + * @return true if set + */ bool furi_hal_rtc_is_flag_set(FuriHalRtcFlag flag); +/** Set RTC boot mode + * + * @param[in] mode The mode to set + */ void furi_hal_rtc_set_boot_mode(FuriHalRtcBootMode mode); +/** Get RTC boot mode + * + * @return The RTC boot mode. + */ FuriHalRtcBootMode furi_hal_rtc_get_boot_mode(); +/** Set Heap Track mode + * + * @param[in] mode The mode to set + */ void furi_hal_rtc_set_heap_track_mode(FuriHalRtcHeapTrackMode mode); +/** Get RTC Heap Track mode + * + * @return The RTC heap track mode. + */ FuriHalRtcHeapTrackMode furi_hal_rtc_get_heap_track_mode(); +/** Set locale units + * + * @param[in] mode The RTC Locale Units + */ +void furi_hal_rtc_set_locale_units(FuriHalRtcLocaleUnits value); + +/** Get RTC Locale Units + * + * @return The RTC Locale Units. + */ +FuriHalRtcLocaleUnits furi_hal_rtc_get_locale_units(); + +/** Set RTC Locale Time Format + * + * @param[in] value The RTC Locale Time Format + */ +void furi_hal_rtc_set_locale_timeformat(FuriHalRtcLocaleTimeFormat value); + +/** Get RTC Locale Time Format + * + * @return The RTC Locale Time Format. + */ +FuriHalRtcLocaleTimeFormat furi_hal_rtc_get_locale_timeformat(); + +/** Set RTC Locale Date Format + * + * @param[in] value The RTC Locale Date Format + */ +void furi_hal_rtc_set_locale_dateformat(FuriHalRtcLocaleDateFormat value); + +/** Get RTC Locale Date Format + * + * @return The RTC Locale Date Format + */ +FuriHalRtcLocaleDateFormat furi_hal_rtc_get_locale_dateformat(); + +/** Set RTC Date Time + * + * @param datetime The date time to set + */ void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime); +/** Get RTC Date Time + * + * @param datetime The datetime + */ void furi_hal_rtc_get_datetime(FuriHalRtcDateTime* datetime); +/** Validate Date Time + * + * @param datetime The datetime to validate + * + * @return { description_of_the_return_value } + */ bool furi_hal_rtc_validate_datetime(FuriHalRtcDateTime* datetime); +/** Set RTC Fault Data + * + * @param[in] value The value + */ void furi_hal_rtc_set_fault_data(uint32_t value); +/** Get RTC Fault Data + * + * @return RTC Fault Data value + */ uint32_t furi_hal_rtc_get_fault_data(); +/** Set Pin Fails count + * + * @param[in] value The Pin Fails count + */ void furi_hal_rtc_set_pin_fails(uint32_t value); +/** Get Pin Fails count + * + * @return Pin Fails Count + */ uint32_t furi_hal_rtc_get_pin_fails(); +/** Get UNIX Timestamp + * + * @return Unix Timestamp in seconds from UNIX epoch start + */ uint32_t furi_hal_rtc_get_timestamp(); +/** Convert DateTime to UNIX timestamp + * + * @param datetime The datetime + * + * @return UNIX Timestamp in seconds from UNIX epoch start + */ uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime); #ifdef __cplusplus diff --git a/firmware/targets/furi_hal_include/furi_hal_speaker.h b/firmware/targets/furi_hal_include/furi_hal_speaker.h index 67de41d9..0b33d923 100644 --- a/firmware/targets/furi_hal_include/furi_hal_speaker.h +++ b/firmware/targets/furi_hal_include/furi_hal_speaker.h @@ -4,16 +4,63 @@ */ #pragma once +#include + #ifdef __cplusplus extern "C" { #endif +/** Init speaker */ void furi_hal_speaker_init(); +/** Deinit speaker */ +void furi_hal_speaker_deinit(); + +/** Acquire speaker ownership + * + * @warning You must acquire speaker ownership before use + * + * @param timeout Timeout during which speaker ownership must be acquired + * + * @return bool returns true on success + */ +FURI_WARN_UNUSED bool furi_hal_speaker_acquire(uint32_t timeout); + +/** Release speaker ownership + * + * @warning You must release speaker ownership after use + */ +void furi_hal_speaker_release(); + +/** Check current process speaker ownership + * + * @warning always returns true if called from ISR + * + * @return bool returns true if process owns speaker + */ +bool furi_hal_speaker_is_mine(); + +/** Play a note + * + * @warning no ownership check if called from ISR + * + * @param frequency The frequency + * @param volume The volume + */ void furi_hal_speaker_start(float frequency, float volume); +/** Set volume + * + * @warning no ownership check if called from ISR + * + * @param volume The volume + */ void furi_hal_speaker_set_volume(float volume); +/** Stop playback + * + * @warning no ownership check if called from ISR + */ void furi_hal_speaker_stop(); #ifdef __cplusplus diff --git a/firmware/targets/furi_hal_include/furi_hal_subghz.h b/firmware/targets/furi_hal_include/furi_hal_subghz.h index 1f99386c..102981db 100644 --- a/firmware/targets/furi_hal_include/furi_hal_subghz.h +++ b/firmware/targets/furi_hal_include/furi_hal_subghz.h @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -34,9 +35,9 @@ typedef enum { /** Switchable Radio Paths */ typedef enum { FuriHalSubGhzPathIsolate, /**< Isolate Radio from antenna */ - FuriHalSubGhzPath433, /**< Center Frquency: 433MHz. Path 1: SW1RF1-SW2RF2, LCLCL */ - FuriHalSubGhzPath315, /**< Center Frquency: 315MHz. Path 2: SW1RF2-SW2RF1, LCLCLCL */ - FuriHalSubGhzPath868, /**< Center Frquency: 868MHz. Path 3: SW1RF3-SW2RF3, LCLC */ + FuriHalSubGhzPath433, /**< Center Frequency: 433MHz. Path 1: SW1RF1-SW2RF2, LCLCL */ + FuriHalSubGhzPath315, /**< Center Frequency: 315MHz. Path 2: SW1RF2-SW2RF1, LCLCLCL */ + FuriHalSubGhzPath868, /**< Center Frequency: 868MHz. Path 3: SW1RF3-SW2RF3, LCLC */ } FuriHalSubGhzPath; /** SubGhz state */ @@ -60,8 +61,17 @@ typedef enum { SubGhzRegulationTxRx, /**TxRx*/ } SubGhzRegulation; +/* Mirror RX/TX async modulation signal to specified pin + * + * @warning Configures pin to output mode. Make sure it is not connected + * directly to power or ground. + * + * @param[in] pin pointer to the gpio pin structure or NULL to disable + */ +void furi_hal_subghz_set_async_mirror_pin(const GpioPin* pin); + /** Initialize and switch to power save mode Used by internal API-HAL - * initalization routine Can be used to reinitialize device to safe state and + * initialization routine Can be used to reinitialize device to safe state and * send it to sleep */ void furi_hal_subghz_init(); @@ -105,13 +115,13 @@ void furi_hal_subghz_load_patable(const uint8_t data[8]); */ void furi_hal_subghz_write_packet(const uint8_t* data, uint8_t size); -/** Check if recieve pipe is not empty +/** Check if receive pipe is not empty * * @return true if not empty */ bool furi_hal_subghz_rx_pipe_not_empty(); -/** Check if recieved data crc is valid +/** Check if received data crc is valid * * @return true if valid */ @@ -132,7 +142,7 @@ void furi_hal_subghz_flush_rx(); */ void furi_hal_subghz_flush_tx(); -/** Shutdown Issue spwd command +/** Shutdown Issue SPWD command * @warning registers content will be lost */ void furi_hal_subghz_shutdown(); @@ -146,7 +156,7 @@ void furi_hal_subghz_reset(); */ void furi_hal_subghz_idle(); -/** Switch to Recieve +/** Switch to Receive */ void furi_hal_subghz_rx(); @@ -172,7 +182,7 @@ uint8_t furi_hal_subghz_get_lqi(); * * @param value frequency in Hz * - * @return true if frequncy is valid, otherwise false + * @return true if frequency is valid, otherwise false */ bool furi_hal_subghz_is_frequency_valid(uint32_t value); @@ -181,7 +191,7 @@ bool furi_hal_subghz_is_frequency_valid(uint32_t value); * * @param value frequency in Hz * - * @return real frequency in herz + * @return real frequency in Hz */ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value); @@ -189,7 +199,7 @@ uint32_t furi_hal_subghz_set_frequency_and_path(uint32_t value); * * @param value frequency in Hz * - * @return real frequency in herz + * @return real frequency in Hz */ uint32_t furi_hal_subghz_set_frequency(uint32_t value); diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index 31be7fff..c7acf95b 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -11,6 +11,10 @@ extern "C" { #include +#ifndef FURI_WARN_UNUSED +#define FURI_WARN_UNUSED __attribute__((warn_unused_result)) +#endif + #ifndef FURI_IS_IRQ_MASKED #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U) #endif diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index bd29bd8e..2c1f0ad9 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -1,6 +1,7 @@ #include "lfrfid_protocols.h" #include "protocol_em4100.h" #include "protocol_h10301.h" +#include "protocol_idteck.h" #include "protocol_indala26.h" #include "protocol_io_prox_xsf.h" #include "protocol_awid.h" @@ -19,6 +20,7 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, [LFRFIDProtocolH10301] = &protocol_h10301, + [LFRFIDProtocolIdteck] = &protocol_idteck, [LFRFIDProtocolIndala26] = &protocol_indala26, [LFRFIDProtocolIOProxXSF] = &protocol_io_prox_xsf, [LFRFIDProtocolAwid] = &protocol_awid, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 26065c9a..848f003a 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -10,6 +10,7 @@ typedef enum { typedef enum { LFRFIDProtocolEM4100, LFRFIDProtocolH10301, + LFRFIDProtocolIdteck, LFRFIDProtocolIndala26, LFRFIDProtocolIOProxXSF, LFRFIDProtocolAwid, diff --git a/lib/lfrfid/protocols/protocol_idteck.c b/lib/lfrfid/protocols/protocol_idteck.c new file mode 100644 index 00000000..033fcd28 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_idteck.c @@ -0,0 +1,269 @@ +#include +#include +#include +#include "lfrfid_protocols.h" + +// Example: 4944544B 351FBE4B +// 01001001 01000100 01010100 01001011 00110101 00011111 10111110 01001011 +// 4 9 4 4 5 4 4 B 3 5 1 F B E 4 B +// 0100 1001 0100 0100 0101 0100 0100 1011 0011 0101 0001 1111 1011 1110 0100 1011 + +#define IDTECK_PREAMBLE_BIT_SIZE (32) +#define IDTECK_PREAMBLE_DATA_SIZE (8) + +#define IDTECK_ENCODED_BIT_SIZE (64) +#define IDTECK_ENCODED_DATA_SIZE (((IDTECK_ENCODED_BIT_SIZE) / 8) + IDTECK_PREAMBLE_DATA_SIZE) +#define IDTECK_ENCODED_DATA_LAST ((IDTECK_ENCODED_BIT_SIZE) / 8) + +#define IDTECK_DECODED_BIT_SIZE (64) +#define IDTECK_DECODED_DATA_SIZE (8) + +#define IDTECK_US_PER_BIT (255) +#define IDTECK_ENCODER_PULSES_PER_BIT (16) + +typedef struct { + uint8_t data_index; + uint8_t bit_clock_index; + bool last_bit; + bool current_polarity; + bool pulse_phase; +} ProtocolIdteckEncoder; + +typedef struct { + uint8_t encoded_data[IDTECK_ENCODED_DATA_SIZE]; + uint8_t negative_encoded_data[IDTECK_ENCODED_DATA_SIZE]; + uint8_t corrupted_encoded_data[IDTECK_ENCODED_DATA_SIZE]; + uint8_t corrupted_negative_encoded_data[IDTECK_ENCODED_DATA_SIZE]; + + uint8_t data[IDTECK_DECODED_DATA_SIZE]; + ProtocolIdteckEncoder encoder; +} ProtocolIdteck; + +ProtocolIdteck* protocol_idteck_alloc(void) { + ProtocolIdteck* protocol = malloc(sizeof(ProtocolIdteck)); + return protocol; +}; + +void protocol_idteck_free(ProtocolIdteck* protocol) { + free(protocol); +}; + +uint8_t* protocol_idteck_get_data(ProtocolIdteck* protocol) { + return protocol->data; +}; + +void protocol_idteck_decoder_start(ProtocolIdteck* protocol) { + memset(protocol->encoded_data, 0, IDTECK_ENCODED_DATA_SIZE); + memset(protocol->negative_encoded_data, 0, IDTECK_ENCODED_DATA_SIZE); + memset(protocol->corrupted_encoded_data, 0, IDTECK_ENCODED_DATA_SIZE); + memset(protocol->corrupted_negative_encoded_data, 0, IDTECK_ENCODED_DATA_SIZE); +}; + +static bool protocol_idteck_check_preamble(uint8_t* data, size_t bit_index) { + // Preamble 01001001 01000100 01010100 01001011 + if(*(uint32_t*)&data[bit_index / 8] != 0b01001011010101000100010001001001) return false; + return true; +} + +static bool protocol_idteck_can_be_decoded(uint8_t* data) { + if(!protocol_idteck_check_preamble(data, 0)) return false; + return true; +} + +static bool protocol_idteck_decoder_feed_internal(bool polarity, uint32_t time, uint8_t* data) { + time += (IDTECK_US_PER_BIT / 2); + + size_t bit_count = (time / IDTECK_US_PER_BIT); + bool result = false; + + if(bit_count < IDTECK_ENCODED_BIT_SIZE) { + for(size_t i = 0; i < bit_count; i++) { + bit_lib_push_bit(data, IDTECK_ENCODED_DATA_SIZE, polarity); + if(protocol_idteck_can_be_decoded(data)) { + result = true; + break; + } + } + } + + return result; +} + +static void protocol_idteck_decoder_save(uint8_t* data_to, const uint8_t* data_from) { + bit_lib_copy_bits(data_to, 0, 64, data_from, 0); +} + +bool protocol_idteck_decoder_feed(ProtocolIdteck* protocol, bool level, uint32_t duration) { + bool result = false; + + if(duration > (IDTECK_US_PER_BIT / 2)) { + if(protocol_idteck_decoder_feed_internal(level, duration, protocol->encoded_data)) { + protocol_idteck_decoder_save(protocol->data, protocol->encoded_data); + FURI_LOG_D("Idteck", "Positive"); + result = true; + return result; + } + + if(protocol_idteck_decoder_feed_internal( + !level, duration, protocol->negative_encoded_data)) { + protocol_idteck_decoder_save(protocol->data, protocol->negative_encoded_data); + FURI_LOG_D("Idteck", "Negative"); + result = true; + return result; + } + } + + if(duration > (IDTECK_US_PER_BIT / 4)) { + // Try to decode wrong phase synced data + if(level) { + duration += 120; + } else { + if(duration > 120) { + duration -= 120; + } + } + + if(protocol_idteck_decoder_feed_internal( + level, duration, protocol->corrupted_encoded_data)) { + protocol_idteck_decoder_save(protocol->data, protocol->corrupted_encoded_data); + FURI_LOG_D("Idteck", "Positive Corrupted"); + + result = true; + return result; + } + + if(protocol_idteck_decoder_feed_internal( + !level, duration, protocol->corrupted_negative_encoded_data)) { + protocol_idteck_decoder_save( + protocol->data, protocol->corrupted_negative_encoded_data); + FURI_LOG_D("Idteck", "Negative Corrupted"); + + result = true; + return result; + } + } + + return result; +}; + +bool protocol_idteck_encoder_start(ProtocolIdteck* protocol) { + memset(protocol->encoded_data, 0, IDTECK_ENCODED_DATA_SIZE); + *(uint32_t*)&protocol->encoded_data[0] = 0b01001011010101000100010001001001; + bit_lib_copy_bits(protocol->encoded_data, 32, 32, protocol->data, 32); + + protocol->encoder.last_bit = + bit_lib_get_bit(protocol->encoded_data, IDTECK_ENCODED_BIT_SIZE - 1); + protocol->encoder.data_index = 0; + protocol->encoder.current_polarity = true; + protocol->encoder.pulse_phase = true; + protocol->encoder.bit_clock_index = 0; + + return true; +}; + +LevelDuration protocol_idteck_encoder_yield(ProtocolIdteck* protocol) { + LevelDuration level_duration; + ProtocolIdteckEncoder* encoder = &protocol->encoder; + + if(encoder->pulse_phase) { + level_duration = level_duration_make(encoder->current_polarity, 1); + encoder->pulse_phase = false; + } else { + level_duration = level_duration_make(!encoder->current_polarity, 1); + encoder->pulse_phase = true; + + encoder->bit_clock_index++; + if(encoder->bit_clock_index >= IDTECK_ENCODER_PULSES_PER_BIT) { + encoder->bit_clock_index = 0; + + bool current_bit = bit_lib_get_bit(protocol->encoded_data, encoder->data_index); + + if(current_bit != encoder->last_bit) { + encoder->current_polarity = !encoder->current_polarity; + } + + encoder->last_bit = current_bit; + + bit_lib_increment_index(encoder->data_index, IDTECK_ENCODED_BIT_SIZE); + } + } + + return level_duration; +}; + +// factory code +static uint32_t get_fc(const uint8_t* data) { + uint32_t fc = 0; + fc = bit_lib_get_bits_32(data, 0, 32); + return fc; +} + +// card number +static uint32_t get_card(const uint8_t* data) { + uint32_t cn = 0; + cn = bit_lib_get_bits_32(data, 32, 32); + return cn; +} + +void protocol_idteck_render_data_internal(ProtocolIdteck* protocol, FuriString* result, bool brief) { + const uint32_t fc = get_fc(protocol->data); + const uint32_t card = get_card(protocol->data); + + if(brief) { + furi_string_printf(result, "FC: %08lX\r\nCard: %08lX", fc, card); + } else { + furi_string_printf( + result, + "FC: %08lX\r\n" + "Card: %08lX\r\n", + fc, + card); + } +} +void protocol_idteck_render_data(ProtocolIdteck* protocol, FuriString* result) { + protocol_idteck_render_data_internal(protocol, result, false); +} +void protocol_idteck_render_brief_data(ProtocolIdteck* protocol, FuriString* result) { + protocol_idteck_render_data_internal(protocol, result, true); +} + +bool protocol_idteck_write_data(ProtocolIdteck* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + protocol_idteck_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = LFRFID_T5577_BITRATE_RF_32 | LFRFID_T5577_MODULATION_PSK1 | + (2 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); + request->t5577.blocks_to_write = 3; + result = true; + } + return result; +}; + +const ProtocolBase protocol_idteck = { + .name = "Idteck", + .manufacturer = "IDTECK", + .data_size = IDTECK_DECODED_DATA_SIZE, + .features = LFRFIDFeaturePSK, + .validate_count = 6, + .alloc = (ProtocolAlloc)protocol_idteck_alloc, + .free = (ProtocolFree)protocol_idteck_free, + .get_data = (ProtocolGetData)protocol_idteck_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_idteck_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_idteck_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_idteck_encoder_start, + .yield = (ProtocolEncoderYield)protocol_idteck_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_idteck_render_data, + .render_brief_data = (ProtocolRenderData)protocol_idteck_render_brief_data, + .write_data = (ProtocolWriteData)protocol_idteck_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_idteck.h b/lib/lfrfid/protocols/protocol_idteck.h new file mode 100644 index 00000000..b7a5ade4 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_idteck.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_idteck; diff --git a/applications/main/nfc/helpers/nfc_generators.c b/lib/nfc/helpers/nfc_generators.c similarity index 95% rename from applications/main/nfc/helpers/nfc_generators.c rename to lib/nfc/helpers/nfc_generators.c index 5f0527c6..769b9c7b 100644 --- a/applications/main/nfc/helpers/nfc_generators.c +++ b/lib/nfc/helpers/nfc_generators.c @@ -376,103 +376,86 @@ static void nfc_generate_mf_classic_4k_7b_uid(NfcDeviceData* data) { static const NfcGenerator mf_ul_generator = { .name = "Mifare Ultralight", .generator_func = nfc_generate_mf_ul_orig, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator mf_ul_11_generator = { .name = "Mifare Ultralight EV1 11", .generator_func = nfc_generate_mf_ul_11, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator mf_ul_h11_generator = { .name = "Mifare Ultralight EV1 H11", .generator_func = nfc_generate_mf_ul_h11, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator mf_ul_21_generator = { .name = "Mifare Ultralight EV1 21", .generator_func = nfc_generate_mf_ul_21, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator mf_ul_h21_generator = { .name = "Mifare Ultralight EV1 H21", .generator_func = nfc_generate_mf_ul_h21, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag203_generator = { .name = "NTAG203", .generator_func = nfc_generate_mf_ul_ntag203, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag213_generator = { .name = "NTAG213", .generator_func = nfc_generate_ntag213, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag215_generator = { .name = "NTAG215", .generator_func = nfc_generate_ntag215, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag216_generator = { .name = "NTAG216", .generator_func = nfc_generate_ntag216, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag_i2c_1k_generator = { .name = "NTAG I2C 1k", .generator_func = nfc_generate_ntag_i2c_1k, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag_i2c_2k_generator = { .name = "NTAG I2C 2k", .generator_func = nfc_generate_ntag_i2c_2k, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag_i2c_plus_1k_generator = { .name = "NTAG I2C Plus 1k", .generator_func = nfc_generate_ntag_i2c_plus_1k, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator ntag_i2c_plus_2k_generator = { .name = "NTAG I2C Plus 2k", .generator_func = nfc_generate_ntag_i2c_plus_2k, - .next_scene = NfcSceneMfUltralightMenu, }; static const NfcGenerator mifare_classic_1k_4b_uid_generator = { .name = "Mifare Classic 1k 4byte UID", .generator_func = nfc_generate_mf_classic_1k_4b_uid, - .next_scene = NfcSceneMfClassicMenu, }; static const NfcGenerator mifare_classic_1k_7b_uid_generator = { .name = "Mifare Classic 1k 7byte UID", .generator_func = nfc_generate_mf_classic_1k_7b_uid, - .next_scene = NfcSceneMfClassicMenu, }; static const NfcGenerator mifare_classic_4k_4b_uid_generator = { .name = "Mifare Classic 4k 4byte UID", .generator_func = nfc_generate_mf_classic_4k_4b_uid, - .next_scene = NfcSceneMfClassicMenu, }; static const NfcGenerator mifare_classic_4k_7b_uid_generator = { .name = "Mifare Classic 4k 7byte UID", .generator_func = nfc_generate_mf_classic_4k_7b_uid, - .next_scene = NfcSceneMfClassicMenu, }; const NfcGenerator* const nfc_generators[] = { diff --git a/applications/main/nfc/helpers/nfc_generators.h b/lib/nfc/helpers/nfc_generators.h similarity index 79% rename from applications/main/nfc/helpers/nfc_generators.h rename to lib/nfc/helpers/nfc_generators.h index 362a19b1..8cee6706 100644 --- a/applications/main/nfc/helpers/nfc_generators.h +++ b/lib/nfc/helpers/nfc_generators.h @@ -1,14 +1,13 @@ #pragma once -#include "../nfc_i.h" +#include "../nfc_device.h" typedef void (*NfcGeneratorFunc)(NfcDeviceData* data); -struct NfcGenerator { +typedef struct { const char* name; NfcGeneratorFunc generator_func; - NfcScene next_scene; -}; +} NfcGenerator; extern const NfcGenerator* const nfc_generators[]; diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c index 3ab10a4f..49eebc37 100644 --- a/lib/nfc/nfc_device.c +++ b/lib/nfc/nfc_device.c @@ -27,6 +27,7 @@ NfcDevice* nfc_device_alloc() { nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS); nfc_dev->load_path = furi_string_alloc(); nfc_dev->dev_data.parsed_data = furi_string_alloc(); + nfc_dev->folder = furi_string_alloc(); // Rename cache folder name for backward compatibility if(storage_common_stat(nfc_dev->storage, "/ext/nfc/cache", NULL) == FSE_OK) { @@ -42,6 +43,7 @@ void nfc_device_free(NfcDevice* nfc_dev) { furi_record_close(RECORD_DIALOGS); furi_string_free(nfc_dev->load_path); furi_string_free(nfc_dev->dev_data.parsed_data); + furi_string_free(nfc_dev->folder); free(nfc_dev); } @@ -1018,6 +1020,16 @@ static void nfc_device_get_shadow_path(FuriString* orig_path, FuriString* shadow furi_string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); } +static void nfc_device_get_folder_from_path(FuriString* path, FuriString* folder) { + size_t last_slash = furi_string_search_rchar(path, '/'); + if(last_slash == FURI_STRING_FAILURE) { + // No slashes in the path, treat the whole path as a folder + furi_string_set(folder, path); + } else { + furi_string_set_n(folder, path, 0, last_slash); + } +} + bool nfc_device_save(NfcDevice* dev, const char* dev_name) { furi_assert(dev); @@ -1028,10 +1040,19 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) { temp_str = furi_string_alloc(); do { - // Create nfc directory if necessary - if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; + // Create directory if necessary + FuriString* folder = furi_string_alloc(); + // Get folder from filename (filename is in the form of "folder/filename.nfc", so the folder is "folder/") + furi_string_set(temp_str, dev_name); + // Get folder from filename + nfc_device_get_folder_from_path(temp_str, folder); + FURI_LOG_I("Nfc", "Saving to folder %s", furi_string_get_cstr(folder)); + if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(folder))) { + FURI_LOG_E("Nfc", "Failed to create folder %s", furi_string_get_cstr(folder)); + break; + } + furi_string_free(folder); // First remove nfc device file if it was saved - furi_string_printf(temp_str, "%s", dev_name); // Open file if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; // Write header @@ -1199,10 +1220,9 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) { bool nfc_file_select(NfcDevice* dev) { furi_assert(dev); + const char* folder = furi_string_get_cstr(dev->folder); // Input events and views are managed by file_browser - FuriString* nfc_app_folder; - nfc_app_folder = furi_string_alloc_set(NFC_APP_FOLDER); const DialogsFileBrowserOptions browser_options = { .extension = NFC_APP_EXTENSION, @@ -1212,13 +1232,12 @@ bool nfc_file_select(NfcDevice* dev) { .hide_ext = true, .item_loader_callback = NULL, .item_loader_context = NULL, - .base_path = NFC_APP_FOLDER, + .base_path = folder, }; bool res = dialog_file_browser_show(dev->dialogs, dev->load_path, dev->load_path, &browser_options); - furi_string_free(nfc_app_folder); if(res) { FuriString* filename; filename = furi_string_alloc(); @@ -1271,7 +1290,11 @@ bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { furi_string_set(file_path, dev->load_path); } else { furi_string_printf( - file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + file_path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; // Delete shadow file if it exists @@ -1280,7 +1303,11 @@ bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { nfc_device_get_shadow_path(dev->load_path, file_path); } else { furi_string_printf( - file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + file_path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_SHADOW_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; } @@ -1309,14 +1336,23 @@ bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { nfc_device_get_shadow_path(dev->load_path, path); } else { furi_string_printf( - path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_SHADOW_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(path))) break; dev->shadow_file_exist = false; if(use_load_path && !furi_string_empty(dev->load_path)) { furi_string_set(path, dev->load_path); } else { - furi_string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + furi_string_printf( + path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_EXTENSION); } if(!nfc_device_load_data(dev, path, true)) break; restored = true; diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 4be07f01..55ee4ac4 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -20,7 +20,6 @@ extern "C" { #define NFC_READER_DATA_MAX_SIZE 64 #define NFC_DICT_KEY_BATCH_SIZE 50 -#define NFC_APP_FOLDER ANY_PATH("nfc") #define NFC_APP_EXTENSION ".nfc" #define NFC_APP_SHADOW_EXTENSION ".shd" @@ -84,6 +83,7 @@ typedef struct { NfcDeviceData dev_data; char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; FuriString* load_path; + FuriString* folder; NfcDeviceSaveFormat format; bool shadow_file_exist; diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index cb6adaf6..67b8cdfc 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -8,6 +8,8 @@ #define TAG "SubGhzProtocolHormannHSM" +#define HORMANN_HSM_PATTERN 0xFF000000003 + static const SubGhzBlockConst subghz_protocol_hormann_const = { .te_short = 500, .te_long = 1000, @@ -101,20 +103,13 @@ static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHorm furi_assert(instance); size_t index = 0; - size_t size_upload = 3 + (instance->generic.data_count_bit * 2 + 2) * 20 + 1; + size_t size_upload = (instance->generic.data_count_bit * 2 + 2) * 20 + 1; if(size_upload > instance->encoder.size_upload) { FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); return false; } else { instance->encoder.size_upload = size_upload; } - //Send header - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short * 64); - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short * 64); - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_hormann_const.te_short * 64); instance->encoder.repeat = 10; //original remote does 10 repeats for(size_t repeat = 0; repeat < 20; repeat++) { @@ -209,6 +204,10 @@ void subghz_protocol_decoder_hormann_free(void* context) { free(instance); } +static bool subghz_protocol_decoder_hormann_check_pattern(SubGhzProtocolDecoderHormann* instance) { + return (instance->decoder.decode_data & HORMANN_HSM_PATTERN) == HORMANN_HSM_PATTERN; +} + void subghz_protocol_decoder_hormann_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderHormann* instance = context; @@ -221,25 +220,9 @@ void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t du switch(instance->decoder.parser_step) { case HormannDecoderStepReset: - if((level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 64) < - subghz_protocol_hormann_const.te_delta * 64)) { - instance->decoder.parser_step = HormannDecoderStepFoundStartHeader; - } - break; - case HormannDecoderStepFoundStartHeader: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 64) < - subghz_protocol_hormann_const.te_delta * 64)) { - instance->decoder.parser_step = HormannDecoderStepFoundHeader; - } else { - instance->decoder.parser_step = HormannDecoderStepReset; - } - break; - case HormannDecoderStepFoundHeader: if((level) && (DURATION_DIFF(duration, subghz_protocol_hormann_const.te_short * 24) < subghz_protocol_hormann_const.te_delta * 24)) { instance->decoder.parser_step = HormannDecoderStepFoundStartBit; - } else { - instance->decoder.parser_step = HormannDecoderStepReset; } break; case HormannDecoderStepFoundStartBit: @@ -254,7 +237,8 @@ void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t du break; case HormannDecoderStepSaveDuration: if(level) { //save interval - if(duration >= (subghz_protocol_hormann_const.te_short * 5)) { + if(duration >= (subghz_protocol_hormann_const.te_short * 5) && + subghz_protocol_decoder_hormann_check_pattern(instance)) { instance->decoder.parser_step = HormannDecoderStepFoundStartBit; if(instance->decoder.decode_count_bit >= subghz_protocol_hormann_const.min_count_bit_for_found) { diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 24aaae8d..ed46f5c9 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -12,7 +12,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec, &subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, &subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3, - &subghz_protocol_clemsa, &subghz_protocol_ansonic, + &subghz_protocol_clemsa, &subghz_protocol_ansonic, &subghz_protocol_smc5326, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 114dc504..9f446739 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -36,5 +36,6 @@ #include "intertechno_v3.h" #include "clemsa.h" #include "ansonic.h" +#include "smc5326.h" extern const SubGhzProtocolRegistry subghz_protocol_registry; diff --git a/lib/subghz/protocols/smc5326.c b/lib/subghz/protocols/smc5326.c new file mode 100644 index 00000000..889e39f0 --- /dev/null +++ b/lib/subghz/protocols/smc5326.c @@ -0,0 +1,387 @@ +#include "smc5326.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* + * Help + * https://datasheetspdf.com/pdf-file/532079/Aslic/AX5326-4/1 + * + */ + +#define TAG "SubGhzProtocolSMC5326" + +#define DIP_P 0b11 //(+) +#define DIP_O 0b10 //(0) +#define DIP_N 0b00 //(-) + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c" +#define SHOW_DIP_P(dip, check_dip) \ + ((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_') + +static const SubGhzBlockConst subghz_protocol_smc5326_const = { + .te_short = 300, + .te_long = 900, + .te_delta = 200, + .min_count_bit_for_found = 25, +}; + +struct SubGhzProtocolDecoderSMC5326 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t te; + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderSMC5326 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +typedef enum { + SMC5326DecoderStepReset = 0, + SMC5326DecoderStepSaveDuration, + SMC5326DecoderStepCheckDuration, +} SMC5326DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_smc5326_decoder = { + .alloc = subghz_protocol_decoder_smc5326_alloc, + .free = subghz_protocol_decoder_smc5326_free, + + .feed = subghz_protocol_decoder_smc5326_feed, + .reset = subghz_protocol_decoder_smc5326_reset, + + .get_hash_data = subghz_protocol_decoder_smc5326_get_hash_data, + .serialize = subghz_protocol_decoder_smc5326_serialize, + .deserialize = subghz_protocol_decoder_smc5326_deserialize, + .get_string = subghz_protocol_decoder_smc5326_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_smc5326_encoder = { + .alloc = subghz_protocol_encoder_smc5326_alloc, + .free = subghz_protocol_encoder_smc5326_free, + + .deserialize = subghz_protocol_encoder_smc5326_deserialize, + .stop = subghz_protocol_encoder_smc5326_stop, + .yield = subghz_protocol_encoder_smc5326_yield, +}; + +const SubGhzProtocol subghz_protocol_smc5326 = { + .name = SUBGHZ_PROTOCOL_SMC5326_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_smc5326_decoder, + .encoder = &subghz_protocol_smc5326_encoder, +}; + +void* subghz_protocol_encoder_smc5326_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderSMC5326* instance = malloc(sizeof(SubGhzProtocolEncoderSMC5326)); + + instance->base.protocol = &subghz_protocol_smc5326; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_smc5326_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderSMC5326* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderSMC5326 instance + * @return true On success + */ +static bool subghz_protocol_encoder_smc5326_get_upload(SubGhzProtocolEncoderSMC5326* instance) { + furi_assert(instance); + + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + //Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 3); + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); + } else { + //send bit 0 + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 3); + } + } + + //Send Stop bit + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + //Send PT_GUARD + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 25); + + return true; +} + +bool subghz_protocol_encoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderSMC5326* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_smc5326_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_smc5326_get_upload(instance)) break; + instance->encoder.is_running = true; + + res = true; + } while(false); + + return res; +} + +void subghz_protocol_encoder_smc5326_stop(void* context) { + SubGhzProtocolEncoderSMC5326* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_smc5326_yield(void* context) { + SubGhzProtocolEncoderSMC5326* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_smc5326_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderSMC5326* instance = malloc(sizeof(SubGhzProtocolDecoderSMC5326)); + instance->base.protocol = &subghz_protocol_smc5326; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_smc5326_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + free(instance); +} + +void subghz_protocol_decoder_smc5326_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + instance->decoder.parser_step = SMC5326DecoderStepReset; + instance->last_data = 0; +} + +void subghz_protocol_decoder_smc5326_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + + switch(instance->decoder.parser_step) { + case SMC5326DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_smc5326_const.te_short * 24) < + subghz_protocol_smc5326_const.te_delta * 12)) { + //Found Preambula + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + } + break; + case SMC5326DecoderStepSaveDuration: + //save duration + if(level) { + instance->decoder.te_last = duration; + instance->te += duration; + instance->decoder.parser_step = SMC5326DecoderStepCheckDuration; + } + break; + case SMC5326DecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)subghz_protocol_smc5326_const.te_long * 2)) { + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + if(instance->decoder.decode_count_bit == + subghz_protocol_smc5326_const.min_count_bit_for_found) { + if((instance->last_data == instance->decoder.decode_data) && + instance->last_data) { + instance->te /= (instance->decoder.decode_count_bit * 4 + 1); + + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->last_data = instance->decoder.decode_data; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + break; + } + + instance->te += duration; + + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_smc5326_const.te_short) < + subghz_protocol_smc5326_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_smc5326_const.te_long) < + subghz_protocol_smc5326_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_smc5326_const.te_long) < + subghz_protocol_smc5326_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_smc5326_const.te_short) < + subghz_protocol_smc5326_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = SMC5326DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = SMC5326DecoderStepReset; + } + } else { + instance->decoder.parser_step = SMC5326DecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_smc5326_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_smc5326_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + bool res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res && !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(instance->generic.data_count_bit != + subghz_protocol_smc5326_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + break; + } + res = true; + } while(false); + + return res; +} + +static void subghz_protocol_smc5326_get_event_serialize(uint8_t event, FuriString* output) { + furi_string_cat_printf( + output, + "%s%s%s%s\r\n", + (((event >> 6) & 0x3) == 0x3 ? "B1 " : ""), + (((event >> 4) & 0x3) == 0x3 ? "B2 " : ""), + (((event >> 2) & 0x3) == 0x3 ? "B3 " : ""), + (((event >> 0) & 0x3) == 0x3 ? "B4 " : "")); +} + +void subghz_protocol_decoder_smc5326_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderSMC5326* instance = context; + uint32_t data = (uint32_t)((instance->generic.data >> 9) & 0xFFFF); + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%07lX Te:%ldus\r\n" + " +: " DIP_PATTERN "\r\n" + " o: " DIP_PATTERN " ", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0x1FFFFFF), + instance->te, + SHOW_DIP_P(data, DIP_P), + SHOW_DIP_P(data, DIP_O)); + subghz_protocol_smc5326_get_event_serialize(instance->generic.data >> 1, output); + furi_string_cat_printf(output, " -: " DIP_PATTERN "\r\n", SHOW_DIP_P(data, DIP_N)); +} diff --git a/lib/subghz/protocols/smc5326.h b/lib/subghz/protocols/smc5326.h new file mode 100644 index 00000000..ddc954bd --- /dev/null +++ b/lib/subghz/protocols/smc5326.h @@ -0,0 +1,107 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_SMC5326_NAME "SMC5326" + +typedef struct SubGhzProtocolDecoderSMC5326 SubGhzProtocolDecoderSMC5326; +typedef struct SubGhzProtocolEncoderSMC5326 SubGhzProtocolEncoderSMC5326; + +extern const SubGhzProtocolDecoder subghz_protocol_smc5326_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_smc5326_encoder; +extern const SubGhzProtocol subghz_protocol_smc5326; + +/** + * Allocate SubGhzProtocolEncoderSMC5326. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderSMC5326* pointer to a SubGhzProtocolEncoderSMC5326 instance + */ +void* subghz_protocol_encoder_smc5326_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderSMC5326. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + */ +void subghz_protocol_encoder_smc5326_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_encoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + */ +void subghz_protocol_encoder_smc5326_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderSMC5326 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_smc5326_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderSMC5326. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSMC5326* pointer to a SubGhzProtocolDecoderSMC5326 instance + */ +void* subghz_protocol_decoder_smc5326_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + */ +void subghz_protocol_decoder_smc5326_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + */ +void subghz_protocol_decoder_smc5326_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_smc5326_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_smc5326_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_decoder_smc5326_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderSMC5326. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_smc5326_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_smc5326_get_string(void* context, FuriString* output); diff --git a/lib/toolbox/saved_struct.c b/lib/toolbox/saved_struct.c index 65b761f8..02b73f21 100644 --- a/lib/toolbox/saved_struct.c +++ b/lib/toolbox/saved_struct.c @@ -125,3 +125,54 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic, return result; } + +bool saved_struct_get_payload_size( + const char* path, + uint8_t magic, + uint8_t version, + size_t* payload_size) { + furi_assert(path); + furi_assert(payload_size); + + SavedStructHeader header; + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + bool result = false; + do { + if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E( + TAG, "Failed to read \"%s\". Error: %s", path, storage_file_get_error_desc(file)); + break; + } + + uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader)); + if(bytes_count != sizeof(SavedStructHeader)) { + FURI_LOG_E(TAG, "Failed to read header"); + break; + } + + if((header.magic != magic) || (header.version != version)) { + FURI_LOG_E( + TAG, + "Magic(%d != %d) or Version(%d != %d) mismatch of file \"%s\"", + header.magic, + magic, + header.version, + version, + path); + break; + } + + uint64_t file_size = storage_file_size(file); + *payload_size = file_size - sizeof(SavedStructHeader); + + result = true; + } while(false); + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + return result; +} diff --git a/lib/toolbox/saved_struct.h b/lib/toolbox/saved_struct.h index aaa04eef..9ce83656 100644 --- a/lib/toolbox/saved_struct.h +++ b/lib/toolbox/saved_struct.h @@ -12,6 +12,12 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic, bool saved_struct_save(const char* path, void* data, size_t size, uint8_t magic, uint8_t version); +bool saved_struct_get_payload_size( + const char* path, + uint8_t magic, + uint8_t version, + size_t* payload_size); + #ifdef __cplusplus } #endif diff --git a/scripts/get_env.py b/scripts/get_env.py index d0527309..e2da6eda 100644 --- a/scripts/get_env.py +++ b/scripts/get_env.py @@ -77,28 +77,38 @@ def add_env(name, value, file): print(f"{delimeter}", file=file) -def add_envs(data, env_file, args): - add_env("COMMIT_MSG", data["commit_comment"], env_file) - add_env("COMMIT_HASH", data["commit_hash"], env_file) - add_env("COMMIT_SHA", data["commit_sha"], env_file) - add_env("SUFFIX", data["suffix"], env_file) - add_env("BRANCH_NAME", data["branch_name"], env_file) - add_env("DIST_SUFFIX", data["suffix"], env_file) - add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], env_file) +def add_set_output_var(name, value, file): + print(f"{name}={value}", file=file) + + +def add_envs(data, gh_env_file, gh_out_file, args): + add_env("COMMIT_MSG", data["commit_comment"], gh_env_file) + add_env("COMMIT_HASH", data["commit_hash"], gh_env_file) + add_env("COMMIT_SHA", data["commit_sha"], gh_env_file) + add_env("SUFFIX", data["suffix"], gh_env_file) + add_env("BRANCH_NAME", data["branch_name"], gh_env_file) + add_env("DIST_SUFFIX", data["suffix"], gh_env_file) + add_env("WORKFLOW_BRANCH_OR_TAG", data["branch_name"], gh_env_file) + add_set_output_var("branch_name", data["branch_name"], gh_out_file) + add_set_output_var("commit_sha", data["commit_sha"], gh_out_file) + add_set_output_var("default_target", os.getenv("DEFAULT_TARGET"), gh_out_file) + add_set_output_var("suffix", data["suffix"], gh_out_file) if args.type == "pull": - add_env("PULL_ID", data["pull_id"], env_file) - add_env("PULL_NAME", data["pull_name"], env_file) + add_env("PULL_ID", data["pull_id"], gh_env_file) + add_env("PULL_NAME", data["pull_name"], gh_env_file) def main(): args = parse_args() - event_file = open(args.event_file) + event_file = open(args.event_file, "r") event = json.load(event_file) - env_file = open(os.environ["GITHUB_ENV"], "a") + gh_env_file = open(os.environ["GITHUB_ENV"], "a") + gh_out_file = open(os.environ["GITHUB_OUTPUT"], "a") data = get_details(event, args) - add_envs(data, env_file, args) + add_envs(data, gh_env_file, gh_out_file, args) event_file.close() - env_file.close() + gh_env_file.close() + gh_out_file.close() if __name__ == "__main__": diff --git a/scripts/merge_report_qa.py b/scripts/merge_report_qa.py new file mode 100755 index 00000000..c0707848 --- /dev/null +++ b/scripts/merge_report_qa.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import os +import re +import sys +import argparse +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("slack_token") + parser.add_argument("slack_channel") + args = parser.parse_args() + return args + + +def checkCommitMessage(msg): + regex = re.compile(r"^'?\[FL-\d+\]") + if regex.match(msg): + return True + return False + + +def reportSlack(commit_hash, slack_token, slack_channel, message): + client = WebClient(token=slack_token) + try: + client.chat_postMessage(channel="#" + slack_channel, text=message) + except SlackApiError as e: + print(e) + sys.exit(1) + + +def main(): + args = parse_args() + commit_msg = os.getenv("COMMIT_MSG") + commit_hash = os.getenv("COMMIT_HASH") + commit_sha = os.getenv("COMMIT_SHA") + commit_link = ( + "" + ) + message = "Commit " + commit_link + " merged to dev without 'FL' ticket!" + if not checkCommitMessage(commit_msg): + reportSlack(commit_hash, args.slack_token, args.slack_channel, message) + + +if __name__ == "__main__": + main() diff --git a/scripts/testing/await_flipper.py b/scripts/testing/await_flipper.py index 1f0d1619..efae6765 100755 --- a/scripts/testing/await_flipper.py +++ b/scripts/testing/await_flipper.py @@ -24,7 +24,7 @@ def flp_serial_by_name(flp_name): return "" -UPDATE_TIMEOUT = 30 +UPDATE_TIMEOUT = 60 def main():