Compare commits
	
		
			28 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 027ea9ea36 | ||
|   | 4f50ef9b54 | ||
|   | a11fcfc72d | ||
|   | 95737958ad | ||
|   | f9f67e6d54 | ||
|   | dd182ab179 | ||
|   | fc043da9c6 | ||
|   | d73d007797 | ||
|   | 3fd5f15e7f | ||
|   | 1f9fd4c42a | ||
|   | 48e4de1213 | ||
|   | 0789cbdefa | ||
|   | d289545bf8 | ||
|   | 34539cda17 | ||
|   | 0b15fc3807 | ||
|   | 4d99a454fd | ||
|   | 3452fbc351 | ||
|   | 8c04947aa2 | ||
|   | 7eeb60e17e | ||
|   | d511d76a1b | ||
|   | a7b60bf2a6 | ||
|   | 895694c624 | ||
|   | 2a0a54a224 | ||
|   | 35e74c07d1 | ||
|   | f6a38352e8 | ||
|   | 76ed466eb4 | ||
|   | c9e3f20314 | ||
|   | 0084443ed7 | 
							
								
								
									
										68
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| # Who owns all the fish by default | ||||
| * @skotopes @DrZlo13 @hedger @gsurkov | ||||
| 
 | ||||
| # Apps | ||||
| /applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich | ||||
| /applications/debug/accessor/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/debug/battery_test_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich | ||||
| /applications/debug/bt_debug_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich | ||||
| /applications/debug/file_browser_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/debug/lfrfid_debug/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/debug/text_box_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/debug/uart_echo/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/debug/usb_mouse/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/debug/usb_test/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| 
 | ||||
| /applications/main/archive/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/main/bad_usb/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/main/gpio/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/main/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| /applications/main/infrared/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| /applications/main/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra | ||||
| /applications/main/subghz/ @skotopes @DrZlo13 @hedger @gsurkov @Skorpionm | ||||
| /applications/main/u2f/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| 
 | ||||
| /applications/services/bt/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich | ||||
| /applications/services/cli/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/services/crypto/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/services/desktop/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/services/dolphin/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/services/power/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich | ||||
| /applications/services/rpc/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| 
 | ||||
| /applications/services/bt_settings_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich | ||||
| /applications/services/desktop_settings/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/services/dolphin_passport/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /applications/services/power_settings_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich | ||||
| 
 | ||||
| /applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| 
 | ||||
| /applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @gornekich @Astrrra @Skorpionm | ||||
| 
 | ||||
| /applications/examples/example_thermo/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| 
 | ||||
| # Firmware targets | ||||
| /targets/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| 
 | ||||
| # Assets | ||||
| /applications/main/infrared/resources/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| 
 | ||||
| # Documentation | ||||
| /documentation/ @skotopes @DrZlo13 @hedger @gsurkov @drunkbatya | ||||
| /scripts/toolchain/ @skotopes @DrZlo13 @hedger @gsurkov @drunkbatya | ||||
| 
 | ||||
| # Lib | ||||
| /lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich | ||||
| /lib/digital_signal/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich | ||||
| /lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| /lib/lfrfid/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /lib/libusb_stm32/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /lib/mbedtls/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /lib/micro-ecc/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /lib/nanopb/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov | ||||
| /lib/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra | ||||
| /lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov | ||||
| /lib/subghz/ @skotopes @DrZlo13 @hedger @gsurkov @Skorpionm | ||||
| 
 | ||||
| # CI/CD | ||||
| /.github/workflows/ @skotopes @DrZlo13 @hedger @gsurkov @drunkbatya | ||||
							
								
								
									
										46
									
								
								.github/ISSUE_TEMPLATE/01_bug_report.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								.github/ISSUE_TEMPLATE/01_bug_report.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| name: Bug report | ||||
| description: File a bug reports regarding the firmware. | ||||
| labels: ['bug'] | ||||
| body: | ||||
| - type: markdown | ||||
|   attributes: | ||||
|     value: | | ||||
|       Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Flipper Zero firmware. | ||||
|       If you require help with the Flipper zero and its firmware, we ask that you join [our forum](https://forum.flipperzero.one) | ||||
| - type: textarea | ||||
|   id: description | ||||
|   attributes: | ||||
|     label: Describe the bug. | ||||
|     description: "A clear and concise description of what the bug is." | ||||
|   validations: | ||||
|     required: true | ||||
| - type: textarea | ||||
|   id: repro | ||||
|   attributes:  | ||||
|     label: Reproduction | ||||
|     description: "How can this bug be reproduced?" | ||||
|     placeholder: | | ||||
|       1. Switch on... | ||||
|       2. Press button '....' | ||||
|       3. Wait for the moon phase | ||||
|       4. It burns | ||||
|   validations: | ||||
|     required: true | ||||
| - type: input | ||||
|   id: target | ||||
|   attributes: | ||||
|     label: Target | ||||
|     description: Specify the target | ||||
|     # Target seems to be largely ignored by outside sources. | ||||
| - type: textarea | ||||
|   id: logs | ||||
|   attributes: | ||||
|     label: Logs | ||||
|     description: Attach your debug logs here | ||||
|     render: Text | ||||
|     # Avoid rendering as Markdown here. | ||||
| - type: textarea | ||||
|   id: anything-else | ||||
|   attributes: | ||||
|     label: Anything else? | ||||
|     description: Let us know if you have anything else to share. | ||||
							
								
								
									
										21
									
								
								.github/ISSUE_TEMPLATE/02_enhancements.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.github/ISSUE_TEMPLATE/02_enhancements.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| name: Enhancements | ||||
| description: Suggest improvements for any existing functionality within the firmware. | ||||
| body: | ||||
| - type: markdown | ||||
|   attributes: | ||||
|     value: | | ||||
|       Thank you for taking the time to fill out an issue. This template is meant for feature requests and improvements to already existing functionality. | ||||
|       If you require help with the Flipper zero and its firmware, we ask that you join [our forum](https://forum.flipperzero.one) | ||||
| - type: textarea | ||||
|   id: proposal | ||||
|   attributes: | ||||
|     label: "Describe the enhancement you're suggesting." | ||||
|     description: | | ||||
|       Feel free to describe in as much detail as you wish. | ||||
|   validations: | ||||
|     required: true | ||||
| - type: textarea | ||||
|   id: anything-else | ||||
|   attributes: | ||||
|     label: Anything else? | ||||
|     description: Let us know if you have anything else to share. | ||||
							
								
								
									
										24
									
								
								.github/ISSUE_TEMPLATE/03_feature_request.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								.github/ISSUE_TEMPLATE/03_feature_request.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| name: Feature Request | ||||
| description: For feature requests regarding the firmware. | ||||
| labels: ['feature request'] | ||||
| body: | ||||
| - type: markdown | ||||
|   attributes: | ||||
|     value: | | ||||
|       Thank you for taking the time to fill out an issue, this template is meant for any feature suggestions. | ||||
|       If you require help with the Flipper zero and its firmware, we ask that you join [our forum](https://forum.flipperzero.one) | ||||
| - type: textarea | ||||
|   id: proposal | ||||
|   attributes: | ||||
|     label: "Description of the feature you're suggesting." | ||||
|     description: | | ||||
|       Please describe your feature request in as many details as possible. | ||||
|         - Describe what it should do. | ||||
|         - Note whether it is to extend existing functionality or introduce new functionality. | ||||
|   validations: | ||||
|     required: true | ||||
| - type: textarea | ||||
|   id: anything-else | ||||
|   attributes: | ||||
|     label: Anything else? | ||||
|     description: Let us know if you have anything else to share. | ||||
							
								
								
									
										5
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| blank_issues_enabled: true | ||||
| contact_links: | ||||
|   - name: Need help? | ||||
|     url: https://forum.flipperzero.one | ||||
|     about: For any question regarding on how to use the Flipper Zero and its firmware. | ||||
							
								
								
									
										78
									
								
								.github/actions/submit_sdk/action.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								.github/actions/submit_sdk/action.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| name: Submit SDK to Catalog | ||||
| author: hedger | ||||
| description: | | ||||
|   This action checks if SDK exists in the catalog and if not, adds and/or publishes it. | ||||
| 
 | ||||
| inputs: | ||||
|   catalog-url: | ||||
|     description: The URL of the Catalog API. Must not be empty or end with a /. | ||||
|     required: true | ||||
|   catalog-api-token: | ||||
|     description: The token to use to authenticate with the Catalog API. Must not be empty. | ||||
|     required: true | ||||
|   firmware-api: | ||||
|     description: Fimware's API version, major.minor | ||||
|     required: true | ||||
|   firmware-target: | ||||
|     description: Firmware's target, e.g. f7/f18 | ||||
|     required: true | ||||
|   firmware-version: | ||||
|     description: Firmware's version, e.g. 0.13.37-rc3, or 0.13.37 | ||||
|     required: true | ||||
| 
 | ||||
| runs: | ||||
|   using: composite | ||||
|   steps: | ||||
|     - name: Check inputs | ||||
|       shell: bash | ||||
|       run: | | ||||
|         if [ -z "${{ inputs.catalog-url }}" ] ; then | ||||
|           echo "Invalid catalog-url: ${{ inputs.catalog-url }}" | ||||
|           exit 1 | ||||
|         fi | ||||
|         if [ -z "${{ inputs.catalog-api-token }}" ] ; then | ||||
|           echo "Invalid catalog-api-token: ${{ inputs.catalog-api-token }}" | ||||
|           exit 1 | ||||
|         fi | ||||
|         if ! echo "${{ inputs.firmware-api }}" | grep -q "^[0-9]\+\.[0-9]\+$" ; then | ||||
|           echo "Invalid firmware-api: ${{ inputs.firmware-api }}" | ||||
|           exit 1 | ||||
|         fi | ||||
|         if ! echo "${{ inputs.firmware-target }}" | grep -q "^f[0-9]\+$" ; then | ||||
|           echo "Invalid firmware-target: ${{ inputs.firmware-target }}" | ||||
|           exit 1 | ||||
|         fi | ||||
|         if ! echo "${{ inputs.firmware-version }}" | grep -q "^[0-9]\+\.[0-9]\+\.[0-9]\+\(-rc\)\?\([0-9]\+\)\?$" ; then | ||||
|           echo "Invalid firmware-version: ${{ inputs.firmware-version }}" | ||||
|           exit 1 | ||||
|         fi | ||||
|     - name: Submit SDK | ||||
|       shell: bash | ||||
|       run: | | ||||
|         curl -sX 'GET' \ | ||||
|           '${{ inputs.catalog-url }}/api/v0/0/sdk?length=500' \ | ||||
|           -H 'Accept: application/json' > sdk_versions.json | ||||
|         if jq -r -e ".[] | select((.api == \"${{ inputs.firmware-api }}\") and .target == \"${{ inputs.firmware-target }}\")" sdk_versions.json > found_sdk.json ; then | ||||
|           echo "API version ${{ inputs.firmware-api }} already exists in catalog" | ||||
|           if [ $(jq -r -e ".released_at" found_sdk.json) != "null" ] ; then | ||||
|             echo "API version is already released" | ||||
|             exit 0 | ||||
|           fi | ||||
|           if ! echo "${{ inputs.firmware-version }}" | grep -q -- "-rc" ; then | ||||
|             SDK_ID=$(jq -r ._id found_sdk.json) | ||||
|             echo "Marking SDK $SDK_ID as released" | ||||
|             curl -X 'POST' \ | ||||
|               "${{ inputs.catalog-url }}/api/v0/0/sdk/${SDK_ID}/release" \ | ||||
|               -H 'Accept: application/json' \ | ||||
|               -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \ | ||||
|               -d '' | ||||
|           fi | ||||
|         else | ||||
|           echo "API version ${{ inputs.firmware-api }} doesn't exist in catalog, adding" | ||||
|           curl -X 'POST' \ | ||||
|             '${{ inputs.catalog-url }}/api/v0/0/sdk' \ | ||||
|             -H 'Accept: application/json' \ | ||||
|             -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \ | ||||
|             -H 'Content-Type: application/json' \ | ||||
|             -d "{\"name\": \"${{ inputs.firmware-version }}\", \"target\": \"${{ inputs.firmware-target }}\", \"api\": \"${{ inputs.firmware-api }}\"}" | ||||
|         fi | ||||
							
								
								
									
										
											BIN
										
									
								
								.github/assets/Born2bSportyV2.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.github/assets/Born2bSportyV2.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								.github/assets/dark_theme_banner.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.github/assets/dark_theme_banner.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 48 KiB | 
							
								
								
									
										
											BIN
										
									
								
								.github/assets/latest-firmware-template.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.github/assets/latest-firmware-template.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								.github/assets/light_theme_banner.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.github/assets/light_theme_banner.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 44 KiB | 
							
								
								
									
										13
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| # What's new | ||||
| 
 | ||||
| - [ Describe changes here ] | ||||
| 
 | ||||
| # Verification  | ||||
| 
 | ||||
| - [ Describe how to verify changes ] | ||||
| 
 | ||||
| # Checklist (For Reviewer) | ||||
| 
 | ||||
| - [ ] PR has description of feature/bug or link to Confluence/Jira task | ||||
| - [ ] Description contains actions to verify feature/bugfix | ||||
| - [ ] I've built this code, uploaded it to the device and verified feature/bugfix | ||||
							
								
								
									
										170
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,170 @@ | ||||
| name: 'Build' | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - dev | ||||
|     tags: | ||||
|       - '*' | ||||
|   pull_request: | ||||
| 
 | ||||
| env: | ||||
|   DEFAULT_TARGET: f7 | ||||
|   FBT_TOOLCHAIN_PATH: /runner/_work | ||||
|   FBT_GIT_SUBMODULE_SHALLOW: 1 | ||||
| 
 | ||||
| jobs: | ||||
|   main: | ||||
|     runs-on: [self-hosted, FlipperZeroShell] | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         target: [f7, f18]     | ||||
|     steps: | ||||
|       - name: 'Wipe workspace' | ||||
|         run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; | ||||
| 
 | ||||
|       - name: 'Checkout code' | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 1 | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Get commit details' | ||||
|         id: names | ||||
|         run: | | ||||
|           BUILD_TYPE='DEBUG=1 COMPACT=0' | ||||
|           if [[ ${{ github.event_name }} == 'pull_request' ]]; then | ||||
|             TYPE="pull" | ||||
|           elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then | ||||
|             TYPE="tag" | ||||
|             BUILD_TYPE='DEBUG=0 COMPACT=1' | ||||
|           else | ||||
|             TYPE="other" | ||||
|           fi | ||||
|           python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" | ||||
|           echo "event_type=$TYPE" >> $GITHUB_OUTPUT | ||||
|           echo "FBT_BUILD_TYPE=$BUILD_TYPE" >> $GITHUB_ENV | ||||
|           echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV | ||||
|           echo "TARGET_HW=$(echo "${{ matrix.target }}" | sed 's/f//')" >> $GITHUB_ENV | ||||
| 
 | ||||
|       - name: 'Check API versions for consistency between targets' | ||||
|         run: | | ||||
|           set -e | ||||
|           N_API_HEADER_SIGNATURES=`ls -1 targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l` | ||||
|           if [ $N_API_HEADER_SIGNATURES != 1 ] ; then | ||||
|             echo API versions aren\'t matching for available targets. Please update! | ||||
|             echo API versions are: | ||||
|             head -n2 targets/f*/api_symbols.csv | ||||
|             exit 1 | ||||
|           fi | ||||
| 
 | ||||
|       - name: 'Build the firmware and apps' | ||||
|         id: build-fw | ||||
|         run: | | ||||
|           ./fbt TARGET_HW=$TARGET_HW $FBT_BUILD_TYPE copro_dist updater_package fap_dist | ||||
|           echo "firmware_api=$(./fbt TARGET_HW=$TARGET_HW get_apiversion)" >> $GITHUB_OUTPUT | ||||
| 
 | ||||
|       - name: 'Check for uncommitted changes' | ||||
|         run: | | ||||
|           git diff --exit-code | ||||
| 
 | ||||
|       - name: 'Copy build output' | ||||
|         run: | | ||||
|           set -e | ||||
|           rm -rf artifacts map_analyser_files || true | ||||
|           mkdir artifacts map_analyser_files | ||||
|           cp dist/${TARGET}-*/* artifacts/ || true | ||||
|           tar czpf "artifacts/flipper-z-${TARGET}-resources-${SUFFIX}.tgz" \ | ||||
|             -C build/latest resources | ||||
|           tar czpf "artifacts/flipper-z-${TARGET}-debugapps-${SUFFIX}.tgz" \ | ||||
|             -C dist/${TARGET}-*/apps/Debug . | ||||
|           tar czpf "artifacts/flipper-z-${TARGET}-appsymbols-${SUFFIX}.tgz" \ | ||||
|             -C dist/${TARGET}-*/debug_elf . | ||||
| 
 | ||||
|       - name: 'Copy universal artifacts' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET }} | ||||
|         run: | | ||||
|           tar czpf "artifacts/flipper-z-any-scripts-${SUFFIX}.tgz" scripts | ||||
|           cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" | ||||
| 
 | ||||
|       - name: 'Upload artifacts to update server' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         run: | | ||||
|           FILES=$(for ARTIFACT in $(find artifacts -maxdepth 1 -not -type d); do echo "-F files=@${ARTIFACT}"; done) | ||||
|           curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ | ||||
|               -F "branch=${BRANCH_NAME}" \ | ||||
|               -F "version_token=${COMMIT_SHA}" \ | ||||
|               ${FILES[@]} \ | ||||
|               "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles | ||||
| 
 | ||||
|       - name: 'Copy & analyse map analyser files' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET }} | ||||
|         run: | | ||||
|           cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map | ||||
|           cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf map_analyser_files/firmware.elf | ||||
|           cp ${{ github.event_path }} map_analyser_files/event.json | ||||
|           source scripts/toolchain/fbtenv.sh | ||||
|           get_size() | ||||
|           { | ||||
|             SECTION="$1"; | ||||
|             arm-none-eabi-size \ | ||||
|               -A map_analyser_files/firmware.elf \ | ||||
|               | grep "^$SECTION" | awk '{print $2}' | ||||
|           } | ||||
|           export BSS_SIZE="$(get_size ".bss")" | ||||
|           export TEXT_SIZE="$(get_size ".text")" | ||||
|           export RODATA_SIZE="$(get_size ".rodata")" | ||||
|           export DATA_SIZE="$(get_size ".data")" | ||||
|           export FREE_FLASH_SIZE="$(get_size ".free_flash")" | ||||
|           python3 -m pip install mariadb==1.1.6 cxxfilt==0.3.0 | ||||
|           python3 scripts/map_parser.py map_analyser_files/firmware.elf.map map_analyser_files/firmware.elf.map.all | ||||
|           python3 scripts/map_mariadb_insert.py \ | ||||
|               ${{ secrets.AMAP_MARIADB_USER }} \ | ||||
|               ${{ secrets.AMAP_MARIADB_PASSWORD }} \ | ||||
|               ${{ secrets.AMAP_MARIADB_HOST }} \ | ||||
|               ${{ secrets.AMAP_MARIADB_PORT }} \ | ||||
|               ${{ secrets.AMAP_MARIADB_DATABASE }} \ | ||||
|               map_analyser_files/firmware.elf.map.all | ||||
| 
 | ||||
|       - name: 'Find previous comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }} | ||||
|         uses: peter-evans/find-comment@v2 | ||||
|         id: find-comment | ||||
|         with: | ||||
|           issue-number: ${{ github.event.pull_request.number }} | ||||
|           comment-author: 'github-actions[bot]' | ||||
|           body-includes: 'Compiled ${{ matrix.target }} firmware for commit' | ||||
| 
 | ||||
|       - name: 'Create or update comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }} | ||||
|         uses: peter-evans/create-or-update-comment@v3 | ||||
|         with: | ||||
|           comment-id: ${{ steps.find-comment.outputs.comment-id }} | ||||
|           issue-number: ${{ github.event.pull_request.number }} | ||||
|           body: | | ||||
|             **Compiled ${{ matrix.target }} firmware for commit `${{steps.names.outputs.commit_sha}}`:** | ||||
|             - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz) | ||||
|             - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu) | ||||
|             - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) | ||||
|           edit-mode: replace | ||||
| 
 | ||||
|       - name: 'SDK submission to staging catalog' | ||||
|         if: ${{ steps.names.outputs.event_type == 'tag' && matrix.target == env.DEFAULT_TARGET }} | ||||
|         uses: ./.github/actions/submit_sdk | ||||
|         with: | ||||
|           catalog-url: ${{ secrets.CATALOG_STAGING_URL }} | ||||
|           catalog-api-token: ${{ secrets.CATALOG_STAGING_API_TOKEN }} | ||||
|           firmware-api: ${{ steps.build-fw.outputs.firmware_api }} | ||||
|           firmware-target: ${{ matrix.target }} | ||||
|           firmware-version: ${{ steps.names.outputs.suffix }} | ||||
| 
 | ||||
|       - name: 'SDK submission to prod catalog' | ||||
|         if: ${{ steps.names.outputs.event_type == 'tag' && matrix.target == env.DEFAULT_TARGET }} | ||||
|         uses: ./.github/actions/submit_sdk | ||||
|         with: | ||||
|           catalog-url: ${{ secrets.CATALOG_URL }} | ||||
|           catalog-api-token: ${{ secrets.CATALOG_API_TOKEN }} | ||||
|           firmware-api: ${{ steps.build-fw.outputs.firmware_api }} | ||||
|           firmware-target: ${{ matrix.target }} | ||||
|           firmware-version: ${{ steps.names.outputs.suffix }} | ||||
							
								
								
									
										86
									
								
								.github/workflows/build_compact.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								.github/workflows/build_compact.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,86 @@ | ||||
| name: 'Compact build' | ||||
| 
 | ||||
| on: | ||||
|   pull_request: | ||||
| 
 | ||||
| env: | ||||
|   FBT_TOOLCHAIN_PATH: /runner/_work | ||||
|   FBT_GIT_SUBMODULE_SHALLOW: 1 | ||||
| 
 | ||||
| jobs: | ||||
|   compact: | ||||
|     runs-on: [self-hosted, FlipperZeroShell] | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         target: [f7, f18] | ||||
|     steps: | ||||
|       - name: 'Wipe workspace' | ||||
|         run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; | ||||
| 
 | ||||
|       - name: 'Checkout code' | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 1 | ||||
|           submodules: false | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Get commit details' | ||||
|         run: | | ||||
|           if [[ ${{ github.event_name }} == 'pull_request' ]]; then | ||||
|             TYPE="pull" | ||||
|           elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then | ||||
|             TYPE="tag" | ||||
|           else | ||||
|             TYPE="other" | ||||
|           fi | ||||
|           python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" | ||||
| 
 | ||||
|       - name: 'Build the firmware' | ||||
|         id: build-fw | ||||
|         run: | | ||||
|           set -e | ||||
|           TARGET="$(echo '${{ matrix.target }}' | sed 's/f//')"; \ | ||||
|           ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package | ||||
|           echo "sdk-file=$(ls dist/${{ matrix.target }}-*/flipper-z-${{ matrix.target }}-sdk-*.zip)" >> $GITHUB_OUTPUT | ||||
|           echo "hw-target-code=$TARGET" >> $GITHUB_OUTPUT | ||||
| 
 | ||||
|       - name: Deploy uFBT with SDK | ||||
|         uses: flipperdevices/flipperzero-ufbt-action@v0.1 | ||||
|         with: | ||||
|           task: setup | ||||
|           sdk-file: ${{ steps.build-fw.outputs.sdk-file }} | ||||
|           sdk-hw-target: ${{ steps.build-fw.outputs.hw-target-code }} | ||||
| 
 | ||||
|       - name: Build test app with SDK | ||||
|         run: | | ||||
|           mkdir testapp | ||||
|           cd testapp | ||||
|           ufbt create APPID=testapp | ||||
|           ufbt | ||||
| 
 | ||||
|       - name: Build example & external apps with uFBT | ||||
|         run: | | ||||
|           for appdir in 'applications/examples'; do | ||||
|             for app in $(find "$appdir" -maxdepth 1 -mindepth 1 -type d); do | ||||
|               pushd $app | ||||
|               TARGETS_FAM=$(grep "targets" application.fam || echo "${{ matrix.target }}") | ||||
|               if ! grep -q "${{ matrix.target }}" <<< $TARGETS_FAM ; then | ||||
|                   echo Skipping unsupported app: $app | ||||
|                   popd | ||||
|                   continue | ||||
|               fi | ||||
|               echo Building $app | ||||
|               ufbt | ||||
|               popd | ||||
|             done | ||||
|           done | ||||
| 
 | ||||
| ## Uncomment this for a single job that will run only if all targets are built successfully           | ||||
| #  report-status: | ||||
| #    name: Report status | ||||
| #    needs: [compact] | ||||
| #    if: always() && !contains(needs.*.result, 'failure') | ||||
| #    runs-on: [self-hosted, FlipperZeroShell] | ||||
| #    steps: | ||||
| #      - run: echo "All good ✨" ; | ||||
							
								
								
									
										101
									
								
								.github/workflows/lint_and_submodule_check.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								.github/workflows/lint_and_submodule_check.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,101 @@ | ||||
| name: 'Lint sources & check submodule integrity' | ||||
| 
 | ||||
| on: | ||||
|   pull_request: | ||||
| 
 | ||||
| env: | ||||
|   TARGETS: f7 | ||||
|   FBT_TOOLCHAIN_PATH: /runner/_work | ||||
|   SET_GH_OUTPUT: 1 | ||||
| 
 | ||||
| jobs: | ||||
|   lint_sources_check_submodules: | ||||
|     runs-on: [self-hosted, FlipperZeroShell] | ||||
|     steps: | ||||
|       - name: 'Wipe workspace' | ||||
|         run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; | ||||
| 
 | ||||
|       - name: 'Checkout code' | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 2 | ||||
|           ref: ${{ github.sha }} | ||||
| 
 | ||||
|       - name: 'Check protobuf branch' | ||||
|         run: | | ||||
|           git submodule update --init; | ||||
|           SUB_PATH="assets/protobuf"; | ||||
|           SUB_BRANCH="dev"; | ||||
|           SUB_COMMITS_MIN=40; | ||||
|           cd "$SUB_PATH"; | ||||
|           SUBMODULE_HASH="$(git rev-parse HEAD)"; | ||||
|           BRANCHES=$(git branch -r --contains "$SUBMODULE_HASH"); | ||||
|           COMMITS_IN_BRANCH="$(git rev-list --count dev)"; | ||||
|           if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then | ||||
|             echo "name=fails::error" >> $GITHUB_OUTPUT; | ||||
|             echo "::error::Error: Too few commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)"; | ||||
|             exit 1; | ||||
|           fi | ||||
|           if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then | ||||
|             echo "name=fails::error" >> $GITHUB_OUTPUT; | ||||
|             echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH"; | ||||
|             exit 1; | ||||
|           fi | ||||
| 
 | ||||
|       - name: 'Check for new TODOs' | ||||
|         id: check_todos | ||||
|         if: github.event_name == 'pull_request' | ||||
|         run: | | ||||
|           set +e; | ||||
|           git diff --unified=0 --no-color ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -E '^\+' | grep -i -E '(TODO|HACK|FIXME|XXX)[ :]' | grep -v -- '-nofl' > lines.log; | ||||
|           MISSING_TICKETS=$( grep -v -E 'FL-[0-9]+' lines.log ); | ||||
|           if [ -n "$MISSING_TICKETS" ]; then | ||||
|             echo "Error: Missing ticket number in \`TODO\` comment(s)" >> $GITHUB_STEP_SUMMARY; | ||||
|             echo "\`\`\`" >> $GITHUB_STEP_SUMMARY; | ||||
|             echo "$MISSING_TICKETS" >> $GITHUB_STEP_SUMMARY; | ||||
|             echo "\`\`\`" >> $GITHUB_STEP_SUMMARY; | ||||
|             exit 1; | ||||
|           else | ||||
|             echo "No new TODOs without tickets found" >> $GITHUB_STEP_SUMMARY; | ||||
|           fi | ||||
| 
 | ||||
|       - name: 'Check Python code formatting' | ||||
|         id: syntax_check_py | ||||
|         if: always() | ||||
|         run: | | ||||
|           set +e; | ||||
|           ./fbt -s lint_py 2>&1 | tee lint-py.log; | ||||
|           if [ "${PIPESTATUS[0]}" -ne 0 ]; then | ||||
|             # Save multiline output | ||||
|             echo "errors=1" >> $GITHUB_OUTPUT; | ||||
|             printf "Python Lint errors:\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY; | ||||
|             echo "$(cat lint-py.log)" >> $GITHUB_STEP_SUMMARY; | ||||
|             printf "\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY; | ||||
|             exit 1; | ||||
|           else | ||||
|             echo "Python Lint: all good ✨" >> $GITHUB_STEP_SUMMARY; | ||||
|           fi | ||||
| 
 | ||||
|       - name: 'Check C++ code formatting' | ||||
|         id: syntax_check_cpp | ||||
|         if: always() | ||||
|         run: | | ||||
|           set +e; | ||||
|           ./fbt -s lint 2>&1 | tee lint-cpp.log; | ||||
|           if [ "${PIPESTATUS[0]}" -ne 0 ]; then | ||||
|             # Save multiline output | ||||
|             echo "errors=1" >> $GITHUB_OUTPUT; | ||||
|             printf "C Lint errors:\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY; | ||||
|             echo "$(cat lint-cpp.log)" >> $GITHUB_STEP_SUMMARY; | ||||
|             printf "\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY; | ||||
|             exit 1; | ||||
|           else | ||||
|             echo "C Lint: all good ✨" >> $GITHUB_STEP_SUMMARY; | ||||
|           fi | ||||
| 
 | ||||
