Merge branch 'dev' into release-candidate
This commit is contained in:
		
						commit
						04c780f48a
					
				
							
								
								
									
										9
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							| @ -18,7 +18,7 @@ | |||||||
| /applications/main/gpio/ @skotopes @DrZlo13 @hedger @nminaylov | /applications/main/gpio/ @skotopes @DrZlo13 @hedger @nminaylov | ||||||
| /applications/main/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov | /applications/main/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov | ||||||
| /applications/main/infrared/ @skotopes @DrZlo13 @hedger @gsurkov | /applications/main/infrared/ @skotopes @DrZlo13 @hedger @gsurkov | ||||||
| /applications/main/nfc/ @skotopes @DrZlo13 @hedger @gornekich | /applications/main/nfc/ @skotopes @DrZlo13 @hedger @gornekich @Astrrra | ||||||
| /applications/main/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm | /applications/main/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm | ||||||
| /applications/main/u2f/ @skotopes @DrZlo13 @hedger @nminaylov | /applications/main/u2f/ @skotopes @DrZlo13 @hedger @nminaylov | ||||||
| 
 | 
 | ||||||
| @ -40,6 +40,8 @@ | |||||||
| 
 | 
 | ||||||
| /applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @nminaylov | /applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @nminaylov | ||||||
| 
 | 
 | ||||||
|  | /applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @nminaylov @gornekich @Astrrra @gsurkov @Skorpionm | ||||||
|  | 
 | ||||||
| # Documentation | # Documentation | ||||||
| /documentation/ @skotopes @DrZlo13 @hedger @drunkbatya | /documentation/ @skotopes @DrZlo13 @hedger @drunkbatya | ||||||
| /scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya | /scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya | ||||||
| @ -54,6 +56,9 @@ | |||||||
| /lib/mbedtls/ @skotopes @DrZlo13 @hedger @nminaylov | /lib/mbedtls/ @skotopes @DrZlo13 @hedger @nminaylov | ||||||
| /lib/micro-ecc/ @skotopes @DrZlo13 @hedger @nminaylov | /lib/micro-ecc/ @skotopes @DrZlo13 @hedger @nminaylov | ||||||
| /lib/nanopb/ @skotopes @DrZlo13 @hedger @nminaylov | /lib/nanopb/ @skotopes @DrZlo13 @hedger @nminaylov | ||||||
| /lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich | /lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich @Astrrra | ||||||
| /lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov | /lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov | ||||||
| /lib/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm | /lib/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm | ||||||
|  | 
 | ||||||
|  | # CI/CD | ||||||
|  | /.github/workflows/ @skotopes @DrZlo13 @hedger @drunkbatya | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/amap_analyse.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/amap_analyse.yml
									
									
									
									
										vendored
									
									
								
							| @ -91,7 +91,7 @@ jobs: | |||||||
|           export RODATA_SIZE="$(get_size ".rodata")" |           export RODATA_SIZE="$(get_size ".rodata")" | ||||||
|           export DATA_SIZE="$(get_size ".data")" |           export DATA_SIZE="$(get_size ".data")" | ||||||
|           export FREE_FLASH_SIZE="$(get_size ".free_flash")" |           export FREE_FLASH_SIZE="$(get_size ".free_flash")" | ||||||
|           python3 -m pip install mariadb |           python3 -m pip install mariadb==1.1.4 | ||||||
|           python3 scripts/amap_mariadb_insert.py \ |           python3 scripts/amap_mariadb_insert.py \ | ||||||
|             ${{ secrets.AMAP_MARIADB_USER }} \ |             ${{ secrets.AMAP_MARIADB_USER }} \ | ||||||
|             ${{ secrets.AMAP_MARIADB_PASSWORD }} \ |             ${{ secrets.AMAP_MARIADB_PASSWORD }} \ | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/pvs_studio.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/pvs_studio.yml
									
									
									
									
										vendored
									
									
								
							| @ -57,7 +57,7 @@ jobs: | |||||||
| 
 | 
 | ||||||
|       - name: 'Generate compile_comands.json' |       - name: 'Generate compile_comands.json' | ||||||
|         run: | |         run: | | ||||||
|           FBT_TOOLCHAIN_PATH=/runner/_work ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking |           FBT_TOOLCHAIN_PATH=/runner/_work ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking _fap_icons | ||||||
| 
 | 
 | ||||||
|       - name: 'Static code analysis' |       - name: 'Static code analysis' | ||||||
|         run: | |         run: | | ||||||
|  | |||||||
							
								
								
									
										16
									
								
								.github/workflows/unit_tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/unit_tests.yml
									
									
									
									
										vendored
									
									
								
							| @ -8,7 +8,7 @@ env: | |||||||
|   DEFAULT_TARGET: f7 |   DEFAULT_TARGET: f7 | ||||||
| 
 | 
 | ||||||
| jobs: | jobs: | ||||||
|   main: |   run_units_on_test_bench: | ||||||
|     runs-on: [self-hosted, FlipperZeroTest] |     runs-on: [self-hosted, FlipperZeroTest] | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout code |       - name: Checkout code | ||||||
| @ -22,31 +22,35 @@ jobs: | |||||||
|         run: | |         run: | | ||||||
|           echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT |           echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT | ||||||
| 
 | 
 | ||||||
|       - name: 'Compile unit tests firmware' |       - name: 'Flash unit tests firmware' | ||||||
|         id: compile |         id: flashing | ||||||
|         run: | |         run: | | ||||||
|           FBT_TOOLCHAIN_PATH=/opt ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 |           FBT_TOOLCHAIN_PATH=/opt ./fbt flash OPENOCD_ADAPTER_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1 | ||||||
| 
 | 
 | ||||||
|       - name: 'Wait for flipper to finish updating' |       - name: 'Wait for flipper to finish updating' | ||||||
|         id: connect |         id: connect | ||||||
|         if: steps.compile.outcome == 'success' |         if: steps.flashing.outcome == 'success' | ||||||
|         run: | |         run: | | ||||||
|           python3 ./scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} |           . scripts/toolchain/fbtenv.sh | ||||||
|  |           ./scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} | ||||||
| 
 | 
 | ||||||
|       - name: 'Format flipper SD card' |       - name: 'Format flipper SD card' | ||||||
|         id: format |         id: format | ||||||
|         if: steps.connect.outcome == 'success' |         if: steps.connect.outcome == 'success' | ||||||
|         run: | |         run: | | ||||||
|  |           . scripts/toolchain/fbtenv.sh | ||||||
|           ./scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext |           ./scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext | ||||||
| 
 | 
 | ||||||
|       - name: 'Copy assets and unit tests data to flipper' |       - name: 'Copy assets and unit tests data to flipper' | ||||||
|         id: copy |         id: copy | ||||||
|         if: steps.format.outcome == 'success' |         if: steps.format.outcome == 'success' | ||||||
|         run: | |         run: | | ||||||
|  |           . scripts/toolchain/fbtenv.sh | ||||||
|           ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/resources /ext |           ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/resources /ext | ||||||
|           ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/unit_tests /ext/unit_tests |           ./scripts/storage.py -p ${{steps.device.outputs.flipper}} send assets/unit_tests /ext/unit_tests | ||||||
| 
 | 
 | ||||||
|       - name: 'Run units and validate results' |       - name: 'Run units and validate results' | ||||||
|         if: steps.copy.outcome == 'success' |         if: steps.copy.outcome == 'success' | ||||||
|         run: | |         run: | | ||||||
|           python3 ./scripts/testing/units.py ${{steps.device.outputs.flipper}} |           . scripts/toolchain/fbtenv.sh | ||||||
|  |           ./scripts/testing/units.py ${{steps.device.outputs.flipper}} | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -54,3 +54,5 @@ openocd.log | |||||||
| # PVS Studio temporary files | # PVS Studio temporary files | ||||||
| .PVS-Studio/ | .PVS-Studio/ | ||||||
| PVS-Studio.log | PVS-Studio.log | ||||||
|  | 
 | ||||||
|  | .gdbinit | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ | |||||||
| //-V:BPTREE_DEF2:779,1086,557,773,512 | //-V:BPTREE_DEF2:779,1086,557,773,512 | ||||||
| //-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685 | //-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685 | ||||||
| //-V:ALGO_DEF:1048,747,1044 | //-V:ALGO_DEF:1048,747,1044 | ||||||
|  | //-V:TUPLE_DEF2:524,590,1001,760 | ||||||
| 
 | 
 | ||||||
| # Non-severe malloc/null pointer deref warnings | # Non-severe malloc/null pointer deref warnings | ||||||
| //-V::522:2,3 | //-V::522:2,3 | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								.vscode/example/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.vscode/example/launch.json
									
									
									
									
										vendored
									
									
								
							| @ -38,6 +38,7 @@ | |||||||
|             "postAttachCommands": [ |             "postAttachCommands": [ | ||||||
|                 // "compare-sections", |                 // "compare-sections", | ||||||
|                 "source debug/flipperapps.py", |                 "source debug/flipperapps.py", | ||||||
|  |                 "fap-set-debug-elf-root build/latest/.extapps", | ||||||
|                 // "source debug/FreeRTOS/FreeRTOS.py", |                 // "source debug/FreeRTOS/FreeRTOS.py", | ||||||
|                 // "svd_load debug/STM32WB55_CM4.svd" |                 // "svd_load debug/STM32WB55_CM4.svd" | ||||||
|             ] |             ] | ||||||
| @ -59,6 +60,7 @@ | |||||||
|                 "set confirm off", |                 "set confirm off", | ||||||
|                 "set mem inaccessible-by-default off", |                 "set mem inaccessible-by-default off", | ||||||
|                 "source debug/flipperapps.py", |                 "source debug/flipperapps.py", | ||||||
|  |                 "fap-set-debug-elf-root build/latest/.extapps", | ||||||
|                 // "compare-sections", |                 // "compare-sections", | ||||||
|             ] |             ] | ||||||
|             // "showDevDebugOutput": "raw", |             // "showDevDebugOutput": "raw", | ||||||
| @ -76,6 +78,7 @@ | |||||||
|             "rtos": "FreeRTOS", |             "rtos": "FreeRTOS", | ||||||
|             "postAttachCommands": [ |             "postAttachCommands": [ | ||||||
|                 "source debug/flipperapps.py", |                 "source debug/flipperapps.py", | ||||||
|  |                 "fap-set-debug-elf-root build/latest/.extapps", | ||||||
|             ] |             ] | ||||||
|             // "showDevDebugOutput": "raw", |             // "showDevDebugOutput": "raw", | ||||||
|         }, |         }, | ||||||
| @ -95,6 +98,7 @@ | |||||||
|             ], |             ], | ||||||
|             "postAttachCommands": [ |             "postAttachCommands": [ | ||||||
|                 "source debug/flipperapps.py", |                 "source debug/flipperapps.py", | ||||||
|  |                 "fap-set-debug-elf-root build/latest/.extapps", | ||||||
|             ], |             ], | ||||||
|             // "showDevDebugOutput": "raw", |             // "showDevDebugOutput": "raw", | ||||||
|         }, |         }, | ||||||
|  | |||||||
							
								
								
									
										41
									
								
								SConstruct
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								SConstruct
									
									
									
									
									
								
							| @ -7,6 +7,7 @@ | |||||||
| # construction of certain targets behind command-line options. | # construction of certain targets behind command-line options. | ||||||
| 
 | 
 | ||||||
| import os | import os | ||||||
|  | from fbt.util import path_as_posix | ||||||
| 
 | 
 | ||||||
| DefaultEnvironment(tools=[]) | DefaultEnvironment(tools=[]) | ||||||
| 
 | 
 | ||||||
| @ -33,10 +34,6 @@ coreenv = SConscript( | |||||||
| ) | ) | ||||||
| SConscript("site_scons/cc.scons", exports={"ENV": coreenv}) | SConscript("site_scons/cc.scons", exports={"ENV": coreenv}) | ||||||
| 
 | 
 | ||||||
