Merge branch 'dev' into release-candidate
This commit is contained in:
		
						commit
						f6d6a626ea
					
				
							
								
								
									
										10
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							| @ -17,7 +17,6 @@ | ||||
| /applications/gui/ @skotopes @DrZlo13 @hedger | ||||
| /applications/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| /applications/infrared/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| /applications/infrared_monitor/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| /applications/input/ @skotopes @DrZlo13 @hedger | ||||
| /applications/lfrfid/ @skotopes @DrZlo13 @hedger | ||||
| /applications/lfrfid_debug/ @skotopes @DrZlo13 @hedger | ||||
| @ -45,12 +44,8 @@ | ||||
| # Debug tools and plugins | ||||
| /debug/ @skotopes @DrZlo13 @hedger | ||||
| 
 | ||||
| # Docker | ||||
| /docker/ @skotopes @DrZlo13 @hedger @aprosvetova | ||||
| /docker-compose.yml @skotopes @DrZlo13 @hedger @aprosvetova | ||||
| 
 | ||||
| # Documentation | ||||
| /documentation/ @skotopes @DrZlo13 @hedger @aprosvetova | ||||
| /documentation/ @skotopes @DrZlo13 @hedger @drunkbatya | ||||
| 
 | ||||
| # Firmware targets | ||||
| /firmware/ @skotopes @DrZlo13 @hedger | ||||
| @ -84,8 +79,5 @@ | ||||
| /lib/u8g2/ @skotopes @DrZlo13 @hedger | ||||
| /lib/update_util/ @skotopes @DrZlo13 @hedger | ||||
| 
 | ||||
| # Make tools | ||||
| /make/ @skotopes @DrZlo13 @hedger @aprosvetova | ||||
| 
 | ||||
| # Helper scripts | ||||
| /scripts/ @skotopes @DrZlo13 @hedger | ||||
|  | ||||
							
								
								
									
										11
									
								
								.github/actions/docker/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/actions/docker/action.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,11 +0,0 @@ | ||||
| name: 'Run in docker' | ||||
| inputs: | ||||
|   run:  # id of input | ||||
|     description: 'A command to run' | ||||
|     required: true | ||||
|     default: '' | ||||
| runs: | ||||
|   using: 'docker' | ||||
|   image: '../../../docker/Dockerfile' | ||||
|   args: | ||||
|     - ${{ inputs.run }} | ||||
							
								
								
									
										122
									
								
								.github/workflows/amap_analyse.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								.github/workflows/amap_analyse.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,122 @@ | ||||
| name: 'Analyze .map file with Amap' | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - dev | ||||
|       - "release*" | ||||
|     tags: | ||||
|       - '*' | ||||
|   pull_request: | ||||
| 
 | ||||
| env: | ||||
|   TARGETS: f7 | ||||
| 
 | ||||
| jobs: | ||||
|   amap_analyse: | ||||
|     runs-on: [self-hosted,FlipperZeroMacShell] | ||||
|     steps: | ||||
|       - name: 'Wait Build workflow' | ||||
|         uses: fountainhead/action-wait-for-check@v1.0.0 | ||||
|         id: wait-for-build | ||||
|         with: | ||||
|           token: ${{ secrets.GITHUB_TOKEN }} | ||||
|           checkName: 'main' | ||||
|           ref: ${{ github.event.pull_request.head.sha || github.sha }} | ||||
|           intervalSeconds: 20 | ||||
| 
 | ||||
|       - name: 'Check Build workflow status' | ||||
|         if: steps.wait-for-build.outputs.conclusion == 'failure' | ||||
|         run: | | ||||
|           exit 1 | ||||
| 
 | ||||
