Merge remote-tracking branch 'origin/dev' into release-candidate
							
								
								
									
										115
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -15,11 +15,8 @@ env: | ||||
| 
 | ||||
| jobs: | ||||
|   main: | ||||
|     runs-on: [self-hosted,FlipperZero] | ||||
|     runs-on: [self-hosted,FlipperZeroShell] | ||||
|     steps: | ||||
|       - name: 'Cleanup workspace' | ||||
|         uses: AutoModality/action-clean@v1 | ||||
| 
 | ||||
|       - name: 'Decontaminate previous build leftovers' | ||||
|         run: | | ||||
|           if [ -d .git ] | ||||
| @ -32,12 +29,8 @@ jobs: | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           submodules: true | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Build docker image' | ||||
|         uses: ./.github/actions/docker | ||||
| 
 | ||||
|       - name: 'Make artifacts directory' | ||||
|         run: | | ||||
|           test -d artifacts && rm -rf artifacts || true | ||||
| @ -71,40 +64,38 @@ jobs: | ||||
|         run: | | ||||
|           tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts | ||||
| 
 | ||||
|       - name: 'Build the firmware in docker' | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: | | ||||
|             set -e | ||||
|             for TARGET in ${TARGETS} | ||||
|             do | ||||
|               ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` --with-updater updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} | ||||
|             done | ||||
|       - name: 'Build the firmware' | ||||
|         run: | | ||||
|           set -e | ||||
|           for TARGET in ${TARGETS} | ||||
|           do | ||||
|             FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} | ||||
|           done | ||||
| 
 | ||||
|       - name: 'Move upload files' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: | | ||||
|             set -e | ||||
|             for TARGET in ${TARGETS} | ||||
|             do | ||||
|               mv dist/${TARGET}-*/* artifacts/ | ||||
|             done | ||||
|         run: | | ||||
|           set -e | ||||
|           for TARGET in ${TARGETS} | ||||
|           do | ||||
|             mv dist/${TARGET}-*/* artifacts/ | ||||
|           done | ||||
| 
 | ||||
|       - name: 'Bundle self-update package' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: | | ||||
|             set -e | ||||
|             for UPDATEBUNDLE in artifacts/*/ | ||||
|             do | ||||
|               BUNDLE_NAME=`echo $UPDATEBUNDLE | cut -d'/' -f2` | ||||
|               echo Packaging ${BUNDLE_NAME} | ||||
|               tar czpf artifacts/flipper-z-${BUNDLE_NAME}.tgz -C artifacts ${BUNDLE_NAME} | ||||
|               rm -rf artifacts/${BUNDLE_NAME} | ||||
|             done | ||||
|         run: | | ||||
|           set -e | ||||
|           for UPDATEBUNDLE in artifacts/*/ | ||||
|           do | ||||
|             BUNDLE_NAME=`echo $UPDATEBUNDLE | cut -d'/' -f2` | ||||
|             echo Packaging ${BUNDLE_NAME} | ||||
|             tar czpf artifacts/flipper-z-${BUNDLE_NAME}.tgz -C artifacts ${BUNDLE_NAME} | ||||
|             rm -rf artifacts/${BUNDLE_NAME} | ||||
|           done | ||||
| 
 | ||||
|       - name: "Check for uncommited changes" | ||||
|         run: | | ||||
|           git diff --exit-code | ||||
| 
 | ||||
|       - name: 'Bundle resources' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
| @ -113,29 +104,23 @@ jobs: | ||||
| 
 | ||||
|       - name: 'Bundle core2 firmware' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: | | ||||
|             ./fbt copro_dist | ||||
|             tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware | ||||
|         run: | | ||||
|           FBT_TOOLCHAIN_PATH=/opt ./fbt copro_dist | ||||
|           tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware | ||||
| 
 | ||||
|       - name: 'Upload artifacts to update server' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         uses: burnett01/rsync-deployments@5.1 | ||||
|         with: | ||||
|           switches: -avzP --delete --mkpath | ||||
|           path: artifacts/ | ||||
|           remote_path: "${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{steps.names.outputs.artifacts-path}}/" | ||||
|           remote_host: ${{ secrets.RSYNC_DEPLOY_HOST }} | ||||
|           remote_port: ${{ secrets.RSYNC_DEPLOY_PORT }} | ||||
|           remote_user: ${{ secrets.RSYNC_DEPLOY_USER }} | ||||
|           remote_key: ${{ secrets.RSYNC_DEPLOY_KEY }} | ||||
|         run: | | ||||
|           echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; | ||||
|           chmod 600 ./deploy_key; | ||||
|           rsync -avzP --mkpath \ | ||||
|               -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ | ||||
|               artifacts/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{steps.names.outputs.artifacts-path}}/"; | ||||
|           rm ./deploy_key; | ||||
| 
 | ||||
|       - name: 'Trigger update server reindex' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork }} | ||||
|         uses: wei/curl@master | ||||
|         with: | ||||
|           args: -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} | ||||
|         run: curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} | ||||
| 
 | ||||
|       - name: 'Find Previous Comment' | ||||
|         if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }} | ||||
| @ -161,11 +146,8 @@ jobs: | ||||
| 
 | ||||
|   compact: | ||||
|     if: ${{ !startsWith(github.ref, 'refs/tags') }} | ||||
|     runs-on: [self-hosted,FlipperZero] | ||||
|     runs-on: [self-hosted,FlipperZeroShell] | ||||
|     steps: | ||||
|       - name: 'Cleanup workspace' | ||||
|         uses: AutoModality/action-clean@v1 | ||||
| 
 | ||||
|       - name: 'Decontaminate previous build leftovers' | ||||
|         run: | | ||||
|           if [ -d .git ] | ||||
| @ -181,9 +163,6 @@ jobs: | ||||
|           submodules: true | ||||
|           ref: ${{ github.event.pull_request.head.sha }} | ||||
| 
 | ||||
|       - name: 'Build docker image' | ||||
|         uses: ./.github/actions/docker | ||||
| 
 | ||||
|       - name: 'Generate suffix and folder name' | ||||
|         id: names | ||||
|         run: | | ||||
| @ -203,12 +182,10 @@ jobs: | ||||
|           echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV | ||||
|           echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV | ||||
| 
 | ||||
|       - name: 'Build the firmware in docker' | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: | | ||||
|             set -e | ||||
|             for TARGET in ${TARGETS} | ||||
|             do | ||||
|               ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` --with-updater updater_package DEBUG=0 COMPACT=1 | ||||
|             done | ||||
|       - name: 'Build the firmware' | ||||
|         run: | | ||||
|           set -e | ||||
|           for TARGET in ${TARGETS} | ||||
|           do | ||||
|             FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` updater_package DEBUG=0 COMPACT=1 | ||||
|           done | ||||
|  | ||||
							
								
								
									
										46
									
								
								.github/workflows/build_toolchain.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,46 +0,0 @@ | ||||
| name: 'Build toolchain Docker image' | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - dev | ||||
|     tags: | ||||
|       - '*' | ||||
| 
 | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout code | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: Docker meta | ||||
|         id: meta | ||||
|         uses: docker/metadata-action@v3 | ||||
|         with: | ||||
|           images: flipperdevices/flipperzero-toolchain | ||||
|           flavor: latest=${{ startsWith(github.ref, 'refs/tags/') && !endsWith(github.ref, 'rc')}} | ||||
|           tags: | | ||||
|             type=ref,event=branch | ||||
|             type=ref,event=tag | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v1 | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v1 | ||||
|       - name: Login to DockerHub | ||||
|         uses: docker/login-action@v1 | ||||
|         with: | ||||
|           username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||
|       - name: Build and push | ||||
|         id: docker_build | ||||
|         uses: docker/build-push-action@v2 | ||||
|         with: | ||||
|           context: docker/ | ||||
|           push: true | ||||
|           tags: ${{ steps.meta.outputs.tags }} | ||||
|           labels: ${{ steps.meta.outputs.labels }} | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|           cache-from: type=registry,ref=flipperdevices/flipperzero-toolchain:buildcache | ||||
|           cache-to: type=registry,ref=flipperdevices/flipperzero-toolchain:buildcache,mode=max | ||||
							
								
								
									
										52
									
								
								.github/workflows/check_submodules.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,17 +1,47 @@ | ||||
| name: 'Check submodules' | ||||
| name: 'Check submodules branch' | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - dev | ||||
|       - "release*" | ||||
|     tags: | ||||
|       - '*' | ||||
|   pull_request: | ||||
| 
 | ||||
| jobs: | ||||
|   protobuf: | ||||
|     runs-on: ubuntu-latest | ||||
|   check_protobuf: | ||||
|     runs-on: [self-hosted, FlipperZeroShell] | ||||
|     steps: | ||||
|     - name: 'Checkout code' | ||||
|       uses: actions/checkout@v2 | ||||
|     - name: 'Check submodule commit branch' | ||||
|       uses: jtmullen/submodule-branch-check-action@v1 | ||||
|       with: | ||||
|         path: assets/protobuf | ||||
|         branch: dev | ||||
|         fetch_depth: 50 | ||||
|       - name: 'Decontaminate previous build leftovers' | ||||
|         run: | | ||||
|           if [ -d .git ] | ||||
|           then | ||||
|             git submodule status \ | ||||
|               || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1` | ||||
|           fi | ||||
| 
 | ||||
|       - name: 'Checkout code' | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
| 
 | ||||
|       - name: 'Check protobuf branch' | ||||
|         run: | | ||||
|           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 "::set-output name=fails::error"; | ||||
|             echo "::error::Error: Too low commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)"; | ||||
|             exit 1; | ||||
|           fi | ||||
|           if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then | ||||
|             echo "::set-output name=fails::error"; | ||||
|             echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH"; | ||||
|             exit 1; | ||||
|           fi | ||||
|  | ||||
							
								
								
									
										22
									
								
								.github/workflows/lint_c.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -14,11 +14,8 @@ env: | ||||
| 
 | ||||
| jobs: | ||||
|   lint_c_cpp: | ||||
|     runs-on: [self-hosted,FlipperZero] | ||||
|     runs-on: [self-hosted,FlipperZeroShell] | ||||
|     steps: | ||||
|       - name: 'Cleanup workspace' | ||||
|         uses: AutoModality/action-clean@v1 | ||||
| 
 | ||||
|       - name: 'Decontaminate previous build leftovers' | ||||
|         run: | | ||||
|           if [ -d .git ] | ||||
| @ -31,23 +28,10 @@ jobs: | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           submodules: true | ||||
| 
 | ||||
|       - name: 'Docker cache' | ||||
|         uses: satackey/action-docker-layer-caching@v0.0.11 | ||||
|         continue-on-error: true | ||||
|         with: | ||||
|           key: docker-cache-${{ hashFiles('docker/**') }}-{hash} | ||||
|           restore-keys: docker-cache-${{ hashFiles('docker/**') }}- | ||||
| 
 | ||||
|       - name: 'Build docker image' | ||||
|         uses: ./.github/actions/docker | ||||
| 
 | ||||
|       - name: 'Check code formatting' | ||||
|         id: syntax_check | ||||
|         uses: ./.github/actions/docker | ||||
|         with: | ||||
|           run: SET_GH_OUTPUT=1 ./fbt lint | ||||
|         run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint | ||||
| 
 | ||||
|       - name: Report code formatting errors | ||||
|         if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request | ||||
| @ -59,4 +43,4 @@ jobs: | ||||
|             ``` | ||||
|             ${{ steps.syntax_check.outputs.errors }} | ||||
|             ``` | ||||
|             You might want to run `docker compose exec dev make format` for an auto-fix. | ||||
|             You might want to run `./fbt format` for an auto-fix. | ||||
|  | ||||
							
								
								
									
										12
									
								
								.github/workflows/lint_python.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -11,11 +11,8 @@ on: | ||||
| 
 | ||||