|       - name: Report code formatting errors | ||||
|         if: ( steps.syntax_check_py.outputs.errors || steps.syntax_check_cpp.outputs.errors ) && github.event.pull_request | ||||
|         run: | | ||||
|           echo "Code formatting errors found"; | ||||
|           echo "Please run './fbt format' or './fbt format_py' to fix them"; | ||||
|           exit 1; | ||||
							
								
								
									
										42
									
								
								.github/workflows/merge_report.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								.github/workflows/merge_report.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| name: 'Check FL ticket in PR name' | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - dev | ||||
| 
 | ||||
| env: | ||||
|   FBT_TOOLCHAIN_PATH: /runner/_work | ||||
| 
 | ||||
| jobs: | ||||
|   merge_report: | ||||
|     runs-on: [self-hosted, FlipperZeroShell] | ||||
|     steps: | ||||
|       - name: 'Wipe workspace' | ||||
|         run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; | ||||
| 
 | ||||
|       - name: 'Checkout code' | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 1 | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Get commit details' | ||||
|         run: | | ||||
|           if [[ ${{ github.event_name }} == 'pull_request' ]]; then | ||||
|             TYPE="pull" | ||||
|           elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then | ||||
|             TYPE="tag" | ||||
|           else | ||||
|             TYPE="other" | ||||
|           fi | ||||
|           python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" | ||||
| 
 | ||||
|       - name: 'Check ticket and report' | ||||
|         run: | | ||||
|           source scripts/toolchain/fbtenv.sh | ||||
|           python3 -m pip install slack_sdk | ||||
|           python3 scripts/merge_report_qa.py \ | ||||
|               ${{ secrets.QA_REPORT_SLACK_TOKEN }} \ | ||||
|               ${{ secrets.QA_REPORT_SLACK_CHANNEL }} | ||||
| 
 | ||||
							
								
								
									
										88
									
								
								.github/workflows/pvs_studio.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								.github/workflows/pvs_studio.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | ||||
| name: 'Static C/C++ analysis with PVS-Studio' | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - dev | ||||
|   pull_request: | ||||
| 
 | ||||
| env: | ||||
|   TARGETS: f7 | ||||
|   DEFAULT_TARGET: f7 | ||||
|   FBT_TOOLCHAIN_PATH: /runner/_work | ||||
|   FBT_GIT_SUBMODULE_SHALLOW: 1 | ||||
| 
 | ||||
| jobs: | ||||
|   analyse_c_cpp: | ||||
|     if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|     runs-on: [self-hosted, FlipperZeroShell] | ||||
|     steps: | ||||
|       - name: 'Wipe workspace' | ||||
|         run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; | ||||
| 
 | ||||
|       - name: 'Checkout code' | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 1 | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Get commit details' | ||||
|         id: names | ||||
|         run: | | ||||
|           if [[ ${{ github.event_name }} == 'pull_request' ]]; then | ||||
|             TYPE="pull" | ||||
|           elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then | ||||
|             TYPE="tag" | ||||
|           else | ||||
|             TYPE="other" | ||||
|           fi | ||||
|           python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}" | ||||
| 
 | ||||
|       - name: 'Supply PVS credentials' | ||||
|         run: | | ||||
|           pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} | ||||
| 
 | ||||
|       - name: 'Convert PVS-Studio output to html and detect warnings' | ||||
|         id: pvs-warn | ||||
|         run: | | ||||
|           WARNINGS=0 | ||||
|           ./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1 | ||||
|           echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT | ||||
| 
 | ||||
|       - name: 'Upload report' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }} | ||||
|         uses: prewk/s3-cp-action@v2 | ||||
|         with: | ||||
|           aws_s3_endpoint: "${{ secrets.PVS_AWS_ENDPOINT }}" | ||||
|           aws_access_key_id: "${{ secrets.PVS_AWS_ACCESS_KEY }}" | ||||
|           aws_secret_access_key: "${{ secrets.PVS_AWS_SECRET_KEY }}" | ||||
|           source: "./build/f7-firmware-DC/pvsreport" | ||||
|           dest: "s3://${{ secrets.PVS_AWS_BUCKET }}/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/" | ||||
|           flags: "--recursive --acl public-read" | ||||
| 
 | ||||
|       - name: 'Find Previous Comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }} | ||||
|         uses: peter-evans/find-comment@v2 | ||||
|         id: fc | ||||
|         with: | ||||
|           issue-number: ${{ github.event.pull_request.number }} | ||||
|           comment-author: 'github-actions[bot]' | ||||
|           body-includes: 'PVS-Studio report for commit' | ||||
| 
 | ||||
|       - name: 'Create or update comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }} | ||||
|         uses: peter-evans/create-or-update-comment@v1 | ||||
|         with: | ||||
|           comment-id: ${{ steps.fc.outputs.comment-id }} | ||||
|           issue-number: ${{ github.event.pull_request.number }} | ||||
|           body: | | ||||
|             **PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:** | ||||
|             - [Report](https://pvs.flipp.dev/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html) | ||||
|           edit-mode: replace | ||||
| 
 | ||||
|       - name: 'Raise exception' | ||||
|         if: ${{ steps.pvs-warn.outputs.warnings != 0 }} | ||||
|         run: | | ||||
|           echo "Please fix all PVS warnings before merge" | ||||
|           exit 1 | ||||
| 
 | ||||
							
								
								
									
										15
									
								
								.github/workflows/reindex.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								.github/workflows/reindex.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| name: 'Reindex' | ||||
| 
 | ||||
| on: | ||||
|   release: | ||||
|     types: [prereleased,released] | ||||
| 
 | ||||
| jobs: | ||||
|   reindex: | ||||
|     name: 'Reindex updates' | ||||
|     runs-on: [self-hosted, FlipperZeroShell] | ||||
|     steps: | ||||
|       - name: Trigger reindex | ||||
|         run: | | ||||
|           curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ | ||||
|               "${{ secrets.INDEXER_URL }}"/firmware/reindex | ||||
							
								
								
									
										69
									
								
								.github/workflows/unit_tests.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								.github/workflows/unit_tests.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| name: 'Unit tests' | ||||
| 
 | ||||
| on: | ||||
|   pull_request: | ||||
| 
 | ||||
| env: | ||||
|   TARGETS: f7 | ||||
|   DEFAULT_TARGET: f7 | ||||
|   FBT_TOOLCHAIN_PATH: /opt | ||||
|   FBT_GIT_SUBMODULE_SHALLOW: 1 | ||||
| 
 | ||||
| jobs: | ||||
|   run_units_on_bench: | ||||
|     runs-on: [self-hosted, FlipperZeroUnitTest] | ||||
|     steps: | ||||
|       - name: 'Wipe workspace' | ||||
|         run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; | ||||
| 
 | ||||
|       - name: Checkout code | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 1 | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Get flipper from device manager (mock)' | ||||
|         id: device | ||||
|         run: | | ||||
|           echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT | ||||
| 
 | ||||
|       - name: 'Flash unit tests firmware' | ||||
|         id: flashing | ||||
|         if: success() | ||||
|         timeout-minutes: 10 | ||||
|         run: | | ||||
|           ./fbt resources firmware_latest flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 | ||||
| 
 | ||||
|       - name: 'Wait for flipper and format ext' | ||||
|         id: format_ext | ||||
|         if: steps.flashing.outcome == 'success' | ||||
|         timeout-minutes: 5 | ||||
|         run: | | ||||
|           source scripts/toolchain/fbtenv.sh | ||||
|           python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} | ||||
|           python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext | ||||
| 
 | ||||
|       - name: 'Copy assets and unit data, reboot and wait for flipper' | ||||
|         id: copy | ||||
|         if: steps.format_ext.outcome == 'success' | ||||
|         timeout-minutes: 7 | ||||
|         run: | | ||||
|           source scripts/toolchain/fbtenv.sh | ||||
|           python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} | ||||
|           rm -rf build/latest/resources/dolphin | ||||
|           python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send build/latest/resources /ext | ||||
|           python3 scripts/power.py -p ${{steps.device.outputs.flipper}} reboot | ||||
|           python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} | ||||
| 
 | ||||
|       - name: 'Run units and validate results' | ||||
|         id: run_units | ||||
|         if: steps.copy.outcome == 'success' | ||||
|         timeout-minutes: 7 | ||||
|         run: | | ||||
|           source scripts/toolchain/fbtenv.sh | ||||
|           python3 scripts/testing/units.py ${{steps.device.outputs.flipper}} | ||||
| 
 | ||||
|       - name: 'Check GDB output' | ||||
|         if: failure() | ||||
|         run: | | ||||
|           ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1 | ||||
							
								
								
									
										75
									
								
								.github/workflows/updater_test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								.github/workflows/updater_test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | ||||
| name: 'Updater test' | ||||
| 
 | ||||
| on: | ||||
|   pull_request: | ||||
| 
 | ||||
| env: | ||||
|   TARGETS: f7 | ||||
|   DEFAULT_TARGET: f7 | ||||
|   FBT_TOOLCHAIN_PATH: /opt | ||||
|   FBT_GIT_SUBMODULE_SHALLOW: 1 | ||||
| 
 | ||||
| jobs: | ||||
|   test_updater_on_bench: | ||||
|     runs-on: [self-hosted, FlipperZeroUpdaterTest] | ||||
|     steps: | ||||
|       - name: 'Wipe workspace' | ||||
|         run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; | ||||
| 
 | ||||
|       - name: Checkout code | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 1 | ||||
|           submodules: false | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Get flipper from device manager (mock)' | ||||
|         id: device | ||||
|         run: | | ||||
|           echo "flipper=Rekigyn" >> $GITHUB_OUTPUT | ||||
|           echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT | ||||
| 
 | ||||
|       - name: 'Flashing target firmware' | ||||
|         id: first_full_flash | ||||
|         timeout-minutes: 10 | ||||
|         run: | | ||||
|           source scripts/toolchain/fbtenv.sh | ||||
|           ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1 | ||||
|           python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} | ||||
| 
 | ||||
|       - name: 'Validating updater' | ||||
|         id: second_full_flash | ||||
|         timeout-minutes: 10 | ||||
|         if: success() | ||||
|         run: | | ||||
|           source scripts/toolchain/fbtenv.sh | ||||
|           ./fbt flash_usb PORT=${{steps.device.outputs.flipper}} FORCE=1 | ||||
|           python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} | ||||
| 
 | ||||
|       - name: 'Get last release tag' | ||||
|         id: release_tag | ||||
|         if: failure() | ||||
|         run: | | ||||
|           echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT | ||||
| 
 | ||||
|       - name: 'Wipe workspace' | ||||
|         run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; | ||||
| 
 | ||||
|       - name: 'Checkout latest release' | ||||
|         uses: actions/checkout@v4 | ||||
|         if: failure() | ||||
|         with: | ||||
|           fetch-depth: 1 | ||||
|           ref: ${{ steps.release_tag.outputs.tag }} | ||||
| 
 | ||||
|       - name: 'Flash last release' | ||||
|         if: failure() | ||||
|         run: | | ||||
|           ./fbt flash SWD_TRANSPORT_SERIAL=${{steps.device.outputs.stlink}} FORCE=1 | ||||
| 
 | ||||
|       - name: 'Wait for flipper and format ext' | ||||
|         if: failure() | ||||
|         run: | | ||||
|           source scripts/toolchain/fbtenv.sh | ||||
|           python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}} | ||||
|           python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext | ||||
| @ -369,7 +369,7 @@ vscode_dist = distenv.Install( | ||||
| ) | ||||
| distenv.Precious(vscode_dist) | ||||
| distenv.NoClean(vscode_dist) | ||||
| distenv.Alias("vscode_dist", vscode_dist) | ||||
| distenv.Alias("vscode_dist", (vscode_dist, firmware_env["FW_CDB"])) | ||||
| 
 | ||||
