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/gui/ @skotopes @DrZlo13 @hedger | ||||||
| /applications/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov | /applications/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov | ||||||
| /applications/infrared/ @skotopes @DrZlo13 @hedger @gsurkov | /applications/infrared/ @skotopes @DrZlo13 @hedger @gsurkov | ||||||
| /applications/infrared_monitor/ @skotopes @DrZlo13 @hedger @gsurkov |  | ||||||
| /applications/input/ @skotopes @DrZlo13 @hedger | /applications/input/ @skotopes @DrZlo13 @hedger | ||||||
| /applications/lfrfid/ @skotopes @DrZlo13 @hedger | /applications/lfrfid/ @skotopes @DrZlo13 @hedger | ||||||
| /applications/lfrfid_debug/ @skotopes @DrZlo13 @hedger | /applications/lfrfid_debug/ @skotopes @DrZlo13 @hedger | ||||||
| @ -45,12 +44,8 @@ | |||||||
| # Debug tools and plugins | # Debug tools and plugins | ||||||
| /debug/ @skotopes @DrZlo13 @hedger | /debug/ @skotopes @DrZlo13 @hedger | ||||||
| 
 | 
 | ||||||
| # Docker |  | ||||||
| /docker/ @skotopes @DrZlo13 @hedger @aprosvetova |  | ||||||
| /docker-compose.yml @skotopes @DrZlo13 @hedger @aprosvetova |  | ||||||
| 
 |  | ||||||
| # Documentation | # Documentation | ||||||
| /documentation/ @skotopes @DrZlo13 @hedger @aprosvetova | /documentation/ @skotopes @DrZlo13 @hedger @drunkbatya | ||||||
| 
 | 
 | ||||||
| # Firmware targets | # Firmware targets | ||||||
| /firmware/ @skotopes @DrZlo13 @hedger | /firmware/ @skotopes @DrZlo13 @hedger | ||||||
| @ -84,8 +79,5 @@ | |||||||
| /lib/u8g2/ @skotopes @DrZlo13 @hedger | /lib/u8g2/ @skotopes @DrZlo13 @hedger | ||||||
| /lib/update_util/ @skotopes @DrZlo13 @hedger | /lib/update_util/ @skotopes @DrZlo13 @hedger | ||||||
| 
 | 
 | ||||||
| # Make tools |  | ||||||
| /make/ @skotopes @DrZlo13 @hedger @aprosvetova |  | ||||||
| 
 |  | ||||||
| # Helper scripts | # Helper scripts | ||||||
| /scripts/ @skotopes @DrZlo13 @hedger | /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 |           FBT_TOOLCHAIN_PATH=/opt ./fbt copro_dist | ||||||
|           tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware |           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' |       - name: 'Upload artifacts to update server' | ||||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} |         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||||
|         run: | |         run: | | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								.github/workflows/check_submodules.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/check_submodules.yml
									
									
									
									
										vendored
									
									
								
							| @ -25,6 +25,7 @@ jobs: | |||||||
|         uses: actions/checkout@v2 |         uses: actions/checkout@v2 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|  |           ref: ${{ github.event.pull_request.head.sha }} | ||||||
| 
 | 
 | ||||||
|       - name: 'Check protobuf branch' |       - name: 'Check protobuf branch' | ||||||
|         run: | |         run: | | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							| @ -28,6 +28,7 @@ jobs: | |||||||
|         uses: actions/checkout@v2 |         uses: actions/checkout@v2 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|  |           ref: ${{ github.event.pull_request.head.sha }} | ||||||
| 
 | 
 | ||||||
|       - name: 'Check code formatting' |       - name: 'Check code formatting' | ||||||
|         id: syntax_check |         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 |         uses: actions/checkout@v2 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|  |           ref: ${{ github.event.pull_request.head.sha }} | ||||||
| 
 | 
 | ||||||