| jobs: | ||||
|   lint_python: | ||||
|     runs-on: ubuntu-latest | ||||
|     runs-on: [self-hosted,FlipperZeroShell] | ||||
|     steps: | ||||
|       - name: 'Cleanup workspace' | ||||
|         uses: AutoModality/action-clean@v1 | ||||
| 
 | ||||
|       - name: 'Decontaminate previous build leftovers' | ||||
|         run: | | ||||
|           if [ -d .git ] | ||||
| @ -29,8 +26,5 @@ jobs: | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
| 
 | ||||
|       - name: 'Setup python' | ||||
|         uses: actions/setup-python@v2 | ||||
| 
 | ||||
|       - name: 'Check python code with black' | ||||
|         uses: psf/black@20.8b1 | ||||
|       - name: 'Check code formatting' | ||||
|         run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint_py | ||||
|  | ||||
							
								
								
									
										7
									
								
								.github/workflows/reindex.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -7,9 +7,8 @@ on: | ||||
| jobs: | ||||
|   reindex: | ||||
|     name: 'Reindex updates' | ||||
|     runs-on: [self-hosted,FlipperZero] | ||||
|     runs-on: [self-hosted,FlipperZeroShell] | ||||
|     steps: | ||||
|       - name: Trigger reindex | ||||
|         uses: wei/curl@master | ||||
|         with: | ||||
|           args: -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} | ||||
|         run: | | ||||
|           curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }} | ||||
|  | ||||
| @ -29,11 +29,11 @@ They both must be flashed in the order described. | ||||
| 
 | ||||
| With Flipper attached over USB: | ||||
| 
 | ||||
| `./fbt --with-updater flash_usb` | ||||
| `./fbt flash_usb` | ||||
| 
 | ||||
| Just building the package: | ||||
| 
 | ||||
| `./fbt --with-updater updater_package` | ||||
| `./fbt updater_package` | ||||
| 
 | ||||
| To update, copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app.  | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										107
									
								
								SConstruct
									
									
									
									
									
								
							
							
						
						| @ -7,6 +7,7 @@ | ||||
| # construction of certain targets behind command-line options. | ||||
| 
 | ||||
| import os | ||||
| import subprocess | ||||
| 
 | ||||
| EnsurePythonVersion(3, 8) | ||||
| 
 | ||||
| @ -33,8 +34,10 @@ coreenv["ROOT_DIR"] = Dir(".") | ||||
| 
 | ||||