| # Configure shell with build tools | ||||
| distenv.PhonyTarget( | ||||
|  | ||||
							
								
								
									
										12
									
								
								applications/debug/expansion_test/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								applications/debug/expansion_test/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| App( | ||||
|     appid="expansion_test", | ||||
|     name="Expansion Module Test", | ||||
|     apptype=FlipperAppType.DEBUG, | ||||
|     entry_point="expansion_test_app", | ||||
|     requires=["expansion_start"], | ||||
|     fap_libs=["assets"], | ||||
|     stack_size=1 * 1024, | ||||
|     order=20, | ||||
|     fap_category="Debug", | ||||
|     fap_file_assets="assets", | ||||
| ) | ||||
							
								
								
									
										9
									
								
								applications/debug/expansion_test/assets/test.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								applications/debug/expansion_test/assets/test.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| "Did you ever hear the tragedy of Darth Plagueis the Wise?" | ||||
| "No." | ||||
| "I thought not. It's not a story the Jedi would tell you. It's a Sith legend. Darth Plagueis... was a Dark Lord of the Sith so powerful and so wise, he could use the Force to influence the midi-chlorians... to create... life. He had such a knowledge of the dark side, he could even keep the ones he cared about... from dying." | ||||
| "He could actually... save people from death?" | ||||
| "The dark side of the Force is a pathway to many abilities... some consider to be unnatural." | ||||
| "Wh– What happened to him?" | ||||
| "He became so powerful, the only thing he was afraid of was... losing his power. Which eventually, of course, he did. Unfortunately, he taught his apprentice everything he knew. Then his apprentice killed him in his sleep. It's ironic. He could save others from death, but not himself." | ||||
| "Is it possible to learn this power?" | ||||
| "Not from a Jedi." | ||||
							
								
								
									
										454
									
								
								applications/debug/expansion_test/expansion_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										454
									
								
								applications/debug/expansion_test/expansion_test.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,454 @@ | ||||
| /**
 | ||||
|  * @file expansion_test.c | ||||
|  * @brief Expansion module support testing application. | ||||
|  * | ||||
|  * Before running, connect pins using the following scheme: | ||||
|  * 13 -> 16 (USART TX to LPUART RX) | ||||
|  * 14 -> 15 (USART RX to LPUART TX) | ||||
|  * | ||||
|  * What this application does: | ||||
|  * | ||||
|  * - Enables module support and emulates the module on a single device | ||||
|  *   (hence the above connection), | ||||
|  * - Connects to the expansion module service, sets baud rate, | ||||
|  * - Starts the RPC session, | ||||
|  * - Creates a directory at `/ext/ExpansionTest` and writes a file | ||||
|  *   named `test.txt` under it, | ||||
|  * - Plays an audiovisual alert (sound and blinking display), | ||||
|  * - Waits 10 cycles of idle loop, | ||||
|  * - Stops the RPC session, | ||||
|  * - Waits another 10 cycles of idle loop, | ||||
|  * - Exits (plays a sound if any of the above steps failed). | ||||
|  */ | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #include <furi_hal_resources.h> | ||||
| 
 | ||||
| #include <furi_hal_serial.h> | ||||
| #include <furi_hal_serial_control.h> | ||||
| 
 | ||||
| #include <pb.h> | ||||
| #include <pb_decode.h> | ||||
| #include <pb_encode.h> | ||||
| 
 | ||||
| #include <flipper.pb.h> | ||||
| 
 | ||||
| #include <storage/storage.h> | ||||
| #include <expansion/expansion.h> | ||||
| #include <notification/notification_messages.h> | ||||
| #include <expansion/expansion_protocol.h> | ||||
| 
 | ||||
| #define TAG "ExpansionTest" | ||||
| 
 | ||||
| #define TEST_DIR_PATH EXT_PATH(TAG) | ||||
| #define TEST_FILE_NAME "test.txt" | ||||
| #define TEST_FILE_PATH EXT_PATH(TAG "/" TEST_FILE_NAME) | ||||
| 
 | ||||
| #define HOST_SERIAL_ID (FuriHalSerialIdLpuart) | ||||
| #define MODULE_SERIAL_ID (FuriHalSerialIdUsart) | ||||
| 
 | ||||
| #define RECEIVE_BUFFER_SIZE (sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)) | ||||
| 
 | ||||
| typedef enum { | ||||
|     ExpansionTestAppFlagData = 1U << 0, | ||||
|     ExpansionTestAppFlagExit = 1U << 1, | ||||
| } ExpansionTestAppFlag; | ||||
| 
 | ||||
| #define EXPANSION_TEST_APP_ALL_FLAGS (ExpansionTestAppFlagData | ExpansionTestAppFlagExit) | ||||
| 
 | ||||
| typedef struct { | ||||
|     FuriThreadId thread_id; | ||||
|     Expansion* expansion; | ||||
|     FuriHalSerialHandle* handle; | ||||
|     FuriStreamBuffer* buf; | ||||
|     ExpansionFrame frame; | ||||
|     PB_Main msg; | ||||
|     Storage* storage; | ||||
| } ExpansionTestApp; | ||||
| 
 | ||||
| static void expansion_test_app_serial_rx_callback( | ||||
|     FuriHalSerialHandle* handle, | ||||
|     FuriHalSerialRxEvent event, | ||||
|     void* context) { | ||||
|     furi_assert(handle); | ||||
|     furi_assert(context); | ||||
|     ExpansionTestApp* app = context; | ||||
| 
 | ||||
|     if(event == FuriHalSerialRxEventData) { | ||||
|         const uint8_t data = furi_hal_serial_async_rx(handle); | ||||
|         furi_stream_buffer_send(app->buf, &data, sizeof(data), 0); | ||||
|         furi_thread_flags_set(app->thread_id, ExpansionTestAppFlagData); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static ExpansionTestApp* expansion_test_app_alloc() { | ||||
|     ExpansionTestApp* instance = malloc(sizeof(ExpansionTestApp)); | ||||
|     instance->buf = furi_stream_buffer_alloc(RECEIVE_BUFFER_SIZE, 1); | ||||
|     return instance; | ||||
| } | ||||
| 
 | ||||
| static void expansion_test_app_free(ExpansionTestApp* instance) { | ||||
|     furi_stream_buffer_free(instance->buf); | ||||
|     free(instance); | ||||
| } | ||||
| 
 | ||||
| static void expansion_test_app_start(ExpansionTestApp* instance) { | ||||
|     instance->thread_id = furi_thread_get_current_id(); | ||||
|     instance->expansion = furi_record_open(RECORD_EXPANSION); | ||||
|     instance->handle = furi_hal_serial_control_acquire(MODULE_SERIAL_ID); | ||||
|     // Configure the serial port
 | ||||
|     furi_hal_serial_init(instance->handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE); | ||||
|     // Start waiting for the initial pulse
 | ||||
|     expansion_enable(instance->expansion, HOST_SERIAL_ID); | ||||
| 
 | ||||
|     furi_hal_serial_async_rx_start( | ||||
|         instance->handle, expansion_test_app_serial_rx_callback, instance, false); | ||||
| } | ||||
| 
 | ||||
| static void expansion_test_app_stop(ExpansionTestApp* instance) { | ||||
|     // Give back the module handle
 | ||||
|     furi_hal_serial_control_release(instance->handle); | ||||
|     // Turn expansion module support off
 | ||||
|     expansion_disable(instance->expansion); | ||||
|     furi_record_close(RECORD_EXPANSION); | ||||
| } | ||||
| 
 | ||||
| static inline bool expansion_test_app_is_success_response(const ExpansionFrame* response) { | ||||
|     return response->header.type == ExpansionFrameTypeStatus && | ||||
|            response->content.status.error == ExpansionFrameErrorNone; | ||||
| } | ||||
| 
 | ||||
| static inline bool expansion_test_app_is_success_rpc_message(const PB_Main* message) { | ||||
|     return (message->command_status == PB_CommandStatus_OK || | ||||
|             message->command_status == PB_CommandStatus_ERROR_STORAGE_EXIST) && | ||||
|            (message->which_content == PB_Main_empty_tag); | ||||
| } | ||||
| 
 | ||||
| static size_t expansion_test_app_receive_callback(uint8_t* data, size_t data_size, void* context) { | ||||
|     ExpansionTestApp* instance = context; | ||||
| 
 | ||||
|     size_t received_size = 0; | ||||
| 
 | ||||
|     while(true) { | ||||
|         received_size += furi_stream_buffer_receive( | ||||
|             instance->buf, data + received_size, data_size - received_size, 0); | ||||
|         if(received_size == data_size) break; | ||||
| 
 | ||||
|         const uint32_t flags = furi_thread_flags_wait( | ||||
|             EXPANSION_TEST_APP_ALL_FLAGS, FuriFlagWaitAny, EXPANSION_PROTOCOL_TIMEOUT_MS); | ||||
| 
 | ||||
|         // Exit on any error
 | ||||
|         if(flags & FuriFlagError) break; | ||||
|     } | ||||
| 
 | ||||
|     return received_size; | ||||
| } | ||||
| 
 | ||||
| static size_t | ||||
|     expansion_test_app_send_callback(const uint8_t* data, size_t data_size, void* context) { | ||||
|     ExpansionTestApp* instance = context; | ||||
| 
 | ||||
|     furi_hal_serial_tx(instance->handle, data, data_size); | ||||
|     furi_hal_serial_tx_wait_complete(instance->handle); | ||||
| 
 | ||||
|     return data_size; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_receive_frame(ExpansionTestApp* instance, ExpansionFrame* frame) { | ||||
|     return expansion_protocol_decode(frame, expansion_test_app_receive_callback, instance) == | ||||
|            ExpansionProtocolStatusOk; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
|     expansion_test_app_send_status_response(ExpansionTestApp* instance, ExpansionFrameError error) { | ||||
|     ExpansionFrame frame = { | ||||
|         .header.type = ExpansionFrameTypeStatus, | ||||
|         .content.status.error = error, | ||||
|     }; | ||||
|     return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) == | ||||
|            ExpansionProtocolStatusOk; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_send_heartbeat(ExpansionTestApp* instance) { | ||||
|     ExpansionFrame frame = { | ||||
|         .header.type = ExpansionFrameTypeHeartbeat, | ||||
|         .content.heartbeat = {}, | ||||
|     }; | ||||
|     return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) == | ||||
|            ExpansionProtocolStatusOk; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
|     expansion_test_app_send_baud_rate_request(ExpansionTestApp* instance, uint32_t baud_rate) { | ||||
|     ExpansionFrame frame = { | ||||
|         .header.type = ExpansionFrameTypeBaudRate, | ||||
|         .content.baud_rate.baud = baud_rate, | ||||
|     }; | ||||
|     return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) == | ||||
|            ExpansionProtocolStatusOk; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_send_control_request( | ||||
|     ExpansionTestApp* instance, | ||||
|     ExpansionFrameControlCommand command) { | ||||
|     ExpansionFrame frame = { | ||||
|         .header.type = ExpansionFrameTypeControl, | ||||
|         .content.control.command = command, | ||||
|     }; | ||||
|     return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) == | ||||
|            ExpansionProtocolStatusOk; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_send_data_request( | ||||
|     ExpansionTestApp* instance, | ||||
|     const uint8_t* data, | ||||
|     size_t data_size) { | ||||
|     furi_assert(data_size <= EXPANSION_PROTOCOL_MAX_DATA_SIZE); | ||||
| 
 | ||||
|     ExpansionFrame frame = { | ||||
|         .header.type = ExpansionFrameTypeData, | ||||
|         .content.data.size = data_size, | ||||
|     }; | ||||
| 
 | ||||
|     memcpy(frame.content.data.bytes, data, data_size); | ||||
|     return expansion_protocol_encode(&frame, expansion_test_app_send_callback, instance) == | ||||
|            ExpansionProtocolStatusOk; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_rpc_encode_callback( | ||||
|     pb_ostream_t* stream, | ||||
|     const pb_byte_t* data, | ||||
|     size_t data_size) { | ||||
|     ExpansionTestApp* instance = stream->state; | ||||
| 
 | ||||
|     size_t size_sent = 0; | ||||
| 
 | ||||
|     while(size_sent < data_size) { | ||||
|         const size_t current_size = MIN(data_size - size_sent, EXPANSION_PROTOCOL_MAX_DATA_SIZE); | ||||
|         if(!expansion_test_app_send_data_request(instance, data + size_sent, current_size)) break; | ||||
|         if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; | ||||
|         if(!expansion_test_app_is_success_response(&instance->frame)) break; | ||||
|         size_sent += current_size; | ||||
|     } | ||||
| 
 | ||||
|     return size_sent == data_size; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_send_rpc_request(ExpansionTestApp* instance, PB_Main* message) { | ||||
|     pb_ostream_t stream = { | ||||
|         .callback = expansion_test_app_rpc_encode_callback, | ||||
|         .state = instance, | ||||
|         .max_size = SIZE_MAX, | ||||
|         .bytes_written = 0, | ||||
|         .errmsg = NULL, | ||||
|     }; | ||||
| 
 | ||||
|     const bool success = pb_encode_ex(&stream, &PB_Main_msg, message, PB_ENCODE_DELIMITED); | ||||
|     pb_release(&PB_Main_msg, message); | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_receive_rpc_request(ExpansionTestApp* instance, PB_Main* message) { | ||||
|     bool success = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; | ||||
|         if(!expansion_test_app_send_status_response(instance, ExpansionFrameErrorNone)) break; | ||||
|         if(instance->frame.header.type != ExpansionFrameTypeData) break; | ||||
|         pb_istream_t stream = pb_istream_from_buffer( | ||||
|             instance->frame.content.data.bytes, instance->frame.content.data.size); | ||||
|         if(!pb_decode_ex(&stream, &PB_Main_msg, message, PB_DECODE_DELIMITED)) break; | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_send_presence(ExpansionTestApp* instance) { | ||||
|     // Send pulses to emulate module insertion
 | ||||
|     const uint8_t init = 0xAA; | ||||
|     furi_hal_serial_tx(instance->handle, &init, sizeof(init)); | ||||
|     furi_hal_serial_tx_wait_complete(instance->handle); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_wait_ready(ExpansionTestApp* instance) { | ||||
|     bool success = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; | ||||
|         if(instance->frame.header.type != ExpansionFrameTypeHeartbeat) break; | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_handshake(ExpansionTestApp* instance) { | ||||
|     bool success = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!expansion_test_app_send_baud_rate_request(instance, 230400)) break; | ||||
|         if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; | ||||
|         if(!expansion_test_app_is_success_response(&instance->frame)) break; | ||||
|         furi_hal_serial_set_br(instance->handle, 230400); | ||||
|         furi_delay_ms(EXPANSION_PROTOCOL_BAUD_CHANGE_DT_MS); | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_start_rpc(ExpansionTestApp* instance) { | ||||
|     bool success = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!expansion_test_app_send_control_request(instance, ExpansionFrameControlCommandStartRpc)) | ||||
|             break; | ||||
|         if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; | ||||
|         if(!expansion_test_app_is_success_response(&instance->frame)) break; | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_rpc_mkdir(ExpansionTestApp* instance) { | ||||
|     bool success = false; | ||||
| 
 | ||||
|     instance->msg.command_id++; | ||||
|     instance->msg.command_status = PB_CommandStatus_OK; | ||||
|     instance->msg.which_content = PB_Main_storage_mkdir_request_tag; | ||||
|     instance->msg.has_next = false; | ||||
|     instance->msg.content.storage_mkdir_request.path = TEST_DIR_PATH; | ||||
| 
 | ||||
|     do { | ||||
|         if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break; | ||||
|         if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break; | ||||
|         if(!expansion_test_app_is_success_rpc_message(&instance->msg)) break; | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_rpc_write(ExpansionTestApp* instance) { | ||||
|     bool success = false; | ||||
| 
 | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
|     File* file = storage_file_alloc(storage); | ||||
| 
 | ||||
|     do { | ||||
|         if(!storage_file_open(file, APP_ASSETS_PATH(TEST_FILE_NAME), FSAM_READ, FSOM_OPEN_EXISTING)) | ||||
|             break; | ||||
| 
 | ||||
|         const uint64_t file_size = storage_file_size(file); | ||||
| 
 | ||||
|         instance->msg.command_id++; | ||||
|         instance->msg.command_status = PB_CommandStatus_OK; | ||||
|         instance->msg.which_content = PB_Main_storage_write_request_tag; | ||||
|         instance->msg.has_next = false; | ||||
|         instance->msg.content.storage_write_request.path = TEST_FILE_PATH; | ||||
|         instance->msg.content.storage_write_request.has_file = true; | ||||
|         instance->msg.content.storage_write_request.file.data = | ||||
|             malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(file_size)); | ||||
|         instance->msg.content.storage_write_request.file.data->size = file_size; | ||||
| 
 | ||||
|         const size_t bytes_read = storage_file_read( | ||||
|             file, instance->msg.content.storage_write_request.file.data->bytes, file_size); | ||||
| 
 | ||||
|         if(bytes_read != file_size) { | ||||
|             pb_release(&PB_Main_msg, &instance->msg); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break; | ||||
|         if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break; | ||||
|         if(!expansion_test_app_is_success_rpc_message(&instance->msg)) break; | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     storage_file_free(file); | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
| 
 | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_rpc_alert(ExpansionTestApp* instance) { | ||||
|     bool success = false; | ||||
| 
 | ||||
|     instance->msg.command_id++; | ||||
|     instance->msg.command_status = PB_CommandStatus_OK; | ||||
|     instance->msg.which_content = PB_Main_system_play_audiovisual_alert_request_tag; | ||||
|     instance->msg.has_next = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break; | ||||
|         if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break; | ||||
|         if(instance->msg.which_content != PB_Main_empty_tag) break; | ||||
|         if(instance->msg.command_status != PB_CommandStatus_OK) break; | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_idle(ExpansionTestApp* instance, uint32_t num_cycles) { | ||||
|     uint32_t num_cycles_done; | ||||
|     for(num_cycles_done = 0; num_cycles_done < num_cycles; ++num_cycles_done) { | ||||
|         if(!expansion_test_app_send_heartbeat(instance)) break; | ||||
|         if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; | ||||
|         if(instance->frame.header.type != ExpansionFrameTypeHeartbeat) break; | ||||
|         furi_delay_ms(EXPANSION_PROTOCOL_TIMEOUT_MS - 50); | ||||
|     } | ||||
| 
 | ||||
|     return num_cycles_done == num_cycles; | ||||
| } | ||||
| 
 | ||||
| static bool expansion_test_app_stop_rpc(ExpansionTestApp* instance) { | ||||
|     bool success = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!expansion_test_app_send_control_request(instance, ExpansionFrameControlCommandStopRpc)) | ||||
|             break; | ||||
|         if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; | ||||
|         if(!expansion_test_app_is_success_response(&instance->frame)) break; | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return success; | ||||
| } | ||||
| 
 | ||||
| int32_t expansion_test_app(void* p) { | ||||
|     UNUSED(p); | ||||
| 
 | ||||
|     ExpansionTestApp* instance = expansion_test_app_alloc(); | ||||
|     expansion_test_app_start(instance); | ||||
| 
 | ||||
|     bool success = false; | ||||
| 
 | ||||
|     do { | ||||
|         if(!expansion_test_app_send_presence(instance)) break; | ||||
|         if(!expansion_test_app_wait_ready(instance)) break; | ||||
|         if(!expansion_test_app_handshake(instance)) break; | ||||
|         if(!expansion_test_app_start_rpc(instance)) break; | ||||
|         if(!expansion_test_app_rpc_mkdir(instance)) break; | ||||
|         if(!expansion_test_app_rpc_write(instance)) break; | ||||
|         if(!expansion_test_app_rpc_alert(instance)) break; | ||||
|         if(!expansion_test_app_idle(instance, 10)) break; | ||||
|         if(!expansion_test_app_stop_rpc(instance)) break; | ||||
|         if(!expansion_test_app_idle(instance, 10)) break; | ||||
|         success = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     expansion_test_app_stop(instance); | ||||
|     expansion_test_app_free(instance); | ||||
| 
 | ||||
|     if(!success) { | ||||
|         NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); | ||||
|         notification_message(notification, &sequence_error); | ||||
|         furi_record_close(RECORD_NOTIFICATION); | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| @ -1,13 +1,14 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| #include <gui/gui.h> | ||||
| #include <notification/notification.h> | ||||
| #include <notification/notification_messages.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi_hal_uart.h> | ||||
| #include <furi_hal_console.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| 
 | ||||
| #include <notification/notification.h> | ||||
| #include <notification/notification_messages.h> | ||||
| 
 | ||||
| #define LINES_ON_SCREEN 6 | ||||
| #define COLUMNS_ON_SCREEN 21 | ||||
| #define TAG "UartEcho" | ||||
| @ -22,6 +23,7 @@ typedef struct { | ||||
|     View* view; | ||||
|     FuriThread* worker_thread; | ||||
|     FuriStreamBuffer* rx_stream; | ||||
|     FuriHalSerialHandle* serial_handle; | ||||
| } UartEchoApp; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -39,10 +41,16 @@ struct UartDumpModel { | ||||
| typedef enum { | ||||
|     WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event
 | ||||
|     WorkerEventStop = (1 << 1), | ||||
|     WorkerEventRx = (1 << 2), | ||||
|     WorkerEventRxData = (1 << 2), | ||||
|     WorkerEventRxIdle = (1 << 3), | ||||
|     WorkerEventRxOverrunError = (1 << 4), | ||||
|     WorkerEventRxFramingError = (1 << 5), | ||||
|     WorkerEventRxNoiseError = (1 << 6), | ||||
| } WorkerEventFlags; | ||||
| 
 | ||||
| #define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventRx) | ||||
| #define WORKER_EVENTS_MASK                                                                 \ | ||||
|     (WorkerEventStop | WorkerEventRxData | WorkerEventRxIdle | WorkerEventRxOverrunError | \ | ||||
|      WorkerEventRxFramingError | WorkerEventRxNoiseError) | ||||
| 
 | ||||
| const NotificationSequence sequence_notification = { | ||||
|     &message_display_backlight_on, | ||||
| @ -91,14 +99,39 @@ static uint32_t uart_echo_exit(void* context) { | ||||
|     return VIEW_NONE; | ||||
| } | ||||
| 
 | ||||
| static void uart_echo_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { | ||||
| static void | ||||
|     uart_echo_on_irq_cb(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
|     UNUSED(handle); | ||||
|     UartEchoApp* app = context; | ||||
|     volatile FuriHalSerialRxEvent event_copy = event; | ||||
|     UNUSED(event_copy); | ||||
| 
 | ||||
|     if(ev == UartIrqEventRXNE) { | ||||
|     WorkerEventFlags flag = 0; | ||||
| 
 | ||||
|     if(event & FuriHalSerialRxEventData) { | ||||
|         uint8_t data = furi_hal_serial_async_rx(handle); | ||||
|         furi_stream_buffer_send(app->rx_stream, &data, 1, 0); | ||||
|         furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventRx); | ||||
|         flag |= WorkerEventRxData; | ||||
|     } | ||||
| 
 | ||||
|     if(event & FuriHalSerialRxEventIdle) { | ||||
|         //idle line detected, packet transmission may have ended
 | ||||
|         flag |= WorkerEventRxIdle; | ||||
|     } | ||||
| 
 | ||||
|     //error detected
 | ||||
|     if(event & FuriHalSerialRxEventFrameError) { | ||||
|         flag |= WorkerEventRxFramingError; | ||||
|     } | ||||
|     if(event & FuriHalSerialRxEventNoiseError) { | ||||
|         flag |= WorkerEventRxNoiseError; | ||||
|     } | ||||
|     if(event & FuriHalSerialRxEventOverrunError) { | ||||
|         flag |= WorkerEventRxOverrunError; | ||||
|     } | ||||
| 
 | ||||
|     furi_thread_flags_set(furi_thread_get_id(app->worker_thread), flag); | ||||
| } | ||||
| 
 | ||||
| static void uart_echo_push_to_list(UartDumpModel* model, const char data) { | ||||
| @ -153,13 +186,13 @@ static int32_t uart_echo_worker(void* context) { | ||||
|         furi_check((events & FuriFlagError) == 0); | ||||
| 
 | ||||
|         if(events & WorkerEventStop) break; | ||||
|         if(events & WorkerEventRx) { | ||||
|         if(events & WorkerEventRxData) { | ||||
|             size_t length = 0; | ||||
|             do { | ||||
|                 uint8_t data[64]; | ||||
|                 length = furi_stream_buffer_receive(app->rx_stream, data, 64, 0); | ||||
|                 if(length > 0) { | ||||
|                     furi_hal_uart_tx(FuriHalUartIdUSART1, data, length); | ||||
|                     furi_hal_serial_tx(app->serial_handle, data, length); | ||||
|                     with_view_model( | ||||
|                         app->view, | ||||
|                         UartDumpModel * model, | ||||
| @ -176,6 +209,23 @@ static int32_t uart_echo_worker(void* context) { | ||||
|             with_view_model( | ||||
|                 app->view, UartDumpModel * model, { UNUSED(model); }, true); | ||||
|         } | ||||
| 
 | ||||
|         if(events & WorkerEventRxIdle) { | ||||
|             furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect IDLE\r\n", 15); | ||||
|         } | ||||
| 
 | ||||
|         if(events & | ||||
|            (WorkerEventRxOverrunError | WorkerEventRxFramingError | WorkerEventRxNoiseError)) { | ||||
|             if(events & WorkerEventRxOverrunError) { | ||||
|                 furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect ORE\r\n", 14); | ||||
|             } | ||||
|             if(events & WorkerEventRxFramingError) { | ||||
|                 furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect FE\r\n", 13); | ||||
|             } | ||||
|             if(events & WorkerEventRxNoiseError) { | ||||
|                 furi_hal_serial_tx(app->serial_handle, (uint8_t*)"\r\nDetect NE\r\n", 13); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| @ -221,9 +271,11 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) { | ||||
|     furi_thread_start(app->worker_thread); | ||||
| 
 | ||||
|     // Enable uart listener
 | ||||
|     furi_hal_console_disable(); | ||||
|     furi_hal_uart_set_br(FuriHalUartIdUSART1, baudrate); | ||||
|     furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app); | ||||
|     app->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart); | ||||
|     furi_check(app->serial_handle); | ||||
|     furi_hal_serial_init(app->serial_handle, baudrate); | ||||
| 
 | ||||
|     furi_hal_serial_async_rx_start(app->serial_handle, uart_echo_on_irq_cb, app, true); | ||||
| 
 | ||||
|     return app; | ||||
| } | ||||
| @ -231,12 +283,13 @@ static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) { | ||||
| static void uart_echo_app_free(UartEchoApp* app) { | ||||
|     furi_assert(app); | ||||
| 
 | ||||
|     furi_hal_console_enable(); // this will also clear IRQ callback so thread is no longer referenced
 | ||||
| 
 | ||||
|     furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop); | ||||
|     furi_thread_join(app->worker_thread); | ||||
|     furi_thread_free(app->worker_thread); | ||||
| 
 | ||||
|     furi_hal_serial_deinit(app->serial_handle); | ||||
|     furi_hal_serial_control_release(app->serial_handle); | ||||
| 
 | ||||
|     // Free views
 | ||||
|     view_dispatcher_remove_view(app->view_dispatcher, 0); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										157
									
								
								applications/debug/unit_tests/expansion/expansion_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								applications/debug/unit_tests/expansion/expansion_test.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | ||||
| #include "../minunit.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <expansion/expansion_protocol.h> | ||||
| 
 | ||||
| MU_TEST(test_expansion_encoded_size) { | ||||
|     ExpansionFrame frame = {}; | ||||
| 
 | ||||
|     frame.header.type = ExpansionFrameTypeHeartbeat; | ||||
|     mu_assert_int_eq(1, expansion_frame_get_encoded_size(&frame)); | ||||
| 
 | ||||
|     frame.header.type = ExpansionFrameTypeStatus; | ||||
|     mu_assert_int_eq(2, expansion_frame_get_encoded_size(&frame)); | ||||
| 
 | ||||
|     frame.header.type = ExpansionFrameTypeBaudRate; | ||||
|     mu_assert_int_eq(5, expansion_frame_get_encoded_size(&frame)); | ||||
| 
 | ||||
|     frame.header.type = ExpansionFrameTypeControl; | ||||
|     mu_assert_int_eq(2, expansion_frame_get_encoded_size(&frame)); | ||||
| 
 | ||||
|     frame.header.type = ExpansionFrameTypeData; | ||||
|     for(size_t i = 0; i <= EXPANSION_PROTOCOL_MAX_DATA_SIZE; ++i) { | ||||
|         frame.content.data.size = i; | ||||
|         mu_assert_int_eq(i + 2, expansion_frame_get_encoded_size(&frame)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| MU_TEST(test_expansion_remaining_size) { | ||||
|     ExpansionFrame frame = {}; | ||||
| 
 | ||||
|     mu_assert_int_eq(1, expansion_frame_get_remaining_size(&frame, 0)); | ||||
| 
 | ||||
|     frame.header.type = ExpansionFrameTypeHeartbeat; | ||||
|     mu_assert_int_eq(1, expansion_frame_get_remaining_size(&frame, 0)); | ||||
|     mu_assert_int_eq(0, expansion_frame_get_remaining_size(&frame, 1)); | ||||
|     mu_assert_int_eq(0, expansion_frame_get_remaining_size(&frame, 100)); | ||||
| 
 | ||||
|     frame.header.type = ExpansionFrameTypeStatus; | ||||
|     mu_assert_int_eq(1, expansion_frame_get_remaining_size(&frame, 0)); | ||||
|     mu_assert_int_eq(1, expansion_frame_get_remaining_size(&frame, 1)); | ||||
|     mu_assert_int_eq(0, expansion_frame_get_remaining_size(&frame, 2)); | ||||
|     mu_assert_int_eq(0, expansion_frame_get_remaining_size(&frame, 100)); | ||||
| 
 | ||||
|     frame.header.type = ExpansionFrameTypeBaudRate; | ||||
|     mu_assert_int_eq(1, expansion_frame_get_remaining_size(&frame, 0)); | ||||
|     mu_assert_int_eq(4, expansion_frame_get_remaining_size(&frame, 1)); | ||||
|     mu_assert_int_eq(0, expansion_frame_get_remaining_size(&frame, 5)); | ||||
|     mu_assert_int_eq(0, expansion_frame_get_remaining_size(&frame, 100)); | ||||
| 
 | ||||
|     frame.header.type = ExpansionFrameTypeControl; | ||||
|     mu_assert_int_eq(1, expansion_frame_get_remaining_size(&frame, 0)); | ||||
|     mu_assert_int_eq(1, expansion_frame_get_remaining_size(&frame, 1)); | ||||
|     mu_assert_int_eq(0, expansion_frame_get_remaining_size(&frame, 2)); | ||||
|     mu_assert_int_eq(0, expansion_frame_get_remaining_size(&frame, 100)); | ||||
| 
 | ||||
|     frame.header.type = ExpansionFrameTypeData; | ||||
|     frame.content.data.size = EXPANSION_PROTOCOL_MAX_DATA_SIZE; | ||||
|     mu_assert_int_eq(1, expansion_frame_get_remaining_size(&frame, 0)); | ||||
|     mu_assert_int_eq(1, expansion_frame_get_remaining_size(&frame, 1)); | ||||
|     mu_assert_int_eq( | ||||
|         EXPANSION_PROTOCOL_MAX_DATA_SIZE, expansion_frame_get_remaining_size(&frame, 2)); | ||||
|     for(size_t i = 0; i <= EXPANSION_PROTOCOL_MAX_DATA_SIZE; ++i) { | ||||
|         mu_assert_int_eq( | ||||
|             EXPANSION_PROTOCOL_MAX_DATA_SIZE - i, | ||||
|             expansion_frame_get_remaining_size(&frame, i + 2)); | ||||
|     } | ||||
|     mu_assert_int_eq(0, expansion_frame_get_remaining_size(&frame, 100)); | ||||
| } | ||||
| 
 | ||||
| typedef struct { | ||||
|     void* data_out; | ||||
|     size_t size_available; | ||||
|     size_t size_sent; | ||||
| } TestExpansionSendStream; | ||||
| 
 | ||||
| static size_t test_expansion_send_callback(const uint8_t* data, size_t data_size, void* context) { | ||||
|     TestExpansionSendStream* stream = context; | ||||
|     const size_t size_sent = MIN(data_size, stream->size_available); | ||||
| 
 | ||||
|     memcpy(stream->data_out + stream->size_sent, data, size_sent); | ||||
| 
 | ||||
|     stream->size_available -= size_sent; | ||||
|     stream->size_sent += size_sent; | ||||
| 
 | ||||
|     return size_sent; | ||||
| } | ||||
| 
 | ||||
| typedef struct { | ||||
|     const void* data_in; | ||||
|     size_t size_available; | ||||
|     size_t size_received; | ||||
| } TestExpansionReceiveStream; | ||||
| 
 | ||||
| static size_t test_expansion_receive_callback(uint8_t* data, size_t data_size, void* context) { | ||||
|     TestExpansionReceiveStream* stream = context; | ||||
|     const size_t size_received = MIN(data_size, stream->size_available); | ||||
| 
 | ||||
|     memcpy(data, stream->data_in + stream->size_received, size_received); | ||||
| 
 | ||||
|     stream->size_available -= size_received; | ||||
|     stream->size_received += size_received; | ||||
| 
 | ||||
|     return size_received; | ||||
| } | ||||
| 
 | ||||
| MU_TEST(test_expansion_encode_decode_frame) { | ||||
|     const ExpansionFrame frame_in = { | ||||
|         .header.type = ExpansionFrameTypeData, | ||||
|         .content.data.size = 8, | ||||
|         .content.data.bytes = {0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xca, 0xfe}, | ||||
|     }; | ||||
| 
 | ||||
|     uint8_t encoded_data[sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)]; | ||||
|     memset(encoded_data, 0, sizeof(encoded_data)); | ||||
| 
 | ||||
|     TestExpansionSendStream send_stream = { | ||||
|         .data_out = &encoded_data, | ||||
|         .size_available = sizeof(encoded_data), | ||||
|         .size_sent = 0, | ||||
|     }; | ||||
| 
 | ||||
|     const size_t encoded_size = expansion_frame_get_encoded_size(&frame_in); | ||||
| 
 | ||||
|     mu_assert_int_eq( | ||||
|         expansion_protocol_encode(&frame_in, test_expansion_send_callback, &send_stream), | ||||
|         ExpansionProtocolStatusOk); | ||||
|     mu_assert_int_eq(encoded_size + sizeof(ExpansionFrameChecksum), send_stream.size_sent); | ||||
|     mu_assert_int_eq( | ||||
|         expansion_protocol_get_checksum((const uint8_t*)&frame_in, encoded_size), | ||||
|         encoded_data[encoded_size]); | ||||
|     mu_assert_mem_eq(&frame_in, &encoded_data, encoded_size); | ||||
| 
 | ||||
|     TestExpansionReceiveStream stream = { | ||||
|         .data_in = encoded_data, | ||||
|         .size_available = send_stream.size_sent, | ||||
|         .size_received = 0, | ||||
|     }; | ||||
| 
 | ||||
|     ExpansionFrame frame_out; | ||||
| 
 | ||||
|     mu_assert_int_eq( | ||||
|         expansion_protocol_decode(&frame_out, test_expansion_receive_callback, &stream), | ||||
|         ExpansionProtocolStatusOk); | ||||
|     mu_assert_int_eq(encoded_size + sizeof(ExpansionFrameChecksum), stream.size_received); | ||||
|     mu_assert_mem_eq(&frame_in, &frame_out, encoded_size); | ||||
| } | ||||
| 
 | ||||
| MU_TEST_SUITE(test_expansion_suite) { | ||||
|     MU_RUN_TEST(test_expansion_encoded_size); | ||||
|     MU_RUN_TEST(test_expansion_remaining_size); | ||||
|     MU_RUN_TEST(test_expansion_encode_decode_frame); | ||||
| } | ||||
| 
 | ||||
| int run_minunit_test_expansion() { | ||||
|     MU_RUN_SUITE(test_expansion_suite); | ||||
|     return MU_EXIT_CODE; | ||||
| } | ||||
| @ -1,8 +1,11 @@ | ||||
| #include "furi_hal_rtc.h" | ||||
| #include <stdint.h> | ||||
| #include <stdio.h> | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <lp5562_reg.h> | ||||
| #include "../minunit.h" | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| #define DATA_SIZE 4 | ||||
| #define EEPROM_ADDRESS 0b10101000 | ||||
| @ -211,6 +214,37 @@ MU_TEST(furi_hal_i2c_ext_eeprom) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| MU_TEST(furi_hal_rtc_timestamp2datetime_min) { | ||||
|     uint32_t test_value = 0; | ||||
|     FuriHalRtcDateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 0}; | ||||
| 
 | ||||
|     FuriHalRtcDateTime result = {0}; | ||||
|     furi_hal_rtc_timestamp_to_datetime(test_value, &result); | ||||
| 
 | ||||
|     mu_assert_mem_eq(&min_datetime_expected, &result, sizeof(result)); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(furi_hal_rtc_timestamp2datetime_max) { | ||||
|     uint32_t test_value = UINT32_MAX; | ||||
|     FuriHalRtcDateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 0}; | ||||
| 
 | ||||
|     FuriHalRtcDateTime result = {0}; | ||||
|     furi_hal_rtc_timestamp_to_datetime(test_value, &result); | ||||
| 
 | ||||
|     mu_assert_mem_eq(&max_datetime_expected, &result, sizeof(result)); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(furi_hal_rtc_timestamp2datetime2timestamp) { | ||||
|     uint32_t test_value = random(); | ||||
| 
 | ||||
|     FuriHalRtcDateTime datetime = {0}; | ||||
|     furi_hal_rtc_timestamp_to_datetime(test_value, &datetime); | ||||
| 
 | ||||
|     uint32_t result = furi_hal_rtc_datetime_to_timestamp(&datetime); | ||||
| 
 | ||||
|     mu_assert_int_eq(test_value, result); | ||||
| } | ||||
| 
 | ||||
| MU_TEST_SUITE(furi_hal_i2c_int_suite) { | ||||
|     MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown); | ||||
|     MU_RUN_TEST(furi_hal_i2c_int_1b); | ||||
| @ -224,8 +258,15 @@ MU_TEST_SUITE(furi_hal_i2c_ext_suite) { | ||||
|     MU_RUN_TEST(furi_hal_i2c_ext_eeprom); | ||||
| } | ||||
| 
 | ||||
| MU_TEST_SUITE(furi_hal_rtc_datetime_suite) { | ||||
|     MU_RUN_TEST(furi_hal_rtc_timestamp2datetime_min); | ||||
|     MU_RUN_TEST(furi_hal_rtc_timestamp2datetime_max); | ||||
|     MU_RUN_TEST(furi_hal_rtc_timestamp2datetime2timestamp); | ||||
| } | ||||
| 
 | ||||
| int run_minunit_test_furi_hal() { | ||||
|     MU_RUN_SUITE(furi_hal_i2c_int_suite); | ||||
|     MU_RUN_SUITE(furi_hal_i2c_ext_suite); | ||||
|     MU_RUN_SUITE(furi_hal_rtc_datetime_suite); | ||||
|     return MU_EXIT_CODE; | ||||
| } | ||||
|  | ||||
| @ -209,6 +209,25 @@ const int8_t indala26_test_timings[INDALA26_EMULATION_TIMINGS_COUNT] = { | ||||
|     -1, 1,  -1, 1,  -1, 1,  -1, 1, | ||||
| }; | ||||
| 
 | ||||
| #define FDXB_TEST_DATA \ | ||||
|     { 0x44, 0x88, 0x23, 0xF2, 0x5A, 0x6F, 0x00, 0x01, 0x00, 0x00, 0x00 } | ||||
| #define FDXB_TEST_DATA_SIZE 11 | ||||
| #define FDXB_TEST_EMULATION_TIMINGS_COUNT (206) | ||||
| 
 | ||||
| const int8_t fdxb_test_timings[FDXB_TEST_EMULATION_TIMINGS_COUNT] = { | ||||
|     32,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16, | ||||
|     -16, 16,  -32, 16,  -16, 32,  -16, 16,  -16, 16,  -16, 16,  -32, 16,  -16, 16,  -16, 32,  -32, | ||||
|     16,  -16, 16,  -16, 16,  -16, 32,  -16, 16,  -16, 16,  -16, 16,  -32, 16,  -16, 16,  -16, 32, | ||||
|     -16, 16,  -16, 16,  -16, 16,  -32, 32,  -32, 32,  -32, 32,  -32, 16,  -16, 16,  -16, 32,  -16, | ||||
|     16,  -32, 16,  -16, 32,  -16, 16,  -32, 32,  -16, 16,  -32, 16,  -16, 32,  -16, 16,  -32, 32, | ||||
|     -16, 16,  -32, 32,  -32, 32,  -32, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, | ||||
|     16,  -16, 16,  -16, 32,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16, | ||||
|     -32, 32,  -32, 32,  -32, 32,  -32, 16,  -16, 32,  -32, 32,  -16, 16,  -16, 16,  -32, 32,  -32, | ||||
|     32,  -32, 32,  -32, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16, | ||||
|     -16, 32,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -32, | ||||
|     16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, 16,  -16, | ||||
| }; | ||||
| 
 | ||||
| MU_TEST(test_lfrfid_protocol_em_read_simple) { | ||||
|     ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); | ||||
|     mu_assert_int_eq(EM_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolEM4100)); | ||||
| @ -445,6 +464,73 @@ MU_TEST(test_lfrfid_protocol_inadala26_emulate_simple) { | ||||
|     protocol_dict_free(dict); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(test_lfrfid_protocol_fdxb_emulate_simple) { | ||||
|     ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); | ||||
|     mu_assert_int_eq(FDXB_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolFDXB)); | ||||
|     mu_assert_string_eq("FDX-B", protocol_dict_get_name(dict, LFRFIDProtocolFDXB)); | ||||
|     mu_assert_string_eq("ISO", protocol_dict_get_manufacturer(dict, LFRFIDProtocolFDXB)); | ||||
| 
 | ||||
|     const uint8_t data[FDXB_TEST_DATA_SIZE] = FDXB_TEST_DATA; | ||||
| 
 | ||||
|     protocol_dict_set_data(dict, LFRFIDProtocolFDXB, data, FDXB_TEST_DATA_SIZE); | ||||
|     mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolFDXB)); | ||||
| 
 | ||||
|     for(size_t i = 0; i < FDXB_TEST_EMULATION_TIMINGS_COUNT; i++) { | ||||
|         LevelDuration level_duration = protocol_dict_encoder_yield(dict, LFRFIDProtocolFDXB); | ||||
| 
 | ||||
|         if(level_duration_get_level(level_duration)) { | ||||
|             mu_assert_int_eq(fdxb_test_timings[i], level_duration_get_duration(level_duration)); | ||||
|         } else { | ||||
|             mu_assert_int_eq(fdxb_test_timings[i], -level_duration_get_duration(level_duration)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protocol_dict_free(dict); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(test_lfrfid_protocol_fdxb_read_simple) { | ||||
|     ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); | ||||
|     mu_assert_int_eq(FDXB_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolFDXB)); | ||||
|     mu_assert_string_eq("FDX-B", protocol_dict_get_name(dict, LFRFIDProtocolFDXB)); | ||||
|     mu_assert_string_eq("ISO", protocol_dict_get_manufacturer(dict, LFRFIDProtocolFDXB)); | ||||
| 
 | ||||
|     const uint8_t data[FDXB_TEST_DATA_SIZE] = FDXB_TEST_DATA; | ||||
| 
 | ||||
|     protocol_dict_decoders_start(dict); | ||||
| 
 | ||||
|     ProtocolId protocol = PROTOCOL_NO; | ||||
|     PulseGlue* pulse_glue = pulse_glue_alloc(); | ||||
| 
 | ||||
|     for(size_t i = 0; i < FDXB_TEST_EMULATION_TIMINGS_COUNT * 10; i++) { | ||||
|         bool pulse_pop = pulse_glue_push( | ||||
|             pulse_glue, | ||||
|             fdxb_test_timings[i % FDXB_TEST_EMULATION_TIMINGS_COUNT] >= 0, | ||||
|             abs(fdxb_test_timings[i % FDXB_TEST_EMULATION_TIMINGS_COUNT]) * | ||||
|                 LF_RFID_READ_TIMING_MULTIPLIER); | ||||
| 
 | ||||
|         if(pulse_pop) { | ||||
|             uint32_t length, period; | ||||
|             pulse_glue_pop(pulse_glue, &length, &period); | ||||
| 
 | ||||
|             protocol = protocol_dict_decoders_feed(dict, true, period); | ||||
|             if(protocol != PROTOCOL_NO) break; | ||||
| 
 | ||||
|             protocol = protocol_dict_decoders_feed(dict, false, length - period); | ||||
|             if(protocol != PROTOCOL_NO) break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pulse_glue_free(pulse_glue); | ||||
| 
 | ||||
|     mu_assert_int_eq(LFRFIDProtocolFDXB, protocol); | ||||
|     uint8_t received_data[FDXB_TEST_DATA_SIZE] = {0}; | ||||
|     protocol_dict_get_data(dict, protocol, received_data, FDXB_TEST_DATA_SIZE); | ||||
| 
 | ||||
|     mu_assert_mem_eq(data, received_data, FDXB_TEST_DATA_SIZE); | ||||
| 
 | ||||
|     protocol_dict_free(dict); | ||||
| } | ||||
| 
 | ||||
| MU_TEST_SUITE(test_lfrfid_protocols_suite) { | ||||
|     MU_RUN_TEST(test_lfrfid_protocol_em_read_simple); | ||||
|     MU_RUN_TEST(test_lfrfid_protocol_em_emulate_simple); | ||||
| @ -456,6 +542,9 @@ MU_TEST_SUITE(test_lfrfid_protocols_suite) { | ||||
|     MU_RUN_TEST(test_lfrfid_protocol_ioprox_xsf_emulate_simple); | ||||
| 
 | ||||
|     MU_RUN_TEST(test_lfrfid_protocol_inadala26_emulate_simple); | ||||
| 
 | ||||
|     MU_RUN_TEST(test_lfrfid_protocol_fdxb_read_simple); | ||||
|     MU_RUN_TEST(test_lfrfid_protocol_fdxb_emulate_simple); | ||||
| } | ||||
| 
 | ||||
| int run_minunit_test_lfrfid_protocols() { | ||||
|  | ||||
| @ -231,17 +231,17 @@ typedef struct { | ||||
|     size_t pos; | ||||
| } SubGhzHalAsyncTxTest; | ||||
| 
 | ||||
| #define SUBGHZ_HAL_TEST_DURATION 1 | ||||
| #define SUBGHZ_HAL_TEST_DURATION 3 | ||||
| 
 | ||||
| static LevelDuration subghz_hal_async_tx_test_yield(void* context) { | ||||
|     SubGhzHalAsyncTxTest* test = context; | ||||
|     bool is_odd = test->pos % 2; | ||||
| 
 | ||||
|     if(test->type == SubGhzHalAsyncTxTestTypeNormal) { | ||||
|         if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|         if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|             test->pos++; | ||||
|             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||
|         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|         } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|             test->pos++; | ||||
|             return level_duration_reset(); | ||||
|         } else { | ||||
| @ -251,36 +251,36 @@ static LevelDuration subghz_hal_async_tx_test_yield(void* context) { | ||||
|         if(test->pos == 0) { | ||||
|             test->pos++; | ||||
|             return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||
|         } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|         } else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|             test->pos++; | ||||
|             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||
|         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|         } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|             test->pos++; | ||||
|             return level_duration_reset(); | ||||
|         } else { | ||||
|             furi_crash("Yield after reset"); | ||||
|         } | ||||
|     } else if(test->type == SubGhzHalAsyncTxTestTypeInvalidMid) { | ||||
|         if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { | ||||
|         if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { | ||||
|             test->pos++; | ||||
|             return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||
|         } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|         } else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|             test->pos++; | ||||
|             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||
|         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|         } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|             test->pos++; | ||||
|             return level_duration_reset(); | ||||
|         } else { | ||||
|             furi_crash("Yield after reset"); | ||||
|         } | ||||
|     } else if(test->type == SubGhzHalAsyncTxTestTypeInvalidEnd) { | ||||
|         if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { | ||||
|         if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { | ||||
|             test->pos++; | ||||
|             return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||
|         } else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|         } else if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|             test->pos++; | ||||
|             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||
|         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|         } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) { | ||||
|             test->pos++; | ||||
|             return level_duration_reset(); | ||||
|         } else { | ||||
| @ -294,20 +294,20 @@ static LevelDuration subghz_hal_async_tx_test_yield(void* context) { | ||||
|             furi_crash("Yield after reset"); | ||||
|         } | ||||
|     } else if(test->type == SubGhzHalAsyncTxTestTypeResetMid) { | ||||
|         if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { | ||||
|         if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { | ||||
|             test->pos++; | ||||
|             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||
|         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { | ||||
|         } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) { | ||||
|             test->pos++; | ||||
|             return level_duration_reset(); | ||||
|         } else { | ||||
|             furi_crash("Yield after reset"); | ||||
|         } | ||||
|     } else if(test->type == SubGhzHalAsyncTxTestTypeResetEnd) { | ||||
|         if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { | ||||
|         if(test->pos < FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL) { | ||||
|             test->pos++; | ||||
|             return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION); | ||||
|         } else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) { | ||||
|         } else if(test->pos == FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL) { | ||||
|             test->pos++; | ||||
|             return level_duration_reset(); | ||||
|         } else { | ||||
| @ -334,6 +334,8 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) { | ||||
| 
 | ||||
|     while(!furi_hal_subghz_is_async_tx_complete()) { | ||||
|         if(furi_hal_cortex_timer_is_expired(timer)) { | ||||
|             furi_hal_subghz_stop_async_tx(); | ||||
|             furi_hal_subghz_sleep(); | ||||
|             return false; | ||||
|         } | ||||
|         furi_delay_ms(10); | ||||
|  | ||||
| @ -29,6 +29,7 @@ int run_minunit_test_bit_lib(); | ||||
| int run_minunit_test_float_tools(); | ||||
| int run_minunit_test_bt(); | ||||
| int run_minunit_test_dialogs_file_browser_options(); | ||||
| int run_minunit_test_expansion(); | ||||
| 
 | ||||
| typedef int (*UnitTestEntry)(); | ||||
| 
 | ||||
| @ -60,6 +61,7 @@ const UnitTest unit_tests[] = { | ||||
|     {.name = "bt", .entry = run_minunit_test_bt}, | ||||
|     {.name = "dialogs_file_browser_options", | ||||
|      .entry = run_minunit_test_dialogs_file_browser_options}, | ||||
|     {.name = "expansion", .entry = run_minunit_test_expansion}, | ||||
| }; | ||||
| 
 | ||||
| void minunit_print_progress() { | ||||
|  | ||||
| @ -18,14 +18,14 @@ | ||||
| 
 | ||||
| #define TAG "SubGhzDeviceCc1101Ext" | ||||
| 
 | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO &gpio_ext_pb2 | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_TX_GPIO (&gpio_ext_pb2) | ||||
| 
 | ||||
| /* DMA Channels definition */ | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA DMA2 | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL LL_DMA_CHANNEL_3 | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL LL_DMA_CHANNEL_4 | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL LL_DMA_CHANNEL_5 | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ FuriHalInterruptIdDma2Ch3 | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA (DMA2) | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL (LL_DMA_CHANNEL_3) | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_CHANNEL (LL_DMA_CHANNEL_4) | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL (LL_DMA_CHANNEL_5) | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ (FuriHalInterruptIdDma2Ch3) | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF \ | ||||
|     SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF \ | ||||
| @ -34,10 +34,10 @@ | ||||
|     SUBGHZ_DEVICE_CC1101_EXT_DMA, SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_CHANNEL | ||||
| 
 | ||||
| /** Low level buffer dimensions and guard times */ | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256) | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL (256u) | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_HALF \ | ||||
|     (SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL / 2) | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME 999 << 1 | ||||
| #define SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME (999u >> 1) | ||||
| 
 | ||||
| /** SubGhz state */ | ||||
| typedef enum { | ||||
| @ -45,7 +45,6 @@ typedef enum { | ||||
|     SubGhzDeviceCC1101ExtStateIdle, /**< Idle, energy save mode */ | ||||
|     SubGhzDeviceCC1101ExtStateAsyncRx, /**< Async RX started */ | ||||
|     SubGhzDeviceCC1101ExtStateAsyncTx, /**< Async TX started, DMA and timer is on */ | ||||
|     SubGhzDeviceCC1101ExtStateAsyncTxEnd, /**< Async TX complete, cleanup needed */ | ||||
| } SubGhzDeviceCC1101ExtState; | ||||
| 
 | ||||
| /** SubGhz regulation, receive transmission on the current frequency for the
 | ||||
| @ -55,13 +54,25 @@ typedef enum { | ||||
|     SubGhzDeviceCC1101ExtRegulationTxRx, /**TxRx*/ | ||||
| } SubGhzDeviceCC1101ExtRegulation; | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle, | ||||
|     SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset, | ||||
|     SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun, | ||||
| } SubGhzDeviceCC1101ExtAsyncTxMiddlewareState; | ||||
| 
 | ||||
| typedef struct { | ||||
|     SubGhzDeviceCC1101ExtAsyncTxMiddlewareState state; | ||||
|     bool is_odd_level; | ||||
|     uint32_t adder_duration; | ||||
| } SubGhzDeviceCC1101ExtAsyncTxMiddleware; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint32_t* buffer; | ||||
|     LevelDuration carry_ld; | ||||
|     SubGhzDeviceCC1101ExtCallback callback; | ||||
|     void* callback_context; | ||||
|     uint32_t gpio_tx_buff[2]; | ||||
|     uint32_t debug_gpio_buff[2]; | ||||
|     SubGhzDeviceCC1101ExtAsyncTxMiddleware middleware; | ||||
| } SubGhzDeviceCC1101ExtAsyncTx; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -259,8 +270,8 @@ void subghz_device_cc1101_ext_dump_state() { | ||||
| 
 | ||||
| void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data) { | ||||
|     //load config
 | ||||
|     subghz_device_cc1101_ext_reset(); | ||||
|     furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     uint32_t i = 0; | ||||
|     uint8_t pa[8] = {0}; | ||||
|     while(preset_data[i]) { | ||||
| @ -289,8 +300,8 @@ void subghz_device_cc1101_ext_load_custom_preset(const uint8_t* preset_data) { | ||||
| } | ||||
| 
 | ||||
| void subghz_device_cc1101_ext_load_registers(const uint8_t* data) { | ||||
|     subghz_device_cc1101_ext_reset(); | ||||
|     furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     uint32_t i = 0; | ||||
|     while(data[i]) { | ||||
|         cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, data[i], data[i + 1]); | ||||
| @ -371,6 +382,7 @@ void subghz_device_cc1101_ext_reset() { | ||||
|     furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); | ||||
|     cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     // Warning: push pull cc1101 clock output on GD0
 | ||||
|     cc1101_write_reg( | ||||
|         subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); | ||||
|     furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); | ||||
| @ -379,12 +391,18 @@ void subghz_device_cc1101_ext_reset() { | ||||
| void subghz_device_cc1101_ext_idle() { | ||||
|     furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     cc1101_switch_to_idle(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     //waiting for the chip to switch to IDLE mode
 | ||||
|     furi_check(cc1101_wait_status_state( | ||||
|         subghz_device_cc1101_ext->spi_bus_handle, CC1101StateIDLE, 10000)); | ||||
|     furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); | ||||
| } | ||||
| 
 | ||||
| void subghz_device_cc1101_ext_rx() { | ||||
|     furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     cc1101_switch_to_rx(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     //waiting for the chip to switch to Rx mode
 | ||||
|     furi_check( | ||||
|         cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateRX, 10000)); | ||||
|     furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); | ||||
| } | ||||
| 
 | ||||
| @ -392,6 +410,9 @@ bool subghz_device_cc1101_ext_tx() { | ||||
|     if(subghz_device_cc1101_ext->regulation != SubGhzDeviceCC1101ExtRegulationTxRx) return false; | ||||
|     furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     cc1101_switch_to_tx(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     //waiting for the chip to switch to Tx mode
 | ||||
|     furi_check( | ||||
|         cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateTX, 10000)); | ||||
|     furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); | ||||
|     return true; | ||||
| } | ||||
| @ -563,50 +584,90 @@ void subghz_device_cc1101_ext_stop_async_rx() { | ||||
|     furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); | ||||
| } | ||||
| 
 | ||||
| static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t samples) { | ||||
|     furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx); | ||||
|     while(samples > 0) { | ||||
|         bool is_odd = samples % 2; | ||||
|         LevelDuration ld; | ||||
|         if(level_duration_is_reset(subghz_device_cc1101_ext->async_tx.carry_ld)) { | ||||
|             ld = subghz_device_cc1101_ext->async_tx.callback( | ||||
|                 subghz_device_cc1101_ext->async_tx.callback_context); | ||||
|         } else { | ||||
|             ld = subghz_device_cc1101_ext->async_tx.carry_ld; | ||||
|             subghz_device_cc1101_ext->async_tx.carry_ld = level_duration_reset(); | ||||
| void subghz_device_cc1101_ext_async_tx_middleware_idle( | ||||
|     SubGhzDeviceCC1101ExtAsyncTxMiddleware* middleware) { | ||||
|     middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle; | ||||
|     middleware->is_odd_level = false; | ||||
|     middleware->adder_duration = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; | ||||
| } | ||||
| 
 | ||||
| static inline uint32_t subghz_device_cc1101_ext_async_tx_middleware_get_duration( | ||||
|     SubGhzDeviceCC1101ExtAsyncTxMiddleware* middleware, | ||||
|     SubGhzDeviceCC1101ExtCallback callback) { | ||||
|     uint32_t ret = 0; | ||||
|     bool is_level = false; | ||||
| 
 | ||||
|     if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset) return 0; | ||||
| 
 | ||||
|     while(1) { | ||||
|         LevelDuration ld = callback(subghz_device_cc1101_ext->async_tx.callback_context); | ||||
|         if(level_duration_is_reset(ld)) { | ||||
|             middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateReset; | ||||
|             if(!middleware->is_odd_level) { | ||||
|                 return 0; | ||||
|             } else { | ||||
|                 return middleware->adder_duration; | ||||
|             } | ||||
|         } else if(level_duration_is_wait(ld)) { | ||||
|             middleware->is_odd_level = !middleware->is_odd_level; | ||||
|             ret = middleware->adder_duration + SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; | ||||
|             middleware->adder_duration = 0; | ||||
|             return ret; | ||||
|         } | ||||
| 
 | ||||
|         if(level_duration_is_wait(ld)) { | ||||
|             *buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; | ||||
|             buffer++; | ||||
|             samples--; | ||||
|         } else if(level_duration_is_reset(ld)) { | ||||
|         is_level = level_duration_get_level(ld); | ||||
| 
 | ||||
|         if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateIdle) { | ||||
|             if(is_level != middleware->is_odd_level) { | ||||
|                 middleware->state = SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun; | ||||
|                 middleware->is_odd_level = is_level; | ||||
|                 middleware->adder_duration = level_duration_get_duration(ld); | ||||
|                 return SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; | ||||
|             } else { | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(middleware->state == SubGhzDeviceCC1101ExtAsyncTxMiddlewareStateRun) { | ||||
|             if(is_level == middleware->is_odd_level) { | ||||
|                 middleware->adder_duration += level_duration_get_duration(ld); | ||||
|                 continue; | ||||
|             } else { | ||||
|                 middleware->is_odd_level = is_level; | ||||
|                 ret = middleware->adder_duration; | ||||
|                 middleware->adder_duration = level_duration_get_duration(ld); | ||||
|                 return ret; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t samples) { | ||||
|     furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx); | ||||
| 
 | ||||
|     while(samples > 0) { | ||||
|         volatile uint32_t duration = subghz_device_cc1101_ext_async_tx_middleware_get_duration( | ||||
|             &subghz_device_cc1101_ext->async_tx.middleware, | ||||
|             subghz_device_cc1101_ext->async_tx.callback); | ||||
|         if(duration == 0) { | ||||
|             *buffer = 0; | ||||
|             buffer++; | ||||
|             samples--; | ||||
|             LL_DMA_DisableIT_HT(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); | ||||
|             LL_DMA_DisableIT_TC(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); | ||||
|             LL_TIM_EnableIT_UPDATE(TIM17); | ||||
|             if(LL_DMA_IsActiveFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) { | ||||
|                 LL_DMA_ClearFlag_HT3(SUBGHZ_DEVICE_CC1101_EXT_DMA); | ||||
|             } | ||||
|             if(LL_DMA_IsActiveFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA)) { | ||||
|                 LL_DMA_ClearFlag_TC3(SUBGHZ_DEVICE_CC1101_EXT_DMA); | ||||
|             } | ||||
|             break; | ||||
|         } else { | ||||
|             bool level = level_duration_get_level(ld); | ||||
| 
 | ||||
|             // Inject guard time if level is incorrect
 | ||||
|             if(is_odd != level) { | ||||
|                 *buffer = SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_GUARD_TIME; | ||||
|                 buffer++; | ||||
|                 samples--; | ||||
| 
 | ||||
|                 // Special case: prevent buffer overflow if sample is last
 | ||||
|                 if(samples == 0) { | ||||
|                     subghz_device_cc1101_ext->async_tx.carry_ld = ld; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             uint32_t duration = level_duration_get_duration(ld); | ||||
|             furi_assert(duration > 0); | ||||
|             *buffer = duration >> 1; | ||||
|             // Lowest possible value is 4us
 | ||||
|             if(duration < 4) duration = 4; | ||||
|             // Divide by 2 since timer resolution is 2us
 | ||||
|             // Subtract 1 since we counting from 0
 | ||||
|             *buffer = (duration >> 1) - 1; | ||||
|             buffer++; | ||||
|             samples--; | ||||
|         } | ||||
| @ -635,20 +696,6 @@ static void subghz_device_cc1101_ext_async_tx_dma_isr() { | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static void subghz_device_cc1101_ext_async_tx_timer_isr() { | ||||
|     if(LL_TIM_IsActiveFlag_UPDATE(TIM17)) { | ||||
|         if(LL_TIM_GetAutoReload(TIM17) == 0) { | ||||
|             LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF); | ||||
|             furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); | ||||
|             if(subghz_device_cc1101_ext->async_mirror_pin != NULL) | ||||
|                 furi_hal_gpio_write(subghz_device_cc1101_ext->async_mirror_pin, false); | ||||
|             LL_TIM_DisableCounter(TIM17); | ||||
|             subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateAsyncTxEnd; | ||||
|         } | ||||
|         LL_TIM_ClearFlag_UPDATE(TIM17); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callback, void* context) { | ||||
|     furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateIdle); | ||||
|     furi_assert(callback); | ||||
| @ -677,7 +724,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb | ||||
|         SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, | ||||
|         LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | | ||||
|             LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_WORD | LL_DMA_MDATAALIGN_WORD | | ||||
|             LL_DMA_MODE_NORMAL); | ||||
|             LL_DMA_PRIORITY_VERYHIGH); | ||||
|     LL_DMA_SetDataLength( | ||||
|         SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL); | ||||
|     LL_DMA_SetPeriphRequest(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, LL_DMAMUX_REQ_TIM17_UP); | ||||
| @ -693,16 +740,15 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb | ||||
| 
 | ||||
|     // Configure TIM
 | ||||
|     // Set the timer resolution to 2 us
 | ||||
|     LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1); | ||||
|     LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); | ||||
|     LL_TIM_SetAutoReload(TIM17, 0xFFFF); | ||||
|     LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); | ||||
|     LL_TIM_SetAutoReload(TIM17, 500); | ||||
|     LL_TIM_SetPrescaler(TIM17, (64 << 1) - 1); | ||||
|     LL_TIM_SetClockSource(TIM17, LL_TIM_CLOCKSOURCE_INTERNAL); | ||||
|     LL_TIM_DisableARRPreload(TIM17); | ||||
| 
 | ||||
|     furi_hal_interrupt_set_isr( | ||||
|         FuriHalInterruptIdTim1TrgComTim17, subghz_device_cc1101_ext_async_tx_timer_isr, NULL); | ||||
| 
 | ||||
|     subghz_device_cc1101_ext_async_tx_middleware_idle( | ||||
|         &subghz_device_cc1101_ext->async_tx.middleware); | ||||
|     subghz_device_cc1101_ext_async_tx_refill( | ||||
|         subghz_device_cc1101_ext->async_tx.buffer, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL); | ||||
| 
 | ||||
| @ -748,7 +794,6 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb | ||||
| 
 | ||||
|     // Start counter
 | ||||
|     LL_TIM_EnableDMAReq_UPDATE(TIM17); | ||||
|     LL_TIM_GenerateEvent_UPDATE(TIM17); | ||||
| 
 | ||||
|     subghz_device_cc1101_ext_tx(); | ||||
| 
 | ||||
| @ -759,19 +804,22 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb | ||||
| } | ||||
| 
 | ||||
| bool subghz_device_cc1101_ext_is_async_tx_complete() { | ||||
|     return subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd; | ||||
|     return ( | ||||
|         (subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx) && | ||||
|         (LL_TIM_GetAutoReload(TIM17) == 0)); | ||||
| } | ||||
| 
 | ||||
| void subghz_device_cc1101_ext_stop_async_tx() { | ||||
|     furi_assert( | ||||
|         subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx || | ||||
|         subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTxEnd); | ||||
|     furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx); | ||||
| 
 | ||||
|     // Shutdown radio
 | ||||
|     subghz_device_cc1101_ext_idle(); | ||||
| 
 | ||||
|     // Deinitialize GPIO
 | ||||
|     furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); | ||||
|     furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); | ||||
| 
 | ||||
|     // Deinitialize Timer
 | ||||
|     FURI_CRITICAL_ENTER(); | ||||
|     furi_hal_bus_disable(FuriHalBusTIM17); | ||||
|     furi_hal_interrupt_set_isr(FuriHalInterruptIdTim1TrgComTim17, NULL, NULL); | ||||
| 
 | ||||
| @ -780,17 +828,11 @@ void subghz_device_cc1101_ext_stop_async_tx() { | ||||
|     LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF); | ||||
|     furi_hal_interrupt_set_isr(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_IRQ, NULL, NULL); | ||||
| 
 | ||||
|     // Deinitialize GPIO
 | ||||
|     furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false); | ||||
|     furi_hal_gpio_init(subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); | ||||
| 
 | ||||
|     // Stop debug
 | ||||
|     if(subghz_device_cc1101_ext_stop_debug()) { | ||||
|         LL_DMA_DisableChannel(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF); | ||||
|     } | ||||
| 
 | ||||
|     FURI_CRITICAL_EXIT(); | ||||
| 
 | ||||
|     free(subghz_device_cc1101_ext->async_tx.buffer); | ||||
| 
 | ||||
|     subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; | ||||
|  | ||||
| @ -3,7 +3,7 @@ App( | ||||
|     name="GPIO", | ||||
|     apptype=FlipperAppType.MENUEXTERNAL, | ||||
|     entry_point="gpio_app", | ||||
|     stack_size=1 * 1024, | ||||
|     stack_size=2 * 1024, | ||||
|     icon="A_GPIO_14", | ||||
|     order=50, | ||||
|     fap_libs=["assets"], | ||||
|  | ||||
| @ -46,7 +46,7 @@ void line_ensure_flow_invariant(GpioApp* app) { | ||||
|     // selected. This function enforces that invariant by resetting flow_pins
 | ||||
|     // to None if it is configured to 16,15 when LPUART is selected.
 | ||||
| 
 | ||||
|     uint8_t available_flow_pins = app->usb_uart_cfg->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4; | ||||
|     uint8_t available_flow_pins = app->usb_uart_cfg->uart_ch == FuriHalSerialIdLpuart ? 3 : 4; | ||||
|     VariableItem* item = app->var_item_flow; | ||||
|     variable_item_set_values_count(item, available_flow_pins); | ||||
| 
 | ||||
| @ -77,9 +77,9 @@ static void line_port_cb(VariableItem* item) { | ||||
|     variable_item_set_current_value_text(item, uart_ch[index]); | ||||
| 
 | ||||
|     if(index == 0) | ||||
|         app->usb_uart_cfg->uart_ch = FuriHalUartIdUSART1; | ||||
|         app->usb_uart_cfg->uart_ch = FuriHalSerialIdUsart; | ||||
|     else if(index == 1) | ||||
|         app->usb_uart_cfg->uart_ch = FuriHalUartIdLPUART1; | ||||
|         app->usb_uart_cfg->uart_ch = FuriHalSerialIdLpuart; | ||||
| 
 | ||||
|     line_ensure_flow_invariant(app); | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); | ||||
|  | ||||
| @ -29,17 +29,18 @@ typedef enum { | ||||
| 
 | ||||
|     WorkerEvtTxStop = (1 << 2), | ||||
|     WorkerEvtCdcRx = (1 << 3), | ||||
|     WorkerEvtCdcTxComplete = (1 << 4), | ||||
| 
 | ||||
|     WorkerEvtCfgChange = (1 << 4), | ||||
|     WorkerEvtCfgChange = (1 << 5), | ||||
| 
 | ||||
|     WorkerEvtLineCfgSet = (1 << 5), | ||||
|     WorkerEvtCtrlLineSet = (1 << 6), | ||||
|     WorkerEvtLineCfgSet = (1 << 6), | ||||
|     WorkerEvtCtrlLineSet = (1 << 7), | ||||
| 
 | ||||
| } WorkerEvtFlags; | ||||
| 
 | ||||
| #define WORKER_ALL_RX_EVENTS                                                      \ | ||||
|     (WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \ | ||||
|      WorkerEvtCtrlLineSet) | ||||
|      WorkerEvtCtrlLineSet | WorkerEvtCdcTxComplete) | ||||
| #define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtCdcRx) | ||||
| 
 | ||||
| struct UsbUartBridge { | ||||
| @ -50,6 +51,7 @@ struct UsbUartBridge { | ||||
|     FuriThread* tx_thread; | ||||
| 
 | ||||
|     FuriStreamBuffer* rx_stream; | ||||
|     FuriHalSerialHandle* serial_handle; | ||||
| 
 | ||||
|     FuriMutex* usb_mutex; | ||||
| 
 | ||||
| @ -80,11 +82,23 @@ static const CdcCallbacks cdc_cb = { | ||||
| 
 | ||||
| static int32_t usb_uart_tx_thread(void* context); | ||||
| 
 | ||||
| static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { | ||||
| static void usb_uart_on_irq_rx_dma_cb( | ||||
|     FuriHalSerialHandle* handle, | ||||
|     FuriHalSerialRxEvent ev, | ||||
|     size_t size, | ||||
|     void* context) { | ||||
|     UsbUartBridge* usb_uart = (UsbUartBridge*)context; | ||||
| 
 | ||||
|     if(ev == UartIrqEventRXNE) { | ||||
|         furi_stream_buffer_send(usb_uart->rx_stream, &data, 1, 0); | ||||
|     if(ev & (FuriHalSerialRxEventData | FuriHalSerialRxEventIdle)) { | ||||
|         uint8_t data[FURI_HAL_SERIAL_DMA_BUFFER_SIZE] = {0}; | ||||
|         while(size) { | ||||
|             size_t ret = furi_hal_serial_dma_rx( | ||||
|                 handle, | ||||
|                 data, | ||||
|                 (size > FURI_HAL_SERIAL_DMA_BUFFER_SIZE) ? FURI_HAL_SERIAL_DMA_BUFFER_SIZE : size); | ||||
|             furi_stream_buffer_send(usb_uart->rx_stream, data, ret, 0); | ||||
|             size -= ret; | ||||
|         }; | ||||
|         furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtRxDone); | ||||
|     } | ||||
| } | ||||
| @ -116,32 +130,33 @@ static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) { | ||||
| } | ||||
| 
 | ||||
| static void usb_uart_serial_init(UsbUartBridge* usb_uart, uint8_t uart_ch) { | ||||
|     if(uart_ch == FuriHalUartIdUSART1) { | ||||
|         furi_hal_console_disable(); | ||||
|     } else if(uart_ch == FuriHalUartIdLPUART1) { | ||||
|         furi_hal_uart_init(uart_ch, 115200); | ||||
|     } | ||||
|     furi_hal_uart_set_irq_cb(uart_ch, usb_uart_on_irq_cb, usb_uart); | ||||
|     furi_assert(!usb_uart->serial_handle); | ||||
| 
 | ||||
|     usb_uart->serial_handle = furi_hal_serial_control_acquire(uart_ch); | ||||
|     furi_assert(usb_uart->serial_handle); | ||||
| 
 | ||||
|     furi_hal_serial_init(usb_uart->serial_handle, 115200); | ||||
|     furi_hal_serial_dma_rx_start( | ||||
|         usb_uart->serial_handle, usb_uart_on_irq_rx_dma_cb, usb_uart, false); | ||||
| } | ||||
| 
 | ||||
| static void usb_uart_serial_deinit(UsbUartBridge* usb_uart, uint8_t uart_ch) { | ||||
|     UNUSED(usb_uart); | ||||
|     furi_hal_uart_set_irq_cb(uart_ch, NULL, NULL); | ||||
|     if(uart_ch == FuriHalUartIdUSART1) | ||||
|         furi_hal_console_enable(); | ||||
|     else if(uart_ch == FuriHalUartIdLPUART1) | ||||
|         furi_hal_uart_deinit(uart_ch); | ||||
| static void usb_uart_serial_deinit(UsbUartBridge* usb_uart) { | ||||
|     furi_assert(usb_uart->serial_handle); | ||||
| 
 | ||||
|     furi_hal_serial_deinit(usb_uart->serial_handle); | ||||
|     furi_hal_serial_control_release(usb_uart->serial_handle); | ||||
|     usb_uart->serial_handle = NULL; | ||||
| } | ||||
| 
 | ||||
| static void usb_uart_set_baudrate(UsbUartBridge* usb_uart, uint32_t baudrate) { | ||||
|     if(baudrate != 0) { | ||||
|         furi_hal_uart_set_br(usb_uart->cfg.uart_ch, baudrate); | ||||
|         furi_hal_serial_set_br(usb_uart->serial_handle, baudrate); | ||||
|         usb_uart->st.baudrate_cur = baudrate; | ||||
|     } else { | ||||
|         struct usb_cdc_line_coding* line_cfg = | ||||
|             furi_hal_cdc_get_port_settings(usb_uart->cfg.vcp_ch); | ||||
|         if(line_cfg->dwDTERate > 0) { | ||||
|             furi_hal_uart_set_br(usb_uart->cfg.uart_ch, line_cfg->dwDTERate); | ||||
|             furi_hal_serial_set_br(usb_uart->serial_handle, line_cfg->dwDTERate); | ||||
|             usb_uart->st.baudrate_cur = line_cfg->dwDTERate; | ||||
|         } | ||||
|     } | ||||
| @ -191,7 +206,7 @@ static int32_t usb_uart_worker(void* context) { | ||||
|             furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); | ||||
|         furi_check(!(events & FuriFlagError)); | ||||
|         if(events & WorkerEvtStop) break; | ||||
|         if(events & WorkerEvtRxDone) { | ||||
|         if(events & (WorkerEvtRxDone | WorkerEvtCdcTxComplete)) { | ||||
|             size_t len = furi_stream_buffer_receive( | ||||
|                 usb_uart->rx_stream, usb_uart->rx_buf, USB_CDC_PKT_LEN, 0); | ||||
|             if(len > 0) { | ||||
| @ -223,7 +238,7 @@ static int32_t usb_uart_worker(void* context) { | ||||
|                 furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtTxStop); | ||||
|                 furi_thread_join(usb_uart->tx_thread); | ||||
| 
 | ||||
|                 usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch); | ||||
|                 usb_uart_serial_deinit(usb_uart); | ||||
|                 usb_uart_serial_init(usb_uart, usb_uart->cfg_new.uart_ch); | ||||
| 
 | ||||
|                 usb_uart->cfg.uart_ch = usb_uart->cfg_new.uart_ch; | ||||
| @ -274,7 +289,7 @@ static int32_t usb_uart_worker(void* context) { | ||||
|         } | ||||
|     } | ||||
|     usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch); | ||||
|     usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch); | ||||
|     usb_uart_serial_deinit(usb_uart); | ||||
| 
 | ||||
|     furi_hal_gpio_init(USB_USART_DE_RE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow); | ||||
| 
 | ||||
| @ -320,18 +335,10 @@ static int32_t usb_uart_tx_thread(void* context) { | ||||
|                 if(usb_uart->cfg.software_de_re != 0) | ||||
|                     furi_hal_gpio_write(USB_USART_DE_RE_PIN, false); | ||||
| 
 | ||||
|                 furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, len); | ||||
|                 furi_hal_serial_tx(usb_uart->serial_handle, data, len); | ||||
| 
 | ||||
|                 if(usb_uart->cfg.software_de_re != 0) { | ||||
|                     //TODO: FL-3276 port to new USART API
 | ||||
|                     if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1) { | ||||
|                         while(!LL_USART_IsActiveFlag_TC(USART1)) | ||||
|                             ; | ||||
|                     } else if(usb_uart->cfg.uart_ch == FuriHalUartIdLPUART1) { | ||||
|                         while(!LL_LPUART_IsActiveFlag_TC(LPUART1)) | ||||
|                             ; | ||||
|                     } | ||||
| 
 | ||||
|                     furi_hal_serial_tx_wait_complete(usb_uart->serial_handle); | ||||
|                     furi_hal_gpio_write(USB_USART_DE_RE_PIN, true); | ||||
|                 } | ||||
|             } | ||||
| @ -345,6 +352,7 @@ static int32_t usb_uart_tx_thread(void* context) { | ||||
| static void vcp_on_cdc_tx_complete(void* context) { | ||||
|     UsbUartBridge* usb_uart = (UsbUartBridge*)context; | ||||
|     furi_semaphore_release(usb_uart->tx_sem); | ||||
|     furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtCdcTxComplete); | ||||
| } | ||||
| 
 | ||||
| static void vcp_on_cdc_rx(void* context) { | ||||
|  | ||||
| @ -9,9 +9,8 @@ void ibutton_scene_delete_success_on_enter(void* context) { | ||||
|     iButton* ibutton = context; | ||||
|     Popup* popup = ibutton->popup; | ||||
| 
 | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); | ||||
|     popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); | ||||
| 
 | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); | ||||
|     popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_callback(popup, ibutton_scene_delete_success_popup_callback); | ||||
|     popup_set_context(popup, ibutton); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|  | ||||
| @ -9,9 +9,8 @@ void ibutton_scene_save_success_on_enter(void* context) { | ||||
|     iButton* ibutton = context; | ||||
|     Popup* popup = ibutton->popup; | ||||
| 
 | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); | ||||
| 
 | ||||
|     popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); | ||||
|     popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_callback(popup, ibutton_scene_save_success_popup_callback); | ||||
|     popup_set_context(popup, ibutton); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|  | ||||
| @ -10,7 +10,7 @@ void ibutton_scene_write_success_on_enter(void* context) { | ||||
|     iButton* ibutton = context; | ||||
|     Popup* popup = ibutton->popup; | ||||
| 
 | ||||
|     popup_set_icon(popup, 0, 12, &I_iButtonDolphinVerySuccess_108x52); | ||||
|     popup_set_icon(popup, 0, 9, &I_iButtonDolphinVerySuccess_92x55); | ||||
|     popup_set_text(popup, "Successfully written!", 40, 12, AlignLeft, AlignBottom); | ||||
| 
 | ||||
|     popup_set_callback(popup, ibutton_scene_write_success_popup_callback); | ||||
|  | ||||
| @ -712,3 +712,79 @@ type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 8425 4291 542 1401 544 538 542 1404 518 1427 544 534 570 540 516 1434 542 1399 544 539 544 539 541 1403 541 1402 516 570 517 1427 545 566 511 1406 517 1431 542 534 544 1406 543 536 517 567 543 1404 542 1400 595 525 544 1405 542 534 516 1433 516 1428 544 537 544 535 543 1403 542 505 488 21084 8424 4298 566 535 569 1402 567 533 568 537 570 1403 570 1403 565 534 566 537 566 1402 569 1398 569 533 569 538 592 1446 569 535 570 1400 569 567 535 535 568 1437 568 533 568 1398 569 1402 565 533 567 534 569 1403 568 536 569 1402 568 535 567 534 571 1403 568 1415 566 536 571 1362 489 21084 8403 4313 516 1439 517 574 515 1442 515 1441 518 573 516 574 567 1397 514 1440 515 573 516 575 516 1443 515 1439 518 574 516 1440 517 608 535 1396 517 1441 517 579 515 1438 515 576 517 578 568 1390 569 1391 516 575 518 1439 516 573 517 1445 566 1391 516 571 517 572 516 1441 514 543 487 | ||||
| # | ||||
| # Model: Samsung DB93 | ||||
| # | ||||
| name: Off | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 667 17837 3089 8903 555 445 578 1411 583 442 554 442 529 468 528 468 554 443 554 442 554 444 552 1441 552 472 523 475 522 1473 522 1473 522 475 547 1447 548 1446 548 1446 548 1446 548 1446 548 449 548 450 547 451 546 474 523 476 521 476 521 476 522 476 521 475 546 451 547 450 548 449 548 449 548 449 548 449 548 449 548 449 548 450 547 450 547 451 546 474 523 474 523 476 521 477 520 477 520 477 521 476 521 476 546 450 547 450 547 450 546 450 547 451 546 451 546 1448 546 1472 522 2982 3005 8963 522 1499 495 502 495 502 495 502 496 501 496 501 521 476 522 475 522 475 522 1472 522 475 522 475 522 1473 521 475 522 1473 522 1499 495 1500 494 1500 496 1499 521 1473 522 475 522 475 522 475 522 475 522 475 522 475 522 476 521 475 522 476 521 476 521 476 521 502 495 503 494 503 495 502 495 501 496 501 521 476 522 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 503 494 503 494 503 493 504 494 502 495 502 495 502 520 477 520 2984 3003 8966 519 1475 520 477 520 477 520 477 520 478 519 477 520 478 519 479 518 504 493 1502 493 504 493 503 494 503 494 1501 519 1476 518 1475 519 478 519 1476 518 1476 519 1477 517 1501 493 1503 491 1503 493 1502 493 1502 518 479 518 479 518 479 518 1476 518 1477 517 1477 517 504 493 504 493 504 493 531 466 507 490 1529 467 506 491 529 468 1527 492 1502 492 505 493 1502 492 1502 492 505 492 504 493 504 492 505 492 506 491 532 465 532 465 531 467 530 467 530 467 1528 467 1527 492 | ||||
| # | ||||
| name: Dh | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 667 17829 3090 8902 556 444 579 1410 584 441 529 468 529 468 554 443 554 442 555 443 553 444 551 1442 551 474 522 475 522 1473 522 475 522 475 547 1448 547 1447 547 1447 547 1447 548 1447 547 449 548 450 547 474 523 474 523 476 521 476 521 476 521 477 521 475 522 476 546 450 547 449 548 449 548 450 547 449 548 450 547 450 547 450 547 451 546 474 523 474 523 474 523 479 518 479 518 479 518 501 496 501 496 477 545 475 522 452 545 474 523 474 523 1472 522 1472 522 1472 522 1472 523 2981 3005 8990 494 1499 495 502 496 501 496 501 496 501 522 475 522 475 522 475 522 475 522 1473 521 475 522 475 522 1473 521 475 522 1473 521 1500 494 1500 495 1499 520 1474 522 1473 522 475 522 475 522 476 521 475 522 476 521 476 521 476 521 476 521 476 521 477 520 503 494 503 494 503 495 502 495 502 520 477 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 477 520 504 493 503 494 503 494 503 495 502 495 502 495 502 520 477 520 477 520 2984 3003 8966 519 1475 519 477 520 478 519 478 519 478 519 479 518 503 494 505 492 505 492 1503 493 504 493 504 493 504 518 1477 518 1476 518 1476 518 479 518 1477 517 1477 517 1501 493 1504 490 1528 468 1527 468 1527 493 1501 493 504 493 504 493 504 493 1501 493 1502 492 1502 492 504 493 505 491 532 440 556 466 531 467 530 468 530 467 530 467 1527 492 1503 491 505 492 504 493 504 493 505 491 1503 492 505 467 530 492 506 466 557 464 533 464 532 466 1528 467 1528 467 1528 466 1528 491 | ||||
| # | ||||
| name: Cool_hi | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 667 17831 3089 8903 581 420 578 1411 583 442 529 468 528 468 554 443 554 442 554 443 553 445 551 1443 550 475 521 475 522 1473 522 475 547 449 548 1447 548 1446 548 1446 548 1446 548 1446 548 449 548 449 548 450 547 473 524 476 521 475 522 476 520 476 522 475 522 475 547 449 549 449 548 449 548 449 548 449 548 449 548 449 548 449 548 449 548 450 547 474 523 474 523 476 521 476 521 476 520 477 521 476 546 451 547 450 547 450 547 449 548 450 547 1447 547 1448 546 1448 546 1472 523 2957 3029 8967 518 1477 517 502 495 501 496 501 521 475 522 475 522 475 522 475 522 475 522 1472 522 474 523 475 522 1473 521 475 522 1472 522 1499 495 1500 495 1499 520 1474 522 1473 521 475 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 476 521 502 495 502 495 503 494 502 496 501 496 501 521 476 522 476 521 475 522 475 522 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 503 494 503 494 503 494 503 495 502 495 502 520 477 520 476 521 476 521 2984 3003 8965 520 1474 521 477 520 477 520 477 520 477 520 477 520 478 519 504 493 504 493 1502 494 503 494 502 495 1500 520 1475 519 1475 519 1475 519 478 519 1475 519 1476 518 1501 493 1502 492 1503 493 1501 494 1501 518 1476 518 478 519 478 519 478 519 1476 518 1476 518 1477 517 504 493 506 491 506 491 506 491 506 492 505 492 504 493 504 518 481 516 1501 493 504 493 504 493 480 517 1501 493 504 493 504 493 504 493 505 491 532 466 531 466 532 466 1528 467 1527 468 1527 492 1502 492 | ||||
| # | ||||
| name: Cool_lo | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 667 17832 3088 8903 575 448 555 1411 583 441 529 468 529 468 554 443 554 442 554 443 553 444 551 1443 550 474 522 475 522 1473 522 475 522 475 547 1447 548 1446 548 1446 548 1446 548 1447 547 449 548 449 548 474 523 474 523 476 521 476 521 476 521 477 521 475 522 475 547 450 548 449 548 450 547 449 548 449 548 450 547 450 547 449 547 451 546 452 545 474 523 474 523 477 520 478 519 477 519 478 520 477 545 452 545 451 547 451 546 474 523 474 523 1450 544 1472 522 1472 522 1472 523 2981 3005 8990 494 1499 495 502 496 501 496 501 521 476 521 475 522 475 522 475 522 475 522 1473 521 475 522 475 522 1473 521 476 521 1473 521 1500 494 1500 495 1499 495 1499 521 1473 522 475 522 475 522 476 521 475 522 476 521 476 521 476 521 476 521 476 521 477 520 503 494 503 494 503 495 502 495 502 520 477 520 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 477 520 477 520 478 519 503 493 504 493 504 494 503 494 502 519 478 520 477 520 477 520 2984 3003 8966 519 1475 519 477 520 477 520 478 519 478 519 478 519 503 494 504 493 505 492 1503 493 504 493 504 493 503 519 478 519 1476 518 1476 518 478 519 1476 518 1477 517 1501 493 1504 490 1504 490 1503 493 1502 493 1502 517 480 517 504 493 480 517 1477 517 1502 492 1502 492 504 493 504 493 504 493 531 466 531 466 1529 467 1527 467 1527 492 504 493 1502 492 505 492 504 493 505 492 1503 492 505 492 505 492 505 467 557 464 532 441 556 466 532 466 1528 466 1528 491 1503 491 1503 466 | ||||
| # | ||||
| name: Heat_hi | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 665 17827 3090 8902 556 444 579 1412 583 441 530 467 529 468 554 442 555 442 554 442 554 444 551 1443 550 473 523 475 522 1472 521 475 523 474 523 1471 549 1446 548 1445 549 1446 548 1445 549 448 549 448 549 449 548 473 523 473 524 475 522 475 522 475 523 475 522 474 548 449 548 449 549 448 549 448 549 448 549 448 549 448 549 449 548 449 548 449 548 450 547 473 524 474 523 476 521 476 521 476 521 476 522 475 547 450 547 450 548 449 548 449 548 1447 547 1447 547 1447 547 1448 546 2957 3030 8962 522 1475 519 501 496 501 497 478 519 500 522 475 522 475 523 474 523 474 523 1472 522 474 523 474 523 1472 522 475 522 1472 522 1499 495 1499 496 1499 496 1498 522 1473 522 474 522 475 522 475 522 474 523 475 522 475 522 475 522 475 522 475 522 475 522 502 495 502 495 502 495 501 496 501 496 501 521 476 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 475 522 476 521 476 521 502 495 502 495 502 495 503 495 502 495 501 521 476 521 476 521 2983 3004 8964 521 1474 520 476 521 476 521 477 520 476 521 477 520 477 520 503 494 503 494 1501 494 502 495 502 495 502 520 477 521 1474 520 1474 520 477 520 1474 520 1475 519 1475 519 1500 494 1502 492 1502 493 1501 494 1500 519 478 519 477 520 477 520 1475 519 1475 519 1475 519 478 519 478 519 503 494 505 492 505 492 505 492 1503 493 1502 493 1502 517 1476 518 479 518 479 518 479 518 480 517 479 518 1501 493 504 493 504 493 530 467 531 466 531 467 1527 468 1527 491 1502 493 1501 493 | ||||
| # | ||||
| name: Heat_lo | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 668 17822 3064 8902 582 442 555 1413 581 442 528 468 529 494 528 469 502 495 527 470 526 471 525 1468 552 446 550 448 549 1446 548 448 549 448 549 1446 548 1448 546 1471 523 1473 521 1473 521 476 521 475 522 475 547 449 548 449 548 449 548 449 548 449 548 449 548 449 548 449 548 450 547 474 523 474 523 474 523 477 520 477 520 477 520 477 521 476 521 476 546 450 548 450 547 474 523 474 523 450 547 474 523 474 523 452 545 474 523 474 523 474 523 1500 494 1499 495 1499 496 1498 522 2955 3032 8963 521 1472 522 475 522 474 523 475 522 475 522 475 522 475 522 475 522 475 522 1499 495 502 495 502 495 1499 496 501 521 1473 522 1473 522 1472 522 1472 522 1473 521 1473 522 475 522 475 522 502 495 502 495 502 495 503 495 501 496 501 496 501 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 476 521 477 520 504 493 503 494 503 494 503 495 502 495 502 495 502 520 477 521 476 521 476 521 476 521 477 520 477 520 477 520 476 521 477 520 2984 3003 8967 518 1502 492 504 493 504 494 503 494 503 494 503 519 478 519 478 519 478 519 1476 518 478 518 479 518 478 519 478 519 1501 493 1501 493 506 491 1504 491 1527 468 1503 492 1526 493 1501 493 1501 494 1501 493 1501 493 504 493 504 493 504 493 1528 466 1529 466 1528 467 529 468 529 493 504 493 504 493 504 493 1502 492 1502 492 1502 467 529 493 1502 491 533 465 532 465 532 465 531 467 530 467 1528 467 530 467 530 467 530 467 530 467 530 467 1527 467 1528 466 1528 466 1529 492 | ||||
| # | ||||
| # Model: Samsung AR-EH04 | ||||
| # | ||||
| name: Off | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 663 17769 3057 8901 529 492 527 1434 556 465 529 464 529 464 555 439 555 438 556 438 554 440 553 1435 552 444 549 470 524 1465 524 1466 522 473 522 1467 522 1466 549 1440 549 1440 548 1440 548 445 549 445 549 446 548 447 547 471 523 470 524 471 523 472 522 472 521 473 522 472 522 472 523 472 548 446 549 445 548 446 548 447 547 447 547 446 548 447 547 470 524 471 523 471 523 471 523 471 523 498 496 475 519 498 496 497 498 497 522 472 523 471 524 470 523 471 523 1443 546 1442 547 2947 3023 8935 522 1466 522 471 523 472 522 474 520 498 496 498 496 498 497 497 497 497 523 1466 523 471 523 471 523 1466 523 471 523 1466 522 1467 522 1467 522 1493 495 1493 496 1493 497 497 522 472 523 471 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 499 495 499 495 499 495 499 495 499 496 498 522 472 523 472 523 471 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 473 521 499 495 500 494 499 495 499 496 499 521 2947 3024 8937 521 1467 521 473 521 473 521 472 521 473 521 473 521 473 521 473 521 474 520 1469 520 500 494 500 494 1495 495 500 495 499 520 474 521 1468 520 1468 521 1468 521 1468 521 1468 521 1469 519 1470 518 1495 493 1496 493 500 495 499 520 474 521 1468 520 1469 520 1469 520 473 521 474 520 474 520 475 519 476 518 500 494 502 492 502 491 1497 493 1495 495 499 495 499 520 474 520 475 519 475 519 475 519 475 519 475 519 500 494 501 493 501 493 501 493 501 493 1498 491 1497 519 | ||||
| # | ||||
| name: Dh | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 665 17760 3083 8875 553 468 527 1432 584 438 555 439 529 464 555 439 555 438 556 438 555 439 553 1435 552 443 550 470 524 1466 523 471 522 473 521 1467 523 1466 549 1439 549 1440 549 1439 549 445 549 445 549 445 549 446 548 470 524 470 524 471 523 472 522 472 522 473 521 472 522 472 547 447 548 445 549 445 549 445 549 445 549 446 548 445 549 445 549 447 547 470 524 471 523 471 523 471 523 473 521 473 521 473 521 473 521 472 523 472 548 446 548 1441 547 1441 548 1441 547 1441 547 2947 3023 8935 522 1466 522 472 522 474 519 475 519 475 519 474 521 473 546 448 547 448 546 1465 523 449 545 448 546 1466 522 471 523 1467 522 1466 522 1493 495 1493 495 1493 496 1493 522 471 523 471 523 471 523 471 523 471 523 471 523 471 523 472 522 472 522 472 522 472 522 472 522 499 495 498 495 499 496 498 496 498 496 498 522 472 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 499 495 500 494 500 495 499 495 499 495 498 522 2947 3023 8937 520 1467 521 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1495 494 500 494 500 495 500 494 1494 520 1468 521 1467 521 473 521 1468 521 1468 520 1469 519 1469 519 1471 517 1495 494 1495 494 1494 520 474 521 473 521 473 520 1468 521 1468 521 1468 520 474 520 475 519 475 519 500 493 500 494 501 493 501 493 501 493 1495 495 1494 520 474 520 474 520 474 520 475 519 1470 518 475 519 476 518 500 493 501 493 501 493 501 493 1497 492 1497 492 1496 493 1495 518 | ||||
| # | ||||
| name: Cool_hi | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 666 17765 3056 8901 529 492 526 1434 556 465 529 464 530 464 555 438 556 438 555 439 554 440 552 1436 551 443 550 470 524 1465 524 471 523 472 522 1467 521 1467 548 1441 549 1440 548 1440 548 445 549 445 549 445 549 446 548 447 547 470 524 471 523 471 523 473 521 472 522 473 521 473 521 473 522 472 548 446 549 446 548 446 548 447 547 447 547 470 524 447 547 471 523 471 523 471 523 471 523 471 523 475 519 498 496 498 496 498 496 497 498 497 523 1466 524 1443 545 1441 548 1442 546 2947 3023 8935 522 1466 522 472 522 472 522 498 496 498 496 498 496 498 496 498 522 472 523 1466 522 471 523 471 523 1466 523 471 523 1466 523 1467 522 1467 521 1493 496 1493 495 1493 497 497 522 472 523 471 523 471 523 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 495 499 496 498 496 498 522 472 522 472 523 472 521 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 474 520 500 494 500 494 500 495 500 495 499 521 2947 3024 8937 520 1467 521 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1469 520 500 494 500 493 1496 494 1494 495 1494 520 1468 520 473 521 1469 519 1468 521 1469 519 1470 519 1470 519 1495 493 1496 493 1495 495 499 520 474 520 474 520 1469 520 1469 519 1469 519 474 520 475 519 500 494 500 494 500 494 502 492 502 492 502 493 501 493 1496 494 500 519 475 520 475 518 1470 519 475 518 476 518 475 519 476 494 525 493 501 493 501 493 1498 491 1498 491 1497 493 1496 518 | ||||
| # | ||||
| name: Cool_lo | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 664 17763 3082 8874 553 444 552 1433 583 438 529 464 530 464 555 439 555 438 556 438 555 440 553 1435 552 443 550 470 524 1466 523 472 521 472 522 1467 523 1466 549 1439 549 1439 549 1439 550 444 550 445 549 445 549 447 547 470 524 471 523 471 523 472 522 472 522 473 521 472 523 472 522 471 549 446 549 445 549 446 548 446 548 446 548 446 548 446 548 448 546 471 523 471 523 471 523 471 523 475 519 497 497 474 520 475 520 497 497 496 524 471 524 1465 523 1442 547 1442 547 1442 547 2946 3024 8935 522 1466 522 471 523 474 520 474 520 498 496 474 521 497 522 471 523 471 523 1465 523 471 523 471 523 1466 522 471 523 1466 523 1466 523 1493 495 1493 495 1493 496 1492 523 471 523 471 523 471 523 471 523 471 523 471 523 471 523 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 496 498 497 498 497 497 523 472 522 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 499 495 499 495 499 496 499 496 498 496 498 522 2946 3024 8937 521 1467 521 472 522 472 522 473 521 473 521 473 521 473 521 474 520 474 520 1495 493 500 494 499 495 499 495 499 496 1493 521 1468 520 473 521 1468 520 1468 521 1468 520 1469 519 1470 518 1495 494 1495 494 1494 495 499 520 473 521 473 521 1468 520 1469 520 1468 521 474 520 474 520 475 519 475 519 476 518 1496 492 1496 494 1495 495 499 520 1469 520 474 520 474 520 474 519 1470 520 474 520 476 518 476 519 477 517 500 494 500 494 503 491 1497 492 1496 493 1495 519 1470 519 | ||||
| # | ||||
| name: Heat_hi | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 666 17758 3056 8901 528 493 526 1434 580 441 554 439 529 465 554 439 555 439 555 439 554 440 553 1435 552 443 549 470 524 1465 524 472 522 472 521 1467 523 1467 548 1440 549 1440 548 1440 549 445 549 445 549 446 548 446 548 471 523 471 523 471 523 471 523 473 521 473 521 473 521 473 522 472 547 446 549 446 549 445 548 446 548 446 548 446 548 446 548 447 547 471 523 471 523 471 523 471 523 473 521 474 520 473 521 474 521 473 522 473 546 447 548 1441 548 1442 547 1442 547 1442 546 2948 3021 8936 521 1466 522 472 522 498 496 499 494 476 519 476 519 497 497 497 523 472 522 1466 523 471 523 472 522 1466 522 472 522 1466 522 1467 522 1467 522 1493 494 1495 495 1493 522 472 522 472 522 471 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 499 495 499 495 499 495 499 496 498 521 473 522 472 523 472 522 472 522 472 522 473 521 472 522 472 522 472 522 473 521 473 521 474 520 473 521 499 495 500 494 500 495 499 495 499 521 2947 3023 8938 520 1468 520 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1495 494 500 494 500 494 500 495 500 494 1494 520 1468 521 474 520 1468 520 1469 520 1468 520 1469 520 1470 518 1496 493 1496 493 1495 495 499 519 475 520 474 520 1468 520 1469 519 1469 519 474 520 475 519 475 519 476 518 500 494 500 494 1497 492 1497 492 1496 493 1495 519 475 519 475 519 475 519 475 519 475 519 1471 518 476 518 500 494 501 493 501 493 501 493 1498 491 1498 491 1496 518 1472 517 | ||||
| # | ||||
| name: Heat_lo | ||||
| type: raw | ||||
| frequency: 38000 | ||||
| duty_cycle: 0.330000 | ||||
| data: 664 17757 3057 8901 528 469 550 1434 556 465 553 440 529 465 554 439 555 439 555 438 555 440 553 1435 552 443 550 470 524 1466 522 472 522 472 521 1467 523 1466 549 1440 549 1440 548 1440 548 445 549 445 549 445 549 446 548 470 524 471 523 471 523 471 523 472 522 472 522 473 522 472 523 472 548 446 549 445 550 445 548 446 548 446 548 446 548 446 548 447 547 470 524 471 523 471 523 471 523 471 523 474 519 474 521 474 521 473 522 473 546 447 547 1441 548 1442 546 1442 547 1442 546 2947 3023 8935 522 1466 522 472 522 498 496 498 495 499 496 498 496 498 521 473 522 471 523 1466 522 471 523 471 522 1466 523 471 523 1467 522 1467 522 1467 521 1493 495 1494 496 1493 521 473 522 472 522 471 523 472 522 471 523 472 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 499 495 499 495 499 495 499 496 498 522 473 522 472 523 471 522 472 522 472 522 472 522 472 522 472 522 472 522 473 521 473 521 473 521 473 521 499 495 500 494 500 495 499 496 498 522 2947 3023 8937 521 1468 520 473 521 473 521 473 521 473 521 473 521 473 521 474 520 474 520 1470 518 500 494 500 494 500 494 500 494 1494 521 1468 521 473 521 1468 520 1468 520 1469 520 1469 520 1470 518 1495 493 1496 493 1495 494 500 519 475 520 474 520 1469 519 1469 520 1469 519 474 520 474 520 475 519 477 517 500 494 1496 493 1496 493 1496 493 500 495 1495 519 475 518 475 519 475 518 476 518 475 519 1470 519 500 494 500 494 501 493 501 493 501 493 1499 490 1497 493 1497 492 1496 518 | ||||
|  | ||||
| @ -4,9 +4,8 @@ void infrared_scene_edit_delete_done_on_enter(void* context) { | ||||
|     InfraredApp* infrared = context; | ||||
|     Popup* popup = infrared->popup; | ||||
| 
 | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); | ||||
|     popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); | ||||
| 
 | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); | ||||
|     popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_callback(popup, infrared_popup_closed_callback); | ||||
|     popup_set_context(popup, context); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|  | ||||
| @ -4,9 +4,8 @@ void infrared_scene_edit_rename_done_on_enter(void* context) { | ||||
|     InfraredApp* infrared = context; | ||||
|     Popup* popup = infrared->popup; | ||||
| 
 | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); | ||||
| 
 | ||||
|     popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); | ||||
|     popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_callback(popup, infrared_popup_closed_callback); | ||||
|     popup_set_context(popup, context); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|  | ||||
| @ -4,12 +4,12 @@ void infrared_scene_learn_done_on_enter(void* context) { | ||||
|     InfraredApp* infrared = context; | ||||
|     Popup* popup = infrared->popup; | ||||
| 
 | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
| 
 | ||||
|     if(infrared->app_state.is_learning_new_remote) { | ||||
|         popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); | ||||
|         popup_set_header(popup, "New remote\ncreated!", 0, 0, AlignLeft, AlignTop); | ||||
|     } else { | ||||
|         popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); | ||||
|         popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); | ||||
|         popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); | ||||
|     } | ||||
| 
 | ||||
|     popup_set_callback(popup, infrared_popup_closed_callback); | ||||
|  | ||||
| @ -25,10 +25,13 @@ void lfrfid_on_system_start() { | ||||
| 
 | ||||
| static void lfrfid_cli_print_usage() { | ||||
|     printf("Usage:\r\n"); | ||||
|     printf("rfid read <optional: normal | indala>\r\n"); | ||||
|     printf("rfid <write | emulate> <key_type> <key_data>\r\n"); | ||||
|     printf("rfid raw_read <ask | psk> <filename>\r\n"); | ||||
|     printf("rfid raw_emulate <filename>\r\n"); | ||||
|     printf("rfid read <optional: normal | indala>         - read in ASK/PSK mode\r\n"); | ||||
|     printf("rfid <write | emulate> <key_type> <key_data>  - write or emulate a card\r\n"); | ||||
|     printf("rfid raw_read <ask | psk> <filename>          - read and save raw data to a file\r\n"); | ||||
|     printf( | ||||
|         "rfid raw_emulate <filename>                   - emulate raw data (not very useful, but helps debug protocols)\r\n"); | ||||
|     printf( | ||||
|         "rfid raw_analyze <filename>                   - outputs raw data to the cli and tries to decode it (useful for protocol development)\r\n"); | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|  | ||||
| @ -4,8 +4,8 @@ void lfrfid_scene_delete_success_on_enter(void* context) { | ||||
|     LfRfid* app = context; | ||||
|     Popup* popup = app->popup; | ||||
| 
 | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); | ||||
|     popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); | ||||
|     popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_context(popup, app); | ||||
|     popup_set_callback(popup, lfrfid_popup_timeout_callback); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|  | ||||
| @ -7,8 +7,8 @@ void lfrfid_scene_save_success_on_enter(void* context) { | ||||
|     // Clear state of data enter scene
 | ||||
|     scene_manager_set_scene_state(app->scene_manager, LfRfidSceneSaveData, 0); | ||||
| 
 | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop); | ||||
|     popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); | ||||
|     popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_context(popup, app); | ||||
|     popup_set_callback(popup, lfrfid_popup_timeout_callback); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|  | ||||
| @ -57,7 +57,7 @@ bool lfrfid_scene_write_on_event(void* context, SceneManagerEvent event) { | ||||
|             scene_manager_next_scene(app->scene_manager, LfRfidSceneWriteSuccess); | ||||
|             consumed = true; | ||||
|         } else if(event.event == LfRfidEventWriteProtocolCannotBeWritten) { | ||||
|             popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); | ||||
|             popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); | ||||
|             popup_set_header(popup, "Error", 64, 3, AlignCenter, AlignTop); | ||||
|             popup_set_text(popup, "This protocol\ncannot be written", 3, 17, AlignLeft, AlignTop); | ||||
|             notification_message(app->notifications, &sequence_blink_start_red); | ||||
| @ -65,7 +65,7 @@ bool lfrfid_scene_write_on_event(void* context, SceneManagerEvent event) { | ||||
|         } else if( | ||||
|             (event.event == LfRfidEventWriteFobCannotBeWritten) || | ||||
|             (event.event == LfRfidEventWriteTooLongToWrite)) { | ||||
|             popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); | ||||
|             popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); | ||||
|             popup_set_header(popup, "Still trying to write...", 64, 3, AlignCenter, AlignTop); | ||||
|             popup_set_text( | ||||
|                 popup, | ||||
|  | ||||
| @ -4,8 +4,8 @@ void lfrfid_scene_write_success_on_enter(void* context) { | ||||
|     LfRfid* app = context; | ||||
|     Popup* popup = app->popup; | ||||
| 
 | ||||
|     popup_set_header(popup, "Successfully\nwritten!", 94, 3, AlignCenter, AlignTop); | ||||
|     popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57); | ||||
|     popup_set_header(popup, "Success!", 75, 10, AlignLeft, AlignTop); | ||||
|     popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); | ||||
|     popup_set_context(popup, app); | ||||
|     popup_set_callback(popup, lfrfid_popup_timeout_callback); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|  | ||||
| @ -65,6 +65,15 @@ App( | ||||
|     sources=["plugins/supported_cards/troika.c"], | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="washcity_parser", | ||||
|     apptype=FlipperAppType.PLUGIN, | ||||
|     entry_point="washcity_plugin_ep", | ||||
|     targets=["f7"], | ||||
|     requires=["nfc"], | ||||
|     sources=["plugins/supported_cards/washcity.c"], | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="plantain_parser", | ||||
|     apptype=FlipperAppType.PLUGIN, | ||||
| @ -101,6 +110,15 @@ App( | ||||
|     sources=["plugins/supported_cards/umarsh.c"], | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="hid_parser", | ||||
|     apptype=FlipperAppType.PLUGIN, | ||||
|     entry_point="hid_plugin_ep", | ||||
|     targets=["f7"], | ||||
|     requires=["nfc"], | ||||
|     sources=["plugins/supported_cards/hid.c"], | ||||
| ) | ||||
| 
 | ||||
| App( | ||||
|     appid="nfc_start", | ||||
|     targets=["f7"], | ||||
|  | ||||
| @ -18,20 +18,20 @@ void nfc_render_iso15693_3_info( | ||||
| } | ||||
| 
 | ||||
| void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) { | ||||
|     furi_string_cat_printf(str, "UID:"); | ||||
|     furi_string_cat_printf(str, "UID:\n"); | ||||
| 
 | ||||
|     size_t uid_len; | ||||
|     const uint8_t* uid = iso15693_3_get_uid(data, &uid_len); | ||||
| 
 | ||||
|     for(size_t i = 0; i < uid_len; i++) { | ||||
|         furi_string_cat_printf(str, " %02X", uid[i]); | ||||
|         furi_string_cat_printf(str, "%02X ", uid[i]); | ||||
|     } | ||||
| 
 | ||||
|     if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { | ||||
|         const uint16_t block_count = iso15693_3_get_block_count(data); | ||||
|         const uint8_t block_size = iso15693_3_get_block_size(data); | ||||
| 
 | ||||
|         furi_string_cat_printf(str, "Memory: %u bytes\n", block_count * block_size); | ||||
|         furi_string_cat_printf(str, "\nMemory: %u bytes\n", block_count * block_size); | ||||
|         furi_string_cat_printf(str, "(%u blocks x %u bytes)", block_count, block_size); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -23,6 +23,8 @@ static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { | ||||
|     FuriString* temp_str = furi_string_alloc(); | ||||
|     furi_string_cat_printf( | ||||
|         temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); | ||||
|     furi_string_replace(temp_str, "Mifare", "MIFARE"); | ||||
| 
 | ||||
|     nfc_render_mf_classic_info(data, NfcProtocolFormatTypeFull, temp_str); | ||||
| 
 | ||||
|     widget_add_text_scroll_element( | ||||
| @ -119,13 +121,15 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) { | ||||
| static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) { //-V524
 | ||||
|     const NfcDevice* device = instance->nfc_device; | ||||
|     const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); | ||||
| 
 | ||||
|     FuriString* temp_str = furi_string_alloc(); | ||||
|     furi_string_cat_printf( | ||||
|         temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); | ||||
|     furi_string_replace(temp_str, "Mifare", "MIFARE"); | ||||
| 
 | ||||
|     nfc_render_mf_classic_info(data, NfcProtocolFormatTypeShort, temp_str); | ||||
| 
 | ||||
|     widget_add_text_scroll_element( | ||||
| @ -168,7 +172,7 @@ static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) { | ||||
| 
 | ||||
| static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) { | ||||
|     if(event == SubmenuIndexDetectReader) { | ||||
|         scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); | ||||
|         scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm); | ||||
|         dolphin_deed(DolphinDeedNfcDetectReader); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| @ -52,9 +52,15 @@ static NfcCommand | ||||
|     if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) { | ||||
|         nfc_device_set_data( | ||||
|             instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); | ||||
|         view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); | ||||
| 
 | ||||
|         const MfUltralightData* data = | ||||
|             nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); | ||||
|         uint32_t event = (data->pages_read == data->pages_total) ? NfcCustomEventPollerSuccess : | ||||
|                                                                    NfcCustomEventPollerIncomplete; | ||||
|         view_dispatcher_send_custom_event(instance->view_dispatcher, event); | ||||
|         return NfcCommandStop; | ||||
|     } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) { | ||||
|         view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); | ||||
|         nfc_device_set_data( | ||||
|             instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); | ||||
|         const MfUltralightData* data = | ||||
| @ -90,10 +96,55 @@ static NfcCommand | ||||
|     return NfcCommandContinue; | ||||
| } | ||||
| 
 | ||||
| enum { | ||||
|     NfcSceneMfUltralightReadMenuStateCardSearch, | ||||
|     NfcSceneMfUltralightReadMenuStateCardFound, | ||||
| }; | ||||
| 
 | ||||
| static void nfc_scene_read_setup_view(NfcApp* instance) { | ||||
|     Popup* popup = instance->popup; | ||||
|     popup_reset(popup); | ||||
|     uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); | ||||
| 
 | ||||
|     if(state == NfcSceneMfUltralightReadMenuStateCardSearch) { | ||||
|         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); | ||||
|         popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); | ||||
|         popup_set_text( | ||||
|             instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); | ||||
|     } else { | ||||
|         popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); | ||||
|         popup_set_icon(instance->popup, 12, 20, &A_Loading_24); | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); | ||||
| } | ||||
| 
 | ||||
| static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { | ||||
|     bool unlocking = | ||||
|         scene_manager_has_previous_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); | ||||
| 
 | ||||
|     uint32_t state = unlocking ? NfcSceneMfUltralightReadMenuStateCardSearch : | ||||
|                                  NfcSceneMfUltralightReadMenuStateCardFound; | ||||
| 
 | ||||
|     scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); | ||||
| 
 | ||||
|     nfc_scene_read_setup_view(instance); | ||||
|     nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance); | ||||
| } | ||||
| 
 | ||||
| bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, uint32_t event) { | ||||
|     if(event == NfcCustomEventCardDetected) { | ||||
|         scene_manager_set_scene_state( | ||||
|             instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound); | ||||
|         nfc_scene_read_setup_view(instance); | ||||
|     } else if((event == NfcCustomEventPollerIncomplete)) { | ||||
|         notification_message(instance->notifications, &sequence_semi_success); | ||||
|         scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); | ||||
|         dolphin_deed(DolphinDeedNfcReadSuccess); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) { | ||||
|     Submenu* submenu = instance->submenu; | ||||
| 
 | ||||
| @ -179,7 +230,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { | ||||
|     .scene_read = | ||||
|         { | ||||
|             .on_enter = nfc_scene_read_on_enter_mf_ultralight, | ||||
|             .on_event = nfc_protocol_support_common_on_event_empty, | ||||
|             .on_event = nfc_scene_read_on_event_mf_ultralight, | ||||
|         }, | ||||
|     .scene_read_menu = | ||||
|         { | ||||
|  | ||||
| @ -146,8 +146,7 @@ static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) { | ||||
| 
 | ||||
| // SceneRead
 | ||||
| static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) { | ||||
|     popup_set_header( | ||||
|         instance->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop); | ||||
|     popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); | ||||
|     popup_set_icon(instance->popup, 12, 23, &A_Loading_24); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); | ||||
| @ -162,7 +161,7 @@ static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) { | ||||
|     // Start poller with the appropriate callback
 | ||||
|     nfc_protocol_support[protocol]->scene_read.on_enter(instance); | ||||
| 
 | ||||
|     nfc_blink_detect_start(instance); | ||||
|     nfc_blink_read_start(instance); | ||||
| } | ||||
| 
 | ||||
| static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneManagerEvent event) { | ||||
| @ -200,6 +199,10 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana | ||||
|                     instance->scene_manager, NfcSceneDetect); | ||||
|             } | ||||
|             consumed = true; | ||||
|         } else if(event.event == NfcCustomEventCardDetected) { | ||||
|             const NfcProtocol protocol = | ||||
|                 instance->protocols_detected[instance->protocols_detected_selected_idx]; | ||||
|             consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event); | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeBack) { | ||||
|         nfc_poller_stop(instance->poller); | ||||
| @ -388,12 +391,15 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { | ||||
|     nfc_protocol_support[protocol]->scene_saved_menu.on_enter(instance); | ||||
| 
 | ||||
|     // Trailer submenu items
 | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Info", | ||||
|         SubmenuIndexCommonInfo, | ||||
|         nfc_protocol_support_common_submenu_callback, | ||||
|         instance); | ||||
|     if(nfc_has_shadow_file(instance)) { | ||||
|         submenu_add_item( | ||||
|             submenu, | ||||
|             "Restore to Original State", | ||||
|             SubmenuIndexCommonRestore, | ||||
|             nfc_protocol_support_common_submenu_callback, | ||||
|             instance); | ||||
|     } | ||||
| 
 | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Rename", | ||||
| @ -406,15 +412,12 @@ static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) { | ||||
|         SubmenuIndexCommonDelete, | ||||
|         nfc_protocol_support_common_submenu_callback, | ||||
|         instance); | ||||
| 
 | ||||
|     if(nfc_has_shadow_file(instance)) { | ||||
|         submenu_add_item( | ||||
|             submenu, | ||||
|             "Restore Data Changes", | ||||
|             SubmenuIndexCommonRestore, | ||||
|             nfc_protocol_support_common_submenu_callback, | ||||
|             instance); | ||||
|     } | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Info", | ||||
|         SubmenuIndexCommonInfo, | ||||
|         nfc_protocol_support_common_submenu_callback, | ||||
|         instance); | ||||
| 
 | ||||
|     submenu_set_selected_item( | ||||
|         instance->submenu, | ||||
| @ -579,8 +582,14 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { | ||||
| 
 | ||||
|     } else { | ||||
|         widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating"); | ||||
|         furi_string_set( | ||||
|             temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); | ||||
|         if(!furi_string_empty(instance->file_name)) { | ||||
|             furi_string_set(temp_str, instance->file_name); | ||||
|         } else { | ||||
|             furi_string_printf( | ||||
|                 temp_str, | ||||
|                 "Unsaved\n%s", | ||||
|                 nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     widget_add_text_box_element( | ||||
|  | ||||
| @ -223,7 +223,7 @@ void nfc_text_store_clear(NfcApp* nfc) { | ||||
| } | ||||
| 
 | ||||
| void nfc_blink_read_start(NfcApp* nfc) { | ||||
|     notification_message(nfc->notifications, &sequence_blink_start_cyan); | ||||
|     notification_message(nfc->notifications, &sequence_blink_start_yellow); | ||||
| } | ||||
| 
 | ||||
| void nfc_blink_emulate_start(NfcApp* nfc) { | ||||
| @ -231,7 +231,7 @@ void nfc_blink_emulate_start(NfcApp* nfc) { | ||||
| } | ||||
| 
 | ||||
| void nfc_blink_detect_start(NfcApp* nfc) { | ||||
|     notification_message(nfc->notifications, &sequence_blink_start_yellow); | ||||
|     notification_message(nfc->notifications, &sequence_blink_start_cyan); | ||||
| } | ||||
| 
 | ||||
| void nfc_blink_stop(NfcApp* nfc) { | ||||
|  | ||||
							
								
								
									
										153
									
								
								applications/main/nfc/plugins/supported_cards/hid.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								applications/main/nfc/plugins/supported_cards/hid.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,153 @@ | ||||
| #include "nfc_supported_card_plugin.h" | ||||
| 
 | ||||
| #include <flipper_application/flipper_application.h> | ||||
| 
 | ||||
| #include <nfc/nfc_device.h> | ||||
| #include <nfc/helpers/nfc_util.h> | ||||
| #include <nfc/protocols/mf_classic/mf_classic_poller_sync.h> | ||||
| 
 | ||||
| #define TAG "HID" | ||||
| 
 | ||||
| static const uint64_t hid_key = 0x484944204953; | ||||
| 
 | ||||
| bool hid_verify(Nfc* nfc) { | ||||
|     bool verified = false; | ||||
| 
 | ||||
|     do { | ||||
|         const uint8_t verify_sector = 1; | ||||
|         uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); | ||||
|         FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); | ||||
| 
 | ||||
|         MfClassicKey key = {}; | ||||
|         nfc_util_num2bytes(hid_key, COUNT_OF(key.data), key.data); | ||||
| 
 | ||||
|         MfClassicAuthContext auth_ctx = {}; | ||||
|         MfClassicError error = | ||||
|             mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx); | ||||
| 
 | ||||
|         if(error != MfClassicErrorNone) { | ||||
|             FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         verified = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return verified; | ||||
| } | ||||
| 
 | ||||
| static bool hid_read(Nfc* nfc, NfcDevice* device) { | ||||
|     furi_assert(nfc); | ||||
|     furi_assert(device); | ||||
| 
 | ||||
|     bool is_read = false; | ||||
| 
 | ||||
|     MfClassicData* data = mf_classic_alloc(); | ||||
|     nfc_device_copy_data(device, NfcProtocolMfClassic, data); | ||||
| 
 | ||||
|     do { | ||||
|         MfClassicType type = MfClassicType1k; | ||||
|         MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); | ||||
|         if(error != MfClassicErrorNone) break; | ||||
| 
 | ||||
|         data->type = type; | ||||
|         MfClassicDeviceKeys keys = {}; | ||||
|         for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { | ||||
|             nfc_util_num2bytes(hid_key, sizeof(MfClassicKey), keys.key_a[i].data); | ||||
|             FURI_BIT_SET(keys.key_a_mask, i); | ||||
|             nfc_util_num2bytes(hid_key, sizeof(MfClassicKey), keys.key_b[i].data); | ||||
|             FURI_BIT_SET(keys.key_b_mask, i); | ||||
|         } | ||||
| 
 | ||||
|         error = mf_classic_poller_sync_read(nfc, &keys, data); | ||||
|         if(error != MfClassicErrorNone) { | ||||
|             FURI_LOG_W(TAG, "Failed to read data"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         nfc_device_set_data(device, NfcProtocolMfClassic, data); | ||||
| 
 | ||||
|         is_read = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     mf_classic_free(data); | ||||
| 
 | ||||
|     return is_read; | ||||
| } | ||||
| 
 | ||||
| static uint8_t get_bit_length(const uint8_t* half_block) { | ||||
|     uint8_t bitLength = 0; | ||||
|     uint32_t* halves = (uint32_t*)half_block; | ||||
|     if(halves[0] == 0) { | ||||
|         uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); | ||||
|         bitLength = 31 - leading0s; | ||||
|     } else { | ||||
|         uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); | ||||
|         bitLength = 63 - leading0s; | ||||
|     } | ||||
| 
 | ||||
|     return bitLength; | ||||
| } | ||||
| 
 | ||||
| static uint64_t get_pacs_bits(const uint8_t* block, uint8_t bitLength) { | ||||
|     // Remove sentinel bit from credential.  Byteswapping to handle array of bytes vs 64bit value
 | ||||
|     uint64_t sentinel = __builtin_bswap64(1ULL << bitLength); | ||||
|     uint64_t swapped = 0; | ||||
|     memcpy(&swapped, block, sizeof(uint64_t)); | ||||
|     swapped = __builtin_bswap64(swapped ^ sentinel); | ||||
|     FURI_LOG_D(TAG, "PACS: (%d) %016llx", bitLength, swapped); | ||||
|     return swapped; | ||||
| } | ||||
| 
 | ||||
| static bool hid_parse(const NfcDevice* device, FuriString* parsed_data) { | ||||
|     furi_assert(device); | ||||
| 
 | ||||
|     const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); | ||||
| 
 | ||||
|     bool parsed = false; | ||||
| 
 | ||||
|     do { | ||||
|         // verify key
 | ||||
|         const uint8_t verify_sector = 1; | ||||
|         MfClassicSectorTrailer* sec_tr = | ||||
|             mf_classic_get_sector_trailer_by_sector(data, verify_sector); | ||||
|         uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); | ||||
|         if(key != hid_key) break; | ||||
| 
 | ||||
|         // Currently doesn't support bit length > 63
 | ||||
|         const uint8_t* credential_block = data->block[5].data + 8; | ||||
| 
 | ||||
|         uint8_t bitLength = get_bit_length(credential_block); | ||||
|         if(bitLength == 0) break; | ||||
| 
 | ||||
|         uint64_t credential = get_pacs_bits(credential_block, bitLength); | ||||
|         if(credential == 0) break; | ||||
| 
 | ||||
|         furi_string_printf(parsed_data, "\e#HID Card\n%dbit\n%llx", bitLength, credential); | ||||
| 
 | ||||
|         parsed = true; | ||||
| 
 | ||||
|     } while(false); | ||||
| 
 | ||||
|     return parsed; | ||||
| } | ||||
| 
 | ||||
| /* Actual implementation of app<>plugin interface */ | ||||
| static const NfcSupportedCardsPlugin hid_plugin = { | ||||
|     .protocol = NfcProtocolMfClassic, | ||||
|     .verify = hid_verify, | ||||
|     .read = hid_read, | ||||
|     .parse = hid_parse, | ||||
| }; | ||||
| 
 | ||||
| /* Plugin descriptor to comply with basic plugin specification */ | ||||
| static const FlipperAppPluginDescriptor hid_plugin_descriptor = { | ||||
|     .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, | ||||
|     .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, | ||||
|     .entry_point = &hid_plugin, | ||||
| }; | ||||
| 
 | ||||
| /* Plugin entry point - must return a pointer to const descriptor  */ | ||||
| const FlipperAppPluginDescriptor* hid_plugin_ep() { | ||||
|     return &hid_plugin_descriptor; | ||||
| } | ||||
| @ -16,6 +16,35 @@ static bool mykey_has_lockid(const St25tbData* data) { | ||||
|     return (data->blocks[5] & 0xFF) == 0x7F; | ||||
| } | ||||
| 
 | ||||
| static bool check_invalid_low_nibble(uint8_t value) { | ||||
|     uint8_t value_lo = value & 0xF; | ||||
|     return value_lo >= 0xA; | ||||
| } | ||||
| 
 | ||||
| static bool mykey_get_production_date( | ||||
|     const St25tbData* data, | ||||
|     uint16_t* year_ptr, | ||||
|     uint8_t* month_ptr, | ||||
|     uint8_t* day_ptr) { | ||||
|     uint32_t date_block = data->blocks[8]; | ||||
|     uint8_t year = date_block >> 16 & 0xFF; | ||||
|     uint8_t month = date_block >> 8 & 0xFF; | ||||
|     uint8_t day = date_block & 0xFF; | ||||
|     // dates are coded in a peculiar way, the hexadecimal value should in fact be interpreted as a decimal value
 | ||||
|     // so anything in range A-F is invalid.
 | ||||
|     if(day > 0x31 || month > 0x12 || day == 0 || month == 0 || year == 0) { | ||||
|         return false; | ||||
|     } | ||||
|     if(check_invalid_low_nibble(day) || check_invalid_low_nibble(month) || | ||||
|        check_invalid_low_nibble(year) || check_invalid_low_nibble(year >> 4)) { | ||||
|         return false; | ||||
|     } | ||||
|     *year_ptr = year + 0x2000; | ||||
|     *month_ptr = month; | ||||
|     *day_ptr = day; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) { | ||||
|     furi_assert(device); | ||||
|     furi_assert(parsed_data); | ||||
| @ -34,7 +63,10 @@ static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if((data->blocks[8] >> 16 & 0xFF) > 0x31 || (data->blocks[8] >> 8 & 0xFF) > 0x12) { | ||||
|     uint16_t mfg_year; | ||||
|     uint8_t mfg_month, mfg_day; | ||||
| 
 | ||||
|     if(!mykey_get_production_date(data, &mfg_year, &mfg_month, &mfg_day)) { | ||||
|         FURI_LOG_D(TAG, "bad mfg date"); | ||||
|         return false; | ||||
|     } | ||||
| @ -56,13 +88,8 @@ static bool mykey_parse(const NfcDevice* device, FuriString* parsed_data) { | ||||
|     furi_string_cat_printf(parsed_data, "Blank: %s\n", is_blank ? "yes" : "no"); | ||||
|     furi_string_cat_printf(parsed_data, "LockID: %s\n", mykey_has_lockid(data) ? "maybe" : "no"); | ||||
| 
 | ||||
|     uint32_t block8 = data->blocks[8]; | ||||
|     furi_string_cat_printf( | ||||
|         parsed_data, | ||||
|         "Prod. date: %02lX/%02lX/%04lX", | ||||
|         block8 >> 16 & 0xFF, | ||||
|         block8 >> 8 & 0xFF, | ||||
|         0x2000 + (block8 & 0xFF)); | ||||
|         parsed_data, "Prod. date: %02X/%02X/%04X", mfg_day, mfg_month, mfg_year); | ||||
| 
 | ||||
|     if(!is_blank) { | ||||
|         furi_string_cat_printf( | ||||
|  | ||||
| @ -32,8 +32,7 @@ | ||||
| #include <nfc/nfc_device.h> | ||||
| #include <nfc/helpers/nfc_util.h> | ||||
| #include <nfc/protocols/mf_classic/mf_classic_poller_sync.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| #include <furi_hal_rtc.h> | ||||
| 
 | ||||
| #define TAG "Umarsh" | ||||
|  | ||||
							
								
								
									
										193
									
								
								applications/main/nfc/plugins/supported_cards/washcity.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								applications/main/nfc/plugins/supported_cards/washcity.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,193 @@ | ||||
| /*
 | ||||
|  * Parser for WashCity MarkItaly Card (Europe). | ||||
|  * | ||||
|  * Copyright 2023 Filipe Polido (YaBaPT) <polido@gmail.com> | ||||
|  *  | ||||
|  * Based on MetroMoney by Leptoptilos <leptoptilos@icloud.com> | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify it | ||||
|  * under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, but | ||||
|  * WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|  * General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
|  */ | ||||
| #include "nfc_supported_card_plugin.h" | ||||
| 
 | ||||
| #include "protocols/mf_classic/mf_classic.h" | ||||
| #include <flipper_application/flipper_application.h> | ||||
| 
 | ||||
| #include <nfc/nfc_device.h> | ||||
| #include <nfc/helpers/nfc_util.h> | ||||
| #include <nfc/protocols/mf_classic/mf_classic_poller_sync.h> | ||||
| 
 | ||||
| #define TAG "WashCity" | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint64_t a; | ||||
|     uint64_t b; | ||||
| } MfClassicKeyPair; | ||||
| 
 | ||||
| static const MfClassicKeyPair washcity_1k_keys[] = { | ||||
|     {.a = 0xA0A1A2A3A4A5, .b = 0x010155010100}, // Sector 00
 | ||||
|     {.a = 0xC78A3D0E1BCD, .b = 0xFFFFFFFFFFFF}, // Sector 01
 | ||||
|     {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 02
 | ||||
|     {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 03
 | ||||
|     {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 04
 | ||||
|     {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 05
 | ||||
|     {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 06
 | ||||
|     {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 07
 | ||||
|     {.a = 0xC78A3D0E0000, .b = 0xFFFFFFFFFFFF}, // Sector 08
 | ||||
|     {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 09
 | ||||
|     {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 10
 | ||||
|     {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 11
 | ||||
|     {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 12
 | ||||
|     {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 13
 | ||||
|     {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 14
 | ||||
|     {.a = 0x010155010100, .b = 0xFFFFFFFFFFFF}, // Sector 15
 | ||||
| }; | ||||
| 
 | ||||
| static bool washcity_verify(Nfc* nfc) { | ||||
|     bool verified = false; | ||||
| 
 | ||||
|     do { | ||||
|         const uint8_t ticket_sector_number = 0; | ||||
|         const uint8_t ticket_block_number = | ||||
|             mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1; | ||||
|         FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number); | ||||
| 
 | ||||
|         MfClassicKey key = {0}; | ||||
|         nfc_util_num2bytes(washcity_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data); | ||||
| 
 | ||||
|         MfClassicAuthContext auth_context; | ||||
|         MfClassicError error = mf_classic_poller_sync_auth( | ||||
|             nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context); | ||||
|         if(error != MfClassicErrorNone) { | ||||
|             FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         verified = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return verified; | ||||
| } | ||||
| 
 | ||||
| static bool washcity_read(Nfc* nfc, NfcDevice* device) { | ||||
|     furi_assert(nfc); | ||||
|     furi_assert(device); | ||||
| 
 | ||||
|     bool is_read = false; | ||||
| 
 | ||||
|     MfClassicData* data = mf_classic_alloc(); | ||||
|     nfc_device_copy_data(device, NfcProtocolMfClassic, data); | ||||
| 
 | ||||
|     do { | ||||
|         MfClassicType type = MfClassicTypeMini; | ||||
|         MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); | ||||
|         if(error != MfClassicErrorNone) break; | ||||
| 
 | ||||
|         data->type = type; | ||||
|         if(type != MfClassicType1k) break; | ||||
| 
 | ||||
|         MfClassicDeviceKeys keys = { | ||||
|             .key_a_mask = 0, | ||||
|             .key_b_mask = 0, | ||||
|         }; | ||||
|         for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { | ||||
|             nfc_util_num2bytes(washcity_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); | ||||
|             FURI_BIT_SET(keys.key_a_mask, i); | ||||
|             nfc_util_num2bytes(washcity_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); | ||||
|             FURI_BIT_SET(keys.key_b_mask, i); | ||||
|         } | ||||
| 
 | ||||
|         error = mf_classic_poller_sync_read(nfc, &keys, data); | ||||
|         if(error != MfClassicErrorNone) { | ||||
|             FURI_LOG_W(TAG, "Failed to read data"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         nfc_device_set_data(device, NfcProtocolMfClassic, data); | ||||
| 
 | ||||
|         is_read = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     mf_classic_free(data); | ||||
| 
 | ||||
|     return is_read; | ||||
| } | ||||
| 
 | ||||
| static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { | ||||
|     furi_assert(device); | ||||
| 
 | ||||
|     const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); | ||||
| 
 | ||||
|     bool parsed = false; | ||||
| 
 | ||||
|     do { | ||||
|         // Verify key
 | ||||
|         const uint8_t ticket_sector_number = 1; | ||||
|         const uint8_t ticket_block_number = 0; | ||||
| 
 | ||||
|         const MfClassicSectorTrailer* sec_tr = | ||||
|             mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number); | ||||
| 
 | ||||
|         const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); | ||||
|         if(key != washcity_1k_keys[ticket_sector_number].a) break; | ||||
| 
 | ||||
|         // Parse data
 | ||||
|         const uint8_t start_block_num = | ||||
|             mf_classic_get_first_block_num_of_sector(ticket_sector_number); | ||||
| 
 | ||||
|         const uint8_t* block_start_ptr = | ||||
|             &data->block[start_block_num + ticket_block_number].data[0]; | ||||
| 
 | ||||
|         uint32_t balance = nfc_util_bytes2num(block_start_ptr + 2, 2); | ||||
| 
 | ||||
|         uint32_t balance_eur = balance / 100; | ||||
|         uint8_t balance_cents = balance % 100; | ||||
| 
 | ||||
|         size_t uid_len = 0; | ||||
|         const uint8_t* uid = mf_classic_get_uid(data, &uid_len); | ||||
| 
 | ||||
|         // Card Number is printed in HEX (equal to UID)
 | ||||
|         uint64_t card_number = nfc_util_bytes2num(uid, uid_len); | ||||
| 
 | ||||
|         furi_string_printf( | ||||
|             parsed_data, | ||||
|             "\e#WashCity\nCard number: %0*llX\nBalance: %lu.%02u EUR", | ||||
|             uid_len * 2, | ||||
|             card_number, | ||||
|             balance_eur, | ||||
|             balance_cents); | ||||
|         parsed = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return parsed; | ||||
| } | ||||
| 
 | ||||
| /* Actual implementation of app<>plugin interface */ | ||||
| static const NfcSupportedCardsPlugin washcity_plugin = { | ||||
|     .protocol = NfcProtocolMfClassic, | ||||
|     .verify = washcity_verify, | ||||
|     .read = washcity_read, | ||||
|     .parse = washcity_parse, | ||||
| }; | ||||
| 
 | ||||
| /* Plugin descriptor to comply with basic plugin specification */ | ||||
| static const FlipperAppPluginDescriptor washcity_plugin_descriptor = { | ||||
|     .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, | ||||
|     .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, | ||||
|     .entry_point = &washcity_plugin, | ||||
| }; | ||||
| 
 | ||||
| /* Plugin entry point - must return a pointer to const descriptor  */ | ||||
| const FlipperAppPluginDescriptor* washcity_plugin_ep() { | ||||
|     return &washcity_plugin_descriptor; | ||||
| } | ||||
| @ -1316,3 +1316,38 @@ CE99FBC8BD26 | ||||
| # Volgograd (Russia) Volna transport cards keys | ||||
| 2B787A063D5D | ||||
| D37C8F1793F7 | ||||
| 
 | ||||
| # Bandai Namco Passport [fka Banapassport] / Sega Aime Card | ||||
| 6090D00632F5 | ||||
| 019761AA8082 | ||||
| 574343467632 | ||||
| A99164400748 | ||||
| 62742819AD7C | ||||
| CC5075E42BA1 | ||||
| B9DF35A0814C | ||||
| 8AF9C718F23D | ||||
| 58CD5C3673CB | ||||
| FC80E88EB88C | ||||
| 7A3CDAD7C023 | ||||
| 30424C029001 | ||||
| 024E4E44001F | ||||
| ECBBFA57C6AD | ||||
| 4757698143BD | ||||
| 1D30972E6485 | ||||
| F8526D1A8D6D | ||||
| 1300EC8C7E80 | ||||
| F80A65A87FFA | ||||
| DEB06ED4AF8E | ||||
| 4AD96BF28190 | ||||
| 000390014D41 | ||||
| 0800F9917CB0 | ||||
| 730050555253 | ||||
| 4146D4A956C4 | ||||
| 131157FBB126 | ||||
| E69DD9015A43 | ||||
| 337237F254D5 | ||||
| 9A8389F32FBF | ||||
| 7B8FB4A7100B | ||||
| C8382A233993 | ||||
| 7B304F2A12A6 | ||||
| FC9418BF788B | ||||
|  | ||||
| @ -23,6 +23,7 @@ ADD_SCENE(nfc, debug, Debug) | ||||
| ADD_SCENE(nfc, field, Field) | ||||
| ADD_SCENE(nfc, retry_confirm, RetryConfirm) | ||||
| ADD_SCENE(nfc, exit_confirm, ExitConfirm) | ||||
| ADD_SCENE(nfc, save_confirm, SaveConfirm) | ||||
| 
 | ||||
| ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite) | ||||
| ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess) | ||||
|  | ||||
| @ -10,8 +10,8 @@ void nfc_scene_delete_success_on_enter(void* context) { | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Popup* popup = nfc->popup; | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); | ||||
|     popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); | ||||
|     popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, nfc); | ||||
|     popup_set_callback(popup, nfc_scene_delete_success_popup_callback); | ||||
|  | ||||
| @ -17,8 +17,9 @@ void nfc_scene_detect_on_enter(void* context) { | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     popup_reset(instance->popup); | ||||
|     popup_set_header(instance->popup, "Reading", 97, 15, AlignCenter, AlignTop); | ||||
|     popup_set_text( | ||||
|         instance->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop); | ||||
|         instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); | ||||
|     popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); | ||||
|     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); | ||||
| 
 | ||||
|  | ||||
| @ -12,9 +12,8 @@ void nfc_scene_exit_confirm_on_enter(void* context) { | ||||
| 
 | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Exit"); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, "Stay"); | ||||
|     dialog_ex_set_header(dialog_ex, "Exit to NFC Menu?", 64, 11, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_text( | ||||
|         dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_header(dialog_ex, "Exit to NFC Menu?", 64, 0, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_text(dialog_ex, "All unsaved data will be lost", 64, 12, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_context(dialog_ex, nfc); | ||||
|     dialog_ex_set_result_callback(dialog_ex, nfc_scene_exit_confirm_dialog_callback); | ||||
| 
 | ||||
|  | ||||
| @ -24,7 +24,7 @@ void nfc_scene_extra_actions_on_enter(void* context) { | ||||
|         instance); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Mifare Classic Keys", | ||||
|         "MIFARE Classic Keys", | ||||
|         SubmenuIndexMfClassicKeys, | ||||
|         nfc_scene_extra_actions_submenu_callback, | ||||
|         instance); | ||||
|  | ||||
| @ -134,6 +134,13 @@ bool nfc_scene_mf_classic_detect_reader_on_event(void* context, SceneManagerEven | ||||
|             instance->listener = NULL; | ||||
|         } | ||||
|         mfkey32_logger_free(instance->mfkey32_logger); | ||||
|         if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSaveSuccess)) { | ||||
|             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|                 instance->scene_manager, NfcSceneStart); | ||||
|         } else if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneReadSuccess)) { | ||||
|             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|                 instance->scene_manager, NfcSceneReadSuccess); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
|  | ||||
| @ -175,6 +175,16 @@ void nfc_scene_mf_classic_dict_attack_on_enter(void* context) { | ||||
|     nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); | ||||
| } | ||||
| 
 | ||||
| static void nfc_scene_mf_classic_dict_attack_notify_read(NfcApp* instance) { | ||||
|     const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); | ||||
|     bool is_card_fully_read = mf_classic_is_card_read(mfc_data); | ||||
|     if(is_card_fully_read) { | ||||
|         notification_message(instance->notifications, &sequence_success); | ||||
|     } else { | ||||
|         notification_message(instance->notifications, &sequence_semi_success); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) { | ||||
|     NfcApp* instance = context; | ||||
|     bool consumed = false; | ||||
| @ -196,7 +206,7 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent | ||||
|                 nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); | ||||
|                 consumed = true; | ||||
|             } else { | ||||
|                 notification_message(instance->notifications, &sequence_success); | ||||
|                 nfc_scene_mf_classic_dict_attack_notify_read(instance); | ||||
|                 scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); | ||||
|                 dolphin_deed(DolphinDeedNfcReadSuccess); | ||||
|                 consumed = true; | ||||
| @ -225,13 +235,13 @@ bool nfc_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent | ||||
|                     instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); | ||||
|                     nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); | ||||
|                 } else { | ||||
|                     notification_message(instance->notifications, &sequence_success); | ||||
|                     nfc_scene_mf_classic_dict_attack_notify_read(instance); | ||||
|                     scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); | ||||
|                     dolphin_deed(DolphinDeedNfcReadSuccess); | ||||
|                 } | ||||
|                 consumed = true; | ||||
|             } else if(state == DictAttackStateSystemDictInProgress) { | ||||
|                 notification_message(instance->notifications, &sequence_success); | ||||
|                 nfc_scene_mf_classic_dict_attack_notify_read(instance); | ||||
|                 scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); | ||||
|                 dolphin_deed(DolphinDeedNfcReadSuccess); | ||||
|                 consumed = true; | ||||
|  | ||||
| @ -11,7 +11,7 @@ void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) { | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Popup* popup = instance->popup; | ||||
|     popup_set_icon(popup, 72, 16, &I_DolphinCommon_56x48); | ||||
|     popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); | ||||
|     popup_set_header(popup, "Key already exists!", 64, 3, AlignCenter, AlignTop); | ||||
|     popup_set_text( | ||||
|         popup, | ||||
|  | ||||
| @ -18,15 +18,16 @@ void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) { | ||||
|     widget_add_string_multiline_element( | ||||
|         instance->widget, | ||||
|         64, | ||||
|         32, | ||||
|         AlignCenter, | ||||
|         13, | ||||
|         AlignCenter, | ||||
|         AlignTop, | ||||
|         FontSecondary, | ||||
|         "Now use Mfkey32\nto extract keys"); | ||||
|         "Now use Mfkey32 to extract \nkeys: lab.flipper.net/nfc-tools"); | ||||
|     widget_add_icon_element(instance->widget, 50, 39, &I_MFKey_qr_25x25); | ||||
|     widget_add_button_element( | ||||
|         instance->widget, | ||||
|         GuiButtonTypeCenter, | ||||
|         "OK", | ||||
|         GuiButtonTypeRight, | ||||
|         "Finish", | ||||
|         nfc_scene_mf_classic_mfkey_complete_callback, | ||||
|         instance); | ||||
| 
 | ||||
| @ -38,7 +39,7 @@ bool nfc_scene_mf_classic_mfkey_complete_on_event(void* context, SceneManagerEve | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == GuiButtonTypeCenter) { | ||||
|         if(event.event == GuiButtonTypeRight) { | ||||
|             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|                 instance->scene_manager, NfcSceneStart); | ||||
|         } | ||||
|  | ||||
| @ -12,8 +12,8 @@ void nfc_scene_mf_classic_update_initial_success_on_enter(void* context) { | ||||
|     notification_message(instance->notifications, &sequence_success); | ||||
| 
 | ||||
|     Popup* popup = instance->popup; | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom); | ||||
|     popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); | ||||
|     popup_set_header(popup, "Updated", 11, 20, AlignLeft, AlignBottom); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, instance); | ||||
|     popup_set_callback(popup, nfc_scene_mf_classic_update_initial_success_popup_callback); | ||||
|  | ||||
| @ -65,8 +65,9 @@ static void nfc_scene_mf_classic_write_initial_setup_view(NfcApp* instance) { | ||||
|         scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfClassicWriteInitial); | ||||
| 
 | ||||
|     if(state == NfcSceneMfClassicWriteInitialStateCardSearch) { | ||||
|         popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter); | ||||
|         popup_set_text( | ||||
|             instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); | ||||
|             instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter); | ||||
|         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); | ||||
|     } else { | ||||
|         popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); | ||||
|  | ||||
| @ -16,7 +16,7 @@ void nfc_scene_mf_classic_write_initial_fail_on_enter(void* context) { | ||||
| 
 | ||||
|     notification_message(instance->notifications, &sequence_error); | ||||
| 
 | ||||
|     widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); | ||||
|     widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); | ||||
|     widget_add_string_element( | ||||
|         widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); | ||||
|     widget_add_string_multiline_element( | ||||
|  | ||||
| @ -12,8 +12,8 @@ void nfc_scene_mf_classic_write_initial_success_on_enter(void* context) { | ||||
|     notification_message(instance->notifications, &sequence_success); | ||||
| 
 | ||||
|     Popup* popup = instance->popup; | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); | ||||
|     popup_set_header(popup, "Success!", 75, 10, AlignLeft, AlignTop); | ||||
|     popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, instance); | ||||
|     popup_set_callback(popup, nfc_scene_mf_classic_write_initial_success_popup_callback); | ||||
|  | ||||
| @ -16,7 +16,7 @@ void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { | ||||
| 
 | ||||
|     notification_message(instance->notifications, &sequence_error); | ||||
| 
 | ||||
|     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); | ||||
|     widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); | ||||
|     widget_add_string_element( | ||||
|         widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); | ||||
|     widget_add_string_multiline_element( | ||||
|  | ||||
| @ -40,7 +40,7 @@ void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { | ||||
|         dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop); | ||||
|         dialog_ex_set_icon(dialog_ex, 73, 20, &I_DolphinCommon_56x48); | ||||
|         dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); | ||||
|         dialog_ex_set_center_button_text(dialog_ex, "OK"); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -46,8 +46,9 @@ static void nfc_scene_mf_ultralight_write_setup_view(NfcApp* instance) { | ||||
|         scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightWrite); | ||||
| 
 | ||||
|     if(state == NfcSceneMfUltralightWriteStateCardSearch) { | ||||
|         popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter); | ||||
|         popup_set_text( | ||||
|             instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); | ||||
|             instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter); | ||||
|         popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); | ||||
|     } else { | ||||
|         popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); | ||||
|  | ||||
| @ -16,7 +16,7 @@ void nfc_scene_mf_ultralight_write_fail_on_enter(void* context) { | ||||
| 
 | ||||
|     notification_message(instance->notifications, &sequence_error); | ||||
| 
 | ||||
|     widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); | ||||
|     widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); | ||||
|     widget_add_string_element( | ||||
|         widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); | ||||
|     widget_add_string_multiline_element( | ||||
|  | ||||
| @ -12,8 +12,8 @@ void nfc_scene_mf_ultralight_write_success_on_enter(void* context) { | ||||
|     notification_message(instance->notifications, &sequence_success); | ||||
| 
 | ||||
|     Popup* popup = instance->popup; | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); | ||||
|     popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); | ||||
|     popup_set_header(popup, "Successfully\nwritten", 5, 22, AlignLeft, AlignBottom); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, instance); | ||||
|     popup_set_callback(popup, nfc_scene_mf_ultralight_write_success_popup_callback); | ||||
| @ -28,8 +28,11 @@ bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerE | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == NfcCustomEventViewExit) { | ||||
|             bool was_saved = | ||||
|                 scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu); | ||||
| 
 | ||||