|       - name: 'Check code formatting' |       - name: 'Check code formatting' | ||||||
|         run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint_py |         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 output file | ||||||
| openocd.log | 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` | 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 | # Build on Linux/macOS | ||||||
| 
 | 
 | ||||||
| Check out `documentation/fbt.md` for details on building and flashing firmware.  | 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 | - `assets`          - Assets used by applications and services | ||||||
| - `furi`            - Furi Core: os level primitives and helpers | - `furi`            - Furi Core: os level primitives and helpers | ||||||
| - `debug`           - Debug tool: GDB-plugins, SVD-file and etc | - `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 | - `documentation`   - Documentation generation system configs and input files | ||||||
| - `firmware`        - Firmware source code | - `firmware`        - Firmware source code | ||||||
| - `lib`             - Our and 3rd party libraries, drivers and etc... | - `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) { |     if(app == ArchiveAppTypeU2f) { | ||||||
|         bool file_exists = false; |         bool file_exists = false; | ||||||
|         Storage* fs_api = furi_record_open(RECORD_STORAGE); |         Storage* storage = furi_record_open(RECORD_STORAGE); | ||||||
|         File* file = storage_file_alloc(fs_api); |  | ||||||
| 
 | 
 | ||||||
|         file_exists = |         if(storage_file_exists(storage, ANY_PATH("u2f/key.u2f"))) { | ||||||
|             storage_file_open(file, ANY_PATH("u2f/key.u2f"), FSAM_READ, FSOM_OPEN_EXISTING); |             file_exists = storage_file_exists(storage, ANY_PATH("u2f/cnt.u2f")); | ||||||
|         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); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         storage_file_free(file); |  | ||||||
|         furi_record_close(RECORD_STORAGE); |         furi_record_close(RECORD_STORAGE); | ||||||
| 
 |  | ||||||
|         return file_exists; |         return file_exists; | ||||||
|     } else { |     } else { | ||||||
|         return false; |         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); |     furi_assert(browser); | ||||||
| 
 |     if(!browser->worker_running) { | ||||||
|     file_browser_worker_set_callback_context(browser->worker, browser); |         browser->worker = file_browser_worker_alloc(path, filter_ext, skip_assets); | ||||||
|     file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb); |         file_browser_worker_set_callback_context(browser->worker, browser); | ||||||
|     file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb); |         file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb); | ||||||
|     file_browser_worker_set_item_callback(browser->worker, archive_list_item_cb); |         file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb); | ||||||
|     file_browser_worker_set_long_load_callback(browser->worker, archive_long_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) { | 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); |         tab = archive_get_tab(browser); | ||||||
|         if(archive_is_dir_exists(browser->path)) { |         if(archive_is_dir_exists(browser->path)) { | ||||||
|             bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; |             bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true; | ||||||
|             file_browser_worker_set_config( |             archive_file_browser_set_path( | ||||||
|                 browser->worker, browser->path, archive_get_tab_ext(tab), skip_assets); |                 browser, browser->path, archive_get_tab_ext(tab), skip_assets); | ||||||
|             tab_empty = false; // Empty check will be performed later
 |             tab_empty = false; // Empty check will be performed later
 | ||||||
|         } else { |         } else { | ||||||
|             tab_empty = true; |             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_enter_dir(ArchiveBrowserView* browser, string_t name); | ||||||
| void archive_leave_dir(ArchiveBrowserView* browser); | void archive_leave_dir(ArchiveBrowserView* browser); | ||||||
| void archive_refresh_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() { | static bool archive_favourites_rescan() { | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
|     Storage* fs_api = furi_record_open(RECORD_STORAGE); |     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||||
|     File* file = storage_file_alloc(fs_api); |     File* file = storage_file_alloc(storage); | ||||||
|     File* fav_item_file = storage_file_alloc(fs_api); |  | ||||||
| 
 | 
 | ||||||
|     bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); |     bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|     if(result) { |     if(result) { | ||||||
| @ -101,13 +100,8 @@ static bool archive_favourites_rescan() { | |||||||
|                     archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); |                     archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 bool file_exists = storage_file_open( |                 if(storage_file_exists(storage, string_get_cstr(buffer))) { | ||||||
|                     fav_item_file, string_get_cstr(buffer), FSAM_READ, FSOM_OPEN_EXISTING); |  | ||||||
|                 if(file_exists) { |  | ||||||
|                     storage_file_close(fav_item_file); |  | ||||||
|                     archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", 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); |     string_clear(buffer); | ||||||
| 
 | 
 | ||||||
|     storage_file_close(file); |     storage_file_close(file); | ||||||
|     storage_common_remove(fs_api, ARCHIVE_FAV_PATH); |     storage_common_remove(storage, ARCHIVE_FAV_PATH); | ||||||
|     storage_common_rename(fs_api, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); |     storage_common_rename(storage, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||||
|     storage_common_remove(fs_api, ARCHIVE_FAV_TEMP_PATH); |     storage_common_remove(storage, ARCHIVE_FAV_TEMP_PATH); | ||||||
| 
 | 
 | ||||||
|     storage_file_free(file); |     storage_file_free(file); | ||||||
|     storage_file_free(fav_item_file); |  | ||||||
|     furi_record_close(RECORD_STORAGE); |     furi_record_close(RECORD_STORAGE); | ||||||
| 
 | 
 | ||||||
|     return result; |     return result; | ||||||
| @ -131,9 +124,8 @@ bool archive_favorites_read(void* context) { | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     ArchiveBrowserView* browser = context; |     ArchiveBrowserView* browser = context; | ||||||
|     Storage* fs_api = furi_record_open(RECORD_STORAGE); |     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||||
|     File* file = storage_file_alloc(fs_api); |     File* file = storage_file_alloc(storage); | ||||||
|     File* fav_item_file = storage_file_alloc(fs_api); |  | ||||||
| 
 | 
 | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
| @ -163,16 +155,12 @@ bool archive_favorites_read(void* context) { | |||||||
|                     need_refresh = true; |                     need_refresh = true; | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 bool file_exists = storage_file_open( |                 if(storage_file_exists(storage, string_get_cstr(buffer))) { | ||||||
|                     fav_item_file, string_get_cstr(buffer), FSAM_READ, FSOM_OPEN_EXISTING); |                     storage_common_stat(storage, string_get_cstr(buffer), &file_info); | ||||||
|                 if(file_exists) { |  | ||||||
|                     storage_common_stat(fs_api, string_get_cstr(buffer), &file_info); |  | ||||||
|                     storage_file_close(fav_item_file); |  | ||||||
|                     archive_add_file_item( |                     archive_add_file_item( | ||||||
|                         browser, (file_info.flags & FSF_DIRECTORY), string_get_cstr(buffer)); |                         browser, (file_info.flags & FSF_DIRECTORY), string_get_cstr(buffer)); | ||||||
|                     file_count++; |                     file_count++; | ||||||
|                 } else { |                 } else { | ||||||
|                     storage_file_close(fav_item_file); |  | ||||||
|                     need_refresh = true; |                     need_refresh = true; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -183,7 +171,6 @@ bool archive_favorites_read(void* context) { | |||||||
|     storage_file_close(file); |     storage_file_close(file); | ||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
|     storage_file_free(file); |     storage_file_free(file); | ||||||
|     storage_file_free(fav_item_file); |  | ||||||
|     furi_record_close(RECORD_STORAGE); |     furi_record_close(RECORD_STORAGE); | ||||||
| 
 | 
 | ||||||
|     archive_set_item_count(browser, file_count); |     archive_set_item_count(browser, file_count); | ||||||
|  | |||||||
| @ -370,18 +370,15 @@ ArchiveBrowserView* browser_alloc() { | |||||||
|             return true; |             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; |     return browser; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void browser_free(ArchiveBrowserView* browser) { | void browser_free(ArchiveBrowserView* browser) { | ||||||
|     furi_assert(browser); |     furi_assert(browser); | ||||||
| 
 | 
 | ||||||
|     file_browser_worker_free(browser->worker); |     if(browser->worker_running) { | ||||||
|  |         file_browser_worker_free(browser->worker); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         browser->view, (ArchiveBrowserViewModel * model) { |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  | |||||||
| @ -74,6 +74,7 @@ typedef enum { | |||||||
| struct ArchiveBrowserView { | struct ArchiveBrowserView { | ||||||
|     View* view; |     View* view; | ||||||
|     BrowserWorker* worker; |     BrowserWorker* worker; | ||||||
|  |     bool worker_running; | ||||||
|     ArchiveBrowserViewCallback callback; |     ArchiveBrowserViewCallback callback; | ||||||
|     void* context; |     void* context; | ||||||
|     string_t path; |     string_t path; | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| #include <applications/cli/cli.h> | #include <applications/cli/cli.h> | ||||||
| #include <lib/toolbox/args.h> | #include <lib/toolbox/args.h> | ||||||
| 
 | 
 | ||||||
| #include "ble.h" | #include <ble/ble.h> | ||||||
| #include "bt_settings.h" | #include "bt_settings.h" | ||||||
| #include "bt_service/bt.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); |         app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app); |         app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app); | ||||||
|     submenu_add_item( |     submenu_add_item(app->submenu, "Media", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); | ||||||
|         app->submenu, "Media Player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app); |  | ||||||
|     submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, 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_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit); | ||||||
|     view_dispatcher_add_view( |     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)); |         app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse)); | ||||||
| 
 | 
 | ||||||
|     // TODO switch to menu after Media is done
 |     // 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; |     return app; | ||||||
| } | } | ||||||
|  | |||||||
| @ -43,7 +43,10 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) { | |||||||
|     } |     } | ||||||
|     canvas_set_font(canvas, FontPrimary); |     canvas_set_font(canvas, FontPrimary); | ||||||
|     elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote"); |     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); |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit"); | ||||||
| 
 | 
 | ||||||
|     // Up
 |     // Up
 | ||||||
|     canvas_draw_icon(canvas, 21, 24, &I_Button_18x18); |     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); |         elements_slightly_rounded_box(canvas, 66, 47, 60, 13); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     canvas_draw_icon(canvas, 110, 49, &I_Ok_btn_9x9); |     canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8); | ||||||
|     elements_multiline_text_aligned(canvas, 76, 56, AlignLeft, AlignBottom, "Back"); |     elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) { | 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
 |     // Up
 | ||||||
|     if(model->up_pressed) { |     if(model->up_pressed) { | ||||||
|  |         canvas_set_bitmap_mode(canvas, 1); | ||||||
|         canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13); | ||||||
|  |         canvas_set_bitmap_mode(canvas, 0); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6); |     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
 |     // Down
 | ||||||
|     if(model->down_pressed) { |     if(model->down_pressed) { | ||||||
|  |         canvas_set_bitmap_mode(canvas, 1); | ||||||
|         canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13); | ||||||
|  |         canvas_set_bitmap_mode(canvas, 0); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6); |     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
 |     // Left
 | ||||||
|     if(model->left_pressed) { |     if(model->left_pressed) { | ||||||
|  |         canvas_set_bitmap_mode(canvas, 1); | ||||||
|         canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13); | ||||||
|  |         canvas_set_bitmap_mode(canvas, 0); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft); |     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
 |     // Right
 | ||||||
|     if(model->right_pressed) { |     if(model->right_pressed) { | ||||||
|  |         canvas_set_bitmap_mode(canvas, 1); | ||||||
|         canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13); | ||||||
|  |         canvas_set_bitmap_mode(canvas, 0); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight); |     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); |     bt_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight); | ||||||
|     canvas_draw_line(canvas, 100, 29, 100, 33); |     canvas_draw_line(canvas, 100, 29, 100, 33); | ||||||
|     canvas_draw_line(canvas, 102, 29, 102, 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) { | 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); |     canvas_set_font(canvas, FontSecondary); | ||||||
| 
 | 
 | ||||||
|     if(model->left_mouse_held == true) { |     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
 |     // Keypad circles
 | ||||||
| @ -44,7 +48,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) { | |||||||
| 
 | 
 | ||||||
|     // Up
 |     // Up
 | ||||||
|     if(model->up_pressed) { |     if(model->up_pressed) { | ||||||
|  |         canvas_set_bitmap_mode(canvas, 1); | ||||||
|         canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13); | ||||||
|  |         canvas_set_bitmap_mode(canvas, 0); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9); |     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
 |     // Down
 | ||||||
|     if(model->down_pressed) { |     if(model->down_pressed) { | ||||||
|  |         canvas_set_bitmap_mode(canvas, 1); | ||||||
|         canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13); | ||||||
|  |         canvas_set_bitmap_mode(canvas, 0); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9); |     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
 |     // Left
 | ||||||
|     if(model->left_pressed) { |     if(model->left_pressed) { | ||||||
|  |         canvas_set_bitmap_mode(canvas, 1); | ||||||
|         canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13); | ||||||
|  |         canvas_set_bitmap_mode(canvas, 0); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7); |     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
 |     // Right
 | ||||||
|     if(model->right_pressed) { |     if(model->right_pressed) { | ||||||
|  |         canvas_set_bitmap_mode(canvas, 1); | ||||||
|         canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13); | ||||||
|  |         canvas_set_bitmap_mode(canvas, 0); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } |     } | ||||||
|     canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7); |     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
 |     // Ok
 | ||||||
|     if(model->left_mouse_pressed) { |     if(model->left_mouse_pressed) { | ||||||
|         canvas_draw_icon(canvas, 81, 25, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 81, 25, &I_Ok_btn_pressed_13x13); | ||||||
|         canvas_set_color(canvas, ColorWhite); |     } 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
 |     // Back
 | ||||||
|     if(model->right_mouse_pressed) { |     if(model->right_mouse_pressed) { | ||||||
|         canvas_draw_icon(canvas, 108, 48, &I_Pressed_Button_13x13); |         canvas_draw_icon(canvas, 108, 48, &I_Ok_btn_pressed_13x13); | ||||||
|         canvas_set_color(canvas, ColorWhite); |     } 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) { | 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); |             furi_assert(blocking_animation); | ||||||
|             animation_manager->sd_shown_sd_ok = true; |             animation_manager->sd_shown_sd_ok = true; | ||||||
|         } else if(!animation_manager->sd_shown_no_db) { |         } else if(!animation_manager->sd_shown_no_db) { | ||||||
|             bool db_exists = storage_common_stat(storage, EXT_PATH("Manifest"), NULL) == FSE_OK; |             if(!storage_file_exists(storage, EXT_PATH("Manifest"))) { | ||||||
|             if(!db_exists) { |  | ||||||
|                 blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME); |                 blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME); | ||||||
|                 furi_assert(blocking_animation); |                 furi_assert(blocking_animation); | ||||||
|                 animation_manager->sd_shown_no_db = true; |                 animation_manager->sd_shown_no_db = true; | ||||||
|  | |||||||
| @ -94,6 +94,10 @@ bool slideshow_is_loaded(Slideshow* slideshow) { | |||||||
|     return slideshow->loaded; |     return slideshow->loaded; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool slideshow_is_one_page(Slideshow* slideshow) { | ||||||
|  |     return slideshow->loaded && (slideshow->icon.frame_count == 1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool slideshow_advance(Slideshow* slideshow) { | bool slideshow_advance(Slideshow* slideshow) { | ||||||
|     uint8_t next_frame = slideshow->current_frame + 1; |     uint8_t next_frame = slideshow->current_frame + 1; | ||||||
|     if(next_frame < slideshow->icon.frame_count) { |     if(next_frame < slideshow->icon.frame_count) { | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ Slideshow* slideshow_alloc(); | |||||||
| void slideshow_free(Slideshow* slideshow); | void slideshow_free(Slideshow* slideshow); | ||||||
| bool slideshow_load(Slideshow* slideshow, const char* fspath); | bool slideshow_load(Slideshow* slideshow, const char* fspath); | ||||||
| bool slideshow_is_loaded(Slideshow* slideshow); | bool slideshow_is_loaded(Slideshow* slideshow); | ||||||
|  | bool slideshow_is_one_page(Slideshow* slideshow); | ||||||
| void slideshow_goback(Slideshow* slideshow); | void slideshow_goback(Slideshow* slideshow); | ||||||
| bool slideshow_advance(Slideshow* slideshow); | bool slideshow_advance(Slideshow* slideshow); | ||||||
| void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y); | 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; |     const Version* ver; | ||||||
|     char buffer[64]; |     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_color(canvas, ColorBlack); | ||||||
|     canvas_set_font(canvas, FontPrimary); |     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); |     canvas_set_font(canvas, FontSecondary); | ||||||
| 
 | 
 | ||||||
|     if(m->screen != DesktopViewStatsMeta) { |     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_version_get_hw_region_name(), | ||||||
|             furi_hal_region_get_name(), |             furi_hal_region_get_name(), | ||||||
|             my_name ? my_name : "Unknown"); |             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(); |         ver = furi_hal_version_get_firmware_version(); | ||||||
|         const BleGlueC2Info* c2_ver = NULL; |         const BleGlueC2Info* c2_ver = NULL; | ||||||
| @ -52,7 +53,7 @@ void desktop_debug_render(Canvas* canvas, void* model) { | |||||||
|         c2_ver = ble_glue_get_c2_info(); |         c2_ver = ble_glue_get_c2_info(); | ||||||
| #endif | #endif | ||||||
|         if(!ver) { |         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; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -62,7 +63,7 @@ void desktop_debug_render(Canvas* canvas, void* model) { | |||||||
|             "%s [%s]", |             "%s [%s]", | ||||||
|             version_get_version(ver), |             version_get_version(ver), | ||||||
|             version_get_builddate(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( |         snprintf( | ||||||
|             buffer, |             buffer, | ||||||
| @ -72,11 +73,11 @@ void desktop_debug_render(Canvas* canvas, void* model) { | |||||||
|             version_get_githash(ver), |             version_get_githash(ver), | ||||||
|             version_get_gitbranchnum(ver), |             version_get_gitbranchnum(ver), | ||||||
|             c2_ver ? c2_ver->StackTypeString : "<none>"); |             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( |         snprintf( | ||||||
|             buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver)); |             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 { |     } else { | ||||||
|         Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); |         Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); | ||||||
|  | |||||||
| @ -35,8 +35,9 @@ static bool desktop_view_slideshow_input(InputEvent* event, void* context) { | |||||||
|     furi_assert(event); |     furi_assert(event); | ||||||
|     DesktopSlideshowView* instance = context; |     DesktopSlideshowView* instance = context; | ||||||
| 
 | 
 | ||||||
|  |     DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||||
|  |     bool update_view = false; | ||||||
|     if(event->type == InputTypeShort) { |     if(event->type == InputTypeShort) { | ||||||
|         DesktopSlideshowViewModel* model = view_get_model(instance->view); |  | ||||||
|         bool end_slideshow = false; |         bool end_slideshow = false; | ||||||
|         switch(event->key) { |         switch(event->key) { | ||||||
|         case InputKeyLeft: |         case InputKeyLeft: | ||||||
| @ -54,15 +55,18 @@ static bool desktop_view_slideshow_input(InputEvent* event, void* context) { | |||||||
|         if(end_slideshow) { |         if(end_slideshow) { | ||||||
|             instance->callback(DesktopSlideshowCompleted, instance->context); |             instance->callback(DesktopSlideshowCompleted, instance->context); | ||||||
|         } |         } | ||||||
|         view_commit_model(instance->view, true); |         update_view = true; | ||||||
|     } else if(event->key == InputKeyOk) { |     } else if(event->key == InputKeyOk) { | ||||||
|         if(event->type == InputTypePress) { |         if(event->type == InputTypePress) { | ||||||
|             furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_SHORT); |             furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_SHORT); | ||||||
|         } else if(event->type == InputTypeRelease) { |         } else if(event->type == InputTypeRelease) { | ||||||
|             furi_timer_stop(instance->timer); |             furi_timer_stop(instance->timer); | ||||||
|             furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_LONG); |             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; |     return true; | ||||||
| } | } | ||||||
| @ -79,12 +83,12 @@ static void desktop_view_slideshow_enter(void* context) { | |||||||
|     instance->timer = |     instance->timer = | ||||||
|         furi_timer_alloc(desktop_first_start_timer_callback, FuriTimerTypeOnce, instance); |         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); |     DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||||
|     model->slideshow = slideshow_alloc(); |     model->slideshow = slideshow_alloc(); | ||||||
|     if(!slideshow_load(model->slideshow, SLIDESHOW_FS_PATH)) { |     if(!slideshow_load(model->slideshow, SLIDESHOW_FS_PATH)) { | ||||||
|         instance->callback(DesktopSlideshowCompleted, instance->context); |         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); |     view_commit_model(instance->view, false); | ||||||
| } | } | ||||||
|  | |||||||
| @ -14,8 +14,8 @@ | |||||||
| #define DOLPHIN_STATE_PATH INT_PATH(DOLPHIN_STATE_FILE_NAME) | #define DOLPHIN_STATE_PATH INT_PATH(DOLPHIN_STATE_FILE_NAME) | ||||||
| #define DOLPHIN_STATE_HEADER_MAGIC 0xD0 | #define DOLPHIN_STATE_HEADER_MAGIC 0xD0 | ||||||
| #define DOLPHIN_STATE_HEADER_VERSION 0x01 | #define DOLPHIN_STATE_HEADER_VERSION 0x01 | ||||||
| #define LEVEL2_THRESHOLD 735 | #define LEVEL2_THRESHOLD 300 | ||||||
| #define LEVEL3_THRESHOLD 2940 | #define LEVEL3_THRESHOLD 1800 | ||||||
| #define BUTTHURT_MAX 14 | #define BUTTHURT_MAX 14 | ||||||
| #define BUTTHURT_MIN 0 | #define BUTTHURT_MIN 0 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -99,6 +99,11 @@ static bool browser_folder_check_and_switch(string_t path) { | |||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); |     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||||
|     bool is_root = false; |     bool is_root = false; | ||||||
|  | 
 | ||||||
|  |     if(string_search_rchar(path, '/') == 0) { | ||||||
|  |         is_root = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     while(1) { |     while(1) { | ||||||
|         // Check if folder is existing and navigate back if not
 |         // Check if folder is existing and navigate back if not
 | ||||||
|         if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { |         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); |     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( | void widget_add_button_element( | ||||||
|     Widget* widget, |     Widget* widget, | ||||||
|     GuiButtonType button_type, |     GuiButtonType button_type, | ||||||
|  | |||||||
| @ -105,6 +105,27 @@ void widget_add_text_box_element( | |||||||
|     const char* text, |     const char* text, | ||||||
|     bool strip_to_dots); |     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
 | /** Add Button Element
 | ||||||
|  * |  * | ||||||
|  * @param      widget       Widget instance |  * @param      widget       Widget instance | ||||||
|  | |||||||
| @ -29,6 +29,7 @@ struct WidgetElement { | |||||||
| 
 | 
 | ||||||
|     // generic model holder
 |     // generic model holder
 | ||||||
|     void* model; |     void* model; | ||||||
|  |     FuriMutex* model_mutex; | ||||||
| 
 | 
 | ||||||
|     // pointer to widget that hold our element
 |     // pointer to widget that hold our element
 | ||||||
|     Widget* parent; |     Widget* parent; | ||||||
| @ -80,3 +81,10 @@ WidgetElement* widget_element_frame_create( | |||||||
|     uint8_t width, |     uint8_t width, | ||||||
|     uint8_t height, |     uint8_t height, | ||||||
|     uint8_t radius); |     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_confirm.h" | ||||||
| #include "scene/lfrfid_app_scene_delete_success.h" | #include "scene/lfrfid_app_scene_delete_success.h" | ||||||
| #include "scene/lfrfid_app_scene_rpc.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 <toolbox/path.h> | ||||||
| #include <flipper_format/flipper_format.h> | #include <flipper_format/flipper_format.h> | ||||||
| @ -28,24 +33,44 @@ | |||||||
| #include <rpc/rpc_app.h> | #include <rpc/rpc_app.h> | ||||||
| 
 | 
 | ||||||
| const char* LfRfidApp::app_folder = ANY_PATH("lfrfid"); | 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_extension = ".rfid"; | ||||||
| const char* LfRfidApp::app_filetype = "Flipper RFID key"; | const char* LfRfidApp::app_filetype = "Flipper RFID key"; | ||||||
| 
 | 
 | ||||||
| LfRfidApp::LfRfidApp() | LfRfidApp::LfRfidApp() | ||||||
|     : scene_controller{this} |     : scene_controller{this} | ||||||
|     , notification{"notification"} |     , notification{RECORD_NOTIFICATION} | ||||||
|     , storage{"storage"} |     , storage{RECORD_STORAGE} | ||||||
|     , dialogs{"dialogs"} |     , dialogs{RECORD_DIALOGS} | ||||||
|     , text_store(40) { |     , text_store(40) { | ||||||
|  |     string_init(file_name); | ||||||
|  |     string_init(raw_file_name); | ||||||
|     string_init_set_str(file_path, app_folder); |     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() { | LfRfidApp::~LfRfidApp() { | ||||||
|  |     string_clear(raw_file_name); | ||||||
|  |     string_clear(file_name); | ||||||
|     string_clear(file_path); |     string_clear(file_path); | ||||||
|  |     protocol_dict_free(dict); | ||||||
|  | 
 | ||||||
|  |     lfrfid_worker_free(lfworker); | ||||||
|  | 
 | ||||||
|     if(rpc_ctx) { |     if(rpc_ctx) { | ||||||
|         rpc_system_app_set_callback(rpc_ctx, NULL, NULL); |         rpc_system_app_set_callback(rpc_ctx, NULL, NULL); | ||||||
|         rpc_system_app_send_exited(rpc_ctx); |         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) { | 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); |             scene_controller.process(100, SceneType::Rpc); | ||||||
|         } else { |         } else { | ||||||
|             string_set_str(file_path, args); |             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); |             view_controller.attach_to_gui(ViewDispatcherTypeFullscreen); | ||||||
|             scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); |             scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); | ||||||
|             scene_controller.process(100, SceneType::Emulate); |             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::SavedInfo, new LfRfidAppSceneSavedInfo()); | ||||||
|         scene_controller.add_scene(SceneType::DeleteConfirm, new LfRfidAppSceneDeleteConfirm()); |         scene_controller.add_scene(SceneType::DeleteConfirm, new LfRfidAppSceneDeleteConfirm()); | ||||||
|         scene_controller.add_scene(SceneType::DeleteSuccess, new LfRfidAppSceneDeleteSuccess()); |         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); |         scene_controller.process(100); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::save_key(RfidKey* key) { | bool LfRfidApp::save_key() { | ||||||
|     bool result = false; |     bool result = false; | ||||||
| 
 | 
 | ||||||
|     make_app_folder(); |     make_app_folder(); | ||||||
| @ -128,9 +158,9 @@ bool LfRfidApp::save_key(RfidKey* key) { | |||||||
|         string_left(file_path, filename_start); |         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; |     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); |         dialogs, file_path, file_path, app_extension, true, &I_125_10px, true); | ||||||
| 
 | 
 | ||||||
|     if(result) { |     if(result) { | ||||||
|         result = load_key_data(file_path, &worker.key, true); |         result = load_key_data(file_path, true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::delete_key(RfidKey* key) { | bool LfRfidApp::delete_key() { | ||||||
|     UNUSED(key); |  | ||||||
|     return storage_simply_remove(storage, string_get_cstr(file_path)); |     return storage_simply_remove(storage, string_get_cstr(file_path)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::load_key_data(string_t path, RfidKey* key, bool show_dialog) { | bool LfRfidApp::load_key_data(string_t path, bool show_dialog) { | ||||||
|     FlipperFormat* file = flipper_format_file_alloc(storage); |  | ||||||
|     bool result = false; |     bool result = false; | ||||||
|     string_t str_result; |  | ||||||
|     string_init(str_result); |  | ||||||
| 
 | 
 | ||||||
|     do { |     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
 |         path_extract_filename(path, file_name, true); | ||||||
|         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; |  | ||||||
|         result = true; |         result = true; | ||||||
|     } while(0); |     } while(0); | ||||||
| 
 | 
 | ||||||
|     flipper_format_free(file); |  | ||||||
|     string_clear(str_result); |  | ||||||
| 
 |  | ||||||
|     if((!result) && (show_dialog)) { |     if((!result) && (show_dialog)) { | ||||||
|         dialog_message_show_storage_error(dialogs, "Cannot load\nkey file"); |         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; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool LfRfidApp::save_key_data(string_t path, RfidKey* key) { | bool LfRfidApp::save_key_data(string_t path) { | ||||||
|     FlipperFormat* file = flipper_format_file_alloc(storage); |     bool result = lfrfid_dict_file_save(dict, protocol_id, string_get_cstr(path)); | ||||||
|     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); |  | ||||||
| 
 | 
 | ||||||
|     if(!result) { |     if(!result) { | ||||||
|         dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); |         dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); | ||||||
|  | |||||||
| @ -20,9 +20,15 @@ | |||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include <dialogs/dialogs.h> | #include <dialogs/dialogs.h> | ||||||
| 
 | 
 | ||||||
| #include "helpers/rfid_worker.h" |  | ||||||
| #include "rpc/rpc_app.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 { | class LfRfidApp { | ||||||
| public: | public: | ||||||
|     enum class EventType : uint8_t { |     enum class EventType : uint8_t { | ||||||
| @ -32,7 +38,19 @@ public: | |||||||
|         Stay, |         Stay, | ||||||
|         Retry, |         Retry, | ||||||
|         Exit, |         Exit, | ||||||
|         EmulateStart, |         ReadEventSenseStart, | ||||||
|  |         ReadEventSenseEnd, | ||||||
|  |         ReadEventSenseCardStart, | ||||||
|  |         ReadEventSenseCardEnd, | ||||||
|  |         ReadEventStartASK, | ||||||
|  |         ReadEventStartPSK, | ||||||
|  |         ReadEventDone, | ||||||
|  |         ReadEventOverrun, | ||||||
|  |         ReadEventError, | ||||||
|  |         WriteEventOK, | ||||||
|  |         WriteEventProtocolCannotBeWritten, | ||||||
|  |         WriteEventFobCannotBeWritten, | ||||||
|  |         WriteEventTooLongToWrite, | ||||||
|         RpcLoadFile, |         RpcLoadFile, | ||||||
|         RpcSessionClose, |         RpcSessionClose, | ||||||
|     }; |     }; | ||||||
| @ -57,12 +75,17 @@ public: | |||||||
|         DeleteConfirm, |         DeleteConfirm, | ||||||
|         DeleteSuccess, |         DeleteSuccess, | ||||||
|         Rpc, |         Rpc, | ||||||
|  |         ExtraActions, | ||||||
|  |         RawInfo, | ||||||
|  |         RawName, | ||||||
|  |         RawRead, | ||||||
|  |         RawSuccess, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     class Event { |     class Event { | ||||||
|     public: |     public: | ||||||
|         union { |         union { | ||||||
|             int32_t menu_index; |             int32_t signed_int; | ||||||
|         } payload; |         } payload; | ||||||
| 
 | 
 | ||||||
|         EventType type; |         EventType type; | ||||||
| @ -79,8 +102,6 @@ public: | |||||||
|     RecordController<Storage> storage; |     RecordController<Storage> storage; | ||||||
|     RecordController<DialogsApp> dialogs; |     RecordController<DialogsApp> dialogs; | ||||||
| 
 | 
 | ||||||
|     RfidWorker worker; |  | ||||||
| 
 |  | ||||||
|     TextStore text_store; |     TextStore text_store; | ||||||
| 
 | 
 | ||||||
|     string_t file_path; |     string_t file_path; | ||||||
| @ -90,15 +111,27 @@ public: | |||||||
|     void run(void* args); |     void run(void* args); | ||||||
| 
 | 
 | ||||||
|     static const char* app_folder; |     static const char* app_folder; | ||||||
|  |     static const char* app_sd_folder; | ||||||
|     static const char* app_extension; |     static const char* app_extension; | ||||||
|     static const char* app_filetype; |     static const char* app_filetype; | ||||||
| 
 | 
 | ||||||
|     bool save_key(RfidKey* key); |     bool save_key(); | ||||||
|     bool load_key_from_file_select(bool need_restore); |     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 load_key_data(string_t path, bool show_dialog); | ||||||
|     bool save_key_data(string_t path, RfidKey* key); |     bool save_key_data(string_t path); | ||||||
| 
 | 
 | ||||||
|     void make_app_folder(); |     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 */) { | void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore */) { | ||||||
|     string_init(string_data); |     string_init(string_data); | ||||||
|     string_init(string_decrypted); |  | ||||||
|     string_init(string_header); |     string_init(string_header); | ||||||
| 
 | 
 | ||||||
|     auto container = app->view_controller.get<ContainerVM>(); |     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_1 = container->add<StringElement>(); | ||||||
|     auto line_2 = container->add<StringElement>(); |     auto line_2 = container->add<StringElement>(); | ||||||
|     auto line_3 = container->add<StringElement>(); |     auto line_3 = container->add<StringElement>(); | ||||||
|     auto line_4 = container->add<StringElement>(); |  | ||||||
| 
 | 
 | ||||||
|     RfidKey& key = app->worker.key; |     size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id); | ||||||
|     const uint8_t* data = key.get_data(); |     uint8_t* data = (uint8_t*)malloc(size); | ||||||
| 
 |     protocol_dict_get_data(app->dict, app->protocol_id, data, size); | ||||||
|     for(uint8_t i = 0; i < key.get_type_data_count(); i++) { |     for(uint8_t i = 0; i < MIN(size, (size_t)8); i++) { | ||||||
|         if(i != 0) { |         if(i != 0) { | ||||||
|             string_cat_printf(string_data, " "); |             string_cat_printf(string_data, " "); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|         string_cat_printf(string_data, "%02X", data[i]); |         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( |     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( |     line_2->set_text( | ||||||
|         string_get_cstr(string_data), 64, 29, 0, AlignCenter, AlignBottom, FontSecondary); |         string_get_cstr(string_data), 64, 19, 0, AlignCenter, AlignTop, 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; |  | ||||||
|     } |  | ||||||
|     line_3->set_text( |     line_3->set_text( | ||||||
|         string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary); |         protocol_dict_get_name(app->dict, app->protocol_id), | ||||||
| 
 |  | ||||||
|     line_4->set_text( |  | ||||||
|         lfrfid_key_get_type_string(key.get_type()), |  | ||||||
|         64, |         64, | ||||||
|         49, |         49, | ||||||
|         0, |         0, | ||||||
| @ -78,7 +54,7 @@ bool LfRfidAppSceneDeleteConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* eve | |||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == LfRfidApp::EventType::Next) { |     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); |         app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::DeleteSuccess); | ||||||
|         consumed = true; |         consumed = true; | ||||||
|     } else if(event->type == LfRfidApp::EventType::Stay) { |     } 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) { | void LfRfidAppSceneDeleteConfirm::on_exit(LfRfidApp* app) { | ||||||
|     app->view_controller.get<ContainerVM>()->clean(); |     app->view_controller.get<ContainerVM>()->clean(); | ||||||
|     string_clear(string_data); |     string_clear(string_data); | ||||||
|     string_clear(string_decrypted); |  | ||||||
|     string_clear(string_header); |     string_clear(string_header); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -13,5 +13,4 @@ private: | |||||||
| 
 | 
 | ||||||
|     string_t string_header; |     string_t string_header; | ||||||
|     string_t string_data; |     string_t string_data; | ||||||
|     string_t string_decrypted; |  | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -3,28 +3,21 @@ | |||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| 
 | 
 | ||||||
| void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool /* need_restore */) { | void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool /* need_restore */) { | ||||||
|     string_init(data_string); |  | ||||||
| 
 |  | ||||||
|     DOLPHIN_DEED(DolphinDeedRfidEmulate); |     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>(); |     auto popup = app->view_controller.get<PopupVM>(); | ||||||
| 
 | 
 | ||||||
|     popup->set_header("Emulating", 89, 30, AlignCenter, AlignTop); |     popup->set_header("Emulating", 89, 30, AlignCenter, AlignTop); | ||||||
|     if(strlen(app->worker.key.get_name())) { |     if(string_size(app->file_name)) { | ||||||
|         popup->set_text(app->worker.key.get_name(), 89, 43, AlignCenter, AlignTop); |         popup->set_text(string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop); | ||||||
|     } else { |     } 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); |     popup->set_icon(0, 3, &I_RFIDDolphinSend_97x61); | ||||||
| 
 | 
 | ||||||
|     app->view_controller.switch_to<PopupVM>(); |     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); |     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) { | void LfRfidAppSceneEmulate::on_exit(LfRfidApp* app) { | ||||||
|     app->view_controller.get<PopupVM>()->clean(); |     app->view_controller.get<PopupVM>()->clean(); | ||||||
|     app->worker.stop_emulate(); |     lfrfid_worker_stop(app->lfworker); | ||||||
|     string_clear(data_string); |     lfrfid_worker_stop_thread(app->lfworker); | ||||||
|     notification_message(app->notification, &sequence_blink_stop); |     notification_message(app->notification, &sequence_blink_stop); | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,7 +6,4 @@ public: | |||||||
|     void on_enter(LfRfidApp* app, bool need_restore) final; |     void on_enter(LfRfidApp* app, bool need_restore) final; | ||||||
|     bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; |     bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final; | ||||||
|     void on_exit(LfRfidApp* app) 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