| # Create a separate "dist" environment and add construction envs to it | ||||
| distenv = coreenv.Clone( | ||||
|     tools=["fbt_dist", "openocd", "blackmagic"], | ||||
|     OPENOCD_GDB_PIPE=["|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}"], | ||||
|     tools=["fbt_dist", "openocd", "blackmagic", "jflash"], | ||||
|     OPENOCD_GDB_PIPE=[ | ||||
|         "|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" | ||||
|     ], | ||||
|     GDBOPTS_BASE=[ | ||||
|         "-ex", | ||||
|         "target extended-remote ${GDBREMOTE}", | ||||
| @ -61,6 +64,7 @@ distenv = coreenv.Clone( | ||||
|         "-ex", | ||||
|         "compare-sections", | ||||
|     ], | ||||
|     JFLASHPROJECT="${ROOT_DIR.abspath}/debug/fw.jflash", | ||||
|     ENV=os.environ, | ||||
| ) | ||||
| 
 | ||||
| @ -71,7 +75,9 @@ firmware_env = distenv.AddFwProject( | ||||
| ) | ||||
| 
 | ||||
| # If enabled, initialize updater-related targets | ||||
| if GetOption("fullenv"): | ||||
| if GetOption("fullenv") or any( | ||||
|     filter(lambda target: "updater" in target or "flash_usb" in target, BUILD_TARGETS) | ||||
| ): | ||||
|     updater_env = distenv.AddFwProject( | ||||
|         base_env=coreenv, | ||||
|         fw_type="updater", | ||||
| @ -79,11 +85,11 @@ if GetOption("fullenv"): | ||||
|     ) | ||||
| 
 | ||||
|     # Target for self-update package | ||||
|     dist_arguments = [ | ||||
|         "-r", | ||||
|         '"${ROOT_DIR.abspath}/assets/resources"', | ||||
|     dist_basic_arguments = [ | ||||
|         "--bundlever", | ||||
|         '"${UPDATE_VERSION_STRING}"', | ||||
|     ] | ||||
|     dist_radio_arguments = [ | ||||
|         "--radio", | ||||
|         '"${ROOT_DIR.abspath}/${COPRO_STACK_BIN_DIR}/${COPRO_STACK_BIN}"', | ||||
|         "--radiotype", | ||||
| @ -92,16 +98,34 @@ if GetOption("fullenv"): | ||||
|         "--obdata", | ||||
|         '"${ROOT_DIR.abspath}/${COPRO_OB_DATA}"', | ||||
|     ] | ||||
|     if distenv["UPDATE_SPLASH"]: | ||||
|         dist_arguments += [ | ||||
|     dist_resource_arguments = [ | ||||
|         "-r", | ||||
|         '"${ROOT_DIR.abspath}/assets/resources"', | ||||
|     ] | ||||
|     dist_splash_arguments = ( | ||||
|         [ | ||||
|             "--splash", | ||||
|             distenv.subst("assets/slideshow/$UPDATE_SPLASH"), | ||||
|         ] | ||||
|         if distenv["UPDATE_SPLASH"] | ||||
|         else [] | ||||
|     ) | ||||
| 
 | ||||
|     selfupdate_dist = distenv.DistCommand( | ||||
|         "updater_package", | ||||
|         (distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES"]), | ||||
|         DIST_EXTRA=dist_arguments, | ||||
|         DIST_EXTRA=[ | ||||
|             *dist_basic_arguments, | ||||
|             *dist_radio_arguments, | ||||
|             *dist_resource_arguments, | ||||
|             *dist_splash_arguments, | ||||
|         ], | ||||
|     ) | ||||
| 
 | ||||
|     selfupdate_min_dist = distenv.DistCommand( | ||||
|         "updater_minpackage", | ||||
|         distenv["DIST_DEPENDS"], | ||||
|         DIST_EXTRA=dist_basic_arguments, | ||||
|     ) | ||||
| 
 | ||||
|     # Updater debug | ||||
| @ -121,18 +145,16 @@ if GetOption("fullenv"): | ||||
|     ) | ||||
| 
 | ||||
|     # Installation over USB & CLI | ||||
|     usb_update_package = distenv.UsbInstall( | ||||
|         "#build/usbinstall.flag", | ||||
|         ( | ||||
|             distenv["DIST_DEPENDS"], | ||||
|             firmware_env["FW_RESOURCES"], | ||||
|             selfupdate_dist, | ||||
|         ), | ||||
|     usb_update_package = distenv.AddUsbFlashTarget( | ||||
|         "#build/usbinstall.flag", (firmware_env["FW_RESOURCES"], selfupdate_dist) | ||||
|     ) | ||||
|     if distenv["FORCE"]: | ||||
|         distenv.AlwaysBuild(usb_update_package) | ||||
|     distenv.Depends(usb_update_package, selfupdate_dist) | ||||
|     distenv.Alias("flash_usb", usb_update_package) | ||||
|     distenv.Alias("flash_usb_full", usb_update_package) | ||||
| 
 | ||||
|     usb_minupdate_package = distenv.AddUsbFlashTarget( | ||||
|         "#build/minusbinstall.flag", (selfupdate_min_dist,) | ||||
|     ) | ||||
|     distenv.Alias("flash_usb", usb_minupdate_package) | ||||
| 
 | ||||
| 
 | ||||
| # Target for copying & renaming binaries to dist folder | ||||
| basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"]) | ||||
| @ -147,8 +169,9 @@ distenv.Alias("copro_dist", copro_dist) | ||||
| 
 | ||||
| firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env) | ||||
| distenv.Alias("flash", firmware_flash) | ||||
| if distenv["FORCE"]: | ||||
|     distenv.AlwaysBuild(firmware_flash) | ||||
| 
 | ||||
| firmware_jflash = distenv.AddJFlashTarget(firmware_env) | ||||
| distenv.Alias("jflash", firmware_jflash) | ||||
| 
 | ||||
| firmware_bm_flash = distenv.PhonyTarget( | ||||
|     "flash_blackmagic", | ||||
| @ -209,6 +232,46 @@ distenv.PhonyTarget( | ||||
|     LINT_SOURCES=firmware_env["LINT_SOURCES"], | ||||
| ) | ||||
| 
 | ||||
| # PY_LINT_SOURCES contains recursively-built modules' SConscript files + application manifests | ||||
| # Here we add additional Python files residing in repo root | ||||
| firmware_env.Append( | ||||
|     PY_LINT_SOURCES=[ | ||||
|         # Py code folders | ||||
|         "site_scons", | ||||
|         "scripts", | ||||
|         # Extra files | ||||
|         "applications/extapps.scons", | ||||
|         "SConstruct", | ||||
|         "firmware.scons", | ||||
|         "fbt_options.py", | ||||
|     ] | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| black_commandline = "@${PYTHON3} -m black ${PY_BLACK_ARGS} ${PY_LINT_SOURCES}" | ||||
| black_base_args = ["--include", '"\\.scons|\\.py|SConscript|SConstruct"'] | ||||
| 
 | ||||
| distenv.PhonyTarget( | ||||
|     "lint_py", | ||||
|     black_commandline, | ||||
|     PY_BLACK_ARGS=[ | ||||
|         "--check", | ||||
|         "--diff", | ||||
|         *black_base_args, | ||||
|     ], | ||||
|     PY_LINT_SOURCES=firmware_env["PY_LINT_SOURCES"], | ||||
| ) | ||||
| 
 | ||||
| distenv.PhonyTarget( | ||||
|     "format_py", | ||||
|     black_commandline, | ||||
|     PY_BLACK_ARGS=black_base_args, | ||||
|     PY_LINT_SOURCES=firmware_env["PY_LINT_SOURCES"], | ||||
| ) | ||||
| 
 | ||||
| # Start Flipper CLI via PySerial's miniterm | ||||
| distenv.PhonyTarget("cli", "${PYTHON3} scripts/serial_cli.py") | ||||
| 
 | ||||
| 
 | ||||
| # Find blackmagic probe | ||||
| 
 | ||||
|  | ||||
| @ -38,6 +38,8 @@ appenv.AppendUnique( | ||||
|         "-Wl,--no-export-dynamic", | ||||
|         "-fvisibility=hidden", | ||||
|         "-Wl,-e${APP_ENTRY}", | ||||
|         "-Xlinker", | ||||
|         "-Map=${TARGET}.map", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
|  | ||||
| @ -5,7 +5,7 @@ | ||||
| #include "m-string.h" | ||||
| #include <toolbox/path.h> | ||||
| #include <flipper_format/flipper_format.h> | ||||
| #include "rpc/rpc_app.h" | ||||
| #include <rpc/rpc_app.h> | ||||
| 
 | ||||
| #define TAG "iButtonApp" | ||||
| 
 | ||||
| @ -58,7 +58,7 @@ static void ibutton_make_app_folder(iButton* ibutton) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool ibutton_load_key_data(iButton* ibutton, string_t key_path, bool show_dialog) { | ||||
| bool ibutton_load_key_data(iButton* ibutton, string_t key_path, bool show_dialog) { | ||||
|     FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); | ||||
|     bool result = false; | ||||
|     string_t data; | ||||
| @ -99,33 +99,20 @@ static bool ibutton_load_key_data(iButton* ibutton, string_t key_path, bool show | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| static bool ibutton_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) { | ||||
| static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
|     iButton* ibutton = context; | ||||
| 
 | ||||
|     bool result = false; | ||||
| 
 | ||||
|     if(event == RpcAppEventSessionClose) { | ||||
|         rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL); | ||||
|         ibutton->rpc_ctx = NULL; | ||||
|         view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit); | ||||
|         result = true; | ||||
|         view_dispatcher_send_custom_event( | ||||
|             ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose); | ||||
|     } else if(event == RpcAppEventAppExit) { | ||||
|         view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit); | ||||
|         result = true; | ||||
|     } else if(event == RpcAppEventLoadFile) { | ||||
|         if(arg) { | ||||
|             string_set_str(ibutton->file_path, arg); | ||||
|             if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) { | ||||
|                 ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key); | ||||
|                 view_dispatcher_send_custom_event( | ||||
|                     ibutton->view_dispatcher, iButtonCustomEventRpcLoad); | ||||
|                 result = true; | ||||
|             } | ||||
|         } | ||||
|         view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad); | ||||
|     } else { | ||||
|         rpc_system_app_confirm(ibutton->rpc_ctx, event, false); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool ibutton_custom_event_callback(void* context, uint32_t event) { | ||||
|  | ||||
| @ -12,4 +12,5 @@ enum iButtonCustomEvent { | ||||
| 
 | ||||
|     iButtonCustomEventRpcLoad, | ||||
|     iButtonCustomEventRpcExit, | ||||
|     iButtonCustomEventRpcSessionClose, | ||||
| }; | ||||
|  | ||||
| @ -78,6 +78,7 @@ typedef enum { | ||||
| } iButtonNotificationMessage; | ||||
| 
 | ||||
| bool ibutton_file_select(iButton* ibutton); | ||||
| bool ibutton_load_key_data(iButton* ibutton, string_t key_path, bool show_dialog); | ||||
| bool ibutton_save_key(iButton* ibutton, const char* key_name); | ||||
| bool ibutton_delete_key(iButton* ibutton); | ||||
| void ibutton_text_store_set(iButton* ibutton, const char* text, ...); | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| #include "../ibutton_i.h" | ||||
| #include <toolbox/path.h> | ||||
| #include <rpc/rpc_app.h> | ||||
| 
 | ||||
| void ibutton_scene_rpc_on_enter(void* context) { | ||||
|     iButton* ibutton = context; | ||||
| @ -26,23 +27,40 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) { | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         consumed = true; | ||||
|         if(event.event == iButtonCustomEventRpcLoad) { | ||||
|             string_t key_name; | ||||
|             string_init(key_name); | ||||
|             if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { | ||||
|                 path_extract_filename(ibutton->file_path, key_name, true); | ||||
|             const char* arg = rpc_system_app_get_data(ibutton->rpc_ctx); | ||||
|             bool result = false; | ||||
|             if(arg) { | ||||
|                 string_set_str(ibutton->file_path, arg); | ||||
|                 if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) { | ||||
|                     ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key); | ||||
|                     string_t key_name; | ||||
|                     string_init(key_name); | ||||
|                     if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { | ||||
|                         path_extract_filename(ibutton->file_path, key_name, true); | ||||
|                     } | ||||
| 
 | ||||
|                     if(!string_empty_p(key_name)) { | ||||
|                         ibutton_text_store_set( | ||||
|                             ibutton, "emulating\n%s", string_get_cstr(key_name)); | ||||
|                     } else { | ||||
|                         ibutton_text_store_set(ibutton, "emulating"); | ||||
|                     } | ||||
|                     popup_set_text(popup, ibutton->text_store, 82, 32, AlignCenter, AlignTop); | ||||
| 
 | ||||
|                     ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); | ||||
| 
 | ||||
|                     string_clear(key_name); | ||||
|                     result = true; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(!string_empty_p(key_name)) { | ||||
|                 ibutton_text_store_set(ibutton, "emulating\n%s", string_get_cstr(key_name)); | ||||
|             } else { | ||||
|                 ibutton_text_store_set(ibutton, "emulating"); | ||||
|             } | ||||
|             popup_set_text(popup, ibutton->text_store, 82, 32, AlignCenter, AlignTop); | ||||
| 
 | ||||
|             ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart); | ||||
| 
 | ||||
|             string_clear(key_name); | ||||
|             rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventLoadFile, result); | ||||
|         } else if(event.event == iButtonCustomEventRpcExit) { | ||||
|             rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventAppExit, true); | ||||
|             ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop); | ||||
|             view_dispatcher_stop(ibutton->view_dispatcher); | ||||
|         } else if(event.event == iButtonCustomEventRpcSessionClose) { | ||||
|             rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL); | ||||
|             ibutton->rpc_ctx = NULL; | ||||
|             ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop); | ||||
|             view_dispatcher_stop(ibutton->view_dispatcher); | ||||
|         } | ||||
|  | ||||
| @ -36,52 +36,29 @@ static void infrared_tick_event_callback(void* context) { | ||||
|     scene_manager_handle_tick_event(infrared->scene_manager); | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
|     infrared_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) { | ||||
| static void infrared_rpc_command_callback(RpcAppSystemEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
|     Infrared* infrared = context; | ||||
| 
 | ||||
|     if(!infrared->rpc_ctx) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool result = false; | ||||
|     furi_assert(infrared->rpc_ctx); | ||||
| 
 | ||||
|     if(event == RpcAppEventSessionClose) { | ||||
|         rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL); | ||||
|         infrared->rpc_ctx = NULL; | ||||
|         view_dispatcher_send_custom_event( | ||||
|             infrared->view_dispatcher, InfraredCustomEventTypeBackPressed); | ||||
|         result = true; | ||||
|             infrared->view_dispatcher, InfraredCustomEventTypeRpcSessionClose); | ||||
|     } else if(event == RpcAppEventAppExit) { | ||||
|         view_dispatcher_send_custom_event( | ||||
|             infrared->view_dispatcher, InfraredCustomEventTypeBackPressed); | ||||
|         result = true; | ||||
|             infrared->view_dispatcher, InfraredCustomEventTypeRpcExit); | ||||
|     } else if(event == RpcAppEventLoadFile) { | ||||
|         if(arg) { | ||||
|             string_set_str(infrared->file_path, arg); | ||||
|             result = infrared_remote_load(infrared->remote, infrared->file_path); | ||||
|             infrared_worker_tx_set_get_signal_callback( | ||||
|                 infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared); | ||||
|             infrared_worker_tx_set_signal_sent_callback( | ||||
|                 infrared->worker, infrared_signal_sent_callback, infrared); | ||||
|             view_dispatcher_send_custom_event( | ||||
|                 infrared->view_dispatcher, InfraredCustomEventTypeRpcLoaded); | ||||
|         } | ||||
|         view_dispatcher_send_custom_event( | ||||
|             infrared->view_dispatcher, InfraredCustomEventTypeRpcLoad); | ||||
|     } else if(event == RpcAppEventButtonPress) { | ||||
|         if(arg) { | ||||
|             size_t button_index = 0; | ||||
|             if(infrared_remote_find_button_by_name(infrared->remote, arg, &button_index)) { | ||||
|                 infrared_tx_start_button_index(infrared, button_index); | ||||
|                 result = true; | ||||
|             } | ||||
|         } | ||||
|         view_dispatcher_send_custom_event( | ||||
|             infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonPress); | ||||
|     } else if(event == RpcAppEventButtonRelease) { | ||||
|         infrared_tx_stop(infrared); | ||||
|         result = true; | ||||
|         view_dispatcher_send_custom_event( | ||||
|             infrared->view_dispatcher, InfraredCustomEventTypeRpcButtonRelease); | ||||
|     } else { | ||||
|         rpc_system_app_confirm(infrared->rpc_ctx, event, false); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| static void infrared_find_vacant_remote_name(string_t name, const char* path) { | ||||
|  | ||||
| @ -14,7 +14,12 @@ enum InfraredCustomEventType { | ||||
|     InfraredCustomEventTypePopupClosed, | ||||
|     InfraredCustomEventTypeButtonSelected, | ||||
|     InfraredCustomEventTypeBackPressed, | ||||
|     InfraredCustomEventTypeRpcLoaded, | ||||
| 
 | ||||
|     InfraredCustomEventTypeRpcLoad, | ||||
|     InfraredCustomEventTypeRpcExit, | ||||
|     InfraredCustomEventTypeRpcButtonPress, | ||||
|     InfraredCustomEventTypeRpcButtonRelease, | ||||
|     InfraredCustomEventTypeRpcSessionClose, | ||||
| }; | ||||
| 
 | ||||
| #pragma pack(push, 1) | ||||
|  | ||||
| @ -28,12 +28,45 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { | ||||
|             view_dispatcher_stop(infrared->view_dispatcher); | ||||
|         } else if(event.event == InfraredCustomEventTypePopupClosed) { | ||||
|             view_dispatcher_stop(infrared->view_dispatcher); | ||||
|         } else if(event.event == InfraredCustomEventTypeRpcLoaded) { | ||||
|         } else if(event.event == InfraredCustomEventTypeRpcLoad) { | ||||
|             bool result = false; | ||||
|             const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); | ||||
|             if(arg) { | ||||
|                 string_set_str(infrared->file_path, arg); | ||||
|                 result = infrared_remote_load(infrared->remote, infrared->file_path); | ||||
|                 infrared_worker_tx_set_get_signal_callback( | ||||
|                     infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared); | ||||
|                 infrared_worker_tx_set_signal_sent_callback( | ||||
|                     infrared->worker, infrared_signal_sent_callback, infrared); | ||||
|             } | ||||
|             const char* remote_name = infrared_remote_get_name(infrared->remote); | ||||
| 
 | ||||
|             infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); | ||||
|             popup_set_text( | ||||
|                 infrared->popup, infrared->text_store[0], 82, 32, AlignCenter, AlignTop); | ||||
| 
 | ||||
|             rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventLoadFile, result); | ||||
|         } else if(event.event == InfraredCustomEventTypeRpcButtonPress) { | ||||
|             bool result = false; | ||||
|             const char* arg = rpc_system_app_get_data(infrared->rpc_ctx); | ||||
|             if(arg) { | ||||
|                 size_t button_index = 0; | ||||
|                 if(infrared_remote_find_button_by_name(infrared->remote, arg, &button_index)) { | ||||
|                     infrared_tx_start_button_index(infrared, button_index); | ||||
|                     result = true; | ||||
|                 } | ||||
|             } | ||||
|             rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, result); | ||||
|         } else if(event.event == InfraredCustomEventTypeRpcButtonRelease) { | ||||
|             infrared_tx_stop(infrared); | ||||
|             rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventButtonRelease, true); | ||||
|         } else if(event.event == InfraredCustomEventTypeRpcExit) { | ||||
|             view_dispatcher_stop(infrared->view_dispatcher); | ||||
|             rpc_system_app_confirm(infrared->rpc_ctx, RpcAppEventAppExit, true); | ||||
|         } else if(event.event == InfraredCustomEventTypeRpcSessionClose) { | ||||
|             rpc_system_app_set_callback(infrared->rpc_ctx, NULL, NULL); | ||||
|             infrared->rpc_ctx = NULL; | ||||
|             view_dispatcher_stop(infrared->view_dispatcher); | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
|  | ||||
| @ -25,7 +25,7 @@ | ||||
| #include <toolbox/path.h> | ||||
| #include <flipper_format/flipper_format.h> | ||||
| 
 | ||||
| #include "rpc/rpc_app.h" | ||||
| #include <rpc/rpc_app.h> | ||||
| 
 | ||||
| const char* LfRfidApp::app_folder = ANY_PATH("lfrfid"); | ||||
| const char* LfRfidApp::app_extension = ".rfid"; | ||||
| @ -48,38 +48,25 @@ LfRfidApp::~LfRfidApp() { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) { | ||||
| static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) { | ||||
|     furi_assert(context); | ||||
|     LfRfidApp* app = static_cast<LfRfidApp*>(context); | ||||
| 
 | ||||
|     bool result = false; | ||||
| 
 | ||||
|     if(event == RpcAppEventSessionClose) { | ||||
|         rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL); | ||||
|         app->rpc_ctx = NULL; | ||||
|     if(rpc_event == RpcAppEventSessionClose) { | ||||
|         LfRfidApp::Event event; | ||||
|         event.type = LfRfidApp::EventType::RpcSessionClose; | ||||
|         app->view_controller.send_event(&event); | ||||
|     } else if(rpc_event == RpcAppEventAppExit) { | ||||
|         LfRfidApp::Event event; | ||||
|         event.type = LfRfidApp::EventType::Exit; | ||||
|         app->view_controller.send_event(&event); | ||||
|         result = true; | ||||
|     } else if(event == RpcAppEventAppExit) { | ||||
|     } else if(rpc_event == RpcAppEventLoadFile) { | ||||
|         LfRfidApp::Event event; | ||||
|         event.type = LfRfidApp::EventType::Exit; | ||||
|         event.type = LfRfidApp::EventType::RpcLoadFile; | ||||
|         app->view_controller.send_event(&event); | ||||
|         result = true; | ||||
|     } else if(event == RpcAppEventLoadFile) { | ||||
|         if(arg) { | ||||
|             string_set_str(app->file_path, arg); | ||||
|             if(app->load_key_data(app->file_path, &(app->worker.key), false)) { | ||||
|                 LfRfidApp::Event event; | ||||
|                 event.type = LfRfidApp::EventType::EmulateStart; | ||||
|                 app->view_controller.send_event(&event); | ||||
|                 app->worker.start_emulate(); | ||||
|                 result = true; | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         rpc_system_app_confirm(app->rpc_ctx, rpc_event, false); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void LfRfidApp::run(void* _args) { | ||||
|  | ||||
| @ -33,6 +33,8 @@ public: | ||||
|         Retry, | ||||
|         Exit, | ||||
|         EmulateStart, | ||||
|         RpcLoadFile, | ||||
|         RpcSessionClose, | ||||
|     }; | ||||
| 
 | ||||
|     enum class SceneType : uint8_t { | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| #include "lfrfid_app_scene_rpc.h" | ||||
| #include <core/common_defines.h> | ||||
| #include <dolphin/dolphin.h> | ||||
| #include <rpc/rpc_app.h> | ||||
| 
 | ||||
| static const NotificationSequence sequence_blink_start_magenta = { | ||||
|     &message_blink_start_10, | ||||
| @ -36,6 +37,16 @@ bool LfRfidAppSceneRpc::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
|         LfRfidApp::Event view_event; | ||||
|         view_event.type = LfRfidApp::EventType::Back; | ||||
|         app->view_controller.send_event(&view_event); | ||||
|         rpc_system_app_confirm(app->rpc_ctx, RpcAppEventAppExit, true); | ||||
|     } else if(event->type == LfRfidApp::EventType::RpcSessionClose) { | ||||
|         // Detach RPC
 | ||||
|         rpc_system_app_set_callback(app->rpc_ctx, NULL, NULL); | ||||
|         app->rpc_ctx = NULL; | ||||
| 
 | ||||
|         consumed = true; | ||||
|         LfRfidApp::Event view_event; | ||||
|         view_event.type = LfRfidApp::EventType::Back; | ||||
|         app->view_controller.send_event(&view_event); | ||||
|     } else if(event->type == LfRfidApp::EventType::EmulateStart) { | ||||
|         auto popup = app->view_controller.get<PopupVM>(); | ||||
|         consumed = true; | ||||
| @ -45,7 +56,22 @@ bool LfRfidAppSceneRpc::on_event(LfRfidApp* app, LfRfidApp::Event* event) { | ||||
|         popup->set_text(app->text_store.text, 89, 43, AlignCenter, AlignTop); | ||||
| 
 | ||||
|         notification_message(app->notification, &sequence_blink_start_magenta); | ||||
|     } else if(event->type == LfRfidApp::EventType::RpcLoadFile) { | ||||
|         const char* arg = rpc_system_app_get_data(app->rpc_ctx); | ||||
|         bool result = false; | ||||
|         if(arg) { | ||||
|             string_set_str(app->file_path, arg); | ||||
|             if(app->load_key_data(app->file_path, &(app->worker.key), false)) { | ||||
|                 LfRfidApp::Event event; | ||||
|                 event.type = LfRfidApp::EventType::EmulateStart; | ||||
|                 app->view_controller.send_event(&event); | ||||
|                 app->worker.start_emulate(); | ||||
|                 result = true; | ||||
|             } | ||||
|         } | ||||
|         rpc_system_app_confirm(app->rpc_ctx, RpcAppEventLoadFile, result); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -11,4 +11,5 @@ enum NfcCustomEvent { | ||||
|     NfcCustomEventDictAttackDone, | ||||
|     NfcCustomEventDictAttackSkip, | ||||
|     NfcCustomEventRpcLoad, | ||||
|     NfcCustomEventRpcSessionClose, | ||||
| }; | ||||
|  | ||||
| @ -13,78 +13,21 @@ bool nfc_back_event_callback(void* context) { | ||||
|     return scene_manager_handle_back_event(nfc->scene_manager); | ||||
| } | ||||
| 
 | ||||
| void nfc_rpc_exit_callback(Nfc* nfc) { | ||||
|     if(nfc->rpc_state == NfcRpcStateEmulating) { | ||||
|         // Stop worker
 | ||||
|         nfc_worker_stop(nfc->worker); | ||||
|     } else if(nfc->rpc_state == NfcRpcStateEmulated) { | ||||
|         // Stop worker
 | ||||
|         nfc_worker_stop(nfc->worker); | ||||
|         // Save data in shadow file
 | ||||
|         nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); | ||||
|     } | ||||
|     if(nfc->rpc_ctx) { | ||||
|         rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); | ||||
|         rpc_system_app_send_exited(nfc->rpc_ctx); | ||||
|         nfc->rpc_ctx = NULL; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool nfc_rpc_emulate_callback(NfcWorkerEvent event, void* context) { | ||||
|     UNUSED(event); | ||||
|     Nfc* nfc = context; | ||||
| 
 | ||||
|     nfc->rpc_state = NfcRpcStateEmulated; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool nfc_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) { | ||||
| static void nfc_rpc_command_callback(RpcAppSystemEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
|     Nfc* nfc = context; | ||||
| 
 | ||||
|     if(!nfc->rpc_ctx) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool result = false; | ||||
|     furi_assert(nfc->rpc_ctx); | ||||
| 
 | ||||
|     if(event == RpcAppEventSessionClose) { | ||||
|         rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); | ||||
|         nfc->rpc_ctx = NULL; | ||||
|         view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); | ||||
|         result = true; | ||||
|         view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose); | ||||
|     } else if(event == RpcAppEventAppExit) { | ||||
|         view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); | ||||
|         result = true; | ||||
|     } else if(event == RpcAppEventLoadFile) { | ||||
|         if((arg) && (nfc->rpc_state == NfcRpcStateIdle)) { | ||||
|             if(nfc_device_load(nfc->dev, arg, false)) { | ||||
|                 if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { | ||||
|                     nfc_worker_start( | ||||
|                         nfc->worker, | ||||
|                         NfcWorkerStateMfUltralightEmulate, | ||||
|                         &nfc->dev->dev_data, | ||||
|                         nfc_rpc_emulate_callback, | ||||
|                         nfc); | ||||
|                 } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { | ||||
|                     nfc_worker_start( | ||||
|                         nfc->worker, | ||||
|                         NfcWorkerStateMfClassicEmulate, | ||||
|                         &nfc->dev->dev_data, | ||||
|                         nfc_rpc_emulate_callback, | ||||
|                         nfc); | ||||
|                 } else { | ||||
|                     nfc_worker_start( | ||||
|                         nfc->worker, NfcWorkerStateUidEmulate, &nfc->dev->dev_data, NULL, nfc); | ||||
|                 } | ||||
|                 nfc->rpc_state = NfcRpcStateEmulating; | ||||
|                 view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad); | ||||
|                 result = true; | ||||
|             } | ||||
|         } | ||||
|         view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad); | ||||
|     } else { | ||||
|         rpc_system_app_confirm(nfc->rpc_ctx, event, false); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| Nfc* nfc_alloc() { | ||||
| @ -163,6 +106,21 @@ Nfc* nfc_alloc() { | ||||
| void nfc_free(Nfc* nfc) { | ||||
|     furi_assert(nfc); | ||||
| 
 | ||||
|     if(nfc->rpc_state == NfcRpcStateEmulating) { | ||||
|         // Stop worker
 | ||||
|         nfc_worker_stop(nfc->worker); | ||||
|     } else if(nfc->rpc_state == NfcRpcStateEmulated) { | ||||
|         // Stop worker
 | ||||
|         nfc_worker_stop(nfc->worker); | ||||
|         // Save data in shadow file
 | ||||
|         nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name); | ||||
|     } | ||||
|     if(nfc->rpc_ctx) { | ||||
|         rpc_system_app_send_exited(nfc->rpc_ctx); | ||||
|         rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); | ||||
|         nfc->rpc_ctx = NULL; | ||||
|     } | ||||
| 
 | ||||
|     // Nfc device
 | ||||
|     nfc_device_free(nfc->dev); | ||||
| 
 | ||||
|  | ||||
| @ -102,5 +102,3 @@ void nfc_blink_start(Nfc* nfc); | ||||
| void nfc_blink_stop(Nfc* nfc); | ||||
| 
 | ||||
| void nfc_show_loading_popup(void* context, bool show); | ||||
| 
 | ||||
| void nfc_rpc_exit_callback(Nfc* nfc); | ||||
|  | ||||
| @ -14,6 +14,14 @@ void nfc_scene_rpc_on_enter(void* context) { | ||||
|     notification_message(nfc->notifications, &sequence_display_backlight_on); | ||||
| } | ||||
| 
 | ||||
| static bool nfc_scene_rpc_emulate_callback(NfcWorkerEvent event, void* context) { | ||||
|     UNUSED(event); | ||||
|     Nfc* nfc = context; | ||||
| 
 | ||||
|     nfc->rpc_state = NfcRpcStateEmulated; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) { | ||||
|     Nfc* nfc = context; | ||||
|     Popup* popup = nfc->popup; | ||||
| @ -22,13 +30,47 @@ bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) { | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         consumed = true; | ||||
|         if(event.event == NfcCustomEventViewExit) { | ||||
|             rpc_system_app_confirm(nfc->rpc_ctx, RpcAppEventAppExit, true); | ||||
|             view_dispatcher_stop(nfc->view_dispatcher); | ||||
|             nfc_blink_stop(nfc); | ||||
|         } else if(event.event == NfcCustomEventRpcSessionClose) { | ||||
|             rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL); | ||||
|             nfc->rpc_ctx = NULL; | ||||
|             view_dispatcher_stop(nfc->view_dispatcher); | ||||
|             nfc_blink_stop(nfc); | ||||
|         } else if(event.event == NfcCustomEventRpcLoad) { | ||||
|             nfc_blink_start(nfc); | ||||
|             bool result = false; | ||||
|             const char* arg = rpc_system_app_get_data(nfc->rpc_ctx); | ||||
|             if((arg) && (nfc->rpc_state == NfcRpcStateIdle)) { | ||||
|                 if(nfc_device_load(nfc->dev, arg, false)) { | ||||
|                     if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) { | ||||
|                         nfc_worker_start( | ||||
|                             nfc->worker, | ||||
|                             NfcWorkerStateMfUltralightEmulate, | ||||
|                             &nfc->dev->dev_data, | ||||
|                             nfc_scene_rpc_emulate_callback, | ||||
|                             nfc); | ||||
|                     } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { | ||||
|                         nfc_worker_start( | ||||
|                             nfc->worker, | ||||
|                             NfcWorkerStateMfClassicEmulate, | ||||
|                             &nfc->dev->dev_data, | ||||
|                             nfc_scene_rpc_emulate_callback, | ||||
|                             nfc); | ||||
|                     } else { | ||||
|                         nfc_worker_start( | ||||
|                             nfc->worker, NfcWorkerStateUidEmulate, &nfc->dev->dev_data, NULL, nfc); | ||||
|                     } | ||||
|                     nfc->rpc_state = NfcRpcStateEmulating; | ||||
|                     result = true; | ||||
| 
 | ||||
|             nfc_text_store_set(nfc, "emulating\n%s", nfc->dev->dev_name); | ||||
|             popup_set_text(popup, nfc->text_store, 82, 32, AlignCenter, AlignTop); | ||||
|                     nfc_blink_start(nfc); | ||||
|                     nfc_text_store_set(nfc, "emulating\n%s", nfc->dev->dev_name); | ||||
|                     popup_set_text(popup, nfc->text_store, 82, 32, AlignCenter, AlignTop); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             rpc_system_app_confirm(nfc->rpc_ctx, RpcAppEventLoadFile, result); | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| @ -38,7 +80,6 @@ void nfc_scene_rpc_on_exit(void* context) { | ||||
|     Nfc* nfc = context; | ||||
|     Popup* popup = nfc->popup; | ||||
| 
 | ||||
|     nfc_rpc_exit_callback(nfc); | ||||
|     nfc_blink_stop(nfc); | ||||
| 
 | ||||
|     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); | ||||
|  | ||||
| @ -6,24 +6,18 @@ | ||||
| #include "rpc_app.h" | ||||
| 
 | ||||
| #define TAG "RpcSystemApp" | ||||
| #define APP_BUTTON_TIMEOUT 1000 | ||||
| 
 | ||||
| struct RpcAppSystem { | ||||
|     RpcSession* session; | ||||
|     RpcAppSystemCallback app_callback; | ||||
|     void* app_context; | ||||
|     PB_Main* state_msg; | ||||
|     FuriTimer* timer; | ||||
| 
 | ||||
|     uint32_t last_id; | ||||
|     char* last_data; | ||||
| }; | ||||
| 
 | ||||
| static void rpc_system_app_timer_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     RpcAppSystem* rpc_app = context; | ||||
| 
 | ||||
|     if(rpc_app->app_callback) { | ||||
|         rpc_app->app_callback(RpcAppEventButtonRelease, NULL, rpc_app->app_context); | ||||
|     } | ||||
| } | ||||
| #define RPC_SYSTEM_APP_TEMP_ARGS_SIZE 16 | ||||
| 
 | ||||
| static void rpc_system_app_start_process(const PB_Main* request, void* context) { | ||||
|     furi_assert(request); | ||||
| @ -33,9 +27,12 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) | ||||
|     RpcAppSystem* rpc_app = context; | ||||
|     RpcSession* session = rpc_app->session; | ||||
|     furi_assert(session); | ||||
|     char args_temp[16]; | ||||
|     char args_temp[RPC_SYSTEM_APP_TEMP_ARGS_SIZE]; | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "Start"); | ||||
|     furi_assert(!rpc_app->last_id); | ||||
|     furi_assert(!rpc_app->last_data); | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "StartProcess: id %d", request->command_id); | ||||
| 
 | ||||
|     PB_CommandStatus result = PB_CommandStatus_ERROR_APP_CANT_START; | ||||
| 
 | ||||
| @ -43,9 +40,9 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) | ||||
|     const char* app_name = request->content.app_start_request.name; | ||||
|     if(app_name) { | ||||
|         const char* app_args = request->content.app_start_request.args; | ||||
|         if(strcmp(app_args, "RPC") == 0) { | ||||
|         if(app_args && strcmp(app_args, "RPC") == 0) { | ||||
|             // If app is being started in RPC mode - pass RPC context via args string
 | ||||
|             snprintf(args_temp, 16, "RPC %08lX", (uint32_t)rpc_app); | ||||
|             snprintf(args_temp, RPC_SYSTEM_APP_TEMP_ARGS_SIZE, "RPC %08lX", (uint32_t)rpc_app); | ||||
|             app_args = args_temp; | ||||
|         } | ||||
|         LoaderStatus status = loader_start(loader, app_name, app_args); | ||||
| @ -58,7 +55,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) | ||||
|         } else if(status == LoaderStatusOk) { | ||||
|             result = PB_CommandStatus_OK; | ||||
|         } else { | ||||
|             furi_assert(0); | ||||
|             furi_crash("Programming Error"); | ||||
|         } | ||||
|     } else { | ||||
|         result = PB_CommandStatus_ERROR_INVALID_PARAMETERS; | ||||
| @ -66,6 +63,7 @@ static void rpc_system_app_start_process(const PB_Main* request, void* context) | ||||
| 
 | ||||
|     furi_record_close(RECORD_LOADER); | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "StartProcess: response id %d, result %d", request->command_id, result); | ||||
|     rpc_send_and_release_empty(session, request->command_id, result); | ||||
| } | ||||
| 
 | ||||
| @ -93,6 +91,7 @@ static void rpc_system_app_lock_status_process(const PB_Main* request, void* con | ||||
| 
 | ||||
|     furi_record_close(RECORD_LOADER); | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "LockStatus: response"); | ||||
|     rpc_send_and_release(session, &response); | ||||
|     pb_release(&PB_Main_msg, &response); | ||||
| } | ||||
| @ -109,17 +108,17 @@ static void rpc_system_app_exit_request(const PB_Main* request, void* context) { | ||||
|     PB_CommandStatus status; | ||||
| 
 | ||||
|     if(rpc_app->app_callback) { | ||||
|         if(rpc_app->app_callback(RpcAppEventAppExit, NULL, rpc_app->app_context)) { | ||||
|             status = PB_CommandStatus_OK; | ||||
|             furi_timer_stop(rpc_app->timer); | ||||
|         } else { | ||||
|             status = PB_CommandStatus_ERROR_APP_CMD_ERROR; | ||||
|         } | ||||
|         FURI_LOG_D(TAG, "ExitRequest: id %d", request->command_id); | ||||
|         furi_assert(!rpc_app->last_id); | ||||
|         furi_assert(!rpc_app->last_data); | ||||
|         rpc_app->last_id = request->command_id; | ||||
|         rpc_app->app_callback(RpcAppEventAppExit, rpc_app->app_context); | ||||
|     } else { | ||||
|         status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; | ||||
|         FURI_LOG_E( | ||||
|             TAG, "ExitRequest: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status); | ||||
|         rpc_send_and_release_empty(session, request->command_id, status); | ||||
|     } | ||||
| 
 | ||||
|     rpc_send_and_release_empty(session, request->command_id, status); | ||||
| } | ||||
| 
 | ||||
| static void rpc_system_app_load_file(const PB_Main* request, void* context) { | ||||
| @ -133,17 +132,18 @@ static void rpc_system_app_load_file(const PB_Main* request, void* context) { | ||||
| 
 | ||||
|     PB_CommandStatus status; | ||||
|     if(rpc_app->app_callback) { | ||||
|         const char* file_path = request->content.app_load_file_request.path; | ||||
|         if(rpc_app->app_callback(RpcAppEventLoadFile, file_path, rpc_app->app_context)) { | ||||
|             status = PB_CommandStatus_OK; | ||||
|         } else { | ||||
|             status = PB_CommandStatus_ERROR_APP_CMD_ERROR; | ||||
|         } | ||||
|         FURI_LOG_D(TAG, "LoadFile: id %d", request->command_id); | ||||
|         furi_assert(!rpc_app->last_id); | ||||
|         furi_assert(!rpc_app->last_data); | ||||
|         rpc_app->last_id = request->command_id; | ||||
|         rpc_app->last_data = strdup(request->content.app_load_file_request.path); | ||||
|         rpc_app->app_callback(RpcAppEventLoadFile, rpc_app->app_context); | ||||
|     } else { | ||||
|         status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; | ||||
|         FURI_LOG_E( | ||||
|             TAG, "LoadFile: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status); | ||||
|         rpc_send_and_release_empty(session, request->command_id, status); | ||||
|     } | ||||
| 
 | ||||
|     rpc_send_and_release_empty(session, request->command_id, status); | ||||
| } | ||||
| 
 | ||||
| static void rpc_system_app_button_press(const PB_Main* request, void* context) { | ||||
| @ -157,18 +157,18 @@ static void rpc_system_app_button_press(const PB_Main* request, void* context) { | ||||
| 
 | ||||
|     PB_CommandStatus status; | ||||
|     if(rpc_app->app_callback) { | ||||
|         const char* args = request->content.app_button_press_request.args; | ||||
|         if(rpc_app->app_callback(RpcAppEventButtonPress, args, rpc_app->app_context)) { | ||||
|             status = PB_CommandStatus_OK; | ||||
|             furi_timer_start(rpc_app->timer, APP_BUTTON_TIMEOUT); | ||||
|         } else { | ||||
|             status = PB_CommandStatus_ERROR_APP_CMD_ERROR; | ||||
|         } | ||||
|         FURI_LOG_D(TAG, "ButtonPress"); | ||||
|         furi_assert(!rpc_app->last_id); | ||||
|         furi_assert(!rpc_app->last_data); | ||||
|         rpc_app->last_id = request->command_id; | ||||
|         rpc_app->last_data = strdup(request->content.app_button_press_request.args); | ||||
|         rpc_app->app_callback(RpcAppEventButtonPress, rpc_app->app_context); | ||||
|     } else { | ||||
|         status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; | ||||
|         FURI_LOG_E( | ||||
|             TAG, "ButtonPress: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status); | ||||
|         rpc_send_and_release_empty(session, request->command_id, status); | ||||
|     } | ||||
| 
 | ||||
|     rpc_send_and_release_empty(session, request->command_id, status); | ||||
| } | ||||
| 
 | ||||
| static void rpc_system_app_button_release(const PB_Main* request, void* context) { | ||||
| @ -182,17 +182,17 @@ static void rpc_system_app_button_release(const PB_Main* request, void* context) | ||||
| 
 | ||||
|     PB_CommandStatus status; | ||||
|     if(rpc_app->app_callback) { | ||||
|         if(rpc_app->app_callback(RpcAppEventButtonRelease, NULL, rpc_app->app_context)) { | ||||
|             status = PB_CommandStatus_OK; | ||||
|             furi_timer_stop(rpc_app->timer); | ||||
|         } else { | ||||
|             status = PB_CommandStatus_ERROR_APP_CMD_ERROR; | ||||
|         } | ||||
|         FURI_LOG_D(TAG, "ButtonRelease"); | ||||
|         furi_assert(!rpc_app->last_id); | ||||
|         furi_assert(!rpc_app->last_data); | ||||
|         rpc_app->last_id = request->command_id; | ||||
|         rpc_app->app_callback(RpcAppEventButtonRelease, rpc_app->app_context); | ||||
|     } else { | ||||
|         status = PB_CommandStatus_ERROR_APP_NOT_RUNNING; | ||||
|         FURI_LOG_E( | ||||
|             TAG, "ButtonRelease: APP_NOT_RUNNING, id %d, status: %d", request->command_id, status); | ||||
|         rpc_send_and_release_empty(session, request->command_id, status); | ||||
|     } | ||||
| 
 | ||||
|     rpc_send_and_release_empty(session, request->command_id, status); | ||||
| } | ||||
| 
 | ||||
| void rpc_system_app_send_started(RpcAppSystem* rpc_app) { | ||||
| @ -201,6 +201,8 @@ void rpc_system_app_send_started(RpcAppSystem* rpc_app) { | ||||
|     furi_assert(session); | ||||
| 
 | ||||
|     rpc_app->state_msg->content.app_state_response.state = PB_App_AppState_APP_STARTED; | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "SendStarted"); | ||||
|     rpc_send(session, rpc_app->state_msg); | ||||
| } | ||||
| 
 | ||||
| @ -210,9 +212,46 @@ void rpc_system_app_send_exited(RpcAppSystem* rpc_app) { | ||||
|     furi_assert(session); | ||||
| 
 | ||||
|     rpc_app->state_msg->content.app_state_response.state = PB_App_AppState_APP_CLOSED; | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "SendExit"); | ||||
|     rpc_send(session, rpc_app->state_msg); | ||||
| } | ||||
| 
 | ||||
| const char* rpc_system_app_get_data(RpcAppSystem* rpc_app) { | ||||
|     furi_assert(rpc_app); | ||||
|     furi_assert(rpc_app->last_data); | ||||
|     return rpc_app->last_data; | ||||
| } | ||||
| 
 | ||||
| void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool result) { | ||||
|     furi_assert(rpc_app); | ||||
|     RpcSession* session = rpc_app->session; | ||||
|     furi_assert(session); | ||||
|     furi_assert(rpc_app->last_id); | ||||
| 
 | ||||
|     PB_CommandStatus status = result ? PB_CommandStatus_OK : PB_CommandStatus_ERROR_APP_CMD_ERROR; | ||||
| 
 | ||||
|     uint32_t last_id = 0; | ||||
|     switch(event) { | ||||
|     case RpcAppEventAppExit: | ||||
|     case RpcAppEventLoadFile: | ||||
|     case RpcAppEventButtonPress: | ||||
|     case RpcAppEventButtonRelease: | ||||
|         last_id = rpc_app->last_id; | ||||
|         rpc_app->last_id = 0; | ||||
|         if(rpc_app->last_data) { | ||||
|             free(rpc_app->last_data); | ||||
|             rpc_app->last_data = NULL; | ||||
|         } | ||||
|         FURI_LOG_D(TAG, "AppConfirm: event %d last_id %d status %d", event, last_id, status); | ||||
|         rpc_send_and_release_empty(session, last_id, status); | ||||
|         break; | ||||
|     default: | ||||
|         furi_crash("RPC App state programming Error"); | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void rpc_system_app_set_callback(RpcAppSystem* rpc_app, RpcAppSystemCallback callback, void* ctx) { | ||||
|     furi_assert(rpc_app); | ||||
| 
 | ||||
| @ -226,8 +265,6 @@ void* rpc_system_app_alloc(RpcSession* session) { | ||||
|     RpcAppSystem* rpc_app = malloc(sizeof(RpcAppSystem)); | ||||
|     rpc_app->session = session; | ||||
| 
 | ||||
|     rpc_app->timer = furi_timer_alloc(rpc_system_app_timer_callback, FuriTimerTypeOnce, rpc_app); | ||||
| 
 | ||||
|     // App exit message
 | ||||
|     rpc_app->state_msg = malloc(sizeof(PB_Main)); | ||||
|     rpc_app->state_msg->which_content = PB_Main_app_state_response_tag; | ||||
| @ -265,12 +302,16 @@ void rpc_system_app_free(void* context) { | ||||
|     RpcSession* session = rpc_app->session; | ||||
|     furi_assert(session); | ||||
| 
 | ||||
|     furi_timer_free(rpc_app->timer); | ||||
| 
 | ||||
|     if(rpc_app->app_callback) { | ||||
|         rpc_app->app_callback(RpcAppEventSessionClose, NULL, rpc_app->app_context); | ||||
|         rpc_app->app_callback(RpcAppEventSessionClose, rpc_app->app_context); | ||||
|     } | ||||
| 
 | ||||
|     while(rpc_app->app_callback) { | ||||
|         furi_delay_tick(1); | ||||
|     } | ||||
| 
 | ||||
|     if(rpc_app->last_data) free(rpc_app->last_data); | ||||
| 
 | ||||
|     free(rpc_app->state_msg); | ||||
|     free(rpc_app); | ||||
| } | ||||
|  | ||||
| @ -13,7 +13,7 @@ typedef enum { | ||||
|     RpcAppEventButtonRelease, | ||||
| } RpcAppSystemEvent; | ||||
| 
 | ||||
| typedef bool (*RpcAppSystemCallback)(RpcAppSystemEvent event, const char* arg, void* context); | ||||
| typedef void (*RpcAppSystemCallback)(RpcAppSystemEvent event, void* context); | ||||
| 
 | ||||
| typedef struct RpcAppSystem RpcAppSystem; | ||||
| 
 | ||||
| @ -23,6 +23,10 @@ void rpc_system_app_send_started(RpcAppSystem* rpc_app); | ||||
| 
 | ||||
| void rpc_system_app_send_exited(RpcAppSystem* rpc_app); | ||||
| 
 | ||||
| const char* rpc_system_app_get_data(RpcAppSystem* rpc_app); | ||||
| 
 | ||||
| void rpc_system_app_confirm(RpcAppSystem* rpc_app, RpcAppSystemEvent event, bool result); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -594,23 +594,19 @@ static void rpc_system_storage_backup_create_process(const PB_Main* request, voi | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "BackupCreate"); | ||||
| 
 | ||||
|     RpcSession* session = (RpcSession*)context; | ||||
|     RpcStorageSystem* rpc_storage = context; | ||||
|     RpcSession* session = rpc_storage->session; | ||||
|     furi_assert(session); | ||||
| 
 | ||||
|     PB_Main* response = malloc(sizeof(PB_Main)); | ||||
|     response->command_id = request->command_id; | ||||
|     response->has_next = false; | ||||
| 
 | ||||
|     Storage* fs_api = furi_record_open(RECORD_STORAGE); | ||||
| 
 | ||||
|     bool backup_ok = | ||||
|         lfs_backup_create(fs_api, request->content.storage_backup_create_request.archive_path); | ||||
|     response->command_status = backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR; | ||||
| 
 | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
| 
 | ||||
|     rpc_send_and_release(session, response); | ||||
|     free(response); | ||||
|     rpc_send_and_release_empty( | ||||
|         session, request->command_id, backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR); | ||||
| } | ||||
| 
 | ||||
| static void rpc_system_storage_backup_restore_process(const PB_Main* request, void* context) { | ||||
| @ -619,24 +615,19 @@ static void rpc_system_storage_backup_restore_process(const PB_Main* request, vo | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "BackupRestore"); | ||||
| 
 | ||||
|     RpcSession* session = (RpcSession*)context; | ||||
|     RpcStorageSystem* rpc_storage = context; | ||||
|     RpcSession* session = rpc_storage->session; | ||||
|     furi_assert(session); | ||||
| 
 | ||||
|     PB_Main* response = malloc(sizeof(PB_Main)); | ||||
|     response->command_id = request->command_id; | ||||
|     response->has_next = false; | ||||
|     response->command_status = PB_CommandStatus_OK; | ||||
| 
 | ||||
|     Storage* fs_api = furi_record_open(RECORD_STORAGE); | ||||
| 
 | ||||
|     bool backup_ok = | ||||
|         lfs_backup_unpack(fs_api, request->content.storage_backup_restore_request.archive_path); | ||||
|     response->command_status = backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR; | ||||
| 
 | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
| 
 | ||||
|     rpc_send_and_release(session, response); | ||||
|     free(response); | ||||
|     rpc_send_and_release_empty( | ||||
|         session, request->command_id, backup_ok ? PB_CommandStatus_OK : PB_CommandStatus_ERROR); | ||||
| } | ||||
| 
 | ||||
| void* rpc_system_storage_alloc(RpcSession* session) { | ||||
|  | ||||
| @ -26,8 +26,7 @@ static FS_Error storage_ext_parse_error(SDError error); | ||||
| 
 | ||||
| static bool sd_mount_card(StorageData* storage, bool notify) { | ||||
|     bool result = false; | ||||
|     const uint8_t max_init_counts = 10; | ||||
|     uint8_t counter = max_init_counts; | ||||
|     uint8_t counter = BSP_SD_MaxMountRetryCount(); | ||||
|     uint8_t bsp_result; | ||||
|     SDData* sd_data = storage->data; | ||||
| 
 | ||||
|  | ||||
| @ -47,6 +47,9 @@ typedef enum { | ||||
|     SubGhzCustomEventSceneStay, | ||||
| 
 | ||||
|     SubGhzCustomEventSceneRpcLoad, | ||||
|     SubGhzCustomEventSceneRpcButtonPress, | ||||
|     SubGhzCustomEventSceneRpcButtonRelease, | ||||
|     SubGhzCustomEventSceneRpcSessionClose, | ||||
| 
 | ||||
|     SubGhzCustomEventViewReceiverOK, | ||||
|     SubGhzCustomEventViewReceiverConfig, | ||||
|  | ||||
| @ -72,11 +72,11 @@ typedef enum { | ||||
|     SubGhzViewIdTestPacket, | ||||
| } SubGhzViewId; | ||||
| 
 | ||||
| struct SubGhzPesetDefinition { | ||||
| struct SubGhzPresetDefinition { | ||||
|     string_t name; | ||||
|     uint32_t frequency; | ||||
|     uint8_t* data; | ||||
|     size_t data_size; | ||||
| }; | ||||
| 
 | ||||
| typedef struct SubGhzPesetDefinition SubGhzPesetDefinition; | ||||
| typedef struct SubGhzPresetDefinition SubGhzPresetDefinition; | ||||
|  | ||||
| @ -28,8 +28,8 @@ static bool subghz_scene_receiver_info_update_parser(void* context) { | ||||
|             subghz->txrx->decoder_result, | ||||
|             subghz_history_get_raw_data(subghz->txrx->history, subghz->txrx->idx_menu_chosen)); | ||||
| 
 | ||||
|         SubGhzPesetDefinition* preset = | ||||
|             subghz_history_get_presset(subghz->txrx->history, subghz->txrx->idx_menu_chosen); | ||||
|         SubGhzPresetDefinition* preset = | ||||
|             subghz_history_get_preset_def(subghz->txrx->history, subghz->txrx->idx_menu_chosen); | ||||
|         subghz_preset_init( | ||||
|             subghz, | ||||
|             string_get_cstr(preset->name), | ||||
|  | ||||
| @ -22,20 +22,60 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) { | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         consumed = true; | ||||
|         if(event.event == SubGhzCustomEventSceneExit) { | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { | ||||
|                 subghz_tx_stop(subghz); | ||||
|                 subghz_sleep(subghz); | ||||
|             } | ||||
|             view_dispatcher_stop(subghz->view_dispatcher); | ||||
|             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventAppExit, true); | ||||
|         } else if(event.event == SubGhzCustomEventSceneRpcSessionClose) { | ||||
|             rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL); | ||||
|             subghz->rpc_ctx = NULL; | ||||
|             subghz_blink_stop(subghz); | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { | ||||
|                 subghz_tx_stop(subghz); | ||||
|                 subghz_sleep(subghz); | ||||
|             } | ||||
|             view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit); | ||||
|         } else if(event.event == SubGhzCustomEventSceneRpcButtonPress) { | ||||
|             bool result = false; | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateSleep) { | ||||
|                 subghz_blink_start(subghz); | ||||
|                 result = subghz_tx_start(subghz, subghz->txrx->fff_data); | ||||
|                 result = true; | ||||
|             } | ||||
|             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonPress, result); | ||||
|         } else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) { | ||||
|             bool result = false; | ||||
|             if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { | ||||
|                 subghz_blink_stop(subghz); | ||||
|                 subghz_tx_stop(subghz); | ||||
|                 subghz_sleep(subghz); | ||||
|                 result = true; | ||||
|             } | ||||
|             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventButtonRelease, result); | ||||
|         } else if(event.event == SubGhzCustomEventSceneRpcLoad) { | ||||
|             string_t file_name; | ||||
|             string_init(file_name); | ||||
|             path_extract_filename(subghz->file_path, file_name, true); | ||||
|             bool result = false; | ||||
|             const char* arg = rpc_system_app_get_data(subghz->rpc_ctx); | ||||
|             if(arg) { | ||||
|                 if(subghz_key_load(subghz, arg, false)) { | ||||
|                     string_set_str(subghz->file_path, arg); | ||||
|                     result = true; | ||||
|                     string_t file_name; | ||||
|                     string_init(file_name); | ||||
|                     path_extract_filename(subghz->file_path, file_name, true); | ||||
| 
 | ||||
|             snprintf( | ||||
|                 subghz->file_name_tmp, | ||||
|                 SUBGHZ_MAX_LEN_NAME, | ||||
|                 "loaded\n%s", | ||||
|                 string_get_cstr(file_name)); | ||||
|             popup_set_text(popup, subghz->file_name_tmp, 82, 32, AlignCenter, AlignTop); | ||||
|                     snprintf( | ||||
|                         subghz->file_name_tmp, | ||||
|                         SUBGHZ_MAX_LEN_NAME, | ||||
|                         "loaded\n%s", | ||||
|                         string_get_cstr(file_name)); | ||||
|                     popup_set_text(popup, subghz->file_name_tmp, 82, 32, AlignCenter, AlignTop); | ||||
| 
 | ||||
|             string_clear(file_name); | ||||
|                     string_clear(file_name); | ||||
|                 } | ||||
|             } | ||||
|             rpc_system_app_confirm(subghz->rpc_ctx, RpcAppEventLoadFile, result); | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
|  | ||||
| @ -35,57 +35,38 @@ void subghz_tick_event_callback(void* context) { | ||||
|     scene_manager_handle_tick_event(subghz->scene_manager); | ||||
| } | ||||
| 
 | ||||
| static bool subghz_rpc_command_callback(RpcAppSystemEvent event, const char* arg, void* context) { | ||||
| static void subghz_rpc_command_callback(RpcAppSystemEvent event, void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhz* subghz = context; | ||||
| 
 | ||||
|     if(!subghz->rpc_ctx) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool result = false; | ||||
|     furi_assert(subghz->rpc_ctx); | ||||
| 
 | ||||
|     if(event == RpcAppEventSessionClose) { | ||||
|         rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL); | ||||
|         subghz->rpc_ctx = NULL; | ||||
|         notification_message(subghz->notifications, &sequence_blink_stop); | ||||
|         view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit); | ||||
|         if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { | ||||
|             subghz_tx_stop(subghz); | ||||
|             subghz_sleep(subghz); | ||||
|         } | ||||
|         result = true; | ||||
|         view_dispatcher_send_custom_event( | ||||
|             subghz->view_dispatcher, SubGhzCustomEventSceneRpcSessionClose); | ||||
|     } else if(event == RpcAppEventAppExit) { | ||||
|         view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneExit); | ||||
|         if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { | ||||
|             subghz_tx_stop(subghz); | ||||
|             subghz_sleep(subghz); | ||||
|         } | ||||
|         result = true; | ||||
|     } else if(event == RpcAppEventLoadFile) { | ||||
|         if(arg) { | ||||
|             if(subghz_key_load(subghz, arg, false)) { | ||||
|                 string_set_str(subghz->file_path, arg); | ||||
|                 view_dispatcher_send_custom_event( | ||||
|                     subghz->view_dispatcher, SubGhzCustomEventSceneRpcLoad); | ||||
|                 result = true; | ||||
|             } | ||||
|         } | ||||
|         view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneRpcLoad); | ||||
|     } else if(event == RpcAppEventButtonPress) { | ||||
|         if(subghz->txrx->txrx_state == SubGhzTxRxStateSleep) { | ||||
|             notification_message(subghz->notifications, &sequence_blink_start_magenta); | ||||
|             result = subghz_tx_start(subghz, subghz->txrx->fff_data); | ||||
|         } | ||||
|         view_dispatcher_send_custom_event( | ||||
|             subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonPress); | ||||
|     } else if(event == RpcAppEventButtonRelease) { | ||||
|         if(subghz->txrx->txrx_state == SubGhzTxRxStateTx) { | ||||
|             notification_message(subghz->notifications, &sequence_blink_stop); | ||||
|             subghz_tx_stop(subghz); | ||||
|             subghz_sleep(subghz); | ||||
|             result = true; | ||||
|         } | ||||
|         view_dispatcher_send_custom_event( | ||||
|             subghz->view_dispatcher, SubGhzCustomEventSceneRpcButtonRelease); | ||||
|     } else { | ||||
|         rpc_system_app_confirm(subghz->rpc_ctx, event, false); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     return result; | ||||
| void subghz_blink_start(SubGhz* instance) { | ||||
|     furi_assert(instance); | ||||
|     notification_message(instance->notifications, &sequence_blink_start_magenta); | ||||
| } | ||||
| 
 | ||||
| void subghz_blink_stop(SubGhz* instance) { | ||||
|     furi_assert(instance); | ||||
|     notification_message(instance->notifications, &sequence_blink_stop); | ||||
| } | ||||
| 
 | ||||
| SubGhz* subghz_alloc() { | ||||
| @ -199,7 +180,7 @@ SubGhz* subghz_alloc() { | ||||
|     //init Worker & Protocol & History & KeyBoard
 | ||||
|     subghz->lock = SubGhzLockOff; | ||||
|     subghz->txrx = malloc(sizeof(SubGhzTxRx)); | ||||
|     subghz->txrx->preset = malloc(sizeof(SubGhzPesetDefinition)); | ||||
|     subghz->txrx->preset = malloc(sizeof(SubGhzPresetDefinition)); | ||||
|     string_init(subghz->txrx->preset->name); | ||||
|     subghz_preset_init( | ||||
|         subghz, "AM650", subghz_setting_get_default_frequency(subghz->setting), NULL, 0); | ||||
| @ -237,7 +218,7 @@ void subghz_free(SubGhz* subghz) { | ||||
|     if(subghz->rpc_ctx) { | ||||
|         rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL); | ||||
|         rpc_system_app_send_exited(subghz->rpc_ctx); | ||||
|         notification_message(subghz->notifications, &sequence_blink_stop); | ||||
|         subghz_blink_stop(subghz); | ||||
|         subghz->rpc_ctx = NULL; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -12,7 +12,7 @@ typedef struct { | ||||
|     string_t item_str; | ||||
|     FlipperFormat* flipper_string; | ||||
|     uint8_t type; | ||||
|     SubGhzPesetDefinition* preset; | ||||
|     SubGhzPresetDefinition* preset; | ||||
| } SubGhzHistoryItem; | ||||
| 
 | ||||
| ARRAY_DEF(SubGhzHistoryItemArray, SubGhzHistoryItem, M_POD_OPLIST) | ||||
| @ -61,7 +61,7 @@ uint32_t subghz_history_get_frequency(SubGhzHistory* instance, uint16_t idx) { | ||||
|     return item->preset->frequency; | ||||
| } | ||||
| 
 | ||||
| SubGhzPesetDefinition* subghz_history_get_presset(SubGhzHistory* instance, uint16_t idx) { | ||||
| SubGhzPresetDefinition* subghz_history_get_preset_def(SubGhzHistory* instance, uint16_t idx) { | ||||
|     furi_assert(instance); | ||||
|     SubGhzHistoryItem* item = SubGhzHistoryItemArray_get(instance->history->data, idx); | ||||
|     return item->preset; | ||||
| @ -139,7 +139,7 @@ void subghz_history_get_text_item_menu(SubGhzHistory* instance, string_t output, | ||||
| bool subghz_history_add_to_history( | ||||
|     SubGhzHistory* instance, | ||||
|     void* context, | ||||
|     SubGhzPesetDefinition* preset) { | ||||
|     SubGhzPresetDefinition* preset) { | ||||
|     furi_assert(instance); | ||||
|     furi_assert(context); | ||||
| 
 | ||||
| @ -159,7 +159,7 @@ bool subghz_history_add_to_history( | ||||
|     string_t text; | ||||
|     string_init(text); | ||||
|     SubGhzHistoryItem* item = SubGhzHistoryItemArray_push_raw(instance->history->data); | ||||
|     item->preset = malloc(sizeof(SubGhzPesetDefinition)); | ||||
|     item->preset = malloc(sizeof(SubGhzPresetDefinition)); | ||||
|     item->type = decoder_base->protocol->type; | ||||
|     item->preset->frequency = preset->frequency; | ||||
|     string_init(item->preset->name); | ||||
|  | ||||
| @ -35,7 +35,7 @@ void subghz_history_reset(SubGhzHistory* instance); | ||||
|  */ | ||||
| uint32_t subghz_history_get_frequency(SubGhzHistory* instance, uint16_t idx); | ||||
| 
 | ||||
| SubGhzPesetDefinition* subghz_history_get_presset(SubGhzHistory* instance, uint16_t idx); | ||||
| SubGhzPresetDefinition* subghz_history_get_preset_def(SubGhzHistory* instance, uint16_t idx); | ||||
| 
 | ||||
| /** Get preset to history[idx]
 | ||||
|  *  | ||||
| @ -88,13 +88,13 @@ bool subghz_history_get_text_space_left(SubGhzHistory* instance, string_t output | ||||
|  *  | ||||
|  * @param instance  - SubGhzHistory instance | ||||
|  * @param context    - SubGhzProtocolCommon context | ||||
|  * @param preset    - SubGhzPesetDefinition preset | ||||
|  * @param preset    - SubGhzPresetDefinition preset | ||||
|  * @return bool; | ||||
|  */ | ||||
| bool subghz_history_add_to_history( | ||||
|     SubGhzHistory* instance, | ||||
|     void* context, | ||||
|     SubGhzPesetDefinition* preset); | ||||
|     SubGhzPresetDefinition* preset); | ||||
| 
 | ||||
| /** Get SubGhzProtocolCommonLoad to load into the protocol decoder bin data
 | ||||
|  *  | ||||
|  | ||||
| @ -331,8 +331,10 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) { | ||||
|         subghz->txrx->decoder_result = subghz_receiver_search_decoder_base_by_name( | ||||
|             subghz->txrx->receiver, string_get_cstr(temp_str)); | ||||
|         if(subghz->txrx->decoder_result) { | ||||
|             subghz_protocol_decoder_base_deserialize( | ||||
|                 subghz->txrx->decoder_result, subghz->txrx->fff_data); | ||||
|             if(!subghz_protocol_decoder_base_deserialize( | ||||
|                    subghz->txrx->decoder_result, subghz->txrx->fff_data)) { | ||||
|                 break; | ||||
|             } | ||||
|         } else { | ||||
|             FURI_LOG_E(TAG, "Protocol not found"); | ||||
|             break; | ||||
|  | ||||
| @ -49,7 +49,7 @@ struct SubGhzTxRx { | ||||
|     SubGhzProtocolDecoderBase* decoder_result; | ||||
|     FlipperFormat* fff_data; | ||||
| 
 | ||||
|     SubGhzPesetDefinition* preset; | ||||
|     SubGhzPresetDefinition* preset; | ||||
|     SubGhzHistory* history; | ||||
|     uint16_t idx_menu_chosen; | ||||
|     SubGhzTxRxState txrx_state; | ||||
| @ -108,6 +108,10 @@ void subghz_begin(SubGhz* subghz, uint8_t* preset_data); | ||||
| uint32_t subghz_rx(SubGhz* subghz, uint32_t frequency); | ||||
| void subghz_rx_end(SubGhz* subghz); | ||||
| void subghz_sleep(SubGhz* subghz); | ||||
| 
 | ||||
| void subghz_blink_start(SubGhz* instance); | ||||
| void subghz_blink_stop(SubGhz* instance); | ||||
| 
 | ||||
| bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format); | ||||
| void subghz_tx_stop(SubGhz* subghz); | ||||
| void subghz_dialog_message_show_only_rx(SubGhz* subghz); | ||||
|  | ||||
| @ -216,8 +216,8 @@ void subghz_read_raw_draw(Canvas* canvas, SubGhzReadRAWModel* model) { | ||||
|     uint8_t graphics_mode = 1; | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     canvas_draw_str(canvas, 5, 8, string_get_cstr(model->frequency_str)); | ||||
|     canvas_draw_str(canvas, 40, 8, string_get_cstr(model->preset_str)); | ||||
|     canvas_draw_str(canvas, 5, 7, string_get_cstr(model->frequency_str)); | ||||
|     canvas_draw_str(canvas, 40, 7, string_get_cstr(model->preset_str)); | ||||
|     canvas_draw_str_aligned( | ||||
|         canvas, 126, 0, AlignRight, AlignTop, string_get_cstr(model->sample_write)); | ||||
| 
 | ||||
|  | ||||
| @ -387,6 +387,34 @@ MU_TEST(stream_split_test) { | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(stream_buffered_write_after_read_test) { | ||||
|     const char* prefix = "I write "; | ||||
|     const char* substr = "Hello there"; | ||||
| 
 | ||||
|     const size_t substr_len = strlen(substr); | ||||
|     const size_t prefix_len = strlen(prefix); | ||||
|     const size_t buf_size = substr_len + 1; | ||||
| 
 | ||||
|     char buf[buf_size]; | ||||
|     memset(buf, 0, buf_size); | ||||
| 
 | ||||
|     Storage* storage = furi_record_open(RECORD_STORAGE); | ||||
|     Stream* stream = buffered_file_stream_alloc(storage); | ||||
|     mu_check(buffered_file_stream_open( | ||||
|         stream, EXT_PATH("filestream.str"), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)); | ||||
|     mu_assert_int_eq(strlen(stream_test_data), stream_write_cstring(stream, stream_test_data)); | ||||
|     mu_check(stream_rewind(stream)); | ||||
|     mu_assert_int_eq(prefix_len, stream_read(stream, (uint8_t*)buf, prefix_len)); | ||||
|     mu_assert_string_eq(prefix, buf); | ||||
|     mu_assert_int_eq(substr_len, stream_write(stream, (uint8_t*)substr, substr_len)); | ||||
|     mu_check(stream_seek(stream, prefix_len, StreamOffsetFromStart)); | ||||
|     mu_assert_int_eq(substr_len, stream_read(stream, (uint8_t*)buf, substr_len)); | ||||
|     mu_assert_string_eq(substr, buf); | ||||
| 
 | ||||
|     stream_free(stream); | ||||
|     furi_record_close(RECORD_STORAGE); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(stream_buffered_large_file_test) { | ||||
|     string_t input_data; | ||||
|     string_t output_data; | ||||
| @ -470,6 +498,7 @@ MU_TEST_SUITE(stream_suite) { | ||||
|     MU_RUN_TEST(stream_write_read_save_load_test); | ||||
|     MU_RUN_TEST(stream_composite_test); | ||||
|     MU_RUN_TEST(stream_split_test); | ||||
|     MU_RUN_TEST(stream_buffered_write_after_read_test); | ||||
|     MU_RUN_TEST(stream_buffered_large_file_test); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -103,8 +103,7 @@ if assetsenv["IS_BASE_FIRMWARE"]: | ||||
|     ) | ||||
|     assetsenv.Precious(resources) | ||||
|     assetsenv.NoClean(resources) | ||||
|     if assetsenv["FORCE"]: | ||||
|         assetsenv.AlwaysBuild(resources) | ||||
|     assetsenv.AlwaysBuild(resources) | ||||
| 
 | ||||
|     # Exporting resources node to external environment | ||||
|     env["FW_RESOURCES"] = resources | ||||
|  | ||||
| Before Width: | Height: | Size: 301 B | 
| Before Width: | Height: | Size: 3.6 KiB | 
| Before Width: | Height: | Size: 3.6 KiB | 
| Before Width: | Height: | Size: 624 B | 
| Before Width: | Height: | Size: 558 B | 
| Before Width: | Height: | Size: 574 B | 
| Before Width: | Height: | Size: 527 B | 
| Before Width: | Height: | Size: 556 B | 
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 553 B | 
| Before Width: | Height: | Size: 423 B | 
| Before Width: | Height: | Size: 4.1 KiB | 
| Before Width: | Height: | Size: 3.9 KiB | 
| Before Width: | Height: | Size: 4.1 KiB | 
| Before Width: | Height: | Size: 372 B | 
| Before Width: | Height: | Size: 364 B | 
| Before Width: | Height: | Size: 298 B | 
| Before Width: | Height: | Size: 100 B | 
| Before Width: | Height: | Size: 99 B | 
| Before Width: | Height: | Size: 99 B | 
| Before Width: | Height: | Size: 96 B | 
| Before Width: | Height: | Size: 97 B | 
| Before Width: | Height: | Size: 99 B | 
| @ -1 +0,0 @@ | ||||
| 3 | ||||
| Before Width: | Height: | Size: 96 B | 
| Before Width: | Height: | Size: 99 B | 
| Before Width: | Height: | Size: 105 B | 
| Before Width: | Height: | Size: 104 B | 
| Before Width: | Height: | Size: 105 B | 
| Before Width: | Height: | Size: 102 B | 
| Before Width: | Height: | Size: 101 B | 
| Before Width: | Height: | Size: 102 B | 
| Before Width: | Height: | Size: 100 B | 
| @ -1 +0,0 @@ | ||||
| 3 | ||||
| Before Width: | Height: | Size: 98 B | 
| Before Width: | Height: | Size: 81 B | 
| Before Width: | Height: | Size: 86 B | 
| Before Width: | Height: | Size: 89 B | 
| Before Width: | Height: | Size: 89 B | 
| Before Width: | Height: | Size: 94 B | 
| Before Width: | Height: | Size: 97 B | 
| Before Width: | Height: | Size: 97 B | 
| Before Width: | Height: | Size: 99 B | 
| Before Width: | Height: | Size: 98 B | 
| @ -1 +0,0 @@ | ||||
| 3 | ||||
| Before Width: | Height: | Size: 326 B | 
| @ -1 +0,0 @@ | ||||
| 3 | ||||
| Before Width: | Height: | Size: 106 B | 
| Before Width: | Height: | Size: 106 B | 
| Before Width: | Height: | Size: 106 B | 
| Before Width: | Height: | Size: 106 B | 
| Before Width: | Height: | Size: 106 B | 
| Before Width: | Height: | Size: 106 B | 
| @ -1 +0,0 @@ | ||||
| 3 | ||||
| Before Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 83 B | 
| Before Width: | Height: | Size: 81 B | 
| Before Width: | Height: | Size: 312 B | 
| Before Width: | Height: | Size: 318 B | 
| @ -1,5 +1,5 @@ | ||||
| V:0 | ||||
| T:1655152832 | ||||
| T:1658906571 | ||||
| D:badusb | ||||
| D:dolphin | ||||
| D:infrared | ||||
| @ -232,10 +232,10 @@ F:41b4f08774249014cb8d3dffa5f5c07d:1757:nfc/assets/currency_code.nfc | ||||
| F:c60e862919731b0bd538a1001bbc1098:17453:nfc/assets/mf_classic_dict.nfc | ||||
| D:subghz/assets | ||||
| F:dda1ef895b8a25fde57c874feaaef997:650:subghz/assets/came_atomo | ||||
| F:610a0ffa2479a874f2060eb2348104c5:2712:subghz/assets/keeloq_mfcodes | ||||
| F:788eef2cc74e29f3388463d6607dab0d:3264:subghz/assets/keeloq_mfcodes | ||||
| F:9214f9c10463b746a27e82ce0b96e040:465:subghz/assets/keeloq_mfcodes_user | ||||
| F:653bd8d349055a41e1152e557d4a52d3:202:subghz/assets/nice_flor_s | ||||
| F:c6ec4374275cd20f482ecd46de9f53e3:528:subghz/assets/setting_user | ||||
| F:c1c63fbd5f5aa3ea504027014652191f:1150:subghz/assets/setting_user | ||||
| D:u2f/assets | ||||
| F:7e11e688e39034bbb9d88410044795e1:365:u2f/assets/cert.der | ||||
| F:f60b88c20ed479ed9684e249f7134618:264:u2f/assets/cert_key.u2f | ||||
|  | ||||
							
								
								
									
										90
									
								
								debug/fw.jflash
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,90 @@ | ||||
|   AppVersion = 76803 | ||||
|   FileVersion = 2 | ||||
| [GENERAL] | ||||
|   aATEModuleSel[24] = 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 | ||||
|   ConnectMode = 0 | ||||
|   CurrentFile = "..\build\latest\firmware.bin" | ||||
|   DataFileSAddr = 0x08000000 | ||||
|   GUIMode = 0 | ||||
|   HostName = "" | ||||
|   TargetIF = 1 | ||||
|   USBPort = 0 | ||||
|   USBSerialNo = 0x00000000 | ||||
|   UseATEModuleSelection = 0 | ||||
| [JTAG] | ||||
|   IRLen = 0 | ||||
|   MultipleTargets = 0 | ||||
|   NumDevices = 0 | ||||
|   Speed0 = 8000 | ||||
|   Speed1 = 8000 | ||||
|   TAP_Number = 0 | ||||
|   UseAdaptive0 = 0 | ||||
|   UseAdaptive1 = 0 | ||||
|   UseMaxSpeed0 = 0 | ||||
|   UseMaxSpeed1 = 0 | ||||
| [CPU] | ||||
|   NumInitSteps = 2 | ||||
|   InitStep0_Action = "Reset" | ||||
|   InitStep0_Value0 = 0x00000000 | ||||
|   InitStep0_Value1 = 0x00000000 | ||||
|   InitStep0_Comment = "" | ||||
|   InitStep1_Action = "Halt" | ||||
|   InitStep1_Value0 = 0xFFFFFFFF | ||||
|   InitStep1_Value1 = 0xFFFFFFFF | ||||
|   InitStep1_Comment = "" | ||||
|   NumExitSteps = 1 | ||||
|   ExitStep0_Action = "Reset" | ||||
|   ExitStep0_Value0 = 0x00000005 | ||||
|   ExitStep0_Value1 = 0x00000032 | ||||
|   ExitStep0_Comment = "" | ||||
|   UseScriptFile = 0 | ||||
|   ScriptFile = "" | ||||
|   UseRAM = 1 | ||||
|   RAMAddr = 0x20000000 | ||||
|   RAMSize = 0x00030000 | ||||
|   CheckCoreID = 1 | ||||
|   CoreID = 0x6BA02477 | ||||
|   CoreIDMask = 0x0F000FFF | ||||
|   UseAutoSpeed = 0x00000001 | ||||
|   ClockSpeed = 0x00000000 | ||||
|   EndianMode = 0 | ||||
|   ChipName = "ST STM32WB55RG" | ||||
| [FLASH] | ||||
|   aRangeSel[1] = 0-255 | ||||
|   BankName = "Internal flash" | ||||
|   BankSelMode = 1 | ||||
|   BaseAddr = 0x08000000 | ||||
|   NumBanks = 1 | ||||
| [PRODUCTION] | ||||
|   AutoPerformsDisconnect = 0 | ||||
|   AutoPerformsErase = 1 | ||||
|   AutoPerformsProgram = 1 | ||||
|   AutoPerformsSecure = 0 | ||||
|   AutoPerformsStartApp = 1 | ||||
|   AutoPerformsUnsecure = 0 | ||||
|   AutoPerformsVerify = 0 | ||||
|   EnableFixedVTref = 0 | ||||
|   EnableTargetPower = 0 | ||||
|   EraseType = 1 | ||||
|   FixedVTref = 0x00000CE4 | ||||
|   MonitorVTref = 0 | ||||
|   MonitorVTrefMax = 0x0000157C | ||||
|   MonitorVTrefMin = 0x000003E8 | ||||
|   OverrideTimeouts = 0 | ||||
|   ProgramSN = 0 | ||||
|   SerialFile = "" | ||||
|   SNAddr = 0x00000000 | ||||
|   SNInc = 0x00000001 | ||||
|   SNLen = 0x00000004 | ||||
|   SNListFile = "" | ||||
|   SNValue = 0x00000001 | ||||
|   StartAppType = 1 | ||||
|   TargetPowerDelay = 0x00000014 | ||||
|   TimeoutErase = 0x00003A98 | ||||
|   TimeoutProgram = 0x00002710 | ||||
|   TimeoutVerify = 0x00002710 | ||||
|   VerifyType = 1 | ||||
| [PERFORMANCE] | ||||
|   DisableSkipBlankDataOnProgram = 0x00000000 | ||||
|   PerfromBlankCheckPriorEraseChip = 0x00000001 | ||||
|   PerfromBlankCheckPriorEraseSelectedSectors = 0x00000001 | ||||
 Aleksandr Kutuzov
						Aleksandr Kutuzov