|             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|                 instance->scene_manager, NfcSceneSavedMenu); | ||||
|                 instance->scene_manager, was_saved ? NfcSceneSavedMenu : NfcSceneReadSuccess); | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
|  | ||||
| @ -16,7 +16,7 @@ void nfc_scene_mf_ultralight_wrong_card_on_enter(void* context) { | ||||
| 
 | ||||
|     notification_message(instance->notifications, &sequence_error); | ||||
| 
 | ||||
|     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); | ||||
|     widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); | ||||
|     widget_add_string_element( | ||||
|         widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); | ||||
|     widget_add_string_multiline_element( | ||||
|  | ||||
| @ -10,8 +10,8 @@ void nfc_scene_restore_original_on_enter(void* context) { | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Popup* popup = nfc->popup; | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     popup_set_header(popup, "Original file\nrestored", 13, 22, AlignLeft, AlignBottom); | ||||
|     popup_set_icon(popup, 48, 6, &I_DolphinDone_80x58); | ||||
|     popup_set_header(popup, "Original file\nrestored", 5, 22, AlignLeft, AlignBottom); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, nfc); | ||||
|     popup_set_callback(popup, nfc_scene_restore_original_popup_callback); | ||||
|  | ||||
| @ -12,9 +12,8 @@ void nfc_scene_retry_confirm_on_enter(void* context) { | ||||
| 
 | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Retry"); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, "Stay"); | ||||
|     dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_text( | ||||
|         dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 0, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_text(dialog_ex, "All unsaved data will be lost", 64, 12, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_context(dialog_ex, nfc); | ||||
|     dialog_ex_set_result_callback(dialog_ex, nfc_scene_retry_confirm_dialog_callback); | ||||
| 
 | ||||
| @ -29,7 +28,11 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { | ||||
|         if(event.event == DialogExResultRight) { | ||||
|             consumed = scene_manager_previous_scene(nfc->scene_manager); | ||||
|         } else if(event.event == DialogExResultLeft) { | ||||
|             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneDetect)) { | ||||
|             if(scene_manager_has_previous_scene( | ||||
|                    nfc->scene_manager, NfcSceneMfUltralightUnlockWarn)) { | ||||
|                 consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|                     nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); | ||||
|             } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneDetect)) { | ||||
|                 consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|                     nfc->scene_manager, NfcSceneDetect); | ||||
|             } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneRead)) { | ||||
|  | ||||
							
								
								
									
										44
									
								
								applications/main/nfc/scenes/nfc_scene_save_confirm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								applications/main/nfc/scenes/nfc_scene_save_confirm.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| #include "../nfc_app_i.h" | ||||
| 
 | ||||
| void nfc_scene_save_confirm_dialog_callback(DialogExResult result, void* context) { | ||||
|     NfcApp* nfc = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event(nfc->view_dispatcher, result); | ||||
| } | ||||
| 
 | ||||
| void nfc_scene_save_confirm_on_enter(void* context) { | ||||
|     NfcApp* nfc = context; | ||||
|     DialogEx* dialog_ex = nfc->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Skip"); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, "Save"); | ||||
|     dialog_ex_set_header(dialog_ex, "Save the Key?", 64, 0, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_text(dialog_ex, "All unsaved data will be lost", 64, 12, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_context(dialog_ex, nfc); | ||||
|     dialog_ex_set_result_callback(dialog_ex, nfc_scene_save_confirm_dialog_callback); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); | ||||
| } | ||||
| 
 | ||||
| bool nfc_scene_save_confirm_on_event(void* context, SceneManagerEvent event) { | ||||
|     NfcApp* nfc = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == DialogExResultRight) { | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); | ||||
|             consumed = true; | ||||
|         } else if(event.event == DialogExResultLeft) { | ||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void nfc_scene_save_confirm_on_exit(void* context) { | ||||
|     NfcApp* nfc = context; | ||||
| 
 | ||||
|     // Clean view
 | ||||
|     dialog_ex_reset(nfc->dialog_ex); | ||||
| } | ||||
| @ -10,8 +10,8 @@ void nfc_scene_save_success_on_enter(void* context) { | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Popup* popup = nfc->popup; | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); | ||||
|     popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); | ||||
|     popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, nfc); | ||||
|     popup_set_callback(popup, nfc_scene_save_success_popup_callback); | ||||
| @ -28,6 +28,9 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { | ||||
|             if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { | ||||
|                 consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|                     nfc->scene_manager, NfcSceneMfClassicKeys); | ||||
|             } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) { | ||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); | ||||
|                 consumed = true; | ||||
|             } else { | ||||
|                 consumed = scene_manager_search_and_switch_to_another_scene( | ||||
|                     nfc->scene_manager, NfcSceneFileSelect); | ||||
|  | ||||
| @ -32,10 +32,20 @@ void nfc_scene_set_type_on_enter(void* context) { | ||||
|         nfc_protocol_support_common_submenu_callback, | ||||
|         instance); | ||||
| 
 | ||||
|     FuriString* str = furi_string_alloc(); | ||||
|     for(size_t i = 0; i < NfcDataGeneratorTypeNum; i++) { | ||||
|         const char* name = nfc_data_generator_get_name(i); | ||||
|         submenu_add_item(submenu, name, i, nfc_protocol_support_common_submenu_callback, instance); | ||||
|         furi_string_cat_str(str, nfc_data_generator_get_name(i)); | ||||
|         furi_string_replace_str(str, "Mifare", "MIFARE"); | ||||
| 
 | ||||
|         submenu_add_item( | ||||
|             submenu, | ||||
|             furi_string_get_cstr(str), | ||||
|             i, | ||||
|             nfc_protocol_support_common_submenu_callback, | ||||
|             instance); | ||||
|         furi_string_reset(str); | ||||
|     } | ||||
|     furi_string_free(str); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); | ||||
| } | ||||
|  | ||||
| @ -50,6 +50,7 @@ static void detect_reader_draw_callback(Canvas* canvas, void* model) { | ||||
|         if(m->state == DetectReaderStateDone) { | ||||
|             canvas_set_font(canvas, FontPrimary); | ||||
|             canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Completed!"); | ||||
|             canvas_draw_icon(canvas, 24, 23, &I_check_big_20x17); | ||||
|         } else { | ||||
|             canvas_set_font(canvas, FontPrimary); | ||||
|             canvas_draw_str_aligned(canvas, 51, 22, AlignLeft, AlignTop, "Collecting..."); | ||||
|  | ||||
| @ -26,6 +26,7 @@ typedef enum { | ||||
|     //SubGhzCustomEvent
 | ||||
|     SubGhzCustomEventSceneDeleteSuccess = 100, | ||||
|     SubGhzCustomEventSceneDelete, | ||||
|     SubGhzCustomEventSceneDeleteBack, | ||||
|     SubGhzCustomEventSceneDeleteRAW, | ||||
|     SubGhzCustomEventSceneDeleteRAWBack, | ||||
| 
 | ||||
| @ -70,5 +71,6 @@ typedef enum { | ||||
|     SubGhzCustomEventViewTransmitterBack, | ||||
|     SubGhzCustomEventViewTransmitterSendStart, | ||||
|     SubGhzCustomEventViewTransmitterSendStop, | ||||
|     SubGhzCustomEventViewTransmitterSendSave, | ||||
|     SubGhzCustomEventViewTransmitterError, | ||||
| } SubGhzCustomEvent; | ||||
|  | ||||
| @ -72,7 +72,6 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | ||||
|     uint32_t frequency = 0; | ||||
|     float rssi_temp = -127.0f; | ||||
|     uint32_t frequency_temp = 0; | ||||
|     CC1101Status status; | ||||
| 
 | ||||
|     //Start CC1101
 | ||||
|     furi_hal_subghz_reset(); | ||||
| @ -123,9 +122,9 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | ||||
|                     subghz_setting_get_frequency(instance->setting, i)); | ||||
| 
 | ||||
|                 cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); | ||||
|                 do { | ||||
|                     status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); | ||||
|                 } while(status.STATE != CC1101StateIDLE); | ||||
| 
 | ||||