| # Store root dir in environment for certain tools |  | ||||||
| coreenv["ROOT_DIR"] = Dir(".") |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # Create a separate "dist" environment and add construction envs to it | # Create a separate "dist" environment and add construction envs to it | ||||||
| distenv = coreenv.Clone( | distenv = coreenv.Clone( | ||||||
|     tools=[ |     tools=[ | ||||||
| @ -47,6 +44,7 @@ distenv = coreenv.Clone( | |||||||
|         "jflash", |         "jflash", | ||||||
|     ], |     ], | ||||||
|     ENV=os.environ, |     ENV=os.environ, | ||||||
|  |     UPDATE_BUNDLE_DIR="dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}", | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| firmware_env = distenv.AddFwProject( | firmware_env = distenv.AddFwProject( | ||||||
| @ -144,21 +142,28 @@ distenv.Default(basic_dist) | |||||||
| dist_dir = distenv.GetProjetDirName() | dist_dir = distenv.GetProjetDirName() | ||||||
| fap_dist = [ | fap_dist = [ | ||||||
|     distenv.Install( |     distenv.Install( | ||||||
|         f"#/dist/{dist_dir}/apps/debug_elf", |         distenv.Dir(f"#/dist/{dist_dir}/apps/debug_elf"), | ||||||
|         firmware_env["FW_EXTAPPS"]["debug"].values(), |         list( | ||||||
|  |             app_artifact.debug | ||||||
|  |             for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() | ||||||
|  |         ), | ||||||
|     ), |     ), | ||||||
|     *( |     distenv.Install( | ||||||
|         distenv.Install(f"#/dist/{dist_dir}/apps/{dist_entry[0]}", dist_entry[1]) |         f"#/dist/{dist_dir}/apps", | ||||||
|         for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values() |         "#/assets/resources/apps", | ||||||
|     ), |     ), | ||||||
| ] | ] | ||||||
| Depends(fap_dist, firmware_env["FW_EXTAPPS"]["validators"].values()) | Depends( | ||||||
|  |     fap_dist, | ||||||
|  |     list( | ||||||
|  |         app_artifact.validator | ||||||
|  |         for app_artifact in firmware_env["FW_EXTAPPS"].applications.values() | ||||||
|  |     ), | ||||||
|  | ) | ||||||
| Alias("fap_dist", fap_dist) | Alias("fap_dist", fap_dist) | ||||||
| # distenv.Default(fap_dist) | # distenv.Default(fap_dist) | ||||||
| 
 | 
 | ||||||
| distenv.Depends( | distenv.Depends(firmware_env["FW_RESOURCES"], firmware_env["FW_EXTAPPS"].resources_dist) | ||||||
|     firmware_env["FW_RESOURCES"], firmware_env["FW_EXTAPPS"]["resources_dist"] |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Target for bundling core2 package for qFlipper | # Target for bundling core2 package for qFlipper | ||||||
| @ -196,6 +201,7 @@ firmware_debug = distenv.PhonyTarget( | |||||||
|     source=firmware_env["FW_ELF"], |     source=firmware_env["FW_ELF"], | ||||||
|     GDBOPTS="${GDBOPTS_BASE}", |     GDBOPTS="${GDBOPTS_BASE}", | ||||||
|     GDBREMOTE="${OPENOCD_GDB_PIPE}", |     GDBREMOTE="${OPENOCD_GDB_PIPE}", | ||||||
|  |     FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), | ||||||
| ) | ) | ||||||
| distenv.Depends(firmware_debug, firmware_flash) | distenv.Depends(firmware_debug, firmware_flash) | ||||||
| 
 | 
 | ||||||
| @ -205,6 +211,7 @@ distenv.PhonyTarget( | |||||||
|     source=firmware_env["FW_ELF"], |     source=firmware_env["FW_ELF"], | ||||||
|     GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", |     GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", | ||||||
|     GDBREMOTE="${BLACKMAGIC_ADDR}", |     GDBREMOTE="${BLACKMAGIC_ADDR}", | ||||||
|  |     FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| # Debug alien elf | # Debug alien elf | ||||||
| @ -213,7 +220,7 @@ distenv.PhonyTarget( | |||||||
|     "${GDBPYCOM}", |     "${GDBPYCOM}", | ||||||
|     GDBOPTS="${GDBOPTS_BASE}", |     GDBOPTS="${GDBOPTS_BASE}", | ||||||
|     GDBREMOTE="${OPENOCD_GDB_PIPE}", |     GDBREMOTE="${OPENOCD_GDB_PIPE}", | ||||||
|     GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ', |     GDBPYOPTS='-ex "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py" ', | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| distenv.PhonyTarget( | distenv.PhonyTarget( | ||||||
| @ -233,13 +240,13 @@ distenv.PhonyTarget( | |||||||
| # Linter | # Linter | ||||||
| distenv.PhonyTarget( | distenv.PhonyTarget( | ||||||
|     "lint", |     "lint", | ||||||
|     "${PYTHON3} scripts/lint.py check ${LINT_SOURCES}", |     "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}", | ||||||
|     LINT_SOURCES=firmware_env["LINT_SOURCES"], |     LINT_SOURCES=firmware_env["LINT_SOURCES"], | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| distenv.PhonyTarget( | distenv.PhonyTarget( | ||||||
|     "format", |     "format", | ||||||
|     "${PYTHON3} scripts/lint.py format ${LINT_SOURCES}", |     "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}", | ||||||
|     LINT_SOURCES=firmware_env["LINT_SOURCES"], |     LINT_SOURCES=firmware_env["LINT_SOURCES"], | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -280,7 +287,7 @@ distenv.PhonyTarget( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| # Start Flipper CLI via PySerial's miniterm | # Start Flipper CLI via PySerial's miniterm | ||||||
| distenv.PhonyTarget("cli", "${PYTHON3} scripts/serial_cli.py") | distenv.PhonyTarget("cli", "${PYTHON3} ${FBT_SCRIPT_DIR}/serial_cli.py") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Find blackmagic probe | # Find blackmagic probe | ||||||
|  | |||||||
| @ -424,6 +424,7 @@ MU_TEST(infrared_test_decoder_mixed) { | |||||||
|     infrared_test_run_decoder(InfraredProtocolRC5, 5); |     infrared_test_run_decoder(InfraredProtocolRC5, 5); | ||||||
|     infrared_test_run_decoder(InfraredProtocolSamsung32, 1); |     infrared_test_run_decoder(InfraredProtocolSamsung32, 1); | ||||||
|     infrared_test_run_decoder(InfraredProtocolSIRC, 3); |     infrared_test_run_decoder(InfraredProtocolSIRC, 3); | ||||||
|  |     infrared_test_run_decoder(InfraredProtocolKaseikyo, 1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MU_TEST(infrared_test_decoder_nec) { | MU_TEST(infrared_test_decoder_nec) { | ||||||
| @ -489,6 +490,15 @@ MU_TEST(infrared_test_encoder_rc6) { | |||||||
|     infrared_test_run_encoder(InfraredProtocolRC6, 1); |     infrared_test_run_encoder(InfraredProtocolRC6, 1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | MU_TEST(infrared_test_decoder_kaseikyo) { | ||||||
|  |     infrared_test_run_decoder(InfraredProtocolKaseikyo, 1); | ||||||
|  |     infrared_test_run_decoder(InfraredProtocolKaseikyo, 2); | ||||||
|  |     infrared_test_run_decoder(InfraredProtocolKaseikyo, 3); | ||||||
|  |     infrared_test_run_decoder(InfraredProtocolKaseikyo, 4); | ||||||
|  |     infrared_test_run_decoder(InfraredProtocolKaseikyo, 5); | ||||||
|  |     infrared_test_run_decoder(InfraredProtocolKaseikyo, 6); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| MU_TEST(infrared_test_encoder_decoder_all) { | MU_TEST(infrared_test_encoder_decoder_all) { | ||||||
|     infrared_test_run_encoder_decoder(InfraredProtocolNEC, 1); |     infrared_test_run_encoder_decoder(InfraredProtocolNEC, 1); | ||||||
|     infrared_test_run_encoder_decoder(InfraredProtocolNECext, 1); |     infrared_test_run_encoder_decoder(InfraredProtocolNECext, 1); | ||||||
| @ -498,6 +508,7 @@ MU_TEST(infrared_test_encoder_decoder_all) { | |||||||
|     infrared_test_run_encoder_decoder(InfraredProtocolRC6, 1); |     infrared_test_run_encoder_decoder(InfraredProtocolRC6, 1); | ||||||
|     infrared_test_run_encoder_decoder(InfraredProtocolRC5, 1); |     infrared_test_run_encoder_decoder(InfraredProtocolRC5, 1); | ||||||
|     infrared_test_run_encoder_decoder(InfraredProtocolSIRC, 1); |     infrared_test_run_encoder_decoder(InfraredProtocolSIRC, 1); | ||||||
|  |     infrared_test_run_encoder_decoder(InfraredProtocolKaseikyo, 1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MU_TEST_SUITE(infrared_test) { | MU_TEST_SUITE(infrared_test) { | ||||||
| @ -515,6 +526,7 @@ MU_TEST_SUITE(infrared_test) { | |||||||
|     MU_RUN_TEST(infrared_test_decoder_nec); |     MU_RUN_TEST(infrared_test_decoder_nec); | ||||||
|     MU_RUN_TEST(infrared_test_decoder_samsung32); |     MU_RUN_TEST(infrared_test_decoder_samsung32); | ||||||
|     MU_RUN_TEST(infrared_test_decoder_necext1); |     MU_RUN_TEST(infrared_test_decoder_necext1); | ||||||
|  |     MU_RUN_TEST(infrared_test_decoder_kaseikyo); | ||||||
|     MU_RUN_TEST(infrared_test_decoder_mixed); |     MU_RUN_TEST(infrared_test_decoder_mixed); | ||||||
|     MU_RUN_TEST(infrared_test_encoder_decoder_all); |     MU_RUN_TEST(infrared_test_encoder_decoder_all); | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,6 +5,8 @@ | |||||||
| #include <lib/nfc/protocols/nfca.h> | #include <lib/nfc/protocols/nfca.h> | ||||||
| #include <lib/nfc/helpers/mf_classic_dict.h> | #include <lib/nfc/helpers/mf_classic_dict.h> | ||||||
| #include <lib/digital_signal/digital_signal.h> | #include <lib/digital_signal/digital_signal.h> | ||||||
|  | #include <lib/nfc/nfc_device.h> | ||||||
|  | #include <applications/main/nfc/helpers/nfc_generators.h> | ||||||
| 
 | 
 | ||||||
| #include <lib/flipper_format/flipper_format_i.h> | #include <lib/flipper_format/flipper_format_i.h> | ||||||
| #include <lib/toolbox/stream/file_stream.h> | #include <lib/toolbox/stream/file_stream.h> | ||||||
| @ -17,6 +19,7 @@ | |||||||
| #define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc" | #define NFC_TEST_SIGNAL_SHORT_FILE "nfc_nfca_signal_short.nfc" | ||||||
| #define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc" | #define NFC_TEST_SIGNAL_LONG_FILE "nfc_nfca_signal_long.nfc" | ||||||
| #define NFC_TEST_DICT_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc") | #define NFC_TEST_DICT_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc") | ||||||
|  | #define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_dev_test.nfc") | ||||||
| 
 | 
 | ||||||
| static const char* nfc_test_file_type = "Flipper NFC test"; | static const char* nfc_test_file_type = "Flipper NFC test"; | ||||||
| static const uint32_t nfc_test_file_version = 1; | static const uint32_t nfc_test_file_version = 1; | ||||||
| @ -287,9 +290,203 @@ MU_TEST(mf_classic_dict_load_test) { | |||||||
|     furi_record_close(RECORD_STORAGE); |     furi_record_close(RECORD_STORAGE); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | MU_TEST(nfca_file_test) { | ||||||
|  |     NfcDevice* nfc = nfc_device_alloc(); | ||||||
|  |     mu_assert(nfc != NULL, "nfc_device_data != NULL assert failed\r\n"); | ||||||
|  |     nfc->format = NfcDeviceSaveFormatUid; | ||||||
|  | 
 | ||||||
|  |     // Fill the UID, sak, ATQA and type
 | ||||||
|  |     uint8_t uid[7] = {0x04, 0x01, 0x23, 0x45, 0x67, 0x89, 0x00}; | ||||||
|  |     memcpy(nfc->dev_data.nfc_data.uid, uid, 7); | ||||||
|  |     nfc->dev_data.nfc_data.uid_len = 7; | ||||||
|  | 
 | ||||||
|  |     nfc->dev_data.nfc_data.sak = 0x08; | ||||||
|  |     nfc->dev_data.nfc_data.atqa[0] = 0x00; | ||||||
|  |     nfc->dev_data.nfc_data.atqa[1] = 0x04; | ||||||
|  |     nfc->dev_data.nfc_data.type = FuriHalNfcTypeA; | ||||||
|  | 
 | ||||||
|  |     // Save the NFC device data to the file
 | ||||||
|  |     mu_assert( | ||||||
|  |         nfc_device_save(nfc, NFC_TEST_NFC_DEV_PATH), "nfc_device_save == true assert failed\r\n"); | ||||||
|  |     nfc_device_free(nfc); | ||||||
|  | 
 | ||||||
|  |     // Load the NFC device data from the file
 | ||||||
|  |     NfcDevice* nfc_validate = nfc_device_alloc(); | ||||||
|  |     mu_assert( | ||||||
|  |         nfc_device_load(nfc_validate, NFC_TEST_NFC_DEV_PATH, true), | ||||||
|  |         "nfc_device_load == true assert failed\r\n"); | ||||||
|  | 
 | ||||||
|  |     // Check the UID, sak, ATQA and type
 | ||||||
|  |     mu_assert(memcmp(nfc_validate->dev_data.nfc_data.uid, uid, 7) == 0, "uid assert failed\r\n"); | ||||||
|  |     mu_assert(nfc_validate->dev_data.nfc_data.sak == 0x08, "sak == 0x08 assert failed\r\n"); | ||||||
|  |     mu_assert( | ||||||
|  |         nfc_validate->dev_data.nfc_data.atqa[0] == 0x00, "atqa[0] == 0x00 assert failed\r\n"); | ||||||
|  |     mu_assert( | ||||||
|  |         nfc_validate->dev_data.nfc_data.atqa[1] == 0x04, "atqa[1] == 0x04 assert failed\r\n"); | ||||||
|  |     mu_assert( | ||||||
|  |         nfc_validate->dev_data.nfc_data.type == FuriHalNfcTypeA, | ||||||
|  |         "type == FuriHalNfcTypeA assert failed\r\n"); | ||||||
|  |     nfc_device_free(nfc_validate); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) { | ||||||
|  |     NfcDevice* nfc_dev = nfc_device_alloc(); | ||||||
|  |     mu_assert(nfc_dev != NULL, "nfc_device_data != NULL assert failed\r\n"); | ||||||
|  |     nfc_dev->format = NfcDeviceSaveFormatMifareClassic; | ||||||
|  | 
 | ||||||
|  |     // Create a test file
 | ||||||
|  |     nfc_generate_mf_classic(&nfc_dev->dev_data, uid_len, type); | ||||||
|  | 
 | ||||||
|  |     // Get the uid from generated MFC
 | ||||||
|  |     uint8_t uid[7] = {0}; | ||||||
|  |     memcpy(uid, nfc_dev->dev_data.nfc_data.uid, uid_len); | ||||||
|  |     uint8_t sak = nfc_dev->dev_data.nfc_data.sak; | ||||||
|  |     uint8_t atqa[2] = {}; | ||||||
|  |     memcpy(atqa, nfc_dev->dev_data.nfc_data.atqa, 2); | ||||||
|  | 
 | ||||||
|  |     MfClassicData* mf_data = &nfc_dev->dev_data.mf_classic_data; | ||||||
|  |     // Check the manufacturer block (should be uid[uid_len] + 0xFF[rest])
 | ||||||
|  |     uint8_t manufacturer_block[16] = {0}; | ||||||
|  |     memcpy(manufacturer_block, nfc_dev->dev_data.mf_classic_data.block[0].value, 16); | ||||||
|  |     mu_assert( | ||||||
|  |         memcmp(manufacturer_block, uid, uid_len) == 0, | ||||||
|  |         "manufacturer_block uid doesn't match the file\r\n"); | ||||||
|  |     for(uint8_t i = uid_len; i < 16; i++) { | ||||||
|  |         mu_assert( | ||||||
|  |             manufacturer_block[i] == 0xFF, "manufacturer_block[i] == 0xFF assert failed\r\n"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Reference sector trailers (should be 0xFF[6] + 0xFF + 0x07 + 0x80 + 0x69 + 0xFF[6])
 | ||||||
|  |     uint8_t sector_trailer[16] = { | ||||||
|  |         0xFF, | ||||||
|  |         0xFF, | ||||||
|  |         0xFF, | ||||||
|  |         0xFF, | ||||||
|  |         0xFF, | ||||||
|  |         0xFF, | ||||||
|  |         0xFF, | ||||||
|  |         0x07, | ||||||
|  |         0x80, | ||||||
|  |         0x69, | ||||||
|  |         0xFF, | ||||||
|  |         0xFF, | ||||||
|  |         0xFF, | ||||||
|  |         0xFF, | ||||||
|  |         0xFF, | ||||||
|  |         0xFF}; | ||||||
|  |     // Reference block data
 | ||||||
|  |     uint8_t block_data[16] = {}; | ||||||
|  |     memset(block_data, 0xff, sizeof(block_data)); | ||||||
|  |     uint16_t total_blocks = mf_classic_get_total_block_num(type); | ||||||
|  |     for(size_t i = 1; i < total_blocks; i++) { | ||||||
|  |         if(mf_classic_is_sector_trailer(i)) { | ||||||
|  |             mu_assert( | ||||||
|  |                 memcmp(mf_data->block[i].value, sector_trailer, 16) == 0, | ||||||
|  |                 "Failed sector trailer compare"); | ||||||
|  |         } else { | ||||||
|  |             mu_assert(memcmp(mf_data->block[i].value, block_data, 16) == 0, "Failed data compare"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     // Save the NFC device data to the file
 | ||||||
|  |     mu_assert( | ||||||
|  |         nfc_device_save(nfc_dev, NFC_TEST_NFC_DEV_PATH), | ||||||
|  |         "nfc_device_save == true assert failed\r\n"); | ||||||
|  |     // Verify that key cache is saved
 | ||||||
|  |     FuriString* key_cache_name = furi_string_alloc(); | ||||||
|  |     furi_string_set_str(key_cache_name, "/ext/nfc/cache/"); | ||||||
|  |     for(size_t i = 0; i < uid_len; i++) { | ||||||
|  |         furi_string_cat_printf(key_cache_name, "%02X", uid[i]); | ||||||
|  |     } | ||||||
|  |     furi_string_cat_printf(key_cache_name, ".keys"); | ||||||
|  |     mu_assert( | ||||||
|  |         storage_common_stat(nfc_dev->storage, furi_string_get_cstr(key_cache_name), NULL) == | ||||||
|  |             FSE_OK, | ||||||
|  |         "Key cache file save failed"); | ||||||
|  |     nfc_device_free(nfc_dev); | ||||||
|  | 
 | ||||||
|  |     // Load the NFC device data from the file
 | ||||||
|  |     NfcDevice* nfc_validate = nfc_device_alloc(); | ||||||
|  |     mu_assert(nfc_validate, "Nfc device alloc assert"); | ||||||
|  |     mu_assert( | ||||||
|  |         nfc_device_load(nfc_validate, NFC_TEST_NFC_DEV_PATH, false), | ||||||
|  |         "nfc_device_load == true assert failed\r\n"); | ||||||
|  | 
 | ||||||
|  |     // Check the UID, sak, ATQA and type
 | ||||||
|  |     mu_assert( | ||||||
|  |         memcmp(nfc_validate->dev_data.nfc_data.uid, uid, uid_len) == 0, | ||||||
|  |         "uid compare assert failed\r\n"); | ||||||
|  |     mu_assert(nfc_validate->dev_data.nfc_data.sak == sak, "sak compare assert failed\r\n"); | ||||||
|  |     mu_assert( | ||||||
|  |         memcmp(nfc_validate->dev_data.nfc_data.atqa, atqa, 2) == 0, | ||||||
|  |         "atqa compare assert failed\r\n"); | ||||||
|  |     mu_assert( | ||||||
|  |         nfc_validate->dev_data.nfc_data.type == FuriHalNfcTypeA, | ||||||
|  |         "type == FuriHalNfcTypeA assert failed\r\n"); | ||||||
|  | 
 | ||||||
|  |     // Check the manufacturer block
 | ||||||
|  |     mu_assert( | ||||||
|  |         memcmp(nfc_validate->dev_data.mf_classic_data.block[0].value, manufacturer_block, 16) == 0, | ||||||
|  |         "manufacturer_block assert failed\r\n"); | ||||||
|  |     // Check other blocks
 | ||||||
|  |     for(size_t i = 1; i < total_blocks; i++) { | ||||||
|  |         if(mf_classic_is_sector_trailer(i)) { | ||||||
|  |             mu_assert( | ||||||
|  |                 memcmp(mf_data->block[i].value, sector_trailer, 16) == 0, | ||||||
|  |                 "Failed sector trailer compare"); | ||||||
|  |         } else { | ||||||
|  |             mu_assert(memcmp(mf_data->block[i].value, block_data, 16) == 0, "Failed data compare"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     nfc_device_free(nfc_validate); | ||||||
|  | 
 | ||||||
|  |     // Check saved key cache
 | ||||||
|  |     NfcDevice* nfc_keys = nfc_device_alloc(); | ||||||
|  |     mu_assert(nfc_validate, "Nfc device alloc assert"); | ||||||
|  |     nfc_keys->dev_data.nfc_data.uid_len = uid_len; | ||||||
|  |     memcpy(nfc_keys->dev_data.nfc_data.uid, uid, uid_len); | ||||||
|  |     mu_assert(nfc_device_load_key_cache(nfc_keys), "Failed to load key cache"); | ||||||
|  |     uint8_t total_sec = mf_classic_get_total_sectors_num(type); | ||||||
|  |     uint8_t default_key[6] = {}; | ||||||
|  |     memset(default_key, 0xff, 6); | ||||||
|  |     for(size_t i = 0; i < total_sec; i++) { | ||||||
|  |         MfClassicSectorTrailer* sec_tr = | ||||||
|  |             mf_classic_get_sector_trailer_by_sector(&nfc_keys->dev_data.mf_classic_data, i); | ||||||
|  |         mu_assert(memcmp(sec_tr->key_a, default_key, 6) == 0, "Failed key compare"); | ||||||
|  |         mu_assert(memcmp(sec_tr->key_b, default_key, 6) == 0, "Failed key compare"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Delete key cache file
 | ||||||
|  |     mu_assert( | ||||||
|  |         storage_common_remove(nfc_keys->storage, furi_string_get_cstr(key_cache_name)) == FSE_OK, | ||||||
|  |         "Failed to remove key cache file"); | ||||||
|  |     furi_string_free(key_cache_name); | ||||||
|  |     nfc_device_free(nfc_keys); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | MU_TEST(mf_classic_1k_4b_file_test) { | ||||||
|  |     mf_classic_generator_test(4, MfClassicType1k); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | MU_TEST(mf_classic_4k_4b_file_test) { | ||||||
|  |     mf_classic_generator_test(4, MfClassicType4k); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | MU_TEST(mf_classic_1k_7b_file_test) { | ||||||
|  |     mf_classic_generator_test(7, MfClassicType1k); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | MU_TEST(mf_classic_4k_7b_file_test) { | ||||||
|  |     mf_classic_generator_test(7, MfClassicType4k); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| MU_TEST_SUITE(nfc) { | MU_TEST_SUITE(nfc) { | ||||||
|     nfc_test_alloc(); |     nfc_test_alloc(); | ||||||
| 
 | 
 | ||||||
|  |     MU_RUN_TEST(nfca_file_test); | ||||||
|  |     MU_RUN_TEST(mf_classic_1k_4b_file_test); | ||||||
|  |     MU_RUN_TEST(mf_classic_4k_4b_file_test); | ||||||
|  |     MU_RUN_TEST(mf_classic_1k_7b_file_test); | ||||||
|  |     MU_RUN_TEST(mf_classic_4k_7b_file_test); | ||||||
|     MU_RUN_TEST(nfc_digital_signal_test); |     MU_RUN_TEST(nfc_digital_signal_test); | ||||||
|     MU_RUN_TEST(mf_classic_dict_test); |     MU_RUN_TEST(mf_classic_dict_test); | ||||||
|     MU_RUN_TEST(mf_classic_dict_load_test); |     MU_RUN_TEST(mf_classic_dict_load_test); | ||||||
|  | |||||||
| @ -11,4 +11,5 @@ App( | |||||||
|     stack_size=2 * 1024, |     stack_size=2 * 1024, | ||||||
|     icon="A_BadUsb_14", |     icon="A_BadUsb_14", | ||||||
|     order=70, |     order=70, | ||||||
|  |     fap_libs=["assets"], | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -82,7 +82,7 @@ static const DuckyKey ducky_keys[] = { | |||||||
|     {"PAGEUP", HID_KEYBOARD_PAGE_UP}, |     {"PAGEUP", HID_KEYBOARD_PAGE_UP}, | ||||||
|     {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, |     {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, | ||||||
|     {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, |     {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, | ||||||
|     {"SCROLLOCK", HID_KEYBOARD_SCROLL_LOCK}, |     {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, | ||||||
|     {"SPACE", HID_KEYBOARD_SPACEBAR}, |     {"SPACE", HID_KEYBOARD_SPACEBAR}, | ||||||
|     {"TAB", HID_KEYBOARD_TAB}, |     {"TAB", HID_KEYBOARD_TAB}, | ||||||
|     {"MENU", HID_KEYBOARD_APPLICATION}, |     {"MENU", HID_KEYBOARD_APPLICATION}, | ||||||
| @ -338,10 +338,6 @@ static int32_t | |||||||
|         furi_hal_hid_kb_release(key); |         furi_hal_hid_kb_release(key); | ||||||
|         return (0); |         return (0); | ||||||
|     } |     } | ||||||
|     if(error != NULL) { |  | ||||||
|         strncpy(error, "Unknown error", error_len); |  | ||||||
|     } |  | ||||||
|     return SCRIPT_STATE_ERROR; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) { | static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) { | ||||||
| @ -524,12 +520,16 @@ static int32_t bad_usb_worker(void* context) { | |||||||
| 
 | 
 | ||||||
|         } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
 |         } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
 | ||||||
|             uint32_t flags = furi_thread_flags_wait( |             uint32_t flags = furi_thread_flags_wait( | ||||||
|                 WorkerEvtEnd | WorkerEvtConnect, FuriFlagWaitAny, FuriWaitForever); |                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, | ||||||
|  |                 FuriFlagWaitAny, | ||||||
|  |                 FuriWaitForever); | ||||||
|             furi_check((flags & FuriFlagError) == 0); |             furi_check((flags & FuriFlagError) == 0); | ||||||
|             if(flags & WorkerEvtEnd) { |             if(flags & WorkerEvtEnd) { | ||||||
|                 break; |                 break; | ||||||
|             } else if(flags & WorkerEvtConnect) { |             } else if(flags & WorkerEvtConnect) { | ||||||
|                 worker_state = BadUsbStateIdle; // Ready to run
 |                 worker_state = BadUsbStateIdle; // Ready to run
 | ||||||
|  |             } else if(flags & WorkerEvtToggle) { | ||||||
|  |                 worker_state = BadUsbStateWillRun; // Will run when USB is connected
 | ||||||
|             } |             } | ||||||
|             bad_usb->st.state = worker_state; |             bad_usb->st.state = worker_state; | ||||||
| 
 | 
 | ||||||
| @ -556,6 +556,31 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|             } |             } | ||||||
|             bad_usb->st.state = worker_state; |             bad_usb->st.state = worker_state; | ||||||
| 
 | 
 | ||||||
|  |         } else if(worker_state == BadUsbStateWillRun) { // State: start on connection
 | ||||||
|  |             uint32_t flags = furi_thread_flags_wait( | ||||||
|  |                 WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, | ||||||
|  |                 FuriFlagWaitAny, | ||||||
|  |                 FuriWaitForever); | ||||||
|  |             furi_check((flags & FuriFlagError) == 0); | ||||||
|  |             if(flags & WorkerEvtEnd) { | ||||||
|  |                 break; | ||||||
|  |             } else if(flags & WorkerEvtConnect) { // Start executing script
 | ||||||
|  |                 DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); | ||||||
|  |                 delay_val = 0; | ||||||
|  |                 bad_usb->buf_len = 0; | ||||||
|  |                 bad_usb->st.line_cur = 0; | ||||||
|  |                 bad_usb->defdelay = 0; | ||||||
|  |                 bad_usb->repeat_cnt = 0; | ||||||
|  |                 bad_usb->file_end = false; | ||||||
|  |                 storage_file_seek(script_file, 0, true); | ||||||
|  |                 // extra time for PC to recognize Flipper as keyboard
 | ||||||
|  |                 furi_thread_flags_wait(0, FuriFlagWaitAny, 1500); | ||||||
|  |                 worker_state = BadUsbStateRunning; | ||||||
|  |             } else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
 | ||||||
|  |                 worker_state = BadUsbStateNotConnected; | ||||||
|  |             } | ||||||
|  |             bad_usb->st.state = worker_state; | ||||||
|  | 
 | ||||||
|         } else if(worker_state == BadUsbStateRunning) { // State: running
 |         } else if(worker_state == BadUsbStateRunning) { // State: running
 | ||||||
|             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); |             uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val); | ||||||
|             uint32_t flags = furi_thread_flags_wait( |             uint32_t flags = furi_thread_flags_wait( | ||||||
| @ -627,7 +652,7 @@ static int32_t bad_usb_worker(void* context) { | |||||||
| BadUsbScript* bad_usb_script_open(FuriString* file_path) { | BadUsbScript* bad_usb_script_open(FuriString* file_path) { | ||||||
|     furi_assert(file_path); |     furi_assert(file_path); | ||||||
| 
 | 
 | ||||||
|     BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); |     BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript)); //-V773
 | ||||||
|     bad_usb->file_path = furi_string_alloc(); |     bad_usb->file_path = furi_string_alloc(); | ||||||
|     furi_string_set(bad_usb->file_path, file_path); |     furi_string_set(bad_usb->file_path, file_path); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ typedef enum { | |||||||
|     BadUsbStateInit, |     BadUsbStateInit, | ||||||
|     BadUsbStateNotConnected, |     BadUsbStateNotConnected, | ||||||
|     BadUsbStateIdle, |     BadUsbStateIdle, | ||||||
|  |     BadUsbStateWillRun, | ||||||
|     BadUsbStateRunning, |     BadUsbStateRunning, | ||||||
|     BadUsbStateDelay, |     BadUsbStateDelay, | ||||||
|     BadUsbStateDone, |     BadUsbStateDone, | ||||||
|  | |||||||
| @ -29,10 +29,13 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | |||||||
| 
 | 
 | ||||||
|     canvas_draw_icon(canvas, 22, 20, &I_UsbTree_48x22); |     canvas_draw_icon(canvas, 22, 20, &I_UsbTree_48x22); | ||||||
| 
 | 
 | ||||||
|     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) { |     if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone) || | ||||||
|  |        (model->state.state == BadUsbStateNotConnected)) { | ||||||
|         elements_button_center(canvas, "Run"); |         elements_button_center(canvas, "Run"); | ||||||
|     } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { |     } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) { | ||||||
|         elements_button_center(canvas, "Stop"); |         elements_button_center(canvas, "Stop"); | ||||||
|  |     } else if(model->state.state == BadUsbStateWillRun) { | ||||||
|  |         elements_button_center(canvas, "Cancel"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(model->state.state == BadUsbStateNotConnected) { |     if(model->state.state == BadUsbStateNotConnected) { | ||||||
| @ -40,6 +43,11 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) { | |||||||
|         canvas_set_font(canvas, FontPrimary); |         canvas_set_font(canvas, FontPrimary); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Connect"); |         canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Connect"); | ||||||
|         canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "to USB"); |         canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "to USB"); | ||||||
|  |     } else if(model->state.state == BadUsbStateWillRun) { | ||||||
|  |         canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18); | ||||||
|  |         canvas_set_font(canvas, FontPrimary); | ||||||
|  |         canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Will run"); | ||||||
|  |         canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "on connect"); | ||||||
|     } else if(model->state.state == BadUsbStateFileError) { |     } else if(model->state.state == BadUsbStateFileError) { | ||||||
|         canvas_draw_icon(canvas, 4, 22, &I_Error_18x18); |         canvas_draw_icon(canvas, 4, 22, &I_Error_18x18); | ||||||
|         canvas_set_font(canvas, FontPrimary); |         canvas_set_font(canvas, FontPrimary); | ||||||
|  | |||||||
| @ -155,7 +155,7 @@ static bool fap_loader_select_app(FapLoader* loader) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static FapLoader* fap_loader_alloc(const char* path) { | static FapLoader* fap_loader_alloc(const char* path) { | ||||||
|     FapLoader* loader = malloc(sizeof(FapLoader)); |     FapLoader* loader = malloc(sizeof(FapLoader)); //-V773
 | ||||||
|     loader->fap_path = furi_string_alloc_set(path); |     loader->fap_path = furi_string_alloc_set(path); | ||||||
|     loader->storage = furi_record_open(RECORD_STORAGE); |     loader->storage = furi_record_open(RECORD_STORAGE); | ||||||
|     loader->dialogs = furi_record_open(RECORD_DIALOGS); |     loader->dialogs = furi_record_open(RECORD_DIALOGS); | ||||||
|  | |||||||
| @ -8,4 +8,5 @@ App( | |||||||
|     stack_size=1 * 1024, |     stack_size=1 * 1024, | ||||||
|     icon="A_GPIO_14", |     icon="A_GPIO_14", | ||||||
|     order=50, |     order=50, | ||||||
|  |     fap_libs=["assets"], | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -184,7 +184,7 @@ static int32_t usb_uart_worker(void* context) { | |||||||
|     while(1) { |     while(1) { | ||||||
|         uint32_t events = |         uint32_t events = | ||||||
|             furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); |             furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); | ||||||
|         furi_check((events & FuriFlagError) == 0); |         furi_check(!(events & FuriFlagError)); | ||||||
|         if(events & WorkerEvtStop) break; |         if(events & WorkerEvtStop) break; | ||||||
|         if(events & WorkerEvtRxDone) { |         if(events & WorkerEvtRxDone) { | ||||||
|             size_t len = furi_stream_buffer_receive( |             size_t len = furi_stream_buffer_receive( | ||||||
| @ -288,7 +288,7 @@ static int32_t usb_uart_tx_thread(void* context) { | |||||||
|     while(1) { |     while(1) { | ||||||
|         uint32_t events = |         uint32_t events = | ||||||
|             furi_thread_flags_wait(WORKER_ALL_TX_EVENTS, FuriFlagWaitAny, FuriWaitForever); |             furi_thread_flags_wait(WORKER_ALL_TX_EVENTS, FuriFlagWaitAny, FuriWaitForever); | ||||||
|         furi_check((events & FuriFlagError) == 0); |         furi_check(!(events & FuriFlagError)); | ||||||
|         if(events & WorkerEvtTxStop) break; |         if(events & WorkerEvtTxStop) break; | ||||||
|         if(events & WorkerEvtCdcRx) { |         if(events & WorkerEvtCdcRx) { | ||||||
|             furi_check(furi_mutex_acquire(usb_uart->usb_mutex, FuriWaitForever) == FuriStatusOk); |             furi_check(furi_mutex_acquire(usb_uart->usb_mutex, FuriWaitForever) == FuriStatusOk); | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ App( | |||||||
|     icon="A_iButton_14", |     icon="A_iButton_14", | ||||||
|     stack_size=2 * 1024, |     stack_size=2 * 1024, | ||||||
|     order=60, |     order=60, | ||||||
|  |     fap_libs=["assets"], | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| App( | App( | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ App( | |||||||
|     icon="A_Infrared_14", |     icon="A_Infrared_14", | ||||||
|     stack_size=3 * 1024, |     stack_size=3 * 1024, | ||||||
|     order=40, |     order=40, | ||||||
|  |     fap_libs=["assets"], | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| App( | App( | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ App( | |||||||
|     icon="A_125khz_14", |     icon="A_125khz_14", | ||||||
|     stack_size=2 * 1024, |     stack_size=2 * 1024, | ||||||
|     order=20, |     order=20, | ||||||
|  |     fap_libs=["assets"], | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| App( | App( | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static LfRfid* lfrfid_alloc() { | static LfRfid* lfrfid_alloc() { | ||||||
|     LfRfid* lfrfid = malloc(sizeof(LfRfid)); |     LfRfid* lfrfid = malloc(sizeof(LfRfid)); //-V773
 | ||||||
| 
 | 
 | ||||||
|     lfrfid->storage = furi_record_open(RECORD_STORAGE); |     lfrfid->storage = furi_record_open(RECORD_STORAGE); | ||||||
|     lfrfid->dialogs = furi_record_open(RECORD_DIALOGS); |     lfrfid->dialogs = furi_record_open(RECORD_DIALOGS); | ||||||
|  | |||||||
| @ -314,7 +314,7 @@ static void nfc_generate_ntag_i2c_plus_2k(NfcDeviceData* data) { | |||||||
|     mful->version.storage_size = 0x15; |     mful->version.storage_size = 0x15; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { | void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { | ||||||
|     nfc_generate_common_start(data); |     nfc_generate_common_start(data); | ||||||
|     nfc_generate_mf_classic_common(data, uid_len, type); |     nfc_generate_mf_classic_common(data, uid_len, type); | ||||||
| 
 | 
 | ||||||
| @ -337,6 +337,9 @@ static void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClas | |||||||
|             } |             } | ||||||
|             mf_classic_set_block_read(mfc, i, &mfc->block[i]); |             mf_classic_set_block_read(mfc, i, &mfc->block[i]); | ||||||
|         } |         } | ||||||
|  |         // Set SAK to 18
 | ||||||
|  |         data->nfc_data.sak = 0x18; | ||||||
|  | 
 | ||||||
|     } else if(type == MfClassicType1k) { |     } else if(type == MfClassicType1k) { | ||||||
|         // Set every block to 0xFF
 |         // Set every block to 0xFF
 | ||||||
|         for(uint16_t i = 1; i < MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; i += 1) { |         for(uint16_t i = 1; i < MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; i += 1) { | ||||||
| @ -347,6 +350,8 @@ static void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClas | |||||||
|             } |             } | ||||||
|             mf_classic_set_block_read(mfc, i, &mfc->block[i]); |             mf_classic_set_block_read(mfc, i, &mfc->block[i]); | ||||||
|         } |         } | ||||||
|  |         // Set SAK to 08
 | ||||||
|  |         data->nfc_data.sak = 0x08; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     mfc->type = type; |     mfc->type = type; | ||||||
|  | |||||||
| @ -11,3 +11,5 @@ struct NfcGenerator { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| extern const NfcGenerator* const nfc_generators[]; | extern const NfcGenerator* const nfc_generators[]; | ||||||
|  | 
 | ||||||
|  | void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type); | ||||||
|  | |||||||
| @ -116,7 +116,9 @@ void nfc_free(Nfc* nfc) { | |||||||
|         // Stop worker
 |         // Stop worker
 | ||||||
|         nfc_worker_stop(nfc->worker); |         nfc_worker_stop(nfc->worker); | ||||||
|         // Save data in shadow file
 |         // Save data in shadow file
 | ||||||
|         nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); |         if(furi_string_size(nfc->dev->load_path)) { | ||||||
|  |             nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     if(nfc->rpc_ctx) { |     if(nfc->rpc_ctx) { | ||||||
|         rpc_system_app_send_exited(nfc->rpc_ctx); |         rpc_system_app_send_exited(nfc->rpc_ctx); | ||||||
| @ -218,6 +220,13 @@ void nfc_blink_stop(Nfc* nfc) { | |||||||
|     notification_message(nfc->notifications, &sequence_blink_stop); |     notification_message(nfc->notifications, &sequence_blink_stop); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool nfc_save_file(Nfc* nfc) { | ||||||
|  |     furi_string_printf( | ||||||
|  |         nfc->dev->load_path, "%s/%s%s", NFC_APP_FOLDER, nfc->dev->dev_name, NFC_APP_EXTENSION); | ||||||
|  |     bool file_saved = nfc_device_save(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); | ||||||
|  |     return file_saved; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void nfc_show_loading_popup(void* context, bool show) { | void nfc_show_loading_popup(void* context, bool show) { | ||||||
|     Nfc* nfc = context; |     Nfc* nfc = context; | ||||||
|     TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); |     TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); | ||||||
|  | |||||||
| @ -114,4 +114,6 @@ void nfc_blink_detect_start(Nfc* nfc); | |||||||
| 
 | 
 | ||||||
| void nfc_blink_stop(Nfc* nfc); | void nfc_blink_stop(Nfc* nfc); | ||||||
| 
 | 
 | ||||||
|  | bool nfc_save_file(Nfc* nfc); | ||||||
|  | 
 | ||||||
| void nfc_show_loading_popup(void* context, bool show); | void nfc_show_loading_popup(void* context, bool show); | ||||||
|  | |||||||
| @ -60,3 +60,4 @@ ADD_SCENE(nfc, detect_reader, DetectReader) | |||||||
| ADD_SCENE(nfc, mfkey_nonces_info, MfkeyNoncesInfo) | ADD_SCENE(nfc, mfkey_nonces_info, MfkeyNoncesInfo) | ||||||
| ADD_SCENE(nfc, mfkey_complete, MfkeyComplete) | ADD_SCENE(nfc, mfkey_complete, MfkeyComplete) | ||||||
| ADD_SCENE(nfc, nfc_data_info, NfcDataInfo) | ADD_SCENE(nfc, nfc_data_info, NfcDataInfo) | ||||||
|  | ADD_SCENE(nfc, read_card_type, ReadCardType) | ||||||
|  | |||||||
| @ -29,8 +29,13 @@ bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { | |||||||
|         if(event.event == DialogExResultRight) { |         if(event.event == DialogExResultRight) { | ||||||
|             consumed = scene_manager_previous_scene(nfc->scene_manager); |             consumed = scene_manager_previous_scene(nfc->scene_manager); | ||||||
|         } else if(event.event == DialogExResultLeft) { |         } else if(event.event == DialogExResultLeft) { | ||||||
|             consumed = scene_manager_search_and_switch_to_previous_scene( |             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadCardType)) { | ||||||
|                 nfc->scene_manager, NfcSceneStart); |                 consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                     nfc->scene_manager, NfcSceneReadCardType); | ||||||
|  |             } else { | ||||||
|  |                 consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                     nfc->scene_manager, NfcSceneStart); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } else if(event.type == SceneManagerEventTypeBack) { |     } else if(event.type == SceneManagerEventTypeBack) { | ||||||
|         consumed = true; |         consumed = true; | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #include "../nfc_i.h" | #include "../nfc_i.h" | ||||||
| 
 | 
 | ||||||
| enum SubmenuIndex { | enum SubmenuIndex { | ||||||
|  |     SubmenuIndexReadCardType, | ||||||
|     SubmenuIndexMfClassicKeys, |     SubmenuIndexMfClassicKeys, | ||||||
|     SubmenuIndexMfUltralightUnlock, |     SubmenuIndexMfUltralightUnlock, | ||||||
| }; | }; | ||||||
| @ -15,6 +16,12 @@ void nfc_scene_extra_actions_on_enter(void* context) { | |||||||
|     Nfc* nfc = context; |     Nfc* nfc = context; | ||||||
|     Submenu* submenu = nfc->submenu; |     Submenu* submenu = nfc->submenu; | ||||||
| 
 | 
 | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Read Specific Card Type", | ||||||
|  |         SubmenuIndexReadCardType, | ||||||
|  |         nfc_scene_extra_actions_submenu_callback, | ||||||
|  |         nfc); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, |         submenu, | ||||||
|         "Mifare Classic Keys", |         "Mifare Classic Keys", | ||||||
| @ -44,9 +51,15 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { | |||||||
|             consumed = true; |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexMfUltralightUnlock) { |         } else if(event.event == SubmenuIndexMfUltralightUnlock) { | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexReadCardType) { | ||||||
|  |             scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, 0); | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardType); | ||||||
|  |             consumed = true; | ||||||
|         } |         } | ||||||
|         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event); |         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,6 +5,9 @@ void nfc_scene_file_select_on_enter(void* context) { | |||||||
|     Nfc* nfc = context; |     Nfc* nfc = context; | ||||||
|     // Process file_select return
 |     // Process file_select return
 | ||||||
|     nfc_device_set_loading_callback(nfc->dev, nfc_show_loading_popup, nfc); |     nfc_device_set_loading_callback(nfc->dev, nfc_show_loading_popup, nfc); | ||||||
|  |     if(!furi_string_size(nfc->dev->load_path)) { | ||||||
|  |         furi_string_set_str(nfc->dev->load_path, NFC_APP_FOLDER); | ||||||
|  |     } | ||||||
|     if(nfc_file_select(nfc->dev)) { |     if(nfc_file_select(nfc->dev)) { | ||||||
|         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, 0); |         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneSavedMenu, 0); | ||||||
|         scene_manager_next_scene(nfc->scene_manager, NfcSceneSavedMenu); |         scene_manager_next_scene(nfc->scene_manager, NfcSceneSavedMenu); | ||||||
|  | |||||||
| @ -48,7 +48,10 @@ bool nfc_scene_mf_classic_emulate_on_event(void* context, SceneManagerEvent even | |||||||
|            NFC_MF_CLASSIC_DATA_CHANGED) { |            NFC_MF_CLASSIC_DATA_CHANGED) { | ||||||
|             scene_manager_set_scene_state( |             scene_manager_set_scene_state( | ||||||
|                 nfc->scene_manager, NfcSceneMfClassicEmulate, NFC_MF_CLASSIC_DATA_NOT_CHANGED); |                 nfc->scene_manager, NfcSceneMfClassicEmulate, NFC_MF_CLASSIC_DATA_NOT_CHANGED); | ||||||
|             nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); |             // Save shadow file
 | ||||||
|  |             if(furi_string_size(nfc->dev->load_path)) { | ||||||
|  |                 nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         consumed = false; |         consumed = false; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -24,7 +24,7 @@ void nfc_scene_mf_classic_read_success_on_enter(void* context) { | |||||||
|     widget_add_button_element( |     widget_add_button_element( | ||||||
|         widget, GuiButtonTypeRight, "More", nfc_scene_mf_classic_read_success_widget_callback, nfc); |         widget, GuiButtonTypeRight, "More", nfc_scene_mf_classic_read_success_widget_callback, nfc); | ||||||
| 
 | 
 | ||||||
|     FuriString* temp_str; |     FuriString* temp_str = NULL; | ||||||
|     if(furi_string_size(nfc->dev->dev_data.parsed_data)) { |     if(furi_string_size(nfc->dev->dev_data.parsed_data)) { | ||||||
|         temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); |         temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); | ||||||
|     } else { |     } else { | ||||||
|  | |||||||
| @ -57,7 +57,7 @@ bool nfc_scene_mf_classic_update_on_event(void* context, SceneManagerEvent event | |||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == NfcWorkerEventSuccess) { |         if(event.event == NfcWorkerEventSuccess) { | ||||||
|             nfc_worker_stop(nfc->worker); |             nfc_worker_stop(nfc->worker); | ||||||
|             if(nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name)) { |             if(nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path))) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdateSuccess); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdateSuccess); | ||||||
|             } else { |             } else { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); | ||||||
|  | |||||||
| @ -48,7 +48,10 @@ bool nfc_scene_mf_ultralight_emulate_on_event(void* context, SceneManagerEvent e | |||||||
|            NFC_MF_UL_DATA_CHANGED) { |            NFC_MF_UL_DATA_CHANGED) { | ||||||
|             scene_manager_set_scene_state( |             scene_manager_set_scene_state( | ||||||
|                 nfc->scene_manager, NfcSceneMfUltralightEmulate, NFC_MF_UL_DATA_NOT_CHANGED); |                 nfc->scene_manager, NfcSceneMfUltralightEmulate, NFC_MF_UL_DATA_NOT_CHANGED); | ||||||
|             nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); |             // Save shadow file
 | ||||||
|  |             if(furi_string_size(nfc->dev->load_path)) { | ||||||
|  |                 nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path)); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         consumed = false; |         consumed = false; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ void nfc_scene_mf_ultralight_read_success_on_enter(void* context) { | |||||||
|         nfc_scene_mf_ultralight_read_success_widget_callback, |         nfc_scene_mf_ultralight_read_success_widget_callback, | ||||||
|         nfc); |         nfc); | ||||||
| 
 | 
 | ||||||
|     FuriString* temp_str; |     FuriString* temp_str = NULL; | ||||||
|     if(furi_string_size(nfc->dev->dev_data.parsed_data)) { |     if(furi_string_size(nfc->dev->dev_data.parsed_data)) { | ||||||
|         temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); |         temp_str = furi_string_alloc_set(nfc->dev->dev_data.parsed_data); | ||||||
|     } else { |     } else { | ||||||
|  | |||||||
							
								
								
									
										97
									
								
								applications/main/nfc/scenes/nfc_scene_read_card_type.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								applications/main/nfc/scenes/nfc_scene_read_card_type.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | #include "nfc_worker_i.h" | ||||||
|  | 
 | ||||||
|  | enum SubmenuIndex { | ||||||
|  |     SubmenuIndexReadMifareClassic, | ||||||
|  |     SubmenuIndexReadMifareDesfire, | ||||||
|  |     SubmenuIndexReadMfUltralight, | ||||||
|  |     SubmenuIndexReadEMV, | ||||||
|  |     SubmenuIndexReadNFCA, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void nfc_scene_read_card_type_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(nfc->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_read_card_type_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     Submenu* submenu = nfc->submenu; | ||||||
|  | 
 | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Read Mifare Classic", | ||||||
|  |         SubmenuIndexReadMifareClassic, | ||||||
|  |         nfc_scene_read_card_type_submenu_callback, | ||||||
|  |         nfc); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Read Mifare DESFire", | ||||||
|  |         SubmenuIndexReadMifareDesfire, | ||||||
|  |         nfc_scene_read_card_type_submenu_callback, | ||||||
|  |         nfc); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Read NTAG/Ultralight", | ||||||
|  |         SubmenuIndexReadMfUltralight, | ||||||
|  |         nfc_scene_read_card_type_submenu_callback, | ||||||
|  |         nfc); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Read EMV card", | ||||||
|  |         SubmenuIndexReadEMV, | ||||||
|  |         nfc_scene_read_card_type_submenu_callback, | ||||||
|  |         nfc); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Read NFC-A data", | ||||||
|  |         SubmenuIndexReadNFCA, | ||||||
|  |         nfc_scene_read_card_type_submenu_callback, | ||||||
|  |         nfc); | ||||||
|  |     uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadCardType); | ||||||
|  |     submenu_set_selected_item(submenu, state); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_read_card_type_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == SubmenuIndexReadMifareClassic) { | ||||||
|  |             nfc->dev->dev_data.read_mode = NfcReadModeMfClassic; | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |         if(event.event == SubmenuIndexReadMifareDesfire) { | ||||||
|  |             nfc->dev->dev_data.read_mode = NfcReadModeMfDesfire; | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |         if(event.event == SubmenuIndexReadMfUltralight) { | ||||||
|  |             nfc->dev->dev_data.read_mode = NfcReadModeMfUltralight; | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |         if(event.event == SubmenuIndexReadEMV) { | ||||||
|  |             nfc->dev->dev_data.read_mode = NfcReadModeEMV; | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |         if(event.event == SubmenuIndexReadNFCA) { | ||||||
|  |             nfc->dev->dev_data.read_mode = NfcReadModeNFCA; | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, event.event); | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_read_card_type_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     submenu_reset(nfc->submenu); | ||||||
|  | } | ||||||
| @ -62,7 +62,7 @@ bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { | |||||||
|                 nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; |                 nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; | ||||||
|             } |             } | ||||||
|             strlcpy(nfc->dev->dev_name, nfc->text_store, strlen(nfc->text_store) + 1); |             strlcpy(nfc->dev->dev_name, nfc->text_store, strlen(nfc->text_store) + 1); | ||||||
|             if(nfc_device_save(nfc->dev, nfc->text_store)) { |             if(nfc_save_file(nfc)) { | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); | ||||||
|                 if(!scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { |                 if(!scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { | ||||||
|                     // Nothing, do not count editing as saving
 |                     // Nothing, do not count editing as saving
 | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { | |||||||
|         if(event.event == NfcCustomEventByteInputDone) { |         if(event.event == NfcCustomEventByteInputDone) { | ||||||
|             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { |             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { | ||||||
|                 nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; |                 nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; | ||||||
|                 if(nfc_device_save(nfc->dev, nfc->dev->dev_name)) { |                 if(nfc_save_file(nfc)) { | ||||||
|                     scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); |                     scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess); | ||||||
|                     consumed = true; |                     consumed = true; | ||||||
|                 } |                 } | ||||||
| @ -41,6 +41,7 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "../nfc_i.h" | #include "../nfc_i.h" | ||||||
|  | #include "nfc_worker_i.h" | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| 
 | 
 | ||||||
| enum SubmenuIndex { | enum SubmenuIndex { | ||||||
| @ -47,6 +48,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == SubmenuIndexRead) { |         if(event.event == SubmenuIndexRead) { | ||||||
|  |             nfc->dev->dev_data.read_mode = NfcReadModeAuto; | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); | ||||||
|             DOLPHIN_DEED(DolphinDeedNfcRead); |             DOLPHIN_DEED(DolphinDeedNfcRead); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|  | |||||||
| @ -0,0 +1,27 @@ | |||||||
|  | #include "subghz_frequency_analyzer_log_item_array.h" | ||||||
|  | 
 | ||||||
|  | const char* | ||||||
|  |     subghz_frequency_analyzer_log_get_order_name(SubGhzFrequencyAnalyzerLogOrderBy order_by) { | ||||||
|  |     if(order_by == SubGhzFrequencyAnalyzerLogOrderBySeqAsc) { | ||||||
|  |         return "Seq. A"; | ||||||
|  |     } | ||||||
|  |     if(order_by == SubGhzFrequencyAnalyzerLogOrderByCountDesc) { | ||||||
|  |         return "Count D"; | ||||||
|  |     } | ||||||
|  |     if(order_by == SubGhzFrequencyAnalyzerLogOrderByCountAsc) { | ||||||
|  |         return "Count A"; | ||||||
|  |     } | ||||||
|  |     if(order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIDesc) { | ||||||
|  |         return "RSSI D"; | ||||||
|  |     } | ||||||
|  |     if(order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIAsc) { | ||||||
|  |         return "RSSI A"; | ||||||
|  |     } | ||||||
|  |     if(order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyDesc) { | ||||||
|  |         return "Freq. D"; | ||||||
|  |     } | ||||||
|  |     if(order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc) { | ||||||
|  |         return "Freq. A"; | ||||||
|  |     } | ||||||
|  |     return "Seq. D"; | ||||||
|  | } | ||||||
| @ -0,0 +1,73 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <m-tuple.h> | ||||||
|  | #include <m-array.h> | ||||||
|  | #include <m-algo.h> | ||||||
|  | #include <m-funcobj.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     SubGhzFrequencyAnalyzerLogOrderBySeqDesc, | ||||||
|  |     SubGhzFrequencyAnalyzerLogOrderBySeqAsc, | ||||||
|  |     SubGhzFrequencyAnalyzerLogOrderByCountDesc, | ||||||
|  |     SubGhzFrequencyAnalyzerLogOrderByCountAsc, | ||||||
|  |     SubGhzFrequencyAnalyzerLogOrderByRSSIDesc, | ||||||
|  |     SubGhzFrequencyAnalyzerLogOrderByRSSIAsc, | ||||||
|  |     SubGhzFrequencyAnalyzerLogOrderByFrequencyDesc, | ||||||
|  |     SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc, | ||||||
|  | } SubGhzFrequencyAnalyzerLogOrderBy; | ||||||
|  | 
 | ||||||
|  | const char* | ||||||
|  |     subghz_frequency_analyzer_log_get_order_name(SubGhzFrequencyAnalyzerLogOrderBy order_by); | ||||||
|  | 
 | ||||||
|  | TUPLE_DEF2( | ||||||
|  |     SubGhzFrequencyAnalyzerLogItem, | ||||||
|  |     (seq, uint8_t), | ||||||
|  |     (frequency, uint32_t), | ||||||
|  |     (count, uint8_t), | ||||||
|  |     (rssi_max, uint8_t)) | ||||||
|  | /* Register globaly the oplist */ | ||||||
|  | #define M_OPL_SubGhzFrequencyAnalyzerLogItem_t() \ | ||||||
|  |     TUPLE_OPLIST(SubGhzFrequencyAnalyzerLogItem, M_POD_OPLIST, M_DEFAULT_OPLIST, M_DEFAULT_OPLIST) | ||||||
|  | 
 | ||||||
|  | /* Define the array, register the oplist and define further algorithms on it */ | ||||||
|  | ARRAY_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItem_t) | ||||||
|  | #define M_OPL_SubGhzFrequencyAnalyzerLogItemArray_t() \ | ||||||
|  |     ARRAY_OPLIST(SubGhzFrequencyAnalyzerLogItemArray, M_OPL_SubGhzFrequencyAnalyzerLogItem_t()) | ||||||
|  | ALGO_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItemArray_t) | ||||||
|  | 
 | ||||||
|  | FUNC_OBJ_INS_DEF( | ||||||
|  |     SubGhzFrequencyAnalyzerLogItemArray_compare_by /* name of the instance */, | ||||||
|  |     SubGhzFrequencyAnalyzerLogItemArray_cmp_obj /* name of the interface */, | ||||||
|  |     (a, | ||||||
|  |      b) /* name of the input parameters of the function like object. The type are inherited from the interface. */ | ||||||
|  |     , | ||||||
|  |     { | ||||||
|  |         /* code of the function object */ | ||||||
|  |         if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc) { | ||||||
|  |             return a->frequency < b->frequency ? -1 : a->frequency > b->frequency; | ||||||
|  |         } | ||||||
|  |         if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByFrequencyDesc) { | ||||||
|  |             return a->frequency > b->frequency ? -1 : a->frequency < b->frequency; | ||||||
|  |         } | ||||||
|  |         if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIAsc) { | ||||||
|  |             return a->rssi_max < b->rssi_max ? -1 : a->rssi_max > b->rssi_max; | ||||||
|  |         } | ||||||
|  |         if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByRSSIDesc) { | ||||||
|  |             return a->rssi_max > b->rssi_max ? -1 : a->rssi_max < b->rssi_max; | ||||||
|  |         } | ||||||
|  |         if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByCountAsc) { | ||||||
|  |             return a->count < b->count ? -1 : a->count > b->count; | ||||||
|  |         } | ||||||
|  |         if(self->order_by == SubGhzFrequencyAnalyzerLogOrderByCountDesc) { | ||||||
|  |             return a->count > b->count ? -1 : a->count < b->count; | ||||||
|  |         } | ||||||
|  |         if(self->order_by == SubGhzFrequencyAnalyzerLogOrderBySeqAsc) { | ||||||
|  |             return a->seq < b->seq ? -1 : a->seq > b->seq; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return a->seq > b->seq ? -1 : a->seq < b->seq; | ||||||
|  |     }, | ||||||
|  |     /* Additional fields stored in the function object */ | ||||||
|  |     (order_by, SubGhzFrequencyAnalyzerLogOrderBy)) | ||||||
|  | #define M_OPL_SubGhzFrequencyAnalyzerLogItemArray_compare_by_t() \ | ||||||
|  |     FUNC_OBJ_INS_OPLIST(SubGhzFrequencyAnalyzerLogItemArray_compare_by, M_DEFAULT_OPLIST) | ||||||
| @ -38,18 +38,34 @@ bool subghz_scene_more_raw_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         if(event.event == SubmenuIndexDelete) { |         if(event.event == SubmenuIndexDelete) { | ||||||
|             scene_manager_set_scene_state( |             if(subghz_file_available(subghz)) { | ||||||
|                 subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); |                 scene_manager_set_scene_state( | ||||||
|             scene_manager_set_scene_state( |                     subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); | ||||||
|                 subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexDelete); |                 scene_manager_set_scene_state( | ||||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW); |                     subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexDelete); | ||||||
|             return true; |                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW); | ||||||
|  |                 return true; | ||||||
|  |             } else { | ||||||
|  |                 if(!scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                        subghz->scene_manager, SubGhzSceneStart)) { | ||||||
|  |                     scene_manager_stop(subghz->scene_manager); | ||||||
|  |                     view_dispatcher_stop(subghz->view_dispatcher); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } else if(event.event == SubmenuIndexEdit) { |         } else if(event.event == SubmenuIndexEdit) { | ||||||
|             furi_string_reset(subghz->file_path_tmp); |             if(subghz_file_available(subghz)) { | ||||||
|             scene_manager_set_scene_state( |                 furi_string_reset(subghz->file_path_tmp); | ||||||
|                 subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit); |                 scene_manager_set_scene_state( | ||||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); |                     subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit); | ||||||
|             return true; |                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); | ||||||
|  |                 return true; | ||||||
|  |             } else { | ||||||
|  |                 if(!scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                        subghz->scene_manager, SubGhzSceneStart)) { | ||||||
|  |                     scene_manager_stop(subghz->scene_manager); | ||||||
|  |                     view_dispatcher_stop(subghz->view_dispatcher); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
|  | |||||||
| @ -198,20 +198,28 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { | |||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case SubGhzCustomEventViewReadRAWMore: |         case SubGhzCustomEventViewReadRAWMore: | ||||||
|             if(subghz_scene_read_raw_update_filename(subghz)) { |             if(subghz_file_available(subghz)) { | ||||||
|                 scene_manager_set_scene_state( |                 if(subghz_scene_read_raw_update_filename(subghz)) { | ||||||
|                     subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); |                     scene_manager_set_scene_state( | ||||||
|                 subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; |                         subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSet); | ||||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW); |                     subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; | ||||||
|                 consumed = true; |                     scene_manager_next_scene(subghz->scene_manager, SubGhzSceneMoreRAW); | ||||||
|  |                     consumed = true; | ||||||
|  |                 } else { | ||||||
|  |                     furi_crash("SubGhz: RAW file name update error."); | ||||||
|  |                 } | ||||||
|             } else { |             } else { | ||||||
|                 furi_crash("SubGhz: RAW file name update error."); |                 if(!scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                        subghz->scene_manager, SubGhzSceneStart)) { | ||||||
|  |                     scene_manager_stop(subghz->scene_manager); | ||||||
|  |                     view_dispatcher_stop(subghz->view_dispatcher); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case SubGhzCustomEventViewReadRAWSendStart: |         case SubGhzCustomEventViewReadRAWSendStart: | ||||||
| 
 | 
 | ||||||
|             if(subghz_scene_read_raw_update_filename(subghz)) { |             if(subghz_file_available(subghz) && subghz_scene_read_raw_update_filename(subghz)) { | ||||||
|                 //start send
 |                 //start send
 | ||||||
|                 subghz->state_notifications = SubGhzNotificationStateIDLE; |                 subghz->state_notifications = SubGhzNotificationStateIDLE; | ||||||
|                 if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { |                 if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { | ||||||
| @ -238,6 +246,12 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { | |||||||
|                         subghz->state_notifications = SubGhzNotificationStateTx; |                         subghz->state_notifications = SubGhzNotificationStateTx; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } else { | ||||||
|  |                 if(!scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                        subghz->scene_manager, SubGhzSceneStart)) { | ||||||
|  |                     scene_manager_stop(subghz->scene_manager); | ||||||
|  |                     view_dispatcher_stop(subghz->view_dispatcher); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| @ -314,11 +328,17 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { | |||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case SubGhzCustomEventViewReadRAWSave: |         case SubGhzCustomEventViewReadRAWSave: | ||||||
|             if(subghz_scene_read_raw_update_filename(subghz)) { |             if(subghz_file_available(subghz) && subghz_scene_read_raw_update_filename(subghz)) { | ||||||
|                 scene_manager_set_scene_state( |                 scene_manager_set_scene_state( | ||||||
|                     subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSetRAW); |                     subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerSetRAW); | ||||||
|                 subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; |                 subghz->txrx->rx_key_state = SubGhzRxKeyStateBack; | ||||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); |                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); | ||||||
|  |             } else { | ||||||
|  |                 if(!scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                        subghz->scene_manager, SubGhzSceneStart)) { | ||||||
|  |                     scene_manager_stop(subghz->scene_manager); | ||||||
|  |                     view_dispatcher_stop(subghz->view_dispatcher); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|  | |||||||
| @ -155,9 +155,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) | |||||||
|         } else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) { |         } else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) { | ||||||
|             //CC1101 Stop RX -> Save
 |             //CC1101 Stop RX -> Save
 | ||||||
|             subghz->state_notifications = SubGhzNotificationStateIDLE; |             subghz->state_notifications = SubGhzNotificationStateIDLE; | ||||||
|             if(subghz->txrx->hopper_state != SubGhzHopperStateOFF) { |             subghz->txrx->hopper_state = SubGhzHopperStateOFF; | ||||||
|                 subghz->txrx->hopper_state = SubGhzHopperStateOFF; |  | ||||||
|             } |  | ||||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { |             if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) { | ||||||
|                 subghz_rx_end(subghz); |                 subghz_rx_end(subghz); | ||||||
|                 subghz_sleep(subghz); |                 subghz_sleep(subghz); | ||||||
|  | |||||||
| @ -40,9 +40,8 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { | |||||||
|             bool result = false; |             bool result = false; | ||||||
|             if((subghz->txrx->txrx_state == SubGhzTxRxStateSleep) && |             if((subghz->txrx->txrx_state == SubGhzTxRxStateSleep) && | ||||||
|                (state == SubGhzRpcStateLoaded)) { |                (state == SubGhzRpcStateLoaded)) { | ||||||
|                 subghz_blink_start(subghz); |  | ||||||
|                 result = subghz_tx_start(subghz, subghz->txrx->fff_data); |                 result = subghz_tx_start(subghz, subghz->txrx->fff_data); | ||||||
|                 result = true; |                 if(result) subghz_blink_start(subghz); | ||||||
|             } |             } | ||||||
|             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); |             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); | ||||||
|         } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { |         } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { | ||||||
|  | |||||||
| @ -490,6 +490,23 @@ bool subghz_rename_file(SubGhz* subghz) { | |||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool subghz_file_available(SubGhz* subghz) { | ||||||
|  |     furi_assert(subghz); | ||||||
|  |     bool ret = true; | ||||||
|  |     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||||
|  | 
 | ||||||
|  |     FS_Error fs_result = | ||||||
|  |         storage_common_stat(storage, furi_string_get_cstr(subghz->file_path), NULL); | ||||||
|  | 
 | ||||||
|  |     if(fs_result != FSE_OK) { | ||||||
|  |         dialog_message_show_storage_error(subghz->dialogs, "File not available\n file/directory"); | ||||||
|  |         ret = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_record_close(RECORD_STORAGE); | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool subghz_delete_file(SubGhz* subghz) { | bool subghz_delete_file(SubGhz* subghz) { | ||||||
|     furi_assert(subghz); |     furi_assert(subghz); | ||||||
| 
 | 
 | ||||||
| @ -513,12 +530,6 @@ bool subghz_path_is_file(FuriString* path) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| uint32_t subghz_random_serial(void) { | uint32_t subghz_random_serial(void) { | ||||||
|     static bool rand_generator_inited = false; |  | ||||||
| 
 |  | ||||||
|     if(!rand_generator_inited) { |  | ||||||
|         srand(DWT->CYCCNT); |  | ||||||
|         rand_generator_inited = true; |  | ||||||
|     } |  | ||||||
|     return (uint32_t)rand(); |     return (uint32_t)rand(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -124,6 +124,7 @@ bool subghz_save_protocol_to_file( | |||||||
|     const char* dev_file_name); |     const char* dev_file_name); | ||||||
| bool subghz_load_protocol_from_file(SubGhz* subghz); | bool subghz_load_protocol_from_file(SubGhz* subghz); | ||||||
| bool subghz_rename_file(SubGhz* subghz); | bool subghz_rename_file(SubGhz* subghz); | ||||||
|  | bool subghz_file_available(SubGhz* subghz); | ||||||
| bool subghz_delete_file(SubGhz* subghz); | bool subghz_delete_file(SubGhz* subghz); | ||||||
| void subghz_file_name_clear(SubGhz* subghz); | void subghz_file_name_clear(SubGhz* subghz); | ||||||
| bool subghz_path_is_file(FuriString* path); | bool subghz_path_is_file(FuriString* path); | ||||||
|  | |||||||
| @ -5,30 +5,53 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| #include <input/input.h> | #include <input/input.h> | ||||||
|  | #include <gui/elements.h> | ||||||
| #include <notification/notification_messages.h> | #include <notification/notification_messages.h> | ||||||
| #include "../helpers/subghz_frequency_analyzer_worker.h" | #include "../helpers/subghz_frequency_analyzer_worker.h" | ||||||
|  | #include "../helpers/subghz_frequency_analyzer_log_item_array.h" | ||||||
| 
 | 
 | ||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| 
 | 
 | ||||||
|  | #define LOG_FREQUENCY_MAX_ITEMS 60 // uint8_t (limited by 'seq' of SubGhzFrequencyAnalyzerLogItem)
 | ||||||
|  | #define RSSI_OFFSET 74 | ||||||
|  | #define RSSI_MAX 53 // 127 - RSSI_OFFSET
 | ||||||
|  | 
 | ||||||
|  | #define SNPRINTF_FREQUENCY(buff, freq) \ | ||||||
|  |     snprintf(buff, sizeof(buff), "%03ld.%03ld", freq / 1000000 % 1000, freq / 1000 % 1000); | ||||||
|  | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     SubGhzFrequencyAnalyzerStatusIDLE, |     SubGhzFrequencyAnalyzerStatusIDLE, | ||||||
| } SubGhzFrequencyAnalyzerStatus; | } SubGhzFrequencyAnalyzerStatus; | ||||||
| 
 | 
 | ||||||
|  | typedef enum { | ||||||
|  |     SubGhzFrequencyAnalyzerFragmentBottomTypeMain, | ||||||
|  |     SubGhzFrequencyAnalyzerFragmentBottomTypeLog, | ||||||
|  | } SubGhzFrequencyAnalyzerFragmentBottomType; | ||||||
|  | 
 | ||||||
| struct SubGhzFrequencyAnalyzer { | struct SubGhzFrequencyAnalyzer { | ||||||
|     View* view; |     View* view; | ||||||
|     SubGhzFrequencyAnalyzerWorker* worker; |     SubGhzFrequencyAnalyzerWorker* worker; | ||||||
|     SubGhzFrequencyAnalyzerCallback callback; |     SubGhzFrequencyAnalyzerCallback callback; | ||||||
|     void* context; |     void* context; | ||||||
|     bool locked; |     bool locked; | ||||||
|  |     uint32_t last_frequency; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint32_t frequency; |     uint32_t frequency; | ||||||
|     float rssi; |     uint8_t rssi; | ||||||
|     uint32_t history_frequency[3]; |     uint32_t history_frequency[3]; | ||||||
|     bool signal; |     bool signal; | ||||||
|  |     SubGhzFrequencyAnalyzerLogItemArray_t log_frequency; | ||||||
|  |     SubGhzFrequencyAnalyzerFragmentBottomType fragment_bottom_type; | ||||||
|  |     SubGhzFrequencyAnalyzerLogOrderBy log_frequency_order_by; | ||||||
|  |     uint8_t log_frequency_scroll_offset; | ||||||
| } SubGhzFrequencyAnalyzerModel; | } SubGhzFrequencyAnalyzerModel; | ||||||
| 
 | 
 | ||||||
|  | static inline uint8_t rssi_sanitize(float rssi) { | ||||||
|  |     return (rssi * -1.0f) - RSSI_OFFSET; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void subghz_frequency_analyzer_set_callback( | void subghz_frequency_analyzer_set_callback( | ||||||
|     SubGhzFrequencyAnalyzer* subghz_frequency_analyzer, |     SubGhzFrequencyAnalyzer* subghz_frequency_analyzer, | ||||||
|     SubGhzFrequencyAnalyzerCallback callback, |     SubGhzFrequencyAnalyzerCallback callback, | ||||||
| @ -39,13 +62,11 @@ void subghz_frequency_analyzer_set_callback( | |||||||
|     subghz_frequency_analyzer->context = context; |     subghz_frequency_analyzer->context = context; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, float rssi) { | void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, uint8_t rssi, uint8_t x, uint8_t y) { | ||||||
|     uint8_t x = 20; |  | ||||||
|     uint8_t y = 64; |  | ||||||
|     uint8_t column_number = 0; |     uint8_t column_number = 0; | ||||||
|     if(rssi) { |     if(rssi) { | ||||||
|         rssi = (rssi + 90) / 3; |         rssi = rssi / 3; | ||||||
|         for(size_t i = 1; i < (uint8_t)rssi; i++) { |         for(uint8_t i = 1; i < rssi; i++) { | ||||||
|             if(i > 20) break; |             if(i > 20) break; | ||||||
|             if(i % 4) { |             if(i % 4) { | ||||||
|                 column_number++; |                 column_number++; | ||||||
| @ -55,6 +76,54 @@ void subghz_frequency_analyzer_draw_rssi(Canvas* canvas, float rssi) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void subghz_frequency_analyzer_log_frequency_draw( | ||||||
|  |     Canvas* canvas, | ||||||
|  |     SubGhzFrequencyAnalyzerModel* model) { | ||||||
|  |     char buffer[64]; | ||||||
|  |     const uint8_t offset_x = 0; | ||||||
|  |     const uint8_t offset_y = 43; | ||||||
|  |     canvas_set_font(canvas, FontKeyboard); | ||||||
|  | 
 | ||||||
|  |     const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); | ||||||
|  |     if(items_count == 0) { | ||||||
|  |         canvas_draw_rframe(canvas, offset_x + 27u, offset_y - 3u, 73u, 16u, 5u); | ||||||
|  |         canvas_draw_str_aligned( | ||||||
|  |             canvas, offset_x + 64u, offset_y + 8u, AlignCenter, AlignBottom, "No records"); | ||||||
|  |         return; | ||||||
|  |     } else if(items_count > 3) { | ||||||
|  |         elements_scrollbar_pos( | ||||||
|  |             canvas, | ||||||
|  |             offset_x + 127, | ||||||
|  |             offset_y - 8, | ||||||
|  |             29, | ||||||
|  |             model->log_frequency_scroll_offset, | ||||||
|  |             items_count - 2); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     SubGhzFrequencyAnalyzerLogItem_t* log_frequency_item; | ||||||
|  |     for(uint8_t i = 0; i < 3; ++i) { | ||||||
|  |         const uint8_t item_pos = model->log_frequency_scroll_offset + i; | ||||||
|  |         if(item_pos >= items_count) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         log_frequency_item = | ||||||
|  |             SubGhzFrequencyAnalyzerLogItemArray_get(model->log_frequency, item_pos); | ||||||
|  |         // Frequency
 | ||||||
|  |         SNPRINTF_FREQUENCY(buffer, (*log_frequency_item)->frequency) | ||||||
|  |         canvas_draw_str(canvas, offset_x, offset_y + i * 10, buffer); | ||||||
|  | 
 | ||||||
|  |         // Count
 | ||||||
|  |         snprintf(buffer, sizeof(buffer), "%3d", (*log_frequency_item)->count); | ||||||
|  |         canvas_draw_str(canvas, offset_x + 48, offset_y + i * 10, buffer); | ||||||
|  | 
 | ||||||
|  |         // Max RSSI
 | ||||||
|  |         subghz_frequency_analyzer_draw_rssi( | ||||||
|  |             canvas, (*log_frequency_item)->rssi_max, offset_x + 69, (offset_y + i * 10)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void subghz_frequency_analyzer_history_frequency_draw( | static void subghz_frequency_analyzer_history_frequency_draw( | ||||||
|     Canvas* canvas, |     Canvas* canvas, | ||||||
|     SubGhzFrequencyAnalyzerModel* model) { |     SubGhzFrequencyAnalyzerModel* model) { | ||||||
| @ -65,12 +134,7 @@ static void subghz_frequency_analyzer_history_frequency_draw( | |||||||
|     canvas_set_font(canvas, FontKeyboard); |     canvas_set_font(canvas, FontKeyboard); | ||||||
|     for(uint8_t i = 0; i < 3; i++) { |     for(uint8_t i = 0; i < 3; i++) { | ||||||
|         if(model->history_frequency[i]) { |         if(model->history_frequency[i]) { | ||||||
|             snprintf( |             SNPRINTF_FREQUENCY(buffer, model->history_frequency[i]) | ||||||
|                 buffer, |  | ||||||
|                 sizeof(buffer), |  | ||||||
|                 "%03ld.%03ld", |  | ||||||
|                 model->history_frequency[i] / 1000000 % 1000, |  | ||||||
|                 model->history_frequency[i] / 1000 % 1000); |  | ||||||
|             canvas_draw_str(canvas, x, y + i * 10, buffer); |             canvas_draw_str(canvas, x, y + i * 10, buffer); | ||||||
|         } else { |         } else { | ||||||
|             canvas_draw_str(canvas, x, y + i * 10, "---.---"); |             canvas_draw_str(canvas, x, y + i * 10, "---.---"); | ||||||
| @ -81,18 +145,34 @@ static void subghz_frequency_analyzer_history_frequency_draw( | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) { | void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel* model) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     furi_assert(model); | ||||||
|     char buffer[64]; |     char buffer[64]; | ||||||
| 
 | 
 | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
|     canvas_set_font(canvas, FontSecondary); |     canvas_set_font(canvas, FontSecondary); | ||||||
|     canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); |  | ||||||
| 
 | 
 | ||||||
|     canvas_draw_str(canvas, 0, 64, "RSSI"); |     if(model->fragment_bottom_type == SubGhzFrequencyAnalyzerFragmentBottomTypeLog) { | ||||||
|     subghz_frequency_analyzer_draw_rssi(canvas, model->rssi); |         const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); | ||||||
|  |         const char* log_order_by_name = | ||||||
|  |             subghz_frequency_analyzer_log_get_order_name(model->log_frequency_order_by); | ||||||
|  |         if(items_count < LOG_FREQUENCY_MAX_ITEMS) { | ||||||
|  |             snprintf(buffer, sizeof(buffer), "Frequency Analyzer [%s]", log_order_by_name); | ||||||
|  |             canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignBottom, buffer); | ||||||
|  |         } else { | ||||||
|  |             snprintf(buffer, sizeof(buffer), "The log is full! [%s]", log_order_by_name); | ||||||
|  |             canvas_draw_str(canvas, 2, 8, buffer); | ||||||
|  |         } | ||||||
|  |         subghz_frequency_analyzer_log_frequency_draw(canvas, model); | ||||||
|  |     } else { | ||||||
|  |         canvas_draw_str(canvas, 20, 8, "Frequency Analyzer"); | ||||||
|  |         canvas_draw_str(canvas, 0, 64, "RSSI"); | ||||||
|  |         subghz_frequency_analyzer_draw_rssi(canvas, model->rssi, 20u, 64u); | ||||||
| 
 | 
 | ||||||
|     subghz_frequency_analyzer_history_frequency_draw(canvas, model); |         subghz_frequency_analyzer_history_frequency_draw(canvas, model); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     //Frequency
 |     // Frequency
 | ||||||
|     canvas_set_font(canvas, FontBigNumbers); |     canvas_set_font(canvas, FontBigNumbers); | ||||||
|     snprintf( |     snprintf( | ||||||
|         buffer, |         buffer, | ||||||
| @ -103,23 +183,151 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel | |||||||
|     if(model->signal) { |     if(model->signal) { | ||||||
|         canvas_draw_box(canvas, 4, 12, 121, 22); |         canvas_draw_box(canvas, 4, 12, 121, 22); | ||||||
|         canvas_set_color(canvas, ColorWhite); |         canvas_set_color(canvas, ColorWhite); | ||||||
|     } else { |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     canvas_draw_str(canvas, 8, 30, buffer); |     canvas_draw_str(canvas, 8, 30, buffer); | ||||||
|     canvas_draw_icon(canvas, 96, 19, &I_MHz_25x11); |     canvas_draw_icon(canvas, 96, 19, &I_MHz_25x11); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void subghz_frequency_analyzer_log_frequency_sort(SubGhzFrequencyAnalyzerModel* model) { | ||||||
|  |     furi_assert(model); | ||||||
|  |     M_LET((cmp, model->log_frequency_order_by), SubGhzFrequencyAnalyzerLogItemArray_compare_by_t) | ||||||
|  |     SubGhzFrequencyAnalyzerLogItemArray_sort_fo( | ||||||
|  |         model->log_frequency, SubGhzFrequencyAnalyzerLogItemArray_compare_by_as_interface(cmp)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { | bool subghz_frequency_analyzer_input(InputEvent* event, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|  |     SubGhzFrequencyAnalyzer* instance = context; | ||||||
| 
 | 
 | ||||||
|     if(event->key == InputKeyBack) { |     if(event->key == InputKeyBack) { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if((event->type == InputTypeShort) && | ||||||
|  |        ((event->key == InputKeyLeft) || (event->key == InputKeyRight))) { | ||||||
|  |         with_view_model( | ||||||
|  |             instance->view, | ||||||
|  |             SubGhzFrequencyAnalyzerModel * model, | ||||||
|  |             { | ||||||
|  |                 if(event->key == InputKeyLeft) { | ||||||
|  |                     if(model->fragment_bottom_type == 0) { | ||||||
|  |                         model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeLog; | ||||||
|  |                     } else { | ||||||
|  |                         --model->fragment_bottom_type; | ||||||
|  |                     } | ||||||
|  |                 } else if(event->key == InputKeyRight) { | ||||||
|  |                     if(model->fragment_bottom_type == | ||||||
|  |                        SubGhzFrequencyAnalyzerFragmentBottomTypeLog) { | ||||||
|  |                         model->fragment_bottom_type = 0; | ||||||
|  |                     } else { | ||||||
|  |                         ++model->fragment_bottom_type; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             true); | ||||||
|  |     } else if((event->type == InputTypeShort) && (event->key == InputKeyOk)) { | ||||||
|  |         with_view_model( | ||||||
|  |             instance->view, | ||||||
|  |             SubGhzFrequencyAnalyzerModel * model, | ||||||
|  |             { | ||||||
|  |                 if(model->fragment_bottom_type == SubGhzFrequencyAnalyzerFragmentBottomTypeLog) { | ||||||
|  |                     ++model->log_frequency_order_by; | ||||||
|  |                     if(model->log_frequency_order_by > | ||||||
|  |                        SubGhzFrequencyAnalyzerLogOrderByFrequencyAsc) { | ||||||
|  |                         model->log_frequency_order_by = 0; | ||||||
|  |                     } | ||||||
|  |                     subghz_frequency_analyzer_log_frequency_sort(model); | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             true); | ||||||
|  |     } else if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { | ||||||
|  |         with_view_model( | ||||||
|  |             instance->view, | ||||||
|  |             SubGhzFrequencyAnalyzerModel * model, | ||||||
|  |             { | ||||||
|  |                 if(model->fragment_bottom_type == SubGhzFrequencyAnalyzerFragmentBottomTypeLog) { | ||||||
|  |                     if(event->key == InputKeyUp) { | ||||||
|  |                         if(model->log_frequency_scroll_offset > 0) { | ||||||
|  |                             --model->log_frequency_scroll_offset; | ||||||
|  |                         } | ||||||
|  |                     } else if(event->key == InputKeyDown) { | ||||||
|  |                         const size_t items_count = | ||||||
|  |                             SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); | ||||||
|  |                         if((model->log_frequency_scroll_offset + 3u) < items_count) { | ||||||
|  |                             ++model->log_frequency_scroll_offset; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void subghz_frequency_analyzer_log_frequency_search_it( | ||||||
|  |     SubGhzFrequencyAnalyzerLogItemArray_it_t* itref, | ||||||
|  |     SubGhzFrequencyAnalyzerLogItemArray_t* log_frequency, | ||||||
|  |     uint32_t frequency) { | ||||||
|  |     furi_assert(log_frequency); | ||||||
|  | 
 | ||||||
|  |     SubGhzFrequencyAnalyzerLogItemArray_it(*itref, *log_frequency); | ||||||
|  |     SubGhzFrequencyAnalyzerLogItem_t* item; | ||||||
|  |     while(!SubGhzFrequencyAnalyzerLogItemArray_end_p(*itref)) { | ||||||
|  |         item = SubGhzFrequencyAnalyzerLogItemArray_ref(*itref); | ||||||
|  |         if((*item)->frequency == frequency) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         SubGhzFrequencyAnalyzerLogItemArray_next(*itref); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool subghz_frequency_analyzer_log_frequency_insert(SubGhzFrequencyAnalyzerModel* model) { | ||||||
|  |     furi_assert(model); | ||||||
|  |     const size_t items_count = SubGhzFrequencyAnalyzerLogItemArray_size(model->log_frequency); | ||||||
|  |     if(items_count < LOG_FREQUENCY_MAX_ITEMS) { | ||||||
|  |         SubGhzFrequencyAnalyzerLogItem_t* item = | ||||||
|  |             SubGhzFrequencyAnalyzerLogItemArray_push_new(model->log_frequency); | ||||||
|  |         if(item == NULL) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         (*item)->frequency = model->frequency; | ||||||
|  |         (*item)->count = 1u; | ||||||
|  |         (*item)->rssi_max = model->rssi; | ||||||
|  |         (*item)->seq = items_count; | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void subghz_frequency_analyzer_log_frequency_update( | ||||||
|  |     SubGhzFrequencyAnalyzerModel* model, | ||||||
|  |     bool need_insert) { | ||||||
|  |     furi_assert(model); | ||||||
|  |     if(!model->frequency) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     SubGhzFrequencyAnalyzerLogItemArray_it_t it; | ||||||
|  |     subghz_frequency_analyzer_log_frequency_search_it( | ||||||
|  |         &it, &model->log_frequency, model->frequency); | ||||||
|  |     if(!SubGhzFrequencyAnalyzerLogItemArray_end_p(it)) { | ||||||
|  |         SubGhzFrequencyAnalyzerLogItem_t* item = SubGhzFrequencyAnalyzerLogItemArray_ref(it); | ||||||
|  |         if((*item)->rssi_max < model->rssi) { | ||||||
|  |             (*item)->rssi_max = model->rssi; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(need_insert && (*item)->count < UINT8_MAX) { | ||||||
|  |             ++(*item)->count; | ||||||
|  |             subghz_frequency_analyzer_log_frequency_sort(model); | ||||||
|  |         } | ||||||
|  |     } else if(need_insert) { | ||||||
|  |         if(subghz_frequency_analyzer_log_frequency_insert(model)) { | ||||||
|  |             subghz_frequency_analyzer_log_frequency_sort(model); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void subghz_frequency_analyzer_pair_callback( | void subghz_frequency_analyzer_pair_callback( | ||||||
|     void* context, |     void* context, | ||||||
|     uint32_t frequency, |     uint32_t frequency, | ||||||
| @ -130,6 +338,7 @@ void subghz_frequency_analyzer_pair_callback( | |||||||
|         if(instance->callback) { |         if(instance->callback) { | ||||||
|             instance->callback(SubGhzCustomEventSceneAnalyzerUnlock, instance->context); |             instance->callback(SubGhzCustomEventSceneAnalyzerUnlock, instance->context); | ||||||
|         } |         } | ||||||
|  |         instance->last_frequency = 0; | ||||||
|         //update history
 |         //update history
 | ||||||
|         with_view_model( |         with_view_model( | ||||||
|             instance->view, |             instance->view, | ||||||
| @ -151,9 +360,14 @@ void subghz_frequency_analyzer_pair_callback( | |||||||
|         instance->view, |         instance->view, | ||||||
|         SubGhzFrequencyAnalyzerModel * model, |         SubGhzFrequencyAnalyzerModel * model, | ||||||
|         { |         { | ||||||
|             model->rssi = rssi; |             model->rssi = rssi_sanitize(rssi); | ||||||
|             model->frequency = frequency; |             model->frequency = frequency; | ||||||
|             model->signal = signal; |             model->signal = signal; | ||||||
|  |             if(frequency) { | ||||||
|  |                 subghz_frequency_analyzer_log_frequency_update( | ||||||
|  |                     model, frequency != instance->last_frequency); | ||||||
|  |                 instance->last_frequency = frequency; | ||||||
|  |             } | ||||||
|         }, |         }, | ||||||
|         true); |         true); | ||||||
| } | } | ||||||
| @ -176,11 +390,14 @@ void subghz_frequency_analyzer_enter(void* context) { | |||||||
|         instance->view, |         instance->view, | ||||||
|         SubGhzFrequencyAnalyzerModel * model, |         SubGhzFrequencyAnalyzerModel * model, | ||||||
|         { |         { | ||||||
|             model->rssi = 0; |             model->rssi = 0u; | ||||||
|             model->frequency = 0; |             model->frequency = 0; | ||||||
|             model->history_frequency[2] = 0; |             model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; | ||||||
|             model->history_frequency[1] = 0; |             model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc; | ||||||
|             model->history_frequency[0] = 0; |             model->log_frequency_scroll_offset = 0u; | ||||||
|  |             model->history_frequency[0] = model->history_frequency[1] = | ||||||
|  |                 model->history_frequency[2] = 0u; | ||||||
|  |             SubGhzFrequencyAnalyzerLogItemArray_init(model->log_frequency); | ||||||
|         }, |         }, | ||||||
|         true); |         true); | ||||||
| } | } | ||||||
| @ -196,13 +413,26 @@ void subghz_frequency_analyzer_exit(void* context) { | |||||||
|     subghz_frequency_analyzer_worker_free(instance->worker); |     subghz_frequency_analyzer_worker_free(instance->worker); | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         instance->view, SubGhzFrequencyAnalyzerModel * model, { model->rssi = 0; }, true); |         instance->view, | ||||||
|  |         SubGhzFrequencyAnalyzerModel * model, | ||||||
|  |         { | ||||||
|  |             model->rssi = 0u; | ||||||
|  |             model->frequency = 0; | ||||||
|  |             model->fragment_bottom_type = SubGhzFrequencyAnalyzerFragmentBottomTypeMain; | ||||||
|  |             model->log_frequency_order_by = SubGhzFrequencyAnalyzerLogOrderBySeqDesc; | ||||||
|  |             model->log_frequency_scroll_offset = 0u; | ||||||
|  |             model->history_frequency[0] = model->history_frequency[1] = | ||||||
|  |                 model->history_frequency[2] = 0u; | ||||||
|  |             SubGhzFrequencyAnalyzerLogItemArray_clear(model->log_frequency); | ||||||
|  |         }, | ||||||
|  |         true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { | SubGhzFrequencyAnalyzer* subghz_frequency_analyzer_alloc() { | ||||||
|     SubGhzFrequencyAnalyzer* instance = malloc(sizeof(SubGhzFrequencyAnalyzer)); |     SubGhzFrequencyAnalyzer* instance = malloc(sizeof(SubGhzFrequencyAnalyzer)); | ||||||
| 
 | 
 | ||||||
|     // View allocation and configuration
 |     // View allocation and configuration
 | ||||||
|  |     instance->last_frequency = 0; | ||||||
|     instance->view = view_alloc(); |     instance->view = view_alloc(); | ||||||
|     view_allocate_model( |     view_allocate_model( | ||||||
|         instance->view, ViewModelTypeLocking, sizeof(SubGhzFrequencyAnalyzerModel)); |         instance->view, ViewModelTypeLocking, sizeof(SubGhzFrequencyAnalyzerModel)); | ||||||
|  | |||||||
| @ -11,4 +11,5 @@ App( | |||||||
|     stack_size=2 * 1024, |     stack_size=2 * 1024, | ||||||
|     icon="A_U2F_14", |     icon="A_U2F_14", | ||||||
|     order=80, |     order=80, | ||||||
|  |     fap_libs=["assets"], | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -203,7 +203,7 @@ static int32_t u2f_hid_worker(void* context) { | |||||||
|             WorkerEvtStop | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtRequest, |             WorkerEvtStop | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtRequest, | ||||||
|             FuriFlagWaitAny, |             FuriFlagWaitAny, | ||||||
|             FuriWaitForever); |             FuriWaitForever); | ||||||
|         furi_check((flags & FuriFlagError) == 0); |         furi_check(!(flags & FuriFlagError)); | ||||||
|         if(flags & WorkerEvtStop) break; |         if(flags & WorkerEvtStop) break; | ||||||
|         if(flags & WorkerEvtConnect) { |         if(flags & WorkerEvtConnect) { | ||||||
|             u2f_set_state(u2f_hid->u2f_instance, 1); |             u2f_set_state(u2f_hid->u2f_instance, 1); | ||||||
|  | |||||||
| @ -13,6 +13,8 @@ | |||||||
| #include "dap_config.h" | #include "dap_config.h" | ||||||
| #include "gui/dap_gui.h" | #include "gui/dap_gui.h" | ||||||
| #include "usb/dap_v2_usb.h" | #include "usb/dap_v2_usb.h" | ||||||
|  | #include <dialogs/dialogs.h> | ||||||
|  | #include "dap_link_icons.h" | ||||||
| 
 | 
 | ||||||
| /***************************************************************************/ | /***************************************************************************/ | ||||||
| /****************************** DAP COMMON *********************************/ | /****************************** DAP COMMON *********************************/ | ||||||
| @ -495,6 +497,24 @@ DapConfig* dap_app_get_config(DapApp* app) { | |||||||
| int32_t dap_link_app(void* p) { | int32_t dap_link_app(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
| 
 | 
 | ||||||
|  |     if(furi_hal_usb_is_locked()) { | ||||||
|  |         DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); | ||||||
|  |         DialogMessage* message = dialog_message_alloc(); | ||||||
|  |         dialog_message_set_header(message, "Connection\nis active!", 3, 2, AlignLeft, AlignTop); | ||||||
|  |         dialog_message_set_text( | ||||||
|  |             message, | ||||||
|  |             "Disconnect from\nPC or phone to\nuse this function.", | ||||||
|  |             3, | ||||||
|  |             30, | ||||||
|  |             AlignLeft, | ||||||
|  |             AlignTop); | ||||||
|  |         dialog_message_set_icon(message, &I_ActiveConnection_50x64, 78, 0); | ||||||
|  |         dialog_message_show(dialogs, message); | ||||||
|  |         dialog_message_free(message); | ||||||
|  |         furi_record_close(RECORD_DIALOGS); | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // alloc app
 |     // alloc app
 | ||||||
|     DapApp* app = dap_app_alloc(); |     DapApp* app = dap_app_alloc(); | ||||||
|     app_handle = app; |     app_handle = app; | ||||||
|  | |||||||
| @ -72,8 +72,8 @@ void dap_scene_config_on_enter(void* context) { | |||||||
|     variable_item_set_current_value_index(item, config->uart_swap); |     variable_item_set_current_value_index(item, config->uart_swap); | ||||||
|     variable_item_set_current_value_text(item, uart_swap[config->uart_swap]); |     variable_item_set_current_value_text(item, uart_swap[config->uart_swap]); | ||||||
| 
 | 
 | ||||||
|     item = variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL); |     variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL); | ||||||
|     item = variable_item_list_add(var_item_list, "About", 0, NULL, NULL); |     variable_item_list_add(var_item_list, "About", 0, NULL, NULL); | ||||||
| 
 | 
 | ||||||
|     variable_item_list_set_selected_item( |     variable_item_list_set_selected_item( | ||||||
|         var_item_list, scene_manager_get_scene_state(app->scene_manager, DapSceneConfig)); |         var_item_list, scene_manager_get_scene_state(app->scene_manager, DapSceneConfig)); | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/dap_link/icons/ActiveConnection_50x64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								applications/plugins/dap_link/icons/ActiveConnection_50x64.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.8 KiB | 
							
								
								
									
										20
									
								
								applications/plugins/nfc_magic/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								applications/plugins/nfc_magic/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | App( | ||||||
|  |     appid="nfc_magic", | ||||||
|  |     name="Nfc Magic", | ||||||
|  |     apptype=FlipperAppType.EXTERNAL, | ||||||
|  |     entry_point="nfc_magic_app", | ||||||
|  |     requires=[ | ||||||
|  |         "storage", | ||||||
|  |         "gui", | ||||||
|  |     ], | ||||||
|  |     stack_size=4 * 1024, | ||||||
|  |     order=30, | ||||||
|  |     fap_icon="../../../assets/icons/Archive/125_10px.png", | ||||||
|  |     fap_category="Tools", | ||||||
|  |     fap_private_libs=[ | ||||||
|  |         Lib( | ||||||
|  |             name="magic", | ||||||
|  |         ), | ||||||
|  |     ], | ||||||
|  |     fap_icon_assets="assets", | ||||||
|  | ) | ||||||
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/nfc_magic/assets/DolphinCommon_56x48.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								applications/plugins/nfc_magic/assets/DolphinCommon_56x48.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/nfc_magic/assets/DolphinNice_96x59.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								applications/plugins/nfc_magic/assets/DolphinNice_96x59.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/nfc_magic/assets/Loading_24.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								applications/plugins/nfc_magic/assets/Loading_24.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/nfc_magic/assets/NFC_manual_60x50.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								applications/plugins/nfc_magic/assets/NFC_manual_60x50.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.7 KiB | 
							
								
								
									
										214
									
								
								applications/plugins/nfc_magic/lib/magic/magic.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								applications/plugins/nfc_magic/lib/magic/magic.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,214 @@ | |||||||
|  | #include "magic.h" | ||||||
|  | 
 | ||||||
|  | #include <furi_hal_nfc.h> | ||||||
|  | 
 | ||||||
|  | #define TAG "Magic" | ||||||
|  | 
 | ||||||
|  | #define MAGIC_CMD_WUPA (0x40) | ||||||
|  | #define MAGIC_CMD_WIPE (0x41) | ||||||
|  | #define MAGIC_CMD_READ (0x43) | ||||||
|  | #define MAGIC_CMD_WRITE (0x43) | ||||||
|  | 
 | ||||||
|  | #define MAGIC_MIFARE_READ_CMD (0x30) | ||||||
|  | #define MAGIC_MIFARE_WRITE_CMD (0xA0) | ||||||
|  | 
 | ||||||
|  | #define MAGIC_ACK (0x0A) | ||||||
|  | 
 | ||||||
|  | #define MAGIC_BUFFER_SIZE (32) | ||||||
|  | 
 | ||||||
|  | bool magic_wupa() { | ||||||
|  |     bool magic_activated = false; | ||||||
|  |     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|  |     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|  |     uint16_t rx_len = 0; | ||||||
|  |     FuriHalNfcReturn ret = 0; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         // Setup nfc poller
 | ||||||
|  |         furi_hal_nfc_exit_sleep(); | ||||||
|  |         furi_hal_nfc_ll_txrx_on(); | ||||||
|  |         furi_hal_nfc_ll_poll(); | ||||||
|  |         ret = furi_hal_nfc_ll_set_mode( | ||||||
|  |             FuriHalNfcModePollNfca, FuriHalNfcBitrate106, FuriHalNfcBitrate106); | ||||||
|  |         if(ret != FuriHalNfcReturnOk) break; | ||||||
|  | 
 | ||||||
|  |         furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER); | ||||||
|  |         furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER); | ||||||
|  |         furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); | ||||||
|  |         furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCA); | ||||||
|  | 
 | ||||||
|  |         // Start communication
 | ||||||
|  |         tx_data[0] = MAGIC_CMD_WUPA; | ||||||
|  |         ret = furi_hal_nfc_ll_txrx_bits( | ||||||
|  |             tx_data, | ||||||
|  |             7, | ||||||
|  |             rx_data, | ||||||
|  |             sizeof(rx_data), | ||||||
|  |             &rx_len, | ||||||
|  |             FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | | ||||||
|  |                 FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, | ||||||
|  |             furi_hal_nfc_ll_ms2fc(20)); | ||||||
|  |         if(ret != FuriHalNfcReturnIncompleteByte) break; | ||||||
|  |         if(rx_len != 4) break; | ||||||
|  |         if(rx_data[0] != MAGIC_ACK) break; | ||||||
|  |         magic_activated = true; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     if(!magic_activated) { | ||||||
|  |         furi_hal_nfc_ll_txrx_off(); | ||||||
|  |         furi_hal_nfc_start_sleep(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return magic_activated; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool magic_data_access_cmd() { | ||||||
|  |     bool write_cmd_success = false; | ||||||
|  |     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|  |     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|  |     uint16_t rx_len = 0; | ||||||
|  |     FuriHalNfcReturn ret = 0; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         tx_data[0] = MAGIC_CMD_WRITE; | ||||||
|  |         ret = furi_hal_nfc_ll_txrx_bits( | ||||||
|  |             tx_data, | ||||||
|  |             8, | ||||||
|  |             rx_data, | ||||||
|  |             sizeof(rx_data), | ||||||
|  |             &rx_len, | ||||||
|  |             FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | | ||||||
|  |                 FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, | ||||||
|  |             furi_hal_nfc_ll_ms2fc(20)); | ||||||
|  |         if(ret != FuriHalNfcReturnIncompleteByte) break; | ||||||
|  |         if(rx_len != 4) break; | ||||||
|  |         if(rx_data[0] != MAGIC_ACK) break; | ||||||
|  | 
 | ||||||
|  |         write_cmd_success = true; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     if(!write_cmd_success) { | ||||||
|  |         furi_hal_nfc_ll_txrx_off(); | ||||||
|  |         furi_hal_nfc_start_sleep(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return write_cmd_success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool magic_read_block(uint8_t block_num, MfClassicBlock* data) { | ||||||
|  |     furi_assert(data); | ||||||
|  | 
 | ||||||
|  |     bool read_success = false; | ||||||
|  | 
 | ||||||
|  |     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|  |     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|  |     uint16_t rx_len = 0; | ||||||
|  |     FuriHalNfcReturn ret = 0; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         tx_data[0] = MAGIC_MIFARE_READ_CMD; | ||||||
|  |         tx_data[1] = block_num; | ||||||
|  |         ret = furi_hal_nfc_ll_txrx_bits( | ||||||
|  |             tx_data, | ||||||
|  |             2 * 8, | ||||||
|  |             rx_data, | ||||||
|  |             sizeof(rx_data), | ||||||
|  |             &rx_len, | ||||||
|  |             FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON, | ||||||
|  |             furi_hal_nfc_ll_ms2fc(20)); | ||||||
|  | 
 | ||||||
|  |         if(ret != FuriHalNfcReturnOk) break; | ||||||
|  |         if(rx_len != 16 * 8) break; | ||||||
|  |         memcpy(data->value, rx_data, sizeof(data->value)); | ||||||
|  |         read_success = true; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     if(!read_success) { | ||||||
|  |         furi_hal_nfc_ll_txrx_off(); | ||||||
|  |         furi_hal_nfc_start_sleep(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return read_success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool magic_write_blk(uint8_t block_num, MfClassicBlock* data) { | ||||||
|  |     furi_assert(data); | ||||||
|  | 
 | ||||||
|  |     bool write_success = false; | ||||||
|  |     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|  |     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|  |     uint16_t rx_len = 0; | ||||||
|  |     FuriHalNfcReturn ret = 0; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         tx_data[0] = MAGIC_MIFARE_WRITE_CMD; | ||||||
|  |         tx_data[1] = block_num; | ||||||
|  |         ret = furi_hal_nfc_ll_txrx_bits( | ||||||
|  |             tx_data, | ||||||
|  |             2 * 8, | ||||||
|  |             rx_data, | ||||||
|  |             sizeof(rx_data), | ||||||
|  |             &rx_len, | ||||||
|  |             FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, | ||||||
|  |             furi_hal_nfc_ll_ms2fc(20)); | ||||||
|  |         if(ret != FuriHalNfcReturnIncompleteByte) break; | ||||||
|  |         if(rx_len != 4) break; | ||||||
|  |         if(rx_data[0] != MAGIC_ACK) break; | ||||||
|  | 
 | ||||||
|  |         memcpy(tx_data, data->value, sizeof(data->value)); | ||||||
|  |         ret = furi_hal_nfc_ll_txrx_bits( | ||||||
|  |             tx_data, | ||||||
|  |             16 * 8, | ||||||
|  |             rx_data, | ||||||
|  |             sizeof(rx_data), | ||||||
|  |             &rx_len, | ||||||
|  |             FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, | ||||||
|  |             furi_hal_nfc_ll_ms2fc(20)); | ||||||
|  |         if(ret != FuriHalNfcReturnIncompleteByte) break; | ||||||
|  |         if(rx_len != 4) break; | ||||||
|  |         if(rx_data[0] != MAGIC_ACK) break; | ||||||
|  | 
 | ||||||
|  |         write_success = true; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     if(!write_success) { | ||||||
|  |         furi_hal_nfc_ll_txrx_off(); | ||||||
|  |         furi_hal_nfc_start_sleep(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return write_success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool magic_wipe() { | ||||||
|  |     bool wipe_success = false; | ||||||
|  |     uint8_t tx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|  |     uint8_t rx_data[MAGIC_BUFFER_SIZE] = {}; | ||||||
|  |     uint16_t rx_len = 0; | ||||||
|  |     FuriHalNfcReturn ret = 0; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         tx_data[0] = MAGIC_CMD_WIPE; | ||||||
|  |         ret = furi_hal_nfc_ll_txrx_bits( | ||||||
|  |             tx_data, | ||||||
|  |             8, | ||||||
|  |             rx_data, | ||||||
|  |             sizeof(rx_data), | ||||||
|  |             &rx_len, | ||||||
|  |             FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | | ||||||
|  |                 FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP, | ||||||
|  |             furi_hal_nfc_ll_ms2fc(2000)); | ||||||
|  | 
 | ||||||
|  |         if(ret != FuriHalNfcReturnIncompleteByte) break; | ||||||
|  |         if(rx_len != 4) break; | ||||||
|  |         if(rx_data[0] != MAGIC_ACK) break; | ||||||
|  | 
 | ||||||
|  |         wipe_success = true; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     return wipe_success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void magic_deactivate() { | ||||||
|  |     furi_hal_nfc_ll_txrx_off(); | ||||||
|  |     furi_hal_nfc_sleep(); | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								applications/plugins/nfc_magic/lib/magic/magic.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								applications/plugins/nfc_magic/lib/magic/magic.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <lib/nfc/protocols/mifare_classic.h> | ||||||
|  | 
 | ||||||
|  | bool magic_wupa(); | ||||||
|  | 
 | ||||||
|  | bool magic_read_block(uint8_t block_num, MfClassicBlock* data); | ||||||
|  | 
 | ||||||
|  | bool magic_data_access_cmd(); | ||||||
|  | 
 | ||||||
|  | bool magic_write_blk(uint8_t block_num, MfClassicBlock* data); | ||||||
|  | 
 | ||||||
|  | bool magic_wipe(); | ||||||
|  | 
 | ||||||
|  | void magic_deactivate(); | ||||||
							
								
								
									
										169
									
								
								applications/plugins/nfc_magic/nfc_magic.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								applications/plugins/nfc_magic/nfc_magic.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,169 @@ | |||||||
|  | #include "nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_custom_event_callback(void* context, uint32_t event) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     return scene_manager_handle_custom_event(nfc_magic->scene_manager, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_back_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     return scene_manager_handle_back_event(nfc_magic->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_tick_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     scene_manager_handle_tick_event(nfc_magic->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_show_loading_popup(void* context, bool show) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); | ||||||
|  | 
 | ||||||
|  |     if(show) { | ||||||
|  |         // Raise timer priority so that animations can play
 | ||||||
|  |         vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); | ||||||
|  |         view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewLoading); | ||||||
|  |     } else { | ||||||
|  |         // Restore default timer priority
 | ||||||
|  |         vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | NfcMagic* nfc_magic_alloc() { | ||||||
|  |     NfcMagic* nfc_magic = malloc(sizeof(NfcMagic)); | ||||||
|  | 
 | ||||||
|  |     nfc_magic->worker = nfc_magic_worker_alloc(); | ||||||
|  |     nfc_magic->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     nfc_magic->scene_manager = scene_manager_alloc(&nfc_magic_scene_handlers, nfc_magic); | ||||||
|  |     view_dispatcher_enable_queue(nfc_magic->view_dispatcher); | ||||||
|  |     view_dispatcher_set_event_callback_context(nfc_magic->view_dispatcher, nfc_magic); | ||||||
|  |     view_dispatcher_set_custom_event_callback( | ||||||
|  |         nfc_magic->view_dispatcher, nfc_magic_custom_event_callback); | ||||||
|  |     view_dispatcher_set_navigation_event_callback( | ||||||
|  |         nfc_magic->view_dispatcher, nfc_magic_back_event_callback); | ||||||
|  |     view_dispatcher_set_tick_event_callback( | ||||||
|  |         nfc_magic->view_dispatcher, nfc_magic_tick_event_callback, 100); | ||||||
|  | 
 | ||||||
|  |     // Nfc device
 | ||||||
|  |     nfc_magic->nfc_dev = nfc_device_alloc(); | ||||||
|  | 
 | ||||||
|  |     // Open GUI record
 | ||||||
|  |     nfc_magic->gui = furi_record_open(RECORD_GUI); | ||||||
|  |     view_dispatcher_attach_to_gui( | ||||||
|  |         nfc_magic->view_dispatcher, nfc_magic->gui, ViewDispatcherTypeFullscreen); | ||||||
|  | 
 | ||||||
|  |     // Open Notification record
 | ||||||
|  |     nfc_magic->notifications = furi_record_open(RECORD_NOTIFICATION); | ||||||
|  | 
 | ||||||
|  |     // Submenu
 | ||||||
|  |     nfc_magic->submenu = submenu_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         nfc_magic->view_dispatcher, NfcMagicViewMenu, submenu_get_view(nfc_magic->submenu)); | ||||||
|  | 
 | ||||||
|  |     // Popup
 | ||||||
|  |     nfc_magic->popup = popup_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         nfc_magic->view_dispatcher, NfcMagicViewPopup, popup_get_view(nfc_magic->popup)); | ||||||
|  | 
 | ||||||
|  |     // Loading
 | ||||||
|  |     nfc_magic->loading = loading_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         nfc_magic->view_dispatcher, NfcMagicViewLoading, loading_get_view(nfc_magic->loading)); | ||||||
|  | 
 | ||||||
|  |     // Text Input
 | ||||||
|  |     nfc_magic->text_input = text_input_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         nfc_magic->view_dispatcher, | ||||||
|  |         NfcMagicViewTextInput, | ||||||
|  |         text_input_get_view(nfc_magic->text_input)); | ||||||
|  | 
 | ||||||
|  |     // Custom Widget
 | ||||||
|  |     nfc_magic->widget = widget_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         nfc_magic->view_dispatcher, NfcMagicViewWidget, widget_get_view(nfc_magic->widget)); | ||||||
|  | 
 | ||||||
|  |     return nfc_magic; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_free(NfcMagic* nfc_magic) { | ||||||
|  |     furi_assert(nfc_magic); | ||||||
|  | 
 | ||||||
|  |     // Nfc device
 | ||||||
|  |     nfc_device_free(nfc_magic->nfc_dev); | ||||||
|  | 
 | ||||||
|  |     // Submenu
 | ||||||
|  |     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); | ||||||
|  |     submenu_free(nfc_magic->submenu); | ||||||
|  | 
 | ||||||
|  |     // Popup
 | ||||||
|  |     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); | ||||||
|  |     popup_free(nfc_magic->popup); | ||||||
|  | 
 | ||||||
|  |     // Loading
 | ||||||
|  |     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewLoading); | ||||||
|  |     loading_free(nfc_magic->loading); | ||||||
|  | 
 | ||||||
|  |     // TextInput
 | ||||||
|  |     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewTextInput); | ||||||
|  |     text_input_free(nfc_magic->text_input); | ||||||
|  | 
 | ||||||
|  |     // Custom Widget
 | ||||||
|  |     view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||||
|  |     widget_free(nfc_magic->widget); | ||||||
|  | 
 | ||||||
|  |     // Worker
 | ||||||
|  |     nfc_magic_worker_stop(nfc_magic->worker); | ||||||
|  |     nfc_magic_worker_free(nfc_magic->worker); | ||||||
|  | 
 | ||||||
|  |     // View Dispatcher
 | ||||||
|  |     view_dispatcher_free(nfc_magic->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     // Scene Manager
 | ||||||
|  |     scene_manager_free(nfc_magic->scene_manager); | ||||||
|  | 
 | ||||||
|  |     // GUI
 | ||||||
|  |     furi_record_close(RECORD_GUI); | ||||||
|  |     nfc_magic->gui = NULL; | ||||||
|  | 
 | ||||||
|  |     // Notifications
 | ||||||
|  |     furi_record_close(RECORD_NOTIFICATION); | ||||||
|  |     nfc_magic->notifications = NULL; | ||||||
|  | 
 | ||||||
|  |     free(nfc_magic); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const NotificationSequence nfc_magic_sequence_blink_start_blue = { | ||||||
|  |     &message_blink_start_10, | ||||||
|  |     &message_blink_set_color_blue, | ||||||
|  |     &message_do_not_reset, | ||||||
|  |     NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const NotificationSequence nfc_magic_sequence_blink_stop = { | ||||||
|  |     &message_blink_stop, | ||||||
|  |     NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void nfc_magic_blink_start(NfcMagic* nfc_magic) { | ||||||
|  |     notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_start_blue); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_blink_stop(NfcMagic* nfc_magic) { | ||||||
|  |     notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_stop); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t nfc_magic_app(void* p) { | ||||||
|  |     UNUSED(p); | ||||||
|  |     NfcMagic* nfc_magic = nfc_magic_alloc(); | ||||||
|  | 
 | ||||||
|  |     scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneStart); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_run(nfc_magic->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     nfc_magic_free(nfc_magic); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								applications/plugins/nfc_magic/nfc_magic.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								applications/plugins/nfc_magic/nfc_magic.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | typedef struct NfcMagic NfcMagic; | ||||||
							
								
								
									
										77
									
								
								applications/plugins/nfc_magic/nfc_magic_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								applications/plugins/nfc_magic/nfc_magic_i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "nfc_magic.h" | ||||||
|  | #include "nfc_magic_worker.h" | ||||||
|  | 
 | ||||||
|  | #include "lib/magic/magic.h" | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | #include <notification/notification_messages.h> | ||||||
|  | 
 | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | #include <gui/modules/popup.h> | ||||||
|  | #include <gui/modules/loading.h> | ||||||
|  | #include <gui/modules/text_input.h> | ||||||
|  | #include <gui/modules/widget.h> | ||||||
|  | 
 | ||||||
|  | #include <input/input.h> | ||||||
|  | 
 | ||||||
|  | #include "scenes/nfc_magic_scene.h" | ||||||
|  | 
 | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <lib/toolbox/path.h> | ||||||
|  | 
 | ||||||
|  | #include <lib/nfc/nfc_device.h> | ||||||
|  | #include "nfc_magic_icons.h" | ||||||
|  | 
 | ||||||
|  | enum NfcMagicCustomEvent { | ||||||
|  |     // Reserve first 100 events for button types and indexes, starting from 0
 | ||||||
|  |     NfcMagicCustomEventReserved = 100, | ||||||
|  | 
 | ||||||
|  |     NfcMagicCustomEventViewExit, | ||||||
|  |     NfcMagicCustomEventWorkerExit, | ||||||
|  |     NfcMagicCustomEventByteInputDone, | ||||||
|  |     NfcMagicCustomEventTextInputDone, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct NfcMagic { | ||||||
|  |     NfcMagicWorker* worker; | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     Gui* gui; | ||||||
|  |     NotificationApp* notifications; | ||||||
|  |     SceneManager* scene_manager; | ||||||
|  |     // NfcMagicDevice* dev;
 | ||||||
|  |     NfcDevice* nfc_dev; | ||||||
|  | 
 | ||||||
|  |     FuriString* text_box_store; | ||||||
|  | 
 | ||||||
|  |     // Common Views
 | ||||||
|  |     Submenu* submenu; | ||||||
|  |     Popup* popup; | ||||||
|  |     Loading* loading; | ||||||
|  |     TextInput* text_input; | ||||||
|  |     Widget* widget; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     NfcMagicViewMenu, | ||||||
|  |     NfcMagicViewPopup, | ||||||
|  |     NfcMagicViewLoading, | ||||||
|  |     NfcMagicViewTextInput, | ||||||
|  |     NfcMagicViewWidget, | ||||||
|  | } NfcMagicView; | ||||||
|  | 
 | ||||||
|  | NfcMagic* nfc_magic_alloc(); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_text_store_set(NfcMagic* nfc_magic, const char* text, ...); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_text_store_clear(NfcMagic* nfc_magic); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_blink_start(NfcMagic* nfc_magic); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_blink_stop(NfcMagic* nfc_magic); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_show_loading_popup(void* context, bool show); | ||||||
							
								
								
									
										173
									
								
								applications/plugins/nfc_magic/nfc_magic_worker.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								applications/plugins/nfc_magic/nfc_magic_worker.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,173 @@ | |||||||
|  | #include "nfc_magic_worker_i.h" | ||||||
|  | 
 | ||||||
|  | #include "lib/magic/magic.h" | ||||||
|  | 
 | ||||||
|  | #define TAG "NfcMagicWorker" | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     nfc_magic_worker_change_state(NfcMagicWorker* nfc_magic_worker, NfcMagicWorkerState state) { | ||||||
|  |     furi_assert(nfc_magic_worker); | ||||||
|  | 
 | ||||||
|  |     nfc_magic_worker->state = state; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | NfcMagicWorker* nfc_magic_worker_alloc() { | ||||||
|  |     NfcMagicWorker* nfc_magic_worker = malloc(sizeof(NfcMagicWorker)); | ||||||
|  | 
 | ||||||
|  |     // Worker thread attributes
 | ||||||
|  |     nfc_magic_worker->thread = furi_thread_alloc(); | ||||||
|  |     furi_thread_set_name(nfc_magic_worker->thread, "NfcMagicWorker"); | ||||||
|  |     furi_thread_set_stack_size(nfc_magic_worker->thread, 8192); | ||||||
|  |     furi_thread_set_callback(nfc_magic_worker->thread, nfc_magic_worker_task); | ||||||
|  |     furi_thread_set_context(nfc_magic_worker->thread, nfc_magic_worker); | ||||||
|  | 
 | ||||||
|  |     nfc_magic_worker->callback = NULL; | ||||||
|  |     nfc_magic_worker->context = NULL; | ||||||
|  | 
 | ||||||
|  |     nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateReady); | ||||||
|  | 
 | ||||||
|  |     return nfc_magic_worker; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_free(NfcMagicWorker* nfc_magic_worker) { | ||||||
|  |     furi_assert(nfc_magic_worker); | ||||||
|  | 
 | ||||||
|  |     furi_thread_free(nfc_magic_worker->thread); | ||||||
|  |     free(nfc_magic_worker); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker) { | ||||||
|  |     furi_assert(nfc_magic_worker); | ||||||
|  | 
 | ||||||
|  |     nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateStop); | ||||||
|  |     furi_thread_join(nfc_magic_worker->thread); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_start( | ||||||
|  |     NfcMagicWorker* nfc_magic_worker, | ||||||
|  |     NfcMagicWorkerState state, | ||||||
|  |     NfcDeviceData* dev_data, | ||||||
|  |     NfcMagicWorkerCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(nfc_magic_worker); | ||||||
|  |     furi_assert(dev_data); | ||||||
|  | 
 | ||||||
|  |     nfc_magic_worker->callback = callback; | ||||||
|  |     nfc_magic_worker->context = context; | ||||||
|  |     nfc_magic_worker->dev_data = dev_data; | ||||||
|  |     nfc_magic_worker_change_state(nfc_magic_worker, state); | ||||||
|  |     furi_thread_start(nfc_magic_worker->thread); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t nfc_magic_worker_task(void* context) { | ||||||
|  |     NfcMagicWorker* nfc_magic_worker = context; | ||||||
|  | 
 | ||||||
|  |     if(nfc_magic_worker->state == NfcMagicWorkerStateCheck) { | ||||||
|  |         nfc_magic_worker_check(nfc_magic_worker); | ||||||
|  |     } else if(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { | ||||||
|  |         nfc_magic_worker_write(nfc_magic_worker); | ||||||
|  |     } else if(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { | ||||||
|  |         nfc_magic_worker_wipe(nfc_magic_worker); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateReady); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) { | ||||||
|  |     bool card_found_notified = false; | ||||||
|  |     FuriHalNfcDevData nfc_data = {}; | ||||||
|  |     MfClassicData* src_data = &nfc_magic_worker->dev_data->mf_classic_data; | ||||||
|  | 
 | ||||||
|  |     while(nfc_magic_worker->state == NfcMagicWorkerStateWrite) { | ||||||
|  |         if(furi_hal_nfc_detect(&nfc_data, 200)) { | ||||||
|  |             if(!card_found_notified) { | ||||||
|  |                 nfc_magic_worker->callback( | ||||||
|  |                     NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); | ||||||
|  |                 card_found_notified = true; | ||||||
|  |             } | ||||||
|  |             furi_hal_nfc_sleep(); | ||||||
|  | 
 | ||||||
|  |             if(!magic_wupa()) { | ||||||
|  |                 FURI_LOG_E(TAG, "Not Magic card"); | ||||||
|  |                 nfc_magic_worker->callback( | ||||||
|  |                     NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(!magic_data_access_cmd()) { | ||||||
|  |                 FURI_LOG_E(TAG, "Not Magic card"); | ||||||
|  |                 nfc_magic_worker->callback( | ||||||
|  |                     NfcMagicWorkerEventWrongCard, nfc_magic_worker->context); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             for(size_t i = 0; i < 64; i++) { | ||||||
|  |                 FURI_LOG_D(TAG, "Writing block %d", i); | ||||||
|  |                 if(!magic_write_blk(i, &src_data->block[i])) { | ||||||
|  |                     FURI_LOG_E(TAG, "Failed to write %d block", i); | ||||||
|  |                     nfc_magic_worker->callback(NfcMagicWorkerEventFail, nfc_magic_worker->context); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||||
|  |             break; | ||||||
|  |         } else { | ||||||
|  |             if(card_found_notified) { | ||||||
|  |                 nfc_magic_worker->callback( | ||||||
|  |                     NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); | ||||||
|  |                 card_found_notified = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         furi_delay_ms(300); | ||||||
|  |     } | ||||||
|  |     magic_deactivate(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) { | ||||||
|  |     bool card_found_notified = false; | ||||||
|  | 
 | ||||||
|  |     while(nfc_magic_worker->state == NfcMagicWorkerStateCheck) { | ||||||
|  |         if(magic_wupa()) { | ||||||
|  |             if(!card_found_notified) { | ||||||
|  |                 nfc_magic_worker->callback( | ||||||
|  |                     NfcMagicWorkerEventCardDetected, nfc_magic_worker->context); | ||||||
|  |                 card_found_notified = true; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||||
|  |             break; | ||||||
|  |         } else { | ||||||
|  |             if(card_found_notified) { | ||||||
|  |                 nfc_magic_worker->callback( | ||||||
|  |                     NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context); | ||||||
|  |                 card_found_notified = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         furi_delay_ms(300); | ||||||
|  |     } | ||||||
|  |     magic_deactivate(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) { | ||||||
|  |     MfClassicBlock block; | ||||||
|  |     memset(&block, 0, sizeof(MfClassicBlock)); | ||||||
|  |     block.value[0] = 0x01; | ||||||
|  |     block.value[1] = 0x02; | ||||||
|  |     block.value[2] = 0x03; | ||||||
|  |     block.value[3] = 0x04; | ||||||
|  |     block.value[4] = 0x04; | ||||||
|  |     block.value[5] = 0x08; | ||||||
|  |     block.value[6] = 0x04; | ||||||
|  | 
 | ||||||
|  |     while(nfc_magic_worker->state == NfcMagicWorkerStateWipe) { | ||||||
|  |         magic_deactivate(); | ||||||
|  |         furi_delay_ms(300); | ||||||
|  |         if(!magic_wupa()) continue; | ||||||
|  |         if(!magic_wipe()) continue; | ||||||
|  |         if(!magic_data_access_cmd()) continue; | ||||||
|  |         if(!magic_write_blk(0, &block)) continue; | ||||||
|  |         nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     magic_deactivate(); | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								applications/plugins/nfc_magic/nfc_magic_worker.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								applications/plugins/nfc_magic/nfc_magic_worker.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <lib/nfc/nfc_device.h> | ||||||
|  | 
 | ||||||
|  | typedef struct NfcMagicWorker NfcMagicWorker; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     NfcMagicWorkerStateReady, | ||||||
|  | 
 | ||||||
|  |     NfcMagicWorkerStateCheck, | ||||||
|  |     NfcMagicWorkerStateWrite, | ||||||
|  |     NfcMagicWorkerStateWipe, | ||||||
|  | 
 | ||||||
|  |     NfcMagicWorkerStateStop, | ||||||
|  | } NfcMagicWorkerState; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     NfcMagicWorkerEventSuccess, | ||||||
|  |     NfcMagicWorkerEventFail, | ||||||
|  |     NfcMagicWorkerEventCardDetected, | ||||||
|  |     NfcMagicWorkerEventNoCardDetected, | ||||||
|  |     NfcMagicWorkerEventWrongCard, | ||||||
|  | } NfcMagicWorkerEvent; | ||||||
|  | 
 | ||||||
|  | typedef bool (*NfcMagicWorkerCallback)(NfcMagicWorkerEvent event, void* context); | ||||||
|  | 
 | ||||||
|  | NfcMagicWorker* nfc_magic_worker_alloc(); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_free(NfcMagicWorker* nfc_magic_worker); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_start( | ||||||
|  |     NfcMagicWorker* nfc_magic_worker, | ||||||
|  |     NfcMagicWorkerState state, | ||||||
|  |     NfcDeviceData* dev_data, | ||||||
|  |     NfcMagicWorkerCallback callback, | ||||||
|  |     void* context); | ||||||
							
								
								
									
										24
									
								
								applications/plugins/nfc_magic/nfc_magic_worker_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								applications/plugins/nfc_magic/nfc_magic_worker_i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | #include "nfc_magic_worker.h" | ||||||
|  | 
 | ||||||
|  | struct NfcMagicWorker { | ||||||
|  |     FuriThread* thread; | ||||||
|  | 
 | ||||||
|  |     NfcDeviceData* dev_data; | ||||||
|  | 
 | ||||||
|  |     NfcMagicWorkerCallback callback; | ||||||
|  |     void* context; | ||||||
|  | 
 | ||||||
|  |     NfcMagicWorkerState state; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | int32_t nfc_magic_worker_task(void* context); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker); | ||||||
|  | 
 | ||||||
|  | void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker); | ||||||
							
								
								
									
										30
									
								
								applications/plugins/nfc_magic/scenes/nfc_magic_scene.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								applications/plugins/nfc_magic/scenes/nfc_magic_scene.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | #include "nfc_magic_scene.h" | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||||
|  | void (*const nfc_magic_on_enter_handlers[])(void*) = { | ||||||
|  | #include "nfc_magic_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, | ||||||
|  | bool (*const nfc_magic_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||||
|  | #include "nfc_magic_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, | ||||||
|  | void (*const nfc_magic_on_exit_handlers[])(void* context) = { | ||||||
|  | #include "nfc_magic_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Initialize scene handlers configuration structure
 | ||||||
|  | const SceneManagerHandlers nfc_magic_scene_handlers = { | ||||||
|  |     .on_enter_handlers = nfc_magic_on_enter_handlers, | ||||||
|  |     .on_event_handlers = nfc_magic_on_event_handlers, | ||||||
|  |     .on_exit_handlers = nfc_magic_on_exit_handlers, | ||||||
|  |     .scene_num = NfcMagicSceneNum, | ||||||
|  | }; | ||||||
							
								
								
									
										29
									
								
								applications/plugins/nfc_magic/scenes/nfc_magic_scene.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								applications/plugins/nfc_magic/scenes/nfc_magic_scene.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | 
 | ||||||
|  | // Generate scene id and total number
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) NfcMagicScene##id, | ||||||
|  | typedef enum { | ||||||
|  | #include "nfc_magic_scene_config.h" | ||||||
|  |     NfcMagicSceneNum, | ||||||
|  | } NfcMagicScene; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | extern const SceneManagerHandlers nfc_magic_scene_handlers; | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||||
|  | #include "nfc_magic_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) \ | ||||||
|  |     bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); | ||||||
|  | #include "nfc_magic_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); | ||||||
|  | #include "nfc_magic_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
| @ -0,0 +1,87 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | enum { | ||||||
|  |     NfcMagicSceneCheckStateCardSearch, | ||||||
|  |     NfcMagicSceneCheckStateCardFound, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_check_worker_callback(NfcMagicWorkerEvent event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void nfc_magic_scene_check_setup_view(NfcMagic* nfc_magic) { | ||||||
|  |     Popup* popup = nfc_magic->popup; | ||||||
|  |     popup_reset(popup); | ||||||
|  |     uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneCheck); | ||||||
|  | 
 | ||||||
|  |     if(state == NfcMagicSceneCheckStateCardSearch) { | ||||||
|  |         popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); | ||||||
|  |         popup_set_text( | ||||||
|  |             nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); | ||||||
|  |     } else { | ||||||
|  |         popup_set_icon(popup, 12, 23, &I_Loading_24); | ||||||
|  |         popup_set_header(popup, "Checking\nDon't move...", 52, 32, AlignLeft, AlignCenter); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_check_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch); | ||||||
|  |     nfc_magic_scene_check_setup_view(nfc_magic); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     nfc_magic_worker_start( | ||||||
|  |         nfc_magic->worker, | ||||||
|  |         NfcMagicWorkerStateCheck, | ||||||
|  |         &nfc_magic->nfc_dev->dev_data, | ||||||
|  |         nfc_magic_check_worker_callback, | ||||||
|  |         nfc_magic); | ||||||
|  |     nfc_magic_blink_start(nfc_magic); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_check_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == NfcMagicWorkerEventSuccess) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneMagicInfo); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventWrongCard) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardFound); | ||||||
|  |             nfc_magic_scene_check_setup_view(nfc_magic); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventNoCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch); | ||||||
|  |             nfc_magic_scene_check_setup_view(nfc_magic); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_check_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     nfc_magic_worker_stop(nfc_magic->worker); | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch); | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(nfc_magic->popup); | ||||||
|  | 
 | ||||||
|  |     nfc_magic_blink_stop(nfc_magic); | ||||||
|  | } | ||||||
| @ -0,0 +1,12 @@ | |||||||
|  | ADD_SCENE(nfc_magic, start, Start) | ||||||
|  | ADD_SCENE(nfc_magic, file_select, FileSelect) | ||||||
|  | ADD_SCENE(nfc_magic, write_confirm, WriteConfirm) | ||||||
|  | ADD_SCENE(nfc_magic, wrong_card, WrongCard) | ||||||
|  | ADD_SCENE(nfc_magic, write, Write) | ||||||
|  | ADD_SCENE(nfc_magic, write_fail, WriteFail) | ||||||
|  | ADD_SCENE(nfc_magic, success, Success) | ||||||
|  | ADD_SCENE(nfc_magic, check, Check) | ||||||
|  | ADD_SCENE(nfc_magic, not_magic, NotMagic) | ||||||
|  | ADD_SCENE(nfc_magic, magic_info, MagicInfo) | ||||||
|  | ADD_SCENE(nfc_magic, wipe, Wipe) | ||||||
|  | ADD_SCENE(nfc_magic, wipe_fail, WipeFail) | ||||||
| @ -0,0 +1,34 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | static bool nfc_magic_scene_file_select_is_file_suitable(NfcDevice* nfc_dev) { | ||||||
|  |     return (nfc_dev->format == NfcDeviceSaveFormatMifareClassic) && | ||||||
|  |            (nfc_dev->dev_data.mf_classic_data.type == MfClassicType1k) && | ||||||
|  |            (nfc_dev->dev_data.nfc_data.uid_len == 4); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_file_select_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     // Process file_select return
 | ||||||
|  |     nfc_device_set_loading_callback(nfc_magic->nfc_dev, nfc_magic_show_loading_popup, nfc_magic); | ||||||
|  | 
 | ||||||
|  |     if(nfc_file_select(nfc_magic->nfc_dev)) { | ||||||
|  |         if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic->nfc_dev)) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm); | ||||||
|  |         } else { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrongCard); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         scene_manager_previous_scene(nfc_magic->scene_manager); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_file_select_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     UNUSED(context); | ||||||
|  |     UNUSED(event); | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_file_select_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     nfc_device_set_loading_callback(nfc_magic->nfc_dev, NULL, nfc_magic); | ||||||
|  | } | ||||||
| @ -0,0 +1,45 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_magic_info_widget_callback( | ||||||
|  |     GuiButtonType result, | ||||||
|  |     InputType type, | ||||||
|  |     void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_magic_info_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     Widget* widget = nfc_magic->widget; | ||||||
|  | 
 | ||||||
|  |     notification_message(nfc_magic->notifications, &sequence_success); | ||||||
|  | 
 | ||||||
|  |     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected"); | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, nfc_magic); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_previous_scene(nfc_magic->scene_manager); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_magic_info_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     widget_reset(nfc_magic->widget); | ||||||
|  | } | ||||||
| @ -0,0 +1,44 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_not_magic_widget_callback(GuiButtonType result, InputType type, void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_not_magic_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     Widget* widget = nfc_magic->widget; | ||||||
|  | 
 | ||||||
|  |     notification_message(nfc_magic->notifications, &sequence_error); | ||||||
|  | 
 | ||||||
|  |     // widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
 | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         widget, 4, 17, AlignLeft, AlignTop, FontSecondary, "Not a magic\ncard"); | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_not_magic_widget_callback, nfc_magic); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_not_magic_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_previous_scene(nfc_magic->scene_manager); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_not_magic_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     widget_reset(nfc_magic->widget); | ||||||
|  | } | ||||||
| @ -0,0 +1,61 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | enum SubmenuIndex { | ||||||
|  |     SubmenuIndexCheck, | ||||||
|  |     SubmenuIndexWriteGen1A, | ||||||
|  |     SubmenuIndexWipe, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_start_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_start_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     Submenu* submenu = nfc_magic->submenu; | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Check Magic Tag", | ||||||
|  |         SubmenuIndexCheck, | ||||||
|  |         nfc_magic_scene_start_submenu_callback, | ||||||
|  |         nfc_magic); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Write Gen1A", | ||||||
|  |         SubmenuIndexWriteGen1A, | ||||||
|  |         nfc_magic_scene_start_submenu_callback, | ||||||
|  |         nfc_magic); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_start_submenu_callback, nfc_magic); | ||||||
|  | 
 | ||||||
|  |     submenu_set_selected_item( | ||||||
|  |         submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart)); | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == SubmenuIndexCheck) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexWriteGen1A) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexWipe) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |         scene_manager_set_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart, event.event); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_start_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     submenu_reset(nfc_magic->submenu); | ||||||
|  | } | ||||||
| @ -0,0 +1,42 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_success_popup_callback(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, NfcMagicCustomEventViewExit); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_success_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     notification_message(nfc_magic->notifications, &sequence_success); | ||||||
|  | 
 | ||||||
|  |     Popup* popup = nfc_magic->popup; | ||||||
|  |     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||||
|  |     popup_set_header(popup, "Success!", 10, 20, AlignLeft, AlignBottom); | ||||||
|  |     popup_set_timeout(popup, 1500); | ||||||
|  |     popup_set_context(popup, nfc_magic); | ||||||
|  |     popup_set_callback(popup, nfc_magic_scene_success_popup_callback); | ||||||
|  |     popup_enable_timeout(popup); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_success_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == NfcMagicCustomEventViewExit) { | ||||||
|  |             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                 nfc_magic->scene_manager, NfcMagicSceneStart); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_success_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(nfc_magic->popup); | ||||||
|  | } | ||||||
							
								
								
									
										90
									
								
								applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								applications/plugins/nfc_magic/scenes/nfc_magic_scene_wipe.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,90 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | enum { | ||||||
|  |     NfcMagicSceneWipeStateCardSearch, | ||||||
|  |     NfcMagicSceneWipeStateCardFound, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_wipe_worker_callback(NfcMagicWorkerEvent event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void nfc_magic_scene_wipe_setup_view(NfcMagic* nfc_magic) { | ||||||
|  |     Popup* popup = nfc_magic->popup; | ||||||
|  |     popup_reset(popup); | ||||||
|  |     uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneWipe); | ||||||
|  | 
 | ||||||
|  |     if(state == NfcMagicSceneWipeStateCardSearch) { | ||||||
|  |         popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); | ||||||
|  |         popup_set_text( | ||||||
|  |             nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); | ||||||
|  |     } else { | ||||||
|  |         popup_set_icon(popup, 12, 23, &I_Loading_24); | ||||||
|  |         popup_set_header(popup, "Wiping\nDon't move...", 52, 32, AlignLeft, AlignCenter); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_wipe_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch); | ||||||
|  |     nfc_magic_scene_wipe_setup_view(nfc_magic); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     nfc_magic_worker_start( | ||||||
|  |         nfc_magic->worker, | ||||||
|  |         NfcMagicWorkerStateWipe, | ||||||
|  |         &nfc_magic->nfc_dev->dev_data, | ||||||
|  |         nfc_magic_wipe_worker_callback, | ||||||
|  |         nfc_magic); | ||||||
|  |     nfc_magic_blink_start(nfc_magic); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_wipe_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == NfcMagicWorkerEventSuccess) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventFail) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipeFail); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventWrongCard) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardFound); | ||||||
|  |             nfc_magic_scene_wipe_setup_view(nfc_magic); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventNoCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch); | ||||||
|  |             nfc_magic_scene_wipe_setup_view(nfc_magic); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_wipe_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     nfc_magic_worker_stop(nfc_magic->worker); | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch); | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(nfc_magic->popup); | ||||||
|  | 
 | ||||||
|  |     nfc_magic_blink_stop(nfc_magic); | ||||||
|  | } | ||||||
| @ -0,0 +1,41 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_wipe_fail_widget_callback(GuiButtonType result, InputType type, void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_wipe_fail_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     Widget* widget = nfc_magic->widget; | ||||||
|  | 
 | ||||||
|  |     notification_message(nfc_magic->notifications, &sequence_error); | ||||||
|  | 
 | ||||||
|  |     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); | ||||||
|  |     widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wipe failed"); | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wipe_fail_widget_callback, nfc_magic); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_wipe_fail_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_previous_scene(nfc_magic->scene_manager); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_wipe_fail_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     widget_reset(nfc_magic->widget); | ||||||
|  | } | ||||||
| @ -0,0 +1,90 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | enum { | ||||||
|  |     NfcMagicSceneWriteStateCardSearch, | ||||||
|  |     NfcMagicSceneWriteStateCardFound, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_write_worker_callback(NfcMagicWorkerEvent event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void nfc_magic_scene_write_setup_view(NfcMagic* nfc_magic) { | ||||||
|  |     Popup* popup = nfc_magic->popup; | ||||||
|  |     popup_reset(popup); | ||||||
|  |     uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneWrite); | ||||||
|  | 
 | ||||||
|  |     if(state == NfcMagicSceneWriteStateCardSearch) { | ||||||
|  |         popup_set_text( | ||||||
|  |             nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter); | ||||||
|  |         popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50); | ||||||
|  |     } else { | ||||||
|  |         popup_set_icon(popup, 12, 23, &I_Loading_24); | ||||||
|  |         popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_write_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch); | ||||||
|  |     nfc_magic_scene_write_setup_view(nfc_magic); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     nfc_magic_worker_start( | ||||||
|  |         nfc_magic->worker, | ||||||
|  |         NfcMagicWorkerStateWrite, | ||||||
|  |         &nfc_magic->nfc_dev->dev_data, | ||||||
|  |         nfc_magic_write_worker_callback, | ||||||
|  |         nfc_magic); | ||||||
|  |     nfc_magic_blink_start(nfc_magic); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_write_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == NfcMagicWorkerEventSuccess) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventFail) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteFail); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventWrongCard) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardFound); | ||||||
|  |             nfc_magic_scene_write_setup_view(nfc_magic); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcMagicWorkerEventNoCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch); | ||||||
|  |             nfc_magic_scene_write_setup_view(nfc_magic); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_write_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     nfc_magic_worker_stop(nfc_magic->worker); | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch); | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(nfc_magic->popup); | ||||||
|  | 
 | ||||||
|  |     nfc_magic_blink_stop(nfc_magic); | ||||||
|  | } | ||||||
| @ -0,0 +1,64 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_write_confirm_widget_callback( | ||||||
|  |     GuiButtonType result, | ||||||
|  |     InputType type, | ||||||
|  |     void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_write_confirm_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     Widget* widget = nfc_magic->widget; | ||||||
|  | 
 | ||||||
|  |     widget_add_string_element(widget, 3, 0, AlignLeft, AlignTop, FontPrimary, "Risky operation"); | ||||||
|  |     widget_add_text_box_element( | ||||||
|  |         widget, | ||||||
|  |         0, | ||||||
|  |         13, | ||||||
|  |         128, | ||||||
|  |         54, | ||||||
|  |         AlignLeft, | ||||||
|  |         AlignTop, | ||||||
|  |         "Writing to this card will change manufacturer block. On some cards it may not be rewritten", | ||||||
|  |         false); | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, | ||||||
|  |         GuiButtonTypeCenter, | ||||||
|  |         "Continue", | ||||||
|  |         nfc_magic_scene_write_confirm_widget_callback, | ||||||
|  |         nfc_magic); | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, | ||||||
|  |         GuiButtonTypeLeft, | ||||||
|  |         "Back", | ||||||
|  |         nfc_magic_scene_write_confirm_widget_callback, | ||||||
|  |         nfc_magic); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_write_confirm_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_previous_scene(nfc_magic->scene_manager); | ||||||
|  |         } else if(event.event == GuiButtonTypeCenter) { | ||||||
|  |             scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrite); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_write_confirm_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     widget_reset(nfc_magic->widget); | ||||||
|  | } | ||||||
| @ -0,0 +1,58 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_write_fail_widget_callback( | ||||||
|  |     GuiButtonType result, | ||||||
|  |     InputType type, | ||||||
|  |     void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_write_fail_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     Widget* widget = nfc_magic->widget; | ||||||
|  | 
 | ||||||
|  |     notification_message(nfc_magic->notifications, &sequence_error); | ||||||
|  | 
 | ||||||
|  |     widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         widget, | ||||||
|  |         7, | ||||||
|  |         17, | ||||||
|  |         AlignLeft, | ||||||
|  |         AlignTop, | ||||||
|  |         FontSecondary, | ||||||
|  |         "Not all sectors\nwere written\ncorrectly."); | ||||||
|  | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, nfc_magic); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_write_fail_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                 nfc_magic->scene_manager, NfcMagicSceneStart); | ||||||
|  |         } | ||||||
|  |     } else if(event.type == SceneManagerEventTypeBack) { | ||||||
|  |         consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |             nfc_magic->scene_manager, NfcMagicSceneStart); | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_write_fail_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     widget_reset(nfc_magic->widget); | ||||||
|  | } | ||||||
| @ -0,0 +1,53 @@ | |||||||
|  | #include "../nfc_magic_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_wrong_card_widget_callback( | ||||||
|  |     GuiButtonType result, | ||||||
|  |     InputType type, | ||||||
|  |     void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_wrong_card_on_enter(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     Widget* widget = nfc_magic->widget; | ||||||
|  | 
 | ||||||
|  |     notification_message(nfc_magic->notifications, &sequence_error); | ||||||
|  | 
 | ||||||
|  |     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 1, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         widget, | ||||||
|  |         1, | ||||||
|  |         17, | ||||||
|  |         AlignLeft, | ||||||
|  |         AlignTop, | ||||||
|  |         FontSecondary, | ||||||
|  |         "Writing is supported\nonly for 4 bytes UID\nMifare CLassic 1k"); | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_magic_scene_wrong_card_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_previous_scene(nfc_magic->scene_manager); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_magic_scene_wrong_card_on_exit(void* context) { | ||||||
|  |     NfcMagic* nfc_magic = context; | ||||||
|  | 
 | ||||||
|  |     widget_reset(nfc_magic->widget); | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/picopass/125_10px.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								applications/plugins/picopass/125_10px.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 308 B | 
| @ -9,7 +9,7 @@ App( | |||||||
|     ], |     ], | ||||||
|     stack_size=4 * 1024, |     stack_size=4 * 1024, | ||||||
|     order=30, |     order=30, | ||||||
|     fap_icon="../../../assets/icons/Archive/125_10px.png", |     fap_icon="125_10px.png", | ||||||
|     fap_category="Tools", |     fap_category="Tools", | ||||||
|     fap_libs=["mbedtls"], |     fap_libs=["mbedtls"], | ||||||
|     fap_private_libs=[ |     fap_private_libs=[ | ||||||
|  | |||||||
| @ -9,8 +9,6 @@ | |||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
| #include <st25r3916.h> |  | ||||||
| #include <rfal_analogConfig.h> |  | ||||||
| #include <rfal_rf.h> | #include <rfal_rf.h> | ||||||
| 
 | 
 | ||||||
| #include <platform.h> | #include <platform.h> | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| #include "rfal_picopass.h" | #include "rfal_picopass.h" | ||||||
| #include "utils.h" |  | ||||||
| 
 | 
 | ||||||
| #define RFAL_PICOPASS_TXRX_FLAGS                                                    \ | #define RFAL_PICOPASS_TXRX_FLAGS                                                    \ | ||||||
|     (FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \ |     (FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \ | ||||||
| @ -97,7 +96,7 @@ FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* s | |||||||
| 
 | 
 | ||||||
|     rfalPicoPassSelectReq selReq; |     rfalPicoPassSelectReq selReq; | ||||||
|     selReq.CMD = RFAL_PICOPASS_CMD_SELECT; |     selReq.CMD = RFAL_PICOPASS_CMD_SELECT; | ||||||
|     ST_MEMCPY(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN); |     memcpy(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN); | ||||||
|     uint16_t recvLen = 0; |     uint16_t recvLen = 0; | ||||||
|     uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; |     uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; | ||||||
|     uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); |     uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); | ||||||
| @ -146,8 +145,8 @@ FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chk | |||||||
|     FuriHalNfcReturn ret; |     FuriHalNfcReturn ret; | ||||||
|     rfalPicoPassCheckReq chkReq; |     rfalPicoPassCheckReq chkReq; | ||||||
|     chkReq.CMD = RFAL_PICOPASS_CMD_CHECK; |     chkReq.CMD = RFAL_PICOPASS_CMD_CHECK; | ||||||
|     ST_MEMCPY(chkReq.mac, mac, 4); |     memcpy(chkReq.mac, mac, 4); | ||||||
|     ST_MEMSET(chkReq.null, 0, 4); |     memset(chkReq.null, 0, 4); | ||||||
|     uint16_t recvLen = 0; |     uint16_t recvLen = 0; | ||||||
|     uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; |     uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; | ||||||
|     uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); |     uint32_t fwt = furi_hal_nfc_ll_ms2fc(20); | ||||||
|  | |||||||
| @ -318,7 +318,6 @@ static void | |||||||
| 
 | 
 | ||||||
| int32_t snake_game_app(void* p) { | int32_t snake_game_app(void* p) { | ||||||
|     UNUSED(p); |     UNUSED(p); | ||||||
|     srand(DWT->CYCCNT); |  | ||||||
| 
 | 
 | ||||||
|     FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent)); |     FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent)); | ||||||
| 
 | 
 | ||||||
| @ -380,6 +379,8 @@ int32_t snake_game_app(void* p) { | |||||||
|                     case InputKeyBack: |                     case InputKeyBack: | ||||||
|                         processing = false; |                         processing = false; | ||||||
|                         break; |                         break; | ||||||
|  |                     default: | ||||||
|  |                         break; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } else if(event.type == EventTypeTick) { |             } else if(event.type == EventTypeTick) { | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
| 
 | 
 | ||||||
| #define WS_VERSION_APP "0.3.1" | #define WS_VERSION_APP "0.4" | ||||||
| #define WS_DEVELOPED "SkorP" | #define WS_DEVELOPED "SkorP" | ||||||
| #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
 | #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Help |  * Help | ||||||
|  * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c
 |  * https://github.com/merbanan/rtl_433/blob/master/src/devices/acurite.c
 | ||||||
|  *  |  *  | ||||||
|  * Acurite 592TXR Temperature Humidity sensor decoder |  * Acurite 592TXR Temperature Humidity sensor decoder | ||||||
|  * Message Type 0x04, 7 bytes |  * Message Type 0x04, 7 bytes | ||||||
| @ -293,7 +293,7 @@ void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* ou | |||||||
|         "%s %dbit\r\n" |         "%s %dbit\r\n" | ||||||
|         "Key:0x%lX%08lX\r\n" |         "Key:0x%lX%08lX\r\n" | ||||||
|         "Sn:0x%lX Ch:%d  Bat:%d\r\n" |         "Sn:0x%lX Ch:%d  Bat:%d\r\n" | ||||||
|         "Temp:%d.%d C Hum:%d%%", |         "Temp:%3.1f C Hum:%d%%", | ||||||
|         instance->generic.protocol_name, |         instance->generic.protocol_name, | ||||||
|         instance->generic.data_count_bit, |         instance->generic.data_count_bit, | ||||||
|         (uint32_t)(instance->generic.data >> 32), |         (uint32_t)(instance->generic.data >> 32), | ||||||
| @ -301,7 +301,6 @@ void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* ou | |||||||
|         instance->generic.id, |         instance->generic.id, | ||||||
|         instance->generic.channel, |         instance->generic.channel, | ||||||
|         instance->generic.battery_low, |         instance->generic.battery_low, | ||||||
|         (int16_t)instance->generic.temp, |         (double)instance->generic.temp, | ||||||
|         abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), |  | ||||||
|         instance->generic.humidity); |         instance->generic.humidity); | ||||||
| } | } | ||||||
|  | |||||||
| @ -234,7 +234,7 @@ void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* out | |||||||
|         "%s %dbit\r\n" |         "%s %dbit\r\n" | ||||||
|         "Key:0x%lX%08lX\r\n" |         "Key:0x%lX%08lX\r\n" | ||||||
|         "Sn:0x%lX Ch:%d  Bat:%d\r\n" |         "Sn:0x%lX Ch:%d  Bat:%d\r\n" | ||||||
|         "Temp:%d.%d C Hum:%d%%", |         "Temp:%3.1f C Hum:%d%%", | ||||||
|         instance->generic.protocol_name, |         instance->generic.protocol_name, | ||||||
|         instance->generic.data_count_bit, |         instance->generic.data_count_bit, | ||||||
|         (uint32_t)(instance->generic.data >> 32), |         (uint32_t)(instance->generic.data >> 32), | ||||||
| @ -242,7 +242,6 @@ void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* out | |||||||
|         instance->generic.id, |         instance->generic.id, | ||||||
|         instance->generic.channel, |         instance->generic.channel, | ||||||
|         instance->generic.battery_low, |         instance->generic.battery_low, | ||||||
|         (int16_t)instance->generic.temp, |         (double)instance->generic.temp, | ||||||
|         abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), |  | ||||||
|         instance->generic.humidity); |         instance->generic.humidity); | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										247
									
								
								applications/plugins/weather_station/protocols/acurite_609txc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								applications/plugins/weather_station/protocols/acurite_609txc.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,247 @@ | |||||||
|  | #include "acurite_609txc.h" | ||||||
|  | 
 | ||||||
|  | #define TAG "WSProtocolAcurite_609TXC" | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Help | ||||||
|  |  * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L216
 | ||||||
|  |  * | ||||||
|  |  *     0000 1111 | 0011 0000 | 0101 1100 | 0000 0000 | 1110 0111 | ||||||
|  |  *     iiii iiii | buuu tttt | tttt tttt | hhhh hhhh | cccc cccc | ||||||
|  |  * - i: identification; changes on battery switch | ||||||
|  |  * - c: checksum (sum of previous by bytes) | ||||||
|  |  * - u: unknown | ||||||
|  |  * - b: battery low; flag to indicate low battery voltage | ||||||
|  |  * - t: temperature; in °C * 10, 12 bit with complement | ||||||
|  |  * - h: humidity | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | static const SubGhzBlockConst ws_protocol_acurite_609txc_const = { | ||||||
|  |     .te_short = 500, | ||||||
|  |     .te_long = 1000, | ||||||
|  |     .te_delta = 150, | ||||||
|  |     .min_count_bit_for_found = 40, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct WSProtocolDecoderAcurite_609TXC { | ||||||
|  |     SubGhzProtocolDecoderBase base; | ||||||
|  | 
 | ||||||
|  |     SubGhzBlockDecoder decoder; | ||||||
|  |     WSBlockGeneric generic; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct WSProtocolEncoderAcurite_609TXC { | ||||||
|  |     SubGhzProtocolEncoderBase base; | ||||||
|  | 
 | ||||||
|  |     SubGhzProtocolBlockEncoder encoder; | ||||||
|  |     WSBlockGeneric generic; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     Acurite_609TXCDecoderStepReset = 0, | ||||||
|  |     Acurite_609TXCDecoderStepSaveDuration, | ||||||
|  |     Acurite_609TXCDecoderStepCheckDuration, | ||||||
|  | } Acurite_609TXCDecoderStep; | ||||||
|  | 
 | ||||||
|  | const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder = { | ||||||
|  |     .alloc = ws_protocol_decoder_acurite_609txc_alloc, | ||||||
|  |     .free = ws_protocol_decoder_acurite_609txc_free, | ||||||
|  | 
 | ||||||
|  |     .feed = ws_protocol_decoder_acurite_609txc_feed, | ||||||
|  |     .reset = ws_protocol_decoder_acurite_609txc_reset, | ||||||
|  | 
 | ||||||
|  |     .get_hash_data = ws_protocol_decoder_acurite_609txc_get_hash_data, | ||||||
|  |     .serialize = ws_protocol_decoder_acurite_609txc_serialize, | ||||||
|  |     .deserialize = ws_protocol_decoder_acurite_609txc_deserialize, | ||||||
|  |     .get_string = ws_protocol_decoder_acurite_609txc_get_string, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder = { | ||||||
|  |     .alloc = NULL, | ||||||
|  |     .free = NULL, | ||||||
|  | 
 | ||||||
|  |     .deserialize = NULL, | ||||||
|  |     .stop = NULL, | ||||||
|  |     .yield = NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const SubGhzProtocol ws_protocol_acurite_609txc = { | ||||||
|  |     .name = WS_PROTOCOL_ACURITE_609TXC_NAME, | ||||||
|  |     .type = SubGhzProtocolWeatherStation, | ||||||
|  |     .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | | ||||||
|  |             SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, | ||||||
|  | 
 | ||||||
|  |     .decoder = &ws_protocol_acurite_609txc_decoder, | ||||||
|  |     .encoder = &ws_protocol_acurite_609txc_encoder, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment) { | ||||||
|  |     UNUSED(environment); | ||||||
|  |     WSProtocolDecoderAcurite_609TXC* instance = malloc(sizeof(WSProtocolDecoderAcurite_609TXC)); | ||||||
|  |     instance->base.protocol = &ws_protocol_acurite_609txc; | ||||||
|  |     instance->generic.protocol_name = instance->base.protocol->name; | ||||||
|  |     return instance; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ws_protocol_decoder_acurite_609txc_free(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAcurite_609TXC* instance = context; | ||||||
|  |     free(instance); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ws_protocol_decoder_acurite_609txc_reset(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAcurite_609TXC* instance = context; | ||||||
|  |     instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool ws_protocol_acurite_609txc_check(WSProtocolDecoderAcurite_609TXC* instance) { | ||||||
|  |     if(!instance->decoder.decode_data) return false; | ||||||
|  |     uint8_t crc = (uint8_t)(instance->decoder.decode_data >> 32) + | ||||||
|  |                   (uint8_t)(instance->decoder.decode_data >> 24) + | ||||||
|  |                   (uint8_t)(instance->decoder.decode_data >> 16) + | ||||||
|  |                   (uint8_t)(instance->decoder.decode_data >> 8); | ||||||
|  |     return (crc == (instance->decoder.decode_data & 0xFF)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Analysis of received data | ||||||
|  |  * @param instance Pointer to a WSBlockGeneric* instance | ||||||
|  |  */ | ||||||
|  | static void ws_protocol_acurite_609txc_remote_controller(WSBlockGeneric* instance) { | ||||||
|  |     instance->id = (instance->data >> 32) & 0xFF; | ||||||
|  |     instance->battery_low = (instance->data >> 31) & 1; | ||||||
|  | 
 | ||||||
|  |     instance->channel = WS_NO_CHANNEL; | ||||||
|  | 
 | ||||||
|  |     // Temperature in Celsius is encoded as a 12 bit integer value
 | ||||||
|  |     // multiplied by 10 using the 4th - 6th nybbles (bytes 1 & 2)
 | ||||||
|  |     // negative values are recovered by sign extend from int16_t.
 | ||||||
|  |     int16_t temp_raw = | ||||||
|  |         (int16_t)(((instance->data >> 12) & 0xf000) | ((instance->data >> 16) << 4)); | ||||||
|  |     instance->temp = (temp_raw >> 4) * 0.1f; | ||||||
|  |     instance->humidity = (instance->data >> 8) & 0xff; | ||||||
|  |     instance->btn = WS_NO_BTN; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAcurite_609TXC* instance = context; | ||||||
|  | 
 | ||||||
|  |     switch(instance->decoder.parser_step) { | ||||||
|  |     case Acurite_609TXCDecoderStepReset: | ||||||
|  |         if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short * 17) < | ||||||
|  |                         ws_protocol_acurite_609txc_const.te_delta * 8)) { | ||||||
|  |             //Found syncPrefix
 | ||||||
|  |             instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; | ||||||
|  |             instance->decoder.decode_data = 0; | ||||||
|  |             instance->decoder.decode_count_bit = 0; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  | 
 | ||||||
|  |     case Acurite_609TXCDecoderStepSaveDuration: | ||||||
|  |         if(level) { | ||||||
|  |             instance->decoder.te_last = duration; | ||||||
|  |             instance->decoder.parser_step = Acurite_609TXCDecoderStepCheckDuration; | ||||||
|  |         } else { | ||||||
|  |             instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  | 
 | ||||||
|  |     case Acurite_609TXCDecoderStepCheckDuration: | ||||||
|  |         if(!level) { | ||||||
|  |             if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_609txc_const.te_short) < | ||||||
|  |                ws_protocol_acurite_609txc_const.te_delta) { | ||||||
|  |                 if((DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short) < | ||||||
|  |                     ws_protocol_acurite_609txc_const.te_delta) || | ||||||
|  |                    (duration > ws_protocol_acurite_609txc_const.te_long * 3)) { | ||||||
|  |                     //Found syncPostfix
 | ||||||
|  |                     instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; | ||||||
|  |                     if((instance->decoder.decode_count_bit == | ||||||
|  |                         ws_protocol_acurite_609txc_const.min_count_bit_for_found) && | ||||||
|  |                        ws_protocol_acurite_609txc_check(instance)) { | ||||||
|  |                         instance->generic.data = instance->decoder.decode_data; | ||||||
|  |                         instance->generic.data_count_bit = instance->decoder.decode_count_bit; | ||||||
|  |                         ws_protocol_acurite_609txc_remote_controller(&instance->generic); | ||||||
|  |                         if(instance->base.callback) | ||||||
|  |                             instance->base.callback(&instance->base, instance->base.context); | ||||||
|  |                     } | ||||||
|  |                     instance->decoder.decode_data = 0; | ||||||
|  |                     instance->decoder.decode_count_bit = 0; | ||||||
|  |                 } else if( | ||||||
|  |                     DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long) < | ||||||
|  |                     ws_protocol_acurite_609txc_const.te_delta * 2) { | ||||||
|  |                     subghz_protocol_blocks_add_bit(&instance->decoder, 0); | ||||||
|  |                     instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; | ||||||
|  |                 } else if( | ||||||
|  |                     DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long * 2) < | ||||||
|  |                     ws_protocol_acurite_609txc_const.te_delta * 4) { | ||||||
|  |                     subghz_protocol_blocks_add_bit(&instance->decoder, 1); | ||||||
|  |                     instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; | ||||||
|  |                 } else { | ||||||
|  |                     instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAcurite_609TXC* instance = context; | ||||||
|  |     return subghz_protocol_blocks_get_hash_data( | ||||||
|  |         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ws_protocol_decoder_acurite_609txc_serialize( | ||||||
|  |     void* context, | ||||||
|  |     FlipperFormat* flipper_format, | ||||||
|  |     SubGhzRadioPreset* preset) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAcurite_609TXC* instance = context; | ||||||
|  |     return ws_block_generic_serialize(&instance->generic, flipper_format, preset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAcurite_609TXC* instance = context; | ||||||
|  |     bool ret = false; | ||||||
|  |     do { | ||||||
|  |         if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         if(instance->generic.data_count_bit != | ||||||
|  |            ws_protocol_acurite_609txc_const.min_count_bit_for_found) { | ||||||
|  |             FURI_LOG_E(TAG, "Wrong number of bits in key"); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         ret = true; | ||||||
|  |     } while(false); | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAcurite_609TXC* instance = context; | ||||||
|  |     furi_string_printf( | ||||||
|  |         output, | ||||||
|  |         "%s %dbit\r\n" | ||||||
|  |         "Key:0x%lX%08lX\r\n" | ||||||
|  |         "Sn:0x%lX Ch:%d  Bat:%d\r\n" | ||||||
|  |         "Temp:%3.1f C Hum:%d%%", | ||||||
|  |         instance->generic.protocol_name, | ||||||
|  |         instance->generic.data_count_bit, | ||||||
|  |         (uint32_t)(instance->generic.data >> 40), | ||||||
|  |         (uint32_t)(instance->generic.data), | ||||||
|  |         instance->generic.id, | ||||||
|  |         instance->generic.channel, | ||||||
|  |         instance->generic.battery_low, | ||||||
|  |         (double)instance->generic.temp, | ||||||
|  |         instance->generic.humidity); | ||||||
|  | } | ||||||
| @ -0,0 +1,79 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <lib/subghz/protocols/base.h> | ||||||
|  | 
 | ||||||
|  | #include <lib/subghz/blocks/const.h> | ||||||
|  | #include <lib/subghz/blocks/decoder.h> | ||||||
|  | #include <lib/subghz/blocks/encoder.h> | ||||||
|  | #include "ws_generic.h" | ||||||
|  | #include <lib/subghz/blocks/math.h> | ||||||
|  | 
 | ||||||
|  | #define WS_PROTOCOL_ACURITE_609TXC_NAME "Acurite-609TXC" | ||||||
|  | 
 | ||||||
|  | typedef struct WSProtocolDecoderAcurite_609TXC WSProtocolDecoderAcurite_609TXC; | ||||||
|  | typedef struct WSProtocolEncoderAcurite_609TXC WSProtocolEncoderAcurite_609TXC; | ||||||
|  | 
 | ||||||
|  | extern const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder; | ||||||
|  | extern const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder; | ||||||
|  | extern const SubGhzProtocol ws_protocol_acurite_609txc; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Allocate WSProtocolDecoderAcurite_609TXC. | ||||||
|  |  * @param environment Pointer to a SubGhzEnvironment instance | ||||||
|  |  * @return WSProtocolDecoderAcurite_609TXC* pointer to a WSProtocolDecoderAcurite_609TXC instance | ||||||
|  |  */ | ||||||
|  | void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Free WSProtocolDecoderAcurite_609TXC. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance | ||||||
|  |  */ | ||||||
|  | void ws_protocol_decoder_acurite_609txc_free(void* context); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Reset decoder WSProtocolDecoderAcurite_609TXC. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance | ||||||
|  |  */ | ||||||
|  | void ws_protocol_decoder_acurite_609txc_reset(void* context); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Parse a raw sequence of levels and durations received from the air. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance | ||||||
|  |  * @param level Signal level true-high false-low | ||||||
|  |  * @param duration Duration of this level in, us | ||||||
|  |  */ | ||||||
|  | void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Getting the hash sum of the last randomly received parcel. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance | ||||||
|  |  * @return hash Hash sum | ||||||
|  |  */ | ||||||
|  | uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Serialize data WSProtocolDecoderAcurite_609TXC. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance | ||||||
|  |  * @param flipper_format Pointer to a FlipperFormat instance | ||||||
|  |  * @param preset The modulation on which the signal was received, SubGhzRadioPreset | ||||||
|  |  * @return true On success | ||||||
|  |  */ | ||||||
|  | bool ws_protocol_decoder_acurite_609txc_serialize( | ||||||
|  |     void* context, | ||||||
|  |     FlipperFormat* flipper_format, | ||||||
|  |     SubGhzRadioPreset* preset); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Deserialize data WSProtocolDecoderAcurite_609TXC. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance | ||||||
|  |  * @param flipper_format Pointer to a FlipperFormat instance | ||||||
|  |  * @return true On success | ||||||
|  |  */ | ||||||
|  | bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Getting a textual representation of the received data. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance | ||||||
|  |  * @param output Resulting text | ||||||
|  |  */ | ||||||
|  | void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output); | ||||||
							
								
								
									
										276
									
								
								applications/plugins/weather_station/protocols/ambient_weather.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								applications/plugins/weather_station/protocols/ambient_weather.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,276 @@ | |||||||
|  | #include "ambient_weather.h" | ||||||
|  | #include <lib/toolbox/manchester_decoder.h> | ||||||
|  | 
 | ||||||
|  | #define TAG "WSProtocolAmbient_Weather" | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Help | ||||||
|  |  * https://github.com/merbanan/rtl_433/blob/master/src/devices/ambient_weather.c
 | ||||||
|  |  *  | ||||||
|  |  * Decode Ambient Weather F007TH, F012TH, TF 30.3208.02, SwitchDoc F016TH. | ||||||
|  |  * Devices supported: | ||||||
|  |  * - Ambient Weather F007TH Thermo-Hygrometer. | ||||||
|  |  * - Ambient Weather F012TH Indoor/Display Thermo-Hygrometer. | ||||||
|  |  * - TFA senders 30.3208.02 from the TFA "Klima-Monitor" 30.3054, | ||||||
|  |  * - SwitchDoc Labs F016TH. | ||||||
|  |  * This decoder handles the 433mhz/868mhz thermo-hygrometers. | ||||||
|  |  * The 915mhz (WH*) family of devices use different modulation/encoding. | ||||||
|  |  * Byte 0   Byte 1   Byte 2   Byte 3   Byte 4   Byte 5 | ||||||
|  |  * xxxxMMMM IIIIIIII BCCCTTTT TTTTTTTT HHHHHHHH MMMMMMMM | ||||||
|  |  * - x: Unknown 0x04 on F007TH/F012TH | ||||||
|  |  * - M: Model Number?, 0x05 on F007TH/F012TH/SwitchDocLabs F016TH | ||||||
|  |  * - I: ID byte (8 bits), volatie, changes at power up, | ||||||
|  |  * - B: Battery Low | ||||||
|  |  * - C: Channel (3 bits 1-8) - F007TH set by Dip switch, F012TH soft setting | ||||||
|  |  * - T: Temperature 12 bits - Fahrenheit * 10 + 400 | ||||||
|  |  * - H: Humidity (8 bits) | ||||||
|  |  * - M: Message integrity check LFSR Digest-8, gen 0x98, key 0x3e, init 0x64 | ||||||
|  |  *  | ||||||
|  |  * three repeats without gap | ||||||
|  |  * full preamble is 0x00145 (the last bits might not be fixed, e.g. 0x00146) | ||||||
|  |  * and on decoding also 0xffd45 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #define AMBIENT_WEATHER_PACKET_HEADER_1 0xFFD440000000000 //0xffd45 .. 0xffd46
 | ||||||
|  | #define AMBIENT_WEATHER_PACKET_HEADER_2 0x001440000000000 //0x00145 .. 0x00146
 | ||||||
|  | #define AMBIENT_WEATHER_PACKET_HEADER_MASK 0xFFFFC0000000000 | ||||||
|  | 
 | ||||||
|  | static const SubGhzBlockConst ws_protocol_ambient_weather_const = { | ||||||
|  |     .te_short = 500, | ||||||
|  |     .te_long = 1000, | ||||||
|  |     .te_delta = 120, | ||||||
|  |     .min_count_bit_for_found = 48, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct WSProtocolDecoderAmbient_Weather { | ||||||
|  |     SubGhzProtocolDecoderBase base; | ||||||
|  | 
 | ||||||
|  |     SubGhzBlockDecoder decoder; | ||||||
|  |     WSBlockGeneric generic; | ||||||
|  |     ManchesterState manchester_saved_state; | ||||||
|  |     uint16_t header_count; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct WSProtocolEncoderAmbient_Weather { | ||||||
|  |     SubGhzProtocolEncoderBase base; | ||||||
|  | 
 | ||||||
|  |     SubGhzProtocolBlockEncoder encoder; | ||||||
|  |     WSBlockGeneric generic; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder = { | ||||||
|  |     .alloc = ws_protocol_decoder_ambient_weather_alloc, | ||||||
|  |     .free = ws_protocol_decoder_ambient_weather_free, | ||||||
|  | 
 | ||||||
|  |     .feed = ws_protocol_decoder_ambient_weather_feed, | ||||||
|  |     .reset = ws_protocol_decoder_ambient_weather_reset, | ||||||
|  | 
 | ||||||
|  |     .get_hash_data = ws_protocol_decoder_ambient_weather_get_hash_data, | ||||||
|  |     .serialize = ws_protocol_decoder_ambient_weather_serialize, | ||||||
|  |     .deserialize = ws_protocol_decoder_ambient_weather_deserialize, | ||||||
|  |     .get_string = ws_protocol_decoder_ambient_weather_get_string, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder = { | ||||||
|  |     .alloc = NULL, | ||||||
|  |     .free = NULL, | ||||||
|  | 
 | ||||||
|  |     .deserialize = NULL, | ||||||
|  |     .stop = NULL, | ||||||
|  |     .yield = NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const SubGhzProtocol ws_protocol_ambient_weather = { | ||||||
|  |     .name = WS_PROTOCOL_AMBIENT_WEATHER_NAME, | ||||||
|  |     .type = SubGhzProtocolWeatherStation, | ||||||
|  |     .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | | ||||||
|  |             SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, | ||||||
|  | 
 | ||||||
|  |     .decoder = &ws_protocol_ambient_weather_decoder, | ||||||
|  |     .encoder = &ws_protocol_ambient_weather_encoder, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment) { | ||||||
|  |     UNUSED(environment); | ||||||
|  |     WSProtocolDecoderAmbient_Weather* instance = malloc(sizeof(WSProtocolDecoderAmbient_Weather)); | ||||||
|  |     instance->base.protocol = &ws_protocol_ambient_weather; | ||||||
|  |     instance->generic.protocol_name = instance->base.protocol->name; | ||||||
|  |     return instance; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ws_protocol_decoder_ambient_weather_free(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAmbient_Weather* instance = context; | ||||||
|  |     free(instance); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ws_protocol_decoder_ambient_weather_reset(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAmbient_Weather* instance = context; | ||||||
|  |     manchester_advance( | ||||||
|  |         instance->manchester_saved_state, | ||||||
|  |         ManchesterEventReset, | ||||||
|  |         &instance->manchester_saved_state, | ||||||
|  |         NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool ws_protocol_ambient_weather_check_crc(WSProtocolDecoderAmbient_Weather* instance) { | ||||||
|  |     uint8_t msg[] = { | ||||||
|  |         instance->decoder.decode_data >> 40, | ||||||
|  |         instance->decoder.decode_data >> 32, | ||||||
|  |         instance->decoder.decode_data >> 24, | ||||||
|  |         instance->decoder.decode_data >> 16, | ||||||
|  |         instance->decoder.decode_data >> 8}; | ||||||
|  | 
 | ||||||
|  |     uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 5, 0x98, 0x3e) ^ 0x64; | ||||||
|  |     return (crc == (uint8_t)(instance->decoder.decode_data & 0xFF)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Analysis of received data | ||||||
|  |  * @param instance Pointer to a WSBlockGeneric* instance | ||||||
|  |  */ | ||||||
|  | static void ws_protocol_ambient_weather_remote_controller(WSBlockGeneric* instance) { | ||||||
|  |     instance->id = (instance->data >> 32) & 0xFF; | ||||||
|  |     instance->battery_low = (instance->data >> 31) & 1; | ||||||
|  |     instance->channel = ((instance->data >> 28) & 0x07) + 1; | ||||||
|  |     instance->temp = ws_block_generic_fahrenheit_to_celsius( | ||||||
|  |         ((float)((instance->data >> 16) & 0x0FFF) - 400.0f) / 10.0f); | ||||||
|  |     instance->humidity = (instance->data >> 8) & 0xFF; | ||||||
|  |     instance->btn = WS_NO_BTN; | ||||||
|  | 
 | ||||||
|  |     // ToDo maybe it won't be needed
 | ||||||
|  |     /*
 | ||||||
|  |     Sanity checks to reduce false positives and other bad data | ||||||
|  |     Packets with Bad data often pass the MIC check. | ||||||
|  |     - humidity > 100 (such as 255) and | ||||||
|  |     - temperatures > 140 F (such as 369.5 F and 348.8 F | ||||||
|  |     Specs in the F007TH and F012TH manuals state the range is: | ||||||
|  |     - Temperature: -40 to 140 F | ||||||
|  |     - Humidity: 10 to 99% | ||||||
|  |     @todo - sanity check b[0] "model number" | ||||||
|  |     - 0x45 - F007TH and F012TH | ||||||
|  |     - 0x?5 - SwitchDocLabs F016TH temperature sensor (based on comment b[0] & 0x0f == 5) | ||||||
|  |     - ? - TFA 30.3208.02 | ||||||
|  |     if (instance->humidity < 0 || instance->humidity > 100) { | ||||||
|  |         ERROR; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (instance->temp < -40.0 || instance->temp > 140.0) { | ||||||
|  |          ERROR; | ||||||
|  |     } | ||||||
|  |     */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAmbient_Weather* instance = context; | ||||||
|  | 
 | ||||||
|  |     ManchesterEvent event = ManchesterEventReset; | ||||||
|  |     if(!level) { | ||||||
|  |         if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) < | ||||||
|  |            ws_protocol_ambient_weather_const.te_delta) { | ||||||
|  |             event = ManchesterEventShortLow; | ||||||
|  |         } else if( | ||||||
|  |             DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) < | ||||||
|  |             ws_protocol_ambient_weather_const.te_delta * 2) { | ||||||
|  |             event = ManchesterEventLongLow; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) < | ||||||
|  |            ws_protocol_ambient_weather_const.te_delta) { | ||||||
|  |             event = ManchesterEventShortHigh; | ||||||
|  |         } else if( | ||||||
|  |             DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) < | ||||||
|  |             ws_protocol_ambient_weather_const.te_delta * 2) { | ||||||
|  |             event = ManchesterEventLongHigh; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     if(event != ManchesterEventReset) { | ||||||
|  |         bool data; | ||||||
|  |         bool data_ok = manchester_advance( | ||||||
|  |             instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); | ||||||
|  | 
 | ||||||
|  |         if(data_ok) { | ||||||
|  |             instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == | ||||||
|  |             AMBIENT_WEATHER_PACKET_HEADER_1) || | ||||||
|  |            ((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == | ||||||
|  |             AMBIENT_WEATHER_PACKET_HEADER_2)) { | ||||||
|  |             if(ws_protocol_ambient_weather_check_crc(instance)) { | ||||||
|  |                 instance->generic.data = instance->decoder.decode_data; | ||||||
|  |                 instance->generic.data_count_bit = | ||||||
|  |                     ws_protocol_ambient_weather_const.min_count_bit_for_found; | ||||||
|  |                 ws_protocol_ambient_weather_remote_controller(&instance->generic); | ||||||
|  |                 if(instance->base.callback) | ||||||
|  |                     instance->base.callback(&instance->base, instance->base.context); | ||||||
|  |                 instance->decoder.decode_data = 0; | ||||||
|  |                 instance->decoder.decode_count_bit = 0; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         instance->decoder.decode_data = 0; | ||||||
|  |         instance->decoder.decode_count_bit = 0; | ||||||
|  |         manchester_advance( | ||||||
|  |             instance->manchester_saved_state, | ||||||
|  |             ManchesterEventReset, | ||||||
|  |             &instance->manchester_saved_state, | ||||||
|  |             NULL); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAmbient_Weather* instance = context; | ||||||
|  |     return subghz_protocol_blocks_get_hash_data( | ||||||
|  |         &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ws_protocol_decoder_ambient_weather_serialize( | ||||||
|  |     void* context, | ||||||
|  |     FlipperFormat* flipper_format, | ||||||
|  |     SubGhzRadioPreset* preset) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAmbient_Weather* instance = context; | ||||||
|  |     return ws_block_generic_serialize(&instance->generic, flipper_format, preset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAmbient_Weather* instance = context; | ||||||
|  |     bool ret = false; | ||||||
|  |     do { | ||||||
|  |         if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         if(instance->generic.data_count_bit != | ||||||
|  |            ws_protocol_ambient_weather_const.min_count_bit_for_found) { | ||||||
|  |             FURI_LOG_E(TAG, "Wrong number of bits in key"); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         ret = true; | ||||||
|  |     } while(false); | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     WSProtocolDecoderAmbient_Weather* instance = context; | ||||||
|  |     furi_string_printf( | ||||||
|  |         output, | ||||||
|  |         "%s %dbit\r\n" | ||||||
|  |         "Key:0x%lX%08lX\r\n" | ||||||
|  |         "Sn:0x%lX Ch:%d  Bat:%d\r\n" | ||||||
|  |         "Temp:%3.1f C Hum:%d%%", | ||||||
|  |         instance->generic.protocol_name, | ||||||
|  |         instance->generic.data_count_bit, | ||||||
|  |         (uint32_t)(instance->generic.data >> 32), | ||||||
|  |         (uint32_t)(instance->generic.data), | ||||||
|  |         instance->generic.id, | ||||||
|  |         instance->generic.channel, | ||||||
|  |         instance->generic.battery_low, | ||||||
|  |         (double)instance->generic.temp, | ||||||
|  |         instance->generic.humidity); | ||||||
|  | } | ||||||
| @ -0,0 +1,79 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <lib/subghz/protocols/base.h> | ||||||
|  | 
 | ||||||
|  | #include <lib/subghz/blocks/const.h> | ||||||
|  | #include <lib/subghz/blocks/decoder.h> | ||||||
|  | #include <lib/subghz/blocks/encoder.h> | ||||||
|  | #include "ws_generic.h" | ||||||
|  | #include <lib/subghz/blocks/math.h> | ||||||
|  | 
 | ||||||
|  | #define WS_PROTOCOL_AMBIENT_WEATHER_NAME "Ambient_Weather" | ||||||
|  | 
 | ||||||
|  | typedef struct WSProtocolDecoderAmbient_Weather WSProtocolDecoderAmbient_Weather; | ||||||
|  | typedef struct WSProtocolEncoderAmbient_Weather WSProtocolEncoderAmbient_Weather; | ||||||
|  | 
 | ||||||
|  | extern const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder; | ||||||
|  | extern const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder; | ||||||
|  | extern const SubGhzProtocol ws_protocol_ambient_weather; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Allocate WSProtocolDecoderAmbient_Weather. | ||||||
|  |  * @param environment Pointer to a SubGhzEnvironment instance | ||||||
|  |  * @return WSProtocolDecoderAmbient_Weather* pointer to a WSProtocolDecoderAmbient_Weather instance | ||||||
|  |  */ | ||||||
|  | void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Free WSProtocolDecoderAmbient_Weather. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance | ||||||
|  |  */ | ||||||
|  | void ws_protocol_decoder_ambient_weather_free(void* context); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Reset decoder WSProtocolDecoderAmbient_Weather. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance | ||||||
|  |  */ | ||||||
|  | void ws_protocol_decoder_ambient_weather_reset(void* context); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Parse a raw sequence of levels and durations received from the air. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance | ||||||
|  |  * @param level Signal level true-high false-low | ||||||
|  |  * @param duration Duration of this level in, us | ||||||
|  |  */ | ||||||
|  | void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Getting the hash sum of the last randomly received parcel. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance | ||||||
|  |  * @return hash Hash sum | ||||||
|  |  */ | ||||||
|  | uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Serialize data WSProtocolDecoderAmbient_Weather. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance | ||||||
|  |  * @param flipper_format Pointer to a FlipperFormat instance | ||||||
|  |  * @param preset The modulation on which the signal was received, SubGhzRadioPreset | ||||||
|  |  * @return true On success | ||||||
|  |  */ | ||||||
|  | bool ws_protocol_decoder_ambient_weather_serialize( | ||||||
|  |     void* context, | ||||||
|  |     FlipperFormat* flipper_format, | ||||||
|  |     SubGhzRadioPreset* preset); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Deserialize data WSProtocolDecoderAmbient_Weather. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance | ||||||
|  |  * @param flipper_format Pointer to a FlipperFormat instance | ||||||
|  |  * @return true On success | ||||||
|  |  */ | ||||||
|  | bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Getting a textual representation of the received data. | ||||||
|  |  * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance | ||||||
|  |  * @param output Resulting text | ||||||
|  |  */ | ||||||
|  | void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output); | ||||||
| @ -4,7 +4,7 @@ | |||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Help |  * Help | ||||||
|  * https://github.com/merbanan/rtl_433/blob/5f0ff6db624270a4598958ab9dd79bb385ced3ef/src/devices/gt_wt_03.c
 |  * https://github.com/merbanan/rtl_433/blob/master/src/devices/gt_wt_03.c
 | ||||||
|  *  |  *  | ||||||
|  *  |  *  | ||||||
|  * Globaltronics GT-WT-03 sensor on 433.92MHz. |  * Globaltronics GT-WT-03 sensor on 433.92MHz. | ||||||
| @ -327,7 +327,7 @@ void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output) | |||||||
|         "%s %dbit\r\n" |         "%s %dbit\r\n" | ||||||
|         "Key:0x%lX%08lX\r\n" |         "Key:0x%lX%08lX\r\n" | ||||||
|         "Sn:0x%lX Ch:%d  Bat:%d\r\n" |         "Sn:0x%lX Ch:%d  Bat:%d\r\n" | ||||||
|         "Temp:%d.%d C Hum:%d%%", |         "Temp:%3.1f C Hum:%d%%", | ||||||
|         instance->generic.protocol_name, |         instance->generic.protocol_name, | ||||||
|         instance->generic.data_count_bit, |         instance->generic.data_count_bit, | ||||||
|         (uint32_t)(instance->generic.data >> 32), |         (uint32_t)(instance->generic.data >> 32), | ||||||
| @ -335,7 +335,6 @@ void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output) | |||||||
|         instance->generic.id, |         instance->generic.id, | ||||||
|         instance->generic.channel, |         instance->generic.channel, | ||||||
|         instance->generic.battery_low, |         instance->generic.battery_low, | ||||||
|         (int16_t)instance->generic.temp, |         (double)instance->generic.temp, | ||||||
|         abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), |  | ||||||
|         instance->generic.humidity); |         instance->generic.humidity); | ||||||
| } | } | ||||||
|  | |||||||
| @ -283,7 +283,7 @@ void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output) | |||||||
|         "%s %dbit\r\n" |         "%s %dbit\r\n" | ||||||
|         "Key:0x%lX%08lX\r\n" |         "Key:0x%lX%08lX\r\n" | ||||||
|         "Sn:0x%lX Ch:%d  Bat:%d\r\n" |         "Sn:0x%lX Ch:%d  Bat:%d\r\n" | ||||||
|         "Temp:%d.%d C Hum:%d%%", |         "Temp:%3.1f C Hum:%d%%", | ||||||
|         instance->generic.protocol_name, |         instance->generic.protocol_name, | ||||||
|         instance->generic.data_count_bit, |         instance->generic.data_count_bit, | ||||||
|         (uint32_t)(instance->generic.data >> 32), |         (uint32_t)(instance->generic.data >> 32), | ||||||
| @ -291,7 +291,6 @@ void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output) | |||||||
|         instance->generic.id, |         instance->generic.id, | ||||||
|         instance->generic.channel, |         instance->generic.channel, | ||||||
|         instance->generic.battery_low, |         instance->generic.battery_low, | ||||||
|         (int16_t)instance->generic.temp, |         (double)instance->generic.temp, | ||||||
|         abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), |  | ||||||
|         instance->generic.humidity); |         instance->generic.humidity); | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Help |  * Help | ||||||
|  * https://github.com/merbanan/rtl_433/blob/7e83cfd27d14247b6c3c81732bfe4a4f9a974d30/src/devices/lacrosse_tx141x.c
 |  * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse_tx141x.c
 | ||||||
|  *   |  *   | ||||||
|  *     iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u |  *     iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u | ||||||
|  * - i: identification; changes on battery switch |  * - i: identification; changes on battery switch | ||||||
| @ -284,7 +284,7 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriStrin | |||||||
|         "%s %dbit\r\n" |         "%s %dbit\r\n" | ||||||
|         "Key:0x%lX%08lX\r\n" |         "Key:0x%lX%08lX\r\n" | ||||||
|         "Sn:0x%lX Ch:%d  Bat:%d\r\n" |         "Sn:0x%lX Ch:%d  Bat:%d\r\n" | ||||||
|         "Temp:%d.%d C Hum:%d%%", |         "Temp:%3.1f C Hum:%d%%", | ||||||
|         instance->generic.protocol_name, |         instance->generic.protocol_name, | ||||||
|         instance->generic.data_count_bit, |         instance->generic.data_count_bit, | ||||||
|         (uint32_t)(instance->generic.data >> 32), |         (uint32_t)(instance->generic.data >> 32), | ||||||
| @ -292,7 +292,6 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriStrin | |||||||
|         instance->generic.id, |         instance->generic.id, | ||||||
|         instance->generic.channel, |         instance->generic.channel, | ||||||
|         instance->generic.battery_low, |         instance->generic.battery_low, | ||||||
|         (int16_t)instance->generic.temp, |         (double)instance->generic.temp, | ||||||
|         abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), |  | ||||||
|         instance->generic.humidity); |         instance->generic.humidity); | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  * Help |  * Help | ||||||
|  * https://github.com/merbanan/rtl_433/blob/ef2d37cf51e3264d11cde9149ef87de2f0a4d37a/src/devices/nexus.c
 |  * https://github.com/merbanan/rtl_433/blob/master/src/devices/nexus.c
 | ||||||
|  * |  * | ||||||
|  * Nexus sensor protocol with ID, temperature and optional humidity |  * Nexus sensor protocol with ID, temperature and optional humidity | ||||||
|  * also FreeTec (Pearl) NC-7345 sensors for FreeTec Weatherstation NC-7344, |  * also FreeTec (Pearl) NC-7345 sensors for FreeTec Weatherstation NC-7344, | ||||||
| @ -247,7 +247,7 @@ void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output) | |||||||
|         "%s %dbit\r\n" |         "%s %dbit\r\n" | ||||||
|         "Key:0x%lX%08lX\r\n" |         "Key:0x%lX%08lX\r\n" | ||||||
|         "Sn:0x%lX Ch:%d  Bat:%d\r\n" |         "Sn:0x%lX Ch:%d  Bat:%d\r\n" | ||||||
|         "Temp:%d.%d C Hum:%d%%", |         "Temp:%3.1f C Hum:%d%%", | ||||||
|         instance->generic.protocol_name, |         instance->generic.protocol_name, | ||||||
|         instance->generic.data_count_bit, |         instance->generic.data_count_bit, | ||||||
|         (uint32_t)(instance->generic.data >> 32), |         (uint32_t)(instance->generic.data >> 32), | ||||||
| @ -255,7 +255,6 @@ void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output) | |||||||
|         instance->generic.id, |         instance->generic.id, | ||||||
|         instance->generic.channel, |         instance->generic.channel, | ||||||
|         instance->generic.battery_low, |         instance->generic.battery_low, | ||||||
|         (int16_t)instance->generic.temp, |         (double)instance->generic.temp, | ||||||
|         abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), |  | ||||||
|         instance->generic.humidity); |         instance->generic.humidity); | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,9 +6,11 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { | |||||||
|     &ws_protocol_nexus_th, |     &ws_protocol_nexus_th, | ||||||
|     &ws_protocol_gt_wt_03, |     &ws_protocol_gt_wt_03, | ||||||
|     &ws_protocol_acurite_606tx, |     &ws_protocol_acurite_606tx, | ||||||
|  |     &ws_protocol_acurite_609txc, | ||||||
|     &ws_protocol_lacrosse_tx141thbv2, |     &ws_protocol_lacrosse_tx141thbv2, | ||||||
|     &ws_protocol_oregon2, |     &ws_protocol_oregon2, | ||||||
|     &ws_protocol_acurite_592txr, |     &ws_protocol_acurite_592txr, | ||||||
|  |     &ws_protocol_ambient_weather, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const SubGhzProtocolRegistry weather_station_protocol_registry = { | const SubGhzProtocolRegistry weather_station_protocol_registry = { | ||||||
|  | |||||||
| @ -6,8 +6,10 @@ | |||||||
| #include "nexus_th.h" | #include "nexus_th.h" | ||||||
| #include "gt_wt_03.h" | #include "gt_wt_03.h" | ||||||
| #include "acurite_606tx.h" | #include "acurite_606tx.h" | ||||||
|  | #include "acurite_609txc.h" | ||||||
| #include "lacrosse_tx141thbv2.h" | #include "lacrosse_tx141thbv2.h" | ||||||
| #include "oregon2.h" | #include "oregon2.h" | ||||||
| #include "acurite_592txr.h" | #include "acurite_592txr.h" | ||||||
|  | #include "ambient_weather.h" | ||||||
| 
 | 
 | ||||||
| extern const SubGhzProtocolRegistry weather_station_protocol_registry; | extern const SubGhzProtocolRegistry weather_station_protocol_registry; | ||||||
|  | |||||||
| @ -246,7 +246,7 @@ void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* out | |||||||
|         "%s %dbit\r\n" |         "%s %dbit\r\n" | ||||||
|         "Key:0x%lX%08lX\r\n" |         "Key:0x%lX%08lX\r\n" | ||||||
|         "Sn:0x%lX Ch:%d  Bat:%d\r\n" |         "Sn:0x%lX Ch:%d  Bat:%d\r\n" | ||||||
|         "Temp:%d.%d C Hum:%d%%", |         "Temp:%3.1f C Hum:%d%%", | ||||||
|         instance->generic.protocol_name, |         instance->generic.protocol_name, | ||||||
|         instance->generic.data_count_bit, |         instance->generic.data_count_bit, | ||||||
|         (uint32_t)(instance->generic.data >> 32), |         (uint32_t)(instance->generic.data >> 32), | ||||||
| @ -254,7 +254,6 @@ void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* out | |||||||
|         instance->generic.id, |         instance->generic.id, | ||||||
|         instance->generic.channel, |         instance->generic.channel, | ||||||
|         instance->generic.battery_low, |         instance->generic.battery_low, | ||||||
|         (int16_t)instance->generic.temp, |         (double)instance->generic.temp, | ||||||
|         abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))), |  | ||||||
|         instance->generic.humidity); |         instance->generic.humidity); | ||||||
| } | } | ||||||
|  | |||||||
| @ -75,12 +75,7 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) { | |||||||
| 
 | 
 | ||||||
|     if(model->generic->temp != WS_NO_TEMPERATURE) { |     if(model->generic->temp != WS_NO_TEMPERATURE) { | ||||||
|         canvas_draw_icon(canvas, 18, 42, &I_Therm_7x16); |         canvas_draw_icon(canvas, 18, 42, &I_Therm_7x16); | ||||||
|         snprintf( |         snprintf(buffer, sizeof(buffer), "%3.1f C", (double)model->generic->temp); | ||||||
|             buffer, |  | ||||||
|             sizeof(buffer), |  | ||||||
|             "%3.2d.%d C", |  | ||||||
|             (int16_t)model->generic->temp, |  | ||||||
|             abs(((int16_t)(model->generic->temp * 10) - (((int16_t)model->generic->temp) * 10)))); |  | ||||||
|         canvas_draw_str_aligned(canvas, 63, 46, AlignRight, AlignTop, buffer); |         canvas_draw_str_aligned(canvas, 63, 46, AlignRight, AlignTop, buffer); | ||||||
|         canvas_draw_circle(canvas, 55, 45, 1); |         canvas_draw_circle(canvas, 55, 45, 1); | ||||||
|     } |     } | ||||||
|  | |||||||
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