|       - 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 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Generate prefixes by commit' | ||||
|         id: names | ||||
|         run: | | ||||
|           REF="${{github.ref}}" | ||||
|           COMMIT_HASH="$(git rev-parse HEAD)" | ||||
|           SHA="$(git rev-parse --short HEAD)" | ||||
|           COMMIT_MSG="${{github.event.head_commit.message}}" | ||||
|           if [[ ${{ github.event_name }} == 'pull_request' ]]; then | ||||
|             REF="${{github.head_ref}}" | ||||
|             COMMIT_HASH="$(git log -1 --pretty=oneline | awk '{print $1}')" | ||||
|             SHA="$(cut -c -8 <<< "$COMMIT_HASH")" | ||||
|             COMMIT_MSG="$(git log -1 --pretty=format:"%s")" | ||||
|             PULL_ID="${{github.event.pull_request.number}}" | ||||
|             PULL_NAME="${{github.event.pull_request.title}}" | ||||
|           fi | ||||
|           BRANCH_NAME=${REF#refs/*/} | ||||
|           SUFFIX=${BRANCH_NAME//\//_}-$(date +'%d%m%Y')-${SHA} | ||||
|           if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then | ||||
|             SUFFIX=${BRANCH_NAME//\//_} | ||||
|           fi | ||||
|           echo "::set-output name=commit-hash::${COMMIT_HASH}" | ||||
|           echo "::set-output name=commit-msg::${COMMIT_MSG}" | ||||
|           echo "::set-output name=pull-id::${PULL_ID}" | ||||
|           echo "::set-output name=pull-name::${PULL_NAME}" | ||||
|           echo "::set-output name=branch-name::${BRANCH_NAME}" | ||||
|           echo "::set-output name=suffix::${SUFFIX}" | ||||
| 
 | ||||
|       - name: 'Make artifacts directory' | ||||
|         run: | | ||||
|           rm -rf artifacts | ||||
|           mkdir artifacts | ||||
| 
 | ||||
|       - name: 'Download build artifacts' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         run: | | ||||
|           echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; | ||||
|           chmod 600 ./deploy_key; | ||||
|           rsync -avzP \ | ||||
|               -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ | ||||
|               ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{steps.names.outputs.branch-name}}/" artifacts/; | ||||
|           rm ./deploy_key; | ||||
| 
 | ||||
|       - name: 'Make .map file analyse' | ||||
|         run: | | ||||
|           cd artifacts/ | ||||
|           /Applications/amap/Contents/MacOS/amap -f flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map | ||||
| 
 | ||||
|       - name: 'Upload report to DB' | ||||
|         run: | | ||||
|           FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh | ||||
|           get_size() | ||||
|           { | ||||
|             SECTION="$1"; | ||||
|             arm-none-eabi-size \ | ||||
|               -A artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf \ | ||||
|               | grep "^$SECTION" | awk '{print $2}' | ||||
|           } | ||||
|           export COMMIT_HASH="${{steps.names.outputs.commit-hash}}" | ||||
|           export COMMIT_MSG="${{steps.names.outputs.commit-msg}}" | ||||
|           export BRANCH_NAME="${{steps.names.outputs.branch-name}}" | ||||
|           export BSS_SIZE="$(get_size ".bss")" | ||||
|           export TEXT_SIZE="$(get_size ".text")" | ||||
|           export RODATA_SIZE="$(get_size ".rodata")" | ||||
|           export DATA_SIZE="$(get_size ".data")" | ||||
|           export FREE_FLASH_SIZE="$(get_size ".free_flash")" | ||||
|           if [[ ${{ github.event_name }} == 'pull_request' ]]; then | ||||
|             export PULL_ID="${{steps.names.outputs.pull-id}}" | ||||
|             export PULL_NAME="${{steps.names.outputs.pull-name}}" | ||||
|           fi | ||||
|           python3 -m pip install mariadb | ||||
|           python3 scripts/amap_mariadb_insert.py \ | ||||
|             ${{ secrets.AMAP_MARIADB_USER }} \ | ||||
|             ${{ secrets.AMAP_MARIADB_PASSWORD }} \ | ||||
|             ${{ secrets.AMAP_MARIADB_HOST }} \ | ||||
|             ${{ secrets.AMAP_MARIADB_PORT }} \ | ||||
|             ${{ secrets.AMAP_MARIADB_DATABASE }} \ | ||||
|             artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map.all | ||||
							
								
								
									
										4
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @ -108,6 +108,10 @@ jobs: | ||||
|           FBT_TOOLCHAIN_PATH=/opt ./fbt copro_dist | ||||
|           tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware | ||||
| 
 | ||||
|       - name: 'Copy .map file' | ||||
|         run: | | ||||
|           cp build/f7-firmware-D/firmware.elf.map artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map | ||||
| 
 | ||||
|       - name: 'Upload artifacts to update server' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         run: | | ||||
|  | ||||
							
								
								
									
										1
									
								
								.github/workflows/check_submodules.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/check_submodules.yml
									
									
									
									
										vendored
									
									
								
							| @ -25,6 +25,7 @@ jobs: | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Check protobuf branch' | ||||
|         run: | | ||||
|  | ||||
							
								
								
									
										1
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							| @ -28,6 +28,7 @@ jobs: | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Check code formatting' | ||||
|         id: syntax_check | ||||
|  | ||||
							
								
								
									
										1
									
								
								.github/workflows/lint_python.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/lint_python.yml
									
									
									
									
										vendored
									
									
								
							| @ -25,6 +25,7 @@ jobs: | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Check code formatting' | ||||
|         run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint_py | ||||
|  | ||||
							
								
								
									
										107
									
								
								.github/workflows/pvs_studio.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								.github/workflows/pvs_studio.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | ||||
| name: 'Static C/C++ analysis with PVS-Studio' | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - dev | ||||
|       - "release*" | ||||
|     tags: | ||||
|       - '*' | ||||
|   pull_request: | ||||
| 
 | ||||
| env: | ||||
|   TARGETS: f7 | ||||
|   DEFAULT_TARGET: f7 | ||||
| 
 | ||||
| jobs: | ||||
|   analyse_c_cpp: | ||||
|     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@v2 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Generate suffix and folder name' | ||||
|         id: names | ||||
|         run: | | ||||
|           REF=${{ github.ref }} | ||||
|           if [[ ${{ github.event_name }} == 'pull_request' ]]; then | ||||
|             REF=${{ github.head_ref }} | ||||
|           fi | ||||
|           BRANCH_OR_TAG=${REF#refs/*/} | ||||
|           SHA=$(git rev-parse --short HEAD) | ||||
| 
 | ||||
|           if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then | ||||
|             SUFFIX=${BRANCH_OR_TAG//\//_} | ||||
|           else | ||||
|             SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA} | ||||
|           fi | ||||
| 
 | ||||
|           echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV | ||||
|           echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV | ||||
|           echo "::set-output name=artifacts-path::${BRANCH_OR_TAG}" | ||||
|           echo "::set-output name=suffix::${SUFFIX}" | ||||
|           echo "::set-output name=short-hash::${SHA}" | ||||
|           echo "::set-output name=default-target::${DEFAULT_TARGET}" | ||||
| 
 | ||||
|       - name: 'Make reports directory' | ||||
|         run: | | ||||
|           rm -rf reports/ | ||||
|           mkdir reports | ||||
| 
 | ||||
|       - name: 'Generate compile_comands.json' | ||||
|         run: | | ||||
|           FBT_TOOLCHAIN_PATH=/opt ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking | ||||
| 
 | ||||
|       - name: 'Static code analysis' | ||||
|         run: | | ||||
|           FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh | ||||
|           pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} | ||||
|           pvs-studio-analyzer analyze \ | ||||
|               @.pvsoptions \ | ||||
|               -j$(grep -c processor /proc/cpuinfo) \ | ||||
|               -f build/f7-firmware-DC/compile_commands.json \ | ||||
|               -o PVS-Studio.log | ||||
| 
 | ||||
|       - name: 'Convert PVS-Studio output to html page' | ||||
|         run: plog-converter -a GA:1,2,3 -t fullhtml PVS-Studio.log -o reports/${{steps.names.outputs.default-target}}-${{steps.names.outputs.suffix}} | ||||
| 
 | ||||
|       - name: 'Upload artifacts to update server' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         run: | | ||||
|           echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; | ||||
|           chmod 600 ./deploy_key; | ||||
|           rsync -avrzP --mkpath \ | ||||
|               -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ | ||||
|               reports/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${{steps.names.outputs.artifacts-path}}/"; | ||||
|           rm ./deploy_key; | ||||
| 
 | ||||
|       - name: 'Find Previous Comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} | ||||
|         uses: peter-evans/find-comment@v1 | ||||
|         id: fc | ||||
|         with: | ||||
|           issue-number: ${{ github.event.pull_request.number }} | ||||
|           comment-author: 'github-actions[bot]' | ||||
|           body-includes: 'PVS-Studio report for commit' | ||||
| 
 | ||||
|       - name: 'Create or update comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}} | ||||
|         uses: peter-evans/create-or-update-comment@v1 | ||||
|         with: | ||||
|           comment-id: ${{ steps.fc.outputs.comment-id }} | ||||
|           issue-number: ${{ github.event.pull_request.number }} | ||||
|           body: | | ||||
|             **PVS-Studio report for commit `${{steps.names.outputs.short-hash}}`:** | ||||
|             - [Report](https://update.flipperzero.one/builds/firmware-pvs-studio-report/${{steps.names.outputs.artifacts-path}}/${{steps.names.outputs.default-target}}-${{steps.names.outputs.suffix}}/index.html) | ||||
|           edit-mode: replace | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -50,3 +50,7 @@ build/ | ||||
| 
 | ||||
| # openocd output file | ||||
| openocd.log | ||||
| 
 | ||||
| # PVS Studio temporary files | ||||
| .PVS-Studio/ | ||||
| PVS-Studio.log | ||||
|  | ||||
							
								
								
									
										22
									
								
								.pvsconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.pvsconfig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| # MLib macros we can't do much about. | ||||
| //-V:M_EACH:1048,1044 | ||||
| //-V:ARRAY_DEF:760,747,568,776,729,712,654 | ||||
| //-V:LIST_DEF:760,747,568,712,729,654,776 | ||||
| //-V:BPTREE_DEF2:779,1086,557,773,512 | ||||
| //-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685 | ||||
| //-V:ALGO_DEF:1048,747,1044 | ||||
| 
 | ||||
| # Non-severe malloc/null pointer deref warnings | ||||
| //-V::522:2,3 | ||||
| 
 | ||||
| # Warning about headers with copyleft license | ||||
| //-V::1042 | ||||
| 
 | ||||
| # Potentially null argument warnings | ||||
| //-V:memset:575 | ||||
| //-V:memcpy:575 | ||||
| //-V:strcpy:575 | ||||
| //-V:strchr:575 | ||||
| 
 | ||||
| # For loop warning on M_FOREACH | ||||
| //-V:for:1044 | ||||
							
								
								
									
										1
									
								
								.pvsoptions
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.pvsoptions
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e */arm-none-eabi/* | ||||
							
								
								
									
										24
									
								
								ReadMe.md
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								ReadMe.md
									
									
									
									
									
								
							| @ -61,29 +61,6 @@ One liner: `./fbt firmware_flash` | ||||
| 
 | ||||
| 3. Run `dfu-util -D full.dfu -a 0` | ||||
| 
 | ||||
| # Build with Docker | ||||
| 
 | ||||
| ## Prerequisites | ||||
| 
 | ||||
| 1. Install [Docker Engine and Docker Compose](https://www.docker.com/get-started) | ||||
| 2. Prepare the container: | ||||
| 
 | ||||
|  ```sh | ||||
|  docker-compose up -d | ||||
|  ``` | ||||
| 
 | ||||
| ## Compile everything | ||||
| 
 | ||||
| ```sh | ||||
| docker-compose exec dev ./fbt | ||||
| ``` | ||||
| 
 | ||||
| Check `dist/` for build outputs. | ||||
| 
 | ||||
| Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device. | ||||
| 
 | ||||
| If compilation fails, make sure all submodules are all initialized. Either clone with `--recursive` or use `git submodule update --init --recursive`. | ||||
| 
 | ||||
| # Build on Linux/macOS | ||||
| 
 | ||||
| Check out `documentation/fbt.md` for details on building and flashing firmware.  | ||||
| @ -157,7 +134,6 @@ Connect your device via ST-Link and run: | ||||
| - `assets`          - Assets used by applications and services | ||||
| - `furi`            - Furi Core: os level primitives and helpers | ||||
| - `debug`           - Debug tool: GDB-plugins, SVD-file and etc | ||||
| - `docker`          - Docker image sources (used for firmware build automation) | ||||
| - `documentation`   - Documentation generation system configs and input files | ||||
| - `firmware`        - Firmware source code | ||||
| - `lib`             - Our and 3rd party libraries, drivers and etc... | ||||
|  | ||||
| @ -29,23 +29,13 @@ bool archive_app_is_available(void* context, const char* path) { | ||||
| 
 | ||||
|     if(app == ArchiveAppTypeU2f) { | ||||
|         bool file_exists = false; | ||||
|         Storage* fs_api = furi_record_open(RECORD_STORAGE); | ||||
|         File* file = storage_file_alloc(fs_api); | ||||
|         Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
| 
 | ||||
|         file_exists = | ||||
|             storage_file_open(file, ANY_PATH("u2f/key.u2f"), FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|         if(file_exists) { | ||||
|             storage_file_close(file); | ||||
|             file_exists = | ||||
|                 storage_file_open(file, ANY_PATH("u2f/cnt.u2f"), FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|             if(file_exists) { | ||||
|                 storage_file_close(file); | ||||
|             } | ||||
|         if(storage_file_exists(storage, ANY_PATH("u2f/key.u2f"))) { | ||||
|             file_exists = storage_file_exists(storage, ANY_PATH("u2f/cnt.u2f")); | ||||
|         } | ||||
| 
 | ||||
|         storage_file_free(file); | ||||
|         furi_record_close(RECORD_STORAGE); | ||||
| 
 | ||||
|         return file_exists; | ||||
|     } else { | ||||
|         return false; | ||||
|  | ||||
| @ -77,14 +77,24 @@ static void archive_long_load_cb(void* context) { | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void archive_file_browser_set_callbacks(ArchiveBrowserView* browser) { | ||||
| static void archive_file_browser_set_path( | ||||
|     ArchiveBrowserView* browser, | ||||
|     string_t path, | ||||
|     const char* filter_ext, | ||||
|     bool skip_assets) { | ||||
|     furi_assert(browser); | ||||
| 
 | ||||
|     if(!browser->worker_running) { | ||||
|         browser->worker = file_browser_worker_alloc(path, filter_ext, skip_assets); | ||||
|         file_browser_worker_set_callback_context(browser->worker, browser); | ||||
|         file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb); | ||||
|         file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb); | ||||
|         file_browser_worker_set_item_callback(browser->worker, archive_list_item_cb); | ||||
|         file_browser_worker_set_long_load_callback(browser->worker, archive_long_load_cb); | ||||
|         browser->worker_running = true; | ||||
|     } else { | ||||
|         furi_assert(browser->worker); | ||||
|         file_browser_worker_set_config(browser->worker, path, filter_ext, skip_assets); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { | ||||
| @ -438,8 +448,8 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { | ||||
|         tab = archive_get_tab(browser); | ||||
|         if(archive_is_dir_exists(browser->path)) { | ||||
|             bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; | ||||
|             file_browser_worker_set_config( | ||||
|                 browser->worker, browser->path, archive_get_tab_ext(tab), skip_assets); | ||||
|             archive_file_browser_set_path( | ||||
|                 browser, browser->path, archive_get_tab_ext(tab), skip_assets); | ||||
|             tab_empty = false; // Empty check will be performed later
 | ||||
|         } else { | ||||
|             tab_empty = true; | ||||
|  | ||||
| @ -87,4 +87,3 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key); | ||||
| void archive_enter_dir(ArchiveBrowserView* browser, string_t name); | ||||
| void archive_leave_dir(ArchiveBrowserView* browser); | ||||
| void archive_refresh_dir(ArchiveBrowserView* browser); | ||||
| void archive_file_browser_set_callbacks(ArchiveBrowserView* browser); | ||||
|  | ||||
| @ -82,9 +82,8 @@ uint16_t archive_favorites_count(void* context) { | ||||
| static bool archive_favourites_rescan() { | ||||
|     string_t buffer; | ||||
|     string_init(buffer); | ||||
|     Storage* fs_api = furi_record_open(RECORD_STORAGE); | ||||
|     File* file = storage_file_alloc(fs_api); | ||||
|     File* fav_item_file = storage_file_alloc(fs_api); | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
|     File* file = storage_file_alloc(storage); | ||||
| 
 | ||||
|     bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     if(result) { | ||||
| @ -101,13 +100,8 @@ static bool archive_favourites_rescan() { | ||||
|                     archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); | ||||
|                 } | ||||
|             } else { | ||||
|                 bool file_exists = storage_file_open( | ||||
|                     fav_item_file, string_get_cstr(buffer), FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|                 if(file_exists) { | ||||
|                     storage_file_close(fav_item_file); | ||||
|                 if(storage_file_exists(storage, string_get_cstr(buffer))) { | ||||
|                     archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); | ||||
|                 } else { | ||||
|                     storage_file_close(fav_item_file); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @ -116,12 +110,11 @@ static bool archive_favourites_rescan() { | ||||
|     string_clear(buffer); | ||||
| 
 | ||||
|     storage_file_close(file); | ||||
|     storage_common_remove(fs_api, ARCHIVE_FAV_PATH); | ||||
|     storage_common_rename(fs_api, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||
|     storage_common_remove(fs_api, ARCHIVE_FAV_TEMP_PATH); | ||||
|     storage_common_remove(storage, ARCHIVE_FAV_PATH); | ||||
|     storage_common_rename(storage, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||
|     storage_common_remove(storage, ARCHIVE_FAV_TEMP_PATH); | ||||
| 
 | ||||
|     storage_file_free(file); | ||||
|     storage_file_free(fav_item_file); | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
| 
 | ||||
|     return result; | ||||
| @ -131,9 +124,8 @@ bool archive_favorites_read(void* context) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveBrowserView* browser = context; | ||||
|     Storage* fs_api = furi_record_open(RECORD_STORAGE); | ||||
|     File* file = storage_file_alloc(fs_api); | ||||
|     File* fav_item_file = storage_file_alloc(fs_api); | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
|     File* file = storage_file_alloc(storage); | ||||
| 
 | ||||
|     string_t buffer; | ||||
|     FileInfo file_info; | ||||
| @ -163,16 +155,12 @@ bool archive_favorites_read(void* context) { | ||||
|                     need_refresh = true; | ||||
|                 } | ||||
|             } else { | ||||
|                 bool file_exists = storage_file_open( | ||||
|                     fav_item_file, string_get_cstr(buffer), FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|                 if(file_exists) { | ||||
|                     storage_common_stat(fs_api, string_get_cstr(buffer), &file_info); | ||||
|                     storage_file_close(fav_item_file); | ||||
|                 if(storage_file_exists(storage, string_get_cstr(buffer))) { | ||||
|                     storage_common_stat(storage, string_get_cstr(buffer), &file_info); | ||||
|                     archive_add_file_item( | ||||
|                         browser, (file_info.flags & FSF_DIRECTORY), string_get_cstr(buffer)); | ||||
|                     file_count++; | ||||
|                 } else { | ||||
|                     storage_file_close(fav_item_file); | ||||
|                     need_refresh = true; | ||||
|                 } | ||||
|             } | ||||
| @ -183,7 +171,6 @@ bool archive_favorites_read(void* context) { | ||||
|     storage_file_close(file); | ||||
|     string_clear(buffer); | ||||
|     storage_file_free(file); | ||||
|     storage_file_free(fav_item_file); | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
| 
 | ||||
|     archive_set_item_count(browser, file_count); | ||||
|  | ||||
| @ -370,18 +370,15 @@ ArchiveBrowserView* browser_alloc() { | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     browser->worker = file_browser_worker_alloc(browser->path, "*", false); | ||||
|     archive_file_browser_set_callbacks(browser); | ||||
| 
 | ||||
|     file_browser_worker_set_callback_context(browser->worker, browser); | ||||
| 
 | ||||
|     return browser; | ||||
| } | ||||
| 
 | ||||
| void browser_free(ArchiveBrowserView* browser) { | ||||
|     furi_assert(browser); | ||||
| 
 | ||||
|     if(browser->worker_running) { | ||||
|         file_browser_worker_free(browser->worker); | ||||
|     } | ||||
| 
 | ||||
|     with_view_model( | ||||
|         browser->view, (ArchiveBrowserViewModel * model) { | ||||
|  | ||||
| @ -74,6 +74,7 @@ typedef enum { | ||||
| struct ArchiveBrowserView { | ||||
|     View* view; | ||||
|     BrowserWorker* worker; | ||||
|     bool worker_running; | ||||
|     ArchiveBrowserViewCallback callback; | ||||
|     void* context; | ||||
|     string_t path; | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| #include <applications/cli/cli.h> | ||||
| #include <lib/toolbox/args.h> | ||||
| 
 | ||||
| #include "ble.h" | ||||
| #include <ble/ble.h> | ||||
| #include "bt_settings.h" | ||||
| #include "bt_service/bt.h" | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										6
									
								
								applications/bt/bt_hid_app/bt_hid.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										6
									
								
								applications/bt/bt_hid_app/bt_hid.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -89,8 +89,7 @@ BtHid* bt_hid_app_alloc() { | ||||
|         app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "Media Player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); | ||||
|     submenu_add_item(app->submenu, "Media", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); | ||||
|     submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app); | ||||
|     view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit); | ||||
|     view_dispatcher_add_view( | ||||
| @ -134,7 +133,8 @@ BtHid* bt_hid_app_alloc() { | ||||
|         app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse)); | ||||
| 
 | ||||
|     // TODO switch to menu after Media is done
 | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote); | ||||
|     app->view_id = BtHidViewSubmenu; | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); | ||||
| 
 | ||||
|     return app; | ||||
| } | ||||
|  | ||||
| @ -43,7 +43,10 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { | ||||
|     } | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
|     elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); | ||||
| 
 | ||||
|     canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit"); | ||||
| 
 | ||||
|     // Up
 | ||||
|     canvas_draw_icon(canvas, 21, 24, &I_Button_18x18); | ||||
| @ -97,8 +100,8 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { | ||||
|         elements_slightly_rounded_box(canvas, 66, 47, 60, 13); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|     } | ||||
|     canvas_draw_icon(canvas, 110, 49, &I_Ok_btn_9x9); | ||||
|     elements_multiline_text_aligned(canvas, 76, 56, AlignLeft, AlignBottom, "Back"); | ||||
|     canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); | ||||
|     elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back"); | ||||
| } | ||||
| 
 | ||||
| static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) { | ||||
|  | ||||
| @ -49,7 +49,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | ||||
| 
 | ||||
|     // Up
 | ||||
|     if(model->up_pressed) { | ||||
|         canvas_set_bitmap_mode(canvas, 1); | ||||
|         canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); | ||||
|         canvas_set_bitmap_mode(canvas, 0); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|     } | ||||
|     canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6); | ||||
| @ -57,7 +59,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | ||||
| 
 | ||||
|     // Down
 | ||||
|     if(model->down_pressed) { | ||||
|         canvas_set_bitmap_mode(canvas, 1); | ||||
|         canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); | ||||
|         canvas_set_bitmap_mode(canvas, 0); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|     } | ||||
|     canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6); | ||||
| @ -65,7 +69,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | ||||
| 
 | ||||
|     // Left
 | ||||
|     if(model->left_pressed) { | ||||
|         canvas_set_bitmap_mode(canvas, 1); | ||||
|         canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); | ||||
|         canvas_set_bitmap_mode(canvas, 0); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|     } | ||||
|     bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); | ||||
| @ -74,7 +80,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | ||||
| 
 | ||||
|     // Right
 | ||||
|     if(model->right_pressed) { | ||||
|         canvas_set_bitmap_mode(canvas, 1); | ||||
|         canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); | ||||
|         canvas_set_bitmap_mode(canvas, 0); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|     } | ||||
|     bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); | ||||
| @ -89,6 +97,12 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) { | ||||
|     bt_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight); | ||||
|     canvas_draw_line(canvas, 100, 29, 100, 33); | ||||
|     canvas_draw_line(canvas, 102, 29, 102, 33); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
| 
 | ||||
|     // Exit
 | ||||
|     canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); | ||||
| } | ||||
| 
 | ||||
| static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* event) { | ||||
|  | ||||
| @ -36,7 +36,11 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
| 
 | ||||
|     if(model->left_mouse_held == true) { | ||||
|         elements_multiline_text_aligned(canvas, 0, 60, AlignLeft, AlignBottom, "Selecting..."); | ||||
|         elements_multiline_text_aligned(canvas, 0, 62, AlignLeft, AlignBottom, "Selecting..."); | ||||
|     } else { | ||||
|         canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8); | ||||
|         canvas_set_font(canvas, FontSecondary); | ||||
|         elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit"); | ||||
|     } | ||||
| 
 | ||||
|     // Keypad circles
 | ||||
| @ -44,7 +48,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { | ||||
| 
 | ||||
|     // Up
 | ||||
|     if(model->up_pressed) { | ||||
|         canvas_set_bitmap_mode(canvas, 1); | ||||
|         canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13); | ||||
|         canvas_set_bitmap_mode(canvas, 0); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|     } | ||||
|     canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9); | ||||
| @ -52,7 +58,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { | ||||
| 
 | ||||
|     // Down
 | ||||
|     if(model->down_pressed) { | ||||
|         canvas_set_bitmap_mode(canvas, 1); | ||||
|         canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13); | ||||
|         canvas_set_bitmap_mode(canvas, 0); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|     } | ||||
|     canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9); | ||||
| @ -60,7 +68,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { | ||||
| 
 | ||||
|     // Left
 | ||||
|     if(model->left_pressed) { | ||||
|         canvas_set_bitmap_mode(canvas, 1); | ||||
|         canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13); | ||||
|         canvas_set_bitmap_mode(canvas, 0); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|     } | ||||
|     canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7); | ||||
| @ -68,7 +78,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { | ||||
| 
 | ||||
|     // Right
 | ||||
|     if(model->right_pressed) { | ||||
|         canvas_set_bitmap_mode(canvas, 1); | ||||
|         canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13); | ||||
|         canvas_set_bitmap_mode(canvas, 0); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|     } | ||||
|     canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7); | ||||
| @ -76,18 +88,17 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { | ||||
| 
 | ||||
|     // Ok
 | ||||
|     if(model->left_mouse_pressed) { | ||||
|         canvas_draw_icon(canvas, 81, 25, &I_Pressed_Button_13x13); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|         canvas_draw_icon(canvas, 81, 25, &I_Ok_btn_pressed_13x13); | ||||
|     } else { | ||||
|         canvas_draw_icon(canvas, 83, 27, &I_Left_mouse_icon_9x9); | ||||
|     } | ||||
|     canvas_draw_icon(canvas, 83, 27, &I_Ok_btn_9x9); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
| 
 | ||||
|     // Back
 | ||||
|     if(model->right_mouse_pressed) { | ||||
|         canvas_draw_icon(canvas, 108, 48, &I_Pressed_Button_13x13); | ||||
|         canvas_set_color(canvas, ColorWhite); | ||||
|         canvas_draw_icon(canvas, 108, 48, &I_Ok_btn_pressed_13x13); | ||||
|     } else { | ||||
|         canvas_draw_icon(canvas, 110, 50, &I_Right_mouse_icon_9x9); | ||||
|     } | ||||
|     canvas_draw_icon(canvas, 110, 50, &I_Ok_btn_9x9); | ||||
| } | ||||
| 
 | ||||
| static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) { | ||||
|  | ||||
| @ -220,8 +220,7 @@ static bool animation_manager_check_blocking(AnimationManager* animation_manager | ||||
|             furi_assert(blocking_animation); | ||||
|             animation_manager->sd_shown_sd_ok = true; | ||||
|         } else if(!animation_manager->sd_shown_no_db) { | ||||
|             bool db_exists = storage_common_stat(storage, EXT_PATH("Manifest"), NULL) == FSE_OK; | ||||
|             if(!db_exists) { | ||||
|             if(!storage_file_exists(storage, EXT_PATH("Manifest"))) { | ||||
|                 blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME); | ||||
|                 furi_assert(blocking_animation); | ||||
|                 animation_manager->sd_shown_no_db = true; | ||||
|  | ||||
| @ -94,6 +94,10 @@ bool slideshow_is_loaded(Slideshow* slideshow) { | ||||
|     return slideshow->loaded; | ||||
| } | ||||
| 
 | ||||
| bool slideshow_is_one_page(Slideshow* slideshow) { | ||||
|     return slideshow->loaded && (slideshow->icon.frame_count == 1); | ||||
| } | ||||
| 
 | ||||
| bool slideshow_advance(Slideshow* slideshow) { | ||||
|     uint8_t next_frame = slideshow->current_frame + 1; | ||||
|     if(next_frame < slideshow->icon.frame_count) { | ||||
|  | ||||
| @ -9,6 +9,7 @@ Slideshow* slideshow_alloc(); | ||||
| void slideshow_free(Slideshow* slideshow); | ||||
| bool slideshow_load(Slideshow* slideshow, const char* fspath); | ||||
| bool slideshow_is_loaded(Slideshow* slideshow); | ||||
| bool slideshow_is_one_page(Slideshow* slideshow); | ||||
| void slideshow_goback(Slideshow* slideshow); | ||||
| bool slideshow_advance(Slideshow* slideshow); | ||||
| void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y); | ||||
|  | ||||
| @ -23,11 +23,12 @@ void desktop_debug_render(Canvas* canvas, void* model) { | ||||
|     const Version* ver; | ||||
|     char buffer[64]; | ||||
| 
 | ||||
|     static const char* headers[] = {"FW Version Info:", "Dolphin Info:"}; | ||||
|     static const char* headers[] = {"Device Info:", "Dolphin Info:"}; | ||||
| 
 | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontPrimary); | ||||
|     canvas_draw_str(canvas, 2, 9 + STATUS_BAR_Y_SHIFT, headers[m->screen]); | ||||
|     canvas_draw_str_aligned( | ||||
|         canvas, 64, 1 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignTop, headers[m->screen]); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
| 
 | ||||
|     if(m->screen != DesktopViewStatsMeta) { | ||||
| @ -44,7 +45,7 @@ void desktop_debug_render(Canvas* canvas, void* model) { | ||||
|             furi_hal_version_get_hw_region_name(), | ||||
|             furi_hal_region_get_name(), | ||||
|             my_name ? my_name : "Unknown"); | ||||
|         canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer); | ||||
|         canvas_draw_str(canvas, 0, 19 + STATUS_BAR_Y_SHIFT, buffer); | ||||
| 
 | ||||
|         ver = furi_hal_version_get_firmware_version(); | ||||
|         const BleGlueC2Info* c2_ver = NULL; | ||||
| @ -52,7 +53,7 @@ void desktop_debug_render(Canvas* canvas, void* model) { | ||||
|         c2_ver = ble_glue_get_c2_info(); | ||||
| #endif | ||||
|         if(!ver) { | ||||
|             canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, "No info"); | ||||
|             canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, "No info"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| @ -62,7 +63,7 @@ void desktop_debug_render(Canvas* canvas, void* model) { | ||||
|             "%s [%s]", | ||||
|             version_get_version(ver), | ||||
|             version_get_builddate(ver)); | ||||
|         canvas_draw_str(canvas, 5, 28 + STATUS_BAR_Y_SHIFT, buffer); | ||||
|         canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, buffer); | ||||
| 
 | ||||
|         snprintf( | ||||
|             buffer, | ||||
| @ -72,11 +73,11 @@ void desktop_debug_render(Canvas* canvas, void* model) { | ||||
|             version_get_githash(ver), | ||||
|             version_get_gitbranchnum(ver), | ||||
|             c2_ver ? c2_ver->StackTypeString : "<none>"); | ||||
|         canvas_draw_str(canvas, 5, 39 + STATUS_BAR_Y_SHIFT, buffer); | ||||
|         canvas_draw_str(canvas, 0, 40 + STATUS_BAR_Y_SHIFT, buffer); | ||||
| 
 | ||||
|         snprintf( | ||||
|             buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver)); | ||||
|         canvas_draw_str(canvas, 5, 50 + STATUS_BAR_Y_SHIFT, buffer); | ||||
|         canvas_draw_str(canvas, 0, 50 + STATUS_BAR_Y_SHIFT, buffer); | ||||
| 
 | ||||
|     } else { | ||||
|         Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); | ||||
|  | ||||
| @ -35,8 +35,9 @@ static bool desktop_view_slideshow_input(InputEvent* event, void* context) { | ||||
|     furi_assert(event); | ||||
|     DesktopSlideshowView* instance = context; | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|     DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||
|     bool update_view = false; | ||||
|     if(event->type == InputTypeShort) { | ||||
|         bool end_slideshow = false; | ||||
|         switch(event->key) { | ||||
|         case InputKeyLeft: | ||||
| @ -54,15 +55,18 @@ static bool desktop_view_slideshow_input(InputEvent* event, void* context) { | ||||
|         if(end_slideshow) { | ||||
|             instance->callback(DesktopSlideshowCompleted, instance->context); | ||||
|         } | ||||
|         view_commit_model(instance->view, true); | ||||
|         update_view = true; | ||||
|     } else if(event->key == InputKeyOk) { | ||||
|         if(event->type == InputTypePress) { | ||||
|             furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_SHORT); | ||||
|         } else if(event->type == InputTypeRelease) { | ||||
|             furi_timer_stop(instance->timer); | ||||
|             if(!slideshow_is_one_page(model->slideshow)) { | ||||
|                 furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_LONG); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     view_commit_model(instance->view, update_view); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| @ -79,12 +83,12 @@ static void desktop_view_slideshow_enter(void* context) { | ||||
|     instance->timer = | ||||
|         furi_timer_alloc(desktop_first_start_timer_callback, FuriTimerTypeOnce, instance); | ||||
| 
 | ||||
|     furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_LONG); | ||||
| 
 | ||||
|     DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||
|     model->slideshow = slideshow_alloc(); | ||||
|     if(!slideshow_load(model->slideshow, SLIDESHOW_FS_PATH)) { | ||||
|         instance->callback(DesktopSlideshowCompleted, instance->context); | ||||
|     } else if(!slideshow_is_one_page(model->slideshow)) { | ||||
|         furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_LONG); | ||||
|     } | ||||
|     view_commit_model(instance->view, false); | ||||
| } | ||||
|  | ||||
| @ -14,8 +14,8 @@ | ||||
| #define DOLPHIN_STATE_PATH INT_PATH(DOLPHIN_STATE_FILE_NAME) | ||||
| #define DOLPHIN_STATE_HEADER_MAGIC 0xD0 | ||||
| #define DOLPHIN_STATE_HEADER_VERSION 0x01 | ||||
| #define LEVEL2_THRESHOLD 735 | ||||
| #define LEVEL3_THRESHOLD 2940 | ||||
| #define LEVEL2_THRESHOLD 300 | ||||
| #define LEVEL3_THRESHOLD 1800 | ||||
| #define BUTTHURT_MAX 14 | ||||
| #define BUTTHURT_MIN 0 | ||||
| 
 | ||||
|  | ||||
| @ -99,6 +99,11 @@ static bool browser_folder_check_and_switch(string_t path) { | ||||
|     FileInfo file_info; | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
|     bool is_root = false; | ||||
| 
 | ||||
|     if(string_search_rchar(path, '/') == 0) { | ||||
|         is_root = true; | ||||
|     } | ||||
| 
 | ||||
|     while(1) { | ||||
|         // Check if folder is existing and navigate back if not
 | ||||
|         if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { | ||||
|  | ||||
| @ -162,6 +162,19 @@ void widget_add_text_box_element( | ||||
|     widget_add_element(widget, text_box_element); | ||||
| } | ||||
| 
 | ||||
| void widget_add_text_scroll_element( | ||||
|     Widget* widget, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     uint8_t width, | ||||
|     uint8_t height, | ||||
|     const char* text) { | ||||
|     furi_assert(widget); | ||||
|     WidgetElement* text_scroll_element = | ||||
|         widget_element_text_scroll_create(x, y, width, height, text); | ||||
|     widget_add_element(widget, text_scroll_element); | ||||
| } | ||||
| 
 | ||||
| void widget_add_button_element( | ||||
|     Widget* widget, | ||||
|     GuiButtonType button_type, | ||||
|  | ||||
| @ -105,6 +105,27 @@ void widget_add_text_box_element( | ||||
|     const char* text, | ||||
|     bool strip_to_dots); | ||||
| 
 | ||||
| /** Add Text Scroll Element
 | ||||
|  * | ||||
|  * @param      widget           Widget instance | ||||
|  * @param      x                x coordinate | ||||
|  * @param      y                y coordinate | ||||
|  * @param      width            width to fit text | ||||
|  * @param      height           height to fit text | ||||
|  * @param[in]  text             Formatted text. Default format: align left, Secondary font. | ||||
|  *                              The following formats are available: | ||||
|  *                               "\e#Bold text" - sets bold font before until next '\n' symbol | ||||
|  *                               "\ecCenter-aligned text" - sets center horizontal align until the next '\n' symbol | ||||
|  *                               "\erRight-aligned text" - sets right horizontal align until the next '\n' symbol | ||||
|  */ | ||||
| void widget_add_text_scroll_element( | ||||
|     Widget* widget, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     uint8_t width, | ||||
|     uint8_t height, | ||||
|     const char* text); | ||||
| 
 | ||||
| /** Add Button Element
 | ||||
|  * | ||||
|  * @param      widget       Widget instance | ||||
|  | ||||
| @ -29,6 +29,7 @@ struct WidgetElement { | ||||
| 
 | ||||
|     // generic model holder
 | ||||
|     void* model; | ||||
|     FuriMutex* model_mutex; | ||||
| 
 | ||||
|     // pointer to widget that hold our element
 | ||||
|     Widget* parent; | ||||
| @ -80,3 +81,10 @@ WidgetElement* widget_element_frame_create( | ||||
|     uint8_t width, | ||||
|     uint8_t height, | ||||
|     uint8_t radius); | ||||
| 
 | ||||
| WidgetElement* widget_element_text_scroll_create( | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     uint8_t width, | ||||
|     uint8_t height, | ||||
|     const char* text); | ||||
|  | ||||
| @ -0,0 +1,245 @@ | ||||
| #include "widget_element_i.h" | ||||
| #include <m-string.h> | ||||
| #include <gui/elements.h> | ||||
| #include <m-array.h> | ||||
| 
 | ||||
| #define WIDGET_ELEMENT_TEXT_SCROLL_BAR_OFFSET (4) | ||||
| 
 | ||||
| typedef struct { | ||||
|     Font font; | ||||
|     Align horizontal; | ||||
|     string_t text; | ||||
| } TextScrollLineArray; | ||||
| 
 | ||||
| ARRAY_DEF(TextScrollLineArray, TextScrollLineArray, M_POD_OPLIST) | ||||
| 
 | ||||
| typedef struct { | ||||
|     TextScrollLineArray_t line_array; | ||||
|     uint8_t x; | ||||
|     uint8_t y; | ||||
|     uint8_t width; | ||||
|     uint8_t height; | ||||
|     string_t text; | ||||
|     uint8_t scroll_pos_total; | ||||
|     uint8_t scroll_pos_current; | ||||
|     bool text_formatted; | ||||
| } WidgetElementTextScrollModel; | ||||
| 
 | ||||
| static bool | ||||
|     widget_element_text_scroll_process_ctrl_symbols(TextScrollLineArray* line, string_t text) { | ||||
|     bool processed = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(string_get_char(text, 0) != '\e') break; | ||||
|         char ctrl_symbol = string_get_char(text, 1); | ||||
|         if(ctrl_symbol == 'c') { | ||||
|             line->horizontal = AlignCenter; | ||||
|         } else if(ctrl_symbol == 'r') { | ||||
|             line->horizontal = AlignRight; | ||||
|         } else if(ctrl_symbol == '#') { | ||||
|             line->font = FontPrimary; | ||||
|         } | ||||
|         string_right(text, 2); | ||||
|         processed = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return processed; | ||||
| } | ||||
| 
 | ||||
| void widget_element_text_scroll_add_line(WidgetElement* element, TextScrollLineArray* line) { | ||||
|     WidgetElementTextScrollModel* model = element->model; | ||||
|     TextScrollLineArray new_line; | ||||
|     new_line.font = line->font; | ||||
|     new_line.horizontal = line->horizontal; | ||||
|     string_init_set(new_line.text, line->text); | ||||
|     TextScrollLineArray_push_back(model->line_array, new_line); | ||||
| } | ||||
| 
 | ||||
| static void widget_element_text_scroll_fill_lines(Canvas* canvas, WidgetElement* element) { | ||||
|     WidgetElementTextScrollModel* model = element->model; | ||||
|     TextScrollLineArray line_tmp; | ||||
|     bool all_text_processed = false; | ||||
|     string_init(line_tmp.text); | ||||
|     bool reached_new_line = true; | ||||
|     uint16_t total_height = 0; | ||||
| 
 | ||||
|     while(!all_text_processed) { | ||||
|         if(reached_new_line) { | ||||
|             // Set default line properties
 | ||||
|             line_tmp.font = FontSecondary; | ||||
|             line_tmp.horizontal = AlignLeft; | ||||
|             string_reset(line_tmp.text); | ||||
|             // Process control symbols
 | ||||
|             while(widget_element_text_scroll_process_ctrl_symbols(&line_tmp, model->text)) | ||||
|                 ; | ||||
|         } | ||||
|         // Set canvas font
 | ||||
|         canvas_set_font(canvas, line_tmp.font); | ||||
|         CanvasFontParameters* params = canvas_get_font_params(canvas, line_tmp.font); | ||||
|         total_height += params->height; | ||||
|         if(total_height > model->height) { | ||||
|             model->scroll_pos_total++; | ||||
|         } | ||||
| 
 | ||||
|         uint8_t line_width = 0; | ||||
|         uint16_t char_i = 0; | ||||
|         while(true) { | ||||
|             char next_char = string_get_char(model->text, char_i++); | ||||
|             if(next_char == '\0') { | ||||
|                 string_push_back(line_tmp.text, '\0'); | ||||
|                 widget_element_text_scroll_add_line(element, &line_tmp); | ||||
|                 total_height += params->leading_default - params->height; | ||||
|                 all_text_processed = true; | ||||
|                 break; | ||||
|             } else if(next_char == '\n') { | ||||
|                 string_push_back(line_tmp.text, '\0'); | ||||
|                 widget_element_text_scroll_add_line(element, &line_tmp); | ||||
|                 string_right(model->text, char_i); | ||||
|                 total_height += params->leading_default - params->height; | ||||
|                 reached_new_line = true; | ||||
|                 break; | ||||
|             } else { | ||||
|                 line_width += canvas_glyph_width(canvas, next_char); | ||||
|                 if(line_width > model->width) { | ||||
|                     string_push_back(line_tmp.text, '\0'); | ||||
|                     widget_element_text_scroll_add_line(element, &line_tmp); | ||||
|                     string_right(model->text, char_i - 1); | ||||
|                     string_reset(line_tmp.text); | ||||
|                     total_height += params->leading_default - params->height; | ||||
|                     reached_new_line = false; | ||||
|                     break; | ||||
|                 } else { | ||||
|                     string_push_back(line_tmp.text, next_char); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(line_tmp.text); | ||||
| } | ||||
| 
 | ||||
| static void widget_element_text_scroll_draw(Canvas* canvas, WidgetElement* element) { | ||||
|     furi_assert(canvas); | ||||
|     furi_assert(element); | ||||
| 
 | ||||
|     furi_mutex_acquire(element->model_mutex, FuriWaitForever); | ||||
| 
 | ||||
|     WidgetElementTextScrollModel* model = element->model; | ||||
|     if(!model->text_formatted) { | ||||
|         widget_element_text_scroll_fill_lines(canvas, element); | ||||
|         model->text_formatted = true; | ||||
|     } | ||||
| 
 | ||||
|     uint8_t y = model->y; | ||||
|     uint8_t x = model->x; | ||||
|     uint16_t curr_line = 0; | ||||
|     if(TextScrollLineArray_size(model->line_array)) { | ||||
|         TextScrollLineArray_it_t it; | ||||
|         for(TextScrollLineArray_it(it, model->line_array); !TextScrollLineArray_end_p(it); | ||||
|             TextScrollLineArray_next(it), curr_line++) { | ||||
|             if(curr_line < model->scroll_pos_current) continue; | ||||
|             TextScrollLineArray* line = TextScrollLineArray_ref(it); | ||||
|             CanvasFontParameters* params = canvas_get_font_params(canvas, line->font); | ||||
|             if(y + params->descender > model->y + model->height) break; | ||||
|             canvas_set_font(canvas, line->font); | ||||
|             if(line->horizontal == AlignLeft) { | ||||
|                 x = model->x; | ||||
|             } else if(line->horizontal == AlignCenter) { | ||||
|                 x = (model->x + model->width) / 2; | ||||
|             } else if(line->horizontal == AlignRight) { | ||||
|                 x = model->x + model->width; | ||||
|             } | ||||
|             canvas_draw_str_aligned( | ||||
|                 canvas, x, y, line->horizontal, AlignTop, string_get_cstr(line->text)); | ||||
|             y += params->leading_default; | ||||
|         } | ||||
|         // Draw scroll bar
 | ||||
|         if(model->scroll_pos_total > 1) { | ||||
|             elements_scrollbar_pos( | ||||
|                 canvas, | ||||
|                 model->x + model->width + WIDGET_ELEMENT_TEXT_SCROLL_BAR_OFFSET, | ||||
|                 model->y, | ||||
|                 model->height, | ||||
|                 model->scroll_pos_current, | ||||
|                 model->scroll_pos_total); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_mutex_release(element->model_mutex); | ||||
| } | ||||
| 
 | ||||
| static bool widget_element_text_scroll_input(InputEvent* event, WidgetElement* element) { | ||||
|     furi_assert(event); | ||||
|     furi_assert(element); | ||||
| 
 | ||||
|     furi_mutex_acquire(element->model_mutex, FuriWaitForever); | ||||
| 
 | ||||
|     WidgetElementTextScrollModel* model = element->model; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { | ||||
|         if(event->key == InputKeyUp) { | ||||
|             if(model->scroll_pos_current > 0) { | ||||
|                 model->scroll_pos_current--; | ||||
|             } | ||||
|             consumed = true; | ||||
|         } else if(event->key == InputKeyDown) { | ||||
|             if((model->scroll_pos_total > 1) && | ||||
|                (model->scroll_pos_current < model->scroll_pos_total - 1)) { | ||||
|                 model->scroll_pos_current++; | ||||
|             } | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_mutex_release(element->model_mutex); | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| static void widget_element_text_scroll_free(WidgetElement* text_scroll) { | ||||
|     furi_assert(text_scroll); | ||||
| 
 | ||||
|     WidgetElementTextScrollModel* model = text_scroll->model; | ||||
|     TextScrollLineArray_it_t it; | ||||
|     for(TextScrollLineArray_it(it, model->line_array); !TextScrollLineArray_end_p(it); | ||||
|         TextScrollLineArray_next(it)) { | ||||
|         TextScrollLineArray* line = TextScrollLineArray_ref(it); | ||||
|         string_clear(line->text); | ||||
|     } | ||||
|     TextScrollLineArray_clear(model->line_array); | ||||
|     string_clear(model->text); | ||||
|     free(text_scroll->model); | ||||
|     furi_mutex_free(text_scroll->model_mutex); | ||||
|     free(text_scroll); | ||||
| } | ||||
| 
 | ||||
| WidgetElement* widget_element_text_scroll_create( | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     uint8_t width, | ||||
|     uint8_t height, | ||||
|     const char* text) { | ||||
|     furi_assert(text); | ||||
| 
 | ||||
|     // Allocate and init model
 | ||||
|     WidgetElementTextScrollModel* model = malloc(sizeof(WidgetElementTextScrollModel)); | ||||
|     model->x = x; | ||||
|     model->y = y; | ||||
|     model->width = width - WIDGET_ELEMENT_TEXT_SCROLL_BAR_OFFSET; | ||||
|     model->height = height; | ||||
|     model->scroll_pos_current = 0; | ||||
|     model->scroll_pos_total = 1; | ||||
|     TextScrollLineArray_init(model->line_array); | ||||
|     string_init_set_str(model->text, text); | ||||
| 
 | ||||
|     WidgetElement* text_scroll = malloc(sizeof(WidgetElement)); | ||||
|     text_scroll->parent = NULL; | ||||
|     text_scroll->draw = widget_element_text_scroll_draw; | ||||
|     text_scroll->input = widget_element_text_scroll_input; | ||||
|     text_scroll->free = widget_element_text_scroll_free; | ||||
|     text_scroll->model = model; | ||||
|     text_scroll->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); | ||||
| 
 | ||||
|     return text_scroll; | ||||
| } | ||||
| @ -1,50 +0,0 @@ | ||||
| #include "decoder_analyzer.h" | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| // FIXME: unused args?
 | ||||
| bool DecoderAnalyzer::read(uint8_t* /* _data */, uint8_t /* _data_size */) { | ||||
|     bool result = false; | ||||
| 
 | ||||
|     if(ready) { | ||||
|         result = true; | ||||
| 
 | ||||
|         for(size_t i = 0; i < data_size; i++) { | ||||
|             printf("%lu ", data[i]); | ||||
|             if((i + 1) % 8 == 0) printf("\r\n"); | ||||
|         } | ||||
|         printf("\r\n--------\r\n"); | ||||
| 
 | ||||
|         ready = false; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void DecoderAnalyzer::process_front(bool polarity, uint32_t time) { | ||||
|     UNUSED(polarity); | ||||
|     if(ready) return; | ||||
| 
 | ||||
|     data[data_index] = time; | ||||
| 
 | ||||
|     if(data_index < data_size) { | ||||
|         data_index++; | ||||
|     } else { | ||||
|         data_index = 0; | ||||
|         ready = true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| DecoderAnalyzer::DecoderAnalyzer() { | ||||
|     data = reinterpret_cast<uint32_t*>(calloc(data_size, sizeof(uint32_t))); | ||||
|     furi_check(data); | ||||
|     data_index = 0; | ||||
|     ready = false; | ||||
| } | ||||
| 
 | ||||
| DecoderAnalyzer::~DecoderAnalyzer() { | ||||
|     free(data); | ||||
| } | ||||
| 
 | ||||
| void DecoderAnalyzer::reset_state() { | ||||
| } | ||||
| @ -1,21 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <atomic> | ||||
| 
 | ||||
| class DecoderAnalyzer { | ||||
| public: | ||||
|     bool read(uint8_t* data, uint8_t data_size); | ||||
|     void process_front(bool polarity, uint32_t time); | ||||
| 
 | ||||
|     DecoderAnalyzer(); | ||||
|     ~DecoderAnalyzer(); | ||||
| 
 | ||||
| private: | ||||
|     void reset_state(); | ||||
| 
 | ||||
|     std::atomic<bool> ready; | ||||
| 
 | ||||
|     static const uint32_t data_size = 2048; | ||||
|     uint32_t data_index = 0; | ||||
|     uint32_t* data; | ||||
| }; | ||||
| @ -1,72 +0,0 @@ | ||||
| #include "emmarin.h" | ||||
| #include "decoder_emmarin.h" | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| constexpr uint32_t clocks_in_us = 64; | ||||
| constexpr uint32_t short_time = 255 * clocks_in_us; | ||||
| constexpr uint32_t long_time = 510 * clocks_in_us; | ||||
| constexpr uint32_t jitter_time = 100 * clocks_in_us; | ||||
| 
 | ||||
| constexpr uint32_t short_time_low = short_time - jitter_time; | ||||
| constexpr uint32_t short_time_high = short_time + jitter_time; | ||||
| constexpr uint32_t long_time_low = long_time - jitter_time; | ||||
| constexpr uint32_t long_time_high = long_time + jitter_time; | ||||
| 
 | ||||
| void DecoderEMMarin::reset_state() { | ||||
|     ready = false; | ||||
|     read_data = 0; | ||||
|     manchester_advance( | ||||
|         manchester_saved_state, ManchesterEventReset, &manchester_saved_state, nullptr); | ||||
| } | ||||
| 
 | ||||
| bool DecoderEMMarin::read(uint8_t* data, uint8_t data_size) { | ||||
|     bool result = false; | ||||
| 
 | ||||
|     if(ready) { | ||||
|         result = true; | ||||
|         em_marin.decode( | ||||
|             reinterpret_cast<const uint8_t*>(&read_data), sizeof(uint64_t), data, data_size); | ||||
|         ready = false; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void DecoderEMMarin::process_front(bool polarity, uint32_t time) { | ||||
|     if(ready) return; | ||||
|     if(time < short_time_low) return; | ||||
| 
 | ||||
|     ManchesterEvent event = ManchesterEventReset; | ||||
| 
 | ||||
|     if(time > short_time_low && time < short_time_high) { | ||||
|         if(polarity) { | ||||
|             event = ManchesterEventShortHigh; | ||||
|         } else { | ||||
|             event = ManchesterEventShortLow; | ||||
|         } | ||||
|     } else if(time > long_time_low && time < long_time_high) { | ||||
|         if(polarity) { | ||||
|             event = ManchesterEventLongHigh; | ||||
|         } else { | ||||
|             event = ManchesterEventLongLow; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(event != ManchesterEventReset) { | ||||
|         bool data; | ||||
|         bool data_ok = | ||||
|             manchester_advance(manchester_saved_state, event, &manchester_saved_state, &data); | ||||
| 
 | ||||
|         if(data_ok) { | ||||
|             read_data = (read_data << 1) | data; | ||||
| 
 | ||||
|             ready = em_marin.can_be_decoded( | ||||
|                 reinterpret_cast<const uint8_t*>(&read_data), sizeof(uint64_t)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| DecoderEMMarin::DecoderEMMarin() { | ||||
|     reset_state(); | ||||
| } | ||||
| @ -1,21 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <atomic> | ||||
| #include <lib/toolbox/manchester_decoder.h> | ||||
| #include "protocols/protocol_emmarin.h" | ||||
| class DecoderEMMarin { | ||||
| public: | ||||
|     bool read(uint8_t* data, uint8_t data_size); | ||||
|     void process_front(bool polarity, uint32_t time); | ||||
| 
 | ||||
|     DecoderEMMarin(); | ||||
| 
 | ||||
| private: | ||||
|     void reset_state(); | ||||
| 
 | ||||
|     uint64_t read_data = 0; | ||||
|     std::atomic<bool> ready; | ||||
| 
 | ||||
|     ManchesterState manchester_saved_state; | ||||
|     ProtocolEMMarin em_marin; | ||||
| }; | ||||
| @ -1,15 +0,0 @@ | ||||
| #include "decoder_gpio_out.h" | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| void DecoderGpioOut::process_front(bool polarity, uint32_t /* time */) { | ||||
|     furi_hal_gpio_write(&gpio_ext_pa7, polarity); | ||||
| } | ||||
| 
 | ||||
| DecoderGpioOut::DecoderGpioOut() { | ||||
|     furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull); | ||||
| } | ||||
| 
 | ||||
| DecoderGpioOut::~DecoderGpioOut() { | ||||
|     furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog); | ||||
| } | ||||
| @ -1,14 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <atomic> | ||||
| 
 | ||||
| class DecoderGpioOut { | ||||
| public: | ||||
|     void process_front(bool polarity, uint32_t time); | ||||
| 
 | ||||
|     DecoderGpioOut(); | ||||
|     ~DecoderGpioOut(); | ||||
| 
 | ||||
| private: | ||||
|     void reset_state(); | ||||
| }; | ||||
| @ -1,98 +0,0 @@ | ||||
| #include "decoder_hid26.h" | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| constexpr uint32_t clocks_in_us = 64; | ||||
| 
 | ||||
| constexpr uint32_t jitter_time_us = 20; | ||||
| constexpr uint32_t min_time_us = 64; | ||||
| constexpr uint32_t max_time_us = 80; | ||||
| 
 | ||||
| constexpr uint32_t min_time = (min_time_us - jitter_time_us) * clocks_in_us; | ||||
| constexpr uint32_t mid_time = ((max_time_us - min_time_us) / 2 + min_time_us) * clocks_in_us; | ||||
| constexpr uint32_t max_time = (max_time_us + jitter_time_us) * clocks_in_us; | ||||
| 
 | ||||
| bool DecoderHID26::read(uint8_t* data, uint8_t data_size) { | ||||
|     bool result = false; | ||||
|     furi_assert(data_size >= 3); | ||||
| 
 | ||||
|     if(ready) { | ||||
|         result = true; | ||||
|         hid.decode( | ||||
|             reinterpret_cast<const uint8_t*>(&stored_data), sizeof(uint32_t) * 3, data, data_size); | ||||
|         ready = false; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void DecoderHID26::process_front(bool polarity, uint32_t time) { | ||||
|     if(ready) return; | ||||
| 
 | ||||
|     if(polarity == true) { | ||||
|         last_pulse_time = time; | ||||
|     } else { | ||||
|         last_pulse_time += time; | ||||
| 
 | ||||
|         if(last_pulse_time > min_time && last_pulse_time < max_time) { | ||||
|             bool pulse; | ||||
| 
 | ||||
|             if(last_pulse_time < mid_time) { | ||||
|                 // 6 pulses
 | ||||
|                 pulse = false; | ||||
|             } else { | ||||
|                 // 5 pulses
 | ||||
|                 pulse = true; | ||||
|             } | ||||
| 
 | ||||
|             if(last_pulse == pulse) { | ||||
|                 pulse_count++; | ||||
| 
 | ||||
|                 if(pulse) { | ||||
|                     if(pulse_count > 4) { | ||||
|                         pulse_count = 0; | ||||
|                         store_data(1); | ||||
|                     } | ||||
|                 } else { | ||||
|                     if(pulse_count > 5) { | ||||
|                         pulse_count = 0; | ||||
|                         store_data(0); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 if(last_pulse) { | ||||
|                     if(pulse_count > 2) { | ||||
|                         store_data(1); | ||||
|                     } | ||||
|                 } else { | ||||
|                     if(pulse_count > 3) { | ||||
|                         store_data(0); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 pulse_count = 0; | ||||
|                 last_pulse = pulse; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| DecoderHID26::DecoderHID26() { | ||||
|     reset_state(); | ||||
| } | ||||
| 
 | ||||
| void DecoderHID26::store_data(bool data) { | ||||
|     stored_data[0] = (stored_data[0] << 1) | ((stored_data[1] >> 31) & 1); | ||||
|     stored_data[1] = (stored_data[1] << 1) | ((stored_data[2] >> 31) & 1); | ||||
|     stored_data[2] = (stored_data[2] << 1) | data; | ||||
| 
 | ||||
|     if(hid.can_be_decoded(reinterpret_cast<const uint8_t*>(&stored_data), sizeof(uint32_t) * 3)) { | ||||
|         ready = true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DecoderHID26::reset_state() { | ||||
|     last_pulse = false; | ||||
|     pulse_count = 0; | ||||
|     ready = false; | ||||
|     last_pulse_time = 0; | ||||
| } | ||||
| @ -1,24 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <atomic> | ||||
| #include "protocols/protocol_hid_h10301.h" | ||||
| 
 | ||||
| class DecoderHID26 { | ||||
| public: | ||||
|     bool read(uint8_t* data, uint8_t data_size); | ||||
|     void process_front(bool polarity, uint32_t time); | ||||
|     DecoderHID26(); | ||||
| 
 | ||||
| private: | ||||
|     uint32_t last_pulse_time = 0; | ||||
|     bool last_pulse; | ||||
|     uint8_t pulse_count; | ||||
| 
 | ||||
|     uint32_t stored_data[3] = {0, 0, 0}; | ||||
|     void store_data(bool data); | ||||
| 
 | ||||
|     std::atomic<bool> ready; | ||||
| 
 | ||||
|     void reset_state(); | ||||
|     ProtocolHID10301 hid; | ||||
| }; | ||||
| @ -1,76 +0,0 @@ | ||||
| #include "decoder_indala.h" | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| constexpr uint32_t clocks_in_us = 64; | ||||
| constexpr uint32_t us_per_bit = 255; | ||||
| 
 | ||||
| bool DecoderIndala::read(uint8_t* data, uint8_t data_size) { | ||||
|     bool result = false; | ||||
| 
 | ||||
|     if(ready) { | ||||
|         result = true; | ||||
|         if(cursed_data_valid) { | ||||
|             indala.decode( | ||||
|                 reinterpret_cast<const uint8_t*>(&cursed_raw_data), | ||||
|                 sizeof(uint64_t), | ||||
|                 data, | ||||
|                 data_size); | ||||
|         } else { | ||||
|             indala.decode( | ||||
|                 reinterpret_cast<const uint8_t*>(&raw_data), sizeof(uint64_t), data, data_size); | ||||
|         } | ||||
|         reset_state(); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void DecoderIndala::process_front(bool polarity, uint32_t time) { | ||||
|     if(ready) return; | ||||
| 
 | ||||
|     process_internal(polarity, time, &raw_data); | ||||
|     if(ready) return; | ||||
| 
 | ||||
|     if(polarity) { | ||||
|         time = time + 110; | ||||
|     } else { | ||||
|         time = time - 110; | ||||
|     } | ||||
| 
 | ||||
|     process_internal(!polarity, time, &cursed_raw_data); | ||||
|     if(ready) { | ||||
|         cursed_data_valid = true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DecoderIndala::process_internal(bool polarity, uint32_t time, uint64_t* data) { | ||||
|     time /= clocks_in_us; | ||||
|     time += (us_per_bit / 2); | ||||
| 
 | ||||
|     uint32_t bit_count = (time / us_per_bit); | ||||
| 
 | ||||
|     if(bit_count < 64) { | ||||
|         for(uint32_t i = 0; i < bit_count; i++) { | ||||
|             *data = (*data << 1) | polarity; | ||||
| 
 | ||||
|             if((*data >> 32) == 0xa0000000ULL) { | ||||
|                 if(indala.can_be_decoded( | ||||
|                        reinterpret_cast<const uint8_t*>(data), sizeof(uint64_t))) { | ||||
|                     ready = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| DecoderIndala::DecoderIndala() { | ||||
|     reset_state(); | ||||
| } | ||||
| 
 | ||||
| void DecoderIndala::reset_state() { | ||||
|     raw_data = 0; | ||||
|     cursed_raw_data = 0; | ||||
|     ready = false; | ||||
|     cursed_data_valid = false; | ||||
| } | ||||
| @ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <limits.h> | ||||
| #include <atomic> | ||||
| #include "protocols/protocol_indala_40134.h" | ||||
| 
 | ||||
| class DecoderIndala { | ||||
| public: | ||||
|     bool read(uint8_t* data, uint8_t data_size); | ||||
|     void process_front(bool polarity, uint32_t time); | ||||
| 
 | ||||
|     void process_internal(bool polarity, uint32_t time, uint64_t* data); | ||||
| 
 | ||||
|     DecoderIndala(); | ||||
| 
 | ||||
| private: | ||||
|     void reset_state(); | ||||
| 
 | ||||
|     uint64_t raw_data; | ||||
|     uint64_t cursed_raw_data; | ||||
| 
 | ||||
|     std::atomic<bool> ready; | ||||
|     std::atomic<bool> cursed_data_valid; | ||||
|     ProtocolIndala40134 indala; | ||||
| }; | ||||
| @ -1,107 +0,0 @@ | ||||
| #include "decoder_ioprox.h" | ||||
| #include <furi_hal.h> | ||||
| #include <cli/cli.h> | ||||
| #include <utility> | ||||
| 
 | ||||
| constexpr uint32_t clocks_in_us = 64; | ||||
| 
 | ||||
| constexpr uint32_t jitter_time_us = 20; | ||||
| constexpr uint32_t min_time_us = 64; | ||||
| constexpr uint32_t max_time_us = 80; | ||||
| constexpr uint32_t baud_time_us = 500; | ||||
| 
 | ||||
| constexpr uint32_t min_time = (min_time_us - jitter_time_us) * clocks_in_us; | ||||
| constexpr uint32_t mid_time = ((max_time_us - min_time_us) / 2 + min_time_us) * clocks_in_us; | ||||
| constexpr uint32_t max_time = (max_time_us + jitter_time_us) * clocks_in_us; | ||||
| constexpr uint32_t baud_time = baud_time_us * clocks_in_us; | ||||
| 
 | ||||
| bool DecoderIoProx::read(uint8_t* data, uint8_t data_size) { | ||||
|     bool result = false; | ||||
|     furi_assert(data_size >= 4); | ||||
| 
 | ||||
|     if(ready) { | ||||
|         result = true; | ||||
|         ioprox.decode(raw_data, sizeof(raw_data), data, data_size); | ||||
|         ready = false; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void DecoderIoProx::process_front(bool is_rising_edge, uint32_t time) { | ||||
|     if(ready) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // Always track the time that's gone by.
 | ||||
|     current_period_duration += time; | ||||
|     demodulation_sample_duration += time; | ||||
| 
 | ||||
|     // If a baud time has elapsed, we're at a sample point.
 | ||||
|     if(demodulation_sample_duration >= baud_time) { | ||||
|         // Start a new baud period...
 | ||||
|         demodulation_sample_duration = 0; | ||||
|         demodulated_value_invalid = false; | ||||
| 
 | ||||
|         // ... and if we didn't have any baud errors, capture a sample.
 | ||||
|         if(!demodulated_value_invalid) { | ||||
|             store_data(current_demodulated_value); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //
 | ||||
|     // FSK demodulator.
 | ||||
|     //
 | ||||
| 
 | ||||
|     // If this isn't a rising edge, this isn't a pulse of interest.
 | ||||
|     // We're done.
 | ||||
|     if(!is_rising_edge) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     bool is_valid_low = (current_period_duration > min_time) && | ||||
|                         (current_period_duration <= mid_time); | ||||
|     bool is_valid_high = (current_period_duration > mid_time) && | ||||
|                          (current_period_duration < max_time); | ||||
| 
 | ||||
|     // If this is between the minimum and our threshold, this is a logical 0.
 | ||||
|     if(is_valid_low) { | ||||
|         current_demodulated_value = false; | ||||
|     } | ||||
|     // Otherwise, if between our threshold and the max time, it's a logical 1.
 | ||||
|     else if(is_valid_high) { | ||||
|         current_demodulated_value = true; | ||||
|     } | ||||
|     // Otherwise, invalidate this sample.
 | ||||
|     else { | ||||
|         demodulated_value_invalid = true; | ||||
|     } | ||||
| 
 | ||||
|     // We're starting a new period; track that.
 | ||||
|     current_period_duration = 0; | ||||
| } | ||||
| 
 | ||||
| DecoderIoProx::DecoderIoProx() { | ||||
|     reset_state(); | ||||
| } | ||||
| 
 | ||||
| void DecoderIoProx::store_data(bool data) { | ||||
|     for(int i = 0; i < 7; ++i) { | ||||
|         raw_data[i] = (raw_data[i] << 1) | ((raw_data[i + 1] >> 7) & 1); | ||||
|     } | ||||
|     raw_data[7] = (raw_data[7] << 1) | data; | ||||
| 
 | ||||
|     if(ioprox.can_be_decoded(raw_data, sizeof(raw_data))) { | ||||
|         ready = true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DecoderIoProx::reset_state() { | ||||
|     current_demodulated_value = false; | ||||
|     demodulated_value_invalid = false; | ||||
| 
 | ||||
|     current_period_duration = 0; | ||||
|     demodulation_sample_duration = 0; | ||||
| 
 | ||||
|     ready = false; | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <atomic> | ||||
| #include "protocols/protocol_ioprox.h" | ||||
| 
 | ||||
| class DecoderIoProx { | ||||
| public: | ||||
|     bool read(uint8_t* data, uint8_t data_size); | ||||
|     void process_front(bool polarity, uint32_t time); | ||||
|     DecoderIoProx(); | ||||
| 
 | ||||
| private: | ||||
|     uint32_t current_period_duration = 0; | ||||
|     uint32_t demodulation_sample_duration = 0; | ||||
| 
 | ||||
|     bool current_demodulated_value = false; | ||||
|     bool demodulated_value_invalid = false; | ||||
| 
 | ||||
|     uint8_t raw_data[8] = {0}; | ||||
|     void store_data(bool data); | ||||
| 
 | ||||
|     std::atomic<bool> ready; | ||||
| 
 | ||||
|     void reset_state(); | ||||
|     ProtocolIoProx ioprox; | ||||
| }; | ||||
| @ -1,15 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| #define EM_HEADER_POS 55 | ||||
| #define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS) | ||||
| 
 | ||||
| #define EM_FIRST_ROW_POS 50 | ||||
| #define EM_ROW_COUNT 10 | ||||
| 
 | ||||
| #define EM_COLUMN_POS 4 | ||||
| #define EM_STOP_POS 0 | ||||
| #define EM_STOP_MASK (0x1LLU << EM_STOP_POS) | ||||
| 
 | ||||
| #define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | EM_STOP_MASK) | ||||
| #define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK) | ||||
| @ -1,24 +0,0 @@ | ||||
| #include "encoder_emmarin.h" | ||||
| #include "protocols/protocol_emmarin.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| void EncoderEM::init(const uint8_t* data, const uint8_t data_size) { | ||||
|     ProtocolEMMarin em_marin; | ||||
|     em_marin.encode(data, data_size, reinterpret_cast<uint8_t*>(&card_data), sizeof(uint64_t)); | ||||
| 
 | ||||
|     card_data_index = 0; | ||||
| } | ||||
| 
 | ||||
| // data transmitted as manchester encoding
 | ||||
| // 0 - high2low
 | ||||
| // 1 - low2high
 | ||||
| void EncoderEM::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) { | ||||
|     *period = clocks_per_bit; | ||||
|     *pulse = clocks_per_bit / 2; | ||||
|     *polarity = (card_data >> (63 - card_data_index)) & 1; | ||||
| 
 | ||||
|     card_data_index++; | ||||
|     if(card_data_index >= 64) { | ||||
|         card_data_index = 0; | ||||
|     } | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| #pragma once | ||||
| #include "encoder_generic.h" | ||||
| 
 | ||||
| class EncoderEM : public EncoderGeneric { | ||||
| public: | ||||
|     /**
 | ||||
|      * @brief init data to emulate | ||||
|      *  | ||||
|      * @param data 1 byte FC, next 4 byte SN | ||||
|      * @param data_size must be 5 | ||||
|      */ | ||||
|     void init(const uint8_t* data, const uint8_t data_size) final; | ||||
| 
 | ||||
|     void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final; | ||||
| 
 | ||||
| private: | ||||
|     // clock pulses per bit
 | ||||
|     static const uint8_t clocks_per_bit = 64; | ||||
| 
 | ||||
|     uint64_t card_data; | ||||
|     uint8_t card_data_index; | ||||
| }; | ||||
| @ -1,27 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| class EncoderGeneric { | ||||
| public: | ||||
|     /**
 | ||||
|      * @brief init encoder | ||||
|      *  | ||||
|      * @param data data array | ||||
|      * @param data_size data array size | ||||
|      */ | ||||
|     virtual void init(const uint8_t* data, const uint8_t data_size) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * @brief Get the next timer pulse | ||||
|      *  | ||||
|      * @param polarity pulse polarity true = high2low, false = low2high | ||||
|      * @param period overall period time in timer clicks | ||||
|      * @param pulse pulse time in timer clicks | ||||
|      */ | ||||
|     virtual void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) = 0; | ||||
| 
 | ||||
|     virtual ~EncoderGeneric(){}; | ||||
| 
 | ||||
| private: | ||||
| }; | ||||
| @ -1,46 +0,0 @@ | ||||
| #include "encoder_hid_h10301.h" | ||||
| #include "protocols/protocol_hid_h10301.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| void EncoderHID_H10301::init(const uint8_t* data, const uint8_t data_size) { | ||||
|     ProtocolHID10301 hid; | ||||
|     hid.encode(data, data_size, reinterpret_cast<uint8_t*>(&card_data), sizeof(card_data) * 3); | ||||
| 
 | ||||
|     card_data_index = 0; | ||||
| } | ||||
| 
 | ||||
| void EncoderHID_H10301::write_bit(bool bit, uint8_t position) { | ||||
|     write_raw_bit(bit, position + 0); | ||||
|     write_raw_bit(!bit, position + 1); | ||||
| } | ||||
| 
 | ||||
| void EncoderHID_H10301::write_raw_bit(bool bit, uint8_t position) { | ||||
|     if(bit) { | ||||
|         card_data[position / 32] |= 1UL << (31 - (position % 32)); | ||||
|     } else { | ||||
|         card_data[position / 32] &= ~(1UL << (31 - (position % 32))); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void EncoderHID_H10301::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) { | ||||
|     uint8_t bit = (card_data[card_data_index / 32] >> (31 - (card_data_index % 32))) & 1; | ||||
| 
 | ||||
|     bool advance = fsk->next(bit, period); | ||||
|     if(advance) { | ||||
|         card_data_index++; | ||||
|         if(card_data_index >= (32 * card_data_max)) { | ||||
|             card_data_index = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     *polarity = true; | ||||
|     *pulse = *period / 2; | ||||
| } | ||||
| 
 | ||||
| EncoderHID_H10301::EncoderHID_H10301() { | ||||
|     fsk = new OscFSK(8, 10, 50); | ||||
| } | ||||
| 
 | ||||
| EncoderHID_H10301::~EncoderHID_H10301() { | ||||
|     delete fsk; | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| #pragma once | ||||
| #include "encoder_generic.h" | ||||
| #include "osc_fsk.h" | ||||
| 
 | ||||
| class EncoderHID_H10301 : public EncoderGeneric { | ||||
| public: | ||||
|     /**
 | ||||
|      * @brief init data to emulate | ||||
|      *  | ||||
|      * @param data 1 byte FC, next 2 byte SN | ||||
|      * @param data_size must be 3 | ||||
|      */ | ||||
|     void init(const uint8_t* data, const uint8_t data_size) final; | ||||
|     void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final; | ||||
|     EncoderHID_H10301(); | ||||
|     ~EncoderHID_H10301(); | ||||
| 
 | ||||
| private: | ||||
|     static const uint8_t card_data_max = 3; | ||||
|     uint32_t card_data[card_data_max]; | ||||
|     uint8_t card_data_index; | ||||
|     void write_bit(bool bit, uint8_t position); | ||||
|     void write_raw_bit(bool bit, uint8_t position); | ||||
| 
 | ||||
|     OscFSK* fsk; | ||||
| }; | ||||
| @ -1,36 +0,0 @@ | ||||
| #include "encoder_indala_40134.h" | ||||
| #include "protocols/protocol_indala_40134.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| void EncoderIndala_40134::init(const uint8_t* data, const uint8_t data_size) { | ||||
|     ProtocolIndala40134 indala; | ||||
|     indala.encode(data, data_size, reinterpret_cast<uint8_t*>(&card_data), sizeof(card_data)); | ||||
| 
 | ||||
|     last_bit = card_data & 1; | ||||
|     card_data_index = 0; | ||||
|     current_polarity = true; | ||||
| } | ||||
| 
 | ||||
| void EncoderIndala_40134::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) { | ||||
|     *period = 2; | ||||
|     *pulse = 1; | ||||
|     *polarity = current_polarity; | ||||
| 
 | ||||
|     bit_clock_index++; | ||||
|     if(bit_clock_index >= clock_per_bit) { | ||||
|         bit_clock_index = 0; | ||||
| 
 | ||||
|         bool current_bit = (card_data >> (63 - card_data_index)) & 1; | ||||
| 
 | ||||
|         if(current_bit != last_bit) { | ||||
|             current_polarity = !current_polarity; | ||||
|         } | ||||
| 
 | ||||
|         last_bit = current_bit; | ||||
| 
 | ||||
|         card_data_index++; | ||||
|         if(card_data_index >= 64) { | ||||
|             card_data_index = 0; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| #pragma once | ||||
| #include "encoder_generic.h" | ||||
| 
 | ||||
| class EncoderIndala_40134 : public EncoderGeneric { | ||||
| public: | ||||
|     /**
 | ||||
|      * @brief init data to emulate | ||||
|      *  | ||||
|      * @param data indala raw data | ||||
|      * @param data_size must be 5 | ||||
|      */ | ||||
|     void init(const uint8_t* data, const uint8_t data_size) final; | ||||
| 
 | ||||
|     void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final; | ||||
| 
 | ||||
| private: | ||||
|     uint64_t card_data; | ||||
|     uint8_t card_data_index; | ||||
|     uint8_t bit_clock_index; | ||||
|     bool last_bit; | ||||
|     bool current_polarity; | ||||
|     static const uint8_t clock_per_bit = 16; | ||||
| }; | ||||
| @ -1,32 +0,0 @@ | ||||
| #include "encoder_ioprox.h" | ||||
| #include "protocols/protocol_ioprox.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| void EncoderIoProx::init(const uint8_t* data, const uint8_t data_size) { | ||||
|     ProtocolIoProx ioprox; | ||||
|     ioprox.encode(data, data_size, card_data, sizeof(card_data)); | ||||
|     card_data_index = 0; | ||||
| } | ||||
| 
 | ||||
| void EncoderIoProx::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) { | ||||
|     uint8_t bit = (card_data[card_data_index / 8] >> (7 - (card_data_index % 8))) & 1; | ||||
| 
 | ||||
|     bool advance = fsk->next(bit, period); | ||||
|     if(advance) { | ||||
|         card_data_index++; | ||||
|         if(card_data_index >= (8 * card_data_max)) { | ||||
|             card_data_index = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     *polarity = true; | ||||
|     *pulse = *period / 2; | ||||
| } | ||||
| 
 | ||||
| EncoderIoProx::EncoderIoProx() { | ||||
|     fsk = new OscFSK(8, 10, 64); | ||||
| } | ||||
| 
 | ||||
| EncoderIoProx::~EncoderIoProx() { | ||||
|     delete fsk; | ||||
| } | ||||
| @ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
| #include "encoder_generic.h" | ||||
| #include "osc_fsk.h" | ||||
| 
 | ||||
| class EncoderIoProx : public EncoderGeneric { | ||||
| public: | ||||
|     /**
 | ||||
|      * @brief init data to emulate | ||||
|      *  | ||||
|      * @param data 1 byte FC, 1 byte Version, 2 bytes code | ||||
|      * @param data_size must be 4 | ||||
|      */ | ||||
|     void init(const uint8_t* data, const uint8_t data_size) final; | ||||
|     void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final; | ||||
|     EncoderIoProx(); | ||||
|     ~EncoderIoProx(); | ||||
| 
 | ||||
| private: | ||||
|     static const uint8_t card_data_max = 8; | ||||
| 
 | ||||
|     uint8_t card_data[card_data_max]; | ||||
|     uint8_t card_data_index; | ||||
| 
 | ||||
|     OscFSK* fsk; | ||||
| }; | ||||
| @ -1,76 +0,0 @@ | ||||
| #include "key_info.h" | ||||
| #include <string.h> | ||||
| 
 | ||||
| const char* lfrfid_key_get_type_string(LfrfidKeyType type) { | ||||
|     switch(type) { | ||||
|     case LfrfidKeyType::KeyEM4100: | ||||
|         return "EM4100"; | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyH10301: | ||||
|         return "H10301"; | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyI40134: | ||||
|         return "I40134"; | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyIoProxXSF: | ||||
|         return "IoProxXSF"; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return "Unknown"; | ||||
| } | ||||
| 
 | ||||
| const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type) { | ||||
|     switch(type) { | ||||
|     case LfrfidKeyType::KeyEM4100: | ||||
|         return "EM-Marin"; | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyH10301: | ||||
|         return "HID"; | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyI40134: | ||||
|         return "Indala"; | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyIoProxXSF: | ||||
|         return "Kantech"; | ||||
|     } | ||||
| 
 | ||||
|     return "Unknown"; | ||||
| } | ||||
| 
 | ||||
| bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type) { | ||||
|     bool result = true; | ||||
| 
 | ||||
|     if(strcmp("EM4100", string) == 0) { | ||||
|         *type = LfrfidKeyType::KeyEM4100; | ||||
|     } else if(strcmp("H10301", string) == 0) { | ||||
|         *type = LfrfidKeyType::KeyH10301; | ||||
|     } else if(strcmp("I40134", string) == 0) { | ||||
|         *type = LfrfidKeyType::KeyI40134; | ||||
|     } else if(strcmp("IoProxXSF", string) == 0) { | ||||
|         *type = LfrfidKeyType::KeyIoProxXSF; | ||||
|     } else { | ||||
|         result = false; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type) { | ||||
|     switch(type) { | ||||
|     case LfrfidKeyType::KeyEM4100: | ||||
|         return 5; | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyH10301: | ||||
|         return 3; | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyI40134: | ||||
|         return 3; | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyIoProxXSF: | ||||
|         return 4; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| @ -1,17 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| static const uint8_t LFRFID_KEY_SIZE = 8; | ||||
| static const uint8_t LFRFID_KEY_NAME_SIZE = 22; | ||||
| 
 | ||||
| enum class LfrfidKeyType : uint8_t { | ||||
|     KeyEM4100, | ||||
|     KeyH10301, | ||||
|     KeyI40134, | ||||
|     KeyIoProxXSF, | ||||
| }; | ||||
| 
 | ||||
| const char* lfrfid_key_get_type_string(LfrfidKeyType type); | ||||
| const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type); | ||||
| bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type); | ||||
| uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type); | ||||
| @ -1,20 +0,0 @@ | ||||
| #include "osc_fsk.h" | ||||
| 
 | ||||
| OscFSK::OscFSK(uint16_t _freq_low, uint16_t _freq_hi, uint16_t _osc_phase_max) | ||||
|     : freq{_freq_low, _freq_hi} | ||||
|     , osc_phase_max(_osc_phase_max) { | ||||
|     osc_phase_current = 0; | ||||
| } | ||||
| 
 | ||||
| bool OscFSK::next(bool bit, uint16_t* period) { | ||||
|     bool advance = false; | ||||
|     *period = freq[bit]; | ||||
|     osc_phase_current += *period; | ||||
| 
 | ||||
|     if(osc_phase_current > osc_phase_max) { | ||||
|         advance = true; | ||||
|         osc_phase_current -= osc_phase_max; | ||||
|     } | ||||
| 
 | ||||
|     return advance; | ||||
| } | ||||
| @ -1,30 +0,0 @@ | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| /**
 | ||||
|  * This code tries to fit the periods into a given number of cycles (phases) by taking cycles from the next cycle of periods. | ||||
|  */ | ||||
| class OscFSK { | ||||
| public: | ||||
|     /**
 | ||||
|      * Get next period | ||||
|      * @param bit bit value | ||||
|      * @param period return period | ||||
|      * @return bool whether to advance to the next bit | ||||
|      */ | ||||
|     bool next(bool bit, uint16_t* period); | ||||
| 
 | ||||
|     /**
 | ||||
|      * FSK ocillator constructor | ||||
|      *  | ||||
|      * @param freq_low bit 0 freq | ||||
|      * @param freq_hi bit 1 freq | ||||
|      * @param osc_phase_max max oscillator phase | ||||
|      */ | ||||
|     OscFSK(uint16_t freq_low, uint16_t freq_hi, uint16_t osc_phase_max); | ||||
| 
 | ||||
| private: | ||||
|     const uint16_t freq[2]; | ||||
|     const uint16_t osc_phase_max; | ||||
|     int32_t osc_phase_current; | ||||
| }; | ||||
| @ -1,150 +0,0 @@ | ||||
| #include "protocol_emmarin.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #define EM_HEADER_POS 55 | ||||
| #define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS) | ||||
| 
 | ||||
| #define EM_FIRST_ROW_POS 50 | ||||
| 
 | ||||
| #define EM_ROW_COUNT 10 | ||||
| #define EM_COLUMN_COUNT 4 | ||||
| #define EM_BITS_PER_ROW_COUNT (EM_COLUMN_COUNT + 1) | ||||
| 
 | ||||
| #define EM_COLUMN_POS 4 | ||||
| #define EM_STOP_POS 0 | ||||
| #define EM_STOP_MASK (0x1LLU << EM_STOP_POS) | ||||
| 
 | ||||
| #define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | EM_STOP_MASK) | ||||
| #define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK) | ||||
| 
 | ||||
| typedef uint64_t EMMarinCardData; | ||||
| 
 | ||||
| void write_nibble(bool low_nibble, uint8_t data, EMMarinCardData* card_data) { | ||||
|     uint8_t parity_sum = 0; | ||||
|     uint8_t start = 0; | ||||
|     if(!low_nibble) start = 4; | ||||
| 
 | ||||
|     for(int8_t i = (start + 3); i >= start; i--) { | ||||
|         parity_sum += (data >> i) & 1; | ||||
|         *card_data = (*card_data << 1) | ((data >> i) & 1); | ||||
|     } | ||||
| 
 | ||||
|     *card_data = (*card_data << 1) | ((parity_sum % 2) & 1); | ||||
| } | ||||
| 
 | ||||
| uint8_t ProtocolEMMarin::get_encoded_data_size() { | ||||
|     return sizeof(EMMarinCardData); | ||||
| } | ||||
| 
 | ||||
| uint8_t ProtocolEMMarin::get_decoded_data_size() { | ||||
|     return 5; | ||||
| } | ||||
| 
 | ||||
| void ProtocolEMMarin::encode( | ||||
|     const uint8_t* decoded_data, | ||||
|     const uint8_t decoded_data_size, | ||||
|     uint8_t* encoded_data, | ||||
|     const uint8_t encoded_data_size) { | ||||
|     furi_check(decoded_data_size >= get_decoded_data_size()); | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
| 
 | ||||
|     EMMarinCardData card_data; | ||||
| 
 | ||||
|     // header
 | ||||
|     card_data = 0b111111111; | ||||
| 
 | ||||
|     // data
 | ||||
|     for(uint8_t i = 0; i < get_decoded_data_size(); i++) { | ||||
|         write_nibble(false, decoded_data[i], &card_data); | ||||
|         write_nibble(true, decoded_data[i], &card_data); | ||||
|     } | ||||
| 
 | ||||
|     // column parity and stop bit
 | ||||
|     uint8_t parity_sum; | ||||
| 
 | ||||
|     for(uint8_t c = 0; c < EM_COLUMN_COUNT; c++) { | ||||
|         parity_sum = 0; | ||||
|         for(uint8_t i = 1; i <= EM_ROW_COUNT; i++) { | ||||
|             uint8_t parity_bit = (card_data >> (i * EM_BITS_PER_ROW_COUNT - 1)) & 1; | ||||
|             parity_sum += parity_bit; | ||||
|         } | ||||
|         card_data = (card_data << 1) | ((parity_sum % 2) & 1); | ||||
|     } | ||||
| 
 | ||||
|     // stop bit
 | ||||
|     card_data = (card_data << 1) | 0; | ||||
| 
 | ||||
|     memcpy(encoded_data, &card_data, get_encoded_data_size()); | ||||
| } | ||||
| 
 | ||||
| void ProtocolEMMarin::decode( | ||||
|     const uint8_t* encoded_data, | ||||
|     const uint8_t encoded_data_size, | ||||
|     uint8_t* decoded_data, | ||||
|     const uint8_t decoded_data_size) { | ||||
|     furi_check(decoded_data_size >= get_decoded_data_size()); | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
| 
 | ||||
|     uint8_t decoded_data_index = 0; | ||||
|     EMMarinCardData card_data = *(reinterpret_cast<const EMMarinCardData*>(encoded_data)); | ||||
| 
 | ||||
|     // clean result
 | ||||
|     memset(decoded_data, 0, decoded_data_size); | ||||
| 
 | ||||
|     // header
 | ||||
|     for(uint8_t i = 0; i < 9; i++) { | ||||
|         card_data = card_data << 1; | ||||
|     } | ||||
| 
 | ||||
|     // nibbles
 | ||||
|     uint8_t value = 0; | ||||
|     for(uint8_t r = 0; r < EM_ROW_COUNT; r++) { | ||||
|         uint8_t nibble = 0; | ||||
|         for(uint8_t i = 0; i < 5; i++) { | ||||
|             if(i < 4) nibble = (nibble << 1) | (card_data & (1LLU << 63) ? 1 : 0); | ||||
|             card_data = card_data << 1; | ||||
|         } | ||||
|         value = (value << 4) | nibble; | ||||
|         if(r % 2) { | ||||
|             decoded_data[decoded_data_index] |= value; | ||||
|             decoded_data_index++; | ||||
|             value = 0; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool ProtocolEMMarin::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
|     const EMMarinCardData* card_data = reinterpret_cast<const EMMarinCardData*>(encoded_data); | ||||
| 
 | ||||
|     // check header and stop bit
 | ||||
|     if((*card_data & EM_HEADER_AND_STOP_MASK) != EM_HEADER_AND_STOP_DATA) return false; | ||||
| 
 | ||||
|     // check row parity
 | ||||
|     for(uint8_t i = 0; i < EM_ROW_COUNT; i++) { | ||||
|         uint8_t parity_sum = 0; | ||||
| 
 | ||||
|         for(uint8_t j = 0; j < EM_BITS_PER_ROW_COUNT; j++) { | ||||
|             parity_sum += (*card_data >> (EM_FIRST_ROW_POS - i * EM_BITS_PER_ROW_COUNT + j)) & 1; | ||||
|         } | ||||
| 
 | ||||
|         if((parity_sum % 2)) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // check columns parity
 | ||||
|     for(uint8_t i = 0; i < EM_COLUMN_COUNT; i++) { | ||||
|         uint8_t parity_sum = 0; | ||||
| 
 | ||||
|         for(uint8_t j = 0; j < EM_ROW_COUNT + 1; j++) { | ||||
|             parity_sum += (*card_data >> (EM_COLUMN_POS - i + j * EM_BITS_PER_ROW_COUNT)) & 1; | ||||
|         } | ||||
| 
 | ||||
|         if((parity_sum % 2)) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| #pragma once | ||||
| #include "protocol_generic.h" | ||||
| 
 | ||||
| class ProtocolEMMarin : public ProtocolGeneric { | ||||
| public: | ||||
|     uint8_t get_encoded_data_size() final; | ||||
|     uint8_t get_decoded_data_size() final; | ||||
| 
 | ||||
|     void encode( | ||||
|         const uint8_t* decoded_data, | ||||
|         const uint8_t decoded_data_size, | ||||
|         uint8_t* encoded_data, | ||||
|         const uint8_t encoded_data_size) final; | ||||
| 
 | ||||
|     void decode( | ||||
|         const uint8_t* encoded_data, | ||||
|         const uint8_t encoded_data_size, | ||||
|         uint8_t* decoded_data, | ||||
|         const uint8_t decoded_data_size) final; | ||||
| 
 | ||||
|     bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final; | ||||
| }; | ||||
| @ -1,60 +0,0 @@ | ||||
| #pragma once | ||||
| #include "stdint.h" | ||||
| #include "stdbool.h" | ||||
| 
 | ||||
| class ProtocolGeneric { | ||||
| public: | ||||
|     /**
 | ||||
|      * @brief Get the encoded data size | ||||
|      *  | ||||
|      * @return uint8_t size of encoded data in bytes | ||||
|      */ | ||||
|     virtual uint8_t get_encoded_data_size() = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * @brief Get the decoded data size | ||||
|      *  | ||||
|      * @return uint8_t size of decoded data in bytes | ||||
|      */ | ||||
|     virtual uint8_t get_decoded_data_size() = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * @brief encode decoded data | ||||
|      *  | ||||
|      * @param decoded_data  | ||||
|      * @param decoded_data_size  | ||||
|      * @param encoded_data  | ||||
|      * @param encoded_data_size  | ||||
|      */ | ||||
|     virtual void encode( | ||||
|         const uint8_t* decoded_data, | ||||
|         const uint8_t decoded_data_size, | ||||
|         uint8_t* encoded_data, | ||||
|         const uint8_t encoded_data_size) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * @brief decode encoded data | ||||
|      *  | ||||
|      * @param encoded_data  | ||||
|      * @param encoded_data_size  | ||||
|      * @param decoded_data  | ||||
|      * @param decoded_data_size  | ||||
|      */ | ||||
|     virtual void decode( | ||||
|         const uint8_t* encoded_data, | ||||
|         const uint8_t encoded_data_size, | ||||
|         uint8_t* decoded_data, | ||||
|         const uint8_t decoded_data_size) = 0; | ||||
| 
 | ||||
|     /**
 | ||||
|      * @brief fast check that data can be correctly decoded | ||||
|      *  | ||||
|      * @param encoded_data  | ||||
|      * @param encoded_data_size  | ||||
|      * @return true - can be correctly decoded | ||||
|      * @return false - cannot be correctly decoded | ||||
|      */ | ||||
|     virtual bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) = 0; | ||||
| 
 | ||||
|     virtual ~ProtocolGeneric(){}; | ||||
| }; | ||||
| @ -1,238 +0,0 @@ | ||||
| #include "protocol_hid_h10301.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| typedef uint32_t HID10301CardData; | ||||
| constexpr uint8_t HID10301Count = 3; | ||||
| constexpr uint8_t HID10301BitSize = sizeof(HID10301CardData) * 8; | ||||
| 
 | ||||
| static void write_raw_bit(bool bit, uint8_t position, HID10301CardData* card_data) { | ||||
|     if(bit) { | ||||
|         card_data[position / HID10301BitSize] |= | ||||
|             1UL << (HID10301BitSize - (position % HID10301BitSize) - 1); | ||||
|     } else { | ||||
|         card_data[position / (sizeof(HID10301CardData) * 8)] &= | ||||
|             ~(1UL << (HID10301BitSize - (position % HID10301BitSize) - 1)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void write_bit(bool bit, uint8_t position, HID10301CardData* card_data) { | ||||
|     write_raw_bit(bit, position + 0, card_data); | ||||
|     write_raw_bit(!bit, position + 1, card_data); | ||||
| } | ||||
| 
 | ||||
| uint8_t ProtocolHID10301::get_encoded_data_size() { | ||||
|     return sizeof(HID10301CardData) * HID10301Count; | ||||
| } | ||||
| 
 | ||||
| uint8_t ProtocolHID10301::get_decoded_data_size() { | ||||
|     return 3; | ||||
| } | ||||
| 
 | ||||
| void ProtocolHID10301::encode( | ||||
|     const uint8_t* decoded_data, | ||||
|     const uint8_t decoded_data_size, | ||||
|     uint8_t* encoded_data, | ||||
|     const uint8_t encoded_data_size) { | ||||
|     furi_check(decoded_data_size >= get_decoded_data_size()); | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
| 
 | ||||
|     HID10301CardData card_data[HID10301Count] = {0, 0, 0}; | ||||
| 
 | ||||
|     uint32_t fc_cn = (decoded_data[0] << 16) | (decoded_data[1] << 8) | decoded_data[2]; | ||||
| 
 | ||||
|     // even parity sum calculation (high 12 bits of data)
 | ||||
|     uint8_t even_parity_sum = 0; | ||||
|     for(int8_t i = 12; i < 24; i++) { | ||||
|         if(((fc_cn >> i) & 1) == 1) { | ||||
|             even_parity_sum++; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // odd parity sum calculation (low 12 bits of data)
 | ||||
|     uint8_t odd_parity_sum = 1; | ||||
|     for(int8_t i = 0; i < 12; i++) { | ||||
|         if(((fc_cn >> i) & 1) == 1) { | ||||
|             odd_parity_sum++; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // 0x1D preamble
 | ||||
|     write_raw_bit(0, 0, card_data); | ||||
|     write_raw_bit(0, 1, card_data); | ||||
|     write_raw_bit(0, 2, card_data); | ||||
|     write_raw_bit(1, 3, card_data); | ||||
|     write_raw_bit(1, 4, card_data); | ||||
|     write_raw_bit(1, 5, card_data); | ||||
|     write_raw_bit(0, 6, card_data); | ||||
|     write_raw_bit(1, 7, card_data); | ||||
| 
 | ||||
|     // company / OEM code 1
 | ||||
|     write_bit(0, 8, card_data); | ||||
|     write_bit(0, 10, card_data); | ||||
|     write_bit(0, 12, card_data); | ||||
|     write_bit(0, 14, card_data); | ||||
|     write_bit(0, 16, card_data); | ||||
|     write_bit(0, 18, card_data); | ||||
|     write_bit(1, 20, card_data); | ||||
| 
 | ||||
|     // card format / length 1
 | ||||
|     write_bit(0, 22, card_data); | ||||
|     write_bit(0, 24, card_data); | ||||
|     write_bit(0, 26, card_data); | ||||
|     write_bit(0, 28, card_data); | ||||
|     write_bit(0, 30, card_data); | ||||
|     write_bit(0, 32, card_data); | ||||
|     write_bit(0, 34, card_data); | ||||
|     write_bit(0, 36, card_data); | ||||
|     write_bit(0, 38, card_data); | ||||
|     write_bit(0, 40, card_data); | ||||
|     write_bit(1, 42, card_data); | ||||
| 
 | ||||
|     // even parity bit
 | ||||
|     write_bit((even_parity_sum % 2), 44, card_data); | ||||
| 
 | ||||
|     // data
 | ||||
|     for(uint8_t i = 0; i < 24; i++) { | ||||
|         write_bit((fc_cn >> (23 - i)) & 1, 46 + (i * 2), card_data); | ||||
|     } | ||||
| 
 | ||||
|     // odd parity bit
 | ||||
|     write_bit((odd_parity_sum % 2), 94, card_data); | ||||
| 
 | ||||
|     memcpy(encoded_data, &card_data, get_encoded_data_size()); | ||||
| } | ||||
| 
 | ||||
| void ProtocolHID10301::decode( | ||||
|     const uint8_t* encoded_data, | ||||
|     const uint8_t encoded_data_size, | ||||
|     uint8_t* decoded_data, | ||||
|     const uint8_t decoded_data_size) { | ||||
|     furi_check(decoded_data_size >= get_decoded_data_size()); | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
| 
 | ||||
|     const HID10301CardData* card_data = reinterpret_cast<const HID10301CardData*>(encoded_data); | ||||
| 
 | ||||
|     // data decoding
 | ||||
|     uint32_t result = 0; | ||||
| 
 | ||||
|     // decode from word 1
 | ||||
|     // coded with 01 = 0, 10 = 1 transitions
 | ||||
|     for(int8_t i = 9; i >= 0; i--) { | ||||
|         switch((*(card_data + 1) >> (2 * i)) & 0b11) { | ||||
|         case 0b01: | ||||
|             result = (result << 1) | 0; | ||||
|             break; | ||||
|         case 0b10: | ||||
|             result = (result << 1) | 1; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // decode from word 2
 | ||||
|     // coded with 01 = 0, 10 = 1 transitions
 | ||||
|     for(int8_t i = 15; i >= 0; i--) { | ||||
|         switch((*(card_data + 2) >> (2 * i)) & 0b11) { | ||||
|         case 0b01: | ||||
|             result = (result << 1) | 0; | ||||
|             break; | ||||
|         case 0b10: | ||||
|             result = (result << 1) | 1; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     uint8_t data[3] = {(uint8_t)(result >> 17), (uint8_t)(result >> 9), (uint8_t)(result >> 1)}; | ||||
| 
 | ||||
|     memcpy(decoded_data, &data, get_decoded_data_size()); | ||||
| } | ||||
| 
 | ||||
| bool ProtocolHID10301::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
| 
 | ||||
|     const HID10301CardData* card_data = reinterpret_cast<const HID10301CardData*>(encoded_data); | ||||
| 
 | ||||
|     // packet preamble
 | ||||
|     // raw data
 | ||||
|     if(*(encoded_data + 3) != 0x1D) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // encoded company/oem
 | ||||
|     // coded with 01 = 0, 10 = 1 transitions
 | ||||
|     // stored in word 0
 | ||||
|     if((*card_data >> 10 & 0x3FFF) != 0x1556) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // encoded format/length
 | ||||
|     // coded with 01 = 0, 10 = 1 transitions
 | ||||
|     // stored in word 0 and word 1
 | ||||
|     if((((*card_data & 0x3FF) << 12) | ((*(card_data + 1) >> 20) & 0xFFF)) != 0x155556) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // data decoding
 | ||||
|     uint32_t result = 0; | ||||
| 
 | ||||
|     // decode from word 1
 | ||||
|     // coded with 01 = 0, 10 = 1 transitions
 | ||||
|     for(int8_t i = 9; i >= 0; i--) { | ||||
|         switch((*(card_data + 1) >> (2 * i)) & 0b11) { | ||||
|         case 0b01: | ||||
|             result = (result << 1) | 0; | ||||
|             break; | ||||
|         case 0b10: | ||||
|             result = (result << 1) | 1; | ||||
|             break; | ||||
|         default: | ||||
|             return false; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // decode from word 2
 | ||||
|     // coded with 01 = 0, 10 = 1 transitions
 | ||||
|     for(int8_t i = 15; i >= 0; i--) { | ||||
|         switch((*(card_data + 2) >> (2 * i)) & 0b11) { | ||||
|         case 0b01: | ||||
|             result = (result << 1) | 0; | ||||
|             break; | ||||
|         case 0b10: | ||||
|             result = (result << 1) | 1; | ||||
|             break; | ||||
|         default: | ||||
|             return false; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // trailing parity (odd) test
 | ||||
|     uint8_t parity_sum = 0; | ||||
|     for(int8_t i = 0; i < 13; i++) { | ||||
|         if(((result >> i) & 1) == 1) { | ||||
|             parity_sum++; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if((parity_sum % 2) != 1) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // leading parity (even) test
 | ||||
|     parity_sum = 0; | ||||
|     for(int8_t i = 13; i < 26; i++) { | ||||
|         if(((result >> i) & 1) == 1) { | ||||
|             parity_sum++; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if((parity_sum % 2) == 1) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| #pragma once | ||||
| #include "protocol_generic.h" | ||||
| 
 | ||||
| class ProtocolHID10301 : public ProtocolGeneric { | ||||
| public: | ||||
|     uint8_t get_encoded_data_size() final; | ||||
|     uint8_t get_decoded_data_size() final; | ||||
| 
 | ||||
|     void encode( | ||||
|         const uint8_t* decoded_data, | ||||
|         const uint8_t decoded_data_size, | ||||
|         uint8_t* encoded_data, | ||||
|         const uint8_t encoded_data_size) final; | ||||
| 
 | ||||
|     void decode( | ||||
|         const uint8_t* encoded_data, | ||||
|         const uint8_t encoded_data_size, | ||||
|         uint8_t* decoded_data, | ||||
|         const uint8_t decoded_data_size) final; | ||||
| 
 | ||||
|     bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final; | ||||
| }; | ||||
| @ -1,237 +0,0 @@ | ||||
| #include "protocol_indala_40134.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| typedef uint64_t Indala40134CardData; | ||||
| 
 | ||||
| static void set_bit(bool bit, uint8_t position, Indala40134CardData* card_data) { | ||||
|     position = (sizeof(Indala40134CardData) * 8) - 1 - position; | ||||
|     if(bit) { | ||||
|         *card_data |= 1ull << position; | ||||
|     } else { | ||||
|         *card_data &= ~(1ull << position); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool get_bit(uint8_t position, const Indala40134CardData* card_data) { | ||||
|     position = (sizeof(Indala40134CardData) * 8) - 1 - position; | ||||
|     return (*card_data >> position) & 1; | ||||
| } | ||||
| 
 | ||||
| uint8_t ProtocolIndala40134::get_encoded_data_size() { | ||||
|     return sizeof(Indala40134CardData); | ||||
| } | ||||
| 
 | ||||
| uint8_t ProtocolIndala40134::get_decoded_data_size() { | ||||
|     return 3; | ||||
| } | ||||
| 
 | ||||
| void ProtocolIndala40134::encode( | ||||
|     const uint8_t* decoded_data, | ||||
|     const uint8_t decoded_data_size, | ||||
|     uint8_t* encoded_data, | ||||
|     const uint8_t encoded_data_size) { | ||||
|     furi_check(decoded_data_size >= get_decoded_data_size()); | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
| 
 | ||||
|     uint32_t fc_and_card = (decoded_data[0] << 16) | (decoded_data[1] << 8) | decoded_data[2]; | ||||
|     Indala40134CardData card_data = 0; | ||||
| 
 | ||||
|     // preamble
 | ||||
|     set_bit(1, 0, &card_data); | ||||
|     set_bit(1, 2, &card_data); | ||||
|     set_bit(1, 32, &card_data); | ||||
| 
 | ||||
|     // factory code
 | ||||
|     set_bit(((fc_and_card >> 23) & 1), 57, &card_data); | ||||
|     set_bit(((fc_and_card >> 22) & 1), 49, &card_data); | ||||
|     set_bit(((fc_and_card >> 21) & 1), 44, &card_data); | ||||
|     set_bit(((fc_and_card >> 20) & 1), 47, &card_data); | ||||
|     set_bit(((fc_and_card >> 19) & 1), 48, &card_data); | ||||
|     set_bit(((fc_and_card >> 18) & 1), 53, &card_data); | ||||
|     set_bit(((fc_and_card >> 17) & 1), 39, &card_data); | ||||
|     set_bit(((fc_and_card >> 16) & 1), 58, &card_data); | ||||
| 
 | ||||
|     // card number
 | ||||
|     set_bit(((fc_and_card >> 15) & 1), 42, &card_data); | ||||
|     set_bit(((fc_and_card >> 14) & 1), 45, &card_data); | ||||
|     set_bit(((fc_and_card >> 13) & 1), 43, &card_data); | ||||
|     set_bit(((fc_and_card >> 12) & 1), 40, &card_data); | ||||
|     set_bit(((fc_and_card >> 11) & 1), 52, &card_data); | ||||
|     set_bit(((fc_and_card >> 10) & 1), 36, &card_data); | ||||
|     set_bit(((fc_and_card >> 9) & 1), 35, &card_data); | ||||
|     set_bit(((fc_and_card >> 8) & 1), 51, &card_data); | ||||
|     set_bit(((fc_and_card >> 7) & 1), 46, &card_data); | ||||
|     set_bit(((fc_and_card >> 6) & 1), 33, &card_data); | ||||
|     set_bit(((fc_and_card >> 5) & 1), 37, &card_data); | ||||
|     set_bit(((fc_and_card >> 4) & 1), 54, &card_data); | ||||
|     set_bit(((fc_and_card >> 3) & 1), 56, &card_data); | ||||
|     set_bit(((fc_and_card >> 2) & 1), 59, &card_data); | ||||
|     set_bit(((fc_and_card >> 1) & 1), 50, &card_data); | ||||
|     set_bit(((fc_and_card >> 0) & 1), 41, &card_data); | ||||
| 
 | ||||
|     // checksum
 | ||||
|     uint8_t checksum = 0; | ||||
|     checksum += ((fc_and_card >> 14) & 1); | ||||
|     checksum += ((fc_and_card >> 12) & 1); | ||||
|     checksum += ((fc_and_card >> 9) & 1); | ||||
|     checksum += ((fc_and_card >> 8) & 1); | ||||
|     checksum += ((fc_and_card >> 6) & 1); | ||||
|     checksum += ((fc_and_card >> 5) & 1); | ||||
|     checksum += ((fc_and_card >> 2) & 1); | ||||
|     checksum += ((fc_and_card >> 0) & 1); | ||||
| 
 | ||||
|     // wiegand parity bits
 | ||||
|     // even parity sum calculation (high 12 bits of data)
 | ||||
|     uint8_t even_parity_sum = 0; | ||||
|     for(int8_t i = 12; i < 24; i++) { | ||||
|         if(((fc_and_card >> i) & 1) == 1) { | ||||
|             even_parity_sum++; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // odd parity sum calculation (low 12 bits of data)
 | ||||
|     uint8_t odd_parity_sum = 1; | ||||
|     for(int8_t i = 0; i < 12; i++) { | ||||
|         if(((fc_and_card >> i) & 1) == 1) { | ||||
|             odd_parity_sum++; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // even parity bit
 | ||||
|     set_bit((even_parity_sum % 2), 34, &card_data); | ||||
| 
 | ||||
|     // odd parity bit
 | ||||
|     set_bit((odd_parity_sum % 2), 38, &card_data); | ||||
| 
 | ||||
|     // checksum
 | ||||
|     if((checksum & 1) == 1) { | ||||
|         set_bit(0, 62, &card_data); | ||||
|         set_bit(1, 63, &card_data); | ||||
|     } else { | ||||
|         set_bit(1, 62, &card_data); | ||||
|         set_bit(0, 63, &card_data); | ||||
|     } | ||||
| 
 | ||||
|     memcpy(encoded_data, &card_data, get_encoded_data_size()); | ||||
| } | ||||
| 
 | ||||
| // factory code
 | ||||
| static uint8_t get_fc(const Indala40134CardData* card_data) { | ||||
|     uint8_t fc = 0; | ||||
| 
 | ||||
|     fc = fc << 1 | get_bit(57, card_data); | ||||
|     fc = fc << 1 | get_bit(49, card_data); | ||||
|     fc = fc << 1 | get_bit(44, card_data); | ||||
|     fc = fc << 1 | get_bit(47, card_data); | ||||
|     fc = fc << 1 | get_bit(48, card_data); | ||||
|     fc = fc << 1 | get_bit(53, card_data); | ||||
|     fc = fc << 1 | get_bit(39, card_data); | ||||
|     fc = fc << 1 | get_bit(58, card_data); | ||||
| 
 | ||||
|     return fc; | ||||
| } | ||||
| 
 | ||||
| // card number
 | ||||
| static uint16_t get_cn(const Indala40134CardData* card_data) { | ||||
|     uint16_t cn = 0; | ||||
| 
 | ||||
|     cn = cn << 1 | get_bit(42, card_data); | ||||
|     cn = cn << 1 | get_bit(45, card_data); | ||||
|     cn = cn << 1 | get_bit(43, card_data); | ||||
|     cn = cn << 1 | get_bit(40, card_data); | ||||
|     cn = cn << 1 | get_bit(52, card_data); | ||||
|     cn = cn << 1 | get_bit(36, card_data); | ||||
|     cn = cn << 1 | get_bit(35, card_data); | ||||
|     cn = cn << 1 | get_bit(51, card_data); | ||||
|     cn = cn << 1 | get_bit(46, card_data); | ||||
|     cn = cn << 1 | get_bit(33, card_data); | ||||
|     cn = cn << 1 | get_bit(37, card_data); | ||||
|     cn = cn << 1 | get_bit(54, card_data); | ||||
|     cn = cn << 1 | get_bit(56, card_data); | ||||
|     cn = cn << 1 | get_bit(59, card_data); | ||||
|     cn = cn << 1 | get_bit(50, card_data); | ||||
|     cn = cn << 1 | get_bit(41, card_data); | ||||
| 
 | ||||
|     return cn; | ||||
| } | ||||
| 
 | ||||
| void ProtocolIndala40134::decode( | ||||
|     const uint8_t* encoded_data, | ||||
|     const uint8_t encoded_data_size, | ||||
|     uint8_t* decoded_data, | ||||
|     const uint8_t decoded_data_size) { | ||||
|     furi_check(decoded_data_size >= get_decoded_data_size()); | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
| 
 | ||||
|     const Indala40134CardData* card_data = | ||||
|         reinterpret_cast<const Indala40134CardData*>(encoded_data); | ||||
| 
 | ||||
|     uint8_t fc = get_fc(card_data); | ||||
|     uint16_t card = get_cn(card_data); | ||||
| 
 | ||||
|     decoded_data[0] = fc; | ||||
|     decoded_data[1] = card >> 8; | ||||
|     decoded_data[2] = card; | ||||
| } | ||||
| 
 | ||||
| bool ProtocolIndala40134::can_be_decoded( | ||||
|     const uint8_t* encoded_data, | ||||
|     const uint8_t encoded_data_size) { | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
|     bool can_be_decoded = false; | ||||
| 
 | ||||
|     const Indala40134CardData* card_data = | ||||
|         reinterpret_cast<const Indala40134CardData*>(encoded_data); | ||||
| 
 | ||||
|     do { | ||||
|         // preambula
 | ||||
|         if((*card_data >> 32) != 0xa0000000UL) break; | ||||
| 
 | ||||
|         // data
 | ||||
|         const uint32_t fc_and_card = get_fc(card_data) << 16 | get_cn(card_data); | ||||
| 
 | ||||
|         // checksum
 | ||||
|         const uint8_t checksum = get_bit(62, card_data) << 1 | get_bit(63, card_data); | ||||
|         uint8_t checksum_sum = 0; | ||||
|         checksum_sum += ((fc_and_card >> 14) & 1); | ||||
|         checksum_sum += ((fc_and_card >> 12) & 1); | ||||
|         checksum_sum += ((fc_and_card >> 9) & 1); | ||||
|         checksum_sum += ((fc_and_card >> 8) & 1); | ||||
|         checksum_sum += ((fc_and_card >> 6) & 1); | ||||
|         checksum_sum += ((fc_and_card >> 5) & 1); | ||||
|         checksum_sum += ((fc_and_card >> 2) & 1); | ||||
|         checksum_sum += ((fc_and_card >> 0) & 1); | ||||
|         checksum_sum = checksum_sum & 0b1; | ||||
| 
 | ||||
|         if(checksum_sum == 1 && checksum == 0b01) { | ||||
|         } else if(checksum_sum == 0 && checksum == 0b10) { | ||||
|         } else { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         // wiegand parity bits
 | ||||
|         // even parity sum calculation (high 12 bits of data)
 | ||||
|         const bool even_parity = get_bit(34, card_data); | ||||
|         uint8_t even_parity_sum = 0; | ||||
|         for(int8_t i = 12; i < 24; i++) { | ||||
|             if(((fc_and_card >> i) & 1) == 1) { | ||||
|                 even_parity_sum++; | ||||
|             } | ||||
|         } | ||||
|         if(even_parity_sum % 2 != even_parity) break; | ||||
| 
 | ||||
|         // odd parity sum calculation (low 12 bits of data)
 | ||||
|         const bool odd_parity = get_bit(38, card_data); | ||||
|         uint8_t odd_parity_sum = 1; | ||||
|         for(int8_t i = 0; i < 12; i++) { | ||||
|             if(((fc_and_card >> i) & 1) == 1) { | ||||
|                 odd_parity_sum++; | ||||
|             } | ||||
|         } | ||||
|         if(odd_parity_sum % 2 != odd_parity) break; | ||||
| 
 | ||||
|         can_be_decoded = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return can_be_decoded; | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| #pragma once | ||||
| #include "protocol_generic.h" | ||||
| 
 | ||||
| class ProtocolIndala40134 : public ProtocolGeneric { | ||||
| public: | ||||
|     uint8_t get_encoded_data_size() final; | ||||
|     uint8_t get_decoded_data_size() final; | ||||
| 
 | ||||
|     void encode( | ||||
|         const uint8_t* decoded_data, | ||||
|         const uint8_t decoded_data_size, | ||||
|         uint8_t* encoded_data, | ||||
|         const uint8_t encoded_data_size) final; | ||||
| 
 | ||||
|     void decode( | ||||
|         const uint8_t* encoded_data, | ||||
|         const uint8_t encoded_data_size, | ||||
|         uint8_t* decoded_data, | ||||
|         const uint8_t decoded_data_size) final; | ||||
| 
 | ||||
|     bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final; | ||||
| }; | ||||
| @ -1,193 +0,0 @@ | ||||
| #include "protocol_ioprox.h" | ||||
| #include <furi.h> | ||||
| #include <cli/cli.h> | ||||
| 
 | ||||
| /**
 | ||||
|  * Writes a bit into the output buffer. | ||||
|  */ | ||||
| static void write_bit(bool bit, uint8_t position, uint8_t* data) { | ||||
|     if(bit) { | ||||
|         data[position / 8] |= 1UL << (7 - (position % 8)); | ||||
|     } else { | ||||
|         data[position / 8] &= ~(1UL << (7 - (position % 8))); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Writes up to eight contiguous bits into the output buffer. | ||||
|  */ | ||||
| static void write_bits(uint8_t byte, uint8_t position, uint8_t* data, uint8_t length) { | ||||
|     furi_check(length <= 8); | ||||
|     furi_check(length > 0); | ||||
| 
 | ||||
|     for(uint8_t i = 0; i < length; ++i) { | ||||
|         uint8_t shift = 7 - i; | ||||
|         write_bit((byte >> shift) & 1, position + i, data); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| uint8_t ProtocolIoProx::get_encoded_data_size() { | ||||
|     return 8; | ||||
| } | ||||
| 
 | ||||
| uint8_t ProtocolIoProx::get_decoded_data_size() { | ||||
|     return 4; | ||||
| } | ||||
| 
 | ||||
| void ProtocolIoProx::encode( | ||||
|     const uint8_t* decoded_data, | ||||
|     const uint8_t decoded_data_size, | ||||
|     uint8_t* encoded_data, | ||||
|     const uint8_t encoded_data_size) { | ||||
|     furi_check(decoded_data_size >= get_decoded_data_size()); | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
| 
 | ||||
|     // Packet to transmit:
 | ||||
|     //
 | ||||
|     // 0           10          20          30          40          50          60
 | ||||
|     // v           v           v           v           v           v           v
 | ||||
|     // 01234567 8 90123456 7 89012345 6 78901234 5 67890123 4 56789012 3 45678901 23
 | ||||
|     // -----------------------------------------------------------------------------
 | ||||
|     // 00000000 0 11110000 1 facility 1 version_ 1 code-one 1 code-two 1 checksum 11
 | ||||
| 
 | ||||
|     // Preamble.
 | ||||
|     write_bits(0b00000000, 0, encoded_data, 8); | ||||
|     write_bit(0, 8, encoded_data); | ||||
| 
 | ||||
|     write_bits(0b11110000, 9, encoded_data, 8); | ||||
|     write_bit(1, 17, encoded_data); | ||||
| 
 | ||||
|     // Facility code.
 | ||||
|     write_bits(decoded_data[0], 18, encoded_data, 8); | ||||
|     write_bit(1, 26, encoded_data); | ||||
| 
 | ||||
|     // Version
 | ||||
|     write_bits(decoded_data[1], 27, encoded_data, 8); | ||||
|     write_bit(1, 35, encoded_data); | ||||
| 
 | ||||
|     // Code one
 | ||||
|     write_bits(decoded_data[2], 36, encoded_data, 8); | ||||
|     write_bit(1, 44, encoded_data); | ||||
| 
 | ||||
|     // Code two
 | ||||
|     write_bits(decoded_data[3], 45, encoded_data, 8); | ||||
|     write_bit(1, 53, encoded_data); | ||||
| 
 | ||||
|     // Checksum
 | ||||
|     write_bits(compute_checksum(encoded_data, 8), 54, encoded_data, 8); | ||||
|     write_bit(1, 62, encoded_data); | ||||
|     write_bit(1, 63, encoded_data); | ||||
| } | ||||
| 
 | ||||
| void ProtocolIoProx::decode( | ||||
|     const uint8_t* encoded_data, | ||||
|     const uint8_t encoded_data_size, | ||||
|     uint8_t* decoded_data, | ||||
|     const uint8_t decoded_data_size) { | ||||
|     furi_check(decoded_data_size >= get_decoded_data_size()); | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
| 
 | ||||
|     // Packet structure:
 | ||||
|     // (Note: the second word seems fixed; but this may not be a guarantee;
 | ||||
|     //  it currently has no meaning.)
 | ||||
|     //
 | ||||
|     //0        1        2        3        4        5        6        7
 | ||||
|     //v        v        v        v        v        v        v        v
 | ||||
|     //01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF
 | ||||
|     //-----------------------------------------------------------------------
 | ||||
|     //00000000 01111000 01FFFFFF FF1VVVVV VVV1CCCC CCCC1CCC CCCCC1XX XXXXXX11
 | ||||
|     //
 | ||||
|     // F = facility code
 | ||||
|     // V = version
 | ||||
|     // C = code
 | ||||
|     // X = checksum
 | ||||
| 
 | ||||
|     // Facility code
 | ||||
|     decoded_data[0] = (encoded_data[2] << 2) | (encoded_data[3] >> 6); | ||||
| 
 | ||||
|     // Version code.
 | ||||
|     decoded_data[1] = (encoded_data[3] << 3) | (encoded_data[4] >> 5); | ||||
| 
 | ||||
|     // Code bytes.
 | ||||
|     decoded_data[2] = (encoded_data[4] << 4) | (encoded_data[5] >> 4); | ||||
|     decoded_data[3] = (encoded_data[5] << 5) | (encoded_data[6] >> 3); | ||||
| } | ||||
| 
 | ||||
| bool ProtocolIoProx::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { | ||||
|     furi_check(encoded_data_size >= get_encoded_data_size()); | ||||
| 
 | ||||
|     // Packet framing
 | ||||
|     //
 | ||||
|     //0        1        2        3        4        5        6        7
 | ||||
|     //v        v        v        v        v        v        v        v
 | ||||
|     //01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF
 | ||||
|     //-----------------------------------------------------------------------
 | ||||
|     //00000000 01______ _1______ __1_____ ___1____ ____1___ _____1XX XXXXXX11
 | ||||
|     //
 | ||||
|     // _ = variable data
 | ||||
|     // 0 = preamble 0
 | ||||
|     // 1 = framing 1
 | ||||
|     // X = checksum
 | ||||
| 
 | ||||
|     // Validate the packet preamble is there...
 | ||||
|     if(encoded_data[0] != 0b00000000) { | ||||
|         return false; | ||||
|     } | ||||
|     if((encoded_data[1] >> 6) != 0b01) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // ... check for known ones...
 | ||||
|     if((encoded_data[2] & 0b01000000) == 0) { | ||||
|         return false; | ||||
|     } | ||||
|     if((encoded_data[3] & 0b00100000) == 0) { | ||||
|         return false; | ||||
|     } | ||||
|     if((encoded_data[4] & 0b00010000) == 0) { | ||||
|         return false; | ||||
|     } | ||||
|     if((encoded_data[5] & 0b00001000) == 0) { | ||||
|         return false; | ||||
|     } | ||||
|     if((encoded_data[6] & 0b00000100) == 0) { | ||||
|         return false; | ||||
|     } | ||||
|     if((encoded_data[7] & 0b00000011) == 0) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // ... and validate our checksums.
 | ||||
|     uint8_t checksum = compute_checksum(encoded_data, 8); | ||||
|     uint8_t checkval = (encoded_data[6] << 6) | (encoded_data[7] >> 2); | ||||
| 
 | ||||
|     if(checksum != checkval) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| uint8_t ProtocolIoProx::compute_checksum(const uint8_t* data, const uint8_t data_size) { | ||||
|     furi_check(data_size == get_encoded_data_size()); | ||||
| 
 | ||||
|     // Packet structure:
 | ||||
|     //
 | ||||
|     //0        1        2         3         4         5         6         7
 | ||||
|     //v        v        v         v         v         v         v         v
 | ||||
|     //01234567 8 9ABCDEF0 1 23456789 A BCDEF012 3 456789AB C DEF01234 5 6789ABCD EF
 | ||||
|     //00000000 0 VVVVVVVV 1 WWWWWWWW 1 XXXXXXXX 1 YYYYYYYY 1 ZZZZZZZZ 1 CHECKSUM 11
 | ||||
|     //
 | ||||
|     // algorithm as observed by the proxmark3 folks
 | ||||
|     // CHECKSUM == 0xFF - (V + W + X + Y + Z)
 | ||||
| 
 | ||||
|     uint8_t checksum = 0; | ||||
| 
 | ||||
|     checksum += (data[1] << 1) | (data[2] >> 7); // VVVVVVVVV
 | ||||
|     checksum += (data[2] << 2) | (data[3] >> 6); // WWWWWWWWW
 | ||||
|     checksum += (data[3] << 3) | (data[4] >> 5); // XXXXXXXXX
 | ||||
|     checksum += (data[4] << 4) | (data[5] >> 4); // YYYYYYYYY
 | ||||
|     checksum += (data[5] << 5) | (data[6] >> 3); // ZZZZZZZZZ
 | ||||
| 
 | ||||
|     return 0xFF - checksum; | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| #pragma once | ||||
| #include "protocol_generic.h" | ||||
| 
 | ||||
| class ProtocolIoProx : public ProtocolGeneric { | ||||
| public: | ||||
|     uint8_t get_encoded_data_size() final; | ||||
|     uint8_t get_decoded_data_size() final; | ||||
| 
 | ||||
|     void encode( | ||||
|         const uint8_t* decoded_data, | ||||
|         const uint8_t decoded_data_size, | ||||
|         uint8_t* encoded_data, | ||||
|         const uint8_t encoded_data_size) final; | ||||
| 
 | ||||
|     void decode( | ||||
|         const uint8_t* encoded_data, | ||||
|         const uint8_t encoded_data_size, | ||||
|         uint8_t* decoded_data, | ||||
|         const uint8_t decoded_data_size) final; | ||||
| 
 | ||||
|     bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final; | ||||
| 
 | ||||
| private: | ||||
|     /**  Computes the IoProx checksum of the provided (decoded) data. */ | ||||
|     uint8_t compute_checksum(const uint8_t* data, const uint8_t data_size); | ||||
| }; | ||||
| @ -1,95 +0,0 @@ | ||||
| #include "pulse_joiner.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| bool PulseJoiner::push_pulse(bool polarity, uint16_t period, uint16_t pulse) { | ||||
|     bool result = false; | ||||
|     furi_check((pulse_index + 1) < pulse_max); | ||||
| 
 | ||||
|     if(polarity == false && pulse_index == 0) { | ||||
|         // first negative pulse is ommited
 | ||||
| 
 | ||||
|     } else { | ||||
|         pulses[pulse_index].polarity = polarity; | ||||
|         pulses[pulse_index].time = pulse; | ||||
|         pulse_index++; | ||||
|     } | ||||
| 
 | ||||
|     if(period > pulse) { | ||||
|         pulses[pulse_index].polarity = !polarity; | ||||
|         pulses[pulse_index].time = period - pulse; | ||||
|         pulse_index++; | ||||
|     } | ||||
| 
 | ||||
|     if(pulse_index >= 4) { | ||||
|         // we know that first pulse is always high
 | ||||
|         // so we wait 2 edges, hi2low and next low2hi
 | ||||
| 
 | ||||
|         uint8_t edges_count = 0; | ||||
|         bool last_polarity = pulses[0].polarity; | ||||
| 
 | ||||
|         for(uint8_t i = 1; i < pulse_index; i++) { | ||||
|             if(pulses[i].polarity != last_polarity) { | ||||
|                 edges_count++; | ||||
|                 last_polarity = pulses[i].polarity; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(edges_count >= 2) { | ||||
|             result = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void PulseJoiner::pop_pulse(uint16_t* period, uint16_t* pulse) { | ||||
|     furi_check(pulse_index <= (pulse_max + 1)); | ||||
| 
 | ||||
|     uint16_t tmp_period = 0; | ||||
|     uint16_t tmp_pulse = 0; | ||||
|     uint8_t edges_count = 0; | ||||
|     bool last_polarity = pulses[0].polarity; | ||||
|     uint8_t next_fist_pulse = 0; | ||||
| 
 | ||||
|     for(uint8_t i = 0; i < pulse_max; i++) { | ||||
|         // count edges
 | ||||
|         if(pulses[i].polarity != last_polarity) { | ||||
|             edges_count++; | ||||
|             last_polarity = pulses[i].polarity; | ||||
|         } | ||||
| 
 | ||||
|         // wait for 2 edges
 | ||||
|         if(edges_count == 2) { | ||||
|             next_fist_pulse = i; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         // sum pulse time
 | ||||
|         if(pulses[i].polarity) { | ||||
|             tmp_period += pulses[i].time; | ||||
|             tmp_pulse += pulses[i].time; | ||||
|         } else { | ||||
|             tmp_period += pulses[i].time; | ||||
|         } | ||||
|         pulse_index--; | ||||
|     } | ||||
| 
 | ||||
|     *period = tmp_period; | ||||
|     *pulse = tmp_pulse; | ||||
| 
 | ||||
|     // remove counted periods and shift data
 | ||||
|     for(uint8_t i = 0; i < pulse_max; i++) { | ||||
|         if((next_fist_pulse + i) < pulse_max) { | ||||
|             pulses[i].polarity = pulses[next_fist_pulse + i].polarity; | ||||
|             pulses[i].time = pulses[next_fist_pulse + i].time; | ||||
|         } else { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| PulseJoiner::PulseJoiner() { | ||||
|     for(uint8_t i = 0; i < pulse_max; i++) { | ||||
|         pulses[i] = {false, 0}; | ||||
|     } | ||||
| } | ||||
| @ -1,36 +0,0 @@ | ||||
| #pragma once | ||||
| #include "stdint.h" | ||||
| 
 | ||||
| class PulseJoiner { | ||||
| public: | ||||
|     /**
 | ||||
|      * @brief Push timer pulse. First negative pulse is ommited. | ||||
|      *  | ||||
|      * @param polarity pulse polarity: true = high2low, false = low2high | ||||
|      * @param period overall period time in timer clicks | ||||
|      * @param pulse pulse time in timer clicks | ||||
|      *  | ||||
|      * @return true - next pulse can and must be popped immediatly | ||||
|      */ | ||||
|     bool push_pulse(bool polarity, uint16_t period, uint16_t pulse); | ||||
| 
 | ||||
|     /**
 | ||||
|      * @brief Get the next timer pulse. Call only if push_pulse returns true. | ||||
|      *  | ||||
|      * @param period overall period time in timer clicks | ||||
|      * @param pulse pulse time in timer clicks | ||||
|      */ | ||||
|     void pop_pulse(uint16_t* period, uint16_t* pulse); | ||||
| 
 | ||||
|     PulseJoiner(); | ||||
| 
 | ||||
| private: | ||||
|     struct Pulse { | ||||
|         bool polarity; | ||||
|         uint16_t time; | ||||
|     }; | ||||
| 
 | ||||
|     uint8_t pulse_index = 0; | ||||
|     static const uint8_t pulse_max = 6; | ||||
|     Pulse pulses[pulse_max]; | ||||
| }; | ||||
| @ -1,65 +0,0 @@ | ||||
| #include "rfid_key.h" | ||||
| #include <core/check.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| RfidKey::RfidKey() { | ||||
|     clear(); | ||||
| } | ||||
| 
 | ||||
| RfidKey::~RfidKey() { | ||||
| } | ||||
| 
 | ||||
| void RfidKey::set_type(LfrfidKeyType _type) { | ||||
|     type = _type; | ||||
| } | ||||
| 
 | ||||
| void RfidKey::set_data(const uint8_t* _data, const uint8_t _data_size) { | ||||
|     furi_assert(_data_size <= data.size()); | ||||
|     for(uint8_t i = 0; i < _data_size; i++) { | ||||
|         data[i] = _data[i]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void RfidKey::set_name(const char* _name) { | ||||
|     strlcpy(name, _name, get_name_length()); | ||||
| } | ||||
| 
 | ||||
| LfrfidKeyType RfidKey::get_type() { | ||||
|     return type; | ||||
| } | ||||
| 
 | ||||
| const uint8_t* RfidKey::get_data() { | ||||
|     return &data[0]; | ||||
| } | ||||
| 
 | ||||
| const char* RfidKey::get_type_text() { | ||||
|     return lfrfid_key_get_type_string(type); | ||||
| } | ||||
| 
 | ||||
| uint8_t RfidKey::get_type_data_count() const { | ||||
|     return lfrfid_key_get_type_data_count(type); | ||||
| } | ||||
| 
 | ||||
| char* RfidKey::get_name() { | ||||
|     return name; | ||||
| } | ||||
| 
 | ||||
| uint8_t RfidKey::get_name_length() { | ||||
|     return LFRFID_KEY_NAME_SIZE; | ||||
| } | ||||
| 
 | ||||
| void RfidKey::clear() { | ||||
|     set_name(""); | ||||
|     set_type(LfrfidKeyType::KeyEM4100); | ||||
|     data.fill(0); | ||||
| } | ||||
| 
 | ||||
| RfidKey& RfidKey::operator=(const RfidKey& rhs) { | ||||
|     if(this == &rhs) return *this; | ||||
| 
 | ||||
|     set_type(rhs.type); | ||||
|     set_name(rhs.name); | ||||
|     set_data(&rhs.data[0], get_type_data_count()); | ||||
| 
 | ||||
|     return *this; | ||||
| } | ||||
| @ -1,27 +0,0 @@ | ||||
| #pragma once | ||||
| #include "key_info.h" | ||||
| #include <array> | ||||
| 
 | ||||
| class RfidKey { | ||||
| public: | ||||
|     RfidKey(); | ||||
|     ~RfidKey(); | ||||
| 
 | ||||
|     void set_type(LfrfidKeyType type); | ||||
|     void set_data(const uint8_t* data, const uint8_t data_size); | ||||
|     void set_name(const char* name); | ||||
| 
 | ||||
|     LfrfidKeyType get_type(); | ||||
|     const uint8_t* get_data(); | ||||
|     const char* get_type_text(); | ||||
|     uint8_t get_type_data_count() const; | ||||
|     char* get_name(); | ||||
|     uint8_t get_name_length(); | ||||
|     void clear(); | ||||
|     RfidKey& operator=(const RfidKey& rhs); | ||||
| 
 | ||||
| private: | ||||
|     std::array<uint8_t, LFRFID_KEY_SIZE> data; | ||||
|     LfrfidKeyType type; | ||||
|     char name[LFRFID_KEY_NAME_SIZE + 1]; | ||||
| }; | ||||
| @ -1,175 +0,0 @@ | ||||
| #include "rfid_reader.h" | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <stm32wbxx_ll_cortex.h> | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief private violation assistant for RfidReader | ||||
|  */ | ||||
| struct RfidReaderAccessor { | ||||
|     static void decode(RfidReader& rfid_reader, bool polarity) { | ||||
|         rfid_reader.decode(polarity); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| void RfidReader::decode(bool polarity) { | ||||
|     uint32_t current_dwt_value = DWT->CYCCNT; | ||||
|     uint32_t period = current_dwt_value - last_dwt_value; | ||||
|     last_dwt_value = current_dwt_value; | ||||
| 
 | ||||
| #ifdef RFID_GPIO_DEBUG | ||||
|     decoder_gpio_out.process_front(polarity, period); | ||||
| #endif | ||||
| 
 | ||||
|     switch(type) { | ||||
|     case Type::Normal: | ||||
|         decoder_em.process_front(polarity, period); | ||||
|         decoder_hid26.process_front(polarity, period); | ||||
|         decoder_ioprox.process_front(polarity, period); | ||||
|         break; | ||||
|     case Type::Indala: | ||||
|         decoder_em.process_front(polarity, period); | ||||
|         decoder_hid26.process_front(polarity, period); | ||||
|         decoder_ioprox.process_front(polarity, period); | ||||
|         decoder_indala.process_front(polarity, period); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     detect_ticks++; | ||||
| } | ||||
| 
 | ||||
| bool RfidReader::switch_timer_elapsed() { | ||||
|     const uint32_t seconds_to_switch = furi_kernel_get_tick_frequency() * 2.0f; | ||||
|     return (furi_get_tick() - switch_os_tick_last) > seconds_to_switch; | ||||
| } | ||||
| 
 | ||||
| void RfidReader::switch_timer_reset() { | ||||
|     switch_os_tick_last = furi_get_tick(); | ||||
| } | ||||
| 
 | ||||
| void RfidReader::switch_mode() { | ||||
|     switch(type) { | ||||
|     case Type::Normal: | ||||
|         type = Type::Indala; | ||||
|         furi_hal_rfid_change_read_config(62500.0f, 0.25f); | ||||
|         break; | ||||
|     case Type::Indala: | ||||
|         type = Type::Normal; | ||||
|         furi_hal_rfid_change_read_config(125000.0f, 0.5f); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     switch_timer_reset(); | ||||
| } | ||||
| 
 | ||||
| static void comparator_trigger_callback(bool level, void* comp_ctx) { | ||||
|     RfidReader* _this = static_cast<RfidReader*>(comp_ctx); | ||||
| 
 | ||||
|     RfidReaderAccessor::decode(*_this, !level); | ||||
| } | ||||
| 
 | ||||
| RfidReader::RfidReader() { | ||||
| } | ||||
| 
 | ||||
| void RfidReader::start() { | ||||
|     type = Type::Normal; | ||||
| 
 | ||||
|     furi_hal_rfid_pins_read(); | ||||
|     furi_hal_rfid_tim_read(125000, 0.5); | ||||
|     furi_hal_rfid_tim_read_start(); | ||||
|     start_comparator(); | ||||
| 
 | ||||
|     switch_timer_reset(); | ||||
|     last_read_count = 0; | ||||
| } | ||||
| 
 | ||||
| void RfidReader::start_forced(RfidReader::Type _type) { | ||||
|     start(); | ||||
|     if(_type == Type::Indala) { | ||||
|         switch_mode(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void RfidReader::stop() { | ||||
|     furi_hal_rfid_pins_reset(); | ||||
|     furi_hal_rfid_tim_read_stop(); | ||||
|     furi_hal_rfid_tim_reset(); | ||||
|     stop_comparator(); | ||||
| } | ||||
| 
 | ||||
| bool RfidReader::read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bool switch_enable) { | ||||
|     bool result = false; | ||||
|     bool something_read = false; | ||||
| 
 | ||||
|     // reading
 | ||||
|     if(decoder_em.read(data, data_size)) { | ||||
|         *_type = LfrfidKeyType::KeyEM4100; | ||||
|         something_read = true; | ||||
|     } | ||||
| 
 | ||||
|     if(decoder_hid26.read(data, data_size)) { | ||||
|         *_type = LfrfidKeyType::KeyH10301; | ||||
|         something_read = true; | ||||
|     } | ||||
| 
 | ||||
|     if(decoder_ioprox.read(data, data_size)) { | ||||
|         *_type = LfrfidKeyType::KeyIoProxXSF; | ||||
|         something_read = true; | ||||
|     } | ||||
| 
 | ||||
|     if(decoder_indala.read(data, data_size)) { | ||||
|         *_type = LfrfidKeyType::KeyI40134; | ||||
|         something_read = true; | ||||
|     } | ||||
| 
 | ||||
|     // validation
 | ||||
|     if(something_read) { | ||||
|         switch_timer_reset(); | ||||
| 
 | ||||
|         if(last_read_type == *_type && memcmp(last_read_data, data, data_size) == 0) { | ||||
|             last_read_count = last_read_count + 1; | ||||
| 
 | ||||
|             if(last_read_count > 2) { | ||||
|                 result = true; | ||||
|             } | ||||
|         } else { | ||||
|             last_read_type = *_type; | ||||
|             memcpy(last_read_data, data, data_size); | ||||
|             last_read_count = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // mode switching
 | ||||
|     if(switch_enable && switch_timer_elapsed()) { | ||||
|         switch_mode(); | ||||
|         last_read_count = 0; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool RfidReader::detect() { | ||||
|     bool detected = false; | ||||
|     if(detect_ticks > 10) { | ||||
|         detected = true; | ||||
|     } | ||||
|     detect_ticks = 0; | ||||
| 
 | ||||
|     return detected; | ||||
| } | ||||
| 
 | ||||
| bool RfidReader::any_read() { | ||||
|     return last_read_count > 0; | ||||
| } | ||||
| 
 | ||||
| void RfidReader::start_comparator(void) { | ||||
|     furi_hal_rfid_comp_set_callback(comparator_trigger_callback, this); | ||||
|     last_dwt_value = DWT->CYCCNT; | ||||
| 
 | ||||
|     furi_hal_rfid_comp_start(); | ||||
| } | ||||
| 
 | ||||
| void RfidReader::stop_comparator(void) { | ||||
|     furi_hal_rfid_comp_stop(); | ||||
|     furi_hal_rfid_comp_set_callback(NULL, NULL); | ||||
| } | ||||
| @ -1,59 +0,0 @@ | ||||
| #pragma once | ||||
| //#include "decoder_analyzer.h"
 | ||||
| #include "decoder_gpio_out.h" | ||||
| #include "decoder_emmarin.h" | ||||
| #include "decoder_hid26.h" | ||||
| #include "decoder_indala.h" | ||||
| #include "decoder_ioprox.h" | ||||
| #include "key_info.h" | ||||
| 
 | ||||
| //#define RFID_GPIO_DEBUG 1
 | ||||
| 
 | ||||
| class RfidReader { | ||||
| public: | ||||
|     enum class Type : uint8_t { | ||||
|         Normal, | ||||
|         Indala, | ||||
|     }; | ||||
| 
 | ||||
|     RfidReader(); | ||||
|     void start(); | ||||
|     void start_forced(RfidReader::Type type); | ||||
|     void stop(); | ||||
|     bool read(LfrfidKeyType* type, uint8_t* data, uint8_t data_size, bool switch_enable = true); | ||||
| 
 | ||||
|     bool detect(); | ||||
|     bool any_read(); | ||||
| 
 | ||||
| private: | ||||
|     friend struct RfidReaderAccessor; | ||||
| 
 | ||||
|     //DecoderAnalyzer decoder_analyzer;
 | ||||
| #ifdef RFID_GPIO_DEBUG | ||||
|     DecoderGpioOut decoder_gpio_out; | ||||
| #endif | ||||
|     DecoderEMMarin decoder_em; | ||||
|     DecoderHID26 decoder_hid26; | ||||
|     DecoderIndala decoder_indala; | ||||
|     DecoderIoProx decoder_ioprox; | ||||
| 
 | ||||
|     uint32_t last_dwt_value; | ||||
| 
 | ||||
|     void start_comparator(void); | ||||
|     void stop_comparator(void); | ||||
| 
 | ||||
|     void decode(bool polarity); | ||||
| 
 | ||||
|     uint32_t detect_ticks; | ||||
| 
 | ||||
|     uint32_t switch_os_tick_last; | ||||
|     bool switch_timer_elapsed(); | ||||
|     void switch_timer_reset(); | ||||
|     void switch_mode(); | ||||
| 
 | ||||
|     LfrfidKeyType last_read_type; | ||||
|     uint8_t last_read_data[LFRFID_KEY_SIZE]; | ||||
|     uint8_t last_read_count; | ||||
| 
 | ||||
|     Type type = Type::Normal; | ||||
| }; | ||||
| @ -1,56 +0,0 @@ | ||||
| #include "rfid_timer_emulator.h" | ||||
| 
 | ||||
| RfidTimerEmulator::RfidTimerEmulator() { | ||||
| } | ||||
| 
 | ||||
| RfidTimerEmulator::~RfidTimerEmulator() { | ||||
|     std::map<LfrfidKeyType, EncoderGeneric*>::iterator it; | ||||
| 
 | ||||
|     for(it = encoders.begin(); it != encoders.end(); ++it) { | ||||
|         delete it->second; | ||||
|     } | ||||
| 
 | ||||
|     encoders.clear(); | ||||
| } | ||||
| 
 | ||||
| void RfidTimerEmulator::start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size) { | ||||
|     if(encoders.count(type)) { | ||||
|         current_encoder = encoders.find(type)->second; | ||||
| 
 | ||||
|         if(data_size >= lfrfid_key_get_type_data_count(type)) { | ||||
|             current_encoder->init(data, data_size); | ||||
| 
 | ||||
|             furi_hal_rfid_tim_emulate(125000); | ||||
|             furi_hal_rfid_pins_emulate(); | ||||
| 
 | ||||
|             furi_hal_rfid_tim_emulate_start(RfidTimerEmulator::timer_update_callback, this); | ||||
|         } | ||||
|     } else { | ||||
|         // not found
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void RfidTimerEmulator::stop() { | ||||
|     furi_hal_rfid_tim_emulate_stop(); | ||||
|     furi_hal_rfid_tim_reset(); | ||||
|     furi_hal_rfid_pins_reset(); | ||||
| } | ||||
| 
 | ||||
| void RfidTimerEmulator::timer_update_callback(void* ctx) { | ||||
|     RfidTimerEmulator* _this = static_cast<RfidTimerEmulator*>(ctx); | ||||
| 
 | ||||
|     bool result; | ||||
|     bool polarity; | ||||
|     uint16_t period; | ||||
|     uint16_t pulse; | ||||
| 
 | ||||
|     do { | ||||
|         _this->current_encoder->get_next(&polarity, &period, &pulse); | ||||
|         result = _this->pulse_joiner.push_pulse(polarity, period, pulse); | ||||
|     } while(result == false); | ||||
| 
 | ||||
|     _this->pulse_joiner.pop_pulse(&period, &pulse); | ||||
| 
 | ||||
|     furi_hal_rfid_set_emulate_period(period - 1); | ||||
|     furi_hal_rfid_set_emulate_pulse(pulse); | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| #pragma once | ||||
| #include <furi_hal.h> | ||||
| #include "key_info.h" | ||||
| #include "encoder_generic.h" | ||||
| #include "encoder_emmarin.h" | ||||
| #include "encoder_hid_h10301.h" | ||||
| #include "encoder_indala_40134.h" | ||||
| #include "encoder_ioprox.h" | ||||
| #include "pulse_joiner.h" | ||||
| #include <map> | ||||
| 
 | ||||
| class RfidTimerEmulator { | ||||
| public: | ||||
|     RfidTimerEmulator(); | ||||
|     ~RfidTimerEmulator(); | ||||
|     void start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size); | ||||
|     void stop(); | ||||
| 
 | ||||
| private: | ||||
|     EncoderGeneric* current_encoder = nullptr; | ||||
| 
 | ||||
|     std::map<LfrfidKeyType, EncoderGeneric*> encoders = { | ||||
|         {LfrfidKeyType::KeyEM4100, new EncoderEM()}, | ||||
|         {LfrfidKeyType::KeyH10301, new EncoderHID_H10301()}, | ||||
|         {LfrfidKeyType::KeyI40134, new EncoderIndala_40134()}, | ||||
|         {LfrfidKeyType::KeyIoProxXSF, new EncoderIoProx()}, | ||||
|     }; | ||||
| 
 | ||||
|     PulseJoiner pulse_joiner; | ||||
|     static void timer_update_callback(void* ctx); | ||||
| }; | ||||
| @ -1,136 +0,0 @@ | ||||
| #include "rfid_worker.h" | ||||
| 
 | ||||
| RfidWorker::RfidWorker() { | ||||
| } | ||||
| 
 | ||||
| RfidWorker::~RfidWorker() { | ||||
| } | ||||
| 
 | ||||
| void RfidWorker::start_read() { | ||||
|     reader.start(); | ||||
| } | ||||
| 
 | ||||
| bool RfidWorker::read() { | ||||
|     static const uint8_t data_size = LFRFID_KEY_SIZE; | ||||
|     uint8_t data[data_size] = {0}; | ||||
|     LfrfidKeyType type; | ||||
| 
 | ||||
|     bool result = reader.read(&type, data, data_size); | ||||
| 
 | ||||
|     if(result) { | ||||
|         key.set_type(type); | ||||
|         key.set_data(data, data_size); | ||||
|     }; | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool RfidWorker::detect() { | ||||
|     return reader.detect(); | ||||
| } | ||||
| 
 | ||||
| bool RfidWorker::any_read() { | ||||
|     return reader.any_read(); | ||||
| } | ||||
| 
 | ||||
| void RfidWorker::stop_read() { | ||||
|     reader.stop(); | ||||
| } | ||||
| 
 | ||||
| void RfidWorker::start_write() { | ||||
|     write_result = WriteResult::Nothing; | ||||
|     write_sequence = new TickSequencer(); | ||||
|     validate_counts = 0; | ||||
| 
 | ||||
|     write_sequence->do_every_tick(1, std::bind(&RfidWorker::sq_write, this)); | ||||
|     write_sequence->do_after_tick(2, std::bind(&RfidWorker::sq_write_start_validate, this)); | ||||
|     write_sequence->do_every_tick(30, std::bind(&RfidWorker::sq_write_validate, this)); | ||||
|     write_sequence->do_every_tick(1, std::bind(&RfidWorker::sq_write_stop_validate, this)); | ||||
| } | ||||
| 
 | ||||
| RfidWorker::WriteResult RfidWorker::write() { | ||||
|     write_sequence->tick(); | ||||
|     return write_result; | ||||
| } | ||||
| 
 | ||||
| void RfidWorker::stop_write() { | ||||
|     delete write_sequence; | ||||
|     reader.stop(); | ||||
| } | ||||
| 
 | ||||
| void RfidWorker::start_emulate() { | ||||
|     emulator.start(key.get_type(), key.get_data(), key.get_type_data_count()); | ||||
| } | ||||
| 
 | ||||
| void RfidWorker::stop_emulate() { | ||||
|     emulator.stop(); | ||||
| } | ||||
| 
 | ||||
| void RfidWorker::sq_write() { | ||||
|     for(size_t i = 0; i < 5; i++) { | ||||
|         switch(key.get_type()) { | ||||
|         case LfrfidKeyType::KeyEM4100: | ||||
|             writer.start(); | ||||
|             writer.write_em(key.get_data()); | ||||
|             writer.stop(); | ||||
|             break; | ||||
|         case LfrfidKeyType::KeyH10301: | ||||
|             writer.start(); | ||||
|             writer.write_hid(key.get_data()); | ||||
|             writer.stop(); | ||||
|             break; | ||||
|         case LfrfidKeyType::KeyI40134: | ||||
|             writer.start(); | ||||
|             writer.write_indala(key.get_data()); | ||||
|             writer.stop(); | ||||
|             break; | ||||
|         case LfrfidKeyType::KeyIoProxXSF: | ||||
|             writer.start(); | ||||
|             writer.write_ioprox(key.get_data()); | ||||
|             writer.stop(); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void RfidWorker::sq_write_start_validate() { | ||||
|     switch(key.get_type()) { | ||||
|     case LfrfidKeyType::KeyEM4100: | ||||
|     case LfrfidKeyType::KeyH10301: | ||||
|     case LfrfidKeyType::KeyIoProxXSF: | ||||
|         reader.start_forced(RfidReader::Type::Normal); | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyI40134: | ||||
|         reader.start_forced(RfidReader::Type::Indala); | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void RfidWorker::sq_write_validate() { | ||||
|     static const uint8_t data_size = LFRFID_KEY_SIZE; | ||||
|     uint8_t data[data_size] = {0}; | ||||
|     LfrfidKeyType type; | ||||
| 
 | ||||
|     bool result = reader.read(&type, data, data_size); | ||||
| 
 | ||||
|     if(result && (write_result != WriteResult::Ok)) { | ||||
|         if(validate_counts > (5 * 60)) { | ||||
|             write_result = WriteResult::NotWritable; | ||||
|         } | ||||
| 
 | ||||
|         if(type == key.get_type()) { | ||||
|             if(memcmp(data, key.get_data(), key.get_type_data_count()) == 0) { | ||||
|                 write_result = WriteResult::Ok; | ||||
|                 validate_counts = 0; | ||||
|             } else { | ||||
|                 validate_counts++; | ||||
|             } | ||||
|         } else { | ||||
|             validate_counts++; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| void RfidWorker::sq_write_stop_validate() { | ||||
|     reader.stop(); | ||||
| } | ||||
| @ -1,48 +0,0 @@ | ||||
| #pragma once | ||||
| #include "key_info.h" | ||||
| #include "rfid_reader.h" | ||||
| #include "rfid_writer.h" | ||||
| #include "rfid_timer_emulator.h" | ||||
| #include "rfid_key.h" | ||||
| #include "state_sequencer.h" | ||||
| 
 | ||||
| class RfidWorker { | ||||
| public: | ||||
|     RfidWorker(); | ||||
|     ~RfidWorker(); | ||||
| 
 | ||||
|     void start_read(); | ||||
|     bool read(); | ||||
|     bool detect(); | ||||
|     bool any_read(); | ||||
|     void stop_read(); | ||||
| 
 | ||||
|     enum class WriteResult : uint8_t { | ||||
|         Ok, | ||||
|         NotWritable, | ||||
|         Nothing, | ||||
|     }; | ||||
| 
 | ||||
|     void start_write(); | ||||
|     WriteResult write(); | ||||
|     void stop_write(); | ||||
| 
 | ||||
|     void start_emulate(); | ||||
|     void stop_emulate(); | ||||
| 
 | ||||
|     RfidKey key; | ||||
| 
 | ||||
| private: | ||||
|     RfidWriter writer; | ||||
|     RfidReader reader; | ||||
|     RfidTimerEmulator emulator; | ||||
| 
 | ||||
|     WriteResult write_result; | ||||
|     TickSequencer* write_sequence; | ||||
| 
 | ||||
|     void sq_write(); | ||||
|     void sq_write_start_validate(); | ||||
|     void sq_write_validate(); | ||||
|     uint16_t validate_counts; | ||||
|     void sq_write_stop_validate(); | ||||
| }; | ||||
| @ -1,183 +0,0 @@ | ||||
| #include "rfid_writer.h" | ||||
| #include "protocols/protocol_ioprox.h" | ||||
| #include <furi_hal.h> | ||||
| #include "protocols/protocol_emmarin.h" | ||||
| #include "protocols/protocol_hid_h10301.h" | ||||
| #include "protocols/protocol_indala_40134.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief all timings are specified in field clocks (field clock = 125 kHz, 8 us) | ||||
|  *  | ||||
|  */ | ||||
| class T55xxTiming { | ||||
| public: | ||||
|     constexpr static const uint16_t wait_time = 400; | ||||
|     constexpr static const uint8_t start_gap = 30; | ||||
|     constexpr static const uint8_t write_gap = 18; | ||||
|     constexpr static const uint8_t data_0 = 24; | ||||
|     constexpr static const uint8_t data_1 = 56; | ||||
|     constexpr static const uint16_t program = 700; | ||||
| }; | ||||
| 
 | ||||
| class T55xxCmd { | ||||
| public: | ||||
|     constexpr static const uint8_t opcode_page_0 = 0b10; | ||||
|     constexpr static const uint8_t opcode_page_1 = 0b11; | ||||
|     constexpr static const uint8_t opcode_reset = 0b00; | ||||
| }; | ||||
| 
 | ||||
| RfidWriter::RfidWriter() { | ||||
| } | ||||
| 
 | ||||
| RfidWriter::~RfidWriter() { | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::start() { | ||||
|     furi_hal_rfid_tim_read(125000, 0.5); | ||||
|     furi_hal_rfid_pins_read(); | ||||
|     furi_hal_rfid_tim_read_start(); | ||||
| 
 | ||||
|     // do not ground the antenna
 | ||||
|     furi_hal_rfid_pin_pull_release(); | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::stop() { | ||||
|     furi_hal_rfid_tim_read_stop(); | ||||
|     furi_hal_rfid_tim_reset(); | ||||
|     furi_hal_rfid_pins_reset(); | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::write_gap(uint32_t gap_time) { | ||||
|     furi_hal_rfid_tim_read_stop(); | ||||
|     furi_delay_us(gap_time * 8); | ||||
|     furi_hal_rfid_tim_read_start(); | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::write_bit(bool value) { | ||||
|     if(value) { | ||||
|         furi_delay_us(T55xxTiming::data_1 * 8); | ||||
|     } else { | ||||
|         furi_delay_us(T55xxTiming::data_0 * 8); | ||||
|     } | ||||
|     write_gap(T55xxTiming::write_gap); | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::write_byte(uint8_t value) { | ||||
|     for(uint8_t i = 0; i < 8; i++) { | ||||
|         write_bit((value >> i) & 1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::write_block(uint8_t page, uint8_t block, bool lock_bit, uint32_t data) { | ||||
|     furi_delay_us(T55xxTiming::wait_time * 8); | ||||
| 
 | ||||
|     // start gap
 | ||||
|     write_gap(T55xxTiming::start_gap); | ||||
| 
 | ||||
|     // opcode
 | ||||
|     switch(page) { | ||||
|     case 0: | ||||
|         write_bit(1); | ||||
|         write_bit(0); | ||||
|         break; | ||||
|     case 1: | ||||
|         write_bit(1); | ||||
|         write_bit(1); | ||||
|         break; | ||||
|     default: | ||||
|         furi_check(false); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     // lock bit
 | ||||
|     write_bit(lock_bit); | ||||
| 
 | ||||
|     // data
 | ||||
|     for(uint8_t i = 0; i < 32; i++) { | ||||
|         write_bit((data >> (31 - i)) & 1); | ||||
|     } | ||||
| 
 | ||||
|     // block address
 | ||||
|     write_bit((block >> 2) & 1); | ||||
|     write_bit((block >> 1) & 1); | ||||
|     write_bit((block >> 0) & 1); | ||||
| 
 | ||||
|     furi_delay_us(T55xxTiming::program * 8); | ||||
| 
 | ||||
|     furi_delay_us(T55xxTiming::wait_time * 8); | ||||
|     write_reset(); | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::write_reset() { | ||||
|     write_gap(T55xxTiming::start_gap); | ||||
|     write_bit(1); | ||||
|     write_bit(0); | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::write_em(const uint8_t em_data[5]) { | ||||
|     ProtocolEMMarin em_card; | ||||
|     uint64_t em_encoded_data; | ||||
|     em_card.encode(em_data, 5, reinterpret_cast<uint8_t*>(&em_encoded_data), sizeof(uint64_t)); | ||||
|     const uint32_t em_config_block_data = 0b00000000000101001000000001000000; | ||||
| 
 | ||||
|     FURI_CRITICAL_ENTER(); | ||||
|     write_block(0, 0, false, em_config_block_data); | ||||
|     write_block(0, 1, false, em_encoded_data); | ||||
|     write_block(0, 2, false, em_encoded_data >> 32); | ||||
|     write_reset(); | ||||
|     FURI_CRITICAL_EXIT(); | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::write_hid(const uint8_t hid_data[3]) { | ||||
|     ProtocolHID10301 hid_card; | ||||
|     uint32_t card_data[3]; | ||||
|     hid_card.encode(hid_data, 3, reinterpret_cast<uint8_t*>(&card_data), sizeof(card_data) * 3); | ||||
| 
 | ||||
|     const uint32_t hid_config_block_data = 0b00000000000100000111000001100000; | ||||
| 
 | ||||
|     FURI_CRITICAL_ENTER(); | ||||
|     write_block(0, 0, false, hid_config_block_data); | ||||
|     write_block(0, 1, false, card_data[0]); | ||||
|     write_block(0, 2, false, card_data[1]); | ||||
|     write_block(0, 3, false, card_data[2]); | ||||
|     write_reset(); | ||||
|     FURI_CRITICAL_EXIT(); | ||||
| } | ||||
| 
 | ||||
| /** Endian fixup. Translates an ioprox block into a t5577 block */ | ||||
| static uint32_t ioprox_encode_block(const uint8_t block_data[4]) { | ||||
|     uint8_t raw_card_data[] = {block_data[3], block_data[2], block_data[1], block_data[0]}; | ||||
|     return *reinterpret_cast<uint32_t*>(&raw_card_data); | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::write_ioprox(const uint8_t ioprox_data[4]) { | ||||
|     ProtocolIoProx ioprox_card; | ||||
| 
 | ||||
|     uint8_t encoded_data[8]; | ||||
|     ioprox_card.encode(ioprox_data, 4, encoded_data, sizeof(encoded_data)); | ||||
| 
 | ||||
|     const uint32_t ioprox_config_block_data = 0b00000000000101000111000001000000; | ||||
| 
 | ||||
|     FURI_CRITICAL_ENTER(); | ||||
|     write_block(0, 0, false, ioprox_config_block_data); | ||||
|     write_block(0, 1, false, ioprox_encode_block(&encoded_data[0])); | ||||
|     write_block(0, 2, false, ioprox_encode_block(&encoded_data[4])); | ||||
|     write_reset(); | ||||
|     FURI_CRITICAL_EXIT(); | ||||
| } | ||||
| 
 | ||||
| void RfidWriter::write_indala(const uint8_t indala_data[3]) { | ||||
|     ProtocolIndala40134 indala_card; | ||||
|     uint32_t card_data[2]; | ||||
|     indala_card.encode( | ||||
|         indala_data, 3, reinterpret_cast<uint8_t*>(&card_data), sizeof(card_data) * 2); | ||||
| 
 | ||||
|     const uint32_t indala_config_block_data = 0b00000000000010000001000001000000; | ||||
| 
 | ||||
|     FURI_CRITICAL_ENTER(); | ||||
|     write_block(0, 0, false, indala_config_block_data); | ||||
|     write_block(0, 1, false, card_data[0]); | ||||
|     write_block(0, 2, false, card_data[1]); | ||||
|     write_reset(); | ||||
|     FURI_CRITICAL_EXIT(); | ||||
| } | ||||
| @ -1,21 +0,0 @@ | ||||
| #pragma once | ||||
| #include "stdint.h" | ||||
| 
 | ||||
| class RfidWriter { | ||||
| public: | ||||
|     RfidWriter(); | ||||
|     ~RfidWriter(); | ||||
|     void start(); | ||||
|     void stop(); | ||||
|     void write_em(const uint8_t em_data[5]); | ||||
|     void write_hid(const uint8_t hid_data[3]); | ||||
|     void write_ioprox(const uint8_t ioprox_data[4]); | ||||
|     void write_indala(const uint8_t indala_data[3]); | ||||
| 
 | ||||
| private: | ||||
|     void write_gap(uint32_t gap_time); | ||||
|     void write_bit(bool value); | ||||
|     void write_byte(uint8_t value); | ||||
|     void write_block(uint8_t page, uint8_t block, bool lock_bit, uint32_t data); | ||||
|     void write_reset(); | ||||
| }; | ||||
| @ -1,50 +0,0 @@ | ||||
| #include "state_sequencer.h" | ||||
| #include "stdio.h" | ||||
| 
 | ||||
| TickSequencer::TickSequencer() { | ||||
| } | ||||
| 
 | ||||
| TickSequencer::~TickSequencer() { | ||||
| } | ||||
| 
 | ||||
| void TickSequencer::tick() { | ||||
|     if(tick_count == list_it->first) { | ||||
|         tick_count = 0; | ||||
| 
 | ||||
|         list_it++; | ||||
|         if(list_it == list.end()) { | ||||
|             list_it = list.begin(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     list_it->second(); | ||||
|     tick_count++; | ||||
| } | ||||
| 
 | ||||
| void TickSequencer::reset() { | ||||
|     list_it = list.begin(); | ||||
|     tick_count = 0; | ||||
| } | ||||
| 
 | ||||
| void TickSequencer::clear() { | ||||
|     list.clear(); | ||||
|     reset(); | ||||
| } | ||||
| 
 | ||||
| void TickSequencer::do_every_tick(uint32_t tick_count, std::function<void(void)> fn) { | ||||
|     list.push_back(std::make_pair(tick_count, fn)); | ||||
|     reset(); | ||||
| } | ||||
| 
 | ||||
| void TickSequencer::do_after_tick(uint32_t tick_count, std::function<void(void)> fn) { | ||||
|     if(tick_count > 1) { | ||||
|         list.push_back( | ||||
|             std::make_pair(tick_count - 1, std::bind(&TickSequencer::do_nothing, this))); | ||||
|     } | ||||
|     list.push_back(std::make_pair(1, fn)); | ||||
| 
 | ||||
|     reset(); | ||||
| } | ||||
| 
 | ||||
| void TickSequencer::do_nothing() { | ||||
| } | ||||
| @ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
| #include "stdint.h" | ||||
| #include <list> | ||||
| #include <functional> | ||||
| 
 | ||||
| class TickSequencer { | ||||
| public: | ||||
|     TickSequencer(); | ||||
|     ~TickSequencer(); | ||||
| 
 | ||||
|     void tick(); | ||||
|     void reset(); | ||||
|     void clear(); | ||||
| 
 | ||||
|     void do_every_tick(uint32_t tick_count, std::function<void(void)> fn); | ||||
|     void do_after_tick(uint32_t tick_count, std::function<void(void)> fn); | ||||
| 
 | ||||
| private: | ||||
|     std::list<std::pair<uint32_t, std::function<void(void)> > > list; | ||||
|     std::list<std::pair<uint32_t, std::function<void(void)> > >::iterator list_it; | ||||
| 
 | ||||
|     uint32_t tick_count; | ||||
| 
 | ||||
|     void do_nothing(); | ||||
| }; | ||||
| @ -21,6 +21,11 @@ | ||||
| #include "scene/lfrfid_app_scene_delete_confirm.h" | ||||
| #include "scene/lfrfid_app_scene_delete_success.h" | ||||
| #include "scene/lfrfid_app_scene_rpc.h" | ||||
| #include "scene/lfrfid_app_scene_extra_actions.h" | ||||
| #include "scene/lfrfid_app_scene_raw_info.h" | ||||
| #include "scene/lfrfid_app_scene_raw_name.h" | ||||
| #include "scene/lfrfid_app_scene_raw_read.h" | ||||
| #include "scene/lfrfid_app_scene_raw_success.h" | ||||
| 
 | ||||
| #include <toolbox/path.h> | ||||
| #include <flipper_format/flipper_format.h> | ||||
| @ -28,24 +33,44 @@ | ||||
| #include <rpc/rpc_app.h> | ||||
| 
 | ||||
| const char* LfRfidApp::app_folder = ANY_PATH("lfrfid"); | ||||
| const char* LfRfidApp::app_sd_folder = EXT_PATH("lfrfid"); | ||||
| const char* LfRfidApp::app_extension = ".rfid"; | ||||
| const char* LfRfidApp::app_filetype = "Flipper RFID key"; | ||||
| 
 | ||||
| LfRfidApp::LfRfidApp() | ||||
|     : scene_controller{this} | ||||
|     , notification{"notification"} | ||||
|     , storage{"storage"} | ||||
|     , dialogs{"dialogs"} | ||||
|     , notification{RECORD_NOTIFICATION} | ||||
|     , storage{RECORD_STORAGE} | ||||
|     , dialogs{RECORD_DIALOGS} | ||||
|     , text_store(40) { | ||||
|     string_init(file_name); | ||||
|     string_init(raw_file_name); | ||||
|     string_init_set_str(file_path, app_folder); | ||||
| 
 | ||||
|     dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); | ||||
| 
 | ||||
|     size_t size = protocol_dict_get_max_data_size(dict); | ||||
|     new_key_data = (uint8_t*)malloc(size); | ||||
|     old_key_data = (uint8_t*)malloc(size); | ||||
| 
 | ||||
|     lfworker = lfrfid_worker_alloc(dict); | ||||
| } | ||||
| 
 | ||||
| LfRfidApp::~LfRfidApp() { | ||||
|     string_clear(raw_file_name); | ||||
|     string_clear(file_name); | ||||
|     string_clear(file_path); | ||||
|     protocol_dict_free(dict); | ||||
| 
 | ||||
|     lfrfid_worker_free(lfworker); | ||||
| 
 | ||||
|     if(rpc_ctx) { | ||||
|         rpc_system_app_set_callback(rpc_ctx, NULL, NULL); | ||||
|         rpc_system_app_send_exited(rpc_ctx); | ||||
|     } | ||||
| 
 | ||||
|     free(new_key_data); | ||||
|     free(old_key_data); | ||||
| } | ||||
| 
 | ||||
| static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { | ||||
| @ -88,7 +113,7 @@ void LfRfidApp::run(void* _args) { | ||||
|             scene_controller.process(100, SceneType::Rpc); | ||||
|         } else { | ||||
|             string_set_str(file_path, args); | ||||
|             load_key_data(file_path, &worker.key, true); | ||||
|             load_key_data(file_path, true); | ||||
|             view_controller.attach_to_gui(ViewDispatcherTypeFullscreen); | ||||
|             scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); | ||||
|             scene_controller.process(100, SceneType::Emulate); | ||||
| @ -114,11 +139,16 @@ void LfRfidApp::run(void* _args) { | ||||
|         scene_controller.add_scene(SceneType::SavedInfo, new LfRfidAppSceneSavedInfo()); | ||||
|         scene_controller.add_scene(SceneType::DeleteConfirm, new LfRfidAppSceneDeleteConfirm()); | ||||
|         scene_controller.add_scene(SceneType::DeleteSuccess, new LfRfidAppSceneDeleteSuccess()); | ||||
|         scene_controller.add_scene(SceneType::ExtraActions, new LfRfidAppSceneExtraActions()); | ||||
|         scene_controller.add_scene(SceneType::RawInfo, new LfRfidAppSceneRawInfo()); | ||||
|         scene_controller.add_scene(SceneType::RawName, new LfRfidAppSceneRawName()); | ||||
|         scene_controller.add_scene(SceneType::RawRead, new LfRfidAppSceneRawRead()); | ||||
|         scene_controller.add_scene(SceneType::RawSuccess, new LfRfidAppSceneRawSuccess()); | ||||
|         scene_controller.process(100); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool LfRfidApp::save_key(RfidKey* key) { | ||||
| bool LfRfidApp::save_key() { | ||||
|     bool result = false; | ||||
| 
 | ||||
|     make_app_folder(); | ||||
| @ -128,9 +158,9 @@ bool LfRfidApp::save_key(RfidKey* key) { | ||||
|         string_left(file_path, filename_start); | ||||
|     } | ||||
| 
 | ||||
|     string_cat_printf(file_path, "/%s%s", key->get_name(), app_extension); | ||||
|     string_cat_printf(file_path, "/%s%s", string_get_cstr(file_name), app_extension); | ||||
| 
 | ||||
|     result = save_key_data(file_path, key); | ||||
|     result = save_key_data(file_path); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| @ -143,56 +173,27 @@ bool LfRfidApp::load_key_from_file_select(bool need_restore) { | ||||
|         dialogs, file_path, file_path, app_extension, true, &I_125_10px, true); | ||||
| 
 | ||||
|     if(result) { | ||||
|         result = load_key_data(file_path, &worker.key, true); | ||||
|         result = load_key_data(file_path, true); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool LfRfidApp::delete_key(RfidKey* key) { | ||||
|     UNUSED(key); | ||||
| bool LfRfidApp::delete_key() { | ||||
|     return storage_simply_remove(storage, string_get_cstr(file_path)); | ||||
| } | ||||
| 
 | ||||
| bool LfRfidApp::load_key_data(string_t path, RfidKey* key, bool show_dialog) { | ||||
|     FlipperFormat* file = flipper_format_file_alloc(storage); | ||||
| bool LfRfidApp::load_key_data(string_t path, bool show_dialog) { | ||||
|     bool result = false; | ||||
|     string_t str_result; | ||||
|     string_init(str_result); | ||||
| 
 | ||||
|     do { | ||||
|         if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; | ||||
|         protocol_id = lfrfid_dict_file_load(dict, string_get_cstr(path)); | ||||
|         if(protocol_id == PROTOCOL_NO) break; | ||||
| 
 | ||||
|         // header
 | ||||
|         uint32_t version; | ||||
|         if(!flipper_format_read_header(file, str_result, &version)) break; | ||||
|         if(string_cmp_str(str_result, app_filetype) != 0) break; | ||||
|         if(version != 1) break; | ||||
| 
 | ||||
|         // key type
 | ||||
|         LfrfidKeyType type; | ||||
|         RfidKey loaded_key; | ||||
| 
 | ||||
|         if(!flipper_format_read_string(file, "Key type", str_result)) break; | ||||
|         if(!lfrfid_key_get_string_type(string_get_cstr(str_result), &type)) break; | ||||
|         loaded_key.set_type(type); | ||||
| 
 | ||||
|         // key data
 | ||||
|         uint8_t key_data[loaded_key.get_type_data_count()] = {}; | ||||
|         if(!flipper_format_read_hex(file, "Data", key_data, loaded_key.get_type_data_count())) | ||||
|             break; | ||||
|         loaded_key.set_data(key_data, loaded_key.get_type_data_count()); | ||||
| 
 | ||||
|         path_extract_filename(path, str_result, true); | ||||
|         loaded_key.set_name(string_get_cstr(str_result)); | ||||
| 
 | ||||
|         *key = loaded_key; | ||||
|         path_extract_filename(path, file_name, true); | ||||
|         result = true; | ||||
|     } while(0); | ||||
| 
 | ||||
|     flipper_format_free(file); | ||||
|     string_clear(str_result); | ||||
| 
 | ||||
|     if((!result) && (show_dialog)) { | ||||
|         dialog_message_show_storage_error(dialogs, "Cannot load\nkey file"); | ||||
|     } | ||||
| @ -200,27 +201,8 @@ bool LfRfidApp::load_key_data(string_t path, RfidKey* key, bool show_dialog) { | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool LfRfidApp::save_key_data(string_t path, RfidKey* key) { | ||||
|     FlipperFormat* file = flipper_format_file_alloc(storage); | ||||
|     bool result = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!flipper_format_file_open_always(file, string_get_cstr(path))) break; | ||||
|         if(!flipper_format_write_header_cstr(file, app_filetype, 1)) break; | ||||
|         if(!flipper_format_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) | ||||
|             break; | ||||
|         if(!flipper_format_write_string_cstr( | ||||
|                file, "Key type", lfrfid_key_get_type_string(key->get_type()))) | ||||
|             break; | ||||
|         if(!flipper_format_write_comment_cstr( | ||||
|                file, "Data size for EM4100 is 5, for H10301 is 3, for I40134 is 3")) | ||||
|             break; | ||||
|         if(!flipper_format_write_hex(file, "Data", key->get_data(), key->get_type_data_count())) | ||||
|             break; | ||||
|         result = true; | ||||
|     } while(0); | ||||
| 
 | ||||
|     flipper_format_free(file); | ||||
| bool LfRfidApp::save_key_data(string_t path) { | ||||
|     bool result = lfrfid_dict_file_save(dict, protocol_id, string_get_cstr(path)); | ||||
| 
 | ||||
|     if(!result) { | ||||
|         dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); | ||||
|  | ||||
| @ -20,9 +20,15 @@ | ||||
| #include <storage/storage.h> | ||||
| #include <dialogs/dialogs.h> | ||||
| 
 | ||||
| #include "helpers/rfid_worker.h" | ||||
| #include "rpc/rpc_app.h" | ||||
| 
 | ||||
| #include <toolbox/protocols/protocol_dict.h> | ||||
| #include <lfrfid/lfrfid_dict_file.h> | ||||
| #include <lfrfid/protocols/lfrfid_protocols.h> | ||||
| #include <lfrfid/lfrfid_worker.h> | ||||
| 
 | ||||
| #define LFRFID_KEY_NAME_SIZE 22 | ||||
| 
 | ||||
| class LfRfidApp { | ||||
| public: | ||||
|     enum class EventType : uint8_t { | ||||
| @ -32,7 +38,19 @@ public: | ||||
|         Stay, | ||||
|         Retry, | ||||
|         Exit, | ||||
|         EmulateStart, | ||||
|         ReadEventSenseStart, | ||||
|         ReadEventSenseEnd, | ||||
|         ReadEventSenseCardStart, | ||||
|         ReadEventSenseCardEnd, | ||||
|         ReadEventStartASK, | ||||
|         ReadEventStartPSK, | ||||
|         ReadEventDone, | ||||
|         ReadEventOverrun, | ||||
|         ReadEventError, | ||||
|         WriteEventOK, | ||||
|         WriteEventProtocolCannotBeWritten, | ||||
|         WriteEventFobCannotBeWritten, | ||||
|         WriteEventTooLongToWrite, | ||||
|         RpcLoadFile, | ||||
|         RpcSessionClose, | ||||
|     }; | ||||
| @ -57,12 +75,17 @@ public: | ||||
|         DeleteConfirm, | ||||
|         DeleteSuccess, | ||||
|         Rpc, | ||||
|         ExtraActions, | ||||
|         RawInfo, | ||||
|         RawName, | ||||
|         RawRead, | ||||
|         RawSuccess, | ||||
|     }; | ||||
| 
 | ||||
|     class Event { | ||||
|     public: | ||||
|         union { | ||||
|             int32_t menu_index; | ||||
|             int32_t signed_int; | ||||
|         } payload; | ||||
| 
 | ||||
|         EventType type; | ||||
| @ -79,8 +102,6 @@ public: | ||||
|     RecordController<Storage> storage; | ||||
|     RecordController<DialogsApp> dialogs; | ||||
| 
 | ||||
|     RfidWorker worker; | ||||
| 
 | ||||
|     TextStore text_store; | ||||
| 
 | ||||
|     string_t file_path; | ||||
| @ -90,15 +111,27 @@ public: | ||||
|     void run(void* args); | ||||
| 
 | ||||
|     static const char* app_folder; | ||||
|     static const char* app_sd_folder; | ||||
|     static const char* app_extension; | ||||
|     static const char* app_filetype; | ||||
| 
 | ||||
|     bool save_key(RfidKey* key); | ||||
|     bool save_key(); | ||||
|     bool load_key_from_file_select(bool need_restore); | ||||
|     bool delete_key(RfidKey* key); | ||||
|     bool delete_key(); | ||||
| 
 | ||||
|     bool load_key_data(string_t path, RfidKey* key, bool show_dialog); | ||||
|     bool save_key_data(string_t path, RfidKey* key); | ||||
|     bool load_key_data(string_t path, bool show_dialog); | ||||
|     bool save_key_data(string_t path); | ||||
| 
 | ||||
|     void make_app_folder(); | ||||
| 
 | ||||
|     ProtocolDict* dict; | ||||
|     LFRFIDWorker* lfworker; | ||||
|     string_t file_name; | ||||
|     ProtocolId protocol_id; | ||||
|     LFRFIDWorkerReadType read_type; | ||||
| 
 | ||||
|     uint8_t* old_key_data; | ||||
|     uint8_t* new_key_data; | ||||
| 
 | ||||
|     string_t raw_file_name; | ||||
| }; | ||||
|  | ||||
							
								
								
									
										575
									
								
								applications/lfrfid/lfrfid_cli.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										575
									
								
								applications/lfrfid/lfrfid_cli.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,575 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <stdarg.h> | ||||
| #include <cli/cli.h> | ||||
| #include <lib/toolbox/args.h> | ||||
| #include <lib/lfrfid/lfrfid_worker.h> | ||||
| #include <storage/storage.h> | ||||
| #include <toolbox/stream/file_stream.h> | ||||
| 
 | ||||
| #include <toolbox/varint.h> | ||||
| 
 | ||||
| #include <toolbox/protocols/protocol_dict.h> | ||||
| #include <lfrfid/protocols/lfrfid_protocols.h> | ||||
| #include <lfrfid/lfrfid_raw_file.h> | ||||
| #include <toolbox/pulse_protocols/pulse_glue.h> | ||||
| 
 | ||||
| static void lfrfid_cli(Cli* cli, string_t args, void* context); | ||||
| 
 | ||||
| // app cli function
 | ||||
| void lfrfid_on_system_start() { | ||||
|     Cli* cli = furi_record_open(RECORD_CLI); | ||||
|     cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL); | ||||
|     furi_record_close(RECORD_CLI); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_print_usage() { | ||||
|     printf("Usage:\r\n"); | ||||
|     printf("rfid read <optional: normal | indala>\r\n"); | ||||
|     printf("rfid <write | emulate> <key_type> <key_data>\r\n"); | ||||
|     printf("rfid raw_read <ask | psk> <filename>\r\n"); | ||||
|     printf("rfid raw_emulate <filename>\r\n"); | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     ProtocolId protocol; | ||||
|     FuriEventFlag* event; | ||||
| } LFRFIDCliReadContext; | ||||
| 
 | ||||
| static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId proto, void* ctx) { | ||||
|     furi_assert(ctx); | ||||
|     LFRFIDCliReadContext* context = ctx; | ||||
|     if(result == LFRFIDWorkerReadDone) { | ||||
|         context->protocol = proto; | ||||
|         FURI_SW_MEMBARRIER(); | ||||
|     } | ||||
|     furi_event_flag_set(context->event, 1 << result); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_read(Cli* cli, string_t args) { | ||||
|     string_t type_string; | ||||
|     string_init(type_string); | ||||
|     LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; | ||||
| 
 | ||||
|     if(args_read_string_and_trim(args, type_string)) { | ||||
|         if(string_cmp_str(type_string, "normal") == 0 || string_cmp_str(type_string, "ask") == 0) { | ||||
|             // ask
 | ||||
|             type = LFRFIDWorkerReadTypeASKOnly; | ||||
|         } else if( | ||||
|             string_cmp_str(type_string, "indala") == 0 || | ||||
|             string_cmp_str(type_string, "psk") == 0) { | ||||
|             // psk
 | ||||
|             type = LFRFIDWorkerReadTypePSKOnly; | ||||
|         } else { | ||||
|             lfrfid_cli_print_usage(); | ||||
|             string_clear(type_string); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     string_clear(type_string); | ||||
| 
 | ||||
|     ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); | ||||
|     LFRFIDWorker* worker = lfrfid_worker_alloc(dict); | ||||
|     LFRFIDCliReadContext context; | ||||
|     context.protocol = PROTOCOL_NO; | ||||
|     context.event = furi_event_flag_alloc(); | ||||
| 
 | ||||
|     lfrfid_worker_start_thread(worker); | ||||
| 
 | ||||
|     printf("Reading RFID...\r\nPress Ctrl+C to abort\r\n"); | ||||
| 
 | ||||
|     const uint32_t available_flags = (1 << LFRFIDWorkerReadDone); | ||||
| 
 | ||||
|     lfrfid_worker_read_start(worker, type, lfrfid_cli_read_callback, &context); | ||||
| 
 | ||||
|     while(true) { | ||||
|         uint32_t flags = | ||||
|             furi_event_flag_wait(context.event, available_flags, FuriFlagWaitAny, 100); | ||||
| 
 | ||||
|         if(flags != FuriFlagErrorTimeout) { | ||||
|             if(FURI_BIT(flags, LFRFIDWorkerReadDone)) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(cli_cmd_interrupt_received(cli)) break; | ||||
|     } | ||||
| 
 | ||||
|     lfrfid_worker_stop(worker); | ||||
|     lfrfid_worker_stop_thread(worker); | ||||
|     lfrfid_worker_free(worker); | ||||
| 
 | ||||
|     if(context.protocol != PROTOCOL_NO) { | ||||
|         printf("%s ", protocol_dict_get_name(dict, context.protocol)); | ||||
| 
 | ||||
|         size_t size = protocol_dict_get_data_size(dict, context.protocol); | ||||
|         uint8_t* data = malloc(size); | ||||
|         protocol_dict_get_data(dict, context.protocol, data, size); | ||||
|         for(size_t i = 0; i < size; i++) { | ||||
|             printf("%02X", data[i]); | ||||
|         } | ||||
|         printf("\r\n"); | ||||
|         free(data); | ||||
| 
 | ||||
|         string_t info; | ||||
|         string_init(info); | ||||
|         protocol_dict_render_data(dict, info, context.protocol); | ||||
|         if(string_size(info) > 0) { | ||||
|             printf("%s\r\n", string_get_cstr(info)); | ||||
|         } | ||||
|         string_clear(info); | ||||
|     } | ||||
| 
 | ||||
|     printf("Reading stopped\r\n"); | ||||
|     protocol_dict_free(dict); | ||||
| 
 | ||||
|     furi_event_flag_free(context.event); | ||||
| } | ||||
| 
 | ||||
| static bool lfrfid_cli_parse_args(string_t args, ProtocolDict* dict, ProtocolId* protocol) { | ||||
|     bool result = false; | ||||
|     string_t protocol_name, data_text; | ||||
|     string_init(protocol_name); | ||||
|     string_init(data_text); | ||||
|     size_t data_size = protocol_dict_get_max_data_size(dict); | ||||
|     uint8_t* data = malloc(data_size); | ||||
| 
 | ||||
|     do { | ||||
|         // load args
 | ||||
|         if(!args_read_string_and_trim(args, protocol_name) || | ||||
|            !args_read_string_and_trim(args, data_text)) { | ||||
|             lfrfid_cli_print_usage(); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         // check protocol arg
 | ||||
|         *protocol = protocol_dict_get_protocol_by_name(dict, string_get_cstr(protocol_name)); | ||||
|         if(*protocol == PROTOCOL_NO) { | ||||
|             printf( | ||||
|                 "Unknown protocol: %s\r\n" | ||||
|                 "Available protocols:\r\n", | ||||
|                 string_get_cstr(protocol_name)); | ||||
| 
 | ||||
|             for(ProtocolId i = 0; i < LFRFIDProtocolMax; i++) { | ||||
|                 printf( | ||||
|                     "\t%s, %d bytes long\r\n", | ||||
|                     protocol_dict_get_name(dict, i), | ||||
|                     protocol_dict_get_data_size(dict, i)); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         data_size = protocol_dict_get_data_size(dict, *protocol); | ||||
| 
 | ||||
|         // check data arg
 | ||||
|         if(!args_read_hex_bytes(data_text, data, data_size)) { | ||||
|             printf( | ||||
|                 "%s data needs to be %d bytes long\r\n", | ||||
|                 protocol_dict_get_name(dict, *protocol), | ||||
|                 data_size); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         // load data to protocol
 | ||||
|         protocol_dict_set_data(dict, *protocol, data, data_size); | ||||
| 
 | ||||
|         result = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     free(data); | ||||
|     string_clear(protocol_name); | ||||
|     string_clear(data_text); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_write_callback(LFRFIDWorkerWriteResult result, void* ctx) { | ||||
|     furi_assert(ctx); | ||||
|     FuriEventFlag* events = ctx; | ||||
|     furi_event_flag_set(events, 1 << result); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_write(Cli* cli, string_t args) { | ||||
|     ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); | ||||
|     ProtocolId protocol; | ||||
| 
 | ||||
|     if(!lfrfid_cli_parse_args(args, dict, &protocol)) { | ||||
|         protocol_dict_free(dict); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     LFRFIDWorker* worker = lfrfid_worker_alloc(dict); | ||||
|     FuriEventFlag* event = furi_event_flag_alloc(); | ||||
| 
 | ||||
|     lfrfid_worker_start_thread(worker); | ||||
|     lfrfid_worker_write_start(worker, protocol, lfrfid_cli_write_callback, event); | ||||
| 
 | ||||
|     printf("Writing RFID...\r\nPress Ctrl+C to abort\r\n"); | ||||
|     const uint32_t available_flags = (1 << LFRFIDWorkerWriteOK) | | ||||
|                                      (1 << LFRFIDWorkerWriteProtocolCannotBeWritten) | | ||||
|                                      (1 << LFRFIDWorkerWriteFobCannotBeWritten); | ||||
| 
 | ||||
|     while(!cli_cmd_interrupt_received(cli)) { | ||||
|         uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); | ||||
|         if(flags != FuriFlagErrorTimeout) { | ||||
|             if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) { | ||||
|                 printf("Written!\r\n"); | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             if(FURI_BIT(flags, LFRFIDWorkerWriteProtocolCannotBeWritten)) { | ||||
|                 printf("This protocol cannot be written.\r\n"); | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             if(FURI_BIT(flags, LFRFIDWorkerWriteFobCannotBeWritten)) { | ||||
|                 printf("Seems this fob cannot be written.\r\n"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     printf("Writing stopped\r\n"); | ||||
| 
 | ||||
|     lfrfid_worker_stop(worker); | ||||
|     lfrfid_worker_stop_thread(worker); | ||||
|     lfrfid_worker_free(worker); | ||||
|     protocol_dict_free(dict); | ||||
|     furi_event_flag_free(event); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_emulate(Cli* cli, string_t args) { | ||||
|     ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); | ||||
|     ProtocolId protocol; | ||||
| 
 | ||||
|     if(!lfrfid_cli_parse_args(args, dict, &protocol)) { | ||||
|         protocol_dict_free(dict); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     LFRFIDWorker* worker = lfrfid_worker_alloc(dict); | ||||
| 
 | ||||
|     lfrfid_worker_start_thread(worker); | ||||
|     lfrfid_worker_emulate_start(worker, protocol); | ||||
| 
 | ||||
|     printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n"); | ||||
|     while(!cli_cmd_interrupt_received(cli)) { | ||||
|         furi_delay_ms(100); | ||||
|     } | ||||
|     printf("Emulation stopped\r\n"); | ||||
| 
 | ||||
|     lfrfid_worker_stop(worker); | ||||
|     lfrfid_worker_stop_thread(worker); | ||||
|     lfrfid_worker_free(worker); | ||||
|     protocol_dict_free(dict); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_raw_analyze(Cli* cli, string_t args) { | ||||
|     UNUSED(cli); | ||||
|     string_t filepath, info_string; | ||||
|     string_init(filepath); | ||||
|     string_init(info_string); | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
|     LFRFIDRawFile* file = lfrfid_raw_file_alloc(storage); | ||||
| 
 | ||||
|     do { | ||||
|         float frequency = 0; | ||||
|         float duty_cycle = 0; | ||||
| 
 | ||||
|         if(!args_read_probably_quoted_string_and_trim(args, filepath)) { | ||||
|             lfrfid_cli_print_usage(); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!lfrfid_raw_file_open_read(file, string_get_cstr(filepath))) { | ||||
|             printf("Failed to open file\r\n"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!lfrfid_raw_file_read_header(file, &frequency, &duty_cycle)) { | ||||
|             printf("Invalid header\r\n"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         bool file_end = false; | ||||
|         uint32_t total_warns = 0; | ||||
|         uint32_t total_duration = 0; | ||||
|         uint32_t total_pulse = 0; | ||||
|         ProtocolId total_protocol = PROTOCOL_NO; | ||||
| 
 | ||||
|         ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); | ||||
|         protocol_dict_decoders_start(dict); | ||||
| 
 | ||||
|         while(!file_end) { | ||||
|             uint32_t pulse = 0; | ||||
|             uint32_t duration = 0; | ||||
|             if(lfrfid_raw_file_read_pair(file, &duration, &pulse, &file_end)) { | ||||
|                 bool warn = false; | ||||
| 
 | ||||
|                 if(pulse > duration || pulse <= 0 || duration <= 0) { | ||||
|                     total_warns += 1; | ||||
|                     warn = true; | ||||
|                 } | ||||
| 
 | ||||
|                 string_printf(info_string, "[%ld %ld]", pulse, duration); | ||||
|                 printf("%-16s", string_get_cstr(info_string)); | ||||
|                 string_printf(info_string, "[%ld %ld]", pulse, duration - pulse); | ||||
|                 printf("%-16s", string_get_cstr(info_string)); | ||||
| 
 | ||||
|                 if(warn) { | ||||
|                     printf(" <<----"); | ||||
|                 } | ||||
| 
 | ||||
|                 if(total_protocol == PROTOCOL_NO) { | ||||
|                     total_protocol = protocol_dict_decoders_feed(dict, true, pulse); | ||||
|                     if(total_protocol == PROTOCOL_NO) { | ||||
|                         total_protocol = | ||||
|                             protocol_dict_decoders_feed(dict, false, duration - pulse); | ||||
|                     } | ||||
| 
 | ||||
|                     if(total_protocol != PROTOCOL_NO) { | ||||
|                         printf(" <FOUND %s>", protocol_dict_get_name(dict, total_protocol)); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 printf("\r\n"); | ||||
| 
 | ||||
|                 total_pulse += pulse; | ||||
|                 total_duration += duration; | ||||
| 
 | ||||
|                 if(total_protocol != PROTOCOL_NO) { | ||||
|                     break; | ||||
|                 } | ||||
|             } else { | ||||
|                 printf("Failed to read pair\r\n"); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         printf("   Frequency: %f\r\n", (double)frequency); | ||||
|         printf("  Duty Cycle: %f\r\n", (double)duty_cycle); | ||||
|         printf("       Warns: %ld\r\n", total_warns); | ||||
|         printf("   Pulse sum: %ld\r\n", total_pulse); | ||||
|         printf("Duration sum: %ld\r\n", total_duration); | ||||
|         printf("     Average: %f\r\n", (double)((float)total_pulse / (float)total_duration)); | ||||
|         printf("    Protocol: "); | ||||
| 
 | ||||
|         if(total_protocol != PROTOCOL_NO) { | ||||
|             size_t data_size = protocol_dict_get_data_size(dict, total_protocol); | ||||
|             uint8_t* data = malloc(data_size); | ||||
|             protocol_dict_get_data(dict, total_protocol, data, data_size); | ||||
| 
 | ||||
|             printf("%s [", protocol_dict_get_name(dict, total_protocol)); | ||||
|             for(size_t i = 0; i < data_size; i++) { | ||||
|                 printf("%02X", data[i]); | ||||
|                 if(i < data_size - 1) { | ||||
|                     printf(" "); | ||||
|                 } | ||||
|             } | ||||
|             printf("]\r\n"); | ||||
| 
 | ||||
|             protocol_dict_render_data(dict, info_string, total_protocol); | ||||
|             printf("%s\r\n", string_get_cstr(info_string)); | ||||
| 
 | ||||
|             free(data); | ||||
|         } else { | ||||
|             printf("not found\r\n"); | ||||
|         } | ||||
| 
 | ||||
|         protocol_dict_free(dict); | ||||
|     } while(false); | ||||
| 
 | ||||
|     string_clear(filepath); | ||||
|     string_clear(info_string); | ||||
|     lfrfid_raw_file_free(file); | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_raw_read_callback(LFRFIDWorkerReadRawResult result, void* context) { | ||||
|     furi_assert(context); | ||||
|     FuriEventFlag* event = context; | ||||
|     furi_event_flag_set(event, 1 << result); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_raw_read(Cli* cli, string_t args) { | ||||
|     UNUSED(cli); | ||||
| 
 | ||||
|     string_t filepath, type_string; | ||||
|     string_init(filepath); | ||||
|     string_init(type_string); | ||||
|     LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto; | ||||
| 
 | ||||
|     do { | ||||
|         if(args_read_string_and_trim(args, type_string)) { | ||||
|             if(string_cmp_str(type_string, "normal") == 0 || | ||||
|                string_cmp_str(type_string, "ask") == 0) { | ||||
|                 // ask
 | ||||
|                 type = LFRFIDWorkerReadTypeASKOnly; | ||||
|             } else if( | ||||
|                 string_cmp_str(type_string, "indala") == 0 || | ||||
|                 string_cmp_str(type_string, "psk") == 0) { | ||||
|                 // psk
 | ||||
|                 type = LFRFIDWorkerReadTypePSKOnly; | ||||
|             } else { | ||||
|                 lfrfid_cli_print_usage(); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(!args_read_probably_quoted_string_and_trim(args, filepath)) { | ||||
|             lfrfid_cli_print_usage(); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); | ||||
|         LFRFIDWorker* worker = lfrfid_worker_alloc(dict); | ||||
|         FuriEventFlag* event = furi_event_flag_alloc(); | ||||
| 
 | ||||
|         lfrfid_worker_start_thread(worker); | ||||
| 
 | ||||
|         bool overrun = false; | ||||
| 
 | ||||
|         const uint32_t available_flags = (1 << LFRFIDWorkerReadRawFileError) | | ||||
|                                          (1 << LFRFIDWorkerReadRawOverrun); | ||||
| 
 | ||||
|         lfrfid_worker_read_raw_start( | ||||
|             worker, string_get_cstr(filepath), type, lfrfid_cli_raw_read_callback, event); | ||||
|         while(true) { | ||||
|             uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); | ||||
| 
 | ||||
|             if(flags != FuriFlagErrorTimeout) { | ||||
|                 if(FURI_BIT(flags, LFRFIDWorkerReadRawFileError)) { | ||||
|                     printf("File is not RFID raw file\r\n"); | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 if(FURI_BIT(flags, LFRFIDWorkerReadRawOverrun)) { | ||||
|                     if(!overrun) { | ||||
|                         printf("Overrun\r\n"); | ||||
|                         overrun = true; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(cli_cmd_interrupt_received(cli)) break; | ||||
|         } | ||||
| 
 | ||||
|         if(overrun) { | ||||
|             printf("An overrun occurred during read\r\n"); | ||||
|         } | ||||
| 
 | ||||
|         lfrfid_worker_stop(worker); | ||||
| 
 | ||||
|         lfrfid_worker_stop_thread(worker); | ||||
|         lfrfid_worker_free(worker); | ||||
|         protocol_dict_free(dict); | ||||
| 
 | ||||
|         furi_event_flag_free(event); | ||||
| 
 | ||||
|     } while(false); | ||||
| 
 | ||||
|     string_clear(filepath); | ||||
|     string_clear(type_string); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result, void* context) { | ||||
|     furi_assert(context); | ||||
|     FuriEventFlag* event = context; | ||||
|     furi_event_flag_set(event, 1 << result); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_raw_emulate(Cli* cli, string_t args) { | ||||
|     UNUSED(cli); | ||||
| 
 | ||||
|     string_t filepath; | ||||
|     string_init(filepath); | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
| 
 | ||||
|     do { | ||||
|         if(!args_read_probably_quoted_string_and_trim(args, filepath)) { | ||||
|             lfrfid_cli_print_usage(); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!storage_file_exists(storage, string_get_cstr(filepath))) { | ||||
|             printf("File not found: \"%s\"\r\n", string_get_cstr(filepath)); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); | ||||
|         LFRFIDWorker* worker = lfrfid_worker_alloc(dict); | ||||
|         FuriEventFlag* event = furi_event_flag_alloc(); | ||||
| 
 | ||||
|         lfrfid_worker_start_thread(worker); | ||||
| 
 | ||||
|         bool overrun = false; | ||||
| 
 | ||||
|         const uint32_t available_flags = (1 << LFRFIDWorkerEmulateRawFileError) | | ||||
|                                          (1 << LFRFIDWorkerEmulateRawOverrun); | ||||
| 
 | ||||
|         lfrfid_worker_emulate_raw_start( | ||||
|             worker, string_get_cstr(filepath), lfrfid_cli_raw_emulate_callback, event); | ||||
|         while(true) { | ||||
|             uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100); | ||||
| 
 | ||||
|             if(flags != FuriFlagErrorTimeout) { | ||||
|                 if(FURI_BIT(flags, LFRFIDWorkerEmulateRawFileError)) { | ||||
|                     printf("File is not RFID raw file\r\n"); | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 if(FURI_BIT(flags, LFRFIDWorkerEmulateRawOverrun)) { | ||||
|                     if(!overrun) { | ||||
|                         printf("Overrun\r\n"); | ||||
|                         overrun = true; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(cli_cmd_interrupt_received(cli)) break; | ||||
|         } | ||||
| 
 | ||||
|         if(overrun) { | ||||
|             printf("An overrun occurred during emulation\r\n"); | ||||
|         } | ||||
| 
 | ||||
|         lfrfid_worker_stop(worker); | ||||
| 
 | ||||
|         lfrfid_worker_stop_thread(worker); | ||||
|         lfrfid_worker_free(worker); | ||||
|         protocol_dict_free(dict); | ||||
| 
 | ||||
|         furi_event_flag_free(event); | ||||
| 
 | ||||
|     } while(false); | ||||
| 
 | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
|     string_clear(filepath); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli(Cli* cli, string_t args, void* context) { | ||||
|     UNUSED(context); | ||||
|     string_t cmd; | ||||
|     string_init(cmd); | ||||
| 
 | ||||
|     if(!args_read_string_and_trim(args, cmd)) { | ||||
|         string_clear(cmd); | ||||
|         lfrfid_cli_print_usage(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if(string_cmp_str(cmd, "read") == 0) { | ||||
|         lfrfid_cli_read(cli, args); | ||||
|     } else if(string_cmp_str(cmd, "write") == 0) { | ||||
|         lfrfid_cli_write(cli, args); | ||||
|     } else if(string_cmp_str(cmd, "emulate") == 0) { | ||||
|         lfrfid_cli_emulate(cli, args); | ||||
|     } else if(string_cmp_str(cmd, "raw_read") == 0) { | ||||
|         lfrfid_cli_raw_read(cli, args); | ||||
|     } else if(string_cmp_str(cmd, "raw_emulate") == 0) { | ||||
|         lfrfid_cli_raw_emulate(cli, args); | ||||
|     } else if(string_cmp_str(cmd, "raw_analyze") == 0) { | ||||
|         lfrfid_cli_raw_analyze(cli, args); | ||||
|     } else { | ||||
|         lfrfid_cli_print_usage(); | ||||
|     } | ||||
| 
 | ||||
|     string_clear(cmd); | ||||
| } | ||||
| @ -1,177 +0,0 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <stdarg.h> | ||||
| #include <cli/cli.h> | ||||
| #include <lib/toolbox/args.h> | ||||
| 
 | ||||
| #include "helpers/rfid_reader.h" | ||||
| #include "helpers/rfid_timer_emulator.h" | ||||
| 
 | ||||
| static void lfrfid_cli(Cli* cli, string_t args, void* context); | ||||
| 
 | ||||
| // app cli function
 | ||||
| extern "C" void lfrfid_on_system_start() { | ||||
| #ifdef SRV_CLI | ||||
|     Cli* cli = static_cast<Cli*>(furi_record_open("cli")); | ||||
|     cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL); | ||||
|     furi_record_close("cli"); | ||||
| #else | ||||
|     UNUSED(lfrfid_cli); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void lfrfid_cli_print_usage() { | ||||
|     printf("Usage:\r\n"); | ||||
|     printf("rfid read <optional: normal | indala>\r\n"); | ||||
|     printf("rfid <write | emulate> <key_type> <key_data>\r\n"); | ||||
|     printf("\t<key_type> choose from:\r\n"); | ||||
|     printf("\tEM4100, EM-Marin (5 bytes key_data)\r\n"); | ||||
|     printf("\tH10301, HID26 (3 bytes key_data)\r\n"); | ||||
|     printf("\tI40134, Indala (3 bytes key_data)\r\n"); | ||||
|     printf("\tIoProxXSF, IoProx (4 bytes key_data)\r\n"); | ||||
|     printf("\t<key_data> are hex-formatted\r\n"); | ||||
| }; | ||||
| 
 | ||||
| static bool lfrfid_cli_get_key_type(string_t data, LfrfidKeyType* type) { | ||||
|     bool result = false; | ||||
| 
 | ||||
|     if(string_cmp_str(data, "EM4100") == 0 || string_cmp_str(data, "EM-Marin") == 0) { | ||||
|         result = true; | ||||
|         *type = LfrfidKeyType::KeyEM4100; | ||||
|     } else if(string_cmp_str(data, "H10301") == 0 || string_cmp_str(data, "HID26") == 0) { | ||||
|         result = true; | ||||
|         *type = LfrfidKeyType::KeyH10301; | ||||
|     } else if(string_cmp_str(data, "I40134") == 0 || string_cmp_str(data, "Indala") == 0) { | ||||
|         result = true; | ||||
|         *type = LfrfidKeyType::KeyI40134; | ||||
|     } else if(string_cmp_str(data, "IoProxXSF") == 0 || string_cmp_str(data, "IoProx") == 0) { | ||||
|         result = true; | ||||
|         *type = LfrfidKeyType::KeyIoProxXSF; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_read(Cli* cli, string_t args) { | ||||
|     RfidReader reader; | ||||
|     string_t type_string; | ||||
|     string_init(type_string); | ||||
|     bool simple_mode = true; | ||||
|     LfrfidKeyType type; | ||||
|     RfidReader::Type reader_type = RfidReader::Type::Normal; | ||||
|     static const uint8_t data_size = LFRFID_KEY_SIZE; | ||||
|     uint8_t data[data_size] = {0}; | ||||
| 
 | ||||
|     if(args_read_string_and_trim(args, type_string)) { | ||||
|         simple_mode = false; | ||||
| 
 | ||||
|         if(string_cmp_str(type_string, "normal") == 0) { | ||||
|             reader_type = RfidReader::Type::Normal; | ||||
|         } else if(string_cmp_str(type_string, "indala") == 0) { | ||||
|             reader_type = RfidReader::Type::Indala; | ||||
|         } else { | ||||
|             lfrfid_cli_print_usage(); | ||||
|             string_clear(type_string); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(simple_mode) { | ||||
|         reader.start(); | ||||
|     } else { | ||||
|         reader.start_forced(reader_type); | ||||
|     } | ||||
| 
 | ||||
|     printf("Reading RFID...\r\nPress Ctrl+C to abort\r\n"); | ||||
|     while(!cli_cmd_interrupt_received(cli)) { | ||||
|         if(reader.read(&type, data, data_size, simple_mode)) { | ||||
|             printf("%s", lfrfid_key_get_type_string(type)); | ||||
|             printf(" "); | ||||
| 
 | ||||
|             for(uint8_t i = 0; i < lfrfid_key_get_type_data_count(type); i++) { | ||||
|                 printf("%02X", data[i]); | ||||
|             } | ||||
|             printf("\r\n"); | ||||
|             break; | ||||
|         } | ||||
|         furi_delay_ms(100); | ||||
|     } | ||||
| 
 | ||||
|     printf("Reading stopped\r\n"); | ||||
|     reader.stop(); | ||||
| 
 | ||||
|     string_clear(type_string); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_write(Cli* cli, string_t args) { | ||||
|     UNUSED(cli); | ||||
|     UNUSED(args); | ||||
|     // TODO implement rfid write
 | ||||
|     printf("Not Implemented :(\r\n"); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli_emulate(Cli* cli, string_t args) { | ||||
|     string_t data; | ||||
|     string_init(data); | ||||
|     RfidTimerEmulator emulator; | ||||
| 
 | ||||
|     static const uint8_t data_size = LFRFID_KEY_SIZE; | ||||
|     uint8_t key_data[data_size] = {0}; | ||||
|     uint8_t key_data_size = 0; | ||||
|     LfrfidKeyType type; | ||||
| 
 | ||||
|     if(!args_read_string_and_trim(args, data)) { | ||||
|         lfrfid_cli_print_usage(); | ||||
|         string_clear(data); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if(!lfrfid_cli_get_key_type(data, &type)) { | ||||
|         lfrfid_cli_print_usage(); | ||||
|         string_clear(data); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     key_data_size = lfrfid_key_get_type_data_count(type); | ||||
| 
 | ||||
|     if(!args_read_hex_bytes(args, key_data, key_data_size)) { | ||||
|         lfrfid_cli_print_usage(); | ||||
|         string_clear(data); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     emulator.start(type, key_data, key_data_size); | ||||
| 
 | ||||
|     printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n"); | ||||
|     while(!cli_cmd_interrupt_received(cli)) { | ||||
|         furi_delay_ms(100); | ||||
|     } | ||||
|     printf("Emulation stopped\r\n"); | ||||
|     emulator.stop(); | ||||
| 
 | ||||
|     string_clear(data); | ||||
| } | ||||
| 
 | ||||
| static void lfrfid_cli(Cli* cli, string_t args, void* context) { | ||||
|     UNUSED(context); | ||||
|     string_t cmd; | ||||
|     string_init(cmd); | ||||
| 
 | ||||
|     if(!args_read_string_and_trim(args, cmd)) { | ||||
|         string_clear(cmd); | ||||
|         lfrfid_cli_print_usage(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if(string_cmp_str(cmd, "read") == 0) { | ||||
|         lfrfid_cli_read(cli, args); | ||||
|     } else if(string_cmp_str(cmd, "write") == 0) { | ||||
|         lfrfid_cli_write(cli, args); | ||||
|     } else if(string_cmp_str(cmd, "emulate") == 0) { | ||||
|         lfrfid_cli_emulate(cli, args); | ||||
|     } else { | ||||
|         lfrfid_cli_print_usage(); | ||||
|     } | ||||
| 
 | ||||
|     string_clear(cmd); | ||||
| } | ||||
| @ -5,7 +5,6 @@ | ||||
| 
 | ||||
| void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore */) { | ||||
|     string_init(string_data); | ||||
|     string_init(string_decrypted); | ||||
|     string_init(string_header); | ||||
| 
 | ||||
|     auto container = app->view_controller.get<ContainerVM>(); | ||||
| @ -21,49 +20,26 @@ void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore | ||||
|     auto line_1 = container->add<StringElement>(); | ||||
|     auto line_2 = container->add<StringElement>(); | ||||
|     auto line_3 = container->add<StringElement>(); | ||||
|     auto line_4 = container->add<StringElement>(); | ||||
| 
 | ||||
|     RfidKey& key = app->worker.key; | ||||
|     const uint8_t* data = key.get_data(); | ||||
| 
 | ||||
|     for(uint8_t i = 0; i < key.get_type_data_count(); i++) { | ||||
|     size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); | ||||
|     uint8_t* data = (uint8_t*)malloc(size); | ||||
|     protocol_dict_get_data(app->dict, app->protocol_id, data, size); | ||||
|     for(uint8_t i = 0; i < MIN(size, (size_t)8); i++) { | ||||
|         if(i != 0) { | ||||
|             string_cat_printf(string_data, " "); | ||||
|         } | ||||
| 
 | ||||
|         string_cat_printf(string_data, "%02X", data[i]); | ||||
|     } | ||||
|     free(data); | ||||
| 
 | ||||
|     string_printf(string_header, "Delete %s?", key.get_name()); | ||||
|     string_printf(string_header, "Delete %s?", string_get_cstr(app->file_name)); | ||||
|     line_1->set_text( | ||||
|         string_get_cstr(string_header), 64, 19, 128 - 2, AlignCenter, AlignBottom, FontPrimary); | ||||
|         string_get_cstr(string_header), 64, 0, 128 - 2, AlignCenter, AlignTop, FontPrimary); | ||||
|     line_2->set_text( | ||||
|         string_get_cstr(string_data), 64, 29, 0, AlignCenter, AlignBottom, FontSecondary); | ||||
| 
 | ||||
|     switch(key.get_type()) { | ||||
|     case LfrfidKeyType::KeyEM4100: | ||||
|         string_printf( | ||||
|             string_decrypted, "%03u,%05u", data[2], (uint16_t)((data[3] << 8) | (data[4]))); | ||||
| 
 | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyH10301: | ||||
|     case LfrfidKeyType::KeyI40134: | ||||
|         string_printf( | ||||
|             string_decrypted, "FC: %u    ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2]))); | ||||
|         break; | ||||
|     case LfrfidKeyType::KeyIoProxXSF: | ||||
|         string_printf( | ||||
|             string_decrypted, | ||||
|             "FC: %u   VC: %u   ID: %u", | ||||
|             data[0], | ||||
|             data[1], | ||||
|             (uint16_t)((data[2] << 8) | (data[3]))); | ||||
|         break; | ||||
|     } | ||||
|         string_get_cstr(string_data), 64, 19, 0, AlignCenter, AlignTop, FontSecondary); | ||||
|     line_3->set_text( | ||||
|         string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); | ||||
| 
 | ||||
|     line_4->set_text( | ||||
|         lfrfid_key_get_type_string(key.get_type()), | ||||
|         protocol_dict_get_name(app->dict, app->protocol_id), | ||||
|         64, | ||||
|         49, | ||||
|         0, | ||||
| @ -78,7 +54,7 @@ bool LfRfidAppSceneDeleteConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* eve | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == LfRfidApp::EventType::Next) { | ||||
|         app->delete_key(&app->worker.key); | ||||
|         app->delete_key(); | ||||
|         app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::DeleteSuccess); | ||||
|         consumed = true; | ||||
|     } else if(event->type == LfRfidApp::EventType::Stay) { | ||||
| @ -94,7 +70,6 @@ bool LfRfidAppSceneDeleteConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* eve | ||||
| void LfRfidAppSceneDeleteConfirm::on_exit(LfRfidApp* app) { | ||||
|     app->view_controller.get<ContainerVM>()->clean(); | ||||
|     string_clear(string_data); | ||||
|     string_clear(string_decrypted); | ||||
|     string_clear(string_header); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -13,5 +13,4 @@ private: | ||||
| 
 | ||||
|     string_t string_header; | ||||
|     string_t string_data; | ||||
|     string_t string_decrypted; | ||||
| }; | ||||
|  | ||||
| @ -3,28 +3,21 @@ | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool /* need_restore */) { | ||||
|     string_init(data_string); | ||||
| 
 | ||||
|     DOLPHIN_DEED(DolphinDeedRfidEmulate); | ||||
|     const uint8_t* data = app->worker.key.get_data(); | ||||
| 
 | ||||
|     for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) { | ||||
|         string_cat_printf(data_string, "%02X", data[i]); | ||||
|     } | ||||
| 
 | ||||
|     auto popup = app->view_controller.get<PopupVM>(); | ||||
| 
 | ||||
|     popup->set_header("Emulating", 89, 30, AlignCenter, AlignTop); | ||||
|     if(strlen(app->worker.key.get_name())) { | ||||
|         popup->set_text(app->worker.key.get_name(), 89, 43, AlignCenter, AlignTop); | ||||
|     if(string_size(app->file_name)) { | ||||
|         popup->set_text(string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); | ||||
|     } else { | ||||
|         popup->set_text(string_get_cstr(data_string), 89, 43, AlignCenter, AlignTop); | ||||
|         popup->set_text( | ||||
|             protocol_dict_get_name(app->dict, app->protocol_id), 89, 43, AlignCenter, AlignTop); | ||||
|     } | ||||
|     popup->set_icon(0, 3, &I_RFIDDolphinSend_97x61); | ||||
| 
 | ||||
|     app->view_controller.switch_to<PopupVM>(); | ||||
|     app->worker.start_emulate(); | ||||
| 
 | ||||
|     lfrfid_worker_start_thread(app->lfworker); | ||||
|     lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id); | ||||
|     notification_message(app->notification, &sequence_blink_start_magenta); | ||||
| } | ||||
| 
 | ||||
| @ -37,7 +30,7 @@ bool LfRfidAppSceneEmulate::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
| 
 | ||||
| void LfRfidAppSceneEmulate::on_exit(LfRfidApp* app) { | ||||
|     app->view_controller.get<PopupVM>()->clean(); | ||||
|     app->worker.stop_emulate(); | ||||
|     string_clear(data_string); | ||||
|     lfrfid_worker_stop(app->lfworker); | ||||
|     lfrfid_worker_stop_thread(app->lfworker); | ||||
|     notification_message(app->notification, &sequence_blink_stop); | ||||
| } | ||||
|  | ||||
| @ -6,7 +6,4 @@ public: | ||||
|     void on_enter(LfRfidApp* app, bool need_restore) final; | ||||
|     bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; | ||||
|     void on_exit(LfRfidApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     string_t data_string; | ||||
| }; | ||||
|  | ||||
							
								
								
									
										63
									
								
								applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| #include "lfrfid_app_scene_extra_actions.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubmenuASK, | ||||
|     SubmenuPSK, | ||||
|     SubmenuRAW, | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| void LfRfidAppSceneExtraActions::on_enter(LfRfidApp* app, bool need_restore) { | ||||
|     auto submenu = app->view_controller.get<SubmenuVM>(); | ||||
| 
 | ||||
|     submenu->add_item("Read ASK (Animal, Ordinary Card)", SubmenuASK, submenu_callback, app); | ||||
|     submenu->add_item("Read PSK (Indala)", SubmenuPSK, submenu_callback, app); | ||||
| 
 | ||||
|     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { | ||||
|         submenu->add_item("Read RAW RFID data", SubmenuRAW, submenu_callback, app); | ||||
|     } | ||||
| 
 | ||||
|     if(need_restore) { | ||||
|         submenu->set_selected_item(submenu_item_selected); | ||||
|     } | ||||
| 
 | ||||
|     app->view_controller.switch_to<SubmenuVM>(); | ||||
| } | ||||
| 
 | ||||
| bool LfRfidAppSceneExtraActions::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == LfRfidApp::EventType::MenuSelected) { | ||||
|         submenu_item_selected = event->payload.signed_int; | ||||
|         switch(event->payload.signed_int) { | ||||
|         case SubmenuASK: | ||||
|             app->read_type = LFRFIDWorkerReadTypeASKOnly; | ||||
|             app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read); | ||||
|             break; | ||||
|         case SubmenuPSK: | ||||
|             app->read_type = LFRFIDWorkerReadTypePSKOnly; | ||||
|             app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read); | ||||
|             break; | ||||
|         case SubmenuRAW: | ||||
|             app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawName); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void LfRfidAppSceneExtraActions::on_exit(LfRfidApp* app) { | ||||
|     app->view_controller.get<SubmenuVM>()->clean(); | ||||
| } | ||||
| 
 | ||||
| void LfRfidAppSceneExtraActions::submenu_callback(void* context, uint32_t index) { | ||||
|     LfRfidApp* app = static_cast<LfRfidApp*>(context); | ||||
|     LfRfidApp::Event event; | ||||
| 
 | ||||
|     event.type = LfRfidApp::EventType::MenuSelected; | ||||
|     event.payload.signed_int = index; | ||||
| 
 | ||||
|     app->view_controller.send_event(&event); | ||||
| } | ||||
							
								
								
									
										13
									
								
								applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| #pragma once | ||||
| #include "../lfrfid_app.h" | ||||
| 
 | ||||
| class LfRfidAppSceneExtraActions : public GenericScene<LfRfidApp> { | ||||
| public: | ||||
|     void on_enter(LfRfidApp* app, bool need_restore) final; | ||||
|     bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; | ||||
|     void on_exit(LfRfidApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     static void submenu_callback(void* context, uint32_t index); | ||||
|     uint32_t submenu_item_selected = 0; | ||||
| }; | ||||
							
								
								
									
										77
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | ||||
| #include "lfrfid_app_scene_raw_info.h" | ||||
| #include "../view/elements/button_element.h" | ||||
| #include "../view/elements/icon_element.h" | ||||
| #include "../view/elements/string_element.h" | ||||
| 
 | ||||
| static void ok_callback(void* context) { | ||||
|     LfRfidApp* app = static_cast<LfRfidApp*>(context); | ||||
|     LfRfidApp::Event event; | ||||
|     event.type = LfRfidApp::EventType::Next; | ||||
|     app->view_controller.send_event(&event); | ||||
| } | ||||
| 
 | ||||
| static void back_callback(void* context) { | ||||
|     LfRfidApp* app = static_cast<LfRfidApp*>(context); | ||||
|     LfRfidApp::Event event; | ||||
|     event.type = LfRfidApp::EventType::Back; | ||||
|     app->view_controller.send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void LfRfidAppSceneRawInfo::on_enter(LfRfidApp* app, bool /* need_restore */) { | ||||
|     string_init(string_info); | ||||
| 
 | ||||
|     auto container = app->view_controller.get<ContainerVM>(); | ||||
| 
 | ||||
|     bool sd_exist = storage_sd_status(app->storage) == FSE_OK; | ||||
|     if(!sd_exist) { | ||||
|         auto icon = container->add<IconElement>(); | ||||
|         icon->set_icon(0, 0, &I_SDQuestion_35x43); | ||||
|         auto line = container->add<StringElement>(); | ||||
|         line->set_text( | ||||
|             "No SD card found.\nThis function will not\nwork without\nSD card.", | ||||
|             81, | ||||
|             4, | ||||
|             0, | ||||
|             AlignCenter, | ||||
|             AlignTop, | ||||
|             FontSecondary); | ||||
| 
 | ||||
|         auto button = container->add<ButtonElement>(); | ||||
|         button->set_type(ButtonElement::Type::Left, "Back"); | ||||
|         button->set_callback(app, back_callback); | ||||
|     } else { | ||||
|         string_printf( | ||||
|             string_info, | ||||
|             "RAW RFID data reader\r\n" | ||||
|             "1) Put the Flipper on your card\r\n" | ||||
|             "2) Press OK\r\n" | ||||
|             "3) Wait until data is read"); | ||||
| 
 | ||||
|         auto line = container->add<StringElement>(); | ||||
|         line->set_text(string_get_cstr(string_info), 0, 1, 0, AlignLeft, AlignTop, FontSecondary); | ||||
| 
 | ||||
|         auto button = container->add<ButtonElement>(); | ||||
|         button->set_type(ButtonElement::Type::Center, "OK"); | ||||
|         button->set_callback(app, ok_callback); | ||||
|     } | ||||
| 
 | ||||
|     app->view_controller.switch_to<ContainerVM>(); | ||||
| } | ||||
| 
 | ||||
| bool LfRfidAppSceneRawInfo::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
|     bool consumed = false; | ||||
|     if(event->type == LfRfidApp::EventType::Next) { | ||||
|         app->scene_controller.switch_to_scene({LfRfidApp::SceneType::RawRead}); | ||||
|         consumed = true; | ||||
|     } else if(event->type == LfRfidApp::EventType::Back) { | ||||
|         app->scene_controller.search_and_switch_to_previous_scene( | ||||
|             {LfRfidApp::SceneType::ExtraActions}); | ||||
|         consumed = true; | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void LfRfidAppSceneRawInfo::on_exit(LfRfidApp* app) { | ||||
|     app->view_controller.get<ContainerVM>()->clean(); | ||||
|     string_clear(string_info); | ||||
| } | ||||
							
								
								
									
										12
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_info.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_info.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| #pragma once | ||||
| #include "../lfrfid_app.h" | ||||
| 
 | ||||
| class LfRfidAppSceneRawInfo : public GenericScene<LfRfidApp> { | ||||
| public: | ||||
|     void on_enter(LfRfidApp* app, bool need_restore) final; | ||||
|     bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; | ||||
|     void on_exit(LfRfidApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     string_t string_info; | ||||
| }; | ||||
							
								
								
									
										46
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| 
 | ||||
| #include "lfrfid_app_scene_raw_name.h" | ||||
| #include "m-string.h" | ||||
| #include <lib/toolbox/random_name.h> | ||||
| #include <lib/toolbox/path.h> | ||||
| 
 | ||||
| void LfRfidAppSceneRawName::on_enter(LfRfidApp* app, bool /* need_restore */) { | ||||
|     const char* key_name = string_get_cstr(app->raw_file_name); | ||||
| 
 | ||||
|     bool key_name_empty = (string_size(app->raw_file_name) == 0); | ||||
|     if(key_name_empty) { | ||||
|         app->text_store.set("RfidRecord"); | ||||
|     } else { | ||||
|         app->text_store.set("%s", key_name); | ||||
|     } | ||||
| 
 | ||||
|     auto text_input = app->view_controller.get<TextInputVM>(); | ||||
|     text_input->set_header_text("Name the raw file"); | ||||
| 
 | ||||
|     text_input->set_result_callback( | ||||
|         save_callback, app, app->text_store.text, LFRFID_KEY_NAME_SIZE, key_name_empty); | ||||
| 
 | ||||
|     app->view_controller.switch_to<TextInputVM>(); | ||||
| } | ||||
| 
 | ||||
| bool LfRfidAppSceneRawName::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == LfRfidApp::EventType::Next) { | ||||
|         string_set_str(app->raw_file_name, app->text_store.text); | ||||
|         app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawInfo); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void LfRfidAppSceneRawName::on_exit(LfRfidApp* app) { | ||||
|     app->view_controller.get<TextInputVM>()->clean(); | ||||
| } | ||||
| 
 | ||||
| void LfRfidAppSceneRawName::save_callback(void* context) { | ||||
|     LfRfidApp* app = static_cast<LfRfidApp*>(context); | ||||
|     LfRfidApp::Event event; | ||||
|     event.type = LfRfidApp::EventType::Next; | ||||
|     app->view_controller.send_event(&event); | ||||
| } | ||||
							
								
								
									
										12
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_name.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_name.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| #pragma once | ||||
| #include "../lfrfid_app.h" | ||||
| 
 | ||||
| class LfRfidAppSceneRawName : public GenericScene<LfRfidApp> { | ||||
| public: | ||||
|     void on_enter(LfRfidApp* app, bool need_restore) final; | ||||
|     bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; | ||||
|     void on_exit(LfRfidApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     static void save_callback(void* context); | ||||
| }; | ||||
							
								
								
									
										107
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | ||||
| #include "lfrfid_app_scene_raw_read.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| #define RAW_READ_TIME 5000 | ||||
| 
 | ||||
| static void lfrfid_read_callback(LFRFIDWorkerReadRawResult result, void* ctx) { | ||||
|     LfRfidApp* app = static_cast<LfRfidApp*>(ctx); | ||||
|     LfRfidApp::Event event; | ||||
| 
 | ||||
|     switch(result) { | ||||
|     case LFRFIDWorkerReadRawFileError: | ||||
|         event.type = LfRfidApp::EventType::ReadEventError; | ||||
|         break; | ||||
|     case LFRFIDWorkerReadRawOverrun: | ||||
|         event.type = LfRfidApp::EventType::ReadEventOverrun; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     app->view_controller.send_event(&event); | ||||
| } | ||||
| 
 | ||||
| static void timer_callback(void* ctx) { | ||||
|     LfRfidApp* app = static_cast<LfRfidApp*>(ctx); | ||||
|     LfRfidApp::Event event; | ||||
|     event.type = LfRfidApp::EventType::ReadEventDone; | ||||
|     app->view_controller.send_event(&event); | ||||
| } | ||||
| 
 | ||||
| void LfRfidAppSceneRawRead::on_enter(LfRfidApp* app, bool /* need_restore */) { | ||||
|     string_init(string_file_name); | ||||
|     auto popup = app->view_controller.get<PopupVM>(); | ||||
|     popup->set_icon(0, 3, &I_RFIDDolphinReceive_97x61); | ||||
|     app->view_controller.switch_to<PopupVM>(); | ||||
|     lfrfid_worker_start_thread(app->lfworker); | ||||
|     app->make_app_folder(); | ||||
| 
 | ||||
|     timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, app); | ||||
|     furi_timer_start(timer, RAW_READ_TIME); | ||||
|     string_printf( | ||||
|         string_file_name, "%s/%s.ask.raw", app->app_sd_folder, string_get_cstr(app->raw_file_name)); | ||||
|     popup->set_header("Reading\nRAW RFID\nASK", 89, 30, AlignCenter, AlignTop); | ||||
|     lfrfid_worker_read_raw_start( | ||||
|         app->lfworker, | ||||
|         string_get_cstr(string_file_name), | ||||
|         LFRFIDWorkerReadTypeASKOnly, | ||||
|         lfrfid_read_callback, | ||||
|         app); | ||||
| 
 | ||||
|     notification_message(app->notification, &sequence_blink_start_cyan); | ||||
| 
 | ||||
|     is_psk = false; | ||||
|     error = false; | ||||
| } | ||||
| 
 | ||||
| bool LfRfidAppSceneRawRead::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
|     UNUSED(app); | ||||
|     bool consumed = true; | ||||
|     auto popup = app->view_controller.get<PopupVM>(); | ||||
| 
 | ||||
|     switch(event->type) { | ||||
|     case LfRfidApp::EventType::ReadEventError: | ||||
|         error = true; | ||||
|         popup->set_header("Reading\nRAW RFID\nFile error", 89, 30, AlignCenter, AlignTop); | ||||
|         notification_message(app->notification, &sequence_blink_start_red); | ||||
|         furi_timer_stop(timer); | ||||
|         break; | ||||
|     case LfRfidApp::EventType::ReadEventDone: | ||||
|         if(!error) { | ||||
|             if(is_psk) { | ||||
|                 notification_message(app->notification, &sequence_success); | ||||
|                 app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawSuccess); | ||||
|             } else { | ||||
|                 popup->set_header("Reading\nRAW RFID\nPSK", 89, 30, AlignCenter, AlignTop); | ||||
|                 notification_message(app->notification, &sequence_blink_start_yellow); | ||||
|                 lfrfid_worker_stop(app->lfworker); | ||||
|                 string_printf( | ||||
|                     string_file_name, | ||||
|                     "%s/%s.psk.raw", | ||||
|                     app->app_sd_folder, | ||||
|                     string_get_cstr(app->raw_file_name)); | ||||
|                 lfrfid_worker_read_raw_start( | ||||
|                     app->lfworker, | ||||
|                     string_get_cstr(string_file_name), | ||||
|                     LFRFIDWorkerReadTypePSKOnly, | ||||
|                     lfrfid_read_callback, | ||||
|                     app); | ||||
|                 furi_timer_start(timer, RAW_READ_TIME); | ||||
|                 is_psk = true; | ||||
|             } | ||||
|         } | ||||
|         break; | ||||
|     default: | ||||
|         consumed = false; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void LfRfidAppSceneRawRead::on_exit(LfRfidApp* app) { | ||||
|     notification_message(app->notification, &sequence_blink_stop); | ||||
|     app->view_controller.get<PopupVM>()->clean(); | ||||
|     lfrfid_worker_stop(app->lfworker); | ||||
|     lfrfid_worker_stop_thread(app->lfworker); | ||||
|     furi_timer_free(timer); | ||||
|     string_clear(string_file_name); | ||||
| } | ||||
							
								
								
									
										15
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_read.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_read.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| #pragma once | ||||
| #include "../lfrfid_app.h" | ||||
| 
 | ||||
| class LfRfidAppSceneRawRead : public GenericScene<LfRfidApp> { | ||||
| public: | ||||
|     void on_enter(LfRfidApp* app, bool need_restore) final; | ||||
|     bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; | ||||
|     void on_exit(LfRfidApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     string_t string_file_name; | ||||
|     FuriTimer* timer; | ||||
|     bool is_psk; | ||||
|     bool error; | ||||
| }; | ||||
							
								
								
									
										45
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_success.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								applications/lfrfid/scene/lfrfid_app_scene_raw_success.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| #include "lfrfid_app_scene_raw_success.h" | ||||
| #include "../view/elements/button_element.h" | ||||
| #include "../view/elements/icon_element.h" | ||||
| #include "../view/elements/string_element.h" | ||||
| 
 | ||||
| void LfRfidAppSceneRawSuccess::on_enter(LfRfidApp* app, bool /* need_restore */) { | ||||
|     string_init(string_info); | ||||
| 
 | ||||
|     string_printf(string_info, "RAW RFID read success!\r\n"); | ||||
|     string_cat_printf(string_info, "Now you can analyze files\r\n"); | ||||
|     string_cat_printf(string_info, "Or send them to developers"); | ||||
| 
 | ||||
|     auto container = app->view_controller.get<ContainerVM>(); | ||||
| 
 | ||||
|     auto line = container->add<StringElement>(); | ||||
|     line->set_text(string_get_cstr(string_info), 0, 1, 0, AlignLeft, AlignTop, FontSecondary); | ||||
| 
 | ||||
|     auto button = container->add<ButtonElement>(); | ||||
|     button->set_type(ButtonElement::Type::Center, "OK"); | ||||
|     button->set_callback(app, LfRfidAppSceneRawSuccess::ok_callback); | ||||
| 
 | ||||
|     app->view_controller.switch_to<ContainerVM>(); | ||||
| } | ||||
| 
 | ||||
| bool LfRfidAppSceneRawSuccess::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
|     bool consumed = false; | ||||
|     if(event->type == LfRfidApp::EventType::Next) { | ||||
|         app->scene_controller.search_and_switch_to_previous_scene( | ||||
|             {LfRfidApp::SceneType::ExtraActions}); | ||||
|         consumed = true; | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void LfRfidAppSceneRawSuccess::on_exit(LfRfidApp* app) { | ||||
|     app->view_controller.get<ContainerVM>()->clean(); | ||||
|     string_clear(string_info); | ||||
| } | ||||
| 
 | ||||
| void LfRfidAppSceneRawSuccess::ok_callback(void* context) { | ||||
|     LfRfidApp* app = static_cast<LfRfidApp*>(context); | ||||
|     LfRfidApp::Event event; | ||||
|     event.type = LfRfidApp::EventType::Next; | ||||
|     app->view_controller.send_event(&event); | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Aleksandr Kutuzov
						Aleksandr Kutuzov