|                 furi_check(cc1101_wait_status_state( | ||||
|                     &furi_hal_spi_bus_handle_subghz, CC1101StateIDLE, 10000)); | ||||
| 
 | ||||
|                 cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); | ||||
|                 furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); | ||||
| @ -168,9 +167,9 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { | ||||
|                     frequency = cc1101_set_frequency(&furi_hal_spi_bus_handle_subghz, i); | ||||
| 
 | ||||
|                     cc1101_calibrate(&furi_hal_spi_bus_handle_subghz); | ||||
|                     do { | ||||
|                         status = cc1101_get_status(&furi_hal_spi_bus_handle_subghz); | ||||
|                     } while(status.STATE != CC1101StateIDLE); | ||||
| 
 | ||||
|                     furi_check(cc1101_wait_status_state( | ||||
|                         &furi_hal_spi_bus_handle_subghz, CC1101StateIDLE, 10000)); | ||||
| 
 | ||||
|                     cc1101_switch_to_rx(&furi_hal_spi_bus_handle_subghz); | ||||
|                     furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz); | ||||
|  | ||||
| @ -6,47 +6,57 @@ void subghz_scene_delete_callback(GuiButtonType result, InputType type, void* co | ||||
|     SubGhz* subghz = context; | ||||
|     if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { | ||||
|         view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneDelete); | ||||
|     } else if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) { | ||||
|         view_dispatcher_send_custom_event( | ||||
|             subghz->view_dispatcher, SubGhzCustomEventSceneDeleteBack); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void subghz_scene_delete_on_enter(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     FuriString* frequency_str; | ||||
|     FuriString* modulation_str; | ||||
|     FuriString* text_out; | ||||
|     FuriString* text; | ||||
|     text_out = furi_string_alloc(); | ||||
|     text = furi_string_alloc(); | ||||
| 
 | ||||
|     path_extract_filename(subghz->file_path, text, true); | ||||
|     furi_string_cat_printf(text_out, "\e#Delete %s?\e#\n", furi_string_get_cstr(text)); | ||||
| 
 | ||||
|     furi_string_reset(text); | ||||
|     subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text); | ||||
| 
 | ||||
|     size_t dot = furi_string_search_char(text, '\r'); | ||||
|     if(dot > 0) { | ||||
|         furi_string_left(text, dot); | ||||
|     } | ||||
|     furi_string_cat_printf(text_out, "%s\n", furi_string_get_cstr(text)); | ||||
| 
 | ||||
|     furi_string_free(text); | ||||
| 
 | ||||
|     frequency_str = furi_string_alloc(); | ||||
|     modulation_str = furi_string_alloc(); | ||||
|     text = furi_string_alloc(); | ||||
| 
 | ||||
|     subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); | ||||
|     widget_add_string_element( | ||||
|         subghz->widget, | ||||
|         78, | ||||
|         0, | ||||
|         AlignLeft, | ||||
|         AlignTop, | ||||
|         FontSecondary, | ||||
|         furi_string_get_cstr(frequency_str)); | ||||
| 
 | ||||
|     widget_add_string_element( | ||||
|         subghz->widget, | ||||
|         113, | ||||
|         0, | ||||
|         AlignLeft, | ||||
|         AlignTop, | ||||
|         FontSecondary, | ||||
|     furi_string_cat_printf( | ||||
|         text_out, | ||||
|         "%s %s", | ||||
|         furi_string_get_cstr(frequency_str), | ||||
|         furi_string_get_cstr(modulation_str)); | ||||
|     subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text); | ||||
|     widget_add_string_multiline_element( | ||||
|         subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text)); | ||||
| 
 | ||||
|     widget_add_text_box_element( | ||||
|         subghz->widget, 0, 0, 128, 54, AlignCenter, AlignTop, furi_string_get_cstr(text_out), false); | ||||
| 
 | ||||
|     furi_string_free(frequency_str); | ||||
|     furi_string_free(modulation_str); | ||||
|     furi_string_free(text); | ||||
|     furi_string_free(text_out); | ||||
| 
 | ||||
|     widget_add_button_element( | ||||
|         subghz->widget, GuiButtonTypeRight, "Delete", subghz_scene_delete_callback, subghz); | ||||
|     widget_add_button_element( | ||||
|         subghz->widget, GuiButtonTypeLeft, "Cancel", subghz_scene_delete_callback, subghz); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); | ||||
| } | ||||
| @ -63,6 +73,8 @@ bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) { | ||||
|                     subghz->scene_manager, SubGhzSceneStart); | ||||
|             } | ||||
|             return true; | ||||
|         } else if(event.event == SubGhzCustomEventSceneDeleteBack) { | ||||
|             return scene_manager_previous_scene(subghz->scene_manager); | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
|  | ||||
| @ -17,48 +17,37 @@ void subghz_scene_delete_raw_on_enter(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     FuriString* frequency_str; | ||||
|     FuriString* modulation_str; | ||||
|     FuriString* text_out; | ||||
|     FuriString* file_name; | ||||
|     text_out = furi_string_alloc(); | ||||
|     file_name = furi_string_alloc(); | ||||
| 
 | ||||
|     path_extract_filename(subghz->file_path, file_name, true); | ||||
|     furi_string_cat_printf( | ||||
|         text_out, "\e#Delete %s?\e#\nRAW signal\n", furi_string_get_cstr(file_name)); | ||||
|     furi_string_free(file_name); | ||||
| 
 | ||||
|     frequency_str = furi_string_alloc(); | ||||
|     modulation_str = furi_string_alloc(); | ||||
|     subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); | ||||
| 
 | ||||
|     char delete_str[SUBGHZ_MAX_LEN_NAME + 16]; | ||||
|     FuriString* file_name; | ||||
|     file_name = furi_string_alloc(); | ||||
|     path_extract_filename(subghz->file_path, file_name, true); | ||||
|     snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", furi_string_get_cstr(file_name)); | ||||
|     furi_string_free(file_name); | ||||
|     furi_string_cat_printf( | ||||
|         text_out, | ||||
|         "%s %s", | ||||
|         furi_string_get_cstr(frequency_str), | ||||
|         furi_string_get_cstr(modulation_str)); | ||||
| 
 | ||||
|     widget_add_text_box_element( | ||||
|         subghz->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, delete_str, false); | ||||
| 
 | ||||
|     widget_add_string_element( | ||||
|         subghz->widget, 38, 25, AlignLeft, AlignTop, FontSecondary, "RAW signal"); | ||||
|     subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); | ||||
|     widget_add_string_element( | ||||
|         subghz->widget, | ||||
|         35, | ||||
|         37, | ||||
|         AlignLeft, | ||||
|         AlignTop, | ||||
|         FontSecondary, | ||||
|         furi_string_get_cstr(frequency_str)); | ||||
| 
 | ||||
|     widget_add_string_element( | ||||
|         subghz->widget, | ||||
|         72, | ||||
|         37, | ||||
|         AlignLeft, | ||||
|         AlignTop, | ||||
|         FontSecondary, | ||||
|         furi_string_get_cstr(modulation_str)); | ||||
|         subghz->widget, 0, 0, 128, 54, AlignCenter, AlignTop, furi_string_get_cstr(text_out), false); | ||||
| 
 | ||||
|     furi_string_free(frequency_str); | ||||
|     furi_string_free(modulation_str); | ||||
|     furi_string_free(text_out); | ||||
| 
 | ||||
|     widget_add_button_element( | ||||
|         subghz->widget, GuiButtonTypeRight, "Delete", subghz_scene_delete_raw_callback, subghz); | ||||
|     widget_add_button_element( | ||||
|         subghz->widget, GuiButtonTypeLeft, "Back", subghz_scene_delete_raw_callback, subghz); | ||||
|         subghz->widget, GuiButtonTypeLeft, "Cancel", subghz_scene_delete_raw_callback, subghz); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); | ||||
| } | ||||
|  | ||||
| @ -12,8 +12,8 @@ void subghz_scene_delete_success_on_enter(void* context) { | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Popup* popup = subghz->popup; | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); | ||||
|     popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_119x62); | ||||
|     popup_set_header(popup, "Deleted", 80, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, subghz); | ||||
|     popup_set_callback(popup, subghz_scene_delete_success_popup_callback); | ||||
|  | ||||
| @ -15,16 +15,16 @@ void subghz_scene_need_saving_callback(GuiButtonType result, InputType type, voi | ||||
| void subghz_scene_need_saving_on_enter(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     widget_add_string_multiline_element( | ||||
|         subghz->widget, 64, 13, AlignCenter, AlignCenter, FontPrimary, "Exit to Sub-GHz Menu?"); | ||||
|     widget_add_string_multiline_element( | ||||
|     widget_add_text_box_element( | ||||
|         subghz->widget, | ||||
|         64, | ||||
|         32, | ||||
|         0, | ||||
|         0, | ||||
|         128, | ||||
|         54, | ||||
|         AlignCenter, | ||||
|         AlignCenter, | ||||
|         FontSecondary, | ||||
|         "All unsaved data\nwill be lost!"); | ||||
|         AlignTop, | ||||
|         "\e#Exit to Sub-GHz Menu?\e#\nAll unsaved data will be lost", | ||||
|         false); | ||||
| 
 | ||||
|     widget_add_button_element( | ||||
|         subghz->widget, GuiButtonTypeRight, "Stay", subghz_scene_need_saving_callback, subghz); | ||||
|  | ||||
| @ -1,20 +1,11 @@ | ||||
| #include "../subghz_i.h" | ||||
| #include "../helpers/subghz_custom_event.h" | ||||
| #include "../views/transmitter.h" | ||||
| 
 | ||||
| void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) { | ||||
| void subghz_scene_receiver_info_callback(SubGhzCustomEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     if((result == GuiButtonTypeCenter) && (type == InputTypePress)) { | ||||
|         view_dispatcher_send_custom_event( | ||||
|             subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoTxStart); | ||||
|     } else if((result == GuiButtonTypeCenter) && (type == InputTypeRelease)) { | ||||
|         view_dispatcher_send_custom_event( | ||||
|             subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoTxStop); | ||||
|     } else if((result == GuiButtonTypeRight) && (type == InputTypeShort)) { | ||||
|         view_dispatcher_send_custom_event( | ||||
|             subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoSave); | ||||
|     } | ||||
|     view_dispatcher_send_custom_event(subghz->view_dispatcher, event); | ||||
| } | ||||
| 
 | ||||
| static bool subghz_scene_receiver_info_update_parser(void* context) { | ||||
| @ -37,6 +28,28 @@ static bool subghz_scene_receiver_info_update_parser(void* context) { | ||||
|             preset->data, | ||||
|             preset->data_size); | ||||
| 
 | ||||
|         FuriString* key_str = furi_string_alloc(); | ||||
|         FuriString* frequency_str = furi_string_alloc(); | ||||
|         FuriString* modulation_str = furi_string_alloc(); | ||||
| 
 | ||||
|         subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), key_str); | ||||
|         subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); | ||||
|         subghz_view_transmitter_add_data_to_show( | ||||
|             subghz->subghz_transmitter, | ||||
|             furi_string_get_cstr(key_str), | ||||
|             furi_string_get_cstr(frequency_str), | ||||
|             furi_string_get_cstr(modulation_str), | ||||
|             subghz_txrx_protocol_is_transmittable(subghz->txrx, true)); | ||||
| 
 | ||||
|         furi_string_free(frequency_str); | ||||
|         furi_string_free(modulation_str); | ||||
|         furi_string_free(key_str); | ||||
| 
 | ||||
|         subghz_view_transmitter_set_radio_device_type( | ||||
|             subghz->subghz_transmitter, subghz_txrx_radio_device_get(subghz->txrx)); | ||||
|         subghz_view_transmitter_set_model_type( | ||||
|             subghz->subghz_transmitter, SubGhzViewTransmitterModelTypeInfo); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| @ -44,67 +57,23 @@ static bool subghz_scene_receiver_info_update_parser(void* context) { | ||||
| 
 | ||||
| void subghz_scene_receiver_info_on_enter(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     if(subghz_scene_receiver_info_update_parser(subghz)) { | ||||
|         FuriString* frequency_str = furi_string_alloc(); | ||||
|         FuriString* modulation_str = furi_string_alloc(); | ||||
|         FuriString* text = furi_string_alloc(); | ||||
| 
 | ||||
|         subghz_txrx_get_frequency_and_modulation(subghz->txrx, frequency_str, modulation_str); | ||||
|         widget_add_string_element( | ||||
|             subghz->widget, | ||||
|             78, | ||||
|             0, | ||||
|             AlignLeft, | ||||
|             AlignTop, | ||||
|             FontSecondary, | ||||
|             furi_string_get_cstr(frequency_str)); | ||||
| 
 | ||||
|         widget_add_string_element( | ||||
|             subghz->widget, | ||||
|             113, | ||||
|             0, | ||||
|             AlignLeft, | ||||
|             AlignTop, | ||||
|             FontSecondary, | ||||
|             furi_string_get_cstr(modulation_str)); | ||||
|         subghz_protocol_decoder_base_get_string(subghz_txrx_get_decoder(subghz->txrx), text); | ||||
|         widget_add_string_multiline_element( | ||||
|             subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(text)); | ||||
| 
 | ||||
|         furi_string_free(frequency_str); | ||||
|         furi_string_free(modulation_str); | ||||
|         furi_string_free(text); | ||||
| 
 | ||||
|         if(subghz_txrx_protocol_is_serializable(subghz->txrx)) { | ||||
|             widget_add_button_element( | ||||
|                 subghz->widget, | ||||
|                 GuiButtonTypeRight, | ||||
|                 "Save", | ||||
|                 subghz_scene_receiver_info_callback, | ||||
|                 subghz); | ||||
|         } | ||||
|         if(subghz_txrx_protocol_is_transmittable(subghz->txrx, true)) { | ||||
|             widget_add_button_element( | ||||
|                 subghz->widget, | ||||
|                 GuiButtonTypeCenter, | ||||
|                 "Send", | ||||
|                 subghz_scene_receiver_info_callback, | ||||
|                 subghz); | ||||
|         } | ||||
|     } else { | ||||
|         widget_add_icon_element(subghz->widget, 37, 15, &I_DolphinCommon_56x48); | ||||
|         widget_add_string_element( | ||||
|             subghz->widget, 13, 8, AlignLeft, AlignBottom, FontSecondary, "Error history parse."); | ||||
|         view_dispatcher_send_custom_event( | ||||
|             subghz->view_dispatcher, SubGhzCustomEventSceneShowErrorSub); | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); | ||||
|     subghz_view_transmitter_set_callback( | ||||
|         subghz->subghz_transmitter, subghz_scene_receiver_info_callback, subghz); | ||||
| 
 | ||||
|     subghz->state_notifications = SubGhzNotificationStateIDLE; | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTransmitter); | ||||
| } | ||||
| 
 | ||||
| bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) { | ||||
|     SubGhz* subghz = context; | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == SubGhzCustomEventSceneReceiverInfoTxStart) { | ||||
|         if(event.event == SubGhzCustomEventViewTransmitterSendStart) { | ||||
|             if(!subghz_scene_receiver_info_update_parser(subghz)) { | ||||
|                 return false; | ||||
|             } | ||||
| @ -120,7 +89,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) | ||||
|                 subghz->state_notifications = SubGhzNotificationStateTx; | ||||
|             } | ||||
|             return true; | ||||
|         } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) { | ||||
|         } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { | ||||
|             //CC1101 Stop Tx -> Start RX
 | ||||
|             subghz->state_notifications = SubGhzNotificationStateIDLE; | ||||
| 
 | ||||
| @ -129,7 +98,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) | ||||
|             subghz_txrx_hopper_unpause(subghz->txrx); | ||||
|             subghz->state_notifications = SubGhzNotificationStateRx; | ||||
|             return true; | ||||
|         } else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) { | ||||
|         } else if(event.event == SubGhzCustomEventViewTransmitterSendSave) { | ||||
|             //CC1101 Stop RX -> Save
 | ||||
|             subghz->state_notifications = SubGhzNotificationStateIDLE; | ||||
|             subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF); | ||||
| @ -144,7 +113,11 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) | ||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); | ||||
|             } | ||||
|             return true; | ||||
|         } else if(event.event == SubGhzCustomEventSceneShowErrorSub) { | ||||
|             furi_string_set(subghz->error_str, "Error history parse."); | ||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); | ||||
|         } | ||||
| 
 | ||||
|     } else if(event.type == SceneManagerEventTypeTick) { | ||||
|         if(subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF) { | ||||
|             subghz_txrx_hopper_update(subghz->txrx); | ||||
|  | ||||
| @ -6,12 +6,13 @@ void subghz_scene_region_info_on_enter(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     const FuriHalRegion* const region = furi_hal_region_get(); | ||||
|     FuriString* buffer = furi_string_alloc(); | ||||
| 
 | ||||
|     if(region) { | ||||
|         furi_string_cat_printf(buffer, "Region: %s,  bands:\n", region->country_code); | ||||
|         furi_string_cat_printf(buffer, "Region: %s\nBands:\n", region->country_code); | ||||
|         for(uint16_t i = 0; i < region->bands_count; ++i) { | ||||
|             furi_string_cat_printf( | ||||
|                 buffer, | ||||
|                 "   %lu-%lu kHz\n", | ||||
|                 "%lu-%lu kHz\n", | ||||
|                 region->bands[i].start / 1000, | ||||
|                 region->bands[i].end / 1000); | ||||
|         } | ||||
| @ -20,7 +21,10 @@ void subghz_scene_region_info_on_enter(void* context) { | ||||
|     } | ||||
| 
 | ||||
|     widget_add_string_multiline_element( | ||||
|         subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(buffer)); | ||||
|         subghz->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Region Information"); | ||||
| 
 | ||||
|     widget_add_string_multiline_element( | ||||
|         subghz->widget, 0, 13, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(buffer)); | ||||
| 
 | ||||
|     furi_string_free(buffer); | ||||
|     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget); | ||||
|  | ||||
| @ -132,7 +132,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { | ||||
|                     scene_manager_set_scene_state( | ||||
|                         subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); | ||||
|                 } else { | ||||
|                     subghz_file_name_clear(subghz); | ||||
|                     furi_string_reset(subghz->file_path_tmp); | ||||
|                 } | ||||
| 
 | ||||
|                 scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess); | ||||
|  | ||||
| @ -11,8 +11,8 @@ void subghz_scene_save_success_on_enter(void* context) { | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Popup* popup = subghz->popup; | ||||
|     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||
|     popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); | ||||
|     popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58); | ||||
|     popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, subghz); | ||||
|     popup_set_callback(popup, subghz_scene_save_success_popup_callback); | ||||
|  | ||||
| @ -22,5 +22,7 @@ bool subghz_scene_saved_on_event(void* context, SceneManagerEvent event) { | ||||
| } | ||||
| 
 | ||||
| void subghz_scene_saved_on_exit(void* context) { | ||||
|     SubGhz* subghz = context; | ||||
|     scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneSavedMenu, 0); | ||||
|     UNUSED(context); | ||||
| } | ||||
|  | ||||
| @ -11,7 +11,7 @@ void subghz_scene_show_error_sub_on_enter(void* context) { | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Popup* popup = subghz->popup; | ||||
|     popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); | ||||
|     popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); | ||||
|     popup_set_header(popup, furi_string_get_cstr(subghz->error_str), 14, 15, AlignLeft, AlignTop); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, subghz); | ||||
|  | ||||
| @ -21,7 +21,7 @@ void subghz_scene_show_only_rx_on_enter(void* context) { | ||||
| 
 | ||||
|     popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop); | ||||
|     popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop); | ||||
|     popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48); | ||||
|     popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); | ||||
| 
 | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, subghz); | ||||
|  | ||||
| @ -37,6 +37,8 @@ bool subghz_scene_transmitter_update_data_show(void* context) { | ||||
|     } | ||||
|     subghz_view_transmitter_set_radio_device_type( | ||||
|         subghz->subghz_transmitter, subghz_txrx_radio_device_get(subghz->txrx)); | ||||
|     subghz_view_transmitter_set_model_type( | ||||
|         subghz->subghz_transmitter, SubGhzViewTransmitterModelTypeTx); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -49,6 +49,28 @@ static void subghz_cli_radio_device_power_off() { | ||||
|     if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg(); | ||||
| } | ||||
| 
 | ||||
| static SubGhzEnvironment* subghz_cli_environment_init(void) { | ||||
|     SubGhzEnvironment* environment = subghz_environment_alloc(); | ||||
|     if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME)) { | ||||
|         printf("Load_keystore keeloq_mfcodes \033[0;32mOK\033[0m\r\n"); | ||||
|     } else { | ||||
|         printf("Load_keystore keeloq_mfcodes \033[0;31mERROR\033[0m\r\n"); | ||||
|     } | ||||
|     if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME)) { | ||||
|         printf("Load_keystore keeloq_mfcodes_user \033[0;32mOK\033[0m\r\n"); | ||||
|     } else { | ||||
|         printf("Load_keystore keeloq_mfcodes_user \033[0;33mAbsent\033[0m\r\n"); | ||||
|     } | ||||
|     subghz_environment_set_came_atomo_rainbow_table_file_name( | ||||
|         environment, SUBGHZ_CAME_ATOMO_DIR_NAME); | ||||
|     subghz_environment_set_alutech_at_4n_rainbow_table_file_name( | ||||
|         environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); | ||||
|     subghz_environment_set_nice_flor_s_rainbow_table_file_name( | ||||
|         environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); | ||||
|     subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); | ||||
|     return environment; | ||||
| } | ||||
| 
 | ||||
| void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { | ||||
|     UNUSED(context); | ||||
|     uint32_t frequency = 433920000; | ||||
| @ -324,16 +346,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { | ||||
|         furi_stream_buffer_alloc(sizeof(LevelDuration) * 1024, sizeof(LevelDuration)); | ||||
|     furi_check(instance->stream); | ||||
| 
 | ||||
|     SubGhzEnvironment* environment = subghz_environment_alloc(); | ||||
|     subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME); | ||||
|     subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME); | ||||
|     subghz_environment_set_came_atomo_rainbow_table_file_name( | ||||
|         environment, SUBGHZ_CAME_ATOMO_DIR_NAME); | ||||
|     subghz_environment_set_alutech_at_4n_rainbow_table_file_name( | ||||
|         environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); | ||||
|     subghz_environment_set_nice_flor_s_rainbow_table_file_name( | ||||
|         environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); | ||||
|     subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); | ||||
|     SubGhzEnvironment* environment = subghz_cli_environment_init(); | ||||
| 
 | ||||
|     SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); | ||||
|     subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable); | ||||
| @ -517,25 +530,7 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { | ||||
|         // Allocate context
 | ||||
|         SubGhzCliCommandRx* instance = malloc(sizeof(SubGhzCliCommandRx)); | ||||
| 
 | ||||
|         SubGhzEnvironment* environment = subghz_environment_alloc(); | ||||
|         if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_NAME)) { | ||||
|             printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes \033[0;32mOK\033[0m\r\n"); | ||||
|         } else { | ||||
|             printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes \033[0;31mERROR\033[0m\r\n"); | ||||
|         } | ||||
|         if(subghz_environment_load_keystore(environment, SUBGHZ_KEYSTORE_DIR_USER_NAME)) { | ||||
|             printf("SubGhz decode_raw: Load_keystore keeloq_mfcodes_user \033[0;32mOK\033[0m\r\n"); | ||||
|         } else { | ||||
|             printf( | ||||
|                 "SubGhz decode_raw: Load_keystore keeloq_mfcodes_user \033[0;31mERROR\033[0m\r\n"); | ||||
|         } | ||||
|         subghz_environment_set_came_atomo_rainbow_table_file_name( | ||||
|             environment, SUBGHZ_CAME_ATOMO_DIR_NAME); | ||||
|         subghz_environment_set_alutech_at_4n_rainbow_table_file_name( | ||||
|             environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME); | ||||
|         subghz_environment_set_nice_flor_s_rainbow_table_file_name( | ||||
|             environment, SUBGHZ_NICE_FLOR_S_DIR_NAME); | ||||
|         subghz_environment_set_protocol_registry(environment, (void*)&subghz_protocol_registry); | ||||
|         SubGhzEnvironment* environment = subghz_cli_environment_init(); | ||||
| 
 | ||||
|         SubGhzReceiver* receiver = subghz_receiver_alloc_init(environment); | ||||
|         subghz_receiver_set_filter(receiver, SubGhzProtocolFlag_Decodable); | ||||
| @ -580,6 +575,262 @@ void subghz_cli_command_decode_raw(Cli* cli, FuriString* args, void* context) { | ||||
|     furi_string_free(file_name); | ||||
| } | ||||
| 
 | ||||
| static FuriHalSubGhzPreset subghz_cli_get_preset_name(const char* preset_name) { | ||||
|     FuriHalSubGhzPreset preset = FuriHalSubGhzPresetIDLE; | ||||
|     if(!strcmp(preset_name, "FuriHalSubGhzPresetOok270Async")) { | ||||
|         preset = FuriHalSubGhzPresetOok270Async; | ||||
|     } else if(!strcmp(preset_name, "FuriHalSubGhzPresetOok650Async")) { | ||||
|         preset = FuriHalSubGhzPresetOok650Async; | ||||
|     } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev238Async")) { | ||||
|         preset = FuriHalSubGhzPreset2FSKDev238Async; | ||||
|     } else if(!strcmp(preset_name, "FuriHalSubGhzPreset2FSKDev476Async")) { | ||||
|         preset = FuriHalSubGhzPreset2FSKDev476Async; | ||||
|     } else if(!strcmp(preset_name, "FuriHalSubGhzPresetCustom")) { | ||||
|         preset = FuriHalSubGhzPresetCustom; | ||||
|     } else { | ||||
|         printf("subghz tx_from_file: unknown preset"); | ||||
|     } | ||||
|     return preset; | ||||
| } | ||||
| 
 | ||||
| void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) { // -V524
 | ||||
|     UNUSED(context); | ||||
|     FuriString* file_name; | ||||
|     file_name = furi_string_alloc(); | ||||
|     furi_string_set(file_name, ANY_PATH("subghz/test.sub")); | ||||
|     uint32_t repeat = 10; | ||||
|     uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT
 | ||||
| 
 | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
|     FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); | ||||
|     FlipperFormat* fff_data_raw = flipper_format_string_alloc(); | ||||
|     FuriString* temp_str; | ||||
|     temp_str = furi_string_alloc(); | ||||
|     uint32_t temp_data32; | ||||
|     bool check_file = false; | ||||
|     const SubGhzDevice* device = NULL; | ||||
| 
 | ||||
|     uint32_t frequency = 0; | ||||
|     SubGhzTransmitter* transmitter = NULL; | ||||
| 
 | ||||
|     subghz_devices_init(); | ||||
| 
 | ||||
|     SubGhzEnvironment* environment = subghz_cli_environment_init(); | ||||
| 
 | ||||
|     do { | ||||
|         if(furi_string_size(args)) { | ||||
|             if(!args_read_string_and_trim(args, file_name)) { | ||||
|                 cli_print_usage( | ||||
|                     "subghz tx_from_file: ", | ||||
|                     "<file_name: path_file> <Repeat count> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>", | ||||
|                     furi_string_get_cstr(args)); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(furi_string_size(args)) { | ||||
|             int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &repeat, &device_ind); | ||||
|             if(ret != 2) { | ||||
|                 printf("sscanf returned %d, repeat: %lu device: %lu\r\n", ret, repeat, device_ind); | ||||
|                 cli_print_usage( | ||||
|                     "subghz tx_from_file:", | ||||
|                     "<file_name: path_file> <Repeat count> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>", | ||||
|                     furi_string_get_cstr(args)); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         device = subghz_cli_command_get_device(&device_ind); | ||||
|         if(device == NULL) { | ||||
|             printf("subghz tx_from_file: \033[0;31mError device not found\033[0m\r\n"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!flipper_format_file_open_existing(fff_data_file, furi_string_get_cstr(file_name))) { | ||||
|             printf( | ||||
|                 "subghz tx_from_file: \033[0;31mError open file\033[0m %s\r\n", | ||||
|                 furi_string_get_cstr(file_name)); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { | ||||
|             printf("subghz tx_from_file: \033[0;31mMissing or incorrect header\033[0m\r\n"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || | ||||
|             (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && | ||||
|            temp_data32 == SUBGHZ_KEY_FILE_VERSION) { | ||||
|         } else { | ||||
|             printf("subghz tx_from_file: \033[0;31mType or version mismatch\033[0m\r\n"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         //Load frequency
 | ||||
|         if(!flipper_format_read_uint32(fff_data_file, "Frequency", &frequency, 1)) { | ||||
|             printf("subghz tx_from_file: \033[0;31mMissing Frequency\033[0m\r\n"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!subghz_devices_is_frequency_valid(device, frequency)) { | ||||
|             printf("subghz tx_from_file: \033[0;31mFrequency not supported\033[0m\r\n"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         //Load preset
 | ||||
|         if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { | ||||
|             printf("subghz tx_from_file: \033[0;31mMissing Preset\033[0m\r\n"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         subghz_devices_begin(device); | ||||
|         subghz_devices_reset(device); | ||||
| 
 | ||||
|         if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { | ||||
|             uint8_t* custom_preset_data; | ||||
|             uint32_t custom_preset_data_size; | ||||
|             if(!flipper_format_get_value_count(fff_data_file, "Custom_preset_data", &temp_data32)) | ||||
|                 break; | ||||
|             if(!temp_data32 || (temp_data32 % 2)) { | ||||
|                 printf("subghz tx_from_file: \033[0;31mCustom_preset_data size error\033[0m\r\n"); | ||||
|                 break; | ||||
|             } | ||||
|             custom_preset_data_size = sizeof(uint8_t) * temp_data32; | ||||
|             custom_preset_data = malloc(custom_preset_data_size); | ||||
|             if(!flipper_format_read_hex( | ||||
|                    fff_data_file, | ||||
|                    "Custom_preset_data", | ||||
|                    custom_preset_data, | ||||
|                    custom_preset_data_size)) { | ||||
|                 printf("subghz tx_from_file: \033[0;31mCustom_preset_data read error\033[0m\r\n"); | ||||
|                 break; | ||||
|             } | ||||
|             subghz_devices_load_preset( | ||||
|                 device, | ||||
|                 subghz_cli_get_preset_name(furi_string_get_cstr(temp_str)), | ||||
|                 custom_preset_data); | ||||
|             free(custom_preset_data); | ||||
|         } else { | ||||
|             subghz_devices_load_preset( | ||||
|                 device, subghz_cli_get_preset_name(furi_string_get_cstr(temp_str)), NULL); | ||||
|         } | ||||
| 
 | ||||
|         subghz_devices_set_frequency(device, frequency); | ||||
| 
 | ||||
|         //Load protocol
 | ||||
|         if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) { | ||||
|             printf("subghz tx_from_file: \033[0;31mMissing protocol\033[0m\r\n"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         SubGhzProtocolStatus status; | ||||
|         bool is_init_protocol = true; | ||||
|         if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { // if RAW protocol
 | ||||
|             subghz_protocol_raw_gen_fff_data( | ||||
|                 fff_data_raw, furi_string_get_cstr(file_name), subghz_devices_get_name(device)); | ||||
| 
 | ||||
|             transmitter = | ||||
|                 subghz_transmitter_alloc_init(environment, furi_string_get_cstr(temp_str)); | ||||
|             if(transmitter == NULL) { | ||||
|                 printf("subghz tx_from_file: \033[0;31mError transmitter\033[0m\r\n"); | ||||
|                 is_init_protocol = false; | ||||
|             } | ||||
| 
 | ||||
|             if(is_init_protocol) { | ||||
|                 status = subghz_transmitter_deserialize(transmitter, fff_data_raw); | ||||
|                 if(status != SubGhzProtocolStatusOk) { | ||||
|                     printf( | ||||
|                         "subghz tx_from_file: \033[0;31mError deserialize protocol\033[0m %d\r\n", | ||||
|                         status); | ||||
|                     is_init_protocol = false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|         } else { //if not RAW protocol
 | ||||
|             flipper_format_insert_or_update_uint32(fff_data_file, "Repeat", &repeat, 1); | ||||
| 
 | ||||
|             transmitter = | ||||
|                 subghz_transmitter_alloc_init(environment, furi_string_get_cstr(temp_str)); | ||||
|             if(transmitter == NULL) { | ||||
|                 printf("subghz tx_from_file: \033[0;31mError transmitter\033[0m\r\n"); | ||||
|                 is_init_protocol = false; | ||||
|             } | ||||
|             if(is_init_protocol) { | ||||
|                 status = subghz_transmitter_deserialize(transmitter, fff_data_file); | ||||
|                 if(status != SubGhzProtocolStatusOk) { | ||||
|                     printf( | ||||
|                         "subghz tx_from_file: \033[0;31mError deserialize protocol\033[0m %d\r\n", | ||||
|                         status); | ||||
|                     is_init_protocol = false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             flipper_format_delete_key(fff_data_file, "Repeat"); | ||||
|         } | ||||
| 
 | ||||
|         if(is_init_protocol) { | ||||
|             check_file = true; | ||||
|         } else { | ||||
|             subghz_devices_sleep(device); | ||||
|             subghz_devices_end(device); | ||||
|             subghz_transmitter_free(transmitter); | ||||
|         } | ||||
| 
 | ||||
|     } while(false); | ||||
| 
 | ||||
|     flipper_format_free(fff_data_file); | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
| 
 | ||||
|     if(check_file) { | ||||
|         furi_hal_power_suppress_charge_enter(); | ||||
| 
 | ||||
|         printf( | ||||
|             "Listening at \033[0;33m%s\033[0m. Frequency=%lu, Protocol=%s\r\n\r\nPress CTRL+C to stop\r\n\r\n", | ||||
|             furi_string_get_cstr(file_name), | ||||
|             frequency, | ||||
|             furi_string_get_cstr(temp_str)); | ||||
|         do { | ||||
|             //delay in downloading files and other preparatory processes
 | ||||
|             furi_delay_ms(200); | ||||
|             if(subghz_devices_start_async_tx(device, subghz_transmitter_yield, transmitter)) { | ||||
|                 while( | ||||
|                     !(subghz_devices_is_async_complete_tx(device) || | ||||
|                       cli_cmd_interrupt_received(cli))) { | ||||
|                     printf("."); | ||||
|                     fflush(stdout); | ||||
|                     furi_delay_ms(333); | ||||
|                 } | ||||
|                 subghz_devices_stop_async_tx(device); | ||||
| 
 | ||||
|             } else { | ||||
|                 printf("Transmission on this frequency is restricted in your region\r\n"); | ||||
|             } | ||||
| 
 | ||||
|             if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) { | ||||
|                 subghz_transmitter_stop(transmitter); | ||||
|                 repeat--; | ||||
|                 if(!cli_cmd_interrupt_received(cli) && repeat) | ||||
|                     subghz_transmitter_deserialize(transmitter, fff_data_raw); | ||||
|             } | ||||
| 
 | ||||
|         } while(!cli_cmd_interrupt_received(cli) && | ||||
|                 (repeat && !strcmp(furi_string_get_cstr(temp_str), "RAW"))); | ||||
| 
 | ||||
|         subghz_devices_sleep(device); | ||||
|         subghz_devices_end(device); | ||||
|         subghz_cli_radio_device_power_off(); | ||||
| 
 | ||||
|         furi_hal_power_suppress_charge_exit(); | ||||
| 
 | ||||
|         subghz_transmitter_free(transmitter); | ||||
|     } | ||||
|     flipper_format_free(fff_data_raw); | ||||
|     furi_string_free(file_name); | ||||
|     furi_string_free(temp_str); | ||||
|     subghz_devices_deinit(); | ||||
|     subghz_environment_free(environment); | ||||
| } | ||||
| 
 | ||||
| static void subghz_cli_command_print_usage() { | ||||
|     printf("Usage:\r\n"); | ||||
|     printf("subghz <cmd> <args>\r\n"); | ||||
| @ -592,11 +843,13 @@ static void subghz_cli_command_print_usage() { | ||||
|     printf("\trx <frequency:in Hz> <device: 0 - CC1101_INT, 1 - CC1101_EXT>\t - Receive\r\n"); | ||||
|     printf("\trx_raw <frequency:in Hz>\t - Receive RAW\r\n"); | ||||
|     printf("\tdecode_raw <file_name: path_RAW_file>\t - Testing\r\n"); | ||||
|     printf( | ||||
|         "\ttx_from_file <file_name: path_file> <repeat: count> <device: 0 - CC1101_INT, 1 - CC1101_EXT>\t - Transmitting from file\r\n"); | ||||
| 
 | ||||
|     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { | ||||
|         printf("\r\n"); | ||||
|         printf("  debug cmd:\r\n"); | ||||
|         printf("\ttx_carrier <frequency:in Hz>\t - Transmit carrier\r\n"); | ||||
|         printf("\ttx_carrier <frequency:in Hz>\t - Transmitting carrier\r\n"); | ||||
|         printf("\trx_carrier <frequency:in Hz>\t - Receive carrier\r\n"); | ||||
|         printf( | ||||
|             "\tencrypt_keeloq <path_decrypted_file> <path_encrypted_file> <IV:16 bytes in hex>\t - Encrypt keeloq manufacture keys\r\n"); | ||||
| @ -915,6 +1168,11 @@ static void subghz_cli_command(Cli* cli, FuriString* args, void* context) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(furi_string_cmp_str(cmd, "tx_from_file") == 0) { | ||||
|             subghz_cli_command_tx_from_file(cli, args, context); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { | ||||
|             if(furi_string_cmp_str(cmd, "encrypt_keeloq") == 0) { | ||||
|                 subghz_cli_command_encrypt_keeloq(cli, args); | ||||
|  | ||||
| @ -70,7 +70,7 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) { | ||||
|     dialog_message_set_header(message, header_text, 63, 3, AlignCenter, AlignTop); | ||||
|     dialog_message_set_text(message, message_text, 0, 17, AlignLeft, AlignTop); | ||||
| 
 | ||||
|     dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); | ||||
|     dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); | ||||
| 
 | ||||
|     dialog_message_show(dialogs, message); | ||||
|     dialog_message_free(message); | ||||
|  | ||||
| @ -251,9 +251,9 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) { | ||||
|     } | ||||
| 
 | ||||
|     if(model->device_type == SubGhzRadioDeviceTypeInternal) { | ||||
|         canvas_draw_icon(canvas, 108, 0, &I_Internal_antenna_20x12); | ||||
|         canvas_draw_icon(canvas, 109, 0, &I_Internal_ant_1_9x11); | ||||
|     } else { | ||||
|         canvas_draw_icon(canvas, 108, 0, &I_External_antenna_20x12); | ||||
|         canvas_draw_icon(canvas, 109, 0, &I_External_ant_1_9x11); | ||||
|     } | ||||
| 
 | ||||
|     subghz_view_rssi_draw(canvas, model); | ||||
|  | ||||
| @ -178,7 +178,7 @@ void subghz_frequency_analyzer_draw(Canvas* canvas, SubGhzFrequencyAnalyzerModel | ||||
|         subghz_frequency_analyzer_log_frequency_draw(canvas, model); | ||||
|     } else { | ||||
|         canvas_draw_str(canvas, 0, 8, "Frequency Analyzer"); | ||||
|         canvas_draw_icon(canvas, 108, 0, &I_Internal_antenna_20x12); | ||||
|         canvas_draw_icon(canvas, 109, 0, &I_Internal_ant_1_9x11); | ||||
|         canvas_draw_str(canvas, 0, 64, "RSSI"); | ||||
|         subghz_frequency_analyzer_draw_rssi(canvas, model->rssi, 20, 64); | ||||
| 
 | ||||
|  | ||||
| @ -294,9 +294,9 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { | ||||
|         canvas, 106, 2, AlignRight, AlignTop, furi_string_get_cstr(model->sample_write)); | ||||
| 
 | ||||
|     if(model->device_type == SubGhzRadioDeviceTypeInternal) { | ||||
|         canvas_draw_icon(canvas, 108, 0, &I_Internal_antenna_20x12); | ||||
|         canvas_draw_icon(canvas, 109, 0, &I_Internal_ant_1_9x11); | ||||
|     } else { | ||||
|         canvas_draw_icon(canvas, 108, 0, &I_External_antenna_20x12); | ||||
|         canvas_draw_icon(canvas, 109, 0, &I_External_ant_1_9x11); | ||||
|     } | ||||
|     canvas_draw_line(canvas, 0, 14, 115, 14); | ||||
|     canvas_draw_line(canvas, 0, 48, 115, 48); | ||||
|  | ||||
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