diff --git a/.clang-format b/.clang-format
index 3337a7409cf..4b76f7fa43b 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,86 +1,191 @@
+---
+Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
-AlignConsecutiveAssignments: false
-AlignConsecutiveDeclarations: false
+AlignArrayOfStructures: None
+AlignConsecutiveMacros: None
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Left
-AlignOperands: true
+AlignOperands: Align
AlignTrailingComments: false
+AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
-AllowShortIfStatementsOnASingleLine: true
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
-AlwaysBreakTemplateDeclarations: false
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+ - __capability
BinPackArguments: false
BinPackParameters: false
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: Never
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
-ColumnLimit: 99
+ColumnLimit: 99
+CommentPragmas: '^ IWYU pragma:'
+QualifierAlignment: Leave
CompactNamespaces: false
-ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
+DeriveLineEnding: true
DerivePointerAlignment: false
-DisableFormat: false
+DisableFormat: false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: BinPack
+BasedOnStyle: ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
FixNamespaceComments: false
-
-IncludeBlocks: Preserve
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IfMacros:
+ - KJ_IF_MAYBE
+IncludeBlocks: Preserve
IncludeCategories:
- - Regex: '.*'
- Priority: 1
+ - Regex: '.*'
+ Priority: 1
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '^(<|"(gtest|gmock|isl|json)/)'
+ Priority: 3
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '.*'
+ Priority: 1
+ SortPriority: 0
+ CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
IndentCaseLabels: false
+IndentCaseBlocks: false
+IndentGotoLabels: true
IndentPPDirectives: None
-IndentWidth: 4
+IndentExternBlock: AfterExternBlock
+IndentRequires: false
+IndentWidth: 4
IndentWrappedFunctionNames: true
+InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
MacroBlockBegin: ''
-MacroBlockEnd: ''
+MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
+ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
-
-# Taken from git's rules
PenaltyBreakAssignment: 10
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
+PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 10
+PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 60
-
+PenaltyIndentedWhitespace: 0
PointerAlignment: Left
-ReflowComments: false
-SortIncludes: false
+PPIndentWidth: -1
+ReferenceAlignment: Pointer
+ReflowComments: false
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes: Never
+SortJavaStaticImport: Before
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: Never
+SpaceBeforeParensOptions:
+ AfterControlStatements: false
+ AfterForeachMacros: false
+ AfterFunctionDefinitionName: false
+ AfterFunctionDeclarationName: false
+ AfterIfMacros: false
+ AfterOverloadedOperator: false
+ BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
-SpacesInAngles: false
+SpacesInAngles: Never
+SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+ Minimum: 1
+ Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
-Standard: Cpp03
-TabWidth: 4
-UseTab: Never
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard: c++03
+StatementAttributeLikeMacros:
+ - Q_EMIT
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 4
+UseCRLF: false
+UseTab: Never
+WhitespaceSensitiveMacros:
+ - STRINGIZE
+ - PP_STRINGIZE
+ - BOOST_PP_STRINGIZE
+ - NS_SWIFT_NAME
+ - CF_SWIFT_NAME
+...
+
diff --git a/.github/ISSUE_TEMPLATE/03_feature_request.yml b/.github/ISSUE_TEMPLATE/03_feature_request.yml
index 2af114547de..052cd34048a 100644
--- a/.github/ISSUE_TEMPLATE/03_feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/03_feature_request.yml
@@ -14,7 +14,7 @@ body:
description: |
Please describe your feature request in as many details as possible.
- Describe what it should do.
- - Note whetever it is to extend existing functionality or introduce new functionality.
+ - Note whether it is to extend existing functionality or introduce new functionality.
validations:
required: true
- type: textarea
diff --git a/.github/actions/docker/action.yml b/.github/actions/docker/action.yml
deleted file mode 100644
index 3608f96e67b..00000000000
--- a/.github/actions/docker/action.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-name: 'Run in docker'
-inputs:
- run: # id of input
- description: 'A command to run'
- required: true
- default: ''
-runs:
- using: 'docker'
- image: '../../../docker/Dockerfile'
- args:
- - ${{ inputs.run }}
diff --git a/.github/actions/submit_sdk/action.yml b/.github/actions/submit_sdk/action.yml
new file mode 100644
index 00000000000..b515b528559
--- /dev/null
+++ b/.github/actions/submit_sdk/action.yml
@@ -0,0 +1,78 @@
+name: Submit SDK to Catalog
+author: hedger
+description: |
+ This action checks if SDK exists in the catalog and if not, adds and/or publishes it.
+
+inputs:
+ catalog-url:
+ description: The URL of the Catalog API. Must not be empty or end with a /.
+ required: true
+ catalog-api-token:
+ description: The token to use to authenticate with the Catalog API. Must not be empty.
+ required: true
+ firmware-api:
+ description: Fimware's API version, major.minor
+ required: true
+ firmware-target:
+ description: Firmware's target, e.g. f7/f18
+ required: true
+ firmware-version:
+ description: Firmware's version, e.g. 0.13.37-rc3, or 0.13.37
+ required: true
+
+runs:
+ using: composite
+ steps:
+ - name: Check inputs
+ shell: bash
+ run: |
+ if [ -z "${{ inputs.catalog-url }}" ] ; then
+ echo "Invalid catalog-url: ${{ inputs.catalog-url }}"
+ exit 1
+ fi
+ if [ -z "${{ inputs.catalog-api-token }}" ] ; then
+ echo "Invalid catalog-api-token: ${{ inputs.catalog-api-token }}"
+ exit 1
+ fi
+ if ! echo "${{ inputs.firmware-api }}" | grep -q "^[0-9]\+\.[0-9]\+$" ; then
+ echo "Invalid firmware-api: ${{ inputs.firmware-api }}"
+ exit 1
+ fi
+ if ! echo "${{ inputs.firmware-target }}" | grep -q "^f[0-9]\+$" ; then
+ echo "Invalid firmware-target: ${{ inputs.firmware-target }}"
+ exit 1
+ fi
+ if ! echo "${{ inputs.firmware-version }}" | grep -q "^[0-9]\+\.[0-9]\+\.[0-9]\+\(-rc\)\?\([0-9]\+\)\?$" ; then
+ echo "Invalid firmware-version: ${{ inputs.firmware-version }}"
+ exit 1
+ fi
+ - name: Submit SDK
+ shell: bash
+ run: |
+ curl -sX 'GET' \
+ '${{ inputs.catalog-url }}/api/v0/0/sdk?length=500' \
+ -H 'Accept: application/json' > sdk_versions.json
+ if jq -r -e ".[] | select((.api == \"${{ inputs.firmware-api }}\") and .target == \"${{ inputs.firmware-target }}\")" sdk_versions.json > found_sdk.json ; then
+ echo "API version ${{ inputs.firmware-api }} already exists in catalog"
+ if [ $(jq -r -e ".released_at" found_sdk.json) != "null" ] ; then
+ echo "API version is already released"
+ exit 0
+ fi
+ if ! echo "${{ inputs.firmware-version }}" | grep -q -- "-rc" ; then
+ SDK_ID=$(jq -r ._id found_sdk.json)
+ echo "Marking SDK $SDK_ID as released"
+ curl -X 'POST' \
+ "${{ inputs.catalog-url }}/api/v0/0/sdk/${SDK_ID}/release" \
+ -H 'Accept: application/json' \
+ -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \
+ -d ''
+ fi
+ else
+ echo "API version ${{ inputs.firmware-api }} doesn't exist in catalog, adding"
+ curl -X 'POST' \
+ '${{ inputs.catalog-url }}/api/v0/0/sdk' \
+ -H 'Accept: application/json' \
+ -H 'Authorization: Bearer ${{ inputs.catalog-api-token }}' \
+ -H 'Content-Type: application/json' \
+ -d "{\"name\": \"${{ inputs.firmware-version }}\", \"target\": \"${{ inputs.firmware-target }}\", \"api\": \"${{ inputs.firmware-api }}\"}"
+ fi
diff --git a/.github/assets/dark_theme_banner.png b/.github/assets/dark_theme_banner.png
new file mode 100644
index 00000000000..10a4e8c3009
Binary files /dev/null and b/.github/assets/dark_theme_banner.png differ
diff --git a/.github/assets/light_theme_banner.png b/.github/assets/light_theme_banner.png
new file mode 100644
index 00000000000..6c6b3b7080a
Binary files /dev/null and b/.github/assets/light_theme_banner.png differ
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a1ae875a8ba..38b1d7b6886 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -4,188 +4,167 @@ on:
push:
branches:
- dev
- - "release*"
tags:
- '*'
pull_request:
env:
- TARGETS: f7
DEFAULT_TARGET: f7
+ FBT_TOOLCHAIN_PATH: /runner/_work
+ FBT_GIT_SUBMODULE_SHALLOW: 1
jobs:
main:
- runs-on: [self-hosted,FlipperZeroShell]
+ runs-on: [self-hosted, FlipperZeroShell]
+ strategy:
+ fail-fast: false
+ matrix:
+ target: [f7, f18]
steps:
- - name: 'Decontaminate previous build leftovers'
- run: |
- if [ -d .git ]
- then
- git submodule status \
- || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
- fi
+ - name: 'Wipe workspace'
+ run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
- name: 'Checkout code'
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
with:
- fetch-depth: 0
+ fetch-depth: 1
ref: ${{ github.event.pull_request.head.sha }}
- - name: 'Make artifacts directory'
- run: |
- test -d artifacts && rm -rf artifacts || true
- mkdir artifacts
-
- - name: 'Generate suffix and folder name'
+ - name: 'Get commit details'
id: names
run: |
- REF=${{ github.ref }}
+ BUILD_TYPE='DEBUG=1 COMPACT=0'
if [[ ${{ github.event_name }} == 'pull_request' ]]; then
- REF=${{ github.head_ref }}
- fi
- BRANCH_OR_TAG=${REF#refs/*/}
- SHA=$(git rev-parse --short HEAD)
-
- if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
- SUFFIX=${BRANCH_OR_TAG//\//_}
+ TYPE="pull"
+ elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
+ TYPE="tag"
+ BUILD_TYPE='DEBUG=0 COMPACT=1'
else
- SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA}
+ TYPE="other"
fi
+ python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}"
+ echo "event_type=$TYPE" >> $GITHUB_OUTPUT
+ echo "FBT_BUILD_TYPE=$BUILD_TYPE" >> $GITHUB_ENV
+ echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV
+ echo "TARGET_HW=$(echo "${{ matrix.target }}" | sed 's/f//')" >> $GITHUB_ENV
- echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV
- echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV
- echo "::set-output name=artifacts-path::${BRANCH_OR_TAG}"
- echo "::set-output name=suffix::${SUFFIX}"
- echo "::set-output name=short-hash::${SHA}"
- echo "::set-output name=default-target::${DEFAULT_TARGET}"
-
- - name: 'Bundle scripts'
- if: ${{ !github.event.pull_request.head.repo.fork }}
- run: |
- tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts
-
- - name: 'Build the firmware'
+ - name: 'Check API versions for consistency between targets'
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
+ N_API_HEADER_SIGNATURES=`ls -1 targets/f*/api_symbols.csv | xargs -I {} sh -c "head -n2 {} | md5sum" | sort -u | wc -l`
+ if [ $N_API_HEADER_SIGNATURES != 1 ] ; then
+ echo API versions aren\'t matching for available targets. Please update!
+ echo API versions are:
+ head -n2 targets/f*/api_symbols.csv
+ exit 1
+ fi
- - name: 'Move upload files'
- if: ${{ !github.event.pull_request.head.repo.fork }}
+ - name: 'Build the firmware and apps'
+ id: build-fw
run: |
- set -e
- for TARGET in ${TARGETS}
- do
- mv dist/${TARGET}-*/* artifacts/
- done
+ ./fbt TARGET_HW=$TARGET_HW $FBT_BUILD_TYPE copro_dist updater_package fap_dist
+ echo "firmware_api=$(./fbt TARGET_HW=$TARGET_HW get_apiversion)" >> $GITHUB_OUTPUT
- - name: 'Bundle self-update package'
- if: ${{ !github.event.pull_request.head.repo.fork }}
- 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"
+ - name: 'Check for uncommitted changes'
run: |
git diff --exit-code
- - name: 'Bundle resources'
- if: ${{ !github.event.pull_request.head.repo.fork }}
+ - name: 'Copy build output'
run: |
- tar czpf artifacts/flipper-z-any-resources-${{steps.names.outputs.suffix}}.tgz -C assets resources
-
- - name: 'Bundle core2 firmware'
- if: ${{ !github.event.pull_request.head.repo.fork }}
+ set -e
+ rm -rf artifacts map_analyser_files || true
+ mkdir artifacts map_analyser_files
+ cp dist/${TARGET}-*/* artifacts/ || true
+ tar czpf "artifacts/flipper-z-${TARGET}-resources-${SUFFIX}.tgz" \
+ -C build/latest resources
+ tar czpf "artifacts/flipper-z-${TARGET}-debugapps-${SUFFIX}.tgz" \
+ -C dist/${TARGET}-*/apps/Debug .
+ tar czpf "artifacts/flipper-z-${TARGET}-appsymbols-${SUFFIX}.tgz" \
+ -C dist/${TARGET}-*/debug_elf .
+
+ - name: 'Copy universal artifacts'
+ if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET }}
run: |
- FBT_TOOLCHAIN_PATH=/opt ./fbt copro_dist
- tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware
+ tar czpf "artifacts/flipper-z-any-scripts-${SUFFIX}.tgz" scripts
+ cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz"
- name: 'Upload artifacts to update server'
if: ${{ !github.event.pull_request.head.repo.fork }}
run: |
- echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key;
- chmod 600 ./deploy_key;
- rsync -avzP --delete --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 }}
- 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 }}
- uses: peter-evans/find-comment@v1
- id: fc
+ FILES=$(for ARTIFACT in $(find artifacts -maxdepth 1 -not -type d); do echo "-F files=@${ARTIFACT}"; done)
+ curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \
+ -F "branch=${BRANCH_NAME}" \
+ -F "version_token=${COMMIT_SHA}" \
+ ${FILES[@]} \
+ "${{ secrets.INDEXER_URL }}"/firmware/uploadfiles
+
+ - name: 'Copy & analyse map analyser files'
+ if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET }}
+ run: |
+ cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf.map map_analyser_files/firmware.elf.map
+ cp build/${DEFAULT_TARGET}-firmware-*/firmware.elf map_analyser_files/firmware.elf
+ cp ${{ github.event_path }} map_analyser_files/event.json
+ source scripts/toolchain/fbtenv.sh
+ get_size()
+ {
+ SECTION="$1";
+ arm-none-eabi-size \
+ -A map_analyser_files/firmware.elf \
+ | grep "^$SECTION" | awk '{print $2}'
+ }
+ export BSS_SIZE="$(get_size ".bss")"
+ export TEXT_SIZE="$(get_size ".text")"
+ export RODATA_SIZE="$(get_size ".rodata")"
+ export DATA_SIZE="$(get_size ".data")"
+ export FREE_FLASH_SIZE="$(get_size ".free_flash")"
+ python3 -m pip install mariadb==1.1.6 cxxfilt==0.3.0
+ python3 scripts/map_parser.py map_analyser_files/firmware.elf.map map_analyser_files/firmware.elf.map.all
+ python3 scripts/map_mariadb_insert.py \
+ ${{ secrets.AMAP_MARIADB_USER }} \
+ ${{ secrets.AMAP_MARIADB_PASSWORD }} \
+ ${{ secrets.AMAP_MARIADB_HOST }} \
+ ${{ secrets.AMAP_MARIADB_PORT }} \
+ ${{ secrets.AMAP_MARIADB_DATABASE }} \
+ map_analyser_files/firmware.elf.map.all
+
+ - name: 'Find previous comment'
+ if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }}
+ uses: peter-evans/find-comment@v2
+ id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
- body-includes: 'Compiled firmware for commit'
+ body-includes: 'Compiled ${{ matrix.target }} firmware for commit'
- name: 'Create or update comment'
- if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}}
- uses: peter-evans/create-or-update-comment@v1
+ if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }}
+ uses: peter-evans/create-or-update-comment@v3
with:
- comment-id: ${{ steps.fc.outputs.comment-id }}
+ comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
- **Compiled firmware for commit `${{steps.names.outputs.short-hash}}`:**
- - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz)
- - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu)
- - [☁️ Web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}})
+ **Compiled ${{ matrix.target }} firmware for commit `${{steps.names.outputs.commit_sha}}`:**
+ - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz)
+ - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu)
+ - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}})
edit-mode: replace
- compact:
- if: ${{ !startsWith(github.ref, 'refs/tags') }}
- runs-on: [self-hosted,FlipperZeroShell]
- steps:
- - name: 'Decontaminate previous build leftovers'
- run: |
- if [ -d .git ]
- then
- git submodule status \
- || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
- fi
-
- - name: 'Checkout code'
- uses: actions/checkout@v2
+ - name: 'SDK submission to staging catalog'
+ if: ${{ steps.names.outputs.event_type == 'tag' && matrix.target == env.DEFAULT_TARGET }}
+ uses: ./.github/actions/submit_sdk
with:
- fetch-depth: 0
- submodules: true
- ref: ${{ github.event.pull_request.head.sha }}
-
- - name: 'Generate suffix and folder name'
- id: names
- run: |
- REF=${{ github.ref }}
- if [[ ${{ github.event_name }} == 'pull_request' ]]; then
- REF=${{ github.head_ref }}
- fi
- BRANCH_OR_TAG=${REF#refs/*/}
- SHA=$(git rev-parse --short HEAD)
-
- if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
- SUFFIX=${BRANCH_OR_TAG//\//_}
- else
- SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA}
- fi
-
- echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV
- echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV
-
- - 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
+ catalog-url: ${{ secrets.CATALOG_STAGING_URL }}
+ catalog-api-token: ${{ secrets.CATALOG_STAGING_API_TOKEN }}
+ firmware-api: ${{ steps.build-fw.outputs.firmware_api }}
+ firmware-target: ${{ matrix.target }}
+ firmware-version: ${{ steps.names.outputs.suffix }}
+
+ - name: 'SDK submission to prod catalog'
+ if: ${{ steps.names.outputs.event_type == 'tag' && matrix.target == env.DEFAULT_TARGET }}
+ uses: ./.github/actions/submit_sdk
+ with:
+ catalog-url: ${{ secrets.CATALOG_URL }}
+ catalog-api-token: ${{ secrets.CATALOG_API_TOKEN }}
+ firmware-api: ${{ steps.build-fw.outputs.firmware_api }}
+ firmware-target: ${{ matrix.target }}
+ firmware-version: ${{ steps.names.outputs.suffix }}
diff --git a/.github/workflows/build_compact.yml b/.github/workflows/build_compact.yml
new file mode 100644
index 00000000000..f45275204a3
--- /dev/null
+++ b/.github/workflows/build_compact.yml
@@ -0,0 +1,86 @@
+name: 'Compact build'
+
+on:
+ pull_request:
+
+env:
+ FBT_TOOLCHAIN_PATH: /runner/_work
+ FBT_GIT_SUBMODULE_SHALLOW: 1
+
+jobs:
+ compact:
+ runs-on: [self-hosted, FlipperZeroShell]
+ strategy:
+ fail-fast: false
+ matrix:
+ target: [f7, f18]
+ steps:
+ - name: 'Wipe workspace'
+ run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
+
+ - name: 'Checkout code'
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ submodules: false
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: 'Get commit details'
+ run: |
+ if [[ ${{ github.event_name }} == 'pull_request' ]]; then
+ TYPE="pull"
+ elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
+ TYPE="tag"
+ else
+ TYPE="other"
+ fi
+ python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}"
+
+ - name: 'Build the firmware'
+ id: build-fw
+ run: |
+ set -e
+ TARGET="$(echo '${{ matrix.target }}' | sed 's/f//')"; \
+ ./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 fap_dist updater_package
+ echo "sdk-file=$(ls dist/${{ matrix.target }}-*/flipper-z-${{ matrix.target }}-sdk-*.zip)" >> $GITHUB_OUTPUT
+ echo "hw-target-code=$TARGET" >> $GITHUB_OUTPUT
+
+ - name: Deploy uFBT with SDK
+ uses: flipperdevices/flipperzero-ufbt-action@v0.1
+ with:
+ task: setup
+ sdk-file: ${{ steps.build-fw.outputs.sdk-file }}
+ sdk-hw-target: ${{ steps.build-fw.outputs.hw-target-code }}
+
+ - name: Build test app with SDK
+ run: |
+ mkdir testapp
+ cd testapp
+ ufbt create APPID=testapp
+ ufbt
+
+ - name: Build example & external apps with uFBT
+ run: |
+ for appdir in 'applications/examples'; do
+ for app in $(find "$appdir" -maxdepth 1 -mindepth 1 -type d); do
+ pushd $app
+ TARGETS_FAM=$(grep "targets" application.fam || echo "${{ matrix.target }}")
+ if ! grep -q "${{ matrix.target }}" <<< $TARGETS_FAM ; then
+ echo Skipping unsupported app: $app
+ popd
+ continue
+ fi
+ echo Building $app
+ ufbt
+ popd
+ done
+ done
+
+## Uncomment this for a single job that will run only if all targets are built successfully
+# report-status:
+# name: Report status
+# needs: [compact]
+# if: always() && !contains(needs.*.result, 'failure')
+# runs-on: [self-hosted, FlipperZeroShell]
+# steps:
+# - run: echo "All good ✨" ;
diff --git a/.github/workflows/check_submodules.yml b/.github/workflows/check_submodules.yml
deleted file mode 100644
index f9699be87ae..00000000000
--- a/.github/workflows/check_submodules.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-name: 'Check submodules branch'
-
-on:
- push:
- branches:
- - dev
- - "release*"
- tags:
- - '*'
- pull_request:
-
-jobs:
- check_protobuf:
- runs-on: [self-hosted, FlipperZeroShell]
- steps:
- - name: 'Decontaminate previous build leftovers'
- run: |
- if [ -d .git ]
- then
- git submodule status \
- || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
- fi
-
- - name: 'Checkout code'
- uses: actions/checkout@v2
- with:
- fetch-depth: 0
-
- - 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
diff --git a/.github/workflows/lint_and_submodule_check.yml b/.github/workflows/lint_and_submodule_check.yml
new file mode 100644
index 00000000000..3f4d8c79bd0
--- /dev/null
+++ b/.github/workflows/lint_and_submodule_check.yml
@@ -0,0 +1,101 @@
+name: 'Lint sources & check submodule integrity'
+
+on:
+ pull_request:
+
+env:
+ TARGETS: f7
+ FBT_TOOLCHAIN_PATH: /runner/_work
+ SET_GH_OUTPUT: 1
+
+jobs:
+ lint_sources_check_submodules:
+ runs-on: [self-hosted, FlipperZeroShell]
+ steps:
+ - name: 'Wipe workspace'
+ run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
+
+ - name: 'Checkout code'
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+ ref: ${{ github.sha }}
+
+ - name: 'Check protobuf branch'
+ run: |
+ git submodule update --init;
+ SUB_PATH="assets/protobuf";
+ SUB_BRANCH="dev";
+ SUB_COMMITS_MIN=40;
+ cd "$SUB_PATH";
+ SUBMODULE_HASH="$(git rev-parse HEAD)";
+ BRANCHES=$(git branch -r --contains "$SUBMODULE_HASH");
+ COMMITS_IN_BRANCH="$(git rev-list --count dev)";
+ if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then
+ echo "name=fails::error" >> $GITHUB_OUTPUT;
+ echo "::error::Error: Too few commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)";
+ exit 1;
+ fi
+ if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then
+ echo "name=fails::error" >> $GITHUB_OUTPUT;
+ echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH";
+ exit 1;
+ fi
+
+ - name: 'Check for new TODOs'
+ id: check_todos
+ if: github.event_name == 'pull_request'
+ run: |
+ set +e;
+ git diff --unified=0 --no-color ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -E '^\+' | grep -i -E '(TODO|HACK|FIXME|XXX)[ :]' | grep -v -- '-nofl' > lines.log;
+ MISSING_TICKETS=$( grep -v -E 'FL-[0-9]+' lines.log );
+ if [ -n "$MISSING_TICKETS" ]; then
+ echo "Error: Missing ticket number in \`TODO\` comment(s)" >> $GITHUB_STEP_SUMMARY;
+ echo "\`\`\`" >> $GITHUB_STEP_SUMMARY;
+ echo "$MISSING_TICKETS" >> $GITHUB_STEP_SUMMARY;
+ echo "\`\`\`" >> $GITHUB_STEP_SUMMARY;
+ exit 1;
+ else
+ echo "No new TODOs without tickets found" >> $GITHUB_STEP_SUMMARY;
+ fi
+
+ - name: 'Check Python code formatting'
+ id: syntax_check_py
+ if: always()
+ run: |
+ set +e;
+ ./fbt -s lint_py 2>&1 | tee lint-py.log;
+ if [ "${PIPESTATUS[0]}" -ne 0 ]; then
+ # Save multiline output
+ echo "errors=1" >> $GITHUB_OUTPUT;
+ printf "Python Lint errors:\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY;
+ echo "$(cat lint-py.log)" >> $GITHUB_STEP_SUMMARY;
+ printf "\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY;
+ exit 1;
+ else
+ echo "Python Lint: all good ✨" >> $GITHUB_STEP_SUMMARY;
+ fi
+
+ - name: 'Check C++ code formatting'
+ id: syntax_check_cpp
+ if: always()
+ run: |
+ set +e;
+ ./fbt -s lint 2>&1 | tee lint-cpp.log;
+ if [ "${PIPESTATUS[0]}" -ne 0 ]; then
+ # Save multiline output
+ echo "errors=1" >> $GITHUB_OUTPUT;
+ printf "C Lint errors:\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY;
+ echo "$(cat lint-cpp.log)" >> $GITHUB_STEP_SUMMARY;
+ printf "\n\`\`\`\n" >> $GITHUB_STEP_SUMMARY;
+ exit 1;
+ else
+ echo "C Lint: all good ✨" >> $GITHUB_STEP_SUMMARY;
+ fi
+
+ - name: Report code formatting errors
+ if: ( steps.syntax_check_py.outputs.errors || steps.syntax_check_cpp.outputs.errors ) && github.event.pull_request
+ run: |
+ echo "Code formatting errors found";
+ echo "Please run './fbt format' or './fbt format_py' to fix them";
+ exit 1;
diff --git a/.github/workflows/lint_c.yml b/.github/workflows/lint_c.yml
deleted file mode 100644
index aaff396ec6f..00000000000
--- a/.github/workflows/lint_c.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-name: 'Lint C/C++ with clang-format'
-
-on:
- push:
- branches:
- - dev
- - "release*"
- tags:
- - '*'
- pull_request:
-
-env:
- TARGETS: f7
-
-jobs:
- lint_c_cpp:
- runs-on: [self-hosted,FlipperZeroShell]
- steps:
- - name: 'Decontaminate previous build leftovers'
- run: |
- if [ -d .git ]
- then
- git submodule status \
- || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
- fi
-
- - name: 'Checkout code'
- uses: actions/checkout@v2
- with:
- fetch-depth: 0
-
- - name: 'Check code formatting'
- id: syntax_check
- 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
- uses: peter-evans/create-or-update-comment@v1
- with:
- issue-number: ${{ github.event.pull_request.number }}
- body: |
- Please fix following code formatting errors:
- ```
- ${{ steps.syntax_check.outputs.errors }}
- ```
- You might want to run `./fbt format` for an auto-fix.
diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml
deleted file mode 100644
index c059f4348ac..00000000000
--- a/.github/workflows/lint_python.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: 'Python Lint'
-
-on:
- push:
- branches:
- - dev
- - "release*"
- tags:
- - '*'
- pull_request:
-
-jobs:
- lint_python:
- runs-on: [self-hosted,FlipperZeroShell]
- steps:
- - name: 'Decontaminate previous build leftovers'
- run: |
- if [ -d .git ]
- then
- git submodule status \
- || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
- fi
-
- - name: 'Checkout code'
- uses: actions/checkout@v2
- with:
- fetch-depth: 0
-
- - name: 'Check code formatting'
- run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint_py
diff --git a/.github/workflows/merge_report.yml b/.github/workflows/merge_report.yml
new file mode 100644
index 00000000000..90302ce1aa0
--- /dev/null
+++ b/.github/workflows/merge_report.yml
@@ -0,0 +1,42 @@
+name: 'Check FL ticket in PR name'
+
+on:
+ push:
+ branches:
+ - dev
+
+env:
+ FBT_TOOLCHAIN_PATH: /runner/_work
+
+jobs:
+ merge_report:
+ runs-on: [self-hosted, FlipperZeroShell]
+ steps:
+ - name: 'Wipe workspace'
+ run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
+
+ - name: 'Checkout code'
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: 'Get commit details'
+ run: |
+ if [[ ${{ github.event_name }} == 'pull_request' ]]; then
+ TYPE="pull"
+ elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
+ TYPE="tag"
+ else
+ TYPE="other"
+ fi
+ python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}"
+
+ - name: 'Check ticket and report'
+ run: |
+ source scripts/toolchain/fbtenv.sh
+ python3 -m pip install slack_sdk
+ python3 scripts/merge_report_qa.py \
+ ${{ secrets.QA_REPORT_SLACK_TOKEN }} \
+ ${{ secrets.QA_REPORT_SLACK_CHANNEL }}
+
diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml
new file mode 100644
index 00000000000..4527e292078
--- /dev/null
+++ b/.github/workflows/pvs_studio.yml
@@ -0,0 +1,88 @@
+name: 'Static C/C++ analysis with PVS-Studio'
+
+on:
+ push:
+ branches:
+ - dev
+ pull_request:
+
+env:
+ TARGETS: f7
+ DEFAULT_TARGET: f7
+ FBT_TOOLCHAIN_PATH: /runner/_work
+ FBT_GIT_SUBMODULE_SHALLOW: 1
+
+jobs:
+ analyse_c_cpp:
+ if: ${{ !github.event.pull_request.head.repo.fork }}
+ runs-on: [self-hosted, FlipperZeroShell]
+ steps:
+ - name: 'Wipe workspace'
+ run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
+
+ - name: 'Checkout code'
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: 'Get commit details'
+ id: names
+ run: |
+ if [[ ${{ github.event_name }} == 'pull_request' ]]; then
+ TYPE="pull"
+ elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
+ TYPE="tag"
+ else
+ TYPE="other"
+ fi
+ python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" || cat "${{ github.event_path }}"
+
+ - name: 'Supply PVS credentials'
+ run: |
+ pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }}
+
+ - name: 'Convert PVS-Studio output to html and detect warnings'
+ id: pvs-warn
+ run: |
+ WARNINGS=0
+ ./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1
+ echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT
+
+ - name: 'Upload report'
+ if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }}
+ uses: prewk/s3-cp-action@v2
+ with:
+ aws_s3_endpoint: "${{ secrets.PVS_AWS_ENDPOINT }}"
+ aws_access_key_id: "${{ secrets.PVS_AWS_ACCESS_KEY }}"
+ aws_secret_access_key: "${{ secrets.PVS_AWS_SECRET_KEY }}"
+ source: "./build/f7-firmware-DC/pvsreport"
+ dest: "s3://${{ secrets.PVS_AWS_BUCKET }}/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/"
+ flags: "--recursive --acl public-read"
+
+ - name: 'Find Previous Comment'
+ if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }}
+ uses: peter-evans/find-comment@v2
+ id: fc
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ comment-author: 'github-actions[bot]'
+ body-includes: 'PVS-Studio report for commit'
+
+ - name: 'Create or update comment'
+ if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }}
+ uses: peter-evans/create-or-update-comment@v1
+ with:
+ comment-id: ${{ steps.fc.outputs.comment-id }}
+ issue-number: ${{ github.event.pull_request.number }}
+ body: |
+ **PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:**
+ - [Report](https://pvs.flipp.dev/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html)
+ edit-mode: replace
+
+ - name: 'Raise exception'
+ if: ${{ steps.pvs-warn.outputs.warnings != 0 }}
+ run: |
+ echo "Please fix all PVS warnings before merge"
+ exit 1
+
diff --git a/.github/workflows/reindex.yml b/.github/workflows/reindex.yml
index ea850e7051b..82cb0468077 100644
--- a/.github/workflows/reindex.yml
+++ b/.github/workflows/reindex.yml
@@ -7,8 +7,9 @@ on:
jobs:
reindex:
name: 'Reindex updates'
- runs-on: [self-hosted,FlipperZeroShell]
+ runs-on: [self-hosted, FlipperZeroShell]
steps:
- name: Trigger reindex
run: |
- curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }}
+ curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \
+ "${{ secrets.INDEXER_URL }}"/firmware/reindex
diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
new file mode 100644
index 00000000000..40f72bd0ba5
--- /dev/null
+++ b/.github/workflows/unit_tests.yml
@@ -0,0 +1,69 @@
+name: 'Unit tests'
+
+on:
+ pull_request:
+
+env:
+ TARGETS: f7
+ DEFAULT_TARGET: f7
+ FBT_TOOLCHAIN_PATH: /opt
+ FBT_GIT_SUBMODULE_SHALLOW: 1
+
+jobs:
+ run_units_on_bench:
+ runs-on: [self-hosted, FlipperZeroUnitTest]
+ steps:
+ - name: 'Wipe workspace'
+ run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
+
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: 'Get flipper from device manager (mock)'
+ id: device
+ run: |
+ echo "flipper=/dev/ttyACM0" >> $GITHUB_OUTPUT
+
+ - name: 'Flash unit tests firmware'
+ id: flashing
+ if: success()
+ timeout-minutes: 10
+ run: |
+ ./fbt resources firmware_latest flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1
+
+ - name: 'Wait for flipper and format ext'
+ id: format_ext
+ if: steps.flashing.outcome == 'success'
+ timeout-minutes: 5
+ run: |
+ source scripts/toolchain/fbtenv.sh
+ python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}}
+ python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext
+
+ - name: 'Copy assets and unit data, reboot and wait for flipper'
+ id: copy
+ if: steps.format_ext.outcome == 'success'
+ timeout-minutes: 7
+ run: |
+ source scripts/toolchain/fbtenv.sh
+ python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}}
+ rm -rf build/latest/resources/dolphin
+ python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} -f send build/latest/resources /ext
+ python3 scripts/power.py -p ${{steps.device.outputs.flipper}} reboot
+ python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}}
+
+ - name: 'Run units and validate results'
+ id: run_units
+ if: steps.copy.outcome == 'success'
+ timeout-minutes: 7
+ run: |
+ source scripts/toolchain/fbtenv.sh
+ python3 scripts/testing/units.py ${{steps.device.outputs.flipper}}
+
+ - name: 'Check GDB output'
+ if: failure()
+ run: |
+ ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1
diff --git a/.github/workflows/updater_test.yml b/.github/workflows/updater_test.yml
new file mode 100644
index 00000000000..b14b618a6eb
--- /dev/null
+++ b/.github/workflows/updater_test.yml
@@ -0,0 +1,75 @@
+name: 'Updater test'
+
+on:
+ pull_request:
+
+env:
+ TARGETS: f7
+ DEFAULT_TARGET: f7
+ FBT_TOOLCHAIN_PATH: /opt
+ FBT_GIT_SUBMODULE_SHALLOW: 1
+
+jobs:
+ test_updater_on_bench:
+ runs-on: [self-hosted, FlipperZeroUpdaterTest]
+ steps:
+ - name: 'Wipe workspace'
+ run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
+
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ submodules: false
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: 'Get flipper from device manager (mock)'
+ id: device
+ run: |
+ echo "flipper=Rekigyn" >> $GITHUB_OUTPUT
+ echo "stlink=0F020D026415303030303032" >> $GITHUB_OUTPUT
+
+ - name: 'Flashing target firmware'
+ id: first_full_flash
+ timeout-minutes: 10
+ run: |
+ source scripts/toolchain/fbtenv.sh
+ ./fbt flash_usb_full PORT=${{steps.device.outputs.flipper}} FORCE=1
+ python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}}
+
+ - name: 'Validating updater'
+ id: second_full_flash
+ timeout-minutes: 10
+ if: success()
+ run: |
+ source scripts/toolchain/fbtenv.sh
+ ./fbt flash_usb PORT=${{steps.device.outputs.flipper}} FORCE=1
+ python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}}
+
+ - name: 'Get last release tag'
+ id: release_tag
+ if: failure()
+ run: |
+ echo "tag=$(git tag -l --sort=-version:refname | grep -v "rc\|RC" | head -1)" >> $GITHUB_OUTPUT
+
+ - name: 'Wipe workspace'
+ run: find ./ -mount -maxdepth 1 -exec rm -rf {} \;
+
+ - name: 'Checkout latest release'
+ uses: actions/checkout@v4
+ if: failure()
+ with:
+ fetch-depth: 1
+ ref: ${{ steps.release_tag.outputs.tag }}
+
+ - name: 'Flash last release'
+ if: failure()
+ run: |
+ ./fbt flash SWD_TRANSPORT_SERIAL=${{steps.device.outputs.stlink}} FORCE=1
+
+ - name: 'Wait for flipper and format ext'
+ if: failure()
+ run: |
+ source scripts/toolchain/fbtenv.sh
+ python3 scripts/testing/await_flipper.py ${{steps.device.outputs.flipper}}
+ python3 scripts/storage.py -p ${{steps.device.outputs.flipper}} format_ext
diff --git a/.gitignore b/.gitignore
index 21f391743ec..d60dcec3f5d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
+*~
*.swp
+*.swo
*.gdb_history
+*.old
# LSP
@@ -27,26 +30,37 @@ bindings/
.mxproject
Brewfile.lock.json
-# Visual Studio Code
-.vscode/
-
-# legendary cmake's
-build
-CMakeLists.txt
-
-# bundle output
-dist
+# Kate
+.kateproject
+.kateconfig
# kde
.directory
# SCons
.sconsign.dblite
+
+
+# Visual Studio Code
+/.vscode
+
+# bundle output
+/dist
+
# SCons build dir
-build/
+/build
# Toolchain
/toolchain
# openocd output file
openocd.log
+
+# PVS Studio temporary files
+.PVS-Studio/
+PVS-Studio.log
+*.PVS-Studio.*
+
+.gdbinit
+
+/fbt_options_local.py
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index b580a8c71a8..52cf4a207b7 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,6 @@
[submodule "lib/mlib"]
path = lib/mlib
url = https://github.com/P-p-H-d/mlib.git
-[submodule "lib/STM32CubeWB"]
- path = lib/STM32CubeWB
- url = https://github.com/Flipper-Zero/STM32CubeWB.git
[submodule "lib/littlefs"]
path = lib/littlefs
url = https://github.com/littlefs-project/littlefs.git
@@ -13,6 +10,7 @@
[submodule "assets/protobuf"]
path = assets/protobuf
url = https://github.com/flipperdevices/flipperzero-protobuf.git
+ shallow = false
[submodule "lib/libusb_stm32"]
path = lib/libusb_stm32
url = https://github.com/flipperdevices/libusb_stm32.git
@@ -22,9 +20,21 @@
[submodule "lib/microtar"]
path = lib/microtar
url = https://github.com/amachronic/microtar.git
-[submodule "lib/scons"]
- path = lib/scons
- url = https://github.com/SCons/scons.git
[submodule "lib/mbedtls"]
path = lib/mbedtls
url = https://github.com/Mbed-TLS/mbedtls.git
+[submodule "lib/cxxheaderparser"]
+ path = lib/cxxheaderparser
+ url = https://github.com/robotpy/cxxheaderparser.git
+[submodule "lib/heatshrink"]
+ path = lib/heatshrink
+ url = https://github.com/flipperdevices/heatshrink.git
+[submodule "lib/st_cmsis_device_wb"]
+ path = lib/stm32wb_cmsis
+ url = https://github.com/STMicroelectronics/cmsis_device_wb
+[submodule "lib/stm32wbxx_hal_driver"]
+ path = lib/stm32wb_hal
+ url = https://github.com/STMicroelectronics/stm32wbxx_hal_driver
+[submodule "lib/stm32wb_copro"]
+ path = lib/stm32wb_copro
+ url = https://github.com/flipperdevices/stm32wb_copro.git
diff --git a/.pvsconfig b/.pvsconfig
new file mode 100644
index 00000000000..49c63ad7398
--- /dev/null
+++ b/.pvsconfig
@@ -0,0 +1,49 @@
+# MLib macros we can't do much about.
+//-V:M_LET:1048,1044
+//-V:M_EACH:1048,1044
+//-V:ARRAY_DEF:760,747,568,776,729,712,654
+//-V:LIST_DEF:760,747,568,712,729,654,776
+//-V:BPTREE_DEF2:779,1086,557,773,512
+//-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685
+//-V:ALGO_DEF:1048,747,1044
+//-V:TUPLE_DEF2:524,590,1001,760
+
+# Non-severe malloc/null pointer deref warnings
+//-V::522:2,3
+
+# Warning about headers with copyleft license
+//-V::1042
+
+# Potentially null argument warnings
+//-V:memset:575
+//-V:memcpy:575
+//-V:memcmp:575
+//-V:strlen:575
+//-V:strcpy:575
+//-V:strncpy:575
+//-V:strchr:575
+
+# For loop warning on M_FOREACH
+//-V:for:1044
+
+# Bitwise OR
+//-V:bit:792
+
+# Do not complain about similar code
+//-V::525
+
+# Common embedded development pointer operations
+//-V::566
+//-V::1032
+
+# Warnings about length mismatch
+//-V:property_value_out:666
+
+# Model-related warnings
+//-V:with_view_model:1044,1048
+
+# Functions that always return the same error code
+//-V:picopass_device_decrypt:1048
+
+# Examples
+//V_EXCLUDE_PATH applications/examples/
\ No newline at end of file
diff --git a/.pvsoptions b/.pvsoptions
new file mode 100644
index 00000000000..3337d7eb5c2
--- /dev/null
+++ b/.pvsoptions
@@ -0,0 +1 @@
+--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/*
diff --git a/.vscode/.gitignore b/.vscode/.gitignore
index 670df7d48f6..481efcdef35 100644
--- a/.vscode/.gitignore
+++ b/.vscode/.gitignore
@@ -1,4 +1,5 @@
-./c_cpp_properties.json
-./launch.json
-./settings.json
-./tasks.json
+/c_cpp_properties.json
+/extensions.json
+/launch.json
+/settings.json
+/tasks.json
diff --git a/.vscode/example/c_cpp_properties.json b/.vscode/example/c_cpp_properties.json
deleted file mode 100644
index db08320c9c9..00000000000
--- a/.vscode/example/c_cpp_properties.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "configurations": [
- {
- "name": "Win32",
- "compilerPath": "${workspaceFolder}/toolchain/i686-windows/bin/arm-none-eabi-gcc.exe",
- "intelliSenseMode": "gcc-arm",
- "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
- "configurationProvider": "ms-vscode.cpptools",
- "cStandard": "gnu17",
- "cppStandard": "c++17"
- },
- {
- "name": "Linux",
- "compilerPath": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gcc",
- "intelliSenseMode": "gcc-arm",
- "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
- "configurationProvider": "ms-vscode.cpptools",
- "cStandard": "gnu17",
- "cppStandard": "c++17"
- },
- {
- "name": "Mac",
- "compilerPath": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gcc",
- "intelliSenseMode": "gcc-arm",
- "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
- "configurationProvider": "ms-vscode.cpptools",
- "cStandard": "gnu17",
- "cppStandard": "c++17"
- }
- ],
- "version": 4
-}
\ No newline at end of file
diff --git a/.vscode/example/clangd/extensions.json b/.vscode/example/clangd/extensions.json
new file mode 100644
index 00000000000..daab417cd01
--- /dev/null
+++ b/.vscode/example/clangd/extensions.json
@@ -0,0 +1,19 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+ // List of extensions which should be recommended for users of this workspace.
+ "recommendations": [
+ "ms-python.black-formatter",
+ "llvm-vs-code-extensions.vscode-clangd",
+ "amiralizadeh9480.cpp-helper",
+ "marus25.cortex-debug",
+ "zxh404.vscode-proto3",
+ "augustocdias.tasks-shell-input"
+ ],
+ // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+ "unwantedRecommendations": [
+ "twxs.cmake",
+ "ms-vscode.cpptools",
+ "ms-vscode.cmake-tools"
+ ]
+}
diff --git a/.vscode/example/cpptools/c_cpp_properties.json b/.vscode/example/cpptools/c_cpp_properties.json
new file mode 100644
index 00000000000..d1cac63e966
--- /dev/null
+++ b/.vscode/example/cpptools/c_cpp_properties.json
@@ -0,0 +1,32 @@
+{
+ "configurations": [
+ {
+ "name": "Win32",
+ "compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe",
+ "intelliSenseMode": "gcc-arm",
+ "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
+ "configurationProvider": "ms-vscode.cpptools",
+ "cStandard": "gnu17",
+ "cppStandard": "c++17"
+ },
+ {
+ "name": "Linux",
+ "compilerPath": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gcc",
+ "intelliSenseMode": "gcc-arm",
+ "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
+ "configurationProvider": "ms-vscode.cpptools",
+ "cStandard": "gnu17",
+ "cppStandard": "c++17"
+ },
+ {
+ "name": "Mac",
+ "compilerPath": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gcc",
+ "intelliSenseMode": "gcc-arm",
+ "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json",
+ "configurationProvider": "ms-vscode.cpptools",
+ "cStandard": "gnu17",
+ "cppStandard": "c++17"
+ }
+ ],
+ "version": 4
+}
\ No newline at end of file
diff --git a/.vscode/example/cpptools/extensions.json b/.vscode/example/cpptools/extensions.json
new file mode 100644
index 00000000000..a8babee1cd8
--- /dev/null
+++ b/.vscode/example/cpptools/extensions.json
@@ -0,0 +1,20 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+ // List of extensions which should be recommended for users of this workspace.
+ "recommendations": [
+ "ms-python.black-formatter",
+ "ms-vscode.cpptools",
+ "amiralizadeh9480.cpp-helper",
+ "marus25.cortex-debug",
+ "zxh404.vscode-proto3",
+ "augustocdias.tasks-shell-input",
+ "rioj7.command-variable"
+ ],
+ // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+ "unwantedRecommendations": [
+ "llvm-vs-code-extensions.vscode-clangd",
+ "twxs.cmake",
+ "ms-vscode.cmake-tools"
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/example/launch.json b/.vscode/example/launch.json
index bdad042859c..f4b7c6e4838 100644
--- a/.vscode/example/launch.json
+++ b/.vscode/example/launch.json
@@ -9,8 +9,12 @@
"type": "command",
"command": "shellCommand.execute",
"args": {
- "command": "./fbt get_blackmagic",
- "description": "Get Blackmagic device",
+ "useSingleResult": true,
+ "env": {
+ "PATH": "${workspaceFolder}${command:extension.commandvariable.envListSep}${env:PATH}"
+ },
+ "command": "fbt -s get_blackmagic",
+ "description": "Get Blackmagic device"
}
}
],
@@ -23,15 +27,21 @@
"type": "cortex-debug",
"servertype": "openocd",
"device": "stlink",
- "svdFile": "./debug/STM32WB55_CM4.svd",
+ "svdFile": "./scripts/debug/STM32WB55_CM4.svd",
+ // If you're debugging early in the boot process, before OS scheduler is running,
+ // you have to comment out the following line.
"rtos": "FreeRTOS",
"configFiles": [
"interface/stlink.cfg",
- "./debug/stm32wbx.cfg",
+ "./scripts/debug/stm32wbx.cfg",
],
"postAttachCommands": [
- // "attach 1",
- "compare-sections",
+ "source scripts/debug/flipperversion.py",
+ "fw-version",
+ // "compare-sections",
+ "source scripts/debug/flipperapps.py",
+ "fap-set-debug-elf-root build/latest/.extapps",
+ // "source scripts/debug/FreeRTOS/FreeRTOS.py",
]
// "showDevDebugOutput": "raw",
},
@@ -43,14 +53,18 @@
"type": "cortex-debug",
"servertype": "external",
"gdbTarget": "${input:BLACKMAGIC}",
- "svdFile": "./debug/STM32WB55_CM4.svd",
+ "svdFile": "./scripts/debug/STM32WB55_CM4.svd",
"rtos": "FreeRTOS",
"postAttachCommands": [
"monitor swdp_scan",
"attach 1",
"set confirm off",
"set mem inaccessible-by-default off",
- "compare-sections",
+ "source scripts/debug/flipperversion.py",
+ "fw-version",
+ "source scripts/debug/flipperapps.py",
+ "fap-set-debug-elf-root build/latest/.extapps",
+ // "compare-sections",
]
// "showDevDebugOutput": "raw",
},
@@ -63,18 +77,37 @@
"servertype": "jlink",
"interface": "swd",
"device": "STM32WB55RG",
- "svdFile": "./debug/STM32WB55_CM4.svd",
+ "svdFile": "./scripts/debug/STM32WB55_CM4.svd",
"rtos": "FreeRTOS",
+ "postAttachCommands": [
+ "source scripts/debug/flipperversion.py",
+ "fw-version",
+ "source scripts/debug/flipperapps.py",
+ "fap-set-debug-elf-root build/latest/.extapps",
+ ]
// "showDevDebugOutput": "raw",
},
{
- "name": "fbt debug",
- "type": "python",
- "request": "launch",
- "program": "./lib/scons/scripts/scons.py",
- "args": [
- "sdk"
- ]
+ "name": "Attach FW (DAP)",
+ "cwd": "${workspaceFolder}",
+ "executable": "./build/latest/firmware.elf",
+ "request": "attach",
+ "type": "cortex-debug",
+ "servertype": "openocd",
+ "device": "cmsis-dap",
+ "svdFile": "./scripts/debug/STM32WB55_CM4.svd",
+ "rtos": "FreeRTOS",
+ "configFiles": [
+ "interface/cmsis-dap.cfg",
+ "./scripts/debug/stm32wbx.cfg",
+ ],
+ "postAttachCommands": [
+ "source scripts/debug/flipperversion.py",
+ "fw-version",
+ "source scripts/debug/flipperapps.py",
+ "fap-set-debug-elf-root build/latest/.extapps",
+ ],
+ // "showDevDebugOutput": "raw",
},
{
"name": "python debug",
diff --git a/.vscode/example/settings.json b/.vscode/example/settings.json
index 925c2e0763b..efa08157b62 100644
--- a/.vscode/example/settings.json
+++ b/.vscode/example/settings.json
@@ -6,17 +6,25 @@
"cortex-debug.enableTelemetry": false,
"cortex-debug.variableUseNaturalFormat": true,
"cortex-debug.showRTOS": true,
- "cortex-debug.armToolchainPath.windows": "${workspaceFolder}/toolchain/i686-windows/bin",
+ "cortex-debug.armToolchainPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/bin",
"cortex-debug.armToolchainPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin",
"cortex-debug.armToolchainPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin",
- "cortex-debug.openocdPath.windows": "${workspaceFolder}/toolchain/i686-windows/openocd/bin/openocd.exe",
+ "cortex-debug.openocdPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/openocd/bin/openocd.exe",
"cortex-debug.openocdPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/openocd/bin/openocd",
"cortex-debug.openocdPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/openocd/bin/openocd",
+ "cortex-debug.gdbPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gdb-py.bat",
+ "cortex-debug.gdbPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gdb-py",
+ "cortex-debug.gdbPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gdb-py",
"editor.formatOnSave": true,
"files.associations": {
"*.scons": "python",
"SConscript": "python",
"SConstruct": "python",
"*.fam": "python",
- }
+ },
+ "clangd.arguments": [
+ // We might be able to tighten this a bit more to only include the correct toolchain.
+ "--query-driver=**",
+ "--compile-commands-dir=${workspaceFolder}/build/latest"
+ ]
}
\ No newline at end of file
diff --git a/.vscode/example/tasks.json b/.vscode/example/tasks.json
index 32efa006827..e36a1fe47ab 100644
--- a/.vscode/example/tasks.json
+++ b/.vscode/example/tasks.json
@@ -4,41 +4,29 @@
"version": "2.0.0",
"tasks": [
{
- "label": "[Release] Build",
+ "label": "[Release] Build Firmware",
"group": "build",
"type": "shell",
"command": "./fbt COMPACT=1 DEBUG=0"
},
{
- "label": "[Debug] Build",
+ "label": "[Debug] Build Firmware",
"group": "build",
"type": "shell",
"command": "./fbt"
},
{
- "label": "[Release] Flash (ST-Link)",
+ "label": "[Release] Flash (SWD)",
"group": "build",
"type": "shell",
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash"
},
{
- "label": "[Debug] Flash (ST-Link)",
+ "label": "[Debug] Flash (SWD)",
"group": "build",
"type": "shell",
"command": "./fbt FORCE=1 flash"
},
- {
- "label": "[Release] Flash (blackmagic)",
- "group": "build",
- "type": "shell",
- "command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_blackmagic"
- },
- {
- "label": "[Debug] Flash (blackmagic)",
- "group": "build",
- "type": "shell",
- "command": "./fbt FORCE=1 flash_blackmagic"
- },
{
"label": "[Release] Flash (JLink)",
"group": "build",
@@ -93,11 +81,103 @@
"type": "shell",
"command": "./fbt FIRMWARE_APP_SET=unit_tests FORCE=1 flash_usb"
},
+ {
+ "label": "[Debug] Flash (USB, with resources)",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt FORCE=1 flash_usb_full"
+ },
{
"label": "[Release] Flash (USB, with resources)",
"group": "build",
"type": "shell",
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb_full"
},
+ {
+ "label": "[Debug] Create PVS-Studio report",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt firmware_pvs"
+ },
+ {
+ "label": "[Debug] Build FAPs",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt fap_dist"
+ },
+ {
+ "label": "[Release] Build FAPs",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt COMPACT=1 DEBUG=0 fap_dist"
+ },
+ {
+ "label": "[Debug] Build App",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt build APPSRC=${relativeFileDirname}"
+ },
+ {
+ "label": "[Release] Build App",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt COMPACT=1 DEBUG=0 build APPSRC=${relativeFileDirname}"
+ },
+ {
+ "label": "[Debug] Launch App on Flipper",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt launch APPSRC=${relativeFileDirname}"
+ },
+ {
+ "label": "[Release] Launch App on Flipper",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt COMPACT=1 DEBUG=0 launch APPSRC=${relativeFileDirname}"
+ },
+ {
+ "label": "[Debug] Launch App on Flipper with Serial Console",
+ "dependsOrder": "sequence",
+ "group": "build",
+ "dependsOn": [
+ "[Debug] Launch App on Flipper",
+ "Serial Console"
+ ]
+ },
+ {
+ "label": "[Debug] Build and upload all FAPs to Flipper over USB",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt fap_deploy"
+ },
+ {
+ "label": "[Release] Build and upload all FAPs to Flipper over USB",
+ "group": "build",
+ "type": "shell",
+ "command": "./fbt COMPACT=1 DEBUG=0 fap_deploy"
+ },
+ {
+ // Press Ctrl+] to quit
+ "label": "Serial Console",
+ "type": "shell",
+ "command": "./fbt cli",
+ "group": "none",
+ "isBackground": true,
+ "options": {
+ "env": {
+ "FBT_NO_SYNC": "0"
+ }
+ },
+ "presentation": {
+ "reveal": "always",
+ "revealProblems": "never",
+ "showReuseMessage": false,
+ "panel": "dedicated",
+ "focus": true,
+ "echo": true,
+ "close": true,
+ "group": "Logger"
+ }
+ }
]
}
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
deleted file mode 100644
index b53ffc24cfc..00000000000
--- a/.vscode/extensions.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
- // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
- // List of extensions which should be recommended for users of this workspace.
- "recommendations": [
- "ms-python.black-formatter",
- "ms-vscode.cpptools",
- "amiralizadeh9480.cpp-helper",
- "marus25.cortex-debug",
- "zxh404.vscode-proto3",
- "augustocdias.tasks-shell-input"
- ],
- // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
- "unwantedRecommendations": []
-}
\ No newline at end of file
diff --git a/Brewfile b/Brewfile
deleted file mode 100644
index d9ba70e0ae1..00000000000
--- a/Brewfile
+++ /dev/null
@@ -1,6 +0,0 @@
-cask "gcc-arm-embedded"
-brew "protobuf"
-brew "gdb"
-brew "open-ocd"
-brew "clang-format"
-brew "dfu-util"
diff --git a/CODING_STYLE.md b/CODING_STYLE.md
index 31426b941b4..002c67f246a 100644
--- a/CODING_STYLE.md
+++ b/CODING_STYLE.md
@@ -3,15 +3,15 @@
Nice to see you reading this document, we really appreciate it.
As all documents of this kind it's unable to cover everything.
-But it will cover general rules that we enforcing on PR review.
+But it will cover general rules that we are enforcing on PR review.
-Also we already have automatic rules checking and formatting,
-but it got it's limitations and this guide is still mandatory.
+Also, we already have automatic rules checking and formatting,
+but it got its limitations and this guide is still mandatory.
-Some part of this project do have it's own naming and coding guides.
+Some part of this project do have its own naming and coding guides.
For example: assets. Take a look into `ReadMe.md` in assets folder for more details.
-Also 3rd party libraries are none of our concern.
+Also, 3rd party libraries are none of our concern.
And yes, this set is not final and we are open to discussion.
If you want to add/remove/change something here please feel free to open new ticket.
@@ -30,7 +30,7 @@ Our guide is inspired by, but not claiming to be compatible with:
Code we write is intended to be public.
Avoid one-liners from hell and keep code complexity under control.
-Try to make code self explanatory and add comments if needed.
+Try to make code self-explanatory and add comments if needed.
Leave references to standards that you are implementing.
Use project wiki to document new/reverse engineered standards.
@@ -48,11 +48,11 @@ Almost everything in flipper firmware is built around this concept.
# C coding style
- Tab is 4 spaces
-- Use `fbt format` to reformat source code and check style guide before commit
+- Use `./fbt format` to reformat source code and check style guide before commit
## Naming
-### Type names are CamelCase
+### Type names are PascalCase
Examples:
@@ -89,7 +89,7 @@ Enforced by linter.
Suffixes:
- `alloc` - allocate and init instance. C style constructor. Returns pointer to instance.
-- `free` - deinit and release instance. C style destructor. Takes pointer to instance.
+- `free` - de-init and release instance. C style destructor. Takes pointer to instance.
# C++ coding style
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 00a0191e5ed..3f4853116a5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,7 +2,7 @@
Thank you for investing your time in contributing to our project!
-Read our [Code of Coduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable.
+Read our [Code of Conduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable.
In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR.
@@ -17,14 +17,14 @@ See the [ReadMe](ReadMe.md) to get an overview of the project. Here are some hel
## Getting started
-Before writing code and creating PR make sure that it aligns with our mission and guidlines:
+Before writing code and creating PR make sure that it aligns with our mission and guidelines:
- All our devices are intended for research and education.
- PR that contains code intended to commit crimes is not going to be accepted.
- Your PR must comply with our [Coding Style](CODING_STYLE.md)
-- Your PR must contain code compatiable with project [LICENSE](LICENSE).
-- PR will only be merged if it pass CI/CD.
-- PR will only be merged if it pass review by code owner.
+- Your PR must contain code compatible with project [LICENSE](LICENSE).
+- PR will only be merged if it passes CI/CD.
+- PR will only be merged if it passes review by code owner.
Feel free to ask questions in issues if you're not sure.
@@ -59,7 +59,7 @@ Commit the changes once you are happy with them. Make sure that code compilation
### Pull Request
When you're done making the changes, open a pull request, often referred to as a PR.
-- Fill out the "Ready for review" template so we can review your PR. This template helps reviewers understand your changes and the purpose of your pull request.
+- Fill out the "Ready for review" template, so we can review your PR. This template helps reviewers understand your changes and the purpose of your pull request.
- Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one.
- Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge.
Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request for additional information.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 30f16cbd382..00000000000
--- a/Makefile
+++ /dev/null
@@ -1,21 +0,0 @@
-$(info +-------------------------------------------------+)
-$(info | |)
-$(info | Hello, this is Flipper team speaking! |)
-$(info | |)
-$(info | We've migrated to new build system |)
-$(info | It's nice and based on scons |)
-$(info | |)
-$(info | Crash course: |)
-$(info | |)
-$(info | `./fbt` |)
-$(info | `./fbt flash` |)
-$(info | `./fbt debug` |)
-$(info | |)
-$(info | More details in documentation/fbt.md |)
-$(info | |)
-$(info | Also Please leave your feedback here: |)
-$(info | https://flipp.dev/4RDu |)
-$(info | or |)
-$(info | https://flipp.dev/2XM8 |)
-$(info | |)
-$(info +-------------------------------------------------+)
diff --git a/ReadMe.md b/ReadMe.md
index 101c98f3603..02bea9256ec 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -1,7 +1,24 @@
+<<<<<<< HEAD
# Flipper Zero Firmware (Wetox fork, unofficial!)
+=======
+
-![Show me the code](https://habrastorage.org/webt/eo/m0/e4/eom0e4btudte7nrhnyic-laiog0.png)
+# Flipper Zero Firmware
+- [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what Flipper Zero can do.
+- [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices.
+- [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and anything you want to ask.
+>>>>>>> upstream/dev
+
+# Contributing
+
+<<<<<<< HEAD
Welcome to the [Wetox Flipper Zero](https://wetox.team/)'s Firmware repo!
This is a modified unofficial version of the Flipper Zero firmware with some additional features.
@@ -11,16 +28,61 @@ This is a modified unofficial version of the Flipper Zero firmware with some add
* Removing T5577 passwords via the cli command `rfid clear_pass_t5577` using a dictionary attack
* Support for inverted screens via a toggle in the desktop settings
+=======
+Our main goal is to build a healthy and sustainable community around Flipper, so we're open to any new ideas and contributions. We also have some rules and taboos here, so please read this page and our [Code of Conduct](/CODE_OF_CONDUCT.md) carefully.
+>>>>>>> upstream/dev
+
+## I need help
+
+The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/).
+
+## I want to report an issue
+
+If you've found an issue and want to report it, please check our [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page. Make sure the description contains information about the firmware version you're using, your platform, and a clear explanation of the steps to reproduce the issue.
+
+## I want to contribute code
+
+Before opening a PR, please confirm that your changes must be contained in the firmware. Many ideas can easily be implemented as external applications and published in the [Flipper Application Catalog](https://github.com/flipperdevices/flipper-application-catalog). If you are unsure, reach out to us on the [Discord Server](https://flipp.dev/discord) or the [Issues](https://github.com/flipperdevices/flipperzero-firmware/issues) page, and we'll help you find the right place for your code.
+
+Also, please read our [Contribution Guide](/CONTRIBUTING.md) and our [Coding Style](/CODING_STYLE.md), and make sure your code is compatible with our [Project License](/LICENSE).
+
+Finally, open a [Pull Request](https://github.com/flipperdevices/flipperzero-firmware/pulls) and make sure that CI/CD statuses are all green.
+
+# Development
+
+Flipper Zero Firmware is written in C, with some bits and pieces written in C++ and armv7m assembly languages. An intermediate level of C knowledge is recommended for comfortable programming. C, C++, and armv7m assembly languages are supported for Flipper applications.
+
+# Firmware RoadMap
+
+[Firmware RoadMap Miro Board](https://miro.com/app/board/uXjVO_3D6xU=/)
+
+## Requirements
+
+Supported development platforms:
+
+- Windows 10+ with PowerShell and Git (x86_64)
+- macOS 12+ with Command Line tools (x86_64, arm64)
+- Ubuntu 20.04+ with build-essential and Git (x86_64)
+
+Supported in-circuit debuggers (optional but highly recommended):
+
+- [Flipper Zero Wi-Fi Development Board](https://shop.flipperzero.one/products/wifi-devboard)
+- ST-Link
+- J-Link
-# Clone the Repository
+Flipper Build System will take care of all the other dependencies.
+
+## Cloning source code
+
+Make sure you have enough space and clone the source code:
-You should clone with
```shell
-$ git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git
+git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git
```
-# Update firmware
+## Building
+<<<<<<< HEAD
Flipper Zero's firmware consists of two components:
- Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory and you should never update it.
@@ -135,38 +197,62 @@ heatshrink has to be compiled [from sources](https://github.com/atomicobject/hea
## Compile everything
```sh
+=======
+Build firmware using Flipper Build Tool:
+
+```shell
+>>>>>>> upstream/dev
./fbt
```
-Check `dist/` for build outputs.
-
-Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device.
+## Flashing firmware using an in-circuit debugger
-## Flash everything
+Connect your in-circuit debugger to your Flipper and flash firmware using Flipper Build Tool:
-Connect your device via ST-Link and run:
-```sh
-./fbt firmware_flash
+```shell
+./fbt flash
```
Or connect via the blackmagic board and run:
```sh
BLACKMAGIC=:2345 make blackmagic_load
```
-# Links
+## Flashing firmware using USB
+<<<<<<< HEAD
* Telegram: [@wetox_flipper](https://t.me/wetox_flipper)
+=======
+Make sure your Flipper is on, and your firmware is functioning. Connect your Flipper with a USB cable and flash firmware using Flipper Build Tool:
+
+```shell
+./fbt flash_usb
+```
+
+## Documentation
+
+- [Flipper Build Tool](/documentation/fbt.md) - building, flashing, and debugging Flipper software
+- [Applications](/documentation/AppsOnSDCard.md), [Application Manifest](/documentation/AppManifests.md) - developing, building, deploying, and debugging Flipper applications
+- [Hardware combos and Un-bricking](/documentation/KeyCombo.md) - recovering your Flipper from the most nasty situations
+- [Flipper File Formats](/documentation/file_formats) - everything about how Flipper stores your data and how you can work with it
+- [Universal Remotes](/documentation/UniversalRemotes.md) - contributing your infrared remote to the universal remote database
+- And much more in the [documentation](/documentation) folder
+>>>>>>> upstream/dev
# Project structure
-- `applications` - Applications and services used in firmware
-- `assets` - Assets used by applications and services
-- `furi` - Furi Core: os level primitives and helpers
-- `debug` - Debug tool: GDB-plugins, SVD-file and etc
-- `docker` - Docker image sources (used for firmware build automation)
-- `documentation` - Documentation generation system configs and input files
-- `firmware` - Firmware source code
-- `lib` - Our and 3rd party libraries, drivers and etc...
-- `scripts` - Supplementary scripts and python libraries home
-
-Also pay attention to `ReadMe.md` files inside of those directories.
+- `applications` - applications and services used in firmware
+- `assets` - assets used by applications and services
+- `furi` - Furi Core: OS-level primitives and helpers
+- `documentation` - documentation generation system configs and input files
+- `firmware` - firmware source code
+- `lib` - our and 3rd party libraries, drivers, etc.
+- `scripts` - supplementary scripts and python libraries home
+
+Also, see `ReadMe.md` files inside those directories for further details.
+
+# Links
+
+- Discord: [flipp.dev/discord](https://flipp.dev/discord)
+- Website: [flipperzero.one](https://flipperzero.one)
+- Forum: [forum.flipperzero.one](https://forum.flipperzero.one/)
+- Kickstarter: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers)
diff --git a/RoadMap.md b/RoadMap.md
deleted file mode 100644
index d0bee3fea74..00000000000
--- a/RoadMap.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# RoadMap (for the stock FW, not the wetox fork)
-
-# Where we are (0.x.x branch)
-
-Our goal for 0.x.x branch is to build stable usable apps and API.
-First public release that we support in this branch is 0.43.1. Your device most likely came with this version.
-You can develop applications but keep in mind that API is not final yet.
-
-## What's already implemented
-
-**Applications**
-
-- SubGhz: all most common protocols, reading RAW for everything else
-- 125kHz RFID: all most common protocols
-- NFC: reading/emulating Mifare Ultralight, reading MiFare Classic and DESFire, basic EMV, basic NFC-B,F,V
-- Infrared: all most common RC protocols, RAW format for everything else
-- GPIO: UART bridge, basic GPIO controls
-- iButton: DS1990, Cyfral, Metacom
-- Bad USB: Full USB Rubber Ducky support, some extras for windows alt codes
-- U2F: Full U2F specification support
-
-**Extras**
-
-- BLE Keyboard
-- Snake game
-
-**System and HAL**
-
-- Furi Core
-- Furi HAL
-
-# Where we're going (Version 1)
-
-Main goal for 1.0.0 is to provide first stable version for both Users and Developers.
-
-## What we're planning to implement in 1.0.0
-
-- Loading applications from SD (tested as PoC, work scheduled for Q2)
-- More protocols (gathering feedback)
-- User documentation (work in progress)
-- FuriCore: get rid of CMSIS API, replace hard real time timers, improve stability and performance (work in progress)
-- FuriHal: deep sleep mode, stable API, examples, documentation (work in progress)
-- Application improvements (a ton of things that we want to add and improve that are too numerous to list here)
-
-## When will it happen and where I can see the progress?
-
-Release 1.0.0 will most likely happen around the end of Q3
-
-Development progress can be tracked in our public Miro board:
-
-https://miro.com/app/board/uXjVO_3D6xU=/?moveToWidget=3458764522498020058&cot=14
diff --git a/SConstruct b/SConstruct
index 52fe75a6a9c..b42218a579c 100644
--- a/SConstruct
+++ b/SConstruct
@@ -1,5 +1,5 @@
#
-# Main Fipper Build System entry point
+# Main Flipper Build System entry point
#
# This file is evaluated by scons (the build system) every time fbt is invoked.
# Scons constructs all referenced environments & their targets' dependency
@@ -7,65 +7,45 @@
# construction of certain targets behind command-line options.
import os
-import subprocess
+from fbt.util import path_as_posix
+
+DefaultEnvironment(tools=[])
EnsurePythonVersion(3, 8)
-DefaultEnvironment(tools=[])
# Progress(["OwO\r", "owo\r", "uwu\r", "owo\r"], interval=15)
-
-# This environment is created only for loading options & validating file/dir existance
+# This environment is created only for loading options & validating file/dir existence
fbt_variables = SConscript("site_scons/commandline.scons")
-cmd_environment = Environment(tools=[], variables=fbt_variables)
-Help(fbt_variables.GenerateHelpText(cmd_environment))
+cmd_environment = Environment(
+ toolpath=["#/scripts/fbt_tools"],
+ tools=[
+ ("fbt_help", {"vars": fbt_variables}),
+ ],
+ variables=fbt_variables,
+)
# Building basic environment - tools, utility methods, cross-compilation
# settings, gcc flags for Cortex-M4, basic builders and more
coreenv = SConscript(
"site_scons/environ.scons",
exports={"VAR_ENV": cmd_environment},
+ toolpath=["#/scripts/fbt_tools"],
)
SConscript("site_scons/cc.scons", exports={"ENV": coreenv})
-# Store root dir in environment for certain tools
-coreenv["ROOT_DIR"] = Dir(".")
-
-
# Create a separate "dist" environment and add construction envs to it
distenv = coreenv.Clone(
- tools=["fbt_dist", "openocd", "blackmagic", "jflash"],
- OPENOCD_GDB_PIPE=[
- "|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}"
+ tools=[
+ "fbt_dist",
+ "fbt_debugopts",
+ "openocd",
+ "blackmagic",
+ "jflash",
],
- GDBOPTS_BASE=[
- "-ex",
- "target extended-remote ${GDBREMOTE}",
- "-ex",
- "set confirm off",
- ],
- GDBOPTS_BLACKMAGIC=[
- "-ex",
- "monitor swdp_scan",
- "-ex",
- "monitor debug_bmp enable",
- "-ex",
- "attach 1",
- "-ex",
- "set mem inaccessible-by-default off",
- ],
- GDBPYOPTS=[
- "-ex",
- "source debug/FreeRTOS/FreeRTOS.py",
- "-ex",
- "source debug/PyCortexMDebug/PyCortexMDebug.py",
- "-ex",
- "svd_load ${SVD_FILE}",
- "-ex",
- "compare-sections",
- ],
- JFLASHPROJECT="${ROOT_DIR.abspath}/debug/fw.jflash",
ENV=os.environ,
+ UPDATE_BUNDLE_DIR="dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}",
+ VSCODE_LANG_SERVER=ARGUMENTS.get("LANG_SERVER", "cpptools"),
)
firmware_env = distenv.AddFwProject(
@@ -87,20 +67,22 @@ if GetOption("fullenv") or any(
# Target for self-update package
dist_basic_arguments = [
"--bundlever",
- '"${UPDATE_VERSION_STRING}"',
+ "${UPDATE_VERSION_STRING}",
]
dist_radio_arguments = [
"--radio",
- '"${ROOT_DIR.abspath}/${COPRO_STACK_BIN_DIR}/${COPRO_STACK_BIN}"',
+ "${ROOT_DIR.abspath}/${COPRO_STACK_BIN_DIR}/${COPRO_STACK_BIN}",
"--radiotype",
"${COPRO_STACK_TYPE}",
"${COPRO_DISCLAIMER}",
"--obdata",
- '"${ROOT_DIR.abspath}/${COPRO_OB_DATA}"',
+ "${ROOT_DIR.abspath}/${COPRO_OB_DATA}",
+ "--stackversion",
+ "${COPRO_CUBE_VERSION}",
]
dist_resource_arguments = [
"-r",
- '"${ROOT_DIR.abspath}/assets/resources"',
+ firmware_env.subst("${RESOURCES_ROOT}"),
]
dist_splash_arguments = (
[
@@ -113,7 +95,7 @@ if GetOption("fullenv") or any(
selfupdate_dist = distenv.DistCommand(
"updater_package",
- (distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES"]),
+ (distenv["DIST_DEPENDS"], firmware_env["FW_RESOURCES_MANIFEST"]),
DIST_EXTRA=[
*dist_basic_arguments,
*dist_radio_arguments,
@@ -146,7 +128,8 @@ if GetOption("fullenv") or any(
# Installation over USB & CLI
usb_update_package = distenv.AddUsbFlashTarget(
- "#build/usbinstall.flag", (firmware_env["FW_RESOURCES"], selfupdate_dist)
+ "#build/usbinstall.flag",
+ (firmware_env["FW_RESOURCES_MANIFEST"], selfupdate_dist),
)
distenv.Alias("flash_usb_full", usb_update_package)
@@ -160,28 +143,78 @@ if GetOption("fullenv") or any(
basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"])
distenv.Default(basic_dist)
+dist_dir_name = distenv.GetProjetDirName()
+dist_dir = distenv.Dir(f"#/dist/{dist_dir_name}")
+external_apps_artifacts = firmware_env["FW_EXTAPPS"]
+external_app_list = external_apps_artifacts.application_map.values()
+
+fap_dist = [
+ distenv.Install(
+ dist_dir.Dir("debug_elf"),
+ list(app_artifact.debug for app_artifact in external_app_list),
+ ),
+ *(
+ distenv.Install(
+ dist_dir.File(dist_entry[1]).dir,
+ app_artifact.compact,
+ )
+ for app_artifact in external_app_list
+ for dist_entry in app_artifact.dist_entries
+ ),
+]
+Depends(
+ fap_dist,
+ list(app_artifact.validator for app_artifact in external_app_list),
+)
+Alias("fap_dist", fap_dist)
+
+# Copy all faps to device
+
+fap_deploy = distenv.PhonyTarget(
+ "fap_deploy",
+ Action(
+ [
+ [
+ "${PYTHON3}",
+ "${FBT_SCRIPT_DIR}/storage.py",
+ "-p",
+ "${FLIP_PORT}",
+ "send",
+ "${SOURCE}",
+ "/ext/apps",
+ ]
+ ]
+ ),
+ source=firmware_env.Dir(("${RESOURCES_ROOT}/apps")),
+)
+Depends(fap_deploy, firmware_env["FW_RESOURCES_MANIFEST"])
+
+
# Target for bundling core2 package for qFlipper
copro_dist = distenv.CoproBuilder(
- distenv.Dir("assets/core2_firmware"),
+ "#/build/core2_firmware.tgz",
[],
)
+distenv.AlwaysBuild(copro_dist)
distenv.Alias("copro_dist", copro_dist)
-firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env)
+
+firmware_flash = distenv.AddFwFlashTarget(firmware_env)
distenv.Alias("flash", firmware_flash)
+# To be implemented in fwflash.py
firmware_jflash = distenv.AddJFlashTarget(firmware_env)
distenv.Alias("jflash", firmware_jflash)
-firmware_bm_flash = distenv.PhonyTarget(
- "flash_blackmagic",
+distenv.PhonyTarget(
+ "gdb_trace_all",
"$GDB $GDBOPTS $SOURCES $GDBFLASH",
source=firmware_env["FW_ELF"],
- GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
- GDBREMOTE="${BLACKMAGIC_ADDR}",
+ GDBOPTS="${GDBOPTS_BASE}",
+ GDBREMOTE="${OPENOCD_GDB_PIPE}",
GDBFLASH=[
"-ex",
- "load",
+ "thread apply all bt",
"-ex",
"quit",
],
@@ -194,6 +227,7 @@ firmware_debug = distenv.PhonyTarget(
source=firmware_env["FW_ELF"],
GDBOPTS="${GDBOPTS_BASE}",
GDBREMOTE="${OPENOCD_GDB_PIPE}",
+ FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")),
)
distenv.Depends(firmware_debug, firmware_flash)
@@ -203,16 +237,38 @@ distenv.PhonyTarget(
source=firmware_env["FW_ELF"],
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
GDBREMOTE="${BLACKMAGIC_ADDR}",
+ FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")),
)
# Debug alien elf
+debug_other_opts = [
+ "-ex",
+ "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py",
+ # "-ex",
+ # "source ${FBT_DEBUG_DIR}/FreeRTOS/FreeRTOS.py",
+ "-ex",
+ "source ${FBT_DEBUG_DIR}/flipperversion.py",
+ "-ex",
+ "fw-version",
+]
+
distenv.PhonyTarget(
"debug_other",
"${GDBPYCOM}",
- GDBPYOPTS='-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" ',
+ GDBOPTS="${GDBOPTS_BASE}",
GDBREMOTE="${OPENOCD_GDB_PIPE}",
+ GDBPYOPTS=debug_other_opts,
)
+distenv.PhonyTarget(
+ "debug_other_blackmagic",
+ "${GDBPYCOM}",
+ GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
+ GDBREMOTE="${BLACKMAGIC_ADDR}",
+ GDBPYOPTS=debug_other_opts,
+)
+
+
# Just start OpenOCD
distenv.PhonyTarget(
"openocd",
@@ -222,25 +278,28 @@ distenv.PhonyTarget(
# Linter
distenv.PhonyTarget(
"lint",
- "${PYTHON3} scripts/lint.py check ${LINT_SOURCES}",
- LINT_SOURCES=firmware_env["LINT_SOURCES"],
+ [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "check", "${LINT_SOURCES}"]],
+ LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
)
distenv.PhonyTarget(
"format",
- "${PYTHON3} scripts/lint.py format ${LINT_SOURCES}",
- LINT_SOURCES=firmware_env["LINT_SOURCES"],
+ [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "format", "${LINT_SOURCES}"]],
+ LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
)
-# PY_LINT_SOURCES contains recursively-built modules' SConscript files + application manifests
+# PY_LINT_SOURCES contains recursively-built modules' SConscript files
# Here we add additional Python files residing in repo root
firmware_env.Append(
PY_LINT_SOURCES=[
# Py code folders
"site_scons",
"scripts",
+ "applications",
+ "applications_user",
+ "assets",
+ "targets",
# Extra files
- "applications/extapps.scons",
"SConstruct",
"firmware.scons",
"fbt_options.py",
@@ -249,7 +308,10 @@ firmware_env.Append(
black_commandline = "@${PYTHON3} -m black ${PY_BLACK_ARGS} ${PY_LINT_SOURCES}"
-black_base_args = ["--include", '"\\.scons|\\.py|SConscript|SConstruct"']
+black_base_args = [
+ "--include",
+ '"(\\.scons|\\.py|SConscript|SConstruct|\\.fam)$"',
+]
distenv.PhonyTarget(
"lint_py",
@@ -270,7 +332,14 @@ distenv.PhonyTarget(
)
# Start Flipper CLI via PySerial's miniterm
-distenv.PhonyTarget("cli", "${PYTHON3} scripts/serial_cli.py")
+distenv.PhonyTarget(
+ "cli", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/serial_cli.py", "-p", "${FLIP_PORT}"]]
+)
+
+# Update WiFi devboard firmware
+distenv.PhonyTarget(
+ "devboard_flash", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/wifi_board.py"]]
+)
# Find blackmagic probe
@@ -279,8 +348,31 @@ distenv.PhonyTarget(
"@echo $( ${BLACKMAGIC_ADDR} $)",
)
+
+# Find STLink probe ids
+distenv.PhonyTarget(
+ "get_stlink",
+ distenv.Action(
+ lambda **kw: distenv.GetDevices(),
+ None,
+ ),
+)
+
# Prepare vscode environment
-vscode_dist = distenv.Install("#.vscode", distenv.Glob("#.vscode/example/*"))
+VSCODE_LANG_SERVER = cmd_environment["LANG_SERVER"]
+vscode_dist = distenv.Install(
+ "#.vscode",
+ [
+ distenv.Glob("#.vscode/example/*.json"),
+ distenv.Glob(f"#.vscode/example/{VSCODE_LANG_SERVER}/*.json"),
+ ],
+)
distenv.Precious(vscode_dist)
distenv.NoClean(vscode_dist)
distenv.Alias("vscode_dist", vscode_dist)
+
+# Configure shell with build tools
+distenv.PhonyTarget(
+ "env",
+ "@echo $( ${FBT_SCRIPT_DIR.abspath}/toolchain/fbtenv.sh $)",
+)
diff --git a/applications/ReadMe.md b/applications/ReadMe.md
index cb2f5628d28..de465832aef 100644
--- a/applications/ReadMe.md
+++ b/applications/ReadMe.md
@@ -1,38 +1,78 @@
# Structure
-- `about` - Small About application that shows flipper info
-- `accessor` - Wiegand server
+## debug
+
+Applications for factory testing the Flipper.
+
+- `accessor` - Wiegand server
+- `battery_test_app` - Battery debug app
+- `blink_test` - LED blinker
+- `bt_debug_app` - BT test app. Requires full BT stack installed
+- `display_test` - Various display tests & tweaks
+- `file_browser_test` - Test UI for file picker
+- `keypad_test` - Keypad test
+- `lfrfid_debug` - LF RFID debug tool
+- `text_box_test` - UI tests
+- `uart_echo` - UART mode test
+- `unit_tests` - Unit tests
+- `usb_mouse` - USB HID test
+- `usb_test` - Other USB tests
+- `vibro_test` - Vibro test
+
+
+## main
+
+Applications for main Flipper menu.
+
- `archive` - Archive and file manager
- `bad_usb` - Bad USB application
+- `gpio` - GPIO application: includes USART bridge and GPIO control
+- `ibutton` - iButton application, onewire keys and more
+- `infrared` - Infrared application, controls your IR devices
+- `lfrfid` - LF RFID application
+- `nfc` - NFC application, HF rfid, EMV and etc
+- `subghz` - SubGhz application, 433 fobs and etc
+- `u2f` - U2F Application
+
+
+## services
+
+Background services providing system APIs to applications.
+
+- `applications.h` - Firmware application list header
+
- `bt` - BLE service and application
- `cli` - Console service and API
- `crypto` - Crypto cli tools
-- `debug_tools` - Different tools that we use for debug
- `desktop` - Desktop service
- `dialogs` - Dialogs service: GUI Dialogs for your app
- `dolphin` - Dolphin service and supplementary apps
-- `gpio` - GPIO application: includes USART bridge and GPIO control
- `gui` - GUI service and API
-- `ibutton` - iButton application, onewire keys and more
- `input` - Input service
-- `infrared` - Infrared application, controls your IR devices
-- `lfrfid` - LF RFID application
-- `lfrfid_debug` - LF RFID debug tool
- `loader` - Application loader service
-- `music_player` - Music player app (demo)
-- `nfc` - NFC application, HF rfid, EMV and etc
- `notification` - Notification service
- `power` - Power service
-- `power_observer` - Power debug tool
- `rpc` - RPC service and API
-- `scened_app_example` - C++ application example
-- `snake_game` - Snake game application
- `storage` - Storage service, internal + sdcard
+
+
+## settings
+
+Small applications providing configuration for basic firmware and its services.
+
+- `about` - Small About application that shows flipper info
+- `bt_settings_app` - Bluetooth options
+- `desktop_settings` - Desktop configuration
+- `dolphin_passport` - Dolphin passport app
+- `notification_settings` - LCD brightness, sound volume, etc configuration
+- `power_settings_app` - Basic power options
- `storage_settings` - Storage settings app
-- `subghz` - SubGhz application, 433 fobs and etc
-- `system` - System settings, tools and API
-- `tests` - Unit tests and etc
-- `u2f` - U2F Application
-- `updater` - Update service & application
+- `system` - System settings
-- `application.h` - Firmware application list header
+
+## system
+
+Utility apps not visible in other menus.
+
+- `storage_move_to_sd` - Data migration tool for internal storage
+- `updater` - Update service & application
diff --git a/applications/accessor/application.fam b/applications/accessor/application.fam
deleted file mode 100644
index 8a94049e14a..00000000000
--- a/applications/accessor/application.fam
+++ /dev/null
@@ -1,10 +0,0 @@
-App(
- appid="accessor",
- name="Accessor",
- apptype=FlipperAppType.DEBUG,
- entry_point="accessor_app",
- cdefines=["APP_ACCESSOR"],
- requires=["gui"],
- stack_size=4 * 1024,
- order=40,
-)
diff --git a/applications/applications.h b/applications/applications.h
deleted file mode 100644
index 012e80ddb67..00000000000
--- a/applications/applications.h
+++ /dev/null
@@ -1,72 +0,0 @@
-#pragma once
-
-#include
-#include
-
-typedef enum {
- FlipperApplicationFlagDefault = 0,
- FlipperApplicationFlagInsomniaSafe = (1 << 0),
-} FlipperApplicationFlag;
-
-typedef struct {
- const FuriThreadCallback app;
- const char* name;
- const size_t stack_size;
- const Icon* icon;
- const FlipperApplicationFlag flags;
-} FlipperApplication;
-
-typedef void (*FlipperOnStartHook)(void);
-
-extern const char* FLIPPER_AUTORUN_APP_NAME;
-
-/* Services list
- * Spawned on startup
- */
-extern const FlipperApplication FLIPPER_SERVICES[];
-extern const size_t FLIPPER_SERVICES_COUNT;
-
-/* Apps list
- * Spawned by loader
- */
-extern const FlipperApplication FLIPPER_APPS[];
-extern const size_t FLIPPER_APPS_COUNT;
-
-/* On system start hooks
- * Called by loader, after OS initialization complete
- */
-extern const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[];
-extern const size_t FLIPPER_ON_SYSTEM_START_COUNT;
-
-/* Plugins list
- * Spawned by loader
- */
-extern const FlipperApplication FLIPPER_PLUGINS[];
-extern const size_t FLIPPER_PLUGINS_COUNT;
-
-/* Debug menu apps
- * Spawned by loader
- */
-extern const FlipperApplication FLIPPER_DEBUG_APPS[];
-extern const size_t FLIPPER_DEBUG_APPS_COUNT;
-
-/* System apps
- * Can only be spawned by loader by name
- */
-extern const FlipperApplication FLIPPER_SYSTEM_APPS[];
-extern const size_t FLIPPER_SYSTEM_APPS_COUNT;
-
-/* Seperate scene app holder
- * Spawned by loader
- */
-extern const FlipperApplication FLIPPER_SCENE;
-extern const FlipperApplication FLIPPER_SCENE_APPS[];
-extern const size_t FLIPPER_SCENE_APPS_COUNT;
-
-extern const FlipperApplication FLIPPER_ARCHIVE;
-
-/* Settings list
- * Spawned by loader
- */
-extern const FlipperApplication FLIPPER_SETTINGS_APPS[];
-extern const size_t FLIPPER_SETTINGS_APPS_COUNT;
diff --git a/applications/archive/helpers/archive_browser.c b/applications/archive/helpers/archive_browser.c
deleted file mode 100644
index 54759dadc95..00000000000
--- a/applications/archive/helpers/archive_browser.c
+++ /dev/null
@@ -1,496 +0,0 @@
-#include "archive/views/archive_browser_view.h"
-#include "archive_files.h"
-#include "archive_apps.h"
-#include "archive_browser.h"
-#include
-#include
-#include "gui/modules/file_browser_worker.h"
-#include "m-string.h"
-#include
-
-static void
- archive_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root) {
- furi_assert(context);
- ArchiveBrowserView* browser = (ArchiveBrowserView*)context;
-
- int32_t load_offset = 0;
- browser->is_root = is_root;
- ArchiveTabEnum tab = archive_get_tab(browser);
-
- if((item_cnt == 0) && (archive_is_home(browser)) && (tab != ArchiveTabBrowser)) {
- archive_switch_tab(browser, browser->last_tab_switch_dir);
- } else if(!string_start_with_str_p(browser->path, "/app:")) {
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- files_array_reset(model->files);
- model->item_cnt = item_cnt;
- model->item_idx = (file_idx > 0) ? file_idx : 0;
- load_offset =
- CLAMP(model->item_idx - FILE_LIST_BUF_LEN / 2, (int32_t)model->item_cnt, 0);
- model->array_offset = 0;
- model->list_offset = 0;
- model->list_loading = true;
- model->folder_loading = false;
- return false;
- });
- archive_update_offset(browser);
-
- file_browser_worker_load(browser->worker, load_offset, FILE_LIST_BUF_LEN);
- }
-}
-
-static void archive_list_load_cb(void* context, uint32_t list_load_offset) {
- furi_assert(context);
- ArchiveBrowserView* browser = (ArchiveBrowserView*)context;
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- files_array_reset(model->files);
- model->array_offset = list_load_offset;
- return false;
- });
-}
-
-static void archive_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) {
- furi_assert(context);
- ArchiveBrowserView* browser = (ArchiveBrowserView*)context;
-
- if(!is_last) {
- archive_add_file_item(browser, is_folder, string_get_cstr(item_path));
- } else {
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- model->list_loading = false;
- return true;
- });
- }
-}
-
-static void archive_long_load_cb(void* context) {
- furi_assert(context);
- ArchiveBrowserView* browser = (ArchiveBrowserView*)context;
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- model->folder_loading = true;
- return true;
- });
-}
-
-void archive_file_browser_set_callbacks(ArchiveBrowserView* browser) {
- furi_assert(browser);
-
- file_browser_worker_set_callback_context(browser->worker, browser);
- file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb);
- file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb);
- file_browser_worker_set_item_callback(browser->worker, archive_list_item_cb);
- file_browser_worker_set_long_load_callback(browser->worker, archive_long_load_cb);
-}
-
-bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) {
- size_t array_size = files_array_size(model->files);
-
- if((idx >= (uint32_t)model->array_offset + array_size) ||
- (idx < (uint32_t)model->array_offset)) {
- return false;
- }
-
- return true;
-}
-
-void archive_update_offset(ArchiveBrowserView* browser) {
- furi_assert(browser);
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- uint16_t bounds = model->item_cnt > 3 ? 2 : model->item_cnt;
-
- if((model->item_cnt > 3u) && (model->item_idx >= ((int32_t)model->item_cnt - 1))) {
- model->list_offset = model->item_idx - 3;
- } else if(model->list_offset < model->item_idx - bounds) {
- model->list_offset =
- CLAMP(model->item_idx - 2, (int32_t)model->item_cnt - bounds, 0);
- } else if(model->list_offset > model->item_idx - bounds) {
- model->list_offset =
- CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0);
- }
-
- return true;
- });
-}
-
-void archive_update_focus(ArchiveBrowserView* browser, const char* target) {
- furi_assert(browser);
- furi_assert(target);
-
- archive_get_items(browser, string_get_cstr(browser->path));
-
- if(!archive_file_get_array_size(browser) && archive_is_home(browser)) {
- archive_switch_tab(browser, TAB_RIGHT);
- } else {
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- uint16_t idx = 0;
- while(idx < files_array_size(model->files)) {
- ArchiveFile_t* current = files_array_get(model->files, idx);
- if(!string_search(current->path, target)) {
- model->item_idx = idx + model->array_offset;
- break;
- }
- ++idx;
- }
- return false;
- });
-
- archive_update_offset(browser);
- }
-}
-
-size_t archive_file_get_array_size(ArchiveBrowserView* browser) {
- furi_assert(browser);
-
- uint16_t size = 0;
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- size = files_array_size(model->files);
- return false;
- });
- return size;
-}
-
-void archive_set_item_count(ArchiveBrowserView* browser, uint32_t count) {
- furi_assert(browser);
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- model->item_cnt = count;
- model->item_idx = CLAMP(model->item_idx, (int32_t)model->item_cnt - 1, 0);
- return false;
- });
- archive_update_offset(browser);
-}
-
-void archive_file_array_rm_selected(ArchiveBrowserView* browser) {
- furi_assert(browser);
- uint32_t items_cnt = 0;
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- files_array_remove_v(
- model->files,
- model->item_idx - model->array_offset,
- model->item_idx - model->array_offset + 1);
- model->item_cnt--;
- model->item_idx = CLAMP(model->item_idx, (int32_t)model->item_cnt - 1, 0);
- items_cnt = model->item_cnt;
- return false;
- });
-
- if((items_cnt == 0) && (archive_is_home(browser))) {
- archive_switch_tab(browser, TAB_RIGHT);
- }
-
- archive_update_offset(browser);
-}
-
-void archive_file_array_swap(ArchiveBrowserView* browser, int8_t dir) {
- furi_assert(browser);
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- ArchiveFile_t temp;
- size_t array_size = files_array_size(model->files) - 1;
- uint8_t swap_idx = CLAMP((size_t)(model->item_idx + dir), array_size, 0u);
-
- if(model->item_idx == 0 && dir < 0) {
- ArchiveFile_t_init(&temp);
- files_array_pop_at(&temp, model->files, array_size);
- files_array_push_at(model->files, model->item_idx, temp);
- ArchiveFile_t_clear(&temp);
- } else if(((uint32_t)model->item_idx == array_size) && (dir > 0)) {
- ArchiveFile_t_init(&temp);
- files_array_pop_at(&temp, model->files, 0);
- files_array_push_at(model->files, array_size, temp);
- ArchiveFile_t_clear(&temp);
- } else {
- files_array_swap_at(model->files, model->item_idx, swap_idx);
- }
- return false;
- });
-}
-
-void archive_file_array_rm_all(ArchiveBrowserView* browser) {
- furi_assert(browser);
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- files_array_reset(model->files);
- return false;
- });
-}
-
-void archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) {
- furi_assert(browser);
-
- int32_t offset_new = 0;
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- if(model->item_cnt > FILE_LIST_BUF_LEN) {
- if(dir < 0) {
- offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 3;
- } else if(dir == 0) {
- offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 2;
- } else {
- offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 1;
- }
- if(offset_new > 0) {
- offset_new =
- CLAMP(offset_new, (int32_t)model->item_cnt - FILE_LIST_BUF_LEN, 0);
- } else {
- offset_new = 0;
- }
- }
- return false;
- });
-
- file_browser_worker_load(browser->worker, offset_new, FILE_LIST_BUF_LEN);
-}
-
-ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) {
- furi_assert(browser);
-
- ArchiveFile_t* selected;
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- selected = files_array_size(model->files) ?
- files_array_get(model->files, model->item_idx - model->array_offset) :
- NULL;
- return false;
- });
- return selected;
-}
-
-ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx) {
- furi_assert(browser);
-
- ArchiveFile_t* selected;
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- idx = CLAMP(idx - model->array_offset, files_array_size(model->files), 0u);
- selected = files_array_size(model->files) ? files_array_get(model->files, idx) : NULL;
- return false;
- });
- return selected;
-}
-
-ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) {
- furi_assert(browser);
-
- ArchiveTabEnum tab_id;
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- tab_id = model->tab_idx;
- return false;
- });
- return tab_id;
-}
-
-bool archive_is_home(ArchiveBrowserView* browser) {
- furi_assert(browser);
-
- if(browser->is_root) {
- return true;
- }
-
- const char* default_path = archive_get_default_path(archive_get_tab(browser));
- return (string_cmp_str(browser->path, default_path) == 0);
-}
-
-const char* archive_get_name(ArchiveBrowserView* browser) {
- ArchiveFile_t* selected = archive_get_current_file(browser);
- return string_get_cstr(selected->path);
-}
-
-void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) {
- furi_assert(browser);
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- model->tab_idx = tab;
- return false;
- });
-}
-
-void archive_add_app_item(ArchiveBrowserView* browser, const char* name) {
- furi_assert(browser);
- furi_assert(name);
-
- ArchiveFile_t item;
- ArchiveFile_t_init(&item);
- string_set_str(item.path, name);
- archive_set_file_type(&item, name, false, true);
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- files_array_push_back(model->files, item);
- model->item_cnt = files_array_size(model->files);
- return false;
- });
- ArchiveFile_t_clear(&item);
-}
-
-void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const char* name) {
- furi_assert(browser);
- furi_assert(name);
-
- ArchiveFile_t item;
-
- ArchiveFile_t_init(&item);
- string_init_set_str(item.path, name);
- archive_set_file_type(&item, string_get_cstr(browser->path), is_folder, false);
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- files_array_push_back(model->files, item);
- return false;
- });
- ArchiveFile_t_clear(&item);
-}
-
-void archive_show_file_menu(ArchiveBrowserView* browser, bool show) {
- furi_assert(browser);
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- if(show) {
- if(archive_is_item_in_array(model, model->item_idx)) {
- model->menu = true;
- model->menu_idx = 0;
- ArchiveFile_t* selected =
- files_array_get(model->files, model->item_idx - model->array_offset);
- selected->fav = archive_is_favorite("%s", string_get_cstr(selected->path));
- }
- } else {
- model->menu = false;
- model->menu_idx = 0;
- }
-
- return true;
- });
-}
-
-void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) {
- furi_assert(browser);
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- model->move_fav = active;
- return true;
- });
-}
-
-static bool archive_is_dir_exists(string_t path) {
- if(string_equal_str_p(path, STORAGE_ANY_PATH_PREFIX)) {
- return true;
- }
- bool state = false;
- FileInfo file_info;
- Storage* storage = furi_record_open(RECORD_STORAGE);
- if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) {
- if(file_info.flags & FSF_DIRECTORY) {
- state = true;
- }
- }
- furi_record_close(RECORD_STORAGE);
- return state;
-}
-
-void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
- furi_assert(browser);
- ArchiveTabEnum tab = archive_get_tab(browser);
-
- browser->last_tab_switch_dir = key;
-
- if(key == InputKeyLeft) {
- tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal;
- } else {
- tab = (tab + 1) % ArchiveTabTotal;
- }
-
- browser->is_root = true;
- archive_set_tab(browser, tab);
-
- string_set_str(browser->path, archive_get_default_path(tab));
- bool tab_empty = true;
- if(tab == ArchiveTabFavorites) {
- if(archive_favorites_count(browser) > 0) {
- tab_empty = false;
- }
- } else if(string_start_with_str_p(browser->path, "/app:")) {
- char* app_name = strchr(string_get_cstr(browser->path), ':');
- if(app_name != NULL) {
- if(archive_app_is_available(browser, string_get_cstr(browser->path))) {
- tab_empty = false;
- }
- }
- } else {
- tab = archive_get_tab(browser);
- if(archive_is_dir_exists(browser->path)) {
- bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true;
- file_browser_worker_set_config(
- browser->worker, browser->path, archive_get_tab_ext(tab), skip_assets);
- tab_empty = false; // Empty check will be performed later
- } else {
- tab_empty = true;
- }
- }
-
- if((tab_empty) && (tab != ArchiveTabBrowser)) {
- archive_switch_tab(browser, key);
- } else {
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- model->item_idx = 0;
- model->array_offset = 0;
- return false;
- });
- archive_get_items(browser, string_get_cstr(browser->path));
- archive_update_offset(browser);
- }
-}
-
-void archive_enter_dir(ArchiveBrowserView* browser, string_t path) {
- furi_assert(browser);
- furi_assert(path);
-
- int32_t idx_temp = 0;
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- idx_temp = model->item_idx;
- return false;
- });
-
- string_set(browser->path, path);
- file_browser_worker_folder_enter(browser->worker, path, idx_temp);
-}
-
-void archive_leave_dir(ArchiveBrowserView* browser) {
- furi_assert(browser);
-
- file_browser_worker_folder_exit(browser->worker);
-}
-
-void archive_refresh_dir(ArchiveBrowserView* browser) {
- furi_assert(browser);
-
- int32_t idx_temp = 0;
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- idx_temp = model->item_idx;
- return false;
- });
- file_browser_worker_folder_refresh(browser->worker, idx_temp);
-}
diff --git a/applications/archive/helpers/archive_favorites.c b/applications/archive/helpers/archive_favorites.c
deleted file mode 100644
index 35199242e95..00000000000
--- a/applications/archive/helpers/archive_favorites.c
+++ /dev/null
@@ -1,350 +0,0 @@
-
-#include "archive_favorites.h"
-#include "archive_files.h"
-#include "archive_apps.h"
-#include "archive_browser.h"
-
-#define ARCHIVE_FAV_FILE_BUF_LEN 32
-
-static bool archive_favorites_read_line(File* file, string_t str_result) {
- string_reset(str_result);
- uint8_t buffer[ARCHIVE_FAV_FILE_BUF_LEN];
- bool result = false;
-
- do {
- uint16_t read_count = storage_file_read(file, buffer, ARCHIVE_FAV_FILE_BUF_LEN);
- if(storage_file_get_error(file) != FSE_OK) {
- return false;
- }
-
- for(uint16_t i = 0; i < read_count; i++) {
- if(buffer[i] == '\n') {
- uint32_t position = storage_file_tell(file);
- if(storage_file_get_error(file) != FSE_OK) {
- return false;
- }
-
- position = position - read_count + i + 1;
-
- storage_file_seek(file, position, true);
- if(storage_file_get_error(file) != FSE_OK) {
- return false;
- }
-
- result = true;
- break;
- } else {
- string_push_back(str_result, buffer[i]);
- }
- }
-
- if(result || read_count == 0) {
- break;
- }
- } while(true);
-
- return result;
-}
-
-uint16_t archive_favorites_count(void* context) {
- furi_assert(context);
-
- Storage* fs_api = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(fs_api);
-
- string_t buffer;
- string_init(buffer);
-
- bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
- uint16_t lines = 0;
-
- if(result) {
- while(1) {
- if(!archive_favorites_read_line(file, buffer)) {
- break;
- }
- if(!string_size(buffer)) {
- continue; // Skip empty lines
- }
- ++lines;
- }
- }
-
- storage_file_close(file);
-
- string_clear(buffer);
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
-
- return lines;
-}
-
-static bool archive_favourites_rescan() {
- string_t buffer;
- string_init(buffer);
- Storage* fs_api = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(fs_api);
- File* fav_item_file = storage_file_alloc(fs_api);
-
- bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
- if(result) {
- while(1) {
- if(!archive_favorites_read_line(file, buffer)) {
- break;
- }
- if(!string_size(buffer)) {
- continue;
- }
-
- if(string_search(buffer, "/app:") == 0) {
- if(archive_app_is_available(NULL, string_get_cstr(buffer))) {
- archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer));
- }
- } else {
- bool file_exists = storage_file_open(
- fav_item_file, string_get_cstr(buffer), FSAM_READ, FSOM_OPEN_EXISTING);
- if(file_exists) {
- storage_file_close(fav_item_file);
- archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer));
- } else {
- storage_file_close(fav_item_file);
- }
- }
- }
- }
-
- string_clear(buffer);
-
- storage_file_close(file);
- storage_common_remove(fs_api, ARCHIVE_FAV_PATH);
- storage_common_rename(fs_api, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH);
- storage_common_remove(fs_api, ARCHIVE_FAV_TEMP_PATH);
-
- storage_file_free(file);
- storage_file_free(fav_item_file);
- furi_record_close(RECORD_STORAGE);
-
- return result;
-}
-
-bool archive_favorites_read(void* context) {
- furi_assert(context);
-
- ArchiveBrowserView* browser = context;
- Storage* fs_api = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(fs_api);
- File* fav_item_file = storage_file_alloc(fs_api);
-
- string_t buffer;
- FileInfo file_info;
- string_init(buffer);
-
- bool need_refresh = false;
- uint16_t file_count = 0;
-
- archive_file_array_rm_all(browser);
-
- bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
-
- if(result) {
- while(1) {
- if(!archive_favorites_read_line(file, buffer)) {
- break;
- }
- if(!string_size(buffer)) {
- continue;
- }
-
- if(string_search(buffer, "/app:") == 0) {
- if(archive_app_is_available(browser, string_get_cstr(buffer))) {
- archive_add_app_item(browser, string_get_cstr(buffer));
- file_count++;
- } else {
- need_refresh = true;
- }
- } else {
- bool file_exists = storage_file_open(
- fav_item_file, string_get_cstr(buffer), FSAM_READ, FSOM_OPEN_EXISTING);
- if(file_exists) {
- storage_common_stat(fs_api, string_get_cstr(buffer), &file_info);
- storage_file_close(fav_item_file);
- archive_add_file_item(
- browser, (file_info.flags & FSF_DIRECTORY), string_get_cstr(buffer));
- file_count++;
- } else {
- storage_file_close(fav_item_file);
- need_refresh = true;
- }
- }
-
- string_reset(buffer);
- }
- }
- storage_file_close(file);
- string_clear(buffer);
- storage_file_free(file);
- storage_file_free(fav_item_file);
- furi_record_close(RECORD_STORAGE);
-
- archive_set_item_count(browser, file_count);
-
- if(need_refresh) {
- archive_favourites_rescan();
- }
-
- return result;
-}
-
-bool archive_favorites_delete(const char* format, ...) {
- string_t buffer;
- string_t filename;
- va_list args;
- va_start(args, format);
- string_init_vprintf(filename, format, args);
- va_end(args);
-
- string_init(buffer);
- Storage* fs_api = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(fs_api);
-
- bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
-
- if(result) {
- while(1) {
- if(!archive_favorites_read_line(file, buffer)) {
- break;
- }
- if(!string_size(buffer)) {
- continue;
- }
-
- if(string_search(buffer, filename)) {
- archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer));
- }
- }
- }
-
- string_clear(buffer);
- string_clear(filename);
-
- storage_file_close(file);
- storage_common_remove(fs_api, ARCHIVE_FAV_PATH);
- storage_common_rename(fs_api, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH);
- storage_common_remove(fs_api, ARCHIVE_FAV_TEMP_PATH);
-
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
-
- return result;
-}
-
-bool archive_is_favorite(const char* format, ...) {
- string_t buffer;
- string_t filename;
- va_list args;
- va_start(args, format);
- string_init_vprintf(filename, format, args);
- va_end(args);
-
- string_init(buffer);
- Storage* fs_api = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(fs_api);
-
- bool found = false;
- bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
-
- if(result) {
- while(1) {
- if(!archive_favorites_read_line(file, buffer)) {
- break;
- }
- if(!string_size(buffer)) {
- continue;
- }
- if(!string_search(buffer, filename)) {
- found = true;
- break;
- }
- }
- }
-
- storage_file_close(file);
- string_clear(buffer);
- string_clear(filename);
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
-
- return found;
-}
-
-bool archive_favorites_rename(const char* src, const char* dst) {
- furi_assert(src);
- furi_assert(dst);
-
- Storage* fs_api = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(fs_api);
-
- string_t path;
- string_t buffer;
-
- string_init(buffer);
- string_init(path);
-
- string_printf(path, "%s", src);
- bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
-
- if(result) {
- while(1) {
- if(!archive_favorites_read_line(file, buffer)) {
- break;
- }
- if(!string_size(buffer)) {
- continue;
- }
-
- archive_file_append(
- ARCHIVE_FAV_TEMP_PATH,
- "%s\n",
- string_search(buffer, path) ? string_get_cstr(buffer) : dst);
- }
- }
-
- string_clear(buffer);
- string_clear(path);
-
- storage_file_close(file);
- storage_common_remove(fs_api, ARCHIVE_FAV_PATH);
- storage_common_rename(fs_api, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH);
- storage_common_remove(fs_api, ARCHIVE_FAV_TEMP_PATH);
-
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
-
- return result;
-}
-
-void archive_add_to_favorites(const char* file_path) {
- furi_assert(file_path);
-
- archive_file_append(ARCHIVE_FAV_PATH, "%s\n", file_path);
-}
-
-void archive_favorites_save(void* context) {
- furi_assert(context);
-
- ArchiveBrowserView* browser = context;
- Storage* fs_api = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(fs_api);
-
- for(size_t i = 0; i < archive_file_get_array_size(browser); i++) {
- ArchiveFile_t* item = archive_get_file_at(browser, i);
- archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(item->path));
- }
-
- storage_common_remove(fs_api, ARCHIVE_FAV_PATH);
- storage_common_rename(fs_api, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH);
- storage_common_remove(fs_api, ARCHIVE_FAV_TEMP_PATH);
-
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
-}
diff --git a/applications/archive/helpers/archive_favorites.h b/applications/archive/helpers/archive_favorites.h
deleted file mode 100644
index 29eedcdb6f3..00000000000
--- a/applications/archive/helpers/archive_favorites.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma once
-
-#include
-
-#define ARCHIVE_FAV_PATH ANY_PATH("favorites.txt")
-#define ARCHIVE_FAV_TEMP_PATH ANY_PATH("favorites.tmp")
-
-uint16_t archive_favorites_count(void* context);
-bool archive_favorites_read(void* context);
-bool archive_favorites_delete(const char* format, ...);
-bool archive_is_favorite(const char* format, ...);
-bool archive_favorites_rename(const char* src, const char* dst);
-void archive_add_to_favorites(const char* file_path);
-void archive_favorites_save(void* context);
diff --git a/applications/archive/helpers/archive_files.c b/applications/archive/helpers/archive_files.c
deleted file mode 100644
index 9f8b4ee1bac..00000000000
--- a/applications/archive/helpers/archive_files.c
+++ /dev/null
@@ -1,111 +0,0 @@
-#include "archive_files.h"
-#include "archive_apps.h"
-#include "archive_browser.h"
-
-#define TAG "Archive"
-
-#define ASSETS_DIR "assets"
-
-void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder, bool is_app) {
- furi_assert(file);
-
- file->is_app = is_app;
- if(is_app) {
- file->type = archive_get_app_filetype(archive_get_app_type(path));
- } else {
- for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
- if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue;
- if(string_search_str(file->path, known_ext[i], 0) != STRING_FAILURE) {
- if(i == ArchiveFileTypeBadUsb) {
- if(string_search_str(file->path, archive_get_default_path(ArchiveTabBadUsb)) ==
- 0) {
- file->type = i;
- return; // *.txt file is a BadUSB script only if it is in BadUSB folder
- }
- } else {
- file->type = i;
- return;
- }
- }
- }
-
- if(is_folder) {
- file->type = ArchiveFileTypeFolder;
- } else {
- file->type = ArchiveFileTypeUnknown;
- }
- }
-}
-
-bool archive_get_items(void* context, const char* path) {
- furi_assert(context);
-
- bool res = false;
- ArchiveBrowserView* browser = context;
-
- if(archive_get_tab(browser) == ArchiveTabFavorites) {
- res = archive_favorites_read(browser);
- } else if(strncmp(path, "/app:", 5) == 0) {
- res = archive_app_read_dir(browser, path);
- }
- return res;
-}
-
-void archive_file_append(const char* path, const char* format, ...) {
- furi_assert(path);
-
- string_t string;
- va_list args;
- va_start(args, format);
- string_init_vprintf(string, format, args);
- va_end(args);
-
- Storage* fs_api = furi_record_open(RECORD_STORAGE);
- File* file = storage_file_alloc(fs_api);
-
- bool res = storage_file_open(file, path, FSAM_WRITE, FSOM_OPEN_APPEND);
-
- if(res) {
- storage_file_write(file, string_get_cstr(string), string_size(string));
- }
-
- storage_file_close(file);
- storage_file_free(file);
- furi_record_close(RECORD_STORAGE);
-}
-
-void archive_delete_file(void* context, const char* format, ...) {
- furi_assert(context);
-
- string_t filename;
- va_list args;
- va_start(args, format);
- string_init_vprintf(filename, format, args);
- va_end(args);
-
- ArchiveBrowserView* browser = context;
- Storage* fs_api = furi_record_open(RECORD_STORAGE);
-
- FileInfo fileinfo;
- storage_common_stat(fs_api, string_get_cstr(filename), &fileinfo);
-
- bool res = false;
-
- if(fileinfo.flags & FSF_DIRECTORY) {
- res = storage_simply_remove_recursive(fs_api, string_get_cstr(filename));
- } else {
- res = (storage_common_remove(fs_api, string_get_cstr(filename)) == FSE_OK);
- }
-
- furi_record_close(RECORD_STORAGE);
-
- if(archive_is_favorite("%s", string_get_cstr(filename))) {
- archive_favorites_delete("%s", string_get_cstr(filename));
- }
-
- if(res) {
- archive_file_array_rm_selected(browser);
- }
-
- string_clear(filename);
-}
diff --git a/applications/archive/helpers/archive_files.h b/applications/archive/helpers/archive_files.h
deleted file mode 100644
index 84b7e24a62f..00000000000
--- a/applications/archive/helpers/archive_files.h
+++ /dev/null
@@ -1,64 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-
-typedef enum {
- ArchiveFileTypeIButton,
- ArchiveFileTypeNFC,
- ArchiveFileTypeSubGhz,
- ArchiveFileTypeLFRFID,
- ArchiveFileTypeInfrared,
- ArchiveFileTypeBadUsb,
- ArchiveFileTypeU2f,
- ArchiveFileTypeUpdateManifest,
- ArchiveFileTypeFolder,
- ArchiveFileTypeUnknown,
- ArchiveFileTypeLoading,
-} ArchiveFileTypeEnum;
-
-typedef struct {
- string_t path;
- ArchiveFileTypeEnum type;
- bool fav;
- bool is_app;
-} ArchiveFile_t;
-
-static void ArchiveFile_t_init(ArchiveFile_t* obj) {
- obj->type = ArchiveFileTypeUnknown;
- obj->is_app = false;
- obj->fav = false;
- string_init(obj->path);
-}
-
-static void ArchiveFile_t_init_set(ArchiveFile_t* obj, const ArchiveFile_t* src) {
- obj->type = src->type;
- obj->is_app = src->is_app;
- obj->fav = src->fav;
- string_init_set(obj->path, src->path);
-}
-
-static void ArchiveFile_t_set(ArchiveFile_t* obj, const ArchiveFile_t* src) {
- obj->type = src->type;
- obj->is_app = src->is_app;
- obj->fav = src->fav;
- string_set(obj->path, src->path);
-}
-
-static void ArchiveFile_t_clear(ArchiveFile_t* obj) {
- string_clear(obj->path);
-}
-
-ARRAY_DEF(
- files_array,
- ArchiveFile_t,
- (INIT(API_2(ArchiveFile_t_init)),
- SET(API_6(ArchiveFile_t_set)),
- INIT_SET(API_6(ArchiveFile_t_init_set)),
- CLEAR(API_2(ArchiveFile_t_clear))))
-
-void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder, bool is_app);
-bool archive_get_items(void* context, const char* path);
-void archive_file_append(const char* path, const char* format, ...);
-void archive_delete_file(void* context, const char* format, ...);
diff --git a/applications/archive/views/archive_browser_view.c b/applications/archive/views/archive_browser_view.c
deleted file mode 100644
index 810d5c8f787..00000000000
--- a/applications/archive/views/archive_browser_view.c
+++ /dev/null
@@ -1,396 +0,0 @@
-#include "assets_icons.h"
-#include "toolbox/path.h"
-#include
-#include "../archive_i.h"
-#include "archive_browser_view.h"
-#include "../helpers/archive_browser.h"
-
-static const char* ArchiveTabNames[] = {
- [ArchiveTabFavorites] = "Favorites",
- [ArchiveTabIButton] = "iButton",
- [ArchiveTabNFC] = "NFC",
- [ArchiveTabSubGhz] = "Sub-GHz",
- [ArchiveTabLFRFID] = "RFID LF",
- [ArchiveTabInfrared] = "Infrared",
- [ArchiveTabBadUsb] = "Bad USB",
- [ArchiveTabU2f] = "U2F",
- [ArchiveTabBrowser] = "Browser",
-};
-
-static const Icon* ArchiveItemIcons[] = {
- [ArchiveFileTypeIButton] = &I_ibutt_10px,
- [ArchiveFileTypeNFC] = &I_Nfc_10px,
- [ArchiveFileTypeSubGhz] = &I_sub1_10px,
- [ArchiveFileTypeLFRFID] = &I_125_10px,
- [ArchiveFileTypeInfrared] = &I_ir_10px,
- [ArchiveFileTypeBadUsb] = &I_badusb_10px,
- [ArchiveFileTypeU2f] = &I_u2f_10px,
- [ArchiveFileTypeUpdateManifest] = &I_update_10px,
- [ArchiveFileTypeFolder] = &I_dir_10px,
- [ArchiveFileTypeUnknown] = &I_unknown_10px,
- [ArchiveFileTypeLoading] = &I_loading_10px,
-};
-
-void archive_browser_set_callback(
- ArchiveBrowserView* browser,
- ArchiveBrowserViewCallback callback,
- void* context) {
- furi_assert(browser);
- furi_assert(callback);
- browser->callback = callback;
- browser->context = context;
-}
-
-static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) {
- canvas_set_color(canvas, ColorWhite);
- canvas_draw_box(canvas, 71, 17, 57, 46);
- canvas_set_color(canvas, ColorBlack);
- elements_slightly_rounded_frame(canvas, 70, 16, 58, 48);
-
- string_t menu[MENU_ITEMS];
-
- string_init_set_str(menu[0], "Run in app");
- string_init_set_str(menu[1], "Pin");
- string_init_set_str(menu[2], "Rename");
- string_init_set_str(menu[3], "Delete");
-
- ArchiveFile_t* selected = files_array_get(model->files, model->item_idx - model->array_offset);
-
- if((selected->fav) || (model->tab_idx == ArchiveTabFavorites)) {
- string_set_str(menu[1], "Unpin");
- }
-
- if(!archive_is_known_app(selected->type)) {
- string_set_str(menu[0], "---");
- string_set_str(menu[1], "---");
- string_set_str(menu[2], "---");
- } else {
- if(model->tab_idx == ArchiveTabFavorites) {
- string_set_str(menu[2], "Move");
- string_set_str(menu[3], "---");
- } else if(selected->is_app) {
- string_set_str(menu[2], "---");
- }
- }
-
- for(size_t i = 0; i < MENU_ITEMS; i++) {
- canvas_draw_str(canvas, 82, 27 + i * 11, string_get_cstr(menu[i]));
- string_clear(menu[i]);
- }
-
- canvas_draw_icon(canvas, 74, 20 + model->menu_idx * 11, &I_ButtonRight_4x7);
-}
-
-static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar, bool moving) {
- uint8_t x_offset = moving ? MOVE_OFFSET : 0;
-
- canvas_set_color(canvas, ColorBlack);
- canvas_draw_box(
- canvas,
- 0 + x_offset,
- 15 + idx * FRAME_HEIGHT,
- (scrollbar ? 122 : 127) - x_offset,
- FRAME_HEIGHT);
-
- canvas_set_color(canvas, ColorWhite);
- canvas_draw_dot(canvas, 0 + x_offset, 15 + idx * FRAME_HEIGHT);
- canvas_draw_dot(canvas, 1 + x_offset, 15 + idx * FRAME_HEIGHT);
- canvas_draw_dot(canvas, 0 + x_offset, (15 + idx * FRAME_HEIGHT) + 1);
-
- canvas_draw_dot(canvas, 0 + x_offset, (15 + idx * FRAME_HEIGHT) + 11);
- canvas_draw_dot(canvas, scrollbar ? 121 : 126, 15 + idx * FRAME_HEIGHT);
- canvas_draw_dot(canvas, scrollbar ? 121 : 126, (15 + idx * FRAME_HEIGHT) + 11);
-}
-
-static void archive_draw_loading(Canvas* canvas, ArchiveBrowserViewModel* model) {
- furi_assert(model);
-
- uint8_t x = 128 / 2 - 24 / 2;
- uint8_t y = 64 / 2 - 24 / 2;
-
- canvas_draw_icon(canvas, x, y, &A_Loading_24);
-}
-
-static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) {
- furi_assert(model);
-
- size_t array_size = files_array_size(model->files);
- bool scrollbar = model->item_cnt > 4;
-
- for(uint32_t i = 0; i < MIN(model->item_cnt, MENU_ITEMS); ++i) {
- string_t str_buf;
- string_init(str_buf);
- int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u);
- uint8_t x_offset = (model->move_fav && model->item_idx == idx) ? MOVE_OFFSET : 0;
-
- ArchiveFileTypeEnum file_type = ArchiveFileTypeLoading;
-
- if(archive_is_item_in_array(model, idx)) {
- ArchiveFile_t* file = files_array_get(
- model->files, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0));
- path_extract_filename(file->path, str_buf, archive_is_known_app(file->type));
- file_type = file->type;
- } else {
- string_set_str(str_buf, "---");
- }
-
- elements_string_fit_width(
- canvas, str_buf, (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset);
-
- if(model->item_idx == idx) {
- archive_draw_frame(canvas, i, scrollbar, model->move_fav);
- } else {
- canvas_set_color(canvas, ColorBlack);
- }
-
- canvas_draw_icon(canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]);
- canvas_draw_str(canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buf));
-
- string_clear(str_buf);
- }
-
- if(scrollbar) {
- elements_scrollbar_pos(canvas, 126, 15, 49, model->item_idx, model->item_cnt);
- }
-
- if(model->menu) {
- render_item_menu(canvas, model);
- }
-}
-
-static void archive_render_status_bar(Canvas* canvas, ArchiveBrowserViewModel* model) {
- furi_assert(model);
-
- const char* tab_name = ArchiveTabNames[model->tab_idx];
-
- canvas_draw_icon(canvas, 0, 0, &I_Background_128x11);
-
- canvas_set_color(canvas, ColorWhite);
- canvas_draw_box(canvas, 0, 0, 50, 13);
- canvas_draw_box(canvas, 107, 0, 20, 13);
-
- canvas_set_color(canvas, ColorBlack);
- canvas_draw_rframe(canvas, 0, 0, 51, 13, 1); // frame
- canvas_draw_line(canvas, 49, 1, 49, 11); // shadow right
- canvas_draw_line(canvas, 1, 11, 49, 11); // shadow bottom
- canvas_draw_str_aligned(canvas, 25, 9, AlignCenter, AlignBottom, tab_name);
-
- canvas_draw_rframe(canvas, 107, 0, 21, 13, 1);
- canvas_draw_line(canvas, 126, 1, 126, 11);
- canvas_draw_line(canvas, 108, 11, 126, 11);
-
- if(model->move_fav) {
- canvas_draw_icon(canvas, 110, 4, &I_ButtonUp_7x4);
- canvas_draw_icon(canvas, 117, 4, &I_ButtonDown_7x4);
- } else {
- canvas_draw_icon(canvas, 111, 2, &I_ButtonLeft_4x7);
- canvas_draw_icon(canvas, 119, 2, &I_ButtonRight_4x7);
- }
-
- canvas_set_color(canvas, ColorWhite);
- canvas_draw_dot(canvas, 50, 0);
- canvas_draw_dot(canvas, 127, 0);
-
- canvas_set_color(canvas, ColorBlack);
-}
-
-void archive_view_render(Canvas* canvas, void* mdl) {
- ArchiveBrowserViewModel* model = mdl;
-
- archive_render_status_bar(canvas, mdl);
-
- if(model->folder_loading) {
- archive_draw_loading(canvas, model);
- } else if(model->item_cnt > 0) {
- draw_list(canvas, model);
- } else {
- canvas_draw_str_aligned(
- canvas, GUI_DISPLAY_WIDTH / 2, 40, AlignCenter, AlignCenter, "Empty");
- }
-}
-
-View* archive_browser_get_view(ArchiveBrowserView* browser) {
- furi_assert(browser);
- return browser->view;
-}
-
-static bool is_file_list_load_required(ArchiveBrowserViewModel* model) {
- size_t array_size = files_array_size(model->files);
-
- if((model->list_loading) || (array_size >= model->item_cnt)) {
- return false;
- }
-
- if((model->array_offset > 0) &&
- (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) {
- return true;
- }
-
- if(((model->array_offset + array_size) < model->item_cnt) &&
- (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) {
- return true;
- }
-
- return false;
-}
-
-bool archive_view_input(InputEvent* event, void* context) {
- furi_assert(event);
- furi_assert(context);
-
- ArchiveBrowserView* browser = context;
-
- bool in_menu;
- bool move_fav_mode;
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- in_menu = model->menu;
- move_fav_mode = model->move_fav;
- return false;
- });
-
- if(in_menu) {
- if(event->type == InputTypeShort) {
- if(event->key == InputKeyUp || event->key == InputKeyDown) {
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- if(event->key == InputKeyUp) {
- model->menu_idx = ((model->menu_idx - 1) + MENU_ITEMS) % MENU_ITEMS;
- } else if(event->key == InputKeyDown) {
- model->menu_idx = (model->menu_idx + 1) % MENU_ITEMS;
- }
- return true;
- });
- }
-
- if(event->key == InputKeyOk) {
- uint8_t idx;
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- idx = model->menu_idx;
- return false;
- });
- browser->callback(file_menu_actions[idx], browser->context);
- } else if(event->key == InputKeyBack) {
- browser->callback(ArchiveBrowserEventFileMenuClose, browser->context);
- }
- }
-
- } else {
- if(event->type == InputTypeShort) {
- if(event->key == InputKeyLeft || event->key == InputKeyRight) {
- if(move_fav_mode) return false;
- archive_switch_tab(browser, event->key);
- } else if(event->key == InputKeyBack) {
- if(move_fav_mode) {
- browser->callback(ArchiveBrowserEventExitFavMove, browser->context);
- } else {
- browser->callback(ArchiveBrowserEventExit, browser->context);
- }
- }
- }
-
- if((event->key == InputKeyUp || event->key == InputKeyDown) &&
- (event->type == InputTypeShort || event->type == InputTypeRepeat)) {
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- if(event->key == InputKeyUp) {
- model->item_idx =
- ((model->item_idx - 1) + model->item_cnt) % model->item_cnt;
- if(is_file_list_load_required(model)) {
- model->list_loading = true;
- browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context);
- }
- if(move_fav_mode) {
- browser->callback(ArchiveBrowserEventFavMoveUp, browser->context);
- }
- } else if(event->key == InputKeyDown) {
- model->item_idx = (model->item_idx + 1) % model->item_cnt;
- if(is_file_list_load_required(model)) {
- model->list_loading = true;
- browser->callback(ArchiveBrowserEventLoadNextItems, browser->context);
- }
- if(move_fav_mode) {
- browser->callback(ArchiveBrowserEventFavMoveDown, browser->context);
- }
- }
-
- return true;
- });
- archive_update_offset(browser);
- }
-
- if(event->key == InputKeyOk) {
- ArchiveFile_t* selected = archive_get_current_file(browser);
-
- if(selected) {
- bool favorites = archive_get_tab(browser) == ArchiveTabFavorites;
- bool folder = selected->type == ArchiveFileTypeFolder;
-
- if(event->type == InputTypeShort) {
- if(favorites) {
- if(move_fav_mode) {
- browser->callback(ArchiveBrowserEventSaveFavMove, browser->context);
- } else {
- browser->callback(ArchiveBrowserEventFileMenuRun, browser->context);
- }
- } else if(folder) {
- browser->callback(ArchiveBrowserEventEnterDir, browser->context);
- } else {
- browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context);
- }
- } else if(event->type == InputTypeLong) {
- if(move_fav_mode) {
- browser->callback(ArchiveBrowserEventSaveFavMove, browser->context);
- } else if(folder || favorites) {
- browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context);
- }
- }
- }
- }
- }
-
- return true;
-}
-
-ArchiveBrowserView* browser_alloc() {
- ArchiveBrowserView* browser = malloc(sizeof(ArchiveBrowserView));
- browser->view = view_alloc();
- view_allocate_model(browser->view, ViewModelTypeLocking, sizeof(ArchiveBrowserViewModel));
- view_set_context(browser->view, browser);
- view_set_draw_callback(browser->view, archive_view_render);
- view_set_input_callback(browser->view, archive_view_input);
-
- string_init_set_str(browser->path, archive_get_default_path(TAB_DEFAULT));
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- files_array_init(model->files);
- model->tab_idx = TAB_DEFAULT;
- return true;
- });
-
- browser->worker = file_browser_worker_alloc(browser->path, "*", false);
- archive_file_browser_set_callbacks(browser);
-
- file_browser_worker_set_callback_context(browser->worker, browser);
-
- return browser;
-}
-
-void browser_free(ArchiveBrowserView* browser) {
- furi_assert(browser);
-
- file_browser_worker_free(browser->worker);
-
- with_view_model(
- browser->view, (ArchiveBrowserViewModel * model) {
- files_array_clear(model->files);
- return false;
- });
-
- string_clear(browser->path);
-
- view_free(browser->view);
- free(browser);
-}
diff --git a/applications/bad_usb/application.fam b/applications/bad_usb/application.fam
deleted file mode 100644
index 4da34f0de1b..00000000000
--- a/applications/bad_usb/application.fam
+++ /dev/null
@@ -1,14 +0,0 @@
-App(
- appid="bad_usb",
- name="Bad USB",
- apptype=FlipperAppType.APP,
- entry_point="bad_usb_app",
- cdefines=["APP_BAD_USB"],
- requires=[
- "gui",
- "dialogs",
- ],
- stack_size=2 * 1024,
- icon="A_BadUsb_14",
- order=70,
-)
diff --git a/applications/bad_usb/bad_usb_app.c b/applications/bad_usb/bad_usb_app.c
deleted file mode 100644
index 09d7d346834..00000000000
--- a/applications/bad_usb/bad_usb_app.c
+++ /dev/null
@@ -1,110 +0,0 @@
-#include "bad_usb_app_i.h"
-#include "m-string.h"
-#include
-#include
-#include
-#include
-
-static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) {
- furi_assert(context);
- BadUsbApp* app = context;
- return scene_manager_handle_custom_event(app->scene_manager, event);
-}
-
-static bool bad_usb_app_back_event_callback(void* context) {
- furi_assert(context);
- BadUsbApp* app = context;
- return scene_manager_handle_back_event(app->scene_manager);
-}
-
-static void bad_usb_app_tick_event_callback(void* context) {
- furi_assert(context);
- BadUsbApp* app = context;
- scene_manager_handle_tick_event(app->scene_manager);
-}
-
-BadUsbApp* bad_usb_app_alloc(char* arg) {
- BadUsbApp* app = malloc(sizeof(BadUsbApp));
-
- string_init(app->file_path);
-
- if(arg && strlen(arg)) {
- string_set_str(app->file_path, arg);
- }
-
- app->gui = furi_record_open(RECORD_GUI);
- app->notifications = furi_record_open(RECORD_NOTIFICATION);
- app->dialogs = furi_record_open(RECORD_DIALOGS);
-
- app->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(app->view_dispatcher);
-
- app->scene_manager = scene_manager_alloc(&bad_usb_scene_handlers, app);
-
- view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
- view_dispatcher_set_tick_event_callback(
- app->view_dispatcher, bad_usb_app_tick_event_callback, 500);
- view_dispatcher_set_custom_event_callback(
- app->view_dispatcher, bad_usb_app_custom_event_callback);
- view_dispatcher_set_navigation_event_callback(
- app->view_dispatcher, bad_usb_app_back_event_callback);
-
- // Custom Widget
- app->widget = widget_alloc();
- view_dispatcher_add_view(
- app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget));
-
- app->bad_usb_view = bad_usb_alloc();
- view_dispatcher_add_view(
- app->view_dispatcher, BadUsbAppViewWork, bad_usb_get_view(app->bad_usb_view));
-
- view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
-
- if(furi_hal_usb_is_locked()) {
- app->error = BadUsbAppErrorCloseRpc;
- scene_manager_next_scene(app->scene_manager, BadUsbSceneError);
- } else {
- if(!string_empty_p(app->file_path)) {
- scene_manager_next_scene(app->scene_manager, BadUsbSceneWork);
- } else {
- string_set_str(app->file_path, BAD_USB_APP_PATH_FOLDER);
- scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect);
- }
- }
-
- return app;
-}
-
-void bad_usb_app_free(BadUsbApp* app) {
- furi_assert(app);
-
- // Views
- view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork);
- bad_usb_free(app->bad_usb_view);
-
- // Custom Widget
- view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError);
- widget_free(app->widget);
-
- // View dispatcher
- view_dispatcher_free(app->view_dispatcher);
- scene_manager_free(app->scene_manager);
-
- // Close records
- furi_record_close(RECORD_GUI);
- furi_record_close(RECORD_NOTIFICATION);
- furi_record_close(RECORD_DIALOGS);
-
- string_clear(app->file_path);
-
- free(app);
-}
-
-int32_t bad_usb_app(void* p) {
- BadUsbApp* bad_usb_app = bad_usb_app_alloc((char*)p);
-
- view_dispatcher_run(bad_usb_app->view_dispatcher);
-
- bad_usb_app_free(bad_usb_app);
- return 0;
-}
diff --git a/applications/bad_usb/bad_usb_app_i.h b/applications/bad_usb/bad_usb_app_i.h
deleted file mode 100644
index 6378bddfb21..00000000000
--- a/applications/bad_usb/bad_usb_app_i.h
+++ /dev/null
@@ -1,42 +0,0 @@
-#pragma once
-
-#include "bad_usb_app.h"
-#include "scenes/bad_usb_scene.h"
-#include "bad_usb_script.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include "views/bad_usb_view.h"
-
-#define BAD_USB_APP_PATH_FOLDER ANY_PATH("badusb")
-#define BAD_USB_APP_EXTENSION ".txt"
-
-typedef enum {
- BadUsbAppErrorNoFiles,
- BadUsbAppErrorCloseRpc,
-} BadUsbAppError;
-
-struct BadUsbApp {
- Gui* gui;
- ViewDispatcher* view_dispatcher;
- SceneManager* scene_manager;
- NotificationApp* notifications;
- DialogsApp* dialogs;
- Widget* widget;
-
- BadUsbAppError error;
- string_t file_path;
- BadUsb* bad_usb_view;
- BadUsbScript* bad_usb_script;
-};
-
-typedef enum {
- BadUsbAppViewError,
- BadUsbAppViewWork,
-} BadUsbAppView;
diff --git a/applications/bad_usb/bad_usb_script.c b/applications/bad_usb/bad_usb_script.c
deleted file mode 100644
index 9d9d3e39719..00000000000
--- a/applications/bad_usb/bad_usb_script.c
+++ /dev/null
@@ -1,624 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include "bad_usb_script.h"
-#include
-
-#define TAG "BadUSB"
-#define WORKER_TAG TAG "Worker"
-#define FILE_BUFFER_LEN 16
-
-#define SCRIPT_STATE_ERROR (-1)
-#define SCRIPT_STATE_END (-2)
-#define SCRIPT_STATE_NEXT_LINE (-3)
-
-typedef enum {
- WorkerEvtToggle = (1 << 0),
- WorkerEvtEnd = (1 << 1),
- WorkerEvtConnect = (1 << 2),
- WorkerEvtDisconnect = (1 << 3),
-} WorkerEvtFlags;
-
-struct BadUsbScript {
- FuriHalUsbHidConfig hid_cfg;
- BadUsbState st;
- string_t file_path;
- uint32_t defdelay;
- FuriThread* thread;
- uint8_t file_buf[FILE_BUFFER_LEN + 1];
- uint8_t buf_start;
- uint8_t buf_len;
- bool file_end;
- string_t line;
-
- string_t line_prev;
- uint32_t repeat_cnt;
-};
-
-typedef struct {
- char* name;
- uint16_t keycode;
-} DuckyKey;
-
-static const DuckyKey ducky_keys[] = {
- {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT},
- {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
- {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
- {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
- {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
-
- {"CTRL", KEY_MOD_LEFT_CTRL},
- {"CONTROL", KEY_MOD_LEFT_CTRL},
- {"SHIFT", KEY_MOD_LEFT_SHIFT},
- {"ALT", KEY_MOD_LEFT_ALT},
- {"GUI", KEY_MOD_LEFT_GUI},
- {"WINDOWS", KEY_MOD_LEFT_GUI},
-
- {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
- {"DOWN", HID_KEYBOARD_DOWN_ARROW},
- {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
- {"LEFT", HID_KEYBOARD_LEFT_ARROW},
- {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW},
- {"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
- {"UPARROW", HID_KEYBOARD_UP_ARROW},
- {"UP", HID_KEYBOARD_UP_ARROW},
-
- {"ENTER", HID_KEYBOARD_RETURN},
- {"BREAK", HID_KEYBOARD_PAUSE},
- {"PAUSE", HID_KEYBOARD_PAUSE},
- {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
- {"DELETE", HID_KEYBOARD_DELETE},
- {"BACKSPACE", HID_KEYPAD_BACKSPACE},
- {"END", HID_KEYBOARD_END},
- {"ESC", HID_KEYBOARD_ESCAPE},
- {"ESCAPE", HID_KEYBOARD_ESCAPE},
- {"HOME", HID_KEYBOARD_HOME},
- {"INSERT", HID_KEYBOARD_INSERT},
- {"NUMLOCK", HID_KEYPAD_NUMLOCK},
- {"PAGEUP", HID_KEYBOARD_PAGE_UP},
- {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
- {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
- {"SCROLLOCK", HID_KEYBOARD_SCROLL_LOCK},
- {"SPACE", HID_KEYBOARD_SPACEBAR},
- {"TAB", HID_KEYBOARD_TAB},
- {"MENU", HID_KEYBOARD_APPLICATION},
- {"APP", HID_KEYBOARD_APPLICATION},
-
- {"F1", HID_KEYBOARD_F1},
- {"F2", HID_KEYBOARD_F2},
- {"F3", HID_KEYBOARD_F3},
- {"F4", HID_KEYBOARD_F4},
- {"F5", HID_KEYBOARD_F5},
- {"F6", HID_KEYBOARD_F6},
- {"F7", HID_KEYBOARD_F7},
- {"F8", HID_KEYBOARD_F8},
- {"F9", HID_KEYBOARD_F9},
- {"F10", HID_KEYBOARD_F10},
- {"F11", HID_KEYBOARD_F11},
- {"F12", HID_KEYBOARD_F12},
-};
-
-static const char ducky_cmd_comment[] = {"REM"};
-static const char ducky_cmd_id[] = {"ID"};
-static const char ducky_cmd_delay[] = {"DELAY "};
-static const char ducky_cmd_string[] = {"STRING "};
-static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "};
-static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "};
-static const char ducky_cmd_repeat[] = {"REPEAT "};
-
-static const char ducky_cmd_altchar[] = {"ALTCHAR "};
-static const char ducky_cmd_altstr_1[] = {"ALTSTRING "};
-static const char ducky_cmd_altstr_2[] = {"ALTCODE "};
-
-static const uint8_t numpad_keys[10] = {
- HID_KEYPAD_0,
- HID_KEYPAD_1,
- HID_KEYPAD_2,
- HID_KEYPAD_3,
- HID_KEYPAD_4,
- HID_KEYPAD_5,
- HID_KEYPAD_6,
- HID_KEYPAD_7,
- HID_KEYPAD_8,
- HID_KEYPAD_9,
-};
-
-static bool ducky_get_number(const char* param, uint32_t* val) {
- uint32_t value = 0;
- if(sscanf(param, "%lu", &value) == 1) {
- *val = value;
- return true;
- }
- return false;
-}
-
-static uint32_t ducky_get_command_len(const char* line) {
- uint32_t len = strlen(line);
- for(uint32_t i = 0; i < len; i++) {
- if(line[i] == ' ') return i;
- }
- return 0;
-}
-
-static bool ducky_is_line_end(const char chr) {
- return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'));
-}
-
-static void ducky_numlock_on() {
- if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
- furi_hal_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
- furi_hal_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
- }
-}
-
-static bool ducky_numpad_press(const char num) {
- if((num < '0') || (num > '9')) return false;
-
- uint16_t key = numpad_keys[num - '0'];
- furi_hal_hid_kb_press(key);
- furi_hal_hid_kb_release(key);
-
- return true;
-}
-
-static bool ducky_altchar(const char* charcode) {
- uint8_t i = 0;
- bool state = false;
-
- FURI_LOG_I(WORKER_TAG, "char %s", charcode);
-
- furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
-
- while(!ducky_is_line_end(charcode[i])) {
- state = ducky_numpad_press(charcode[i]);
- if(state == false) break;
- i++;
- }
-
- furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
- return state;
-}
-
-static bool ducky_altstring(const char* param) {
- uint32_t i = 0;
- bool state = false;
-
- while(param[i] != '\0') {
- if((param[i] < ' ') || (param[i] > '~')) {
- i++;
- continue; // Skip non-printable chars
- }
-
- char temp_str[4];
- snprintf(temp_str, 4, "%u", param[i]);
-
- state = ducky_altchar(temp_str);
- if(state == false) break;
- i++;
- }
- return state;
-}
-
-static bool ducky_string(const char* param) {
- uint32_t i = 0;
- while(param[i] != '\0') {
- uint16_t keycode = HID_ASCII_TO_KEY(param[i]);
- if(keycode != HID_KEYBOARD_NONE) {
- furi_hal_hid_kb_press(keycode);
- furi_hal_hid_kb_release(keycode);
- }
- i++;
- }
- return true;
-}
-
-static uint16_t ducky_get_keycode(const char* param, bool accept_chars) {
- for(uint8_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) {
- uint8_t key_cmd_len = strlen(ducky_keys[i].name);
- if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
- (ducky_is_line_end(param[key_cmd_len]))) {
- return ducky_keys[i].keycode;
- }
- }
- if((accept_chars) && (strlen(param) > 0)) {
- return (HID_ASCII_TO_KEY(param[0]) & 0xFF);
- }
- return 0;
-}
-
-static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
- uint32_t line_len = string_size(line);
- const char* line_tmp = string_get_cstr(line);
- bool state = false;
-
- for(uint32_t i = 0; i < line_len; i++) {
- if((line_tmp[i] != ' ') && (line_tmp[i] != '\t') && (line_tmp[i] != '\n')) {
- line_tmp = &line_tmp[i];
- break; // Skip spaces and tabs
- }
- if(i == line_len - 1) return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
- }
-
- FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
-
- // General commands
- if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
- // REM - comment line
- return (0);
- } else if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
- // ID - executed in ducky_script_preload
- return (0);
- } else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
- // DELAY
- line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
- uint32_t delay_val = 0;
- state = ducky_get_number(line_tmp, &delay_val);
- if((state) && (delay_val > 0)) {
- return (int32_t)delay_val;
- }
- return SCRIPT_STATE_ERROR;
- } else if(
- (strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) ||
- (strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) {
- // DEFAULT_DELAY
- line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
- state = ducky_get_number(line_tmp, &bad_usb->defdelay);
- return (state) ? (0) : SCRIPT_STATE_ERROR;
- } else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
- // STRING
- line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
- state = ducky_string(line_tmp);
- return (state) ? (0) : SCRIPT_STATE_ERROR;
- } else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) {
- // ALTCHAR
- line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
- ducky_numlock_on();
- state = ducky_altchar(line_tmp);
- return (state) ? (0) : SCRIPT_STATE_ERROR;
- } else if(
- (strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) ||
- (strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) {
- // ALTSTRING
- line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
- ducky_numlock_on();
- state = ducky_altstring(line_tmp);
- return (state) ? (0) : SCRIPT_STATE_ERROR;
- } else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) {
- // REPEAT
- line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
- state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt);
- return (state) ? (0) : SCRIPT_STATE_ERROR;
- } else {
- // Special keys + modifiers
- uint16_t key = ducky_get_keycode(line_tmp, false);
- if(key == HID_KEYBOARD_NONE) return SCRIPT_STATE_ERROR;
- if((key & 0xFF00) != 0) {
- // It's a modifier key
- line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
- key |= ducky_get_keycode(line_tmp, true);
- }
- furi_hal_hid_kb_press(key);
- furi_hal_hid_kb_release(key);
- return (0);
- }
- return SCRIPT_STATE_ERROR;
-}
-
-static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) {
- if(sscanf(line, "%lX:%lX", &bad_usb->hid_cfg.vid, &bad_usb->hid_cfg.pid) == 2) {
- bad_usb->hid_cfg.manuf[0] = '\0';
- bad_usb->hid_cfg.product[0] = '\0';
-
- uint8_t id_len = ducky_get_command_len(line);
- if(!ducky_is_line_end(line[id_len + 1])) {
- sscanf(
- &line[id_len + 1],
- "%31[^\r\n:]:%31[^\r\n]",
- bad_usb->hid_cfg.manuf,
- bad_usb->hid_cfg.product);
- }
- FURI_LOG_D(
- WORKER_TAG,
- "set id: %04X:%04X mfr:%s product:%s",
- bad_usb->hid_cfg.vid,
- bad_usb->hid_cfg.pid,
- bad_usb->hid_cfg.manuf,
- bad_usb->hid_cfg.product);
- return true;
- }
- return false;
-}
-
-static bool ducky_script_preload(BadUsbScript* bad_usb, File* script_file) {
- uint8_t ret = 0;
- uint32_t line_len = 0;
-
- string_reset(bad_usb->line);
-
- do {
- ret = storage_file_read(script_file, bad_usb->file_buf, FILE_BUFFER_LEN);
- for(uint16_t i = 0; i < ret; i++) {
- if(bad_usb->file_buf[i] == '\n' && line_len > 0) {
- bad_usb->st.line_nb++;
- line_len = 0;
- } else {
- if(bad_usb->st.line_nb == 0) { // Save first line
- string_push_back(bad_usb->line, bad_usb->file_buf[i]);
- }
- line_len++;
- }
- }
- if(storage_file_eof(script_file)) {
- if(line_len > 0) {
- bad_usb->st.line_nb++;
- break;
- }
- }
- } while(ret > 0);
-
- const char* line_tmp = string_get_cstr(bad_usb->line);
- bool id_set = false; // Looking for ID command at first line
- if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
- id_set = ducky_set_usb_id(bad_usb, &line_tmp[strlen(ducky_cmd_id) + 1]);
- }
-
- if(id_set) {
- furi_check(furi_hal_usb_set_config(&usb_hid, &bad_usb->hid_cfg));
- } else {
- furi_check(furi_hal_usb_set_config(&usb_hid, NULL));
- }
-
- storage_file_seek(script_file, 0, true);
- string_reset(bad_usb->line);
-
- return true;
-}
-
-static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_file) {
- int32_t delay_val = 0;
-
- if(bad_usb->repeat_cnt > 0) {
- bad_usb->repeat_cnt--;
- delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev);
- if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
- return 0;
- } else if(delay_val < 0) { // Script error
- bad_usb->st.error_line = bad_usb->st.line_cur - 1;
- FURI_LOG_E(WORKER_TAG, "Unknown command at line %lu", bad_usb->st.line_cur - 1);
- return SCRIPT_STATE_ERROR;
- } else {
- return (delay_val + bad_usb->defdelay);
- }
- }
-
- string_set(bad_usb->line_prev, bad_usb->line);
- string_reset(bad_usb->line);
-
- while(1) {
- if(bad_usb->buf_len == 0) {
- bad_usb->buf_len = storage_file_read(script_file, bad_usb->file_buf, FILE_BUFFER_LEN);
- if(storage_file_eof(script_file)) {
- if((bad_usb->buf_len < FILE_BUFFER_LEN) && (bad_usb->file_end == false)) {
- bad_usb->file_buf[bad_usb->buf_len] = '\n';
- bad_usb->buf_len++;
- bad_usb->file_end = true;
- }
- }
-
- bad_usb->buf_start = 0;
- if(bad_usb->buf_len == 0) return SCRIPT_STATE_END;
- }
- for(uint8_t i = bad_usb->buf_start; i < (bad_usb->buf_start + bad_usb->buf_len); i++) {
- if(bad_usb->file_buf[i] == '\n' && string_size(bad_usb->line) > 0) {
- bad_usb->st.line_cur++;
- bad_usb->buf_len = bad_usb->buf_len + bad_usb->buf_start - (i + 1);
- bad_usb->buf_start = i + 1;
- delay_val = ducky_parse_line(bad_usb, bad_usb->line);
- if(delay_val < 0) {
- bad_usb->st.error_line = bad_usb->st.line_cur;
- FURI_LOG_E(WORKER_TAG, "Unknown command at line %lu", bad_usb->st.line_cur);
- return SCRIPT_STATE_ERROR;
- } else {
- return (delay_val + bad_usb->defdelay);
- }
- } else {
- string_push_back(bad_usb->line, bad_usb->file_buf[i]);
- }
- }
- bad_usb->buf_len = 0;
- if(bad_usb->file_end) return SCRIPT_STATE_END;
- }
-
- return 0;
-}
-
-static void bad_usb_hid_state_callback(bool state, void* context) {
- furi_assert(context);
- BadUsbScript* bad_usb = context;
-
- if(state == true)
- furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtConnect);
- else
- furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtDisconnect);
-}
-
-static int32_t bad_usb_worker(void* context) {
- BadUsbScript* bad_usb = context;
-
- BadUsbWorkerState worker_state = BadUsbStateInit;
- int32_t delay_val = 0;
-
- FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
-
- FURI_LOG_I(WORKER_TAG, "Init");
- File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
- string_init(bad_usb->line);
- string_init(bad_usb->line_prev);
-
- furi_hal_hid_set_state_callback(bad_usb_hid_state_callback, bad_usb);
-
- while(1) {
- if(worker_state == BadUsbStateInit) { // State: initialization
- if(storage_file_open(
- script_file,
- string_get_cstr(bad_usb->file_path),
- FSAM_READ,
- FSOM_OPEN_EXISTING)) {
- if((ducky_script_preload(bad_usb, script_file)) && (bad_usb->st.line_nb > 0)) {
- if(furi_hal_hid_is_connected()) {
- worker_state = BadUsbStateIdle; // Ready to run
- } else {
- worker_state = BadUsbStateNotConnected; // USB not connected
- }
- } else {
- worker_state = BadUsbStateScriptError; // Script preload error
- }
- } else {
- FURI_LOG_E(WORKER_TAG, "File open error");
- worker_state = BadUsbStateFileError; // File open error
- }
- bad_usb->st.state = worker_state;
-
- } else if(worker_state == BadUsbStateNotConnected) { // State: USB not connected
- uint32_t flags = furi_thread_flags_wait(
- WorkerEvtEnd | WorkerEvtConnect, FuriFlagWaitAny, FuriWaitForever);
- furi_check((flags & FuriFlagError) == 0);
- if(flags & WorkerEvtEnd) {
- break;
- } else if(flags & WorkerEvtConnect) {
- worker_state = BadUsbStateIdle; // Ready to run
- }
- bad_usb->st.state = worker_state;
-
- } else if(worker_state == BadUsbStateIdle) { // State: ready to start
- uint32_t flags = furi_thread_flags_wait(
- WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect,
- FuriFlagWaitAny,
- FuriWaitForever);
- furi_check((flags & FuriFlagError) == 0);
- if(flags & WorkerEvtEnd) {
- break;
- } else if(flags & WorkerEvtToggle) { // Start executing script
- DOLPHIN_DEED(DolphinDeedBadUsbPlayScript);
- delay_val = 0;
- bad_usb->buf_len = 0;
- bad_usb->st.line_cur = 0;
- bad_usb->defdelay = 0;
- bad_usb->repeat_cnt = 0;
- bad_usb->file_end = false;
- storage_file_seek(script_file, 0, true);
- worker_state = BadUsbStateRunning;
- } else if(flags & WorkerEvtDisconnect) {
- worker_state = BadUsbStateNotConnected; // USB disconnected
- }
- bad_usb->st.state = worker_state;
-
- } else if(worker_state == BadUsbStateRunning) { // State: running
- uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
- uint32_t flags = furi_thread_flags_wait(
- WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriFlagWaitAny, delay_cur);
- delay_val -= delay_cur;
- if(!(flags & FuriFlagError)) {
- if(flags & WorkerEvtEnd) {
- break;
- } else if(flags & WorkerEvtToggle) {
- worker_state = BadUsbStateIdle; // Stop executing script
- furi_hal_hid_kb_release_all();
- } else if(flags & WorkerEvtDisconnect) {
- worker_state = BadUsbStateNotConnected; // USB disconnected
- furi_hal_hid_kb_release_all();
- }
- bad_usb->st.state = worker_state;
- continue;
- } else if((flags == FuriFlagErrorTimeout) || (flags == FuriFlagErrorResource)) {
- if(delay_val > 0) {
- bad_usb->st.delay_remain--;
- continue;
- }
- bad_usb->st.state = BadUsbStateRunning;
- delay_val = ducky_script_execute_next(bad_usb, script_file);
- if(delay_val == SCRIPT_STATE_ERROR) { // Script error
- delay_val = 0;
- worker_state = BadUsbStateScriptError;
- bad_usb->st.state = worker_state;
- } else if(delay_val == SCRIPT_STATE_END) { // End of script
- delay_val = 0;
- worker_state = BadUsbStateIdle;
- bad_usb->st.state = BadUsbStateDone;
- furi_hal_hid_kb_release_all();
- continue;
- } else if(delay_val > 1000) {
- bad_usb->st.state = BadUsbStateDelay; // Show long delays
- bad_usb->st.delay_remain = delay_val / 1000;
- }
- } else {
- furi_check((flags & FuriFlagError) == 0);
- }
-
- } else if(
- (worker_state == BadUsbStateFileError) ||
- (worker_state == BadUsbStateScriptError)) { // State: error
- uint32_t flags = furi_thread_flags_wait(
- WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command
- furi_check((flags & FuriFlagError) == 0);
- if(flags & WorkerEvtEnd) {
- break;
- }
- }
- }
-
- furi_hal_hid_set_state_callback(NULL, NULL);
-
- furi_hal_usb_set_config(usb_mode_prev, NULL);
-
- storage_file_close(script_file);
- storage_file_free(script_file);
- string_clear(bad_usb->line);
- string_clear(bad_usb->line_prev);
-
- FURI_LOG_I(WORKER_TAG, "End");
-
- return 0;
-}
-
-BadUsbScript* bad_usb_script_open(string_t file_path) {
- furi_assert(file_path);
-
- BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
- string_init(bad_usb->file_path);
- string_set(bad_usb->file_path, file_path);
-
- bad_usb->st.state = BadUsbStateInit;
-
- bad_usb->thread = furi_thread_alloc();
- furi_thread_set_name(bad_usb->thread, "BadUsbWorker");
- furi_thread_set_stack_size(bad_usb->thread, 2048);
- furi_thread_set_context(bad_usb->thread, bad_usb);
- furi_thread_set_callback(bad_usb->thread, bad_usb_worker);
-
- furi_thread_start(bad_usb->thread);
- return bad_usb;
-}
-
-void bad_usb_script_close(BadUsbScript* bad_usb) {
- furi_assert(bad_usb);
- furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtEnd);
- furi_thread_join(bad_usb->thread);
- furi_thread_free(bad_usb->thread);
- string_clear(bad_usb->file_path);
- free(bad_usb);
-}
-
-void bad_usb_script_toggle(BadUsbScript* bad_usb) {
- furi_assert(bad_usb);
- furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle);
-}
-
-BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb) {
- furi_assert(bad_usb);
- return &(bad_usb->st);
-}
diff --git a/applications/bad_usb/bad_usb_script.h b/applications/bad_usb/bad_usb_script.h
deleted file mode 100644
index 88921de381e..00000000000
--- a/applications/bad_usb/bad_usb_script.h
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include
-#include
-
-typedef struct BadUsbScript BadUsbScript;
-
-typedef enum {
- BadUsbStateInit,
- BadUsbStateNotConnected,
- BadUsbStateIdle,
- BadUsbStateRunning,
- BadUsbStateDelay,
- BadUsbStateDone,
- BadUsbStateScriptError,
- BadUsbStateFileError,
-} BadUsbWorkerState;
-
-typedef struct {
- BadUsbWorkerState state;
- uint16_t line_cur;
- uint16_t line_nb;
- uint32_t delay_remain;
- uint16_t error_line;
-} BadUsbState;
-
-BadUsbScript* bad_usb_script_open(string_t file_path);
-
-void bad_usb_script_close(BadUsbScript* bad_usb);
-
-void bad_usb_script_start(BadUsbScript* bad_usb);
-
-void bad_usb_script_stop(BadUsbScript* bad_usb);
-
-void bad_usb_script_toggle(BadUsbScript* bad_usb);
-
-BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb);
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/applications/bad_usb/scenes/bad_usb_scene_config.h b/applications/bad_usb/scenes/bad_usb_scene_config.h
deleted file mode 100644
index 0ab8f54f874..00000000000
--- a/applications/bad_usb/scenes/bad_usb_scene_config.h
+++ /dev/null
@@ -1,3 +0,0 @@
-ADD_SCENE(bad_usb, file_select, FileSelect)
-ADD_SCENE(bad_usb, work, Work)
-ADD_SCENE(bad_usb, error, Error)
diff --git a/applications/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/bad_usb/scenes/bad_usb_scene_file_select.c
deleted file mode 100644
index 1e6ba895a18..00000000000
--- a/applications/bad_usb/scenes/bad_usb_scene_file_select.c
+++ /dev/null
@@ -1,45 +0,0 @@
-#include "../bad_usb_app_i.h"
-#include "furi_hal_power.h"
-#include "furi_hal_usb.h"
-
-static bool bad_usb_file_select(BadUsbApp* bad_usb) {
- furi_assert(bad_usb);
-
- // Input events and views are managed by file_browser
- bool res = dialog_file_browser_show(
- bad_usb->dialogs,
- bad_usb->file_path,
- bad_usb->file_path,
- BAD_USB_APP_EXTENSION,
- true,
- &I_badusb_10px,
- true);
-
- return res;
-}
-
-void bad_usb_scene_file_select_on_enter(void* context) {
- BadUsbApp* bad_usb = context;
-
- furi_hal_usb_disable();
-
- if(bad_usb_file_select(bad_usb)) {
- scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork);
- } else {
- furi_hal_usb_enable();
- //scene_manager_previous_scene(bad_usb->scene_manager);
- view_dispatcher_stop(bad_usb->view_dispatcher);
- }
-}
-
-bool bad_usb_scene_file_select_on_event(void* context, SceneManagerEvent event) {
- UNUSED(context);
- UNUSED(event);
- // BadUsbApp* bad_usb = context;
- return false;
-}
-
-void bad_usb_scene_file_select_on_exit(void* context) {
- UNUSED(context);
- // BadUsbApp* bad_usb = context;
-}
diff --git a/applications/bad_usb/scenes/bad_usb_scene_work.c b/applications/bad_usb/scenes/bad_usb_scene_work.c
deleted file mode 100644
index 516cbde3af4..00000000000
--- a/applications/bad_usb/scenes/bad_usb_scene_work.c
+++ /dev/null
@@ -1,48 +0,0 @@
-#include "../bad_usb_script.h"
-#include "../bad_usb_app_i.h"
-#include "../views/bad_usb_view.h"
-#include "furi_hal.h"
-#include "m-string.h"
-#include "toolbox/path.h"
-
-void bad_usb_scene_work_ok_callback(InputType type, void* context) {
- furi_assert(context);
- BadUsbApp* app = context;
- view_dispatcher_send_custom_event(app->view_dispatcher, type);
-}
-
-bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
- BadUsbApp* app = context;
- bool consumed = false;
-
- if(event.type == SceneManagerEventTypeCustom) {
- bad_usb_script_toggle(app->bad_usb_script);
- consumed = true;
- } else if(event.type == SceneManagerEventTypeTick) {
- bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
- }
- return consumed;
-}
-
-void bad_usb_scene_work_on_enter(void* context) {
- BadUsbApp* app = context;
-
- string_t file_name;
- string_init(file_name);
-
- path_extract_filename(app->file_path, file_name, true);
- bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name));
- app->bad_usb_script = bad_usb_script_open(app->file_path);
-
- string_clear(file_name);
-
- bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
-
- bad_usb_set_ok_callback(app->bad_usb_view, bad_usb_scene_work_ok_callback, app);
- view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork);
-}
-
-void bad_usb_scene_work_on_exit(void* context) {
- BadUsbApp* app = context;
- bad_usb_script_close(app->bad_usb_script);
-}
diff --git a/applications/bad_usb/views/bad_usb_view.c b/applications/bad_usb/views/bad_usb_view.c
deleted file mode 100644
index 0679669f6b5..00000000000
--- a/applications/bad_usb/views/bad_usb_view.c
+++ /dev/null
@@ -1,171 +0,0 @@
-#include "bad_usb_view.h"
-#include "../bad_usb_script.h"
-#include
-
-#define MAX_NAME_LEN 64
-
-struct BadUsb {
- View* view;
- BadUsbOkCallback callback;
- void* context;
-};
-
-typedef struct {
- char file_name[MAX_NAME_LEN];
- BadUsbState state;
- uint8_t anim_frame;
-} BadUsbModel;
-
-static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
- BadUsbModel* model = _model;
-
- string_t disp_str;
- string_init_set_str(disp_str, model->file_name);
- elements_string_fit_width(canvas, disp_str, 128 - 2);
- canvas_set_font(canvas, FontSecondary);
- canvas_draw_str(canvas, 2, 8, string_get_cstr(disp_str));
- string_reset(disp_str);
-
- canvas_draw_icon(canvas, 22, 20, &I_UsbTree_48x22);
-
- if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) {
- elements_button_center(canvas, "Run");
- } else if((model->state.state == BadUsbStateRunning) || (model->state.state == BadUsbStateDelay)) {
- elements_button_center(canvas, "Stop");
- }
-
- if(model->state.state == BadUsbStateNotConnected) {
- canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18);
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "Connect");
- canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "to USB");
- } else if(model->state.state == BadUsbStateFileError) {
- canvas_draw_icon(canvas, 4, 22, &I_Error_18x18);
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(canvas, 127, 27, AlignRight, AlignBottom, "File");
- canvas_draw_str_aligned(canvas, 127, 39, AlignRight, AlignBottom, "ERROR");
- } else if(model->state.state == BadUsbStateScriptError) {
- canvas_draw_icon(canvas, 4, 22, &I_Error_18x18);
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:");
- canvas_set_font(canvas, FontSecondary);
- string_printf(disp_str, "line %u", model->state.error_line);
- canvas_draw_str_aligned(
- canvas, 127, 46, AlignRight, AlignBottom, string_get_cstr(disp_str));
- string_reset(disp_str);
- } else if(model->state.state == BadUsbStateIdle) {
- canvas_draw_icon(canvas, 4, 22, &I_Smile_18x18);
- canvas_set_font(canvas, FontBigNumbers);
- canvas_draw_str_aligned(canvas, 114, 36, AlignRight, AlignBottom, "0");
- canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14);
- } else if(model->state.state == BadUsbStateRunning) {
- if(model->anim_frame == 0) {
- canvas_draw_icon(canvas, 4, 19, &I_EviSmile1_18x21);
- } else {
- canvas_draw_icon(canvas, 4, 19, &I_EviSmile2_18x21);
- }
- canvas_set_font(canvas, FontBigNumbers);
- string_printf(disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
- canvas_draw_str_aligned(
- canvas, 114, 36, AlignRight, AlignBottom, string_get_cstr(disp_str));
- string_reset(disp_str);
- canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14);
- } else if(model->state.state == BadUsbStateDone) {
- canvas_draw_icon(canvas, 4, 19, &I_EviSmile1_18x21);
- canvas_set_font(canvas, FontBigNumbers);
- canvas_draw_str_aligned(canvas, 114, 36, AlignRight, AlignBottom, "100");
- string_reset(disp_str);
- canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14);
- } else if(model->state.state == BadUsbStateDelay) {
- if(model->anim_frame == 0) {
- canvas_draw_icon(canvas, 4, 19, &I_EviWaiting1_18x21);
- } else {
- canvas_draw_icon(canvas, 4, 19, &I_EviWaiting2_18x21);
- }
- canvas_set_font(canvas, FontBigNumbers);
- string_printf(disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
- canvas_draw_str_aligned(
- canvas, 114, 36, AlignRight, AlignBottom, string_get_cstr(disp_str));
- string_reset(disp_str);
- canvas_draw_icon(canvas, 117, 22, &I_Percent_10x14);
- canvas_set_font(canvas, FontSecondary);
- string_printf(disp_str, "delay %us", model->state.delay_remain);
- canvas_draw_str_aligned(
- canvas, 127, 46, AlignRight, AlignBottom, string_get_cstr(disp_str));
- string_reset(disp_str);
- } else {
- canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18);
- }
-
- string_clear(disp_str);
-}
-
-static bool bad_usb_input_callback(InputEvent* event, void* context) {
- furi_assert(context);
- BadUsb* bad_usb = context;
- bool consumed = false;
-
- if(event->type == InputTypeShort) {
- if(event->key == InputKeyOk) {
- consumed = true;
- furi_assert(bad_usb->callback);
- bad_usb->callback(InputTypeShort, bad_usb->context);
- }
- }
-
- return consumed;
-}
-
-BadUsb* bad_usb_alloc() {
- BadUsb* bad_usb = malloc(sizeof(BadUsb));
-
- bad_usb->view = view_alloc();
- view_allocate_model(bad_usb->view, ViewModelTypeLocking, sizeof(BadUsbModel));
- view_set_context(bad_usb->view, bad_usb);
- view_set_draw_callback(bad_usb->view, bad_usb_draw_callback);
- view_set_input_callback(bad_usb->view, bad_usb_input_callback);
-
- return bad_usb;
-}
-
-void bad_usb_free(BadUsb* bad_usb) {
- furi_assert(bad_usb);
- view_free(bad_usb->view);
- free(bad_usb);
-}
-
-View* bad_usb_get_view(BadUsb* bad_usb) {
- furi_assert(bad_usb);
- return bad_usb->view;
-}
-
-void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context) {
- furi_assert(bad_usb);
- furi_assert(callback);
- with_view_model(
- bad_usb->view, (BadUsbModel * model) {
- UNUSED(model);
- bad_usb->callback = callback;
- bad_usb->context = context;
- return true;
- });
-}
-
-void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) {
- furi_assert(name);
- with_view_model(
- bad_usb->view, (BadUsbModel * model) {
- strlcpy(model->file_name, name, MAX_NAME_LEN);
- return true;
- });
-}
-
-void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
- furi_assert(st);
- with_view_model(
- bad_usb->view, (BadUsbModel * model) {
- memcpy(&(model->state), st, sizeof(BadUsbState));
- model->anim_frame ^= 1;
- return true;
- });
-}
diff --git a/applications/bad_usb/views/bad_usb_view.h b/applications/bad_usb/views/bad_usb_view.h
deleted file mode 100755
index 80a47e2ca67..00000000000
--- a/applications/bad_usb/views/bad_usb_view.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-#include
-#include "../bad_usb_script.h"
-
-typedef struct BadUsb BadUsb;
-typedef void (*BadUsbOkCallback)(InputType type, void* context);
-
-BadUsb* bad_usb_alloc();
-
-void bad_usb_free(BadUsb* bad_usb);
-
-View* bad_usb_get_view(BadUsb* bad_usb);
-
-void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context);
-
-void bad_usb_set_file_name(BadUsb* bad_usb, const char* name);
-
-void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st);
diff --git a/applications/bt/application.fam b/applications/bt/application.fam
deleted file mode 100644
index 248386ff305..00000000000
--- a/applications/bt/application.fam
+++ /dev/null
@@ -1,66 +0,0 @@
-App(
- appid="bt",
- name="BtSrv",
- apptype=FlipperAppType.SERVICE,
- entry_point="bt_srv",
- cdefines=["SRV_BT"],
- requires=[
- "cli",
- "dialogs",
- ],
- provides=[
- "bt_start",
- "bt_settings",
- "bt_debug",
- ],
- stack_size=1 * 1024,
- order=20,
-)
-
-App(
- appid="bt_start",
- apptype=FlipperAppType.STARTUP,
- entry_point="bt_on_system_start",
- order=70,
-)
-
-App(
- appid="bt_settings",
- name="Bluetooth",
- apptype=FlipperAppType.SETTINGS,
- entry_point="bt_settings_app",
- stack_size=1 * 1024,
- requires=[
- "bt",
- "gui",
- ],
- order=10,
-)
-
-App(
- appid="bt_debug",
- name="Bluetooth Debug",
- apptype=FlipperAppType.DEBUG,
- entry_point="bt_debug_app",
- stack_size=1 * 1024,
- requires=[
- "bt",
- "gui",
- "dialogs",
- ],
- order=110,
-)
-
-App(
- appid="bt_hid",
- name="Bluetooth Remote",
- apptype=FlipperAppType.PLUGIN,
- entry_point="bt_hid_app",
- stack_size=1 * 1024,
- cdefines=["APP_BLE_HID"],
- requires=[
- "bt",
- "gui",
- ],
- order=10,
-)
diff --git a/applications/bt/bt_debug_app/views/bt_test.c b/applications/bt/bt_debug_app/views/bt_test.c
deleted file mode 100755
index 70f57c12c18..00000000000
--- a/applications/bt/bt_debug_app/views/bt_test.c
+++ /dev/null
@@ -1,422 +0,0 @@
-#include "bt_test.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-struct BtTestParam {
- const char* label;
- uint8_t current_value_index;
- string_t current_value_text;
- uint8_t values_count;
- BtTestParamChangeCallback change_callback;
- void* context;
-};
-
-ARRAY_DEF(BtTestParamArray, BtTestParam, M_POD_OPLIST);
-
-struct BtTest {
- View* view;
- BtTestChangeStateCallback change_state_callback;
- BtTestBackCallback back_callback;
- void* context;
-};
-
-typedef struct {
- BtTestState state;
- BtTestParamArray_t params;
- uint8_t position;
- uint8_t window_position;
- const char* message;
- float rssi;
- uint32_t packets_num_rx;
- uint32_t packets_num_tx;
-} BtTestModel;
-
-#define BT_TEST_START_MESSAGE "Ok - Start"
-#define BT_TEST_STOP_MESSAGE "Ok - Stop"
-
-static void bt_test_process_up(BtTest* bt_test);
-static void bt_test_process_down(BtTest* bt_test);
-static void bt_test_process_left(BtTest* bt_test);
-static void bt_test_process_right(BtTest* bt_test);
-static void bt_test_process_ok(BtTest* bt_test);
-static void bt_test_process_back(BtTest* bt_test);
-
-static void bt_test_draw_callback(Canvas* canvas, void* _model) {
- BtTestModel* model = _model;
- char info_str[32];
-
- const uint8_t param_height = 16;
- const uint8_t param_width = 123;
-
- canvas_clear(canvas);
-
- uint8_t position = 0;
- BtTestParamArray_it_t it;
-
- canvas_set_font(canvas, FontSecondary);
- for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it);
- BtTestParamArray_next(it)) {
- uint8_t param_position = position - model->window_position;
- uint8_t params_on_screen = 3;
- uint8_t y_offset = 0;
-
- if(param_position < params_on_screen) {
- const BtTestParam* param = BtTestParamArray_cref(it);
- uint8_t param_y = y_offset + (param_position * param_height);
- uint8_t param_text_y = param_y + param_height - 4;
-
- if(position == model->position) {
- canvas_set_color(canvas, ColorBlack);
- elements_slightly_rounded_box(
- canvas, 0, param_y + 1, param_width, param_height - 2);
- canvas_set_color(canvas, ColorWhite);
- } else {
- canvas_set_color(canvas, ColorBlack);
- }
-
- canvas_draw_str(canvas, 6, param_text_y, param->label);
-
- if(param->current_value_index > 0) {
- canvas_draw_str(canvas, 50, param_text_y, "<");
- }
-
- canvas_draw_str(canvas, 61, param_text_y, string_get_cstr(param->current_value_text));
-
- if(param->current_value_index < (param->values_count - 1)) {
- canvas_draw_str(canvas, 113, param_text_y, ">");
- }
- }
-
- position++;
- }
-
- elements_scrollbar(canvas, model->position, BtTestParamArray_size(model->params));
- canvas_draw_str(canvas, 6, 60, model->message);
- if(model->state == BtTestStateStarted) {
- if(model->rssi != 0.0f) {
- snprintf(info_str, sizeof(info_str), "RSSI:%3.1f dB", (double)model->rssi);
- canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
- }
- } else if(model->state == BtTestStateStopped) {
- if(model->packets_num_rx) {
- snprintf(info_str, sizeof(info_str), "%ld pack rcv", model->packets_num_rx);
- canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
- } else if(model->packets_num_tx) {
- snprintf(info_str, sizeof(info_str), "%ld pack sent", model->packets_num_tx);
- canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
- }
- }
-}
-
-static bool bt_test_input_callback(InputEvent* event, void* context) {
- BtTest* bt_test = context;
- furi_assert(bt_test);
- bool consumed = false;
-
- if(event->type == InputTypeShort) {
- switch(event->key) {
- case InputKeyUp:
- consumed = true;
- bt_test_process_up(bt_test);
- break;
- case InputKeyDown:
- consumed = true;
- bt_test_process_down(bt_test);
- break;
- case InputKeyLeft:
- consumed = true;
- bt_test_process_left(bt_test);
- break;
- case InputKeyRight:
- consumed = true;
- bt_test_process_right(bt_test);
- break;
- case InputKeyOk:
- consumed = true;
- bt_test_process_ok(bt_test);
- break;
- case InputKeyBack:
- consumed = false;
- bt_test_process_back(bt_test);
- break;
- default:
- break;
- }
- }
-
- return consumed;
-}
-
-void bt_test_process_up(BtTest* bt_test) {
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- uint8_t params_on_screen = 3;
- if(model->position > 0) {
- model->position--;
- if(((model->position - model->window_position) < 1) &&
- model->window_position > 0) {
- model->window_position--;
- }
- } else {
- model->position = BtTestParamArray_size(model->params) - 1;
- if(model->position > (params_on_screen - 1)) {
- model->window_position = model->position - (params_on_screen - 1);
- }
- }
- return true;
- });
-}
-
-void bt_test_process_down(BtTest* bt_test) {
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- uint8_t params_on_screen = 3;
- if(model->position < (BtTestParamArray_size(model->params) - 1)) {
- model->position++;
- if((model->position - model->window_position) > (params_on_screen - 2) &&
- model->window_position <
- (BtTestParamArray_size(model->params) - params_on_screen)) {
- model->window_position++;
- }
- } else {
- model->position = 0;
- model->window_position = 0;
- }
- return true;
- });
-}
-
-BtTestParam* bt_test_get_selected_param(BtTestModel* model) {
- BtTestParam* param = NULL;
-
- BtTestParamArray_it_t it;
- uint8_t position = 0;
- for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it);
- BtTestParamArray_next(it)) {
- if(position == model->position) {
- break;
- }
- position++;
- }
-
- param = BtTestParamArray_ref(it);
-
- furi_assert(param);
- return param;
-}
-
-void bt_test_process_left(BtTest* bt_test) {
- BtTestParam* param;
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- param = bt_test_get_selected_param(model);
- if(param->current_value_index > 0) {
- param->current_value_index--;
- if(param->change_callback) {
- model->state = BtTestStateStopped;
- model->message = BT_TEST_START_MESSAGE;
- model->rssi = 0.0f;
- model->packets_num_rx = 0;
- model->packets_num_tx = 0;
- }
- }
- return true;
- });
- if(param->change_callback) {
- param->change_callback(param);
- }
-}
-
-void bt_test_process_right(BtTest* bt_test) {
- BtTestParam* param;
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- param = bt_test_get_selected_param(model);
- if(param->current_value_index < (param->values_count - 1)) {
- param->current_value_index++;
- if(param->change_callback) {
- model->state = BtTestStateStopped;
- model->message = BT_TEST_START_MESSAGE;
- model->rssi = 0.0f;
- model->packets_num_rx = 0;
- model->packets_num_tx = 0;
- }
- }
- return true;
- });
- if(param->change_callback) {
- param->change_callback(param);
- }
-}
-
-void bt_test_process_ok(BtTest* bt_test) {
- BtTestState state;
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- if(model->state == BtTestStateStarted) {
- model->state = BtTestStateStopped;
- model->message = BT_TEST_START_MESSAGE;
- model->rssi = 0.0f;
- model->packets_num_rx = 0;
- model->packets_num_tx = 0;
- } else if(model->state == BtTestStateStopped) {
- model->state = BtTestStateStarted;
- model->message = BT_TEST_STOP_MESSAGE;
- }
- state = model->state;
- return true;
- });
- if(bt_test->change_state_callback) {
- bt_test->change_state_callback(state, bt_test->context);
- }
-}
-
-void bt_test_process_back(BtTest* bt_test) {
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- model->state = BtTestStateStopped;
- model->rssi = 0.0f;
- model->packets_num_rx = 0;
- model->packets_num_tx = 0;
- return false;
- });
- if(bt_test->back_callback) {
- bt_test->back_callback(bt_test->context);
- }
-}
-
-BtTest* bt_test_alloc() {
- BtTest* bt_test = malloc(sizeof(BtTest));
- bt_test->view = view_alloc();
- view_set_context(bt_test->view, bt_test);
- view_allocate_model(bt_test->view, ViewModelTypeLocking, sizeof(BtTestModel));
- view_set_draw_callback(bt_test->view, bt_test_draw_callback);
- view_set_input_callback(bt_test->view, bt_test_input_callback);
-
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- model->state = BtTestStateStopped;
- model->message = "Ok - Start";
- BtTestParamArray_init(model->params);
- model->position = 0;
- model->window_position = 0;
- model->rssi = 0.0f;
- model->packets_num_tx = 0;
- model->packets_num_rx = 0;
- return true;
- });
-
- return bt_test;
-}
-
-void bt_test_free(BtTest* bt_test) {
- furi_assert(bt_test);
-
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- BtTestParamArray_it_t it;
- for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it);
- BtTestParamArray_next(it)) {
- string_clear(BtTestParamArray_ref(it)->current_value_text);
- }
- BtTestParamArray_clear(model->params);
- return false;
- });
- view_free(bt_test->view);
- free(bt_test);
-}
-
-View* bt_test_get_view(BtTest* bt_test) {
- furi_assert(bt_test);
- return bt_test->view;
-}
-
-BtTestParam* bt_test_param_add(
- BtTest* bt_test,
- const char* label,
- uint8_t values_count,
- BtTestParamChangeCallback change_callback,
- void* context) {
- BtTestParam* param = NULL;
- furi_assert(label);
- furi_assert(bt_test);
-
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- param = BtTestParamArray_push_new(model->params);
- param->label = label;
- param->values_count = values_count;
- param->change_callback = change_callback;
- param->context = context;
- param->current_value_index = 0;
- string_init(param->current_value_text);
- return true;
- });
-
- return param;
-}
-
-void bt_test_set_rssi(BtTest* bt_test, float rssi) {
- furi_assert(bt_test);
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- model->rssi = rssi;
- return true;
- });
-}
-
-void bt_test_set_packets_tx(BtTest* bt_test, uint32_t packets_num) {
- furi_assert(bt_test);
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- model->packets_num_tx = packets_num;
- return true;
- });
-}
-
-void bt_test_set_packets_rx(BtTest* bt_test, uint32_t packets_num) {
- furi_assert(bt_test);
- with_view_model(
- bt_test->view, (BtTestModel * model) {
- model->packets_num_rx = packets_num;
- return true;
- });
-}
-
-void bt_test_set_change_state_callback(BtTest* bt_test, BtTestChangeStateCallback callback) {
- furi_assert(bt_test);
- furi_assert(callback);
- bt_test->change_state_callback = callback;
-}
-
-void bt_test_set_back_callback(BtTest* bt_test, BtTestBackCallback callback) {
- furi_assert(bt_test);
- furi_assert(callback);
- bt_test->back_callback = callback;
-}
-
-void bt_test_set_context(BtTest* bt_test, void* context) {
- furi_assert(bt_test);
- bt_test->context = context;
-}
-
-void bt_test_set_current_value_index(BtTestParam* param, uint8_t current_value_index) {
- param->current_value_index = current_value_index;
-}
-
-void bt_test_set_current_value_text(BtTestParam* param, const char* current_value_text) {
- string_set_str(param->current_value_text, current_value_text);
-}
-
-uint8_t bt_test_get_current_value_index(BtTestParam* param) {
- return param->current_value_index;
-}
-
-void* bt_test_get_context(BtTestParam* param) {
- return param->context;
-}
diff --git a/applications/bt/bt_hid_app/bt_hid.c b/applications/bt/bt_hid_app/bt_hid.c
deleted file mode 100755
index 3189042c0e9..00000000000
--- a/applications/bt/bt_hid_app/bt_hid.c
+++ /dev/null
@@ -1,198 +0,0 @@
-#include "bt_hid.h"
-#include
-#include
-
-#define TAG "BtHidApp"
-
-enum BtDebugSubmenuIndex {
- BtHidSubmenuIndexKeynote,
- BtHidSubmenuIndexKeyboard,
- BtHidSubmenuIndexMedia,
- BtHidSubmenuIndexMouse,
-};
-
-void bt_hid_submenu_callback(void* context, uint32_t index) {
- furi_assert(context);
- BtHid* app = context;
- if(index == BtHidSubmenuIndexKeynote) {
- app->view_id = BtHidViewKeynote;
- view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
- } else if(index == BtHidSubmenuIndexKeyboard) {
- app->view_id = BtHidViewKeyboard;
- view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeyboard);
- } else if(index == BtHidSubmenuIndexMedia) {
- app->view_id = BtHidViewMedia;
- view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMedia);
- } else if(index == BtHidSubmenuIndexMouse) {
- app->view_id = BtHidViewMouse;
- view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMouse);
- }
-}
-
-void bt_hid_dialog_callback(DialogExResult result, void* context) {
- furi_assert(context);
- BtHid* app = context;
- if(result == DialogExResultLeft) {
- view_dispatcher_stop(app->view_dispatcher);
- } else if(result == DialogExResultRight) {
- view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view
- } else if(result == DialogExResultCenter) {
- view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewSubmenu);
- }
-}
-
-uint32_t bt_hid_exit_confirm_view(void* context) {
- UNUSED(context);
- return BtHidViewExitConfirm;
-}
-
-uint32_t bt_hid_exit(void* context) {
- UNUSED(context);
- return VIEW_NONE;
-}
-
-void bt_hid_connection_status_changed_callback(BtStatus status, void* context) {
- furi_assert(context);
- BtHid* bt_hid = context;
- bool connected = (status == BtStatusConnected);
- if(connected) {
- notification_internal_message(bt_hid->notifications, &sequence_set_blue_255);
- } else {
- notification_internal_message(bt_hid->notifications, &sequence_reset_blue);
- }
- bt_hid_keynote_set_connected_status(bt_hid->bt_hid_keynote, connected);
- bt_hid_keyboard_set_connected_status(bt_hid->bt_hid_keyboard, connected);
- bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected);
- bt_hid_mouse_set_connected_status(bt_hid->bt_hid_mouse, connected);
-}
-
-BtHid* bt_hid_app_alloc() {
- BtHid* app = malloc(sizeof(BtHid));
-
- // Gui
- app->gui = furi_record_open(RECORD_GUI);
-
- // Bt
- app->bt = furi_record_open(RECORD_BT);
-
- // Notifications
- app->notifications = furi_record_open(RECORD_NOTIFICATION);
-
- // View dispatcher
- app->view_dispatcher = view_dispatcher_alloc();
- view_dispatcher_enable_queue(app->view_dispatcher);
- view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
-
- // Submenu view
- app->submenu = submenu_alloc();
- submenu_add_item(
- app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app);
- submenu_add_item(
- app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app);
- submenu_add_item(
- app->submenu, "Media Player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app);
- submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app);
- view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit);
- view_dispatcher_add_view(
- app->view_dispatcher, BtHidViewSubmenu, submenu_get_view(app->submenu));
-
- // Dialog view
- app->dialog = dialog_ex_alloc();
- dialog_ex_set_result_callback(app->dialog, bt_hid_dialog_callback);
- dialog_ex_set_context(app->dialog, app);
- dialog_ex_set_left_button_text(app->dialog, "Exit");
- dialog_ex_set_right_button_text(app->dialog, "Stay");
- dialog_ex_set_center_button_text(app->dialog, "Menu");
- dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop);
- view_dispatcher_add_view(
- app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog));
-
- // Keynote view
- app->bt_hid_keynote = bt_hid_keynote_alloc();
- view_set_previous_callback(
- bt_hid_keynote_get_view(app->bt_hid_keynote), bt_hid_exit_confirm_view);
- view_dispatcher_add_view(
- app->view_dispatcher, BtHidViewKeynote, bt_hid_keynote_get_view(app->bt_hid_keynote));
-
- // Keyboard view
- app->bt_hid_keyboard = bt_hid_keyboard_alloc();
- view_set_previous_callback(
- bt_hid_keyboard_get_view(app->bt_hid_keyboard), bt_hid_exit_confirm_view);
- view_dispatcher_add_view(
- app->view_dispatcher, BtHidViewKeyboard, bt_hid_keyboard_get_view(app->bt_hid_keyboard));
-
- // Media view
- app->bt_hid_media = bt_hid_media_alloc();
- view_set_previous_callback(bt_hid_media_get_view(app->bt_hid_media), bt_hid_exit_confirm_view);
- view_dispatcher_add_view(
- app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media));
-
- // Mouse view
- app->bt_hid_mouse = bt_hid_mouse_alloc();
- view_set_previous_callback(bt_hid_mouse_get_view(app->bt_hid_mouse), bt_hid_exit_confirm_view);
- view_dispatcher_add_view(
- app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse));
-
- // TODO switch to menu after Media is done
- app->view_id = BtHidViewKeynote;
- view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
-
- return app;
-}
-
-void bt_hid_app_free(BtHid* app) {
- furi_assert(app);
-
- // Reset notification
- notification_internal_message(app->notifications, &sequence_reset_blue);
-
- // Free views
- view_dispatcher_remove_view(app->view_dispatcher, BtHidViewSubmenu);
- submenu_free(app->submenu);
- view_dispatcher_remove_view(app->view_dispatcher, BtHidViewExitConfirm);
- dialog_ex_free(app->dialog);
- view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote);
- bt_hid_keynote_free(app->bt_hid_keynote);
- view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeyboard);
- bt_hid_keyboard_free(app->bt_hid_keyboard);
- view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMedia);
- bt_hid_media_free(app->bt_hid_media);
- view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMouse);
- bt_hid_mouse_free(app->bt_hid_mouse);
- view_dispatcher_free(app->view_dispatcher);
-
- // Close records
- furi_record_close(RECORD_GUI);
- app->gui = NULL;
- furi_record_close(RECORD_NOTIFICATION);
- app->notifications = NULL;
- furi_record_close(RECORD_BT);
- app->bt = NULL;
-
- // Free rest
- free(app);
-}
-
-int32_t bt_hid_app(void* p) {
- UNUSED(p);
- // Switch profile to Hid
- BtHid* app = bt_hid_app_alloc();
- bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app);
- // Change profile
- if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
- FURI_LOG_E(TAG, "Failed to switch profile");
- bt_hid_app_free(app);
- return -1;
- }
- furi_hal_bt_start_advertising();
-
- view_dispatcher_run(app->view_dispatcher);
-
- bt_set_status_changed_callback(app->bt, NULL, NULL);
- // Change back profile to Serial
- bt_set_profile(app->bt, BtProfileSerial);
-
- bt_hid_app_free(app);
-
- return 0;
-}
diff --git a/applications/bt/bt_hid_app/bt_hid.h b/applications/bt/bt_hid_app/bt_hid.h
deleted file mode 100644
index 81d092db4d0..00000000000
--- a/applications/bt/bt_hid_app/bt_hid.h
+++ /dev/null
@@ -1,38 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include "views/bt_hid_keynote.h"
-#include "views/bt_hid_keyboard.h"
-#include "views/bt_hid_media.h"
-#include "views/bt_hid_mouse.h"
-
-typedef struct {
- Bt* bt;
- Gui* gui;
- NotificationApp* notifications;
- ViewDispatcher* view_dispatcher;
- Submenu* submenu;
- DialogEx* dialog;
- BtHidKeynote* bt_hid_keynote;
- BtHidKeyboard* bt_hid_keyboard;
- BtHidMedia* bt_hid_media;
- BtHidMouse* bt_hid_mouse;
- uint32_t view_id;
-} BtHid;
-
-typedef enum {
- BtHidViewSubmenu,
- BtHidViewKeynote,
- BtHidViewKeyboard,
- BtHidViewMedia,
- BtHidViewMouse,
- BtHidViewExitConfirm,
-} BtHidView;
diff --git a/applications/bt/bt_hid_app/views/bt_hid_keyboard.c b/applications/bt/bt_hid_app/views/bt_hid_keyboard.c
deleted file mode 100644
index 1088e2959d6..00000000000
--- a/applications/bt/bt_hid_app/views/bt_hid_keyboard.c
+++ /dev/null
@@ -1,384 +0,0 @@
-#include "bt_hid_keyboard.h"
-#include
-#include
-#include
-#include
-#include
-
-struct BtHidKeyboard {
- View* view;
-};
-
-typedef struct {
- bool shift;
- bool alt;
- bool ctrl;
- bool gui;
- uint8_t x;
- uint8_t y;
- uint8_t last_key_code;
- uint16_t modifier_code;
- bool ok_pressed;
- bool back_pressed;
- bool connected;
- char key_string[5];
-} BtHidKeyboardModel;
-
-typedef struct {
- uint8_t width;
- char* key;
- const Icon* icon;
- char* shift_key;
- uint8_t value;
-} BtHidKeyboardKey;
-
-typedef struct {
- int8_t x;
- int8_t y;
-} BtHidKeyboardPoint;
-
-// 4 BY 12
-#define MARGIN_TOP 0
-#define MARGIN_LEFT 4
-#define KEY_WIDTH 9
-#define KEY_HEIGHT 12
-#define KEY_PADDING 1
-#define ROW_COUNT 6
-#define COLUMN_COUNT 12
-
-// 0 width items are not drawn, but there value is used
-const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
- {
- {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1},
- {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2},
- {.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3},
- {.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4},
- {.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5},
- {.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6},
- {.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7},
- {.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8},
- {.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9},
- {.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0},
- {.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE},
- {.width = 0, .value = HID_KEYBOARD_DELETE},
- },
- {
- {.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q},
- {.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W},
- {.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E},
- {.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R},
- {.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T},
- {.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y},
- {.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U},
- {.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I},
- {.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O},
- {.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P},
- {.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET},
- {.width = 1,
- .icon = NULL,
- .key = "]",
- .shift_key = "}",
- .value = HID_KEYBOARD_CLOSE_BRACKET},
- },
- {
- {.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A},
- {.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S},
- {.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D},
- {.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F},
- {.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G},
- {.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H},
- {.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J},
- {.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K},
- {.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L},
- {.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON},
- {.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN},
- {.width = 0, .value = HID_KEYBOARD_RETURN},
- },
- {
- {.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z},
- {.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X},
- {.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C},
- {.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V},
- {.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B},
- {.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N},
- {.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M},
- {.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH},
- {.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH},
- {.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT},
- {.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW},
- {.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS},
- },
- {
- {.width = 1, .icon = &I_Pin_arrow_up7x9, .value = HID_KEYBOARD_L_SHIFT},
- {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA},
- {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT},
- {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR},
- {.width = 0, .value = HID_KEYBOARD_SPACEBAR},
- {.width = 0, .value = HID_KEYBOARD_SPACEBAR},
- {.width = 0, .value = HID_KEYBOARD_SPACEBAR},
- {.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE},
- {.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN},
- {.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW},
- {.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW},
- {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW},
- },
- {
- {.width = 3, .icon = NULL, .key = "Ctrl", .value = HID_KEYBOARD_L_CTRL},
- {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
- {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
- {.width = 3, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT},
- {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
- {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
- {.width = 3, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI},
- {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
- {.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
- {.width = 3, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB},
- {.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
- {.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
- },
-};
-
-static void bt_hid_keyboard_to_upper(char* str) {
- while(*str) {
- *str = toupper((unsigned char)*str);
- str++;
- }
-}
-
-static void bt_hid_keyboard_draw_key(
- Canvas* canvas,
- BtHidKeyboardModel* model,
- uint8_t x,
- uint8_t y,
- BtHidKeyboardKey key,
- bool selected) {
- if(!key.width) return;
-
- canvas_set_color(canvas, ColorBlack);
- uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1);
- if(selected) {
- // Draw a filled box
- elements_slightly_rounded_box(
- canvas,
- MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
- MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
- keyWidth,
- KEY_HEIGHT);
- canvas_set_color(canvas, ColorWhite);
- } else {
- // Draw a framed box
- elements_slightly_rounded_frame(
- canvas,
- MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
- MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
- keyWidth,
- KEY_HEIGHT);
- }
- if(key.icon != NULL) {
- // Draw the icon centered on the button
- canvas_draw_icon(
- canvas,
- MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2,
- MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2,
- key.icon);
- } else {
- // If shift is toggled use the shift key when available
- strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key);
- // Upper case if ctrl or alt was toggled true
- if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) ||
- (model->alt && key.value == HID_KEYBOARD_L_ALT) ||
- (model->gui && key.value == HID_KEYBOARD_L_GUI)) {
- bt_hid_keyboard_to_upper(model->key_string);
- }
- canvas_draw_str_aligned(
- canvas,
- MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1,
- MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2,
- AlignCenter,
- AlignCenter,
- model->key_string);
- }
-}
-
-static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) {
- furi_assert(context);
- BtHidKeyboardModel* model = context;
-
- // Header
- if(!model->connected) {
- canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
- canvas_set_font(canvas, FontPrimary);
- elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard");
- elements_multiline_text_aligned(
- canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection...");
- return; // Dont render the keyboard if we are not yet connected
- }
-
- canvas_set_font(canvas, FontKeyboard);
- // Start shifting the all keys up if on the next row (Scrolling)
- uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0;
- for(uint8_t y = initY; y < ROW_COUNT; y++) {
- const BtHidKeyboardKey* keyboardKeyRow = bt_hid_keyboard_keyset[y];
- uint8_t x = 0;
- for(uint8_t i = 0; i < COLUMN_COUNT; i++) {
- BtHidKeyboardKey key = keyboardKeyRow[i];
- // Select when the button is hovered
- // Select if the button is hovered within its width
- // Select if back is clicked and its the backspace key
- // Deselect when the button clicked or not hovered
- bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
- bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
- bt_hid_keyboard_draw_key(
- canvas,
- model,
- x,
- y - initY,
- key,
- (!model->ok_pressed && keySelected) || backSelected);
- x += key.width;
- }
- }
-}
-
-static uint8_t bt_hid_keyboard_get_selected_key(BtHidKeyboardModel* model) {
- BtHidKeyboardKey key = bt_hid_keyboard_keyset[model->y][model->x];
- // Use upper case if shift is toggled
- bool useUppercase = model->shift;
- // Check if the key has an upper case version
- bool hasUppercase = key.shift_key != 0;
- if(useUppercase && hasUppercase)
- return key.value;
- else
- return key.value;
-}
-
-static void bt_hid_keyboard_get_select_key(BtHidKeyboardModel* model, BtHidKeyboardPoint delta) {
- // Keep going until a valid spot is found, this allows for nulls and zero width keys in the map
- do {
- if(((int8_t)model->y) + delta.y < 0)
- model->y = ROW_COUNT - 1;
- else
- model->y = (model->y + delta.y) % ROW_COUNT;
- } while(delta.y != 0 && bt_hid_keyboard_keyset[model->y][model->x].value == 0);
-
- do {
- if(((int8_t)model->x) + delta.x < 0)
- model->x = COLUMN_COUNT - 1;
- else
- model->x = (model->x + delta.x) % COLUMN_COUNT;
- } while(delta.x != 0 && bt_hid_keyboard_keyset[model->y][model->x].width ==
- 0); // Skip zero width keys, pretend they are one key
-}
-
-static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* event) {
- with_view_model(
- bt_hid_keyboard->view, (BtHidKeyboardModel * model) {
- if(event->key == InputKeyOk) {
- if(event->type == InputTypePress) {
- model->ok_pressed = true;
- } else if(event->type == InputTypeLong || event->type == InputTypeShort) {
- model->last_key_code = bt_hid_keyboard_get_selected_key(model);
-
- // Toggle the modifier key when clicked, and click the key
- if(model->last_key_code == HID_KEYBOARD_L_SHIFT) {
- model->shift = !model->shift;
- if(model->shift)
- model->modifier_code |= KEY_MOD_LEFT_SHIFT;
- else
- model->modifier_code &= ~KEY_MOD_LEFT_SHIFT;
- } else if(model->last_key_code == HID_KEYBOARD_L_ALT) {
- model->alt = !model->alt;
- if(model->alt)
- model->modifier_code |= KEY_MOD_LEFT_ALT;
- else
- model->modifier_code &= ~KEY_MOD_LEFT_ALT;
- } else if(model->last_key_code == HID_KEYBOARD_L_CTRL) {
- model->ctrl = !model->ctrl;
- if(model->ctrl)
- model->modifier_code |= KEY_MOD_LEFT_CTRL;
- else
- model->modifier_code &= ~KEY_MOD_LEFT_CTRL;
- } else if(model->last_key_code == HID_KEYBOARD_L_GUI) {
- model->gui = !model->gui;
- if(model->gui)
- model->modifier_code |= KEY_MOD_LEFT_GUI;
- else
- model->modifier_code &= ~KEY_MOD_LEFT_GUI;
- }
- furi_hal_bt_hid_kb_press(model->modifier_code | model->last_key_code);
- } else if(event->type == InputTypeRelease) {
- // Release happens after short and long presses
- furi_hal_bt_hid_kb_release(model->modifier_code | model->last_key_code);
- model->ok_pressed = false;
- }
- } else if(event->key == InputKeyBack) {
- // If back is pressed for a short time, backspace
- if(event->type == InputTypePress) {
- model->back_pressed = true;
- } else if(event->type == InputTypeShort) {
- furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE);
- furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE);
- } else if(event->type == InputTypeRelease) {
- model->back_pressed = false;
- }
- } else if(event->type == InputTypePress || event->type == InputTypeRepeat) {
- // Cycle the selected keys
- if(event->key == InputKeyUp) {
- bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = -1});
- } else if(event->key == InputKeyDown) {
- bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = 1});
- } else if(event->key == InputKeyLeft) {
- bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = -1, .y = 0});
- } else if(event->key == InputKeyRight) {
- bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 1, .y = 0});
- }
- }
- return true;
- });
-}
-
-static bool bt_hid_keyboard_input_callback(InputEvent* event, void* context) {
- furi_assert(context);
- BtHidKeyboard* bt_hid_keyboard = context;
- bool consumed = false;
-
- if(event->type == InputTypeLong && event->key == InputKeyBack) {
- furi_hal_bt_hid_kb_release_all();
- } else {
- bt_hid_keyboard_process(bt_hid_keyboard, event);
- consumed = true;
- }
-
- return consumed;
-}
-
-BtHidKeyboard* bt_hid_keyboard_alloc() {
- BtHidKeyboard* bt_hid_keyboard = malloc(sizeof(BtHidKeyboard));
- bt_hid_keyboard->view = view_alloc();
- view_set_context(bt_hid_keyboard->view, bt_hid_keyboard);
- view_allocate_model(bt_hid_keyboard->view, ViewModelTypeLocking, sizeof(BtHidKeyboardModel));
- view_set_draw_callback(bt_hid_keyboard->view, bt_hid_keyboard_draw_callback);
- view_set_input_callback(bt_hid_keyboard->view, bt_hid_keyboard_input_callback);
-
- return bt_hid_keyboard;
-}
-
-void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard) {
- furi_assert(bt_hid_keyboard);
- view_free(bt_hid_keyboard->view);
- free(bt_hid_keyboard);
-}
-
-View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard) {
- furi_assert(bt_hid_keyboard);
- return bt_hid_keyboard->view;
-}
-
-void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected) {
- furi_assert(bt_hid_keyboard);
- with_view_model(
- bt_hid_keyboard->view, (BtHidKeyboardModel * model) {
- model->connected = connected;
- return true;
- });
-}
diff --git a/applications/bt/bt_hid_app/views/bt_hid_keyboard.h b/applications/bt/bt_hid_app/views/bt_hid_keyboard.h
deleted file mode 100644
index b2cc928e2a9..00000000000
--- a/applications/bt/bt_hid_app/views/bt_hid_keyboard.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-
-#include
-
-typedef struct BtHidKeyboard BtHidKeyboard;
-
-BtHidKeyboard* bt_hid_keyboard_alloc();
-
-void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard);
-
-View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard);
-
-void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected);
diff --git a/applications/bt/bt_hid_app/views/bt_hid_keynote.c b/applications/bt/bt_hid_app/views/bt_hid_keynote.c
deleted file mode 100755
index 60a1ebc0877..00000000000
--- a/applications/bt/bt_hid_app/views/bt_hid_keynote.c
+++ /dev/null
@@ -1,201 +0,0 @@
-#include "bt_hid_keynote.h"
-#include
-#include
-#include
-#include
-
-struct BtHidKeynote {
- View* view;
-};
-
-typedef struct {
- bool left_pressed;
- bool up_pressed;
- bool right_pressed;
- bool down_pressed;
- bool ok_pressed;
- bool back_pressed;
- bool connected;
-} BtHidKeynoteModel;
-
-static void bt_hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
- canvas_draw_triangle(canvas, x, y, 5, 3, dir);
- if(dir == CanvasDirectionBottomToTop) {
- canvas_draw_line(canvas, x, y + 6, x, y - 1);
- } else if(dir == CanvasDirectionTopToBottom) {
- canvas_draw_line(canvas, x, y - 6, x, y + 1);
- } else if(dir == CanvasDirectionRightToLeft) {
- canvas_draw_line(canvas, x + 6, y, x - 1, y);
- } else if(dir == CanvasDirectionLeftToRight) {
- canvas_draw_line(canvas, x - 6, y, x + 1, y);
- }
-}
-
-static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) {
- furi_assert(context);
- BtHidKeynoteModel* model = context;
-
- // Header
- if(model->connected) {
- canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
- } else {
- canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
- }
- canvas_set_font(canvas, FontPrimary);
- elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote");
- canvas_set_font(canvas, FontSecondary);
-
- // Up
- canvas_draw_icon(canvas, 21, 24, &I_Button_18x18);
- if(model->up_pressed) {
- elements_slightly_rounded_box(canvas, 24, 26, 13, 13);
- canvas_set_color(canvas, ColorWhite);
- }
- bt_hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop);
- canvas_set_color(canvas, ColorBlack);
-
- // Down
- canvas_draw_icon(canvas, 21, 45, &I_Button_18x18);
- if(model->down_pressed) {
- elements_slightly_rounded_box(canvas, 24, 47, 13, 13);
- canvas_set_color(canvas, ColorWhite);
- }
- bt_hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom);
- canvas_set_color(canvas, ColorBlack);
-
- // Left
- canvas_draw_icon(canvas, 0, 45, &I_Button_18x18);
- if(model->left_pressed) {
- elements_slightly_rounded_box(canvas, 3, 47, 13, 13);
- canvas_set_color(canvas, ColorWhite);
- }
- bt_hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft);
- canvas_set_color(canvas, ColorBlack);
-
- // Right
- canvas_draw_icon(canvas, 42, 45, &I_Button_18x18);
- if(model->right_pressed) {
- elements_slightly_rounded_box(canvas, 45, 47, 13, 13);
- canvas_set_color(canvas, ColorWhite);
- }
- bt_hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight);
- canvas_set_color(canvas, ColorBlack);
-
- // Ok
- canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
- if(model->ok_pressed) {
- elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
- canvas_set_color(canvas, ColorWhite);
- }
- canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
- elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space");
- canvas_set_color(canvas, ColorBlack);
-
- // Back
- canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
- if(model->back_pressed) {
- elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
- canvas_set_color(canvas, ColorWhite);
- }
- canvas_draw_icon(canvas, 110, 49, &I_Ok_btn_9x9);
- elements_multiline_text_aligned(canvas, 76, 56, AlignLeft, AlignBottom, "Back");
-}
-
-static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
- with_view_model(
- bt_hid_keynote->view, (BtHidKeynoteModel * model) {
- if(event->type == InputTypePress) {
- if(event->key == InputKeyUp) {
- model->up_pressed = true;
- furi_hal_bt_hid_kb_press(HID_KEYBOARD_UP_ARROW);
- } else if(event->key == InputKeyDown) {
- model->down_pressed = true;
- furi_hal_bt_hid_kb_press(HID_KEYBOARD_DOWN_ARROW);
- } else if(event->key == InputKeyLeft) {
- model->left_pressed = true;
- furi_hal_bt_hid_kb_press(HID_KEYBOARD_LEFT_ARROW);
- } else if(event->key == InputKeyRight) {
- model->right_pressed = true;
- furi_hal_bt_hid_kb_press(HID_KEYBOARD_RIGHT_ARROW);
- } else if(event->key == InputKeyOk) {
- model->ok_pressed = true;
- furi_hal_bt_hid_kb_press(HID_KEYBOARD_SPACEBAR);
- } else if(event->key == InputKeyBack) {
- model->back_pressed = true;
- }
- } else if(event->type == InputTypeRelease) {
- if(event->key == InputKeyUp) {
- model->up_pressed = false;
- furi_hal_bt_hid_kb_release(HID_KEYBOARD_UP_ARROW);
- } else if(event->key == InputKeyDown) {
- model->down_pressed = false;
- furi_hal_bt_hid_kb_release(HID_KEYBOARD_DOWN_ARROW);
- } else if(event->key == InputKeyLeft) {
- model->left_pressed = false;
- furi_hal_bt_hid_kb_release(HID_KEYBOARD_LEFT_ARROW);
- } else if(event->key == InputKeyRight) {
- model->right_pressed = false;
- furi_hal_bt_hid_kb_release(HID_KEYBOARD_RIGHT_ARROW);
- } else if(event->key == InputKeyOk) {
- model->ok_pressed = false;
- furi_hal_bt_hid_kb_release(HID_KEYBOARD_SPACEBAR);
- } else if(event->key == InputKeyBack) {
- model->back_pressed = false;
- }
- } else if(event->type == InputTypeShort) {
- if(event->key == InputKeyBack) {
- furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE);
- furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE);
- furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_AC_BACK);
- furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_AC_BACK);
- }
- }
- return true;
- });
-}
-
-static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) {
- furi_assert(context);
- BtHidKeynote* bt_hid_keynote = context;
- bool consumed = false;
-
- if(event->type == InputTypeLong && event->key == InputKeyBack) {
- furi_hal_bt_hid_kb_release_all();
- } else {
- bt_hid_keynote_process(bt_hid_keynote, event);
- consumed = true;
- }
-
- return consumed;
-}
-
-BtHidKeynote* bt_hid_keynote_alloc() {
- BtHidKeynote* bt_hid_keynote = malloc(sizeof(BtHidKeynote));
- bt_hid_keynote->view = view_alloc();
- view_set_context(bt_hid_keynote->view, bt_hid_keynote);
- view_allocate_model(bt_hid_keynote->view, ViewModelTypeLocking, sizeof(BtHidKeynoteModel));
- view_set_draw_callback(bt_hid_keynote->view, bt_hid_keynote_draw_callback);
- view_set_input_callback(bt_hid_keynote->view, bt_hid_keynote_input_callback);
-
- return bt_hid_keynote;
-}
-
-void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote) {
- furi_assert(bt_hid_keynote);
- view_free(bt_hid_keynote->view);
- free(bt_hid_keynote);
-}
-
-View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote) {
- furi_assert(bt_hid_keynote);
- return bt_hid_keynote->view;
-}
-
-void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected) {
- furi_assert(bt_hid_keynote);
- with_view_model(
- bt_hid_keynote->view, (BtHidKeynoteModel * model) {
- model->connected = connected;
- return true;
- });
-}
diff --git a/applications/bt/bt_hid_app/views/bt_hid_keynote.h b/applications/bt/bt_hid_app/views/bt_hid_keynote.h
deleted file mode 100644
index 05b121457db..00000000000
--- a/applications/bt/bt_hid_app/views/bt_hid_keynote.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-
-#include
-
-typedef struct BtHidKeynote BtHidKeynote;
-
-BtHidKeynote* bt_hid_keynote_alloc();
-
-void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote);
-
-View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote);
-
-void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected);
diff --git a/applications/bt/bt_hid_app/views/bt_hid_media.c b/applications/bt/bt_hid_app/views/bt_hid_media.c
deleted file mode 100755
index b384f47cf18..00000000000
--- a/applications/bt/bt_hid_app/views/bt_hid_media.c
+++ /dev/null
@@ -1,189 +0,0 @@
-#include "bt_hid_media.h"
-#include
-#include
-#include
-#include
-
-struct BtHidMedia {
- View* view;
-};
-
-typedef struct {
- bool left_pressed;
- bool up_pressed;
- bool right_pressed;
- bool down_pressed;
- bool ok_pressed;
- bool connected;
-} BtHidMediaModel;
-
-static void bt_hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
- canvas_draw_triangle(canvas, x, y, 5, 3, dir);
- if(dir == CanvasDirectionBottomToTop) {
- canvas_draw_dot(canvas, x, y - 1);
- } else if(dir == CanvasDirectionTopToBottom) {
- canvas_draw_dot(canvas, x, y + 1);
- } else if(dir == CanvasDirectionRightToLeft) {
- canvas_draw_dot(canvas, x - 1, y);
- } else if(dir == CanvasDirectionLeftToRight) {
- canvas_draw_dot(canvas, x + 1, y);
- }
-}
-
-static void bt_hid_media_draw_callback(Canvas* canvas, void* context) {
- furi_assert(context);
- BtHidMediaModel* model = context;
-
- // Header
- if(model->connected) {
- canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
- } else {
- canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
- }
- canvas_set_font(canvas, FontPrimary);
- elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media");
- canvas_set_font(canvas, FontSecondary);
-
- // Keypad circles
- canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
-
- // Up
- if(model->up_pressed) {
- canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6);
- canvas_set_color(canvas, ColorBlack);
-
- // Down
- if(model->down_pressed) {
- canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6);
- canvas_set_color(canvas, ColorBlack);
-
- // Left
- if(model->left_pressed) {
- canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft);
- bt_hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft);
- canvas_set_color(canvas, ColorBlack);
-
- // Right
- if(model->right_pressed) {
- canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight);
- bt_hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight);
- canvas_set_color(canvas, ColorBlack);
-
- // Ok
- if(model->ok_pressed) {
- canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- bt_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight);
- canvas_draw_line(canvas, 100, 29, 100, 33);
- canvas_draw_line(canvas, 102, 29, 102, 33);
-}
-
-static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* event) {
- with_view_model(
- bt_hid_media->view, (BtHidMediaModel * model) {
- if(event->key == InputKeyUp) {
- model->up_pressed = true;
- furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT);
- } else if(event->key == InputKeyDown) {
- model->down_pressed = true;
- furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT);
- } else if(event->key == InputKeyLeft) {
- model->left_pressed = true;
- furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_PREVIOUS_TRACK);
- } else if(event->key == InputKeyRight) {
- model->right_pressed = true;
- furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_NEXT_TRACK);
- } else if(event->key == InputKeyOk) {
- model->ok_pressed = true;
- furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_PLAY_PAUSE);
- }
- return true;
- });
-}
-
-static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* event) {
- with_view_model(
- bt_hid_media->view, (BtHidMediaModel * model) {
- if(event->key == InputKeyUp) {
- model->up_pressed = false;
- furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT);
- } else if(event->key == InputKeyDown) {
- model->down_pressed = false;
- furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT);
- } else if(event->key == InputKeyLeft) {
- model->left_pressed = false;
- furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_PREVIOUS_TRACK);
- } else if(event->key == InputKeyRight) {
- model->right_pressed = false;
- furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_NEXT_TRACK);
- } else if(event->key == InputKeyOk) {
- model->ok_pressed = false;
- furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_PLAY_PAUSE);
- }
- return true;
- });
-}
-
-static bool bt_hid_media_input_callback(InputEvent* event, void* context) {
- furi_assert(context);
- BtHidMedia* bt_hid_media = context;
- bool consumed = false;
-
- if(event->type == InputTypePress) {
- bt_hid_media_process_press(bt_hid_media, event);
- consumed = true;
- } else if(event->type == InputTypeRelease) {
- bt_hid_media_process_release(bt_hid_media, event);
- consumed = true;
- } else if(event->type == InputTypeShort) {
- if(event->key == InputKeyBack) {
- furi_hal_bt_hid_consumer_key_release_all();
- }
- }
-
- return consumed;
-}
-
-BtHidMedia* bt_hid_media_alloc() {
- BtHidMedia* bt_hid_media = malloc(sizeof(BtHidMedia));
- bt_hid_media->view = view_alloc();
- view_set_context(bt_hid_media->view, bt_hid_media);
- view_allocate_model(bt_hid_media->view, ViewModelTypeLocking, sizeof(BtHidMediaModel));
- view_set_draw_callback(bt_hid_media->view, bt_hid_media_draw_callback);
- view_set_input_callback(bt_hid_media->view, bt_hid_media_input_callback);
-
- return bt_hid_media;
-}
-
-void bt_hid_media_free(BtHidMedia* bt_hid_media) {
- furi_assert(bt_hid_media);
- view_free(bt_hid_media->view);
- free(bt_hid_media);
-}
-
-View* bt_hid_media_get_view(BtHidMedia* bt_hid_media) {
- furi_assert(bt_hid_media);
- return bt_hid_media->view;
-}
-
-void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected) {
- furi_assert(bt_hid_media);
- with_view_model(
- bt_hid_media->view, (BtHidMediaModel * model) {
- model->connected = connected;
- return true;
- });
-}
diff --git a/applications/bt/bt_hid_app/views/bt_hid_media.h b/applications/bt/bt_hid_app/views/bt_hid_media.h
deleted file mode 100644
index 804239dce9b..00000000000
--- a/applications/bt/bt_hid_app/views/bt_hid_media.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-
-#include
-
-typedef struct BtHidMedia BtHidMedia;
-
-BtHidMedia* bt_hid_media_alloc();
-
-void bt_hid_media_free(BtHidMedia* bt_hid_media);
-
-View* bt_hid_media_get_view(BtHidMedia* bt_hid_media);
-
-void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected);
diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.c b/applications/bt/bt_hid_app/views/bt_hid_mouse.c
deleted file mode 100644
index fb1537a2c9d..00000000000
--- a/applications/bt/bt_hid_app/views/bt_hid_mouse.c
+++ /dev/null
@@ -1,207 +0,0 @@
-#include "bt_hid_mouse.h"
-#include
-#include
-#include
-#include
-
-struct BtHidMouse {
- View* view;
-};
-#define MOUSE_MOVE_SHORT 5
-#define MOUSE_MOVE_LONG 20
-
-typedef struct {
- bool left_pressed;
- bool up_pressed;
- bool right_pressed;
- bool down_pressed;
- bool left_mouse_pressed;
- bool left_mouse_held;
- bool right_mouse_pressed;
- bool connected;
-} BtHidMouseModel;
-
-static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
- furi_assert(context);
- BtHidMouseModel* model = context;
-
- // Header
- if(model->connected) {
- canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
- } else {
- canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
- }
- canvas_set_font(canvas, FontPrimary);
- elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse");
- canvas_set_font(canvas, FontSecondary);
-
- if(model->left_mouse_held == true) {
- elements_multiline_text_aligned(canvas, 0, 60, AlignLeft, AlignBottom, "Selecting...");
- }
-
- // Keypad circles
- canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47);
-
- // Up
- if(model->up_pressed) {
- canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9);
- canvas_set_color(canvas, ColorBlack);
-
- // Down
- if(model->down_pressed) {
- canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9);
- canvas_set_color(canvas, ColorBlack);
-
- // Left
- if(model->left_pressed) {
- canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7);
- canvas_set_color(canvas, ColorBlack);
-
- // Right
- if(model->right_pressed) {
- canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7);
- canvas_set_color(canvas, ColorBlack);
-
- // Ok
- if(model->left_mouse_pressed) {
- canvas_draw_icon(canvas, 81, 25, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- canvas_draw_icon(canvas, 83, 27, &I_Ok_btn_9x9);
- canvas_set_color(canvas, ColorBlack);
-
- // Back
- if(model->right_mouse_pressed) {
- canvas_draw_icon(canvas, 108, 48, &I_Pressed_Button_13x13);
- canvas_set_color(canvas, ColorWhite);
- }
- canvas_draw_icon(canvas, 110, 50, &I_Ok_btn_9x9);
-}
-
-static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) {
- with_view_model(
- bt_hid_mouse->view, (BtHidMouseModel * model) {
- if(event->key == InputKeyBack) {
- if(event->type == InputTypeShort) {
- furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_RIGHT);
- furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_RIGHT);
- } else if(event->type == InputTypePress) {
- model->right_mouse_pressed = true;
- } else if(event->type == InputTypeRelease) {
- model->right_mouse_pressed = false;
- }
- } else if(event->key == InputKeyOk) {
- if(event->type == InputTypeShort) {
- // Just release if it was being held before
- if(!model->left_mouse_held) furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
- furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT);
- model->left_mouse_held = false;
- } else if(event->type == InputTypeLong) {
- furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
- model->left_mouse_held = true;
- model->left_mouse_pressed = true;
- } else if(event->type == InputTypePress) {
- model->left_mouse_pressed = true;
- } else if(event->type == InputTypeRelease) {
- // Only release if it wasn't a long press
- if(!model->left_mouse_held) model->left_mouse_pressed = false;
- }
-
- } else if(event->key == InputKeyRight) {
- if(event->type == InputTypePress) {
- model->right_pressed = true;
- furi_hal_bt_hid_mouse_move(MOUSE_MOVE_SHORT, 0);
- } else if(event->type == InputTypeRepeat) {
- furi_hal_bt_hid_mouse_move(MOUSE_MOVE_LONG, 0);
- } else if(event->type == InputTypeRelease) {
- model->right_pressed = false;
- }
- } else if(event->key == InputKeyLeft) {
- if(event->type == InputTypePress) {
- model->left_pressed = true;
- furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_SHORT, 0);
- } else if(event->type == InputTypeRepeat) {
- furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_LONG, 0);
- } else if(event->type == InputTypeRelease) {
- model->left_pressed = false;
- }
- } else if(event->key == InputKeyDown) {
- if(event->type == InputTypePress) {
- model->down_pressed = true;
- furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_SHORT);
- } else if(event->type == InputTypeRepeat) {
- furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_LONG);
- } else if(event->type == InputTypeRelease) {
- model->down_pressed = false;
- }
- } else if(event->key == InputKeyUp) {
- if(event->type == InputTypePress) {
- model->up_pressed = true;
- furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_SHORT);
- } else if(event->type == InputTypeRepeat) {
- furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_LONG);
- } else if(event->type == InputTypeRelease) {
- model->up_pressed = false;
- }
- }
- return true;
- });
-}
-
-static bool bt_hid_mouse_input_callback(InputEvent* event, void* context) {
- furi_assert(context);
- BtHidMouse* bt_hid_mouse = context;
- bool consumed = false;
-
- if(event->type == InputTypeLong && event->key == InputKeyBack) {
- furi_hal_bt_hid_mouse_release_all();
- } else {
- bt_hid_mouse_process(bt_hid_mouse, event);
- consumed = true;
- }
-
- return consumed;
-}
-
-BtHidMouse* bt_hid_mouse_alloc() {
- BtHidMouse* bt_hid_mouse = malloc(sizeof(BtHidMouse));
- bt_hid_mouse->view = view_alloc();
- view_set_context(bt_hid_mouse->view, bt_hid_mouse);
- view_allocate_model(bt_hid_mouse->view, ViewModelTypeLocking, sizeof(BtHidMouseModel));
- view_set_draw_callback(bt_hid_mouse->view, bt_hid_mouse_draw_callback);
- view_set_input_callback(bt_hid_mouse->view, bt_hid_mouse_input_callback);
-
- return bt_hid_mouse;
-}
-
-void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse) {
- furi_assert(bt_hid_mouse);
- view_free(bt_hid_mouse->view);
- free(bt_hid_mouse);
-}
-
-View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse) {
- furi_assert(bt_hid_mouse);
- return bt_hid_mouse->view;
-}
-
-void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected) {
- furi_assert(bt_hid_mouse);
- with_view_model(
- bt_hid_mouse->view, (BtHidMouseModel * model) {
- model->connected = connected;
- return true;
- });
-}
diff --git a/applications/bt/bt_hid_app/views/bt_hid_mouse.h b/applications/bt/bt_hid_app/views/bt_hid_mouse.h
deleted file mode 100644
index a82971d7689..00000000000
--- a/applications/bt/bt_hid_app/views/bt_hid_mouse.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-
-#include
-
-typedef struct BtHidMouse BtHidMouse;
-
-BtHidMouse* bt_hid_mouse_alloc();
-
-void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse);
-
-View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse);
-
-void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected);
diff --git a/applications/bt/bt_service/bt_api.c b/applications/bt/bt_service/bt_api.c
deleted file mode 100644
index 3de896d5a3c..00000000000
--- a/applications/bt/bt_service/bt_api.c
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "bt_i.h"
-
-bool bt_set_profile(Bt* bt, BtProfile profile) {
- furi_assert(bt);
-
- // Send message
- bool result = false;
- BtMessage message = {
- .type = BtMessageTypeSetProfile, .data.profile = profile, .result = &result};
- furi_check(
- furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
- // Wait for unlock
- furi_event_flag_wait(bt->api_event, BT_API_UNLOCK_EVENT, FuriFlagWaitAny, FuriWaitForever);
-
- return result;
-}
-
-void bt_disconnect(Bt* bt) {
- furi_assert(bt);
-
- // Send message
- BtMessage message = {.type = BtMessageTypeDisconnect};
- furi_check(
- furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
- // Wait for unlock
- furi_event_flag_wait(bt->api_event, BT_API_UNLOCK_EVENT, FuriFlagWaitAny, FuriWaitForever);
-}
-
-void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context) {
- furi_assert(bt);
-
- bt->status_changed_cb = callback;
- bt->status_changed_ctx = context;
-}
-
-void bt_forget_bonded_devices(Bt* bt) {
- furi_assert(bt);
- BtMessage message = {.type = BtMessageTypeForgetBondedDevices};
- furi_check(
- furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
-}
diff --git a/applications/bt/bt_service/bt_i.h b/applications/bt/bt_service/bt_i.h
deleted file mode 100644
index a45a36c9eaa..00000000000
--- a/applications/bt/bt_service/bt_i.h
+++ /dev/null
@@ -1,66 +0,0 @@
-#pragma once
-
-#include "bt.h"
-
-#include
-#include
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-#include "../bt_settings.h"
-
-#define BT_API_UNLOCK_EVENT (1UL << 0)
-
-typedef enum {
- BtMessageTypeUpdateStatus,
- BtMessageTypeUpdateBatteryLevel,
- BtMessageTypeUpdatePowerState,
- BtMessageTypePinCodeShow,
- BtMessageTypeKeysStorageUpdated,
- BtMessageTypeSetProfile,
- BtMessageTypeDisconnect,
- BtMessageTypeForgetBondedDevices,
-} BtMessageType;
-
-typedef union {
- uint32_t pin_code;
- uint8_t battery_level;
- BtProfile profile;
-} BtMessageData;
-
-typedef struct {
- BtMessageType type;
- BtMessageData data;
- bool* result;
-} BtMessage;
-
-struct Bt {
- uint8_t* bt_keys_addr_start;
- uint16_t bt_keys_size;
- uint16_t max_packet_size;
- BtSettings bt_settings;
- BtStatus status;
- BtProfile profile;
- FuriMessageQueue* message_queue;
- NotificationApp* notification;
- Gui* gui;
- ViewPort* statusbar_view_port;
- ViewPort* pin_code_view_port;
- uint32_t pin_code;
- DialogsApp* dialogs;
- DialogMessage* dialog_message;
- Power* power;
- Rpc* rpc;
- RpcSession* rpc_session;
- FuriEventFlag* rpc_event;
- FuriEventFlag* api_event;
- BtStatusChangedCallback status_changed_cb;
- void* status_changed_ctx;
-};
diff --git a/applications/bt/bt_service/bt_keys_storage.c b/applications/bt/bt_service/bt_keys_storage.c
deleted file mode 100644
index 91d97d67e98..00000000000
--- a/applications/bt/bt_service/bt_keys_storage.c
+++ /dev/null
@@ -1,57 +0,0 @@
-#include "bt_keys_storage.h"
-
-#include
-#include
-#include
-
-#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME)
-#define BT_KEYS_STORAGE_VERSION (0)
-#define BT_KEYS_STORAGE_MAGIC (0x18)
-
-bool bt_keys_storage_load(Bt* bt) {
- furi_assert(bt);
- bool file_loaded = false;
-
- furi_hal_bt_get_key_storage_buff(&bt->bt_keys_addr_start, &bt->bt_keys_size);
- furi_hal_bt_nvm_sram_sem_acquire();
- file_loaded = saved_struct_load(
- BT_KEYS_STORAGE_PATH,
- bt->bt_keys_addr_start,
- bt->bt_keys_size,
- BT_KEYS_STORAGE_MAGIC,
- BT_KEYS_STORAGE_VERSION);
- furi_hal_bt_nvm_sram_sem_release();
-
- return file_loaded;
-}
-
-bool bt_keys_storage_save(Bt* bt) {
- furi_assert(bt);
- furi_assert(bt->bt_keys_addr_start);
- bool file_saved = false;
-
- furi_hal_bt_nvm_sram_sem_acquire();
- file_saved = saved_struct_save(
- BT_KEYS_STORAGE_PATH,
- bt->bt_keys_addr_start,
- bt->bt_keys_size,
- BT_KEYS_STORAGE_MAGIC,
- BT_KEYS_STORAGE_VERSION);
- furi_hal_bt_nvm_sram_sem_release();
-
- return file_saved;
-}
-
-bool bt_keys_storage_delete(Bt* bt) {
- furi_assert(bt);
- bool delete_succeed = false;
- bool bt_is_active = furi_hal_bt_is_active();
-
- furi_hal_bt_stop_advertising();
- delete_succeed = furi_hal_bt_clear_white_list();
- if(bt_is_active) {
- furi_hal_bt_start_advertising();
- }
-
- return delete_succeed;
-}
diff --git a/applications/bt/bt_service/bt_keys_storage.h b/applications/bt/bt_service/bt_keys_storage.h
deleted file mode 100644
index b82b1035f5c..00000000000
--- a/applications/bt/bt_service/bt_keys_storage.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-#include "bt_i.h"
-#include "bt_keys_filename.h"
-
-bool bt_keys_storage_load(Bt* bt);
-
-bool bt_keys_storage_save(Bt* bt);
-
-bool bt_keys_storage_delete(Bt* bt);
diff --git a/applications/cli/application.fam b/applications/cli/application.fam
deleted file mode 100644
index 73c70aa44ed..00000000000
--- a/applications/cli/application.fam
+++ /dev/null
@@ -1,9 +0,0 @@
-App(
- appid="cli",
- name="CliSrv",
- apptype=FlipperAppType.SERVICE,
- entry_point="cli_srv",
- cdefines=["SRV_CLI"],
- stack_size=4 * 1024,
- order=30,
-)
diff --git a/applications/cli/cli_command_gpio.c b/applications/cli/cli_command_gpio.c
deleted file mode 100644
index d5ec8d65440..00000000000
--- a/applications/cli/cli_command_gpio.c
+++ /dev/null
@@ -1,228 +0,0 @@
-#include "cli_command_gpio.h"
-
-#include
-#include
-#include
-
-typedef struct {
- const GpioPin* pin;
- const char* name;
- const bool debug;
-} CliCommandGpio;
-
-const CliCommandGpio cli_command_gpio_pins[] = {
- {.pin = &gpio_ext_pc0, .name = "PC0", .debug = false},
- {.pin = &gpio_ext_pc1, .name = "PC1", .debug = false},
- {.pin = &gpio_ext_pc3, .name = "PC3", .debug = false},
- {.pin = &gpio_ext_pb2, .name = "PB2", .debug = false},
- {.pin = &gpio_ext_pb3, .name = "PB3", .debug = false},
- {.pin = &gpio_ext_pa4, .name = "PA4", .debug = false},
- {.pin = &gpio_ext_pa6, .name = "PA6", .debug = false},
- {.pin = &gpio_ext_pa7, .name = "PA7", .debug = false},
- /* Dangerous pins, may damage hardware */
- {.pin = &gpio_infrared_rx, .name = "PA0", .debug = true},
- {.pin = &gpio_usart_rx, .name = "PB7", .debug = true},
- {.pin = &gpio_speaker, .name = "PB8", .debug = true},
- {.pin = &gpio_infrared_tx, .name = "PB9", .debug = true},
-};
-
-void cli_command_gpio_print_usage() {
- printf("Usage:\r\n");
- printf("gpio \r\n");
- printf("Cmd list:\r\n");
- printf("\tmode <0|1>\t - Set gpio mode: 0 - input, 1 - output\r\n");
- printf("\tset <0|1>\t - Set gpio value\r\n");
- printf("\tread \t - Read gpio value\r\n");
-}
-
-static bool pin_name_to_int(string_t pin_name, size_t* result) {
- bool found = false;
- bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug);
- for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) {
- if(!string_cmp(pin_name, cli_command_gpio_pins[i].name)) {
- if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) {
- *result = i;
- found = true;
- break;
- }
- }
- }
-
- return found;
-}
-
-static void gpio_print_pins(void) {
- printf("Wrong pin name. Available pins: ");
- bool debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug);
- for(size_t i = 0; i < COUNT_OF(cli_command_gpio_pins); i++) {
- if(!cli_command_gpio_pins[i].debug || (cli_command_gpio_pins[i].debug && debug)) {
- printf("%s ", cli_command_gpio_pins[i].name);
- }
- }
-}
-
-typedef enum { OK, ERR_CMD_SYNTAX, ERR_PIN, ERR_VALUE } GpioParseError;
-
-static GpioParseError gpio_command_parse(string_t args, size_t* pin_num, uint8_t* value) {
- string_t pin_name;
- string_init(pin_name);
-
- size_t ws = string_search_char(args, ' ');
- if(ws == STRING_FAILURE) {
- return ERR_CMD_SYNTAX;
- }
-
- string_set_n(pin_name, args, 0, ws);
- string_right(args, ws);
- string_strim(args);
-
- if(!pin_name_to_int(pin_name, pin_num)) {
- string_clear(pin_name);
- return ERR_PIN;
- }
-
- string_clear(pin_name);
-
- if(!string_cmp(args, "0")) {
- *value = 0;
- } else if(!string_cmp(args, "1")) {
- *value = 1;
- } else {
- return ERR_VALUE;
- }
-
- return OK;
-}
-
-void cli_command_gpio_mode(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(context);
-
- size_t num = 0;
- uint8_t value = 255;
-
- GpioParseError err = gpio_command_parse(args, &num, &value);
-
- if(ERR_CMD_SYNTAX == err) {
- cli_print_usage("gpio mode", " <0|1>", string_get_cstr(args));
- return;
- } else if(ERR_PIN == err) {
- gpio_print_pins();
- return;
- } else if(ERR_VALUE == err) {
- printf("Value is invalid. Enter 1 for input or 0 for output");
- return;
- }
-
- if(cli_command_gpio_pins[num].debug) {
- printf(
- "Changeing this pin mode may damage hardware. Are you sure you want to continue? (y/n)?\r\n");
- char c = cli_getc(cli);
- if(c != 'y' && c != 'Y') {
- printf("Cancelled.\r\n");
- return;
- }
- }
-
- if(value == 1) { // output
- furi_hal_gpio_write(cli_command_gpio_pins[num].pin, false);
- furi_hal_gpio_init_simple(cli_command_gpio_pins[num].pin, GpioModeOutputPushPull);
- printf("Pin %s is now an output (low)", cli_command_gpio_pins[num].name);
- } else { // input
- furi_hal_gpio_init_simple(cli_command_gpio_pins[num].pin, GpioModeInput);
- printf("Pin %s is now an input", cli_command_gpio_pins[num].name);
- }
-}
-
-void cli_command_gpio_read(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(context);
-
- size_t num = 0;
- if(!pin_name_to_int(args, &num)) {
- gpio_print_pins();
- return;
- }
-
- if(LL_GPIO_MODE_INPUT !=
- LL_GPIO_GetPinMode(
- cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) {
- printf("Err: pin %s is not set as an input.", cli_command_gpio_pins[num].name);
- return;
- }
-
- uint8_t val = !!furi_hal_gpio_read(cli_command_gpio_pins[num].pin);
-
- printf("Pin %s <= %u", cli_command_gpio_pins[num].name, val);
-}
-
-void cli_command_gpio_set(Cli* cli, string_t args, void* context) {
- UNUSED(context);
-
- size_t num = 0;
- uint8_t value = 0;
- GpioParseError err = gpio_command_parse(args, &num, &value);
-
- if(ERR_CMD_SYNTAX == err) {
- cli_print_usage("gpio set", " <0|1>", string_get_cstr(args));
- return;
- } else if(ERR_PIN == err) {
- gpio_print_pins();
- return;
- } else if(ERR_VALUE == err) {
- printf("Value is invalid. Enter 1 for high or 0 for low");
- return;
- }
-
- if(LL_GPIO_MODE_OUTPUT !=
- LL_GPIO_GetPinMode(
- cli_command_gpio_pins[num].pin->port, cli_command_gpio_pins[num].pin->pin)) {
- printf("Err: pin %s is not set as an output.", cli_command_gpio_pins[num].name);
- return;
- }
-
- // Extra check if debug pins used
- if(cli_command_gpio_pins[num].debug) {
- printf(
- "Setting this pin may damage hardware. Are you sure you want to continue? (y/n)?\r\n");
- char c = cli_getc(cli);
- if(c != 'y' && c != 'Y') {
- printf("Cancelled.\r\n");
- return;
- }
- }
-
- furi_hal_gpio_write(cli_command_gpio_pins[num].pin, !!value);
- printf("Pin %s => %u", cli_command_gpio_pins[num].name, !!value);
-}
-
-void cli_command_gpio(Cli* cli, string_t args, void* context) {
- string_t cmd;
- string_init(cmd);
-
- do {
- if(!args_read_string_and_trim(args, cmd)) {
- cli_command_gpio_print_usage();
- break;
- }
-
- if(string_cmp_str(cmd, "mode") == 0) {
- cli_command_gpio_mode(cli, args, context);
- break;
- }
-
- if(string_cmp_str(cmd, "set") == 0) {
- cli_command_gpio_set(cli, args, context);
- break;
- }
-
- if(string_cmp_str(cmd, "read") == 0) {
- cli_command_gpio_read(cli, args, context);
- break;
- }
-
- cli_command_gpio_print_usage();
- } while(false);
-
- string_clear(cmd);
-}
diff --git a/applications/cli/cli_command_gpio.h b/applications/cli/cli_command_gpio.h
deleted file mode 100644
index c9b908a0853..00000000000
--- a/applications/cli/cli_command_gpio.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-
-#include "cli_i.h"
-
-void cli_command_gpio(Cli* cli, string_t args, void* context);
diff --git a/applications/cli/cli_commands.c b/applications/cli/cli_commands.c
deleted file mode 100644
index 177a274a106..00000000000
--- a/applications/cli/cli_commands.c
+++ /dev/null
@@ -1,335 +0,0 @@
-#include "cli_commands.h"
-#include "cli_command_gpio.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
-#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d"
-
-void cli_command_device_info_callback(const char* key, const char* value, bool last, void* context) {
- UNUSED(context);
- UNUSED(last);
- printf("%-30s: %s\r\n", key, value);
-}
-
-/*
- * Device Info Command
- * This command is intended to be used by humans
- */
-void cli_command_device_info(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(args);
- furi_hal_info_get(cli_command_device_info_callback, context);
-}
-
-void cli_command_help(Cli* cli, string_t args, void* context) {
- UNUSED(args);
- UNUSED(context);
- printf("Commands we have:");
-
- // Command count
- const size_t commands_count = CliCommandTree_size(cli->commands);
- const size_t commands_count_mid = commands_count / 2 + commands_count % 2;
-
- // Use 2 iterators from start and middle to show 2 columns
- CliCommandTree_it_t it_left;
- CliCommandTree_it(it_left, cli->commands);
- CliCommandTree_it_t it_right;
- CliCommandTree_it(it_right, cli->commands);
- for(size_t i = 0; i < commands_count_mid; i++) CliCommandTree_next(it_right);
-
- // Iterate throw tree
- for(size_t i = 0; i < commands_count_mid; i++) {
- printf("\r\n");
- // Left Column
- if(!CliCommandTree_end_p(it_left)) {
- printf("%-30s", string_get_cstr(*CliCommandTree_ref(it_left)->key_ptr));
- CliCommandTree_next(it_left);
- }
- // Right Column
- if(!CliCommandTree_end_p(it_right)) {
- printf("%s", string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr));
- CliCommandTree_next(it_right);
- }
- };
-
- if(string_size(args) > 0) {
- cli_nl();
- printf("Also I have no clue what '");
- printf("%s", string_get_cstr(args));
- printf("' is.");
- }
-}
-
-void cli_command_date(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(context);
-
- FuriHalRtcDateTime datetime = {0};
-
- if(string_size(args) > 0) {
- uint16_t hours, minutes, seconds, month, day, year, weekday;
- int ret = sscanf(
- string_get_cstr(args),
- "%hu-%hu-%hu %hu:%hu:%hu %hu",
- &year,
- &month,
- &day,
- &hours,
- &minutes,
- &seconds,
- &weekday);
-
- // Some variables are going to discard upper byte
- // There will be some funky behaviour which is not breaking anything
- datetime.hour = hours;
- datetime.minute = minutes;
- datetime.second = seconds;
- datetime.weekday = weekday;
- datetime.month = month;
- datetime.day = day;
- datetime.year = year;
-
- if(ret != 7) {
- printf(
- "Invalid datetime format, use `%s`. sscanf %d %s",
- "%Y-%m-%d %H:%M:%S %u",
- ret,
- string_get_cstr(args));
- return;
- }
-
- if(!furi_hal_rtc_validate_datetime(&datetime)) {
- printf("Invalid datetime data");
- return;
- }
-
- furi_hal_rtc_set_datetime(&datetime);
- // Verification
- furi_hal_rtc_get_datetime(&datetime);
- printf(
- "New datetime is: " CLI_DATE_FORMAT,
- datetime.year,
- datetime.month,
- datetime.day,
- datetime.hour,
- datetime.minute,
- datetime.second,
- datetime.weekday);
- } else {
- furi_hal_rtc_get_datetime(&datetime);
- printf(
- CLI_DATE_FORMAT,
- datetime.year,
- datetime.month,
- datetime.day,
- datetime.hour,
- datetime.minute,
- datetime.second,
- datetime.weekday);
- }
-}
-
-#define CLI_COMMAND_LOG_RING_SIZE 2048
-#define CLI_COMMAND_LOG_BUFFER_SIZE 64
-
-void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* context) {
- xStreamBufferSend(context, buffer, size, 0);
-}
-
-void cli_command_log(Cli* cli, string_t args, void* context) {
- UNUSED(args);
- UNUSED(context);
- StreamBufferHandle_t ring = xStreamBufferCreate(CLI_COMMAND_LOG_RING_SIZE, 1);
- uint8_t buffer[CLI_COMMAND_LOG_BUFFER_SIZE];
-
- furi_hal_console_set_tx_callback(cli_command_log_tx_callback, ring);
-
- printf("Press CTRL+C to stop...\r\n");
- while(!cli_cmd_interrupt_received(cli)) {
- size_t ret = xStreamBufferReceive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50);
- cli_write(cli, buffer, ret);
- }
-
- furi_hal_console_set_tx_callback(NULL, NULL);
-
- vStreamBufferDelete(ring);
-}
-
-void cli_command_vibro(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(context);
- if(!string_cmp(args, "0")) {
- NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
- notification_message_block(notification, &sequence_reset_vibro);
- furi_record_close(RECORD_NOTIFICATION);
- } else if(!string_cmp(args, "1")) {
- NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
- notification_message_block(notification, &sequence_set_vibro_on);
- furi_record_close(RECORD_NOTIFICATION);
- } else {
- cli_print_usage("vibro", "<1|0>", string_get_cstr(args));
- }
-}
-
-void cli_command_debug(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(context);
- if(!string_cmp(args, "0")) {
- furi_hal_rtc_reset_flag(FuriHalRtcFlagDebug);
- loader_update_menu();
- printf("Debug disabled.");
- } else if(!string_cmp(args, "1")) {
- furi_hal_rtc_set_flag(FuriHalRtcFlagDebug);
- loader_update_menu();
- printf("Debug enabled.");
- } else {
- cli_print_usage("debug", "<1|0>", string_get_cstr(args));
- }
-}
-
-void cli_command_led(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(context);
- // Get first word as light name
- NotificationMessage notification_led_message;
- string_t light_name;
- string_init(light_name);
- size_t ws = string_search_char(args, ' ');
- if(ws == STRING_FAILURE) {
- cli_print_usage("led", " <0-255>", string_get_cstr(args));
- string_clear(light_name);
- return;
- } else {
- string_set_n(light_name, args, 0, ws);
- string_right(args, ws);
- string_strim(args);
- }
- // Check light name
- if(!string_cmp(light_name, "r")) {
- notification_led_message.type = NotificationMessageTypeLedRed;
- } else if(!string_cmp(light_name, "g")) {
- notification_led_message.type = NotificationMessageTypeLedGreen;
- } else if(!string_cmp(light_name, "b")) {
- notification_led_message.type = NotificationMessageTypeLedBlue;
- } else if(!string_cmp(light_name, "bl")) {
- notification_led_message.type = NotificationMessageTypeLedDisplayBacklight;
- } else {
- cli_print_usage("led", " <0-255>", string_get_cstr(args));
- string_clear(light_name);
- return;
- }
- string_clear(light_name);
- // Read light value from the rest of the string
- char* end_ptr;
- uint32_t value = strtoul(string_get_cstr(args), &end_ptr, 0);
- if(!(value < 256 && *end_ptr == '\0')) {
- cli_print_usage("led", " <0-255>", string_get_cstr(args));
- return;
- }
-
- // Set led value
- notification_led_message.data.led.value = value;
-
- // Form notification sequence
- const NotificationSequence notification_sequence = {
- ¬ification_led_message,
- NULL,
- };
-
- // Send notification
- NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
- notification_internal_message_block(notification, ¬ification_sequence);
- furi_record_close(RECORD_NOTIFICATION);
-}
-
-void cli_command_ps(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(args);
- UNUSED(context);
-
- const uint8_t threads_num_max = 32;
- FuriThreadId threads_ids[threads_num_max];
- uint8_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max);
- printf(
- "%-20s %-14s %-8s %-8s %s\r\n", "Name", "Stack start", "Heap", "Stack", "Stack min free");
- for(uint8_t i = 0; i < thread_num; i++) {
- TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i];
- printf(
- "%-20s 0x%-12lx %-8d %-8ld %-8ld\r\n",
- furi_thread_get_name(threads_ids[i]),
- (uint32_t)tcb->pxStack,
- memmgr_heap_get_thread_memory(threads_ids[i]),
- (uint32_t)(tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t),
- furi_thread_get_stack_space(threads_ids[i]));
- }
- printf("\r\nTotal: %d", thread_num);
-}
-
-void cli_command_free(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(args);
- UNUSED(context);
-
- printf("Free heap size: %d\r\n", memmgr_get_free_heap());
- printf("Total heap size: %d\r\n", memmgr_get_total_heap());
- printf("Minimum heap size: %d\r\n", memmgr_get_minimum_free_heap());
- printf("Maximum heap block: %d\r\n", memmgr_heap_get_max_free_block());
-}
-
-void cli_command_free_blocks(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(args);
- UNUSED(context);
-
- memmgr_heap_printf_free_blocks();
-}
-
-void cli_command_i2c(Cli* cli, string_t args, void* context) {
- UNUSED(cli);
- UNUSED(args);
- UNUSED(context);
-
- furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
- printf("Scanning external i2c on PC0(SCL)/PC1(SDA)\r\n"
- "Clock: 100khz, 7bit address\r\n"
- "\r\n");
- printf(" | 0 1 2 3 4 5 6 7 8 9 A B C D E F\r\n");
- printf("--+--------------------------------\r\n");
- for(uint8_t row = 0; row < 0x8; row++) {
- printf("%x | ", row);
- for(uint8_t column = 0; column <= 0xF; column++) {
- bool ret = furi_hal_i2c_is_device_ready(
- &furi_hal_i2c_handle_external, ((row << 4) + column) << 1, 2);
- printf("%c ", ret ? '#' : '-');
- }
- printf("\r\n");
- }
- furi_hal_i2c_release(&furi_hal_i2c_handle_external);
-}
-
-void cli_commands_init(Cli* cli) {
- cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_device_info, NULL);
- cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_device_info, NULL);
-
- cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
- cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
-
- cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
- cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
- cli_add_command(cli, "debug", CliCommandFlagDefault, cli_command_debug, NULL);
- cli_add_command(cli, "ps", CliCommandFlagParallelSafe, cli_command_ps, NULL);
- cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
- cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
-
- cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
- cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL);
- cli_add_command(cli, "gpio", CliCommandFlagDefault, cli_command_gpio, NULL);
- cli_add_command(cli, "i2c", CliCommandFlagDefault, cli_command_i2c, NULL);
-}
diff --git a/applications/cli/cli_vcp.c b/applications/cli/cli_vcp.c
deleted file mode 100644
index 5a8b44dcf30..00000000000
--- a/applications/cli/cli_vcp.c
+++ /dev/null
@@ -1,326 +0,0 @@
-#include
-#include
-#include
-#include
-#include "cli_i.h"
-
-#define TAG "CliVcp"
-
-#define USB_CDC_PKT_LEN CDC_DATA_SZ
-#define VCP_RX_BUF_SIZE (USB_CDC_PKT_LEN * 3)
-#define VCP_TX_BUF_SIZE (USB_CDC_PKT_LEN * 3)
-
-#define VCP_IF_NUM 0
-
-typedef enum {
- VcpEvtStop = (1 << 0),
- VcpEvtConnect = (1 << 1),
- VcpEvtDisconnect = (1 << 2),
- VcpEvtStreamRx = (1 << 3),
- VcpEvtRx = (1 << 4),
- VcpEvtStreamTx = (1 << 5),
- VcpEvtTx = (1 << 6),
-} WorkerEvtFlags;
-
-#define VCP_THREAD_FLAG_ALL \
- (VcpEvtStop | VcpEvtConnect | VcpEvtDisconnect | VcpEvtRx | VcpEvtTx | VcpEvtStreamRx | \
- VcpEvtStreamTx)
-
-typedef struct {
- FuriThread* thread;
-
- StreamBufferHandle_t tx_stream;
- StreamBufferHandle_t rx_stream;
-
- volatile bool connected;
- volatile bool running;
-
- FuriHalUsbInterface* usb_if_prev;
-
- uint8_t data_buffer[USB_CDC_PKT_LEN];
-} CliVcp;
-
-static int32_t vcp_worker(void* context);
-static void vcp_on_cdc_tx_complete(void* context);
-static void vcp_on_cdc_rx(void* context);
-static void vcp_state_callback(void* context, uint8_t state);
-static void vcp_on_cdc_control_line(void* context, uint8_t state);
-
-static CdcCallbacks cdc_cb = {
- vcp_on_cdc_tx_complete,
- vcp_on_cdc_rx,
- vcp_state_callback,
- vcp_on_cdc_control_line,
- NULL,
-};
-
-static CliVcp* vcp = NULL;
-
-static const uint8_t ascii_soh = 0x01;
-static const uint8_t ascii_eot = 0x04;
-
-static void cli_vcp_init() {
- if(vcp == NULL) {
- vcp = malloc(sizeof(CliVcp));
- vcp->tx_stream = xStreamBufferCreate(VCP_TX_BUF_SIZE, 1);
- vcp->rx_stream = xStreamBufferCreate(VCP_RX_BUF_SIZE, 1);
- }
- furi_assert(vcp->thread == NULL);
-
- vcp->connected = false;
-
- vcp->thread = furi_thread_alloc();
- furi_thread_set_name(vcp->thread, "CliVcpWorker");
- furi_thread_set_stack_size(vcp->thread, 1024);
- furi_thread_set_callback(vcp->thread, vcp_worker);
- furi_thread_start(vcp->thread);
-
- FURI_LOG_I(TAG, "Init OK");
-}
-
-static void cli_vcp_deinit() {
- furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStop);
- furi_thread_join(vcp->thread);
- furi_thread_free(vcp->thread);
- vcp->thread = NULL;
-}
-
-static int32_t vcp_worker(void* context) {
- UNUSED(context);
- bool tx_idle = true;
- size_t missed_rx = 0;
- uint8_t last_tx_pkt_len = 0;
-
- // Switch USB to VCP mode (if it is not set yet)
- vcp->usb_if_prev = furi_hal_usb_get_config();
- if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) {
- furi_hal_usb_set_config(&usb_cdc_single, NULL);
- }
- furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb, NULL);
-
- FURI_LOG_D(TAG, "Start");
- vcp->running = true;
-
- while(1) {
- uint32_t flags =
- furi_thread_flags_wait(VCP_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
- furi_assert((flags & FuriFlagError) == 0);
-
- // VCP session opened
- if(flags & VcpEvtConnect) {
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "Connect");
-#endif
- if(vcp->connected == false) {
- vcp->connected = true;
- xStreamBufferSend(vcp->rx_stream, &ascii_soh, 1, FuriWaitForever);
- }
- }
-
- // VCP session closed
- if(flags & VcpEvtDisconnect) {
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "Disconnect");
-#endif
- if(vcp->connected == true) {
- vcp->connected = false;
- xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
- xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever);
- }
- }
-
- // Rx buffer was read, maybe there is enough space for new data?
- if((flags & VcpEvtStreamRx) && (missed_rx > 0)) {
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "StreamRx");
-#endif
- if(xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
- flags |= VcpEvtRx;
- missed_rx--;
- }
- }
-
- // New data received
- if(flags & VcpEvtRx) {
- if(xStreamBufferSpacesAvailable(vcp->rx_stream) >= USB_CDC_PKT_LEN) {
- int32_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->data_buffer, USB_CDC_PKT_LEN);
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "Rx %d", len);
-#endif
- if(len > 0) {
- furi_check(
- xStreamBufferSend(vcp->rx_stream, vcp->data_buffer, len, FuriWaitForever) ==
- (size_t)len);
- }
- } else {
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "Rx missed");
-#endif
- missed_rx++;
- }
- }
-
- // New data in Tx buffer
- if(flags & VcpEvtStreamTx) {
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "StreamTx");
-#endif
- if(tx_idle) {
- flags |= VcpEvtTx;
- }
- }
-
- // CDC write transfer done
- if(flags & VcpEvtTx) {
- size_t len =
- xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "Tx %d", len);
-#endif
- if(len > 0) { // Some data left in Tx buffer. Sending it now
- tx_idle = false;
- furi_hal_cdc_send(VCP_IF_NUM, vcp->data_buffer, len);
- last_tx_pkt_len = len;
- } else { // There is nothing to send.
- if(last_tx_pkt_len == 64) {
- // Send extra zero-length packet if last packet len is 64 to indicate transfer end
- furi_hal_cdc_send(VCP_IF_NUM, NULL, 0);
- } else {
- // Set flag to start next transfer instantly
- tx_idle = true;
- }
- last_tx_pkt_len = 0;
- }
- }
-
- if(flags & VcpEvtStop) {
- vcp->connected = false;
- vcp->running = false;
- furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL, NULL);
- // Restore previous USB mode (if it was set during init)
- if((vcp->usb_if_prev != &usb_cdc_single) && (vcp->usb_if_prev != &usb_cdc_dual)) {
- furi_hal_usb_unlock();
- furi_hal_usb_set_config(vcp->usb_if_prev, NULL);
- }
- xStreamBufferReceive(vcp->tx_stream, vcp->data_buffer, USB_CDC_PKT_LEN, 0);
- xStreamBufferSend(vcp->rx_stream, &ascii_eot, 1, FuriWaitForever);
- break;
- }
- }
- FURI_LOG_D(TAG, "End");
- return 0;
-}
-
-static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) {
- furi_assert(vcp);
- furi_assert(buffer);
-
- if(vcp->running == false) {
- return 0;
- }
-
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "rx %u start", size);
-#endif
-
- size_t rx_cnt = 0;
-
- while(size > 0) {
- size_t batch_size = size;
- if(batch_size > VCP_RX_BUF_SIZE) batch_size = VCP_RX_BUF_SIZE;
-
- size_t len = xStreamBufferReceive(vcp->rx_stream, buffer, batch_size, timeout);
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "rx %u ", batch_size);
-#endif
- if(len == 0) break;
- furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamRx);
- size -= len;
- buffer += len;
- rx_cnt += len;
- }
-
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "rx %u end", size);
-#endif
- return rx_cnt;
-}
-
-static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
- furi_assert(vcp);
- furi_assert(buffer);
-
- if(vcp->running == false) {
- return;
- }
-
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "tx %u start", size);
-#endif
-
- while(size > 0 && vcp->connected) {
- size_t batch_size = size;
- if(batch_size > USB_CDC_PKT_LEN) batch_size = USB_CDC_PKT_LEN;
-
- xStreamBufferSend(vcp->tx_stream, buffer, batch_size, FuriWaitForever);
- furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtStreamTx);
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "tx %u", batch_size);
-#endif
-
- size -= batch_size;
- buffer += batch_size;
- }
-
-#ifdef CLI_VCP_DEBUG
- FURI_LOG_D(TAG, "tx %u end", size);
-#endif
-}
-
-static void cli_vcp_tx_stdout(const char* data, size_t size) {
- cli_vcp_tx((const uint8_t*)data, size);
-}
-
-static void vcp_state_callback(void* context, uint8_t state) {
- UNUSED(context);
- if(state == 0) {
- furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect);
- }
-}
-
-static void vcp_on_cdc_control_line(void* context, uint8_t state) {
- UNUSED(context);
- // bit 0: DTR state, bit 1: RTS state
- bool dtr = state & (1 << 0);
-
- if(dtr == true) {
- furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtConnect);
- } else {
- furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtDisconnect);
- }
-}
-
-static void vcp_on_cdc_rx(void* context) {
- UNUSED(context);
- uint32_t ret = furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtRx);
- furi_check((ret & FuriFlagError) == 0);
-}
-
-static void vcp_on_cdc_tx_complete(void* context) {
- UNUSED(context);
- furi_thread_flags_set(furi_thread_get_id(vcp->thread), VcpEvtTx);
-}
-
-static bool cli_vcp_is_connected(void) {
- furi_assert(vcp);
- return vcp->connected;
-}
-
-CliSession cli_vcp = {
- cli_vcp_init,
- cli_vcp_deinit,
- cli_vcp_rx,
- cli_vcp_tx,
- cli_vcp_tx_stdout,
- cli_vcp_is_connected,
-};
diff --git a/applications/accessor/accessor.cpp b/applications/debug/accessor/accessor.cpp
similarity index 100%
rename from applications/accessor/accessor.cpp
rename to applications/debug/accessor/accessor.cpp
diff --git a/applications/accessor/accessor_app.cpp b/applications/debug/accessor/accessor_app.cpp
similarity index 97%
rename from applications/accessor/accessor_app.cpp
rename to applications/debug/accessor/accessor_app.cpp
index 2e3e27ec456..2e40b3c35d7 100644
--- a/applications/accessor/accessor_app.cpp
+++ b/applications/debug/accessor/accessor_app.cpp
@@ -31,9 +31,10 @@ void AccessorApp::run(void) {
onewire_host_stop(onewire_host);
}
-AccessorApp::AccessorApp() {
+AccessorApp::AccessorApp()
+ : text_store{0} {
notification = static_cast(furi_record_open(RECORD_NOTIFICATION));
- onewire_host = onewire_host_alloc();
+ onewire_host = onewire_host_alloc(&gpio_ibutton);
furi_hal_power_enable_otg();
}
diff --git a/applications/accessor/accessor_app.h b/applications/debug/accessor/accessor_app.h
similarity index 100%
rename from applications/accessor/accessor_app.h
rename to applications/debug/accessor/accessor_app.h
diff --git a/applications/accessor/accessor_event.h b/applications/debug/accessor/accessor_event.h
similarity index 100%
rename from applications/accessor/accessor_event.h
rename to applications/debug/accessor/accessor_event.h
diff --git a/applications/accessor/accessor_view_manager.cpp b/applications/debug/accessor/accessor_view_manager.cpp
similarity index 100%
rename from applications/accessor/accessor_view_manager.cpp
rename to applications/debug/accessor/accessor_view_manager.cpp
diff --git a/applications/accessor/accessor_view_manager.h b/applications/debug/accessor/accessor_view_manager.h
similarity index 100%
rename from applications/accessor/accessor_view_manager.h
rename to applications/debug/accessor/accessor_view_manager.h
diff --git a/applications/debug/accessor/application.fam b/applications/debug/accessor/application.fam
new file mode 100644
index 00000000000..65a6c86663a
--- /dev/null
+++ b/applications/debug/accessor/application.fam
@@ -0,0 +1,11 @@
+App(
+ appid="accessor",
+ name="Accessor",
+ apptype=FlipperAppType.DEBUG,
+ targets=["f7"],
+ entry_point="accessor_app",
+ requires=["gui"],
+ stack_size=4 * 1024,
+ order=40,
+ fap_category="Debug",
+)
diff --git a/applications/accessor/helpers/wiegand.cpp b/applications/debug/accessor/helpers/wiegand.cpp
similarity index 96%
rename from applications/accessor/helpers/wiegand.cpp
rename to applications/debug/accessor/helpers/wiegand.cpp
index 79c9f723b2c..10b284eaacd 100644
--- a/applications/accessor/helpers/wiegand.cpp
+++ b/applications/debug/accessor/helpers/wiegand.cpp
@@ -71,7 +71,7 @@ void WIEGAND::end() {
}
void WIEGAND::ReadD0() {
- _bitCount++; // Increament bit count for Interrupt connected to D0
+ _bitCount++; // Increment bit count for Interrupt connected to D0
if(_bitCount > 31) // If bit count more than 31, process high bits
{
_cardTempHigh |= ((0x80000000 & _cardTemp) >> 31); // shift value to high bits
@@ -171,13 +171,10 @@ bool WIEGAND::DoWiegandConversion() {
return true;
} else {
_lastWiegand = sysTick;
- _bitCount = 0;
- _cardTemp = 0;
- _cardTempHigh = 0;
return false;
}
- // TODO: Handle validation failure case!
+ // TODO FL-3490: Handle validation failure case!
} else if(4 == _bitCount) {
// 4-bit Wiegand codes have no data integrity check so we just
// read the LOW nibble.
diff --git a/applications/accessor/helpers/wiegand.h b/applications/debug/accessor/helpers/wiegand.h
similarity index 100%
rename from applications/accessor/helpers/wiegand.h
rename to applications/debug/accessor/helpers/wiegand.h
diff --git a/applications/accessor/scene/accessor_scene_generic.h b/applications/debug/accessor/scene/accessor_scene_generic.h
similarity index 100%
rename from applications/accessor/scene/accessor_scene_generic.h
rename to applications/debug/accessor/scene/accessor_scene_generic.h
diff --git a/applications/accessor/scene/accessor_scene_start.cpp b/applications/debug/accessor/scene/accessor_scene_start.cpp
similarity index 100%
rename from applications/accessor/scene/accessor_scene_start.cpp
rename to applications/debug/accessor/scene/accessor_scene_start.cpp
diff --git a/applications/accessor/scene/accessor_scene_start.h b/applications/debug/accessor/scene/accessor_scene_start.h
similarity index 100%
rename from applications/accessor/scene/accessor_scene_start.h
rename to applications/debug/accessor/scene/accessor_scene_start.h
diff --git a/applications/debug/application.fam b/applications/debug/application.fam
new file mode 100644
index 00000000000..cdbf8fe18b6
--- /dev/null
+++ b/applications/debug/application.fam
@@ -0,0 +1,17 @@
+App(
+ appid="debug_apps",
+ name="Basic debug apps bundle",
+ apptype=FlipperAppType.METAPACKAGE,
+ provides=[
+ "blink_test",
+ "vibro_test",
+ "keypad_test",
+ "usb_test",
+ "usb_mouse",
+ "uart_echo",
+ "display_test",
+ "text_box_test",
+ "file_browser_test",
+ "speaker_debug",
+ ],
+)
diff --git a/applications/debug/battery_test_app/application.fam b/applications/debug/battery_test_app/application.fam
new file mode 100644
index 00000000000..5f4acd83d5f
--- /dev/null
+++ b/applications/debug/battery_test_app/application.fam
@@ -0,0 +1,14 @@
+App(
+ appid="battery_test",
+ name="Battery Test",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="battery_test_app",
+ requires=[
+ "gui",
+ "power",
+ ],
+ stack_size=1 * 1024,
+ order=130,
+ fap_category="Debug",
+ fap_libs=["assets"],
+)
diff --git a/applications/power/battery_test_app/battery_test_app.c b/applications/debug/battery_test_app/battery_test_app.c
old mode 100755
new mode 100644
similarity index 92%
rename from applications/power/battery_test_app/battery_test_app.c
rename to applications/debug/battery_test_app/battery_test_app.c
index ab6889dc8df..eabf3c04b44
--- a/applications/power/battery_test_app/battery_test_app.c
+++ b/applications/debug/battery_test_app/battery_test_app.c
@@ -27,7 +27,7 @@ static void battery_test_battery_info_update_model(void* context) {
.charge = app->info.charge,
.health = app->info.health,
};
- battery_info_set_data(app->batery_info, &battery_info_data);
+ battery_info_set_data(app->battery_info, &battery_info_data);
notification_message(app->notifications, &sequence_display_backlight_on);
}
@@ -48,13 +48,13 @@ BatteryTestApp* battery_test_alloc() {
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
// Views
- app->batery_info = battery_info_alloc();
+ app->battery_info = battery_info_alloc();
view_set_previous_callback(
- battery_info_get_view(app->batery_info), battery_test_exit_confirm_view);
+ battery_info_get_view(app->battery_info), battery_test_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher,
BatteryTestAppViewBatteryInfo,
- battery_info_get_view(app->batery_info));
+ battery_info_get_view(app->battery_info));
app->dialog = dialog_ex_alloc();
dialog_ex_set_header(app->dialog, "Close Battery Test?", 64, 12, AlignCenter, AlignTop);
@@ -76,7 +76,7 @@ void battery_test_free(BatteryTestApp* app) {
// Views
view_dispatcher_remove_view(app->view_dispatcher, BatteryTestAppViewBatteryInfo);
- battery_info_free(app->batery_info);
+ battery_info_free(app->battery_info);
view_dispatcher_remove_view(app->view_dispatcher, BatteryTestAppViewExitDialog);
dialog_ex_free(app->dialog);
// View dispatcher
diff --git a/applications/power/battery_test_app/battery_test_app.h b/applications/debug/battery_test_app/battery_test_app.h
similarity index 82%
rename from applications/power/battery_test_app/battery_test_app.h
rename to applications/debug/battery_test_app/battery_test_app.h
index 9c17626da92..497cdde823f 100644
--- a/applications/power/battery_test_app/battery_test_app.h
+++ b/applications/debug/battery_test_app/battery_test_app.h
@@ -6,14 +6,15 @@
#include
#include
-#include
+// FIXME
+#include "../settings/power_settings_app/views/battery_info.h"
typedef struct {
Power* power;
Gui* gui;
NotificationApp* notifications;
ViewDispatcher* view_dispatcher;
- BatteryInfo* batery_info;
+ BatteryInfo* battery_info;
DialogEx* dialog;
PowerInfo info;
} BatteryTestApp;
diff --git a/applications/debug/battery_test_app/views/battery_info.c b/applications/debug/battery_test_app/views/battery_info.c
new file mode 100644
index 00000000000..5353a2e2a67
--- /dev/null
+++ b/applications/debug/battery_test_app/views/battery_info.c
@@ -0,0 +1,148 @@
+#include "battery_info.h"
+#include
+#include
+#include
+
+#define LOW_CHARGE_THRESHOLD 10
+#define HIGH_DRAIN_CURRENT_THRESHOLD 100
+
+struct BatteryInfo {
+ View* view;
+};
+
+static void draw_stat(Canvas* canvas, int x, int y, const Icon* icon, char* val) {
+ canvas_draw_frame(canvas, x - 7, y + 7, 30, 13);
+ canvas_draw_icon(canvas, x, y, icon);
+ canvas_set_color(canvas, ColorWhite);
+ canvas_draw_box(canvas, x - 4, y + 16, 24, 6);
+ canvas_set_color(canvas, ColorBlack);
+ canvas_draw_str_aligned(canvas, x + 8, y + 22, AlignCenter, AlignBottom, val);
+};
+
+static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
+ char emote[20] = {};
+ char header[20] = {};
+ char value[20] = {};
+
+ int32_t drain_current = data->gauge_current * (-1000);
+ uint32_t charge_current = data->gauge_current * 1000;
+
+ // Draw battery
+ canvas_draw_icon(canvas, x, y, &I_BatteryBody_52x28);
+ if(charge_current > 0) {
+ canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceCharging_29x14);
+ } else if(drain_current > HIGH_DRAIN_CURRENT_THRESHOLD) {
+ canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceConfused_29x14);
+ } else if(data->charge < LOW_CHARGE_THRESHOLD) {
+ canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNopower_29x14);
+ } else {
+ canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNormal_29x14);
+ }
+
+ // Draw bubble
+ elements_bubble(canvas, 53, 0, 71, 39);
+
+ // Set text
+ if(charge_current > 0) {
+ snprintf(emote, sizeof(emote), "%s", "Yummy!");
+ snprintf(header, sizeof(header), "%s", "Charging at");
+ snprintf(
+ value,
+ sizeof(value),
+ "%lu.%luV %lumA",
+ (uint32_t)(data->vbus_voltage),
+ (uint32_t)(data->vbus_voltage * 10) % 10,
+ charge_current);
+ } else if(drain_current > 0) {
+ snprintf(
+ emote,
+ sizeof(emote),
+ "%s",
+ drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "Oh no!" : "Om-nom-nom!");
+ snprintf(header, sizeof(header), "%s", "Consumption is");
+ snprintf(
+ value,
+ sizeof(value),
+ "%ld %s",
+ drain_current,
+ drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA");
+ } else if(drain_current != 0) {
+ snprintf(header, 20, "...");
+ } else if(data->charging_voltage < 4.2) {
+ // Non-default battery charging limit, mention it
+ snprintf(emote, sizeof(emote), "Charged!");
+ snprintf(header, sizeof(header), "Limited to");
+ snprintf(
+ value,
+ sizeof(value),
+ "%lu.%luV",
+ (uint32_t)(data->charging_voltage),
+ (uint32_t)(data->charging_voltage * 10) % 10);
+ } else {
+ snprintf(header, sizeof(header), "Charged!");
+ }
+
+ canvas_draw_str_aligned(canvas, 92, y + 3, AlignCenter, AlignCenter, emote);
+ canvas_draw_str_aligned(canvas, 92, y + 15, AlignCenter, AlignCenter, header);
+ canvas_draw_str_aligned(canvas, 92, y + 27, AlignCenter, AlignCenter, value);
+};
+
+static void battery_info_draw_callback(Canvas* canvas, void* context) {
+ furi_assert(context);
+ BatteryInfoModel* model = context;
+
+ canvas_clear(canvas);
+ canvas_set_color(canvas, ColorBlack);
+ draw_battery(canvas, model, 0, 5);
+
+ char batt_level[10];
+ char temperature[10];
+ char voltage[10];
+ char health[10];
+
+ snprintf(batt_level, sizeof(batt_level), "%lu%%", (uint32_t)model->charge);
+ snprintf(temperature, sizeof(temperature), "%lu C", (uint32_t)model->gauge_temperature);
+ snprintf(
+ voltage,
+ sizeof(voltage),
+ "%lu.%01lu V",
+ (uint32_t)model->gauge_voltage,
+ (uint32_t)(model->gauge_voltage * 10) % 10UL);
+ snprintf(health, sizeof(health), "%d%%", model->health);
+
+ draw_stat(canvas, 8, 42, &I_Battery_16x16, batt_level);
+ draw_stat(canvas, 40, 42, &I_Temperature_16x16, temperature);
+ draw_stat(canvas, 72, 42, &I_Voltage_16x16, voltage);
+ draw_stat(canvas, 104, 42, &I_Health_16x16, health);
+}
+
+BatteryInfo* battery_info_alloc() {
+ BatteryInfo* battery_info = malloc(sizeof(BatteryInfo));
+ battery_info->view = view_alloc();
+ view_set_context(battery_info->view, battery_info);
+ view_allocate_model(battery_info->view, ViewModelTypeLocking, sizeof(BatteryInfoModel));
+ view_set_draw_callback(battery_info->view, battery_info_draw_callback);
+
+ return battery_info;
+}
+
+void battery_info_free(BatteryInfo* battery_info) {
+ furi_assert(battery_info);
+ view_free(battery_info->view);
+ free(battery_info);
+}
+
+View* battery_info_get_view(BatteryInfo* battery_info) {
+ furi_assert(battery_info);
+ return battery_info->view;
+}
+
+void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data) {
+ furi_assert(battery_info);
+ furi_assert(data);
+ with_view_model(
+ battery_info->view,
+ BatteryInfoModel * model,
+ { memcpy(model, data, sizeof(BatteryInfoModel)); },
+ true);
+}
diff --git a/applications/debug/battery_test_app/views/battery_info.h b/applications/debug/battery_test_app/views/battery_info.h
new file mode 100644
index 00000000000..7bfacf69e27
--- /dev/null
+++ b/applications/debug/battery_test_app/views/battery_info.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include
+
+typedef struct BatteryInfo BatteryInfo;
+
+typedef struct {
+ float vbus_voltage;
+ float gauge_voltage;
+ float gauge_current;
+ float gauge_temperature;
+ float charging_voltage;
+ uint8_t charge;
+ uint8_t health;
+} BatteryInfoModel;
+
+BatteryInfo* battery_info_alloc();
+
+void battery_info_free(BatteryInfo* battery_info);
+
+View* battery_info_get_view(BatteryInfo* battery_info);
+
+void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data);
diff --git a/applications/debug/blink_test/application.fam b/applications/debug/blink_test/application.fam
new file mode 100644
index 00000000000..d7d873fb9a0
--- /dev/null
+++ b/applications/debug/blink_test/application.fam
@@ -0,0 +1,10 @@
+App(
+ appid="blink_test",
+ name="Blink Test",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="blink_test_app",
+ requires=["gui"],
+ stack_size=1 * 1024,
+ order=10,
+ fap_category="Debug",
+)
diff --git a/applications/debug_tools/blink_test.c b/applications/debug/blink_test/blink_test.c
similarity index 100%
rename from applications/debug_tools/blink_test.c
rename to applications/debug/blink_test/blink_test.c
diff --git a/applications/debug/bt_debug_app/application.fam b/applications/debug/bt_debug_app/application.fam
new file mode 100644
index 00000000000..8ed1ccc054f
--- /dev/null
+++ b/applications/debug/bt_debug_app/application.fam
@@ -0,0 +1,18 @@
+App(
+ appid="bt_debug",
+ name="Bluetooth Debug",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="bt_debug_app",
+ cdefines=["SRV_BT"],
+ requires=[
+ "bt",
+ "gui",
+ "dialogs",
+ ],
+ provides=[
+ "bt_debug",
+ ],
+ stack_size=1 * 1024,
+ order=110,
+ fap_category="Debug",
+)
diff --git a/applications/bt/bt_debug_app/bt_debug_app.c b/applications/debug/bt_debug_app/bt_debug_app.c
similarity index 96%
rename from applications/bt/bt_debug_app/bt_debug_app.c
rename to applications/debug/bt_debug_app/bt_debug_app.c
index ac442de0a29..bf13f6570a0 100644
--- a/applications/bt/bt_debug_app/bt_debug_app.c
+++ b/applications/debug/bt_debug_app/bt_debug_app.c
@@ -31,9 +31,6 @@ uint32_t bt_debug_start_view(void* context) {
BtDebugApp* bt_debug_app_alloc() {
BtDebugApp* app = malloc(sizeof(BtDebugApp));
- // Load settings
- bt_settings_load(&app->settings);
-
// Gui
app->gui = furi_record_open(RECORD_GUI);
@@ -98,20 +95,22 @@ void bt_debug_app_free(BtDebugApp* app) {
int32_t bt_debug_app(void* p) {
UNUSED(p);
if(!furi_hal_bt_is_testing_supported()) {
- FURI_LOG_E(TAG, "Incorrect radio stack: radio testing fetures are absent.");
+ FURI_LOG_E(TAG, "Incorrect radio stack: radio testing features are absent.");
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack");
return 255;
}
BtDebugApp* app = bt_debug_app_alloc();
+ // Was bt active?
+ const bool was_active = furi_hal_bt_is_active();
// Stop advertising
furi_hal_bt_stop_advertising();
view_dispatcher_run(app->view_dispatcher);
// Restart advertising
- if(app->settings.enabled) {
+ if(was_active) {
furi_hal_bt_start_advertising();
}
bt_debug_app_free(app);
diff --git a/applications/bt/bt_debug_app/bt_debug_app.h b/applications/debug/bt_debug_app/bt_debug_app.h
similarity index 91%
rename from applications/bt/bt_debug_app/bt_debug_app.h
rename to applications/debug/bt_debug_app/bt_debug_app.h
index c3657626e46..0ad94d7ddc1 100644
--- a/applications/bt/bt_debug_app/bt_debug_app.h
+++ b/applications/debug/bt_debug_app/bt_debug_app.h
@@ -4,15 +4,14 @@
#include
#include
#include
+#include
+
#include
-#include
#include "views/bt_carrier_test.h"
#include "views/bt_packet_test.h"
-#include "../bt_settings.h"
typedef struct {
- BtSettings settings;
Gui* gui;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
diff --git a/applications/bt/bt_debug_app/views/bt_carrier_test.c b/applications/debug/bt_debug_app/views/bt_carrier_test.c
old mode 100755
new mode 100644
similarity index 99%
rename from applications/bt/bt_debug_app/views/bt_carrier_test.c
rename to applications/debug/bt_debug_app/views/bt_carrier_test.c
index c09aa3fdfaa..8e224049574
--- a/applications/bt/bt_debug_app/views/bt_carrier_test.c
+++ b/applications/debug/bt_debug_app/views/bt_carrier_test.c
@@ -1,7 +1,7 @@
#include "bt_carrier_test.h"
#include "bt_test.h"
#include "bt_test_types.h"
-#include "furi_hal_bt.h"
+#include
struct BtCarrierTest {
BtTest* bt_test;
diff --git a/applications/bt/bt_debug_app/views/bt_carrier_test.h b/applications/debug/bt_debug_app/views/bt_carrier_test.h
similarity index 100%
rename from applications/bt/bt_debug_app/views/bt_carrier_test.h
rename to applications/debug/bt_debug_app/views/bt_carrier_test.h
diff --git a/applications/bt/bt_debug_app/views/bt_packet_test.c b/applications/debug/bt_debug_app/views/bt_packet_test.c
similarity index 99%
rename from applications/bt/bt_debug_app/views/bt_packet_test.c
rename to applications/debug/bt_debug_app/views/bt_packet_test.c
index 7cbc3c5c537..8a56a30031a 100644
--- a/applications/bt/bt_debug_app/views/bt_packet_test.c
+++ b/applications/debug/bt_debug_app/views/bt_packet_test.c
@@ -1,7 +1,7 @@
#include "bt_packet_test.h"
#include "bt_test.h"
#include "bt_test_types.h"
-#include "furi_hal_bt.h"
+#include
struct BtPacketTest {
BtTest* bt_test;
diff --git a/applications/bt/bt_debug_app/views/bt_packet_test.h b/applications/debug/bt_debug_app/views/bt_packet_test.h
similarity index 100%
rename from applications/bt/bt_debug_app/views/bt_packet_test.h
rename to applications/debug/bt_debug_app/views/bt_packet_test.h
diff --git a/applications/debug/bt_debug_app/views/bt_test.c b/applications/debug/bt_debug_app/views/bt_test.c
new file mode 100644
index 00000000000..cd52b8650e5
--- /dev/null
+++ b/applications/debug/bt_debug_app/views/bt_test.c
@@ -0,0 +1,434 @@
+#include "bt_test.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+struct BtTestParam {
+ const char* label;
+ uint8_t current_value_index;
+ FuriString* current_value_text;
+ uint8_t values_count;
+ BtTestParamChangeCallback change_callback;
+ void* context;
+};
+
+ARRAY_DEF(BtTestParamArray, BtTestParam, M_POD_OPLIST);
+
+struct BtTest {
+ View* view;
+ BtTestChangeStateCallback change_state_callback;
+ BtTestBackCallback back_callback;
+ void* context;
+};
+
+typedef struct {
+ BtTestState state;
+ BtTestParamArray_t params;
+ uint8_t position;
+ uint8_t window_position;
+ const char* message;
+ float rssi;
+ uint32_t packets_num_rx;
+ uint32_t packets_num_tx;
+} BtTestModel;
+
+#define BT_TEST_START_MESSAGE "Ok - Start"
+#define BT_TEST_STOP_MESSAGE "Ok - Stop"
+
+static void bt_test_process_up(BtTest* bt_test);
+static void bt_test_process_down(BtTest* bt_test);
+static void bt_test_process_left(BtTest* bt_test);
+static void bt_test_process_right(BtTest* bt_test);
+static void bt_test_process_ok(BtTest* bt_test);
+static void bt_test_process_back(BtTest* bt_test);
+
+static void bt_test_draw_callback(Canvas* canvas, void* _model) {
+ BtTestModel* model = _model;
+ char info_str[32];
+
+ const uint8_t param_height = 16;
+ const uint8_t param_width = 123;
+
+ canvas_clear(canvas);
+
+ uint8_t position = 0;
+ BtTestParamArray_it_t it;
+
+ canvas_set_font(canvas, FontSecondary);
+ for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it);
+ BtTestParamArray_next(it)) {
+ uint8_t param_position = position - model->window_position;
+ uint8_t params_on_screen = 3;
+ uint8_t y_offset = 0;
+
+ if(param_position < params_on_screen) {
+ const BtTestParam* param = BtTestParamArray_cref(it);
+ uint8_t param_y = y_offset + (param_position * param_height);
+ uint8_t param_text_y = param_y + param_height - 4;
+
+ if(position == model->position) {
+ canvas_set_color(canvas, ColorBlack);
+ elements_slightly_rounded_box(
+ canvas, 0, param_y + 1, param_width, param_height - 2);
+ canvas_set_color(canvas, ColorWhite);
+ } else {
+ canvas_set_color(canvas, ColorBlack);
+ }
+
+ canvas_draw_str(canvas, 6, param_text_y, param->label);
+
+ if(param->current_value_index > 0) {
+ canvas_draw_str(canvas, 50, param_text_y, "<");
+ }
+
+ canvas_draw_str(
+ canvas, 61, param_text_y, furi_string_get_cstr(param->current_value_text));
+
+ if(param->current_value_index < (param->values_count - 1)) {
+ canvas_draw_str(canvas, 113, param_text_y, ">");
+ }
+ }
+
+ position++;
+ }
+
+ elements_scrollbar(canvas, model->position, BtTestParamArray_size(model->params));
+ canvas_draw_str(canvas, 6, 60, model->message);
+ if(model->state == BtTestStateStarted) {
+ if(!float_is_equal(model->rssi, 0.0f)) {
+ snprintf(info_str, sizeof(info_str), "RSSI:%3.1f dB", (double)model->rssi);
+ canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
+ }
+ } else if(model->state == BtTestStateStopped) {
+ if(model->packets_num_rx) {
+ snprintf(info_str, sizeof(info_str), "%" PRIu32 " pack rcv", model->packets_num_rx);
+ canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
+ } else if(model->packets_num_tx) {
+ snprintf(info_str, sizeof(info_str), "%" PRIu32 " pack sent", model->packets_num_tx);
+ canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
+ }
+ }
+}
+
+static bool bt_test_input_callback(InputEvent* event, void* context) {
+ BtTest* bt_test = context;
+ furi_assert(bt_test);
+ bool consumed = false;
+
+ if(event->type == InputTypeShort) {
+ switch(event->key) {
+ case InputKeyUp:
+ consumed = true;
+ bt_test_process_up(bt_test);
+ break;
+ case InputKeyDown:
+ consumed = true;
+ bt_test_process_down(bt_test);
+ break;
+ case InputKeyLeft:
+ consumed = true;
+ bt_test_process_left(bt_test);
+ break;
+ case InputKeyRight:
+ consumed = true;
+ bt_test_process_right(bt_test);
+ break;
+ case InputKeyOk:
+ consumed = true;
+ bt_test_process_ok(bt_test);
+ break;
+ case InputKeyBack:
+ consumed = false;
+ bt_test_process_back(bt_test);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return consumed;
+}
+
+void bt_test_process_up(BtTest* bt_test) {
+ with_view_model( // -V658
+ bt_test->view,
+ BtTestModel * model,
+ {
+ uint8_t params_on_screen = 3;
+ if(model->position > 0) {
+ model->position--;
+ if(((model->position - model->window_position) < 1) &&
+ model->window_position > 0) {
+ model->window_position--;
+ }
+ } else {
+ model->position = BtTestParamArray_size(model->params) - 1;
+ if(model->position > (params_on_screen - 1)) {
+ model->window_position = model->position - (params_on_screen - 1);
+ }
+ }
+ },
+ true);
+}
+
+void bt_test_process_down(BtTest* bt_test) {
+ with_view_model(
+ bt_test->view,
+ BtTestModel * model,
+ {
+ uint8_t params_on_screen = 3;
+ if(model->position < (BtTestParamArray_size(model->params) - 1)) {
+ model->position++;
+ if((model->position - model->window_position) > (params_on_screen - 2) &&
+ model->window_position <
+ (BtTestParamArray_size(model->params) - params_on_screen)) {
+ model->window_position++;
+ }
+ } else {
+ model->position = 0;
+ model->window_position = 0;
+ }
+ },
+ true);
+}
+
+BtTestParam* bt_test_get_selected_param(BtTestModel* model) {
+ BtTestParam* param = NULL;
+
+ BtTestParamArray_it_t it;
+ uint8_t position = 0;
+ for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it);
+ BtTestParamArray_next(it)) {
+ if(position == model->position) {
+ break;
+ }
+ position++;
+ }
+
+ param = BtTestParamArray_ref(it);
+
+ furi_assert(param);
+ return param;
+}
+
+void bt_test_process_left(BtTest* bt_test) {
+ BtTestParam* param;
+ with_view_model(
+ bt_test->view,
+ BtTestModel * model,
+ {
+ param = bt_test_get_selected_param(model);
+ if(param->current_value_index > 0) {
+ param->current_value_index--;
+ if(param->change_callback) {
+ model->state = BtTestStateStopped;
+ model->message = BT_TEST_START_MESSAGE;
+ model->rssi = 0.0f;
+ model->packets_num_rx = 0;
+ model->packets_num_tx = 0;
+ }
+ }
+ },
+ true);
+ if(param->change_callback) {
+ param->change_callback(param);
+ }
+}
+
+void bt_test_process_right(BtTest* bt_test) {
+ BtTestParam* param;
+ with_view_model(
+ bt_test->view,
+ BtTestModel * model,
+ {
+ param = bt_test_get_selected_param(model);
+ if(param->current_value_index < (param->values_count - 1)) {
+ param->current_value_index++;
+ if(param->change_callback) {
+ model->state = BtTestStateStopped;
+ model->message = BT_TEST_START_MESSAGE;
+ model->rssi = 0.0f;
+ model->packets_num_rx = 0;
+ model->packets_num_tx = 0;
+ }
+ }
+ },
+ true);
+ if(param->change_callback) {
+ param->change_callback(param);
+ }
+}
+
+void bt_test_process_ok(BtTest* bt_test) {
+ BtTestState state;
+ with_view_model(
+ bt_test->view,
+ BtTestModel * model,
+ {
+ if(model->state == BtTestStateStarted) {
+ model->state = BtTestStateStopped;
+ model->message = BT_TEST_START_MESSAGE;
+ model->rssi = 0.0f;
+ model->packets_num_rx = 0;
+ model->packets_num_tx = 0;
+ } else if(model->state == BtTestStateStopped) {
+ model->state = BtTestStateStarted;
+ model->message = BT_TEST_STOP_MESSAGE;
+ }
+ state = model->state;
+ },
+ true);
+ if(bt_test->change_state_callback) {
+ bt_test->change_state_callback(state, bt_test->context);
+ }
+}
+
+void bt_test_process_back(BtTest* bt_test) {
+ with_view_model(
+ bt_test->view,
+ BtTestModel * model,
+ {
+ model->state = BtTestStateStopped;
+ model->rssi = 0.0f;
+ model->packets_num_rx = 0;
+ model->packets_num_tx = 0;
+ },
+ false);
+ if(bt_test->back_callback) {
+ bt_test->back_callback(bt_test->context);
+ }
+}
+
+BtTest* bt_test_alloc() {
+ BtTest* bt_test = malloc(sizeof(BtTest));
+ bt_test->view = view_alloc();
+ view_set_context(bt_test->view, bt_test);
+ view_allocate_model(bt_test->view, ViewModelTypeLocking, sizeof(BtTestModel));
+ view_set_draw_callback(bt_test->view, bt_test_draw_callback);
+ view_set_input_callback(bt_test->view, bt_test_input_callback);
+
+ with_view_model(
+ bt_test->view,
+ BtTestModel * model,
+ {
+ model->state = BtTestStateStopped;
+ model->message = "Ok - Start";
+ BtTestParamArray_init(model->params);
+ model->position = 0;
+ model->window_position = 0;
+ model->rssi = 0.0f;
+ model->packets_num_tx = 0;
+ model->packets_num_rx = 0;
+ },
+ true);
+
+ return bt_test;
+}
+
+void bt_test_free(BtTest* bt_test) {
+ furi_assert(bt_test);
+
+ with_view_model(
+ bt_test->view,
+ BtTestModel * model,
+ {
+ BtTestParamArray_it_t it;
+ for(BtTestParamArray_it(it, model->params); !BtTestParamArray_end_p(it);
+ BtTestParamArray_next(it)) {
+ furi_string_free(BtTestParamArray_ref(it)->current_value_text);
+ }
+ BtTestParamArray_clear(model->params);
+ },
+ false);
+ view_free(bt_test->view);
+ free(bt_test);
+}
+
+View* bt_test_get_view(BtTest* bt_test) {
+ furi_assert(bt_test);
+ return bt_test->view;
+}
+
+BtTestParam* bt_test_param_add(
+ BtTest* bt_test,
+ const char* label,
+ uint8_t values_count,
+ BtTestParamChangeCallback change_callback,
+ void* context) {
+ BtTestParam* param = NULL;
+ furi_assert(label);
+ furi_assert(bt_test);
+
+ with_view_model(
+ bt_test->view,
+ BtTestModel * model,
+ {
+ param = BtTestParamArray_push_new(model->params);
+ param->label = label;
+ param->values_count = values_count;
+ param->change_callback = change_callback;
+ param->context = context;
+ param->current_value_index = 0;
+ param->current_value_text = furi_string_alloc();
+ },
+ true);
+
+ return param;
+}
+
+void bt_test_set_rssi(BtTest* bt_test, float rssi) {
+ furi_assert(bt_test);
+ with_view_model(
+ bt_test->view, BtTestModel * model, { model->rssi = rssi; }, true);
+}
+
+void bt_test_set_packets_tx(BtTest* bt_test, uint32_t packets_num) {
+ furi_assert(bt_test);
+ with_view_model(
+ bt_test->view, BtTestModel * model, { model->packets_num_tx = packets_num; }, true);
+}
+
+void bt_test_set_packets_rx(BtTest* bt_test, uint32_t packets_num) {
+ furi_assert(bt_test);
+ with_view_model(
+ bt_test->view, BtTestModel * model, { model->packets_num_rx = packets_num; }, true);
+}
+
+void bt_test_set_change_state_callback(BtTest* bt_test, BtTestChangeStateCallback callback) {
+ furi_assert(bt_test);
+ furi_assert(callback);
+ bt_test->change_state_callback = callback;
+}
+
+void bt_test_set_back_callback(BtTest* bt_test, BtTestBackCallback callback) {
+ furi_assert(bt_test);
+ furi_assert(callback);
+ bt_test->back_callback = callback;
+}
+
+void bt_test_set_context(BtTest* bt_test, void* context) {
+ furi_assert(bt_test);
+ bt_test->context = context;
+}
+
+void bt_test_set_current_value_index(BtTestParam* param, uint8_t current_value_index) {
+ param->current_value_index = current_value_index;
+}
+
+void bt_test_set_current_value_text(BtTestParam* param, const char* current_value_text) {
+ furi_string_set(param->current_value_text, current_value_text);
+}
+
+uint8_t bt_test_get_current_value_index(BtTestParam* param) {
+ return param->current_value_index;
+}
+
+void* bt_test_get_context(BtTestParam* param) {
+ return param->context;
+}
diff --git a/applications/bt/bt_debug_app/views/bt_test.h b/applications/debug/bt_debug_app/views/bt_test.h
old mode 100755
new mode 100644
similarity index 100%
rename from applications/bt/bt_debug_app/views/bt_test.h
rename to applications/debug/bt_debug_app/views/bt_test.h
diff --git a/applications/bt/bt_debug_app/views/bt_test_types.h b/applications/debug/bt_debug_app/views/bt_test_types.h
similarity index 100%
rename from applications/bt/bt_debug_app/views/bt_test_types.h
rename to applications/debug/bt_debug_app/views/bt_test_types.h
diff --git a/applications/debug/ccid_test/application.fam b/applications/debug/ccid_test/application.fam
new file mode 100644
index 00000000000..ad907677085
--- /dev/null
+++ b/applications/debug/ccid_test/application.fam
@@ -0,0 +1,15 @@
+App(
+ appid="ccid_test",
+ name="CCID Debug",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="ccid_test_app",
+ requires=[
+ "gui",
+ ],
+ provides=[
+ "ccid_test",
+ ],
+ stack_size=1 * 1024,
+ order=120,
+ fap_category="Debug",
+)
diff --git a/applications/debug/ccid_test/ccid_test_app.c b/applications/debug/ccid_test/ccid_test_app.c
new file mode 100644
index 00000000000..3d7fba39de5
--- /dev/null
+++ b/applications/debug/ccid_test/ccid_test_app.c
@@ -0,0 +1,169 @@
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include "iso7816_t0_apdu.h"
+
+typedef enum {
+ EventTypeInput,
+} EventType;
+
+typedef struct {
+ Gui* gui;
+ ViewPort* view_port;
+ FuriMessageQueue* event_queue;
+ FuriHalUsbCcidConfig ccid_cfg;
+} CcidTestApp;
+
+typedef struct {
+ union {
+ InputEvent input;
+ };
+ EventType type;
+} CcidTestAppEvent;
+
+typedef enum {
+ CcidTestSubmenuIndexInsertSmartcard,
+ CcidTestSubmenuIndexRemoveSmartcard,
+ CcidTestSubmenuIndexInsertSmartcardReader
+} SubmenuIndex;
+
+void icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) {
+ UNUSED(context);
+
+ iso7816_answer_to_reset(atrBuffer, atrlen);
+}
+
+//dataBlock points to the buffer
+//dataBlockLen tells reader how nany bytes should be read
+void xfr_datablock_callback(
+ const uint8_t* dataBlock,
+ uint32_t dataBlockLen,
+ uint8_t* responseDataBlock,
+ uint32_t* responseDataBlockLen,
+ void* context) {
+ UNUSED(context);
+
+ struct ISO7816_Command_APDU commandAPDU;
+ iso7816_read_command_apdu(&commandAPDU, dataBlock, dataBlockLen);
+
+ struct ISO7816_Response_APDU responseAPDU;
+ //class not supported
+ responseAPDU.SW1 = 0x6E;
+ responseAPDU.SW2 = 0x00;
+
+ iso7816_write_response_apdu(&responseAPDU, responseDataBlock, responseDataBlockLen);
+}
+
+static const CcidCallbacks ccid_cb = {
+ icc_power_on_callback,
+ xfr_datablock_callback,
+};
+
+static void ccid_test_app_render_callback(Canvas* canvas, void* ctx) {
+ UNUSED(ctx);
+ canvas_clear(canvas);
+
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str(canvas, 0, 10, "CCID Test App");
+
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str(canvas, 0, 63, "Hold [back] to exit");
+}
+
+static void ccid_test_app_input_callback(InputEvent* input_event, void* ctx) {
+ FuriMessageQueue* event_queue = ctx;
+
+ CcidTestAppEvent event;
+ event.type = EventTypeInput;
+ event.input = *input_event;
+ furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+uint32_t ccid_test_exit(void* context) {
+ UNUSED(context);
+ return VIEW_NONE;
+}
+
+CcidTestApp* ccid_test_app_alloc() {
+ CcidTestApp* app = malloc(sizeof(CcidTestApp));
+
+ // Gui
+ app->gui = furi_record_open(RECORD_GUI);
+
+ //viewport
+ app->view_port = view_port_alloc();
+ gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
+ view_port_draw_callback_set(app->view_port, ccid_test_app_render_callback, NULL);
+
+ //message queue
+ app->event_queue = furi_message_queue_alloc(8, sizeof(CcidTestAppEvent));
+ furi_check(app->event_queue);
+ view_port_input_callback_set(app->view_port, ccid_test_app_input_callback, app->event_queue);
+
+ return app;
+}
+
+void ccid_test_app_free(CcidTestApp* app) {
+ furi_assert(app);
+
+ //message queue
+ furi_message_queue_free(app->event_queue);
+
+ //view port
+ gui_remove_view_port(app->gui, app->view_port);
+ view_port_free(app->view_port);
+
+ // Close gui record
+ furi_record_close(RECORD_GUI);
+ app->gui = NULL;
+
+ // Free rest
+ free(app);
+}
+
+int32_t ccid_test_app(void* p) {
+ UNUSED(p);
+
+ //setup view
+ CcidTestApp* app = ccid_test_app_alloc();
+
+ //setup CCID USB
+ // On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
+ app->ccid_cfg.vid = 0x1234;
+ app->ccid_cfg.pid = 0x5678;
+
+ FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
+ furi_hal_usb_unlock();
+ furi_hal_ccid_set_callbacks((CcidCallbacks*)&ccid_cb);
+ furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true);
+
+ //handle button events
+ CcidTestAppEvent event;
+ while(1) {
+ FuriStatus event_status =
+ furi_message_queue_get(app->event_queue, &event, FuriWaitForever);
+
+ if(event_status == FuriStatusOk) {
+ if(event.type == EventTypeInput) {
+ if(event.input.type == InputTypeLong && event.input.key == InputKeyBack) {
+ break;
+ }
+ }
+ }
+ view_port_update(app->view_port);
+ }
+
+ //tear down USB
+ furi_hal_usb_set_config(usb_mode_prev, NULL);
+ furi_hal_ccid_set_callbacks(NULL);
+
+ //teardown view
+ ccid_test_app_free(app);
+ return 0;
+}
diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.c b/applications/debug/ccid_test/iso7816_t0_apdu.c
new file mode 100644
index 00000000000..7c690a6944e
--- /dev/null
+++ b/applications/debug/ccid_test/iso7816_t0_apdu.c
@@ -0,0 +1,37 @@
+/* Implements rudimentary iso7816-3 support for APDU (T=0) */
+#include
+#include
+#include
+#include "iso7816_t0_apdu.h"
+
+void iso7816_answer_to_reset(uint8_t* dataBuffer, uint32_t* atrlen) {
+ //minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
+ uint8_t AtrBuffer[2] = {
+ 0x3B, //TS (direct convention)
+ 0x00 // T0 (Y(1): b0000, K: 0 (historical bytes))
+ };
+ *atrlen = 2;
+
+ memcpy(dataBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen));
+}
+
+void iso7816_read_command_apdu(
+ struct ISO7816_Command_APDU* command,
+ const uint8_t* dataBuffer,
+ uint32_t dataLen) {
+ UNUSED(dataLen);
+ command->CLA = dataBuffer[0];
+ command->INS = dataBuffer[1];
+ command->P1 = dataBuffer[2];
+ command->P2 = dataBuffer[3];
+ command->Lc = dataBuffer[4];
+}
+
+void iso7816_write_response_apdu(
+ const struct ISO7816_Response_APDU* response,
+ uint8_t* dataBuffer,
+ uint32_t* dataLen) {
+ dataBuffer[0] = response->SW1;
+ dataBuffer[1] = response->SW2;
+ *dataLen = 2;
+}
\ No newline at end of file
diff --git a/applications/debug/ccid_test/iso7816_t0_apdu.h b/applications/debug/ccid_test/iso7816_t0_apdu.h
new file mode 100644
index 00000000000..5ca13eb6041
--- /dev/null
+++ b/applications/debug/ccid_test/iso7816_t0_apdu.h
@@ -0,0 +1,32 @@
+#ifndef _ISO7816_T0_APDU_H_
+#define _ISO7816_T0_APDU_H_
+
+#include
+
+struct ISO7816_Command_APDU {
+ //header
+ uint8_t CLA;
+ uint8_t INS;
+ uint8_t P1;
+ uint8_t P2;
+
+ //body
+ uint8_t Lc;
+ uint8_t Le;
+} FURI_PACKED;
+
+struct ISO7816_Response_APDU {
+ uint8_t SW1;
+ uint8_t SW2;
+} FURI_PACKED;
+
+void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen);
+void iso7816_read_command_apdu(
+ struct ISO7816_Command_APDU* command,
+ const uint8_t* dataBuffer,
+ uint32_t dataLen);
+void iso7816_write_response_apdu(
+ const struct ISO7816_Response_APDU* response,
+ uint8_t* dataBuffer,
+ uint32_t* dataLen);
+#endif //_ISO7816_T0_APDU_H_
diff --git a/applications/debug/crash_test/application.fam b/applications/debug/crash_test/application.fam
new file mode 100644
index 00000000000..357efe667ac
--- /dev/null
+++ b/applications/debug/crash_test/application.fam
@@ -0,0 +1,9 @@
+App(
+ appid="crash_test",
+ name="Crash Test",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="crash_test_app",
+ requires=["gui"],
+ stack_size=1 * 1024,
+ fap_category="Debug",
+)
diff --git a/applications/debug/crash_test/crash_test.c b/applications/debug/crash_test/crash_test.c
new file mode 100644
index 00000000000..92f1668be9f
--- /dev/null
+++ b/applications/debug/crash_test/crash_test.c
@@ -0,0 +1,128 @@
+#include
+#include
+
+#include
+#include
+#include
+
+#define TAG "CrashTest"
+
+typedef struct {
+ Gui* gui;
+ ViewDispatcher* view_dispatcher;
+ Submenu* submenu;
+} CrashTest;
+
+typedef enum {
+ CrashTestViewSubmenu,
+} CrashTestView;
+
+typedef enum {
+ CrashTestSubmenuCheck,
+ CrashTestSubmenuCheckMessage,
+ CrashTestSubmenuAssert,
+ CrashTestSubmenuAssertMessage,
+ CrashTestSubmenuCrash,
+ CrashTestSubmenuHalt,
+} CrashTestSubmenu;
+
+static void crash_test_submenu_callback(void* context, uint32_t index) {
+ CrashTest* instance = (CrashTest*)context;
+ UNUSED(instance);
+
+ switch(index) {
+ case CrashTestSubmenuCheck:
+ furi_check(false);
+ break;
+ case CrashTestSubmenuCheckMessage:
+ furi_check(false, "Crash test: furi_check with message");
+ break;
+ case CrashTestSubmenuAssert:
+ furi_assert(false);
+ break;
+ case CrashTestSubmenuAssertMessage:
+ furi_assert(false, "Crash test: furi_assert with message");
+ break;
+ case CrashTestSubmenuCrash:
+ furi_crash("Crash test: furi_crash");
+ break;
+ case CrashTestSubmenuHalt:
+ furi_halt("Crash test: furi_halt");
+ break;
+ default:
+ furi_crash("Programming error");
+ }
+}
+
+static uint32_t crash_test_exit_callback(void* context) {
+ UNUSED(context);
+ return VIEW_NONE;
+}
+
+CrashTest* crash_test_alloc() {
+ CrashTest* instance = malloc(sizeof(CrashTest));
+
+ View* view = NULL;
+
+ instance->gui = furi_record_open(RECORD_GUI);
+ instance->view_dispatcher = view_dispatcher_alloc();
+ view_dispatcher_enable_queue(instance->view_dispatcher);
+ view_dispatcher_attach_to_gui(
+ instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
+
+ // Menu
+ instance->submenu = submenu_alloc();
+ view = submenu_get_view(instance->submenu);
+ view_set_previous_callback(view, crash_test_exit_callback);
+ view_dispatcher_add_view(instance->view_dispatcher, CrashTestViewSubmenu, view);
+ submenu_add_item(
+ instance->submenu, "Check", CrashTestSubmenuCheck, crash_test_submenu_callback, instance);
+ submenu_add_item(
+ instance->submenu,
+ "Check with message",
+ CrashTestSubmenuCheckMessage,
+ crash_test_submenu_callback,
+ instance);
+ submenu_add_item(
+ instance->submenu, "Assert", CrashTestSubmenuAssert, crash_test_submenu_callback, instance);
+ submenu_add_item(
+ instance->submenu,
+ "Assert with message",
+ CrashTestSubmenuAssertMessage,
+ crash_test_submenu_callback,
+ instance);
+ submenu_add_item(
+ instance->submenu, "Crash", CrashTestSubmenuCrash, crash_test_submenu_callback, instance);
+ submenu_add_item(
+ instance->submenu, "Halt", CrashTestSubmenuHalt, crash_test_submenu_callback, instance);
+
+ return instance;
+}
+
+void crash_test_free(CrashTest* instance) {
+ view_dispatcher_remove_view(instance->view_dispatcher, CrashTestViewSubmenu);
+ submenu_free(instance->submenu);
+
+ view_dispatcher_free(instance->view_dispatcher);
+ furi_record_close(RECORD_GUI);
+
+ free(instance);
+}
+
+int32_t crash_test_run(CrashTest* instance) {
+ view_dispatcher_switch_to_view(instance->view_dispatcher, CrashTestViewSubmenu);
+ view_dispatcher_run(instance->view_dispatcher);
+ return 0;
+}
+
+int32_t crash_test_app(void* p) {
+ UNUSED(p);
+
+ CrashTest* instance = crash_test_alloc();
+
+ int32_t ret = crash_test_run(instance);
+
+ crash_test_free(instance);
+
+ return ret;
+}
diff --git a/applications/debug/direct_draw/application.fam b/applications/debug/direct_draw/application.fam
new file mode 100644
index 00000000000..11b3bc6ba38
--- /dev/null
+++ b/applications/debug/direct_draw/application.fam
@@ -0,0 +1,10 @@
+App(
+ appid="direct_draw",
+ name="Direct Draw",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="direct_draw_app",
+ requires=["gui", "input"],
+ stack_size=2 * 1024,
+ order=70,
+ fap_category="Debug",
+)
diff --git a/applications/debug/direct_draw/direct_draw.c b/applications/debug/direct_draw/direct_draw.c
new file mode 100644
index 00000000000..63e03530a7a
--- /dev/null
+++ b/applications/debug/direct_draw/direct_draw.c
@@ -0,0 +1,112 @@
+#include
+#include
+#include
+#include
+
+#define BUFFER_SIZE (32U)
+
+typedef struct {
+ FuriPubSub* input;
+ FuriPubSubSubscription* input_subscription;
+ Gui* gui;
+ Canvas* canvas;
+ bool stop;
+ uint32_t counter;
+} DirectDraw;
+
+static void gui_input_events_callback(const void* value, void* ctx) {
+ furi_assert(value);
+ furi_assert(ctx);
+
+ DirectDraw* instance = ctx;
+ const InputEvent* event = value;
+
+ if(event->key == InputKeyBack && event->type == InputTypeShort) {
+ instance->stop = true;
+ }
+}
+
+static DirectDraw* direct_draw_alloc() {
+ DirectDraw* instance = malloc(sizeof(DirectDraw));
+
+ instance->input = furi_record_open(RECORD_INPUT_EVENTS);
+ instance->gui = furi_record_open(RECORD_GUI);
+ instance->canvas = gui_direct_draw_acquire(instance->gui);
+
+ instance->input_subscription =
+ furi_pubsub_subscribe(instance->input, gui_input_events_callback, instance);
+
+ return instance;
+}
+
+static void direct_draw_free(DirectDraw* instance) {
+ furi_pubsub_unsubscribe(instance->input, instance->input_subscription);
+
+ instance->canvas = NULL;
+ gui_direct_draw_release(instance->gui);
+ furi_record_close(RECORD_GUI);
+ furi_record_close(RECORD_INPUT_EVENTS);
+}
+
+static void direct_draw_block(Canvas* canvas, uint32_t size, uint32_t counter) {
+ size += 16;
+ uint8_t width = canvas_width(canvas) - size;
+ uint8_t height = canvas_height(canvas) - size;
+
+ uint8_t x = counter % width;
+ if((counter / width) % 2) {
+ x = width - x;
+ }
+
+ uint8_t y = counter % height;
+ if((counter / height) % 2) {
+ y = height - y;
+ }
+
+ canvas_draw_box(canvas, x, y, size, size);
+}
+
+static void direct_draw_run(DirectDraw* instance) {
+ size_t start = DWT->CYCCNT;
+ size_t counter = 0;
+ float fps = 0;
+
+ furi_thread_set_current_priority(FuriThreadPriorityIdle);
+
+ do {
+ size_t elapsed = DWT->CYCCNT - start;
+ char buffer[BUFFER_SIZE] = {0};
+
+ if(elapsed >= 64000000) {
+ fps = (float)counter / ((float)elapsed / 64000000.0f);
+
+ start = DWT->CYCCNT;
+ counter = 0;
+ }
+ snprintf(buffer, BUFFER_SIZE, "FPS: %.1f", (double)fps);
+
+ canvas_reset(instance->canvas);
+ canvas_set_color(instance->canvas, ColorXOR);
+ direct_draw_block(instance->canvas, instance->counter % 16, instance->counter);
+ direct_draw_block(instance->canvas, instance->counter * 2 % 16, instance->counter * 2);
+ direct_draw_block(instance->canvas, instance->counter * 3 % 16, instance->counter * 3);
+ direct_draw_block(instance->canvas, instance->counter * 4 % 16, instance->counter * 4);
+ direct_draw_block(instance->canvas, instance->counter * 5 % 16, instance->counter * 5);
+ canvas_draw_str(instance->canvas, 10, 10, buffer);
+ canvas_commit(instance->canvas);
+
+ counter++;
+ instance->counter++;
+ furi_thread_yield();
+ } while(!instance->stop);
+}
+
+int32_t direct_draw_app(void* p) {
+ UNUSED(p);
+
+ DirectDraw* instance = direct_draw_alloc();
+ direct_draw_run(instance);
+ direct_draw_free(instance);
+
+ return 0;
+}
diff --git a/applications/debug/display_test/application.fam b/applications/debug/display_test/application.fam
new file mode 100644
index 00000000000..7b2357b01d8
--- /dev/null
+++ b/applications/debug/display_test/application.fam
@@ -0,0 +1,11 @@
+App(
+ appid="display_test",
+ name="Display Test",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="display_test_app",
+ requires=["gui"],
+ fap_libs=["u8g2"],
+ stack_size=1 * 1024,
+ order=120,
+ fap_category="Debug",
+)
diff --git a/applications/debug_tools/display_test/display_test.c b/applications/debug/display_test/display_test.c
similarity index 96%
rename from applications/debug_tools/display_test/display_test.c
rename to applications/debug/display_test/display_test.c
index 947e4dc8b23..8065a23a1f9 100644
--- a/applications/debug_tools/display_test/display_test.c
+++ b/applications/debug/display_test/display_test.c
@@ -91,7 +91,6 @@ static void display_test_reload_config(DisplayTest* instance) {
instance->config_contrast,
instance->config_regulation_ratio,
instance->config_bias);
- gui_update(instance->gui);
}
static void display_config_set_bias(VariableItem* item) {
@@ -113,11 +112,11 @@ static void display_config_set_regulation_ratio(VariableItem* item) {
static void display_config_set_contrast(VariableItem* item) {
DisplayTest* instance = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
- string_t temp;
- string_init(temp);
- string_cat_printf(temp, "%d", index);
- variable_item_set_current_value_text(item, string_get_cstr(temp));
- string_clear(temp);
+ FuriString* temp;
+ temp = furi_string_alloc();
+ furi_string_cat_printf(temp, "%d", index);
+ variable_item_set_current_value_text(item, furi_string_get_cstr(temp));
+ furi_string_free(temp);
instance->config_contrast = index;
display_test_reload_config(instance);
}
@@ -145,7 +144,7 @@ DisplayTest* display_test_alloc() {
view_set_previous_callback(view, display_test_previous_callback);
view_dispatcher_add_view(instance->view_dispatcher, DisplayTestViewConfigure, view);
- // Configurtion items
+ // Configuration items
VariableItem* item;
instance->config_bias = false;
instance->config_contrast = 32;
diff --git a/applications/debug_tools/display_test/display_test.h b/applications/debug/display_test/display_test.h
similarity index 100%
rename from applications/debug_tools/display_test/display_test.h
rename to applications/debug/display_test/display_test.h
diff --git a/applications/debug_tools/display_test/view_display_test.c b/applications/debug/display_test/view_display_test.c
similarity index 96%
rename from applications/debug_tools/display_test/view_display_test.c
rename to applications/debug/display_test/view_display_test.c
index f00fe009445..b47c74c6c2c 100644
--- a/applications/debug_tools/display_test/view_display_test.c
+++ b/applications/debug/display_test/view_display_test.c
@@ -110,7 +110,9 @@ static bool view_display_test_input_callback(InputEvent* event, void* context) {
bool consumed = false;
if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
with_view_model(
- instance->view, (ViewDisplayTestModel * model) {
+ instance->view,
+ ViewDisplayTestModel * model,
+ {
if(event->key == InputKeyLeft && model->test > 0) {
model->test--;
consumed = true;
@@ -129,8 +131,8 @@ static bool view_display_test_input_callback(InputEvent* event, void* context) {
model->flip_flop = !model->flip_flop;
consumed = true;
}
- return consumed;
- });
+ },
+ consumed);
}
return consumed;
@@ -149,10 +151,7 @@ static void view_display_test_exit(void* context) {
static void view_display_test_timer_callback(void* context) {
ViewDisplayTest* instance = context;
with_view_model(
- instance->view, (ViewDisplayTestModel * model) {
- model->counter++;
- return true;
- });
+ instance->view, ViewDisplayTestModel * model, { model->counter++; }, true);
}
ViewDisplayTest* view_display_test_alloc() {
diff --git a/applications/debug_tools/display_test/view_display_test.h b/applications/debug/display_test/view_display_test.h
similarity index 100%
rename from applications/debug_tools/display_test/view_display_test.h
rename to applications/debug/display_test/view_display_test.h
diff --git a/applications/debug/example_custom_font/application.fam b/applications/debug/example_custom_font/application.fam
new file mode 100644
index 00000000000..06c0a7f6183
--- /dev/null
+++ b/applications/debug/example_custom_font/application.fam
@@ -0,0 +1,9 @@
+App(
+ appid="example_custom_font",
+ name="Example: custom font",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="example_custom_font_main",
+ requires=["gui"],
+ stack_size=1 * 1024,
+ fap_category="Debug",
+)
diff --git a/applications/debug/example_custom_font/example_custom_font.c b/applications/debug/example_custom_font/example_custom_font.c
new file mode 100644
index 00000000000..15eeb5f02a4
--- /dev/null
+++ b/applications/debug/example_custom_font/example_custom_font.c
@@ -0,0 +1,98 @@
+#include
+#include
+
+#include
+#include
+
+//This arrays contains the font itself. You can use any u8g2 font you want
+
+/*
+Fontname: -Raccoon-Fixed4x6-Medium-R-Normal--6-60-75-75-P-40-ISO10646-1
+Copyright:
+Glyphs: 95/203
+BBX Build Mode: 0
+*/
+const uint8_t u8g2_font_tom_thumb_4x6_tr[725] =
+ "_\0\2\2\2\3\3\4\4\3\6\0\377\5\377\5\0\0\352\1\330\2\270 \5\340\315\0!\6\265\310"
+ "\254\0\42\6\213\313$\25#\10\227\310\244\241\206\12$\10\227\310\215\70b\2%\10\227\310d\324F\1"
+ "&\10\227\310(\65R\22'\5\251\313\10(\6\266\310\251\62)\10\226\310\304\224\24\0*\6\217\312\244"
+ "\16+\7\217\311\245\225\0,\6\212\310)\0-\5\207\312\14.\5\245\310\4/\7\227\310Ve\4\60"
+ "\7\227\310-k\1\61\6\226\310\255\6\62\10\227\310h\220\312\1\63\11\227\310h\220\62X\0\64\10\227"
+ "\310$\65b\1\65\10\227\310\214\250\301\2\66\10\227\310\315\221F\0\67\10\227\310\314TF\0\70\10\227"
+ "\310\214\64\324\10\71\10\227\310\214\64\342\2:\6\255\311\244\0;\7\222\310e\240\0<\10\227\310\246\32"
+ "d\20=\6\217\311l\60>\11\227\310d\220A*\1\77\10\227\310\314\224a\2@\10\227\310UC\3"
+ "\1A\10\227\310UC\251\0B\10\227\310\250\264\322\2C\7\227\310\315\32\10D\10\227\310\250d-\0"
+ "E\10\227\310\214\70\342\0F\10\227\310\214\70b\4G\10\227\310\315\221\222\0H\10\227\310$\65\224\12"
+ "I\7\227\310\254X\15J\7\227\310\226\252\2K\10\227\310$\265\222\12L\7\227\310\304\346\0M\10\227"
+ "\310\244\61\224\12N\10\227\310\244q\250\0O\7\227\310UV\5P\10\227\310\250\264b\4Q\10\227\310"
+ "Uj$\1R\10\227\310\250\64V\1S\10\227\310m\220\301\2T\7\227\310\254\330\2U\7\227\310$"
+ "W\22V\10\227\310$\253L\0W\10\227\310$\65\206\12X\10\227\310$\325R\1Y\10\227\310$U"
+ "V\0Z\7\227\310\314T\16[\7\227\310\214X\16\134\10\217\311d\220A\0]\7\227\310\314r\4^"
+ "\5\213\313\65_\5\207\310\14`\6\212\313\304\0a\7\223\310\310\65\2b\10\227\310D\225\324\2c\7"
+ "\223\310\315\14\4d\10\227\310\246\245\222\0e\6\223\310\235\2f\10\227\310\246\264b\2g\10\227\307\35"
+ "\61%\0h\10\227\310D\225\254\0i\6\265\310\244\1j\10\233\307f\30U\5k\10\227\310\304\264T"
+ "\1l\7\227\310\310\326\0m\7\223\310
-#include
+#include
+
+#include
#include
#include
+#include
+#include
static bool file_browser_app_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
@@ -47,9 +47,9 @@ FileBrowserApp* file_browser_app_alloc(char* arg) {
app->widget = widget_alloc();
- string_init(app->file_path);
+ app->file_path = furi_string_alloc();
app->file_browser = file_browser_alloc(app->file_path);
- file_browser_configure(app->file_browser, "*", true, &I_badusb_10px, true);
+ file_browser_configure(app->file_browser, "*", NULL, true, false, &I_badusb_10px, true);
view_dispatcher_add_view(
app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget));
@@ -84,7 +84,7 @@ void file_browser_app_free(FileBrowserApp* app) {
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_DIALOGS);
- string_clear(app->file_path);
+ furi_string_free(app->file_path);
free(app);
}
diff --git a/applications/debug_tools/file_browser_test/file_browser_app_i.h b/applications/debug/file_browser_test/file_browser_app_i.h
similarity index 96%
rename from applications/debug_tools/file_browser_test/file_browser_app_i.h
rename to applications/debug/file_browser_test/file_browser_app_i.h
index 6e8412c9bd3..20f4c3a038b 100644
--- a/applications/debug_tools/file_browser_test/file_browser_app_i.h
+++ b/applications/debug/file_browser_test/file_browser_app_i.h
@@ -22,7 +22,7 @@ struct FileBrowserApp {
Widget* widget;
FileBrowser* file_browser;
- string_t file_path;
+ FuriString* file_path;
};
typedef enum {
diff --git a/applications/debug/file_browser_test/icons/badusb_10px.png b/applications/debug/file_browser_test/icons/badusb_10px.png
new file mode 100644
index 00000000000..037474aa3bc
Binary files /dev/null and b/applications/debug/file_browser_test/icons/badusb_10px.png differ
diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c b/applications/debug/file_browser_test/scenes/file_browser_scene.c
similarity index 100%
rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene.c
rename to applications/debug/file_browser_test/scenes/file_browser_scene.c
diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h b/applications/debug/file_browser_test/scenes/file_browser_scene.h
similarity index 100%
rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene.h
rename to applications/debug/file_browser_test/scenes/file_browser_scene.h
diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c b/applications/debug/file_browser_test/scenes/file_browser_scene_browser.c
similarity index 92%
rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c
rename to applications/debug/file_browser_test/scenes/file_browser_scene_browser.c
index d9bd1ac5314..0bf70e9c63f 100644
--- a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c
+++ b/applications/debug/file_browser_test/scenes/file_browser_scene_browser.c
@@ -1,8 +1,5 @@
#include "../file_browser_app_i.h"
-#include
-#include
-#include "furi_hal.h"
-#include "m-string.h"
+#include
#define DEFAULT_PATH "/"
#define EXTENSION "*"
diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h b/applications/debug/file_browser_test/scenes/file_browser_scene_config.h
similarity index 100%
rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h
rename to applications/debug/file_browser_test/scenes/file_browser_scene_config.h
diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c b/applications/debug/file_browser_test/scenes/file_browser_scene_result.c
similarity index 84%
rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c
rename to applications/debug/file_browser_test/scenes/file_browser_scene_result.c
index 53576cef4b4..4c68d80f773 100644
--- a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c
+++ b/applications/debug/file_browser_test/scenes/file_browser_scene_result.c
@@ -1,6 +1,5 @@
#include "../file_browser_app_i.h"
-#include "furi_hal.h"
-#include "m-string.h"
+#include
void file_browser_scene_result_ok_callback(InputType type, void* context) {
furi_assert(context);
@@ -24,7 +23,13 @@ void file_browser_scene_result_on_enter(void* context) {
FileBrowserApp* app = context;
widget_add_string_multiline_element(
- app->widget, 64, 10, AlignCenter, AlignTop, FontSecondary, string_get_cstr(app->file_path));
+ app->widget,
+ 64,
+ 10,
+ AlignCenter,
+ AlignTop,
+ FontSecondary,
+ furi_string_get_cstr(app->file_path));
view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewResult);
}
diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c b/applications/debug/file_browser_test/scenes/file_browser_scene_start.c
similarity index 94%
rename from applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c
rename to applications/debug/file_browser_test/scenes/file_browser_scene_start.c
index b3381f0ad0e..9eb26944ffc 100644
--- a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c
+++ b/applications/debug/file_browser_test/scenes/file_browser_scene_start.c
@@ -19,7 +19,7 @@ bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
- string_set_str(app->file_path, ANY_PATH("badusb/demo_windows.txt"));
+ furi_string_set(app->file_path, ANY_PATH("badusb/demo_windows.txt"));
scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser);
consumed = true;
} else if(event.type == SceneManagerEventTypeTick) {
diff --git a/applications/debug/keypad_test/application.fam b/applications/debug/keypad_test/application.fam
new file mode 100644
index 00000000000..90851950b22
--- /dev/null
+++ b/applications/debug/keypad_test/application.fam
@@ -0,0 +1,10 @@
+App(
+ appid="keypad_test",
+ name="Keypad Test",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="keypad_test_app",
+ requires=["gui"],
+ stack_size=1 * 1024,
+ order=30,
+ fap_category="Debug",
+)
diff --git a/applications/debug_tools/keypad_test.c b/applications/debug/keypad_test/keypad_test.c
similarity index 78%
rename from applications/debug_tools/keypad_test.c
rename to applications/debug/keypad_test/keypad_test.c
index 2470baf8d2d..9e8881defaf 100644
--- a/applications/debug_tools/keypad_test.c
+++ b/applications/debug/keypad_test/keypad_test.c
@@ -11,6 +11,7 @@ typedef struct {
uint16_t left;
uint16_t right;
uint16_t ok;
+ FuriMutex* mutex;
} KeypadTestState;
static void keypad_test_reset_state(KeypadTestState* state) {
@@ -22,7 +23,8 @@ static void keypad_test_reset_state(KeypadTestState* state) {
}
static void keypad_test_render_callback(Canvas* canvas, void* ctx) {
- KeypadTestState* state = (KeypadTestState*)acquire_mutex((ValueMutex*)ctx, 25);
+ KeypadTestState* state = ctx;
+ furi_mutex_acquire(state->mutex, FuriWaitForever);
canvas_clear(canvas);
char strings[5][20];
@@ -51,7 +53,7 @@ static void keypad_test_render_callback(Canvas* canvas, void* ctx) {
canvas_draw_str(canvas, 10, 63, "[back] - reset, hold to exit");
- release_mutex((ValueMutex*)ctx, state);
+ furi_mutex_release(state->mutex);
}
static void keypad_test_input_callback(InputEvent* input_event, void* ctx) {
@@ -64,17 +66,17 @@ int32_t keypad_test_app(void* p) {
FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent));
furi_check(event_queue);
- KeypadTestState _state = {{false, false, false, false, false}, 0, 0, 0, 0, 0};
+ KeypadTestState state = {{false, false, false, false, false}, 0, 0, 0, 0, 0, NULL};
+ state.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
- ValueMutex state_mutex;
- if(!init_mutex(&state_mutex, &_state, sizeof(KeypadTestState))) {
+ if(!state.mutex) {
FURI_LOG_E(TAG, "cannot create mutex");
return 0;
}
ViewPort* view_port = view_port_alloc();
- view_port_draw_callback_set(view_port, keypad_test_render_callback, &state_mutex);
+ view_port_draw_callback_set(view_port, keypad_test_render_callback, &state);
view_port_input_callback_set(view_port, keypad_test_input_callback, event_queue);
// Open GUI and register view_port
@@ -83,7 +85,7 @@ int32_t keypad_test_app(void* p) {
InputEvent event;
while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) {
- KeypadTestState* state = (KeypadTestState*)acquire_mutex_block(&state_mutex);
+ furi_mutex_acquire(state.mutex, FuriWaitForever);
FURI_LOG_I(
TAG,
"key: %s type: %s",
@@ -92,54 +94,54 @@ int32_t keypad_test_app(void* p) {
if(event.key == InputKeyRight) {
if(event.type == InputTypePress) {
- state->press[0] = true;
+ state.press[0] = true;
} else if(event.type == InputTypeRelease) {
- state->press[0] = false;
+ state.press[0] = false;
} else if(event.type == InputTypeShort) {
- ++state->right;
+ ++state.right;
}
} else if(event.key == InputKeyLeft) {
if(event.type == InputTypePress) {
- state->press[1] = true;
+ state.press[1] = true;
} else if(event.type == InputTypeRelease) {
- state->press[1] = false;
+ state.press[1] = false;
} else if(event.type == InputTypeShort) {
- ++state->left;
+ ++state.left;
}
} else if(event.key == InputKeyUp) {
if(event.type == InputTypePress) {
- state->press[2] = true;
+ state.press[2] = true;
} else if(event.type == InputTypeRelease) {
- state->press[2] = false;
+ state.press[2] = false;
} else if(event.type == InputTypeShort) {
- ++state->up;
+ ++state.up;
}
} else if(event.key == InputKeyDown) {
if(event.type == InputTypePress) {
- state->press[3] = true;
+ state.press[3] = true;
} else if(event.type == InputTypeRelease) {
- state->press[3] = false;
+ state.press[3] = false;
} else if(event.type == InputTypeShort) {
- ++state->down;
+ ++state.down;
}
} else if(event.key == InputKeyOk) {
if(event.type == InputTypePress) {
- state->press[4] = true;
+ state.press[4] = true;
} else if(event.type == InputTypeRelease) {
- state->press[4] = false;
+ state.press[4] = false;
} else if(event.type == InputTypeShort) {
- ++state->ok;
+ ++state.ok;
}
} else if(event.key == InputKeyBack) {
if(event.type == InputTypeLong) {
- release_mutex(&state_mutex, state);
+ furi_mutex_release(state.mutex);
break;
} else if(event.type == InputTypeShort) {
- keypad_test_reset_state(state);
+ keypad_test_reset_state(&state);
}
}
- release_mutex(&state_mutex, state);
+ furi_mutex_release(state.mutex);
view_port_update(view_port);
}
@@ -147,7 +149,7 @@ int32_t keypad_test_app(void* p) {
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
- delete_mutex(&state_mutex);
+ furi_mutex_free(state.mutex);
furi_record_close(RECORD_GUI);
diff --git a/applications/debug/lfrfid_debug/application.fam b/applications/debug/lfrfid_debug/application.fam
new file mode 100644
index 00000000000..323f77818fc
--- /dev/null
+++ b/applications/debug/lfrfid_debug/application.fam
@@ -0,0 +1,16 @@
+App(
+ appid="lfrfid_debug",
+ name="LF-RFID Debug",
+ apptype=FlipperAppType.DEBUG,
+ targets=["f7"],
+ entry_point="lfrfid_debug_app",
+ requires=[
+ "gui",
+ ],
+ provides=[
+ "lfrfid_debug",
+ ],
+ stack_size=1 * 1024,
+ order=100,
+ fap_category="Debug",
+)
diff --git a/applications/debug/lfrfid_debug/lfrfid_debug.c b/applications/debug/lfrfid_debug/lfrfid_debug.c
new file mode 100644
index 00000000000..63d66b68b9c
--- /dev/null
+++ b/applications/debug/lfrfid_debug/lfrfid_debug.c
@@ -0,0 +1,81 @@
+#include "lfrfid_debug_i.h"
+
+static bool lfrfid_debug_custom_event_callback(void* context, uint32_t event) {
+ furi_assert(context);
+ LfRfidDebug* app = context;
+ return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool lfrfid_debug_back_event_callback(void* context) {
+ furi_assert(context);
+ LfRfidDebug* app = context;
+ return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static LfRfidDebug* lfrfid_debug_alloc() {
+ LfRfidDebug* app = malloc(sizeof(LfRfidDebug));
+
+ app->view_dispatcher = view_dispatcher_alloc();
+ app->scene_manager = scene_manager_alloc(&lfrfid_debug_scene_handlers, app);
+ view_dispatcher_enable_queue(app->view_dispatcher);
+ view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+ view_dispatcher_set_custom_event_callback(
+ app->view_dispatcher, lfrfid_debug_custom_event_callback);
+ view_dispatcher_set_navigation_event_callback(
+ app->view_dispatcher, lfrfid_debug_back_event_callback);
+
+ // Open GUI record
+ app->gui = furi_record_open(RECORD_GUI);
+ view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+ // Submenu
+ app->submenu = submenu_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, LfRfidDebugViewSubmenu, submenu_get_view(app->submenu));
+
+ // Tune view
+ app->tune_view = lfrfid_debug_view_tune_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ LfRfidDebugViewTune,
+ lfrfid_debug_view_tune_get_view(app->tune_view));
+
+ return app;
+}
+
+static void lfrfid_debug_free(LfRfidDebug* app) {
+ furi_assert(app);
+
+ // Submenu
+ view_dispatcher_remove_view(app->view_dispatcher, LfRfidDebugViewSubmenu);
+ submenu_free(app->submenu);
+
+ // Tune view
+ view_dispatcher_remove_view(app->view_dispatcher, LfRfidDebugViewTune);
+ lfrfid_debug_view_tune_free(app->tune_view);
+
+ // View Dispatcher
+ view_dispatcher_free(app->view_dispatcher);
+
+ // Scene Manager
+ scene_manager_free(app->scene_manager);
+
+ // GUI
+ furi_record_close(RECORD_GUI);
+ app->gui = NULL;
+
+ free(app);
+}
+
+int32_t lfrfid_debug_app(void* p) {
+ UNUSED(p);
+ LfRfidDebug* app = lfrfid_debug_alloc();
+
+ scene_manager_next_scene(app->scene_manager, LfRfidDebugSceneStart);
+
+ view_dispatcher_run(app->view_dispatcher);
+
+ lfrfid_debug_free(app);
+
+ return 0;
+}
\ No newline at end of file
diff --git a/applications/debug/lfrfid_debug/lfrfid_debug_i.h b/applications/debug/lfrfid_debug/lfrfid_debug_i.h
new file mode 100644
index 00000000000..8e4e72388f1
--- /dev/null
+++ b/applications/debug/lfrfid_debug/lfrfid_debug_i.h
@@ -0,0 +1,30 @@
+#pragma once
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "views/lfrfid_debug_view_tune.h"
+#include "scenes/lfrfid_debug_scene.h"
+
+typedef struct LfRfidDebug LfRfidDebug;
+
+struct LfRfidDebug {
+ Gui* gui;
+ ViewDispatcher* view_dispatcher;
+ SceneManager* scene_manager;
+
+ // Common Views
+ Submenu* submenu;
+ LfRfidTuneView* tune_view;
+};
+
+typedef enum {
+ LfRfidDebugViewSubmenu,
+ LfRfidDebugViewTune,
+} LfRfidDebugView;
diff --git a/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c
new file mode 100644
index 00000000000..2fc6706e38c
--- /dev/null
+++ b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_start.c
@@ -0,0 +1,44 @@
+#include "../lfrfid_debug_i.h"
+
+typedef enum {
+ SubmenuIndexTune,
+} SubmenuIndex;
+
+static void lfrfid_debug_scene_start_submenu_callback(void* context, uint32_t index) {
+ LfRfidDebug* app = context;
+
+ view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void lfrfid_debug_scene_start_on_enter(void* context) {
+ LfRfidDebug* app = context;
+ Submenu* submenu = app->submenu;
+
+ submenu_add_item(
+ submenu, "Tune", SubmenuIndexTune, lfrfid_debug_scene_start_submenu_callback, app);
+
+ submenu_set_selected_item(
+ submenu, scene_manager_get_scene_state(app->scene_manager, LfRfidDebugSceneStart));
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidDebugViewSubmenu);
+}
+
+bool lfrfid_debug_scene_start_on_event(void* context, SceneManagerEvent event) {
+ LfRfidDebug* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == SubmenuIndexTune) {
+ scene_manager_next_scene(app->scene_manager, LfRfidDebugSceneTune);
+ consumed = true;
+ }
+ }
+
+ return consumed;
+}
+
+void lfrfid_debug_scene_start_on_exit(void* context) {
+ LfRfidDebug* app = context;
+
+ submenu_reset(app->submenu);
+}
diff --git a/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c
new file mode 100644
index 00000000000..ac2e2b80611
--- /dev/null
+++ b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_app_scene_tune.c
@@ -0,0 +1,52 @@
+#include "../lfrfid_debug_i.h"
+#include
+
+static void comparator_trigger_callback(bool level, void* comp_ctx) {
+ UNUSED(comp_ctx);
+ furi_hal_gpio_write(&gpio_ext_pa7, !level);
+}
+
+void lfrfid_debug_view_tune_callback(void* context) {
+ LfRfidDebug* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, 0xBA);
+}
+
+void lfrfid_debug_scene_tune_on_enter(void* context) {
+ LfRfidDebug* app = context;
+
+ furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull);
+
+ furi_hal_rfid_comp_set_callback(comparator_trigger_callback, app);
+ furi_hal_rfid_comp_start();
+
+ furi_hal_rfid_tim_read_start(125000, 0.5);
+
+ lfrfid_debug_view_tune_set_callback(app->tune_view, lfrfid_debug_view_tune_callback, app);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, LfRfidDebugViewTune);
+}
+
+bool lfrfid_debug_scene_tune_on_event(void* context, SceneManagerEvent event) {
+ UNUSED(event);
+
+ LfRfidDebug* app = context;
+ bool consumed = false;
+
+ if(lfrfid_debug_view_tune_is_dirty(app->tune_view)) {
+ furi_hal_rfid_set_read_period(lfrfid_debug_view_tune_get_arr(app->tune_view));
+ furi_hal_rfid_set_read_pulse(lfrfid_debug_view_tune_get_ccr(app->tune_view));
+ }
+
+ return consumed;
+}
+
+void lfrfid_debug_scene_tune_on_exit(void* context) {
+ UNUSED(context);
+
+ furi_hal_rfid_comp_stop();
+ furi_hal_rfid_comp_set_callback(NULL, NULL);
+
+ furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog);
+ furi_hal_rfid_tim_read_stop();
+ furi_hal_rfid_pins_reset();
+}
diff --git a/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene.c b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene.c
new file mode 100644
index 00000000000..e6288e923b2
--- /dev/null
+++ b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene.c
@@ -0,0 +1,30 @@
+#include "lfrfid_debug_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const lfrfid_debug_on_enter_handlers[])(void*) = {
+#include "lfrfid_debug_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const lfrfid_debug_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "lfrfid_debug_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const lfrfid_debug_on_exit_handlers[])(void* context) = {
+#include "lfrfid_debug_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers lfrfid_debug_scene_handlers = {
+ .on_enter_handlers = lfrfid_debug_on_enter_handlers,
+ .on_event_handlers = lfrfid_debug_on_event_handlers,
+ .on_exit_handlers = lfrfid_debug_on_exit_handlers,
+ .scene_num = LfRfidDebugSceneNum,
+};
diff --git a/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene.h b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene.h
new file mode 100644
index 00000000000..8fc74f72407
--- /dev/null
+++ b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) LfRfidDebugScene##id,
+typedef enum {
+#include "lfrfid_debug_scene_config.h"
+ LfRfidDebugSceneNum,
+} LfRfidDebugScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers lfrfid_debug_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "lfrfid_debug_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+ bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "lfrfid_debug_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "lfrfid_debug_scene_config.h"
+#undef ADD_SCENE
diff --git a/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene_config.h b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene_config.h
new file mode 100644
index 00000000000..f3cca47b88d
--- /dev/null
+++ b/applications/debug/lfrfid_debug/scenes/lfrfid_debug_scene_config.h
@@ -0,0 +1,2 @@
+ADD_SCENE(lfrfid_debug, start, Start)
+ADD_SCENE(lfrfid_debug, tune, Tune)
diff --git a/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c
new file mode 100644
index 00000000000..9e48a7e27fe
--- /dev/null
+++ b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.c
@@ -0,0 +1,252 @@
+#include "lfrfid_debug_view_tune.h"
+#include
+
+#define TEMP_STR_LEN 128
+
+struct LfRfidTuneView {
+ View* view;
+};
+
+typedef struct {
+ bool dirty;
+ bool fine;
+ uint32_t ARR;
+ uint32_t CCR;
+ int pos;
+ void (*update_callback)(void* context);
+ void* update_context;
+} LfRfidTuneViewModel;
+
+static void lfrfid_debug_view_tune_draw_callback(Canvas* canvas, void* _model) {
+ LfRfidTuneViewModel* model = _model;
+ canvas_set_color(canvas, ColorBlack);
+
+ if(model->fine) {
+ canvas_draw_box(
+ canvas,
+ 128 - canvas_string_width(canvas, "Fine") - 4,
+ 0,
+ canvas_string_width(canvas, "Fine") + 4,
+ canvas_current_font_height(canvas) + 1);
+ canvas_set_color(canvas, ColorWhite);
+ }
+ canvas_draw_str_aligned(canvas, 128 - 2, 2, AlignRight, AlignTop, "Fine");
+ canvas_set_color(canvas, ColorBlack);
+
+ char buffer[TEMP_STR_LEN + 1];
+ double freq = ((float)SystemCoreClock / ((float)model->ARR + 1));
+ double duty = ((float)model->CCR + 1) / ((float)model->ARR + 1) * 100.0f;
+ snprintf(
+ buffer,
+ TEMP_STR_LEN,
+ "%sARR: %lu\n"
+ "freq = %.4f\n"
+ "%sCCR: %lu\n"
+ "duty = %.4f",
+ model->pos == 0 ? ">" : "",
+ model->ARR,
+ freq,
+ model->pos == 1 ? ">" : "",
+ model->CCR,
+ duty);
+ elements_multiline_text_aligned(canvas, 2, 2, AlignLeft, AlignTop, buffer);
+}
+
+static void lfrfid_debug_view_tune_button_up(LfRfidTuneView* tune_view) {
+ with_view_model(
+ tune_view->view,
+ LfRfidTuneViewModel * model,
+ {
+ if(model->pos > 0) model->pos--;
+ },
+ true);
+}
+
+static void lfrfid_debug_view_tune_button_down(LfRfidTuneView* tune_view) {
+ with_view_model(
+ tune_view->view,
+ LfRfidTuneViewModel * model,
+ {
+ if(model->pos < 1) model->pos++;
+ },
+ true);
+}
+
+static void lfrfid_debug_view_tune_button_left(LfRfidTuneView* tune_view) {
+ with_view_model(
+ tune_view->view,
+ LfRfidTuneViewModel * model,
+ {
+ if(model->pos == 0) {
+ if(model->fine) {
+ model->ARR -= 1;
+ } else {
+ model->ARR -= 10;
+ }
+ } else if(model->pos == 1) {
+ if(model->fine) {
+ model->CCR -= 1;
+ } else {
+ model->CCR -= 10;
+ }
+ }
+
+ model->dirty = true;
+ },
+ true);
+}
+
+static void lfrfid_debug_view_tune_button_right(LfRfidTuneView* tune_view) {
+ with_view_model(
+ tune_view->view,
+ LfRfidTuneViewModel * model,
+ {
+ if(model->pos == 0) {
+ if(model->fine) {
+ model->ARR += 1;
+ } else {
+ model->ARR += 10;
+ }
+ } else if(model->pos == 1) {
+ if(model->fine) {
+ model->CCR += 1;
+ } else {
+ model->CCR += 10;
+ }
+ }
+
+ model->dirty = true;
+ },
+ true);
+}
+
+static void lfrfid_debug_view_tune_button_ok(LfRfidTuneView* tune_view) {
+ with_view_model(
+ tune_view->view, LfRfidTuneViewModel * model, { model->fine = !model->fine; }, true);
+}
+
+static bool lfrfid_debug_view_tune_input_callback(InputEvent* event, void* context) {
+ LfRfidTuneView* tune_view = context;
+ bool consumed = false;
+
+ // Process key presses only
+ if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
+ consumed = true;
+
+ switch(event->key) {
+ case InputKeyLeft:
+ lfrfid_debug_view_tune_button_left(tune_view);
+ break;
+ case InputKeyRight:
+ lfrfid_debug_view_tune_button_right(tune_view);
+ break;
+ case InputKeyUp:
+ lfrfid_debug_view_tune_button_up(tune_view);
+ break;
+ case InputKeyDown:
+ lfrfid_debug_view_tune_button_down(tune_view);
+ break;
+ case InputKeyOk:
+ lfrfid_debug_view_tune_button_ok(tune_view);
+ break;
+ default:
+ consumed = false;
+ break;
+ }
+
+ if(event->key == InputKeyLeft || event->key == InputKeyRight) {
+ with_view_model(
+ tune_view->view,
+ LfRfidTuneViewModel * model,
+ {
+ if(model->update_callback) {
+ model->update_callback(model->update_context);
+ }
+ },
+ false);
+ }
+ }
+
+ return consumed;
+}
+
+LfRfidTuneView* lfrfid_debug_view_tune_alloc() {
+ LfRfidTuneView* tune_view = malloc(sizeof(LfRfidTuneView));
+ tune_view->view = view_alloc();
+ view_set_context(tune_view->view, tune_view);
+ view_allocate_model(tune_view->view, ViewModelTypeLocking, sizeof(LfRfidTuneViewModel));
+ lfrfid_debug_view_tune_clean(tune_view);
+ view_set_draw_callback(tune_view->view, lfrfid_debug_view_tune_draw_callback);
+ view_set_input_callback(tune_view->view, lfrfid_debug_view_tune_input_callback);
+
+ return tune_view;
+}
+
+void lfrfid_debug_view_tune_free(LfRfidTuneView* tune_view) {
+ view_free(tune_view->view);
+ free(tune_view);
+}
+
+View* lfrfid_debug_view_tune_get_view(LfRfidTuneView* tune_view) {
+ return tune_view->view;
+}
+
+void lfrfid_debug_view_tune_clean(LfRfidTuneView* tune_view) {
+ with_view_model(
+ tune_view->view,
+ LfRfidTuneViewModel * model,
+ {
+ model->dirty = true;
+ model->fine = false;
+ model->ARR = 511;
+ model->CCR = 255;
+ model->pos = 0;
+ model->update_callback = NULL;
+ model->update_context = NULL;
+ },
+ true);
+}
+
+bool lfrfid_debug_view_tune_is_dirty(LfRfidTuneView* tune_view) {
+ bool result = false;
+ with_view_model(
+ tune_view->view,
+ LfRfidTuneViewModel * model,
+ {
+ result = model->dirty;
+ model->dirty = false;
+ },
+ false);
+
+ return result;
+}
+
+uint32_t lfrfid_debug_view_tune_get_arr(LfRfidTuneView* tune_view) {
+ uint32_t result = false;
+ with_view_model(
+ tune_view->view, LfRfidTuneViewModel * model, { result = model->ARR; }, false);
+
+ return result;
+}
+
+uint32_t lfrfid_debug_view_tune_get_ccr(LfRfidTuneView* tune_view) {
+ uint32_t result = false;
+ with_view_model(
+ tune_view->view, LfRfidTuneViewModel * model, { result = model->CCR; }, false);
+
+ return result;
+}
+
+void lfrfid_debug_view_tune_set_callback(
+ LfRfidTuneView* tune_view,
+ void (*callback)(void* context),
+ void* context) {
+ with_view_model(
+ tune_view->view,
+ LfRfidTuneViewModel * model,
+ {
+ model->update_callback = callback;
+ model->update_context = context;
+ },
+ false);
+}
diff --git a/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.h b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.h
new file mode 100644
index 00000000000..be54b63f9a8
--- /dev/null
+++ b/applications/debug/lfrfid_debug/views/lfrfid_debug_view_tune.h
@@ -0,0 +1,23 @@
+#pragma once
+#include
+
+typedef struct LfRfidTuneView LfRfidTuneView;
+
+LfRfidTuneView* lfrfid_debug_view_tune_alloc();
+
+void lfrfid_debug_view_tune_free(LfRfidTuneView* tune_view);
+
+View* lfrfid_debug_view_tune_get_view(LfRfidTuneView* tune_view);
+
+void lfrfid_debug_view_tune_clean(LfRfidTuneView* tune_view);
+
+bool lfrfid_debug_view_tune_is_dirty(LfRfidTuneView* tune_view);
+
+uint32_t lfrfid_debug_view_tune_get_arr(LfRfidTuneView* tune_view);
+
+uint32_t lfrfid_debug_view_tune_get_ccr(LfRfidTuneView* tune_view);
+
+void lfrfid_debug_view_tune_set_callback(
+ LfRfidTuneView* tune_view,
+ void (*callback)(void* context),
+ void* context);
diff --git a/applications/debug/locale_test/application.fam b/applications/debug/locale_test/application.fam
new file mode 100644
index 00000000000..d341122f99d
--- /dev/null
+++ b/applications/debug/locale_test/application.fam
@@ -0,0 +1,10 @@
+App(
+ appid="locale_test",
+ name="Locale Test",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="locale_test_app",
+ requires=["gui", "locale"],
+ stack_size=2 * 1024,
+ order=70,
+ fap_category="Debug",
+)
diff --git a/applications/debug/locale_test/locale_test.c b/applications/debug/locale_test/locale_test.c
new file mode 100644
index 00000000000..003df55dca4
--- /dev/null
+++ b/applications/debug/locale_test/locale_test.c
@@ -0,0 +1,102 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+typedef struct {
+ Gui* gui;
+ ViewDispatcher* view_dispatcher;
+ View* view;
+} LocaleTestApp;
+
+static void locale_test_view_draw_callback(Canvas* canvas, void* _model) {
+ UNUSED(_model);
+
+ // Prepare canvas
+ canvas_set_color(canvas, ColorBlack);
+ canvas_set_font(canvas, FontSecondary);
+
+ FuriString* tmp_string = furi_string_alloc();
+
+ float temp = 25.3f;
+ LocaleMeasurementUnits units = locale_get_measurement_unit();
+ if(units == LocaleMeasurementUnitsMetric) {
+ furi_string_printf(tmp_string, "Temp: %5.1fC", (double)temp);
+ } else {
+ temp = locale_celsius_to_fahrenheit(temp);
+ furi_string_printf(tmp_string, "Temp: %5.1fF", (double)temp);
+ }
+ canvas_draw_str(canvas, 0, 10, furi_string_get_cstr(tmp_string));
+
+ FuriHalRtcDateTime datetime;
+ furi_hal_rtc_get_datetime(&datetime);
+
+ locale_format_time(tmp_string, &datetime, locale_get_time_format(), false);
+ canvas_draw_str(canvas, 0, 25, furi_string_get_cstr(tmp_string));
+
+ locale_format_date(tmp_string, &datetime, locale_get_date_format(), "/");
+ canvas_draw_str(canvas, 0, 40, furi_string_get_cstr(tmp_string));
+
+ furi_string_free(tmp_string);
+}
+
+static bool locale_test_view_input_callback(InputEvent* event, void* context) {
+ UNUSED(event);
+ UNUSED(context);
+ return false;
+}
+
+static uint32_t locale_test_exit(void* context) {
+ UNUSED(context);
+ return VIEW_NONE;
+}
+
+static LocaleTestApp* locale_test_alloc() {
+ LocaleTestApp* app = malloc(sizeof(LocaleTestApp));
+
+ // Gui
+ app->gui = furi_record_open(RECORD_GUI);
+
+ // View dispatcher
+ app->view_dispatcher = view_dispatcher_alloc();
+ view_dispatcher_enable_queue(app->view_dispatcher);
+ view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+ // Views
+ app->view = view_alloc();
+ view_set_draw_callback(app->view, locale_test_view_draw_callback);
+ view_set_input_callback(app->view, locale_test_view_input_callback);
+
+ view_set_previous_callback(app->view, locale_test_exit);
+ view_dispatcher_add_view(app->view_dispatcher, 0, app->view);
+ view_dispatcher_switch_to_view(app->view_dispatcher, 0);
+
+ return app;
+}
+
+static void locale_test_free(LocaleTestApp* app) {
+ furi_assert(app);
+
+ // Free views
+ view_dispatcher_remove_view(app->view_dispatcher, 0);
+
+ view_free(app->view);
+ view_dispatcher_free(app->view_dispatcher);
+
+ // Close gui record
+ furi_record_close(RECORD_GUI);
+ app->gui = NULL;
+
+ // Free rest
+ free(app);
+}
+
+int32_t locale_test_app(void* p) {
+ UNUSED(p);
+ LocaleTestApp* app = locale_test_alloc();
+ view_dispatcher_run(app->view_dispatcher);
+ locale_test_free(app);
+ return 0;
+}
diff --git a/applications/debug/rpc_debug_app/application.fam b/applications/debug/rpc_debug_app/application.fam
new file mode 100644
index 00000000000..d71065afae4
--- /dev/null
+++ b/applications/debug/rpc_debug_app/application.fam
@@ -0,0 +1,10 @@
+App(
+ appid="rpc_debug",
+ name="RPC Debug",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="rpc_debug_app",
+ requires=["gui", "rpc_start", "notification"],
+ stack_size=2 * 1024,
+ order=10,
+ fap_category="Debug",
+)
diff --git a/applications/debug/rpc_debug_app/rpc_debug_app.c b/applications/debug/rpc_debug_app/rpc_debug_app.c
new file mode 100644
index 00000000000..2a0756383c2
--- /dev/null
+++ b/applications/debug/rpc_debug_app/rpc_debug_app.c
@@ -0,0 +1,167 @@
+#include "rpc_debug_app.h"
+#include
+
+#include
+
+static bool rpc_debug_app_custom_event_callback(void* context, uint32_t event) {
+ furi_assert(context);
+ RpcDebugApp* app = context;
+ return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool rpc_debug_app_back_event_callback(void* context) {
+ furi_assert(context);
+ RpcDebugApp* app = context;
+ return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static void rpc_debug_app_tick_event_callback(void* context) {
+ furi_assert(context);
+ RpcDebugApp* app = context;
+ scene_manager_handle_tick_event(app->scene_manager);
+}
+
+static void
+ rpc_debug_app_format_hex(const uint8_t* data, size_t data_size, char* buf, size_t buf_size) {
+ if(data == NULL || data_size == 0) {
+ strncpy(buf, "", buf_size);
+ return;
+ }
+
+ const size_t byte_width = 3;
+ const size_t line_width = 7;
+
+ data_size = MIN(data_size, buf_size / (byte_width + 1));
+
+ for(size_t i = 0; i < data_size; ++i) {
+ char* p = buf + (i * byte_width);
+ char sep = !((i + 1) % line_width) ? '\n' : ' ';
+ snprintf(p, byte_width + 1, "%02X%c", data[i], sep);
+ }
+
+ buf[buf_size - 1] = '\0';
+}
+
+static void rpc_debug_app_rpc_command_callback(const RpcAppSystemEvent* event, void* context) {
+ furi_assert(context);
+ RpcDebugApp* app = context;
+ furi_assert(app->rpc);
+
+ if(event->type == RpcAppEventTypeSessionClose) {
+ scene_manager_stop(app->scene_manager);
+ view_dispatcher_stop(app->view_dispatcher);
+ rpc_system_app_set_callback(app->rpc, NULL, NULL);
+ app->rpc = NULL;
+ } else if(event->type == RpcAppEventTypeAppExit) {
+ scene_manager_stop(app->scene_manager);
+ view_dispatcher_stop(app->view_dispatcher);
+ rpc_system_app_confirm(app->rpc, true);
+ } else if(event->type == RpcAppEventTypeDataExchange) {
+ furi_assert(event->data.type == RpcAppSystemEventDataTypeBytes);
+
+ rpc_debug_app_format_hex(
+ event->data.bytes.ptr, event->data.bytes.size, app->text_store, TEXT_STORE_SIZE);
+
+ view_dispatcher_send_custom_event(
+ app->view_dispatcher, RpcDebugAppCustomEventRpcDataExchange);
+ } else {
+ rpc_system_app_confirm(app->rpc, false);
+ }
+}
+
+static bool rpc_debug_app_rpc_init_rpc(RpcDebugApp* app, const char* args) {
+ bool ret = false;
+ if(args && strlen(args)) {
+ uint32_t rpc = 0;
+ if(sscanf(args, "RPC %lX", &rpc) == 1) {
+ app->rpc = (RpcAppSystem*)rpc;
+ rpc_system_app_set_callback(app->rpc, rpc_debug_app_rpc_command_callback, app);
+ rpc_system_app_send_started(app->rpc);
+ ret = true;
+ }
+ }
+ return ret;
+}
+
+static RpcDebugApp* rpc_debug_app_alloc() {
+ RpcDebugApp* app = malloc(sizeof(RpcDebugApp));
+
+ app->gui = furi_record_open(RECORD_GUI);
+ app->notifications = furi_record_open(RECORD_NOTIFICATION);
+ app->scene_manager = scene_manager_alloc(&rpc_debug_app_scene_handlers, app);
+ app->view_dispatcher = view_dispatcher_alloc();
+
+ view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+ view_dispatcher_set_custom_event_callback(
+ app->view_dispatcher, rpc_debug_app_custom_event_callback);
+ view_dispatcher_set_navigation_event_callback(
+ app->view_dispatcher, rpc_debug_app_back_event_callback);
+ view_dispatcher_set_tick_event_callback(
+ app->view_dispatcher, rpc_debug_app_tick_event_callback, 100);
+ view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+ view_dispatcher_enable_queue(app->view_dispatcher);
+
+ app->widget = widget_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, RpcDebugAppViewWidget, widget_get_view(app->widget));
+ app->submenu = submenu_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, RpcDebugAppViewSubmenu, submenu_get_view(app->submenu));
+ app->text_box = text_box_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, RpcDebugAppViewTextBox, text_box_get_view(app->text_box));
+ app->text_input = text_input_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, RpcDebugAppViewTextInput, text_input_get_view(app->text_input));
+ app->byte_input = byte_input_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, RpcDebugAppViewByteInput, byte_input_get_view(app->byte_input));
+
+ return app;
+}
+
+static void rpc_debug_app_free(RpcDebugApp* app) {
+ view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewByteInput);
+ view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewTextInput);
+ view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewTextBox);
+ view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewSubmenu);
+ view_dispatcher_remove_view(app->view_dispatcher, RpcDebugAppViewWidget);
+
+ free(app->byte_input);
+ free(app->text_input);
+ free(app->text_box);
+ free(app->submenu);
+ free(app->widget);
+
+ free(app->scene_manager);
+ free(app->view_dispatcher);
+
+ furi_record_close(RECORD_NOTIFICATION);
+ app->notifications = NULL;
+ furi_record_close(RECORD_GUI);
+ app->gui = NULL;
+
+ if(app->rpc) {
+ rpc_system_app_set_callback(app->rpc, NULL, NULL);
+ rpc_system_app_send_exited(app->rpc);
+ app->rpc = NULL;
+ }
+
+ free(app);
+}
+
+int32_t rpc_debug_app(void* args) {
+ RpcDebugApp* app = rpc_debug_app_alloc();
+
+ if(rpc_debug_app_rpc_init_rpc(app, args)) {
+ notification_message(app->notifications, &sequence_display_backlight_on);
+ scene_manager_next_scene(app->scene_manager, RpcDebugAppSceneStart);
+ } else {
+ scene_manager_next_scene(app->scene_manager, RpcDebugAppSceneStartDummy);
+ }
+
+ view_dispatcher_run(app->view_dispatcher);
+
+ rpc_debug_app_free(app);
+ return 0;
+}
diff --git a/applications/debug/rpc_debug_app/rpc_debug_app.h b/applications/debug/rpc_debug_app/rpc_debug_app.h
new file mode 100644
index 00000000000..100fc124cf7
--- /dev/null
+++ b/applications/debug/rpc_debug_app/rpc_debug_app.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "scenes/rpc_debug_app_scene.h"
+
+#define DATA_STORE_SIZE 64U
+#define TEXT_STORE_SIZE 64U
+
+typedef struct {
+ Gui* gui;
+ RpcAppSystem* rpc;
+ SceneManager* scene_manager;
+ ViewDispatcher* view_dispatcher;
+ NotificationApp* notifications;
+
+ Widget* widget;
+ Submenu* submenu;
+ TextBox* text_box;
+ TextInput* text_input;
+ ByteInput* byte_input;
+
+ char text_store[TEXT_STORE_SIZE];
+ uint8_t data_store[DATA_STORE_SIZE];
+} RpcDebugApp;
+
+typedef enum {
+ RpcDebugAppViewWidget,
+ RpcDebugAppViewSubmenu,
+ RpcDebugAppViewTextBox,
+ RpcDebugAppViewTextInput,
+ RpcDebugAppViewByteInput,
+} RpcDebugAppView;
+
+typedef enum {
+ // Reserve first 100 events for button types and indexes, starting from 0
+ RpcDebugAppCustomEventInputErrorCode = 100,
+ RpcDebugAppCustomEventInputErrorText,
+ RpcDebugAppCustomEventInputDataExchange,
+ RpcDebugAppCustomEventRpcDataExchange,
+} RpcDebugAppCustomEvent;
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.c
new file mode 100644
index 00000000000..2c593cdebcb
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.c
@@ -0,0 +1,30 @@
+#include "rpc_debug_app_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const rpc_debug_app_on_enter_handlers[])(void*) = {
+#include "rpc_debug_app_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const rpc_debug_app_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "rpc_debug_app_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const rpc_debug_app_on_exit_handlers[])(void* context) = {
+#include "rpc_debug_app_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers rpc_debug_app_scene_handlers = {
+ .on_enter_handlers = rpc_debug_app_on_enter_handlers,
+ .on_event_handlers = rpc_debug_app_on_event_handlers,
+ .on_exit_handlers = rpc_debug_app_on_exit_handlers,
+ .scene_num = RpcDebugAppSceneNum,
+};
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.h b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.h
new file mode 100644
index 00000000000..eb3424fb1e7
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) RpcDebugAppScene##id,
+typedef enum {
+#include "rpc_debug_app_scene_config.h"
+ RpcDebugAppSceneNum,
+} RpcDebugAppScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers rpc_debug_app_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "rpc_debug_app_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+ bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "rpc_debug_app_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "rpc_debug_app_scene_config.h"
+#undef ADD_SCENE
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_config.h b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_config.h
new file mode 100644
index 00000000000..8f163253dcd
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_config.h
@@ -0,0 +1,8 @@
+ADD_SCENE(rpc_debug_app, start, Start)
+ADD_SCENE(rpc_debug_app, start_dummy, StartDummy)
+ADD_SCENE(rpc_debug_app, test_app_error, TestAppError)
+ADD_SCENE(rpc_debug_app, test_data_exchange, TestDataExchange)
+ADD_SCENE(rpc_debug_app, input_error_code, InputErrorCode)
+ADD_SCENE(rpc_debug_app, input_error_text, InputErrorText)
+ADD_SCENE(rpc_debug_app, input_data_exchange, InputDataExchange)
+ADD_SCENE(rpc_debug_app, receive_data_exchange, ReceiveDataExchange)
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_data_exchange.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_data_exchange.c
new file mode 100644
index 00000000000..3ae2ba44d2c
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_data_exchange.c
@@ -0,0 +1,40 @@
+#include "../rpc_debug_app.h"
+
+static void rpc_debug_app_scene_input_data_exchange_result_callback(void* context) {
+ RpcDebugApp* app = context;
+ view_dispatcher_send_custom_event(
+ app->view_dispatcher, RpcDebugAppCustomEventInputDataExchange);
+}
+
+void rpc_debug_app_scene_input_data_exchange_on_enter(void* context) {
+ RpcDebugApp* app = context;
+ byte_input_set_header_text(app->byte_input, "Enter data to exchange");
+ byte_input_set_result_callback(
+ app->byte_input,
+ rpc_debug_app_scene_input_data_exchange_result_callback,
+ NULL,
+ app,
+ app->data_store,
+ DATA_STORE_SIZE);
+ view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewByteInput);
+}
+
+bool rpc_debug_app_scene_input_data_exchange_on_event(void* context, SceneManagerEvent event) {
+ RpcDebugApp* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == RpcDebugAppCustomEventInputDataExchange) {
+ rpc_system_app_exchange_data(app->rpc, app->data_store, DATA_STORE_SIZE);
+ scene_manager_previous_scene(app->scene_manager);
+ consumed = true;
+ }
+ }
+
+ return consumed;
+}
+
+void rpc_debug_app_scene_input_data_exchange_on_exit(void* context) {
+ RpcDebugApp* app = context;
+ UNUSED(app);
+}
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c
new file mode 100644
index 00000000000..367ca7a4ff1
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c
@@ -0,0 +1,64 @@
+#include "../rpc_debug_app.h"
+
+static bool rpc_debug_app_scene_input_error_code_validator_callback(
+ const char* text,
+ FuriString* error,
+ void* context) {
+ UNUSED(context);
+
+ for(; *text; ++text) {
+ const char c = *text;
+ if(c < '0' || c > '9') {
+ furi_string_printf(error, "%s", "Please enter\na number!");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void rpc_debug_app_scene_input_error_code_result_callback(void* context) {
+ RpcDebugApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventInputErrorCode);
+}
+
+void rpc_debug_app_scene_input_error_code_on_enter(void* context) {
+ RpcDebugApp* app = context;
+ strncpy(app->text_store, "666", TEXT_STORE_SIZE);
+ text_input_set_header_text(app->text_input, "Enter error code");
+ text_input_set_validator(
+ app->text_input, rpc_debug_app_scene_input_error_code_validator_callback, NULL);
+ text_input_set_result_callback(
+ app->text_input,
+ rpc_debug_app_scene_input_error_code_result_callback,
+ app,
+ app->text_store,
+ TEXT_STORE_SIZE,
+ true);
+ view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextInput);
+}
+
+bool rpc_debug_app_scene_input_error_code_on_event(void* context, SceneManagerEvent event) {
+ RpcDebugApp* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == RpcDebugAppCustomEventInputErrorCode) {
+ char* end;
+ int error_code = strtol(app->text_store, &end, 10);
+ if(!*end) {
+ rpc_system_app_set_error_code(app->rpc, error_code);
+ }
+ scene_manager_previous_scene(app->scene_manager);
+ consumed = true;
+ }
+ }
+
+ return consumed;
+}
+
+void rpc_debug_app_scene_input_error_code_on_exit(void* context) {
+ RpcDebugApp* app = context;
+ text_input_reset(app->text_input);
+ text_input_set_validator(app->text_input, NULL, NULL);
+}
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c
new file mode 100644
index 00000000000..b07f8f4af27
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_text.c
@@ -0,0 +1,40 @@
+#include "../rpc_debug_app.h"
+
+static void rpc_debug_app_scene_input_error_text_result_callback(void* context) {
+ RpcDebugApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, RpcDebugAppCustomEventInputErrorText);
+}
+
+void rpc_debug_app_scene_input_error_text_on_enter(void* context) {
+ RpcDebugApp* app = context;
+ strncpy(app->text_store, "I'm a scary error message!", TEXT_STORE_SIZE);
+ text_input_set_header_text(app->text_input, "Enter error text");
+ text_input_set_result_callback(
+ app->text_input,
+ rpc_debug_app_scene_input_error_text_result_callback,
+ app,
+ app->text_store,
+ TEXT_STORE_SIZE,
+ true);
+ view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextInput);
+}
+
+bool rpc_debug_app_scene_input_error_text_on_event(void* context, SceneManagerEvent event) {
+ RpcDebugApp* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == RpcDebugAppCustomEventInputErrorText) {
+ rpc_system_app_set_error_text(app->rpc, app->text_store);
+ scene_manager_previous_scene(app->scene_manager);
+ consumed = true;
+ }
+ }
+
+ return consumed;
+}
+
+void rpc_debug_app_scene_input_error_text_on_exit(void* context) {
+ RpcDebugApp* app = context;
+ text_input_reset(app->text_input);
+}
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c
new file mode 100644
index 00000000000..10d5e36f624
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_receive_data_exchange.c
@@ -0,0 +1,33 @@
+#include "../rpc_debug_app.h"
+
+void rpc_debug_app_scene_receive_data_exchange_on_enter(void* context) {
+ RpcDebugApp* app = context;
+ strncpy(app->text_store, "Received data will appear here...", TEXT_STORE_SIZE);
+
+ text_box_set_text(app->text_box, app->text_store);
+ text_box_set_font(app->text_box, TextBoxFontHex);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewTextBox);
+}
+
+bool rpc_debug_app_scene_receive_data_exchange_on_event(void* context, SceneManagerEvent event) {
+ RpcDebugApp* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == RpcDebugAppCustomEventRpcDataExchange) {
+ rpc_system_app_confirm(app->rpc, true);
+ notification_message(app->notifications, &sequence_blink_cyan_100);
+ notification_message(app->notifications, &sequence_display_backlight_on);
+ text_box_set_text(app->text_box, app->text_store);
+ consumed = true;
+ }
+ }
+
+ return consumed;
+}
+
+void rpc_debug_app_scene_receive_data_exchange_on_exit(void* context) {
+ RpcDebugApp* app = context;
+ text_box_reset(app->text_box);
+}
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start.c
new file mode 100644
index 00000000000..9461649ba8a
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start.c
@@ -0,0 +1,57 @@
+#include "../rpc_debug_app.h"
+
+enum SubmenuIndex {
+ SubmenuIndexTestAppError,
+ SubmenuIndexTestDataExchange,
+};
+
+static void rpc_debug_app_scene_start_submenu_callback(void* context, uint32_t index) {
+ RpcDebugApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void rpc_debug_app_scene_start_on_enter(void* context) {
+ RpcDebugApp* app = context;
+ Submenu* submenu = app->submenu;
+
+ submenu_add_item(
+ submenu,
+ "Test App Error",
+ SubmenuIndexTestAppError,
+ rpc_debug_app_scene_start_submenu_callback,
+ app);
+ submenu_add_item(
+ submenu,
+ "Test Data Exchange",
+ SubmenuIndexTestDataExchange,
+ rpc_debug_app_scene_start_submenu_callback,
+ app);
+
+ submenu_set_selected_item(submenu, SubmenuIndexTestAppError);
+ view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewSubmenu);
+}
+
+bool rpc_debug_app_scene_start_on_event(void* context, SceneManagerEvent event) {
+ RpcDebugApp* app = context;
+ SceneManager* scene_manager = app->scene_manager;
+
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ const uint32_t submenu_index = event.event;
+ if(submenu_index == SubmenuIndexTestAppError) {
+ scene_manager_next_scene(scene_manager, RpcDebugAppSceneTestAppError);
+ consumed = true;
+ } else if(submenu_index == SubmenuIndexTestDataExchange) {
+ scene_manager_next_scene(scene_manager, RpcDebugAppSceneTestDataExchange);
+ consumed = true;
+ }
+ }
+
+ return consumed;
+}
+
+void rpc_debug_app_scene_start_on_exit(void* context) {
+ RpcDebugApp* app = context;
+ submenu_reset(app->submenu);
+}
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start_dummy.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start_dummy.c
new file mode 100644
index 00000000000..5a30a079a3d
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_start_dummy.c
@@ -0,0 +1,30 @@
+#include "../rpc_debug_app.h"
+
+void rpc_debug_app_scene_start_dummy_on_enter(void* context) {
+ RpcDebugApp* app = context;
+ widget_add_text_box_element(
+ app->widget,
+ 0,
+ 0,
+ 128,
+ 64,
+ AlignCenter,
+ AlignCenter,
+ "This application\nis meant to be run\nin \e#RPC\e# mode.",
+ false);
+ view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewWidget);
+}
+
+bool rpc_debug_app_scene_start_dummy_on_event(void* context, SceneManagerEvent event) {
+ RpcDebugApp* app = context;
+ UNUSED(app);
+ UNUSED(event);
+
+ bool consumed = false;
+ return consumed;
+}
+
+void rpc_debug_app_scene_start_dummy_on_exit(void* context) {
+ RpcDebugApp* app = context;
+ widget_reset(app->widget);
+}
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_app_error.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_app_error.c
new file mode 100644
index 00000000000..865d475ae83
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_app_error.c
@@ -0,0 +1,57 @@
+#include "../rpc_debug_app.h"
+
+typedef enum {
+ SubmenuIndexSetErrorCode,
+ SubmenuIndexSetErrorText,
+} SubmenuIndex;
+
+static void rpc_debug_app_scene_test_app_error_submenu_callback(void* context, uint32_t index) {
+ RpcDebugApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void rpc_debug_app_scene_test_app_error_on_enter(void* context) {
+ RpcDebugApp* app = context;
+ Submenu* submenu = app->submenu;
+
+ submenu_add_item(
+ submenu,
+ "Set Error Code",
+ SubmenuIndexSetErrorCode,
+ rpc_debug_app_scene_test_app_error_submenu_callback,
+ app);
+ submenu_add_item(
+ submenu,
+ "Set Error Text",
+ SubmenuIndexSetErrorText,
+ rpc_debug_app_scene_test_app_error_submenu_callback,
+ app);
+
+ submenu_set_selected_item(submenu, SubmenuIndexSetErrorCode);
+ view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewSubmenu);
+}
+
+bool rpc_debug_app_scene_test_app_error_on_event(void* context, SceneManagerEvent event) {
+ RpcDebugApp* app = context;
+ SceneManager* scene_manager = app->scene_manager;
+
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ const uint32_t submenu_index = event.event;
+ if(submenu_index == SubmenuIndexSetErrorCode) {
+ scene_manager_next_scene(scene_manager, RpcDebugAppSceneInputErrorCode);
+ consumed = true;
+ } else if(submenu_index == SubmenuIndexSetErrorText) {
+ scene_manager_next_scene(scene_manager, RpcDebugAppSceneInputErrorText);
+ consumed = true;
+ }
+ }
+
+ return consumed;
+}
+
+void rpc_debug_app_scene_test_app_error_on_exit(void* context) {
+ RpcDebugApp* app = context;
+ submenu_reset(app->submenu);
+}
diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_data_exchange.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_data_exchange.c
new file mode 100644
index 00000000000..f90f480c2a9
--- /dev/null
+++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_test_data_exchange.c
@@ -0,0 +1,58 @@
+#include "../rpc_debug_app.h"
+
+typedef enum {
+ SubmenuIndexSendData,
+ SubmenuIndexReceiveData,
+} SubmenuIndex;
+
+static void
+ rpc_debug_app_scene_test_data_exchange_submenu_callback(void* context, uint32_t index) {
+ RpcDebugApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void rpc_debug_app_scene_test_data_exchange_on_enter(void* context) {
+ RpcDebugApp* app = context;
+ Submenu* submenu = app->submenu;
+
+ submenu_add_item(
+ submenu,
+ "Send Data",
+ SubmenuIndexSendData,
+ rpc_debug_app_scene_test_data_exchange_submenu_callback,
+ app);
+ submenu_add_item(
+ submenu,
+ "Receive Data",
+ SubmenuIndexReceiveData,
+ rpc_debug_app_scene_test_data_exchange_submenu_callback,
+ app);
+
+ submenu_set_selected_item(submenu, SubmenuIndexSendData);
+ view_dispatcher_switch_to_view(app->view_dispatcher, RpcDebugAppViewSubmenu);
+}
+
+bool rpc_debug_app_scene_test_data_exchange_on_event(void* context, SceneManagerEvent event) {
+ RpcDebugApp* app = context;
+ SceneManager* scene_manager = app->scene_manager;
+
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ const uint32_t submenu_index = event.event;
+ if(submenu_index == SubmenuIndexSendData) {
+ scene_manager_next_scene(scene_manager, RpcDebugAppSceneInputDataExchange);
+ consumed = true;
+ } else if(submenu_index == SubmenuIndexReceiveData) {
+ scene_manager_next_scene(scene_manager, RpcDebugAppSceneReceiveDataExchange);
+ consumed = true;
+ }
+ }
+
+ return consumed;
+}
+
+void rpc_debug_app_scene_test_data_exchange_on_exit(void* context) {
+ RpcDebugApp* app = context;
+ submenu_reset(app->submenu);
+}
diff --git a/applications/debug/speaker_debug/application.fam b/applications/debug/speaker_debug/application.fam
new file mode 100644
index 00000000000..68d8b188bdd
--- /dev/null
+++ b/applications/debug/speaker_debug/application.fam
@@ -0,0 +1,11 @@
+App(
+ appid="speaker_debug",
+ name="Speaker Debug",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="speaker_debug_app",
+ requires=["gui", "notification"],
+ stack_size=2 * 1024,
+ order=10,
+ fap_category="Debug",
+ fap_libs=["music_worker"],
+)
diff --git a/applications/debug/speaker_debug/speaker_debug.c b/applications/debug/speaker_debug/speaker_debug.c
new file mode 100644
index 00000000000..e01d5b8ec44
--- /dev/null
+++ b/applications/debug/speaker_debug/speaker_debug.c
@@ -0,0 +1,120 @@
+#include
+#include
+#include
+#include
+#include
+
+#define TAG "SpeakerDebug"
+#define CLI_COMMAND "speaker_debug"
+
+typedef enum {
+ SpeakerDebugAppMessageTypeStop,
+} SpeakerDebugAppMessageType;
+
+typedef struct {
+ SpeakerDebugAppMessageType type;
+} SpeakerDebugAppMessage;
+
+typedef struct {
+ MusicWorker* music_worker;
+ FuriMessageQueue* message_queue;
+ Cli* cli;
+} SpeakerDebugApp;
+
+static SpeakerDebugApp* speaker_app_alloc() {
+ SpeakerDebugApp* app = (SpeakerDebugApp*)malloc(sizeof(SpeakerDebugApp));
+ app->music_worker = music_worker_alloc();
+ app->message_queue = furi_message_queue_alloc(8, sizeof(SpeakerDebugAppMessage));
+ app->cli = furi_record_open(RECORD_CLI);
+ return app;
+}
+
+static void speaker_app_free(SpeakerDebugApp* app) {
+ music_worker_free(app->music_worker);
+ furi_message_queue_free(app->message_queue);
+ furi_record_close(RECORD_CLI);
+ free(app);
+}
+
+static void speaker_app_cli(Cli* cli, FuriString* args, void* context) {
+ UNUSED(cli);
+
+ SpeakerDebugApp* app = (SpeakerDebugApp*)context;
+ SpeakerDebugAppMessage message;
+ FuriString* cmd = furi_string_alloc();
+
+ if(!args_read_string_and_trim(args, cmd)) {
+ furi_string_free(cmd);
+ printf("Usage:\r\n");
+ printf("\t" CLI_COMMAND " stop\r\n");
+ return;
+ }
+
+ if(furi_string_cmp(cmd, "stop") == 0) {
+ message.type = SpeakerDebugAppMessageTypeStop;
+ FuriStatus status = furi_message_queue_put(app->message_queue, &message, 100);
+ if(status != FuriStatusOk) {
+ printf("Failed to send message\r\n");
+ } else {
+ printf("Stopping\r\n");
+ }
+ } else {
+ printf("Usage:\r\n");
+ printf("\t" CLI_COMMAND " stop\r\n");
+ }
+
+ furi_string_free(cmd);
+}
+
+static bool speaker_app_music_play(SpeakerDebugApp* app, const char* rtttl) {
+ if(music_worker_is_playing(app->music_worker)) {
+ music_worker_stop(app->music_worker);
+ }
+
+ if(!music_worker_load_rtttl_from_string(app->music_worker, rtttl)) {
+ FURI_LOG_E(TAG, "Failed to load RTTTL");
+ return false;
+ }
+
+ music_worker_set_volume(app->music_worker, 1.0f);
+ music_worker_start(app->music_worker);
+
+ return true;
+}
+
+static void speaker_app_music_stop(SpeakerDebugApp* app) {
+ if(music_worker_is_playing(app->music_worker)) {
+ music_worker_stop(app->music_worker);
+ }
+}
+
+static void speaker_app_run(SpeakerDebugApp* app, const char* arg) {
+ if(!arg || !speaker_app_music_play(app, arg)) {
+ FURI_LOG_E(TAG, "Provided RTTTL is invalid");
+ return;
+ }
+
+ cli_add_command(app->cli, CLI_COMMAND, CliCommandFlagParallelSafe, speaker_app_cli, app);
+
+ SpeakerDebugAppMessage message;
+ FuriStatus status;
+ while(true) {
+ status = furi_message_queue_get(app->message_queue, &message, FuriWaitForever);
+
+ if(status == FuriStatusOk) {
+ if(message.type == SpeakerDebugAppMessageTypeStop) {
+ speaker_app_music_stop(app);
+ break;
+ }
+ }
+ }
+
+ cli_delete_command(app->cli, CLI_COMMAND);
+}
+
+int32_t speaker_debug_app(void* arg) {
+ SpeakerDebugApp* app = speaker_app_alloc();
+ speaker_app_run(app, arg);
+ speaker_app_free(app);
+ return 0;
+}
diff --git a/applications/debug/subghz_test/application.fam b/applications/debug/subghz_test/application.fam
new file mode 100644
index 00000000000..1b3e19d73f9
--- /dev/null
+++ b/applications/debug/subghz_test/application.fam
@@ -0,0 +1,14 @@
+App(
+ appid="subghz_test",
+ name="Sub-Ghz test",
+ apptype=FlipperAppType.DEBUG,
+ targets=["f7"],
+ entry_point="subghz_test_app",
+ requires=["gui"],
+ stack_size=4 * 1024,
+ order=50,
+ fap_icon="subghz_test_10px.png",
+ fap_category="Debug",
+ fap_icon_assets="images",
+ fap_version="0.1",
+)
diff --git a/applications/debug/subghz_test/helpers/subghz_test_event.h b/applications/debug/subghz_test/helpers/subghz_test_event.h
new file mode 100644
index 00000000000..a0a851976ad
--- /dev/null
+++ b/applications/debug/subghz_test/helpers/subghz_test_event.h
@@ -0,0 +1,7 @@
+#pragma once
+
+typedef enum {
+ //SubGhzTestCustomEvent
+ SubGhzTestCustomEventStartId = 100,
+ SubGhzTestCustomEventSceneShowOnlyRX,
+} SubGhzTestCustomEvent;
diff --git a/applications/debug/subghz_test/helpers/subghz_test_frequency.c b/applications/debug/subghz_test/helpers/subghz_test_frequency.c
new file mode 100644
index 00000000000..ed1ba704e4d
--- /dev/null
+++ b/applications/debug/subghz_test/helpers/subghz_test_frequency.c
@@ -0,0 +1,38 @@
+#include "subghz_test_frequency.h"
+
+const uint32_t subghz_frequencies_testing[] = {
+ /* 300 - 348 */
+ 300000000,
+ 304500000,
+ 310000000,
+ 312025000,
+ 313250000,
+ 313625000,
+ 315000000,
+ 315225000,
+ 321950000,
+ 348000000,
+ /* 387 - 464 */
+ 387000000,
+ 433075000, /* LPD433 first */
+ 433825000,
+ 433920000, /* LPD433 mid */
+ 434420000,
+ 434775000, /* LPD433 last channels */
+ 438900000,
+ 464000000,
+ /* 779 - 928 */
+ 779000000,
+ 868150000,
+ 868350000,
+ 868550000,
+ 915000000,
+ 925000000,
+ 926500000,
+ 927950000,
+ 928000000,
+};
+
+const uint32_t subghz_frequencies_count_testing =
+ sizeof(subghz_frequencies_testing) / sizeof(uint32_t);
+const uint32_t subghz_frequencies_433_92_testing = 13;
diff --git a/applications/debug/subghz_test/helpers/subghz_test_frequency.h b/applications/debug/subghz_test/helpers/subghz_test_frequency.h
new file mode 100644
index 00000000000..7dd1423f968
--- /dev/null
+++ b/applications/debug/subghz_test/helpers/subghz_test_frequency.h
@@ -0,0 +1,6 @@
+#pragma once
+#include "../subghz_test_app_i.h"
+
+extern const uint32_t subghz_frequencies_testing[];
+extern const uint32_t subghz_frequencies_count_testing;
+extern const uint32_t subghz_frequencies_433_92_testing;
diff --git a/applications/debug/subghz_test/helpers/subghz_test_types.h b/applications/debug/subghz_test/helpers/subghz_test_types.h
new file mode 100644
index 00000000000..03be6459ec1
--- /dev/null
+++ b/applications/debug/subghz_test/helpers/subghz_test_types.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include
+#include
+
+#define SUBGHZ_TEST_VERSION_APP "0.1"
+#define SUBGHZ_TEST_DEVELOPED "SkorP"
+#define SUBGHZ_TEST_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
+
+typedef enum {
+ SubGhzTestViewVariableItemList,
+ SubGhzTestViewSubmenu,
+ SubGhzTestViewStatic,
+ SubGhzTestViewCarrier,
+ SubGhzTestViewPacket,
+ SubGhzTestViewWidget,
+ SubGhzTestViewPopup,
+} SubGhzTestView;
diff --git a/applications/debug/subghz_test/images/DolphinCommon_56x48.png b/applications/debug/subghz_test/images/DolphinCommon_56x48.png
new file mode 100644
index 00000000000..089aaed8350
Binary files /dev/null and b/applications/debug/subghz_test/images/DolphinCommon_56x48.png differ
diff --git a/applications/debug/subghz_test/protocol/math.c b/applications/debug/subghz_test/protocol/math.c
new file mode 100644
index 00000000000..24202ad1c62
--- /dev/null
+++ b/applications/debug/subghz_test/protocol/math.c
@@ -0,0 +1,244 @@
+#include "math.h"
+
+uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count) {
+ uint64_t reverse_key = 0;
+ for(uint8_t i = 0; i < bit_count; i++) {
+ reverse_key = reverse_key << 1 | bit_read(key, i);
+ }
+ return reverse_key;
+}
+
+uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count) {
+ uint8_t parity = 0;
+ for(uint8_t i = 0; i < bit_count; i++) {
+ parity += bit_read(key, i);
+ }
+ return parity & 0x01;
+}
+
+uint8_t subghz_protocol_blocks_crc4(
+ uint8_t const message[],
+ size_t size,
+ uint8_t polynomial,
+ uint8_t init) {
+ uint8_t remainder = init << 4; // LSBs are unused
+ uint8_t poly = polynomial << 4;
+ uint8_t bit;
+
+ while(size--) {
+ remainder ^= *message++;
+ for(bit = 0; bit < 8; bit++) {
+ if(remainder & 0x80) {
+ remainder = (remainder << 1) ^ poly;
+ } else {
+ remainder = (remainder << 1);
+ }
+ }
+ }
+ return remainder >> 4 & 0x0f; // discard the LSBs
+}
+
+uint8_t subghz_protocol_blocks_crc7(
+ uint8_t const message[],
+ size_t size,
+ uint8_t polynomial,
+ uint8_t init) {
+ uint8_t remainder = init << 1; // LSB is unused
+ uint8_t poly = polynomial << 1;
+
+ for(size_t byte = 0; byte < size; ++byte) {
+ remainder ^= message[byte];
+ for(uint8_t bit = 0; bit < 8; ++bit) {
+ if(remainder & 0x80) {
+ remainder = (remainder << 1) ^ poly;
+ } else {
+ remainder = (remainder << 1);
+ }
+ }
+ }
+ return remainder >> 1 & 0x7f; // discard the LSB
+}
+
+uint8_t subghz_protocol_blocks_crc8(
+ uint8_t const message[],
+ size_t size,
+ uint8_t polynomial,
+ uint8_t init) {
+ uint8_t remainder = init;
+
+ for(size_t byte = 0; byte < size; ++byte) {
+ remainder ^= message[byte];
+ for(uint8_t bit = 0; bit < 8; ++bit) {
+ if(remainder & 0x80) {
+ remainder = (remainder << 1) ^ polynomial;
+ } else {
+ remainder = (remainder << 1);
+ }
+ }
+ }
+ return remainder;
+}
+
+uint8_t subghz_protocol_blocks_crc8le(
+ uint8_t const message[],
+ size_t size,
+ uint8_t polynomial,
+ uint8_t init) {
+ uint8_t remainder = subghz_protocol_blocks_reverse_key(init, 8);
+ polynomial = subghz_protocol_blocks_reverse_key(polynomial, 8);
+
+ for(size_t byte = 0; byte < size; ++byte) {
+ remainder ^= message[byte];
+ for(uint8_t bit = 0; bit < 8; ++bit) {
+ if(remainder & 1) {
+ remainder = (remainder >> 1) ^ polynomial;
+ } else {
+ remainder = (remainder >> 1);
+ }
+ }
+ }
+ return remainder;
+}
+
+uint16_t subghz_protocol_blocks_crc16lsb(
+ uint8_t const message[],
+ size_t size,
+ uint16_t polynomial,
+ uint16_t init) {
+ uint16_t remainder = init;
+
+ for(size_t byte = 0; byte < size; ++byte) {
+ remainder ^= message[byte];
+ for(uint8_t bit = 0; bit < 8; ++bit) {
+ if(remainder & 1) {
+ remainder = (remainder >> 1) ^ polynomial;
+ } else {
+ remainder = (remainder >> 1);
+ }
+ }
+ }
+ return remainder;
+}
+
+uint16_t subghz_protocol_blocks_crc16(
+ uint8_t const message[],
+ size_t size,
+ uint16_t polynomial,
+ uint16_t init) {
+ uint16_t remainder = init;
+
+ for(size_t byte = 0; byte < size; ++byte) {
+ remainder ^= message[byte] << 8;
+ for(uint8_t bit = 0; bit < 8; ++bit) {
+ if(remainder & 0x8000) {
+ remainder = (remainder << 1) ^ polynomial;
+ } else {
+ remainder = (remainder << 1);
+ }
+ }
+ }
+ return remainder;
+}
+
+uint8_t subghz_protocol_blocks_lfsr_digest8(
+ uint8_t const message[],
+ size_t size,
+ uint8_t gen,
+ uint8_t key) {
+ uint8_t sum = 0;
+ for(size_t byte = 0; byte < size; ++byte) {
+ uint8_t data = message[byte];
+ for(int i = 7; i >= 0; --i) {
+ // XOR key into sum if data bit is set
+ if((data >> i) & 1) sum ^= key;
+
+ // roll the key right (actually the LSB is dropped here)
+ // and apply the gen (needs to include the dropped LSB as MSB)
+ if(key & 1)
+ key = (key >> 1) ^ gen;
+ else
+ key = (key >> 1);
+ }
+ }
+ return sum;
+}
+
+uint8_t subghz_protocol_blocks_lfsr_digest8_reflect(
+ uint8_t const message[],
+ size_t size,
+ uint8_t gen,
+ uint8_t key) {
+ uint8_t sum = 0;
+ // Process message from last byte to first byte (reflected)
+ for(int byte = size - 1; byte >= 0; --byte) {
+ uint8_t data = message[byte];
+ // Process individual bits of each byte (reflected)
+ for(uint8_t i = 0; i < 8; ++i) {
+ // XOR key into sum if data bit is set
+ if((data >> i) & 1) {
+ sum ^= key;
+ }
+
+ // roll the key left (actually the LSB is dropped here)
+ // and apply the gen (needs to include the dropped lsb as MSB)
+ if(key & 0x80)
+ key = (key << 1) ^ gen;
+ else
+ key = (key << 1);
+ }
+ }
+ return sum;
+}
+
+uint16_t subghz_protocol_blocks_lfsr_digest16(
+ uint8_t const message[],
+ size_t size,
+ uint16_t gen,
+ uint16_t key) {
+ uint16_t sum = 0;
+ for(size_t byte = 0; byte < size; ++byte) {
+ uint8_t data = message[byte];
+ for(int8_t i = 7; i >= 0; --i) {
+ // if data bit is set then xor with key
+ if((data >> i) & 1) sum ^= key;
+
+ // roll the key right (actually the LSB is dropped here)
+ // and apply the gen (needs to include the dropped LSB as MSB)
+ if(key & 1)
+ key = (key >> 1) ^ gen;
+ else
+ key = (key >> 1);
+ }
+ }
+ return sum;
+}
+
+uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size) {
+ uint32_t result = 0;
+ for(size_t i = 0; i < size; ++i) {
+ result += message[i];
+ }
+ return (uint8_t)result;
+}
+
+uint8_t subghz_protocol_blocks_parity8(uint8_t byte) {
+ byte ^= byte >> 4;
+ byte &= 0xf;
+ return (0x6996 >> byte) & 1;
+}
+
+uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size) {
+ uint8_t result = 0;
+ for(size_t i = 0; i < size; ++i) {
+ result ^= subghz_protocol_blocks_parity8(message[i]);
+ }
+ return result;
+}
+
+uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size) {
+ uint8_t result = 0;
+ for(size_t i = 0; i < size; ++i) {
+ result ^= message[i];
+ }
+ return result;
+}
\ No newline at end of file
diff --git a/applications/debug/subghz_test/protocol/math.h b/applications/debug/subghz_test/protocol/math.h
new file mode 100644
index 00000000000..dcea3da5faf
--- /dev/null
+++ b/applications/debug/subghz_test/protocol/math.h
@@ -0,0 +1,222 @@
+#pragma once
+
+#include
+#include
+#include
+
+#define bit_read(value, bit) (((value) >> (bit)) & 0x01)
+#define bit_set(value, bit) \
+ ({ \
+ __typeof__(value) _one = (1); \
+ (value) |= (_one << (bit)); \
+ })
+#define bit_clear(value, bit) \
+ ({ \
+ __typeof__(value) _one = (1); \
+ (value) &= ~(_one << (bit)); \
+ })
+#define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit))
+#define DURATION_DIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Flip the data bitwise
+ *
+ * @param key In data
+ * @param bit_count number of data bits
+ *
+ * @return Reverse data
+ */
+uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count);
+
+/** Get parity the data bitwise
+ *
+ * @param key In data
+ * @param bit_count number of data bits
+ *
+ * @return parity
+ */
+uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count);
+
+/** CRC-4
+ *
+ * @param message array of bytes to check
+ * @param size number of bytes in message
+ * @param polynomial CRC polynomial
+ * @param init starting crc value
+ *
+ * @return CRC value
+ */
+uint8_t subghz_protocol_blocks_crc4(
+ uint8_t const message[],
+ size_t size,
+ uint8_t polynomial,
+ uint8_t init);
+
+/** CRC-7
+ *
+ * @param message array of bytes to check
+ * @param size number of bytes in message
+ * @param polynomial CRC polynomial
+ * @param init starting crc value
+ *
+ * @return CRC value
+ */
+uint8_t subghz_protocol_blocks_crc7(
+ uint8_t const message[],
+ size_t size,
+ uint8_t polynomial,
+ uint8_t init);
+
+/** Generic Cyclic Redundancy Check CRC-8. Example polynomial: 0x31 = x8 + x5 +
+ * x4 + 1 (x8 is implicit) Example polynomial: 0x80 = x8 + x7 (a normal
+ * bit-by-bit parity XOR)
+ *
+ * @param message array of bytes to check
+ * @param size number of bytes in message
+ * @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one)
+ * @param init starting crc value
+ *
+ * @return CRC value
+ */
+uint8_t subghz_protocol_blocks_crc8(
+ uint8_t const message[],
+ size_t size,
+ uint8_t polynomial,
+ uint8_t init);
+
+/** "Little-endian" Cyclic Redundancy Check CRC-8 LE Input and output are
+ * reflected, i.e. least significant bit is shifted in first
+ *
+ * @param message array of bytes to check
+ * @param size number of bytes in message
+ * @param polynomial CRC polynomial
+ * @param init starting crc value
+ *
+ * @return CRC value
+ */
+uint8_t subghz_protocol_blocks_crc8le(
+ uint8_t const message[],
+ size_t size,
+ uint8_t polynomial,
+ uint8_t init);
+
+/** CRC-16 LSB. Input and output are reflected, i.e. least significant bit is
+ * shifted in first. Note that poly and init already need to be reflected
+ *
+ * @param message array of bytes to check
+ * @param size number of bytes in message
+ * @param polynomial CRC polynomial
+ * @param init starting crc value
+ *
+ * @return CRC value
+ */
+uint16_t subghz_protocol_blocks_crc16lsb(
+ uint8_t const message[],
+ size_t size,
+ uint16_t polynomial,
+ uint16_t init);
+
+/** CRC-16
+ *
+ * @param message array of bytes to check
+ * @param size number of bytes in message
+ * @param polynomial CRC polynomial
+ * @param init starting crc value
+ *
+ * @return CRC value
+ */
+uint16_t subghz_protocol_blocks_crc16(
+ uint8_t const message[],
+ size_t size,
+ uint16_t polynomial,
+ uint16_t init);
+
+/** Digest-8 by "LFSR-based Toeplitz hash"
+ *
+ * @param message bytes of message data
+ * @param size number of bytes to digest
+ * @param gen key stream generator, needs to includes the MSB if the
+ * LFSR is rolling
+ * @param key initial key
+ *
+ * @return digest value
+ */
+uint8_t subghz_protocol_blocks_lfsr_digest8(
+ uint8_t const message[],
+ size_t size,
+ uint8_t gen,
+ uint8_t key);
+
+/** Digest-8 by "LFSR-based Toeplitz hash", byte reflect, bit reflect
+ *
+ * @param message bytes of message data
+ * @param size number of bytes to digest
+ * @param gen key stream generator, needs to includes the MSB if the
+ * LFSR is rolling
+ * @param key initial key
+ *
+ * @return digest value
+ */
+uint8_t subghz_protocol_blocks_lfsr_digest8_reflect(
+ uint8_t const message[],
+ size_t size,
+ uint8_t gen,
+ uint8_t key);
+
+/** Digest-16 by "LFSR-based Toeplitz hash"
+ *
+ * @param message bytes of message data
+ * @param size number of bytes to digest
+ * @param gen key stream generator, needs to includes the MSB if the
+ * LFSR is rolling
+ * @param key initial key
+ *
+ * @return digest value
+ */
+uint16_t subghz_protocol_blocks_lfsr_digest16(
+ uint8_t const message[],
+ size_t size,
+ uint16_t gen,
+ uint16_t key);
+
+/** Compute Addition of a number of bytes
+ *
+ * @param message bytes of message data
+ * @param size number of bytes to sum
+ *
+ * @return summation value
+ */
+uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size);
+
+/** Compute bit parity of a single byte (8 bits)
+ *
+ * @param byte single byte to check
+ *
+ * @return 1 odd parity, 0 even parity
+ */
+uint8_t subghz_protocol_blocks_parity8(uint8_t byte);
+
+/** Compute bit parity of a number of bytes
+ *
+ * @param message bytes of message data
+ * @param size number of bytes to sum
+ *
+ * @return 1 odd parity, 0 even parity
+ */
+uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size);
+
+/** Compute XOR (byte-wide parity) of a number of bytes
+ *
+ * @param message bytes of message data
+ * @param size number of bytes to sum
+ *
+ * @return summation value, per bit-position 1 odd parity, 0 even parity
+ */
+uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/subghz/protocols/princeton_for_testing.c b/applications/debug/subghz_test/protocol/princeton_for_testing.c
similarity index 98%
rename from lib/subghz/protocols/princeton_for_testing.c
rename to applications/debug/subghz_test/protocol/princeton_for_testing.c
index 43078d2e230..334a8241bbe 100644
--- a/lib/subghz/protocols/princeton_for_testing.c
+++ b/applications/debug/subghz_test/protocol/princeton_for_testing.c
@@ -1,7 +1,7 @@
#include "princeton_for_testing.h"
-#include "furi_hal.h"
-#include "../blocks/math.h"
+#include
+#include "math.h"
/*
* Help
@@ -94,12 +94,12 @@ void subghz_encoder_princeton_for_testing_print_log(void* context) {
((float)instance->time_high / (instance->time_high + instance->time_low)) * 100;
FURI_LOG_I(
TAG "Encoder",
- "Radio tx_time=%dus ON=%dus, OFF=%dus, DutyCycle=%d,%d%%",
+ "Radio tx_time=%luus ON=%luus, OFF=%luus, DutyCycle=%lu,%lu%%",
instance->time_high + instance->time_low,
instance->time_high,
instance->time_low,
(uint32_t)duty_cycle,
- (uint32_t)((duty_cycle - (uint32_t)duty_cycle) * 100));
+ (uint32_t)((duty_cycle - (uint32_t)duty_cycle) * 100UL));
}
LevelDuration subghz_encoder_princeton_for_testing_yield(void* context) {
diff --git a/lib/subghz/protocols/princeton_for_testing.h b/applications/debug/subghz_test/protocol/princeton_for_testing.h
similarity index 97%
rename from lib/subghz/protocols/princeton_for_testing.h
rename to applications/debug/subghz_test/protocol/princeton_for_testing.h
index 07a37ec5f7f..7b4201d38a9 100644
--- a/lib/subghz/protocols/princeton_for_testing.h
+++ b/applications/debug/subghz_test/protocol/princeton_for_testing.h
@@ -1,6 +1,8 @@
#pragma once
-#include "base.h"
+//#include "base.h"
+#include
+#include
/** SubGhzDecoderPrinceton anonymous type */
typedef struct SubGhzDecoderPrinceton SubGhzDecoderPrinceton;
diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene.c b/applications/debug/subghz_test/scenes/subghz_test_scene.c
new file mode 100644
index 00000000000..ff439ef0f8d
--- /dev/null
+++ b/applications/debug/subghz_test/scenes/subghz_test_scene.c
@@ -0,0 +1,30 @@
+#include "../subghz_test_app_i.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const subghz_test_scene_on_enter_handlers[])(void*) = {
+#include "subghz_test_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const subghz_test_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "subghz_test_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const subghz_test_scene_on_exit_handlers[])(void* context) = {
+#include "subghz_test_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers subghz_test_scene_handlers = {
+ .on_enter_handlers = subghz_test_scene_on_enter_handlers,
+ .on_event_handlers = subghz_test_scene_on_event_handlers,
+ .on_exit_handlers = subghz_test_scene_on_exit_handlers,
+ .scene_num = SubGhzTestSceneNum,
+};
diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene.h b/applications/debug/subghz_test/scenes/subghz_test_scene.h
new file mode 100644
index 00000000000..0e6e06481b5
--- /dev/null
+++ b/applications/debug/subghz_test/scenes/subghz_test_scene.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) SubGhzTestScene##id,
+typedef enum {
+#include "subghz_test_scene_config.h"
+ SubGhzTestSceneNum,
+} SubGhzTestScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers subghz_test_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "subghz_test_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+ bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "subghz_test_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "subghz_test_scene_config.h"
+#undef ADD_SCENE
diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_about.c b/applications/debug/subghz_test/scenes/subghz_test_scene_about.c
new file mode 100644
index 00000000000..64263d73887
--- /dev/null
+++ b/applications/debug/subghz_test/scenes/subghz_test_scene_about.c
@@ -0,0 +1,66 @@
+#include "../subghz_test_app_i.h"
+
+void subghz_test_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) {
+ SubGhzTestApp* app = context;
+ if(type == InputTypeShort) {
+ view_dispatcher_send_custom_event(app->view_dispatcher, result);
+ }
+}
+
+void subghz_test_scene_about_on_enter(void* context) {
+ SubGhzTestApp* app = context;
+
+ FuriString* temp_str;
+ temp_str = furi_string_alloc();
+ furi_string_printf(temp_str, "\e#%s\n", "Information");
+
+ furi_string_cat_printf(temp_str, "Version: %s\n", SUBGHZ_TEST_VERSION_APP);
+ furi_string_cat_printf(temp_str, "Developed by: %s\n", SUBGHZ_TEST_DEVELOPED);
+ furi_string_cat_printf(temp_str, "Github: %s\n\n", SUBGHZ_TEST_GITHUB);
+
+ furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
+ furi_string_cat_printf(
+ temp_str,
+ "This application is designed\nto test the functionality of the\nbuilt-in CC1101 module.\n\n");
+
+ widget_add_text_box_element(
+ app->widget,
+ 0,
+ 0,
+ 128,
+ 14,
+ AlignCenter,
+ AlignBottom,
+ "\e#\e! \e!\n",
+ false);
+ widget_add_text_box_element(
+ app->widget,
+ 0,
+ 2,
+ 128,
+ 14,
+ AlignCenter,
+ AlignBottom,
+ "\e#\e! Sub-Ghz Test \e!\n",
+ false);
+ widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str));
+ furi_string_free(temp_str);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewWidget);
+}
+
+bool subghz_test_scene_about_on_event(void* context, SceneManagerEvent event) {
+ SubGhzTestApp* app = context;
+ bool consumed = false;
+ UNUSED(app);
+ UNUSED(event);
+
+ return consumed;
+}
+
+void subghz_test_scene_about_on_exit(void* context) {
+ SubGhzTestApp* app = context;
+
+ // Clear views
+ widget_reset(app->widget);
+}
diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c b/applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c
new file mode 100644
index 00000000000..41ff5c8c6e1
--- /dev/null
+++ b/applications/debug/subghz_test/scenes/subghz_test_scene_carrier.c
@@ -0,0 +1,29 @@
+#include "../subghz_test_app_i.h"
+
+void subghz_test_scene_carrier_callback(SubGhzTestCarrierEvent event, void* context) {
+ furi_assert(context);
+ SubGhzTestApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void subghz_test_scene_carrier_on_enter(void* context) {
+ SubGhzTestApp* app = context;
+ subghz_test_carrier_set_callback(
+ app->subghz_test_carrier, subghz_test_scene_carrier_callback, app);
+ view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewCarrier);
+}
+
+bool subghz_test_scene_carrier_on_event(void* context, SceneManagerEvent event) {
+ SubGhzTestApp* app = context;
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == SubGhzTestCarrierEventOnlyRx) {
+ scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx);
+ return true;
+ }
+ }
+ return false;
+}
+
+void subghz_test_scene_carrier_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_config.h b/applications/debug/subghz_test/scenes/subghz_test_scene_config.h
new file mode 100644
index 00000000000..80a42c3761a
--- /dev/null
+++ b/applications/debug/subghz_test/scenes/subghz_test_scene_config.h
@@ -0,0 +1,6 @@
+ADD_SCENE(subghz_test, start, Start)
+ADD_SCENE(subghz_test, about, About)
+ADD_SCENE(subghz_test, carrier, Carrier)
+ADD_SCENE(subghz_test, packet, Packet)
+ADD_SCENE(subghz_test, static, Static)
+ADD_SCENE(subghz_test, show_only_rx, ShowOnlyRx)
diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_packet.c b/applications/debug/subghz_test/scenes/subghz_test_scene_packet.c
new file mode 100644
index 00000000000..b43a4d0cb39
--- /dev/null
+++ b/applications/debug/subghz_test/scenes/subghz_test_scene_packet.c
@@ -0,0 +1,29 @@
+#include "../subghz_test_app_i.h"
+
+void subghz_test_scene_packet_callback(SubGhzTestPacketEvent event, void* context) {
+ furi_assert(context);
+ SubGhzTestApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void subghz_test_scene_packet_on_enter(void* context) {
+ SubGhzTestApp* app = context;
+ subghz_test_packet_set_callback(
+ app->subghz_test_packet, subghz_test_scene_packet_callback, app);
+ view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewPacket);
+}
+
+bool subghz_test_scene_packet_on_event(void* context, SceneManagerEvent event) {
+ SubGhzTestApp* app = context;
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == SubGhzTestPacketEventOnlyRx) {
+ scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx);
+ return true;
+ }
+ }
+ return false;
+}
+
+void subghz_test_scene_packet_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c b/applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c
new file mode 100644
index 00000000000..3d5a54355c3
--- /dev/null
+++ b/applications/debug/subghz_test/scenes/subghz_test_scene_show_only_rx.c
@@ -0,0 +1,49 @@
+#include "../subghz_test_app_i.h"
+#include
+
+void subghz_test_scene_show_only_rx_popup_callback(void* context) {
+ SubGhzTestApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, SubGhzTestCustomEventSceneShowOnlyRX);
+}
+
+void subghz_test_scene_show_only_rx_on_enter(void* context) {
+ SubGhzTestApp* app = context;
+
+ // Setup view
+ Popup* popup = app->popup;
+
+ const char* header_text = "Transmission is blocked";
+ const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region";
+ if(!furi_hal_region_is_provisioned()) {
+ header_text = "Firmware update needed";
+ message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd";
+ }
+
+ popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop);
+ popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop);
+ popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48);
+
+ popup_set_timeout(popup, 1500);
+ popup_set_context(popup, app);
+ popup_set_callback(popup, subghz_test_scene_show_only_rx_popup_callback);
+ popup_enable_timeout(popup);
+ view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewPopup);
+}
+
+bool subghz_test_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) {
+ SubGhzTestApp* app = context;
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == SubGhzTestCustomEventSceneShowOnlyRX) {
+ scene_manager_previous_scene(app->scene_manager);
+ return true;
+ }
+ }
+ return false;
+}
+
+void subghz_test_scene_show_only_rx_on_exit(void* context) {
+ SubGhzTestApp* app = context;
+ Popup* popup = app->popup;
+
+ popup_reset(popup);
+}
diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_start.c b/applications/debug/subghz_test/scenes/subghz_test_scene_start.c
new file mode 100644
index 00000000000..cf3b08163d2
--- /dev/null
+++ b/applications/debug/subghz_test/scenes/subghz_test_scene_start.c
@@ -0,0 +1,77 @@
+#include "../subghz_test_app_i.h"
+
+typedef enum {
+ SubmenuIndexSubGhzTestCarrier,
+ SubmenuIndexSubGhzTestPacket,
+ SubmenuIndexSubGhzTestStatic,
+ SubmenuIndexSubGhzTestAbout,
+} SubmenuIndex;
+
+void subghz_test_scene_start_submenu_callback(void* context, uint32_t index) {
+ SubGhzTestApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void subghz_test_scene_start_on_enter(void* context) {
+ SubGhzTestApp* app = context;
+ Submenu* submenu = app->submenu;
+
+ submenu_add_item(
+ submenu,
+ "Carrier",
+ SubmenuIndexSubGhzTestCarrier,
+ subghz_test_scene_start_submenu_callback,
+ app);
+ submenu_add_item(
+ submenu,
+ "Packet",
+ SubmenuIndexSubGhzTestPacket,
+ subghz_test_scene_start_submenu_callback,
+ app);
+ submenu_add_item(
+ submenu,
+ "Static",
+ SubmenuIndexSubGhzTestStatic,
+ subghz_test_scene_start_submenu_callback,
+ app);
+ submenu_add_item(
+ submenu,
+ "About",
+ SubmenuIndexSubGhzTestAbout,
+ subghz_test_scene_start_submenu_callback,
+ app);
+
+ submenu_set_selected_item(
+ submenu, scene_manager_get_scene_state(app->scene_manager, SubGhzTestSceneStart));
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewSubmenu);
+}
+
+bool subghz_test_scene_start_on_event(void* context, SceneManagerEvent event) {
+ SubGhzTestApp* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == SubmenuIndexSubGhzTestAbout) {
+ scene_manager_next_scene(app->scene_manager, SubGhzTestSceneAbout);
+ consumed = true;
+ } else if(event.event == SubmenuIndexSubGhzTestCarrier) {
+ scene_manager_next_scene(app->scene_manager, SubGhzTestSceneCarrier);
+ consumed = true;
+ } else if(event.event == SubmenuIndexSubGhzTestPacket) {
+ scene_manager_next_scene(app->scene_manager, SubGhzTestScenePacket);
+ consumed = true;
+ } else if(event.event == SubmenuIndexSubGhzTestStatic) {
+ scene_manager_next_scene(app->scene_manager, SubGhzTestSceneStatic);
+ consumed = true;
+ }
+ scene_manager_set_scene_state(app->scene_manager, SubGhzTestSceneStart, event.event);
+ }
+
+ return consumed;
+}
+
+void subghz_test_scene_start_on_exit(void* context) {
+ SubGhzTestApp* app = context;
+ submenu_reset(app->submenu);
+}
diff --git a/applications/debug/subghz_test/scenes/subghz_test_scene_static.c b/applications/debug/subghz_test/scenes/subghz_test_scene_static.c
new file mode 100644
index 00000000000..a008d2438ff
--- /dev/null
+++ b/applications/debug/subghz_test/scenes/subghz_test_scene_static.c
@@ -0,0 +1,29 @@
+#include "../subghz_test_app_i.h"
+
+void subghz_test_scene_static_callback(SubGhzTestStaticEvent event, void* context) {
+ furi_assert(context);
+ SubGhzTestApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void subghz_test_scene_static_on_enter(void* context) {
+ SubGhzTestApp* app = context;
+ subghz_test_static_set_callback(
+ app->subghz_test_static, subghz_test_scene_static_callback, app);
+ view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewStatic);
+}
+
+bool subghz_test_scene_static_on_event(void* context, SceneManagerEvent event) {
+ SubGhzTestApp* app = context;
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == SubGhzTestStaticEventOnlyRx) {
+ scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx);
+ return true;
+ }
+ }
+ return false;
+}
+
+void subghz_test_scene_static_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/debug/subghz_test/subghz_test_10px.png b/applications/debug/subghz_test/subghz_test_10px.png
new file mode 100644
index 00000000000..10dac0ecaac
Binary files /dev/null and b/applications/debug/subghz_test/subghz_test_10px.png differ
diff --git a/applications/debug/subghz_test/subghz_test_app.c b/applications/debug/subghz_test/subghz_test_app.c
new file mode 100644
index 00000000000..704941fb28e
--- /dev/null
+++ b/applications/debug/subghz_test/subghz_test_app.c
@@ -0,0 +1,139 @@
+#include "subghz_test_app_i.h"
+
+#include
+#include
+
+static bool subghz_test_app_custom_event_callback(void* context, uint32_t event) {
+ furi_assert(context);
+ SubGhzTestApp* app = context;
+ return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool subghz_test_app_back_event_callback(void* context) {
+ furi_assert(context);
+ SubGhzTestApp* app = context;
+ return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static void subghz_test_app_tick_event_callback(void* context) {
+ furi_assert(context);
+ SubGhzTestApp* app = context;
+ scene_manager_handle_tick_event(app->scene_manager);
+}
+
+SubGhzTestApp* subghz_test_app_alloc() {
+ SubGhzTestApp* app = malloc(sizeof(SubGhzTestApp));
+
+ // GUI
+ app->gui = furi_record_open(RECORD_GUI);
+
+ // View Dispatcher
+ app->view_dispatcher = view_dispatcher_alloc();
+ app->scene_manager = scene_manager_alloc(&subghz_test_scene_handlers, app);
+ view_dispatcher_enable_queue(app->view_dispatcher);
+
+ view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+ view_dispatcher_set_custom_event_callback(
+ app->view_dispatcher, subghz_test_app_custom_event_callback);
+ view_dispatcher_set_navigation_event_callback(
+ app->view_dispatcher, subghz_test_app_back_event_callback);
+ view_dispatcher_set_tick_event_callback(
+ app->view_dispatcher, subghz_test_app_tick_event_callback, 100);
+
+ view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+ // Open Notification record
+ app->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+ // SubMenu
+ app->submenu = submenu_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, SubGhzTestViewSubmenu, submenu_get_view(app->submenu));
+
+ // Widget
+ app->widget = widget_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, SubGhzTestViewWidget, widget_get_view(app->widget));
+
+ // Popup
+ app->popup = popup_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, SubGhzTestViewPopup, popup_get_view(app->popup));
+
+ // Carrier Test Module
+ app->subghz_test_carrier = subghz_test_carrier_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ SubGhzTestViewCarrier,
+ subghz_test_carrier_get_view(app->subghz_test_carrier));
+
+ // Packet Test
+ app->subghz_test_packet = subghz_test_packet_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ SubGhzTestViewPacket,
+ subghz_test_packet_get_view(app->subghz_test_packet));
+
+ // Static send
+ app->subghz_test_static = subghz_test_static_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ SubGhzTestViewStatic,
+ subghz_test_static_get_view(app->subghz_test_static));
+
+ scene_manager_next_scene(app->scene_manager, SubGhzTestSceneStart);
+
+ return app;
+}
+
+void subghz_test_app_free(SubGhzTestApp* app) {
+ furi_assert(app);
+
+ // Submenu
+ view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewSubmenu);
+ submenu_free(app->submenu);
+
+ // Widget
+ view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewWidget);
+ widget_free(app->widget);
+
+ // Popup
+ view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewPopup);
+ popup_free(app->popup);
+
+ // Carrier Test
+ view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewCarrier);
+ subghz_test_carrier_free(app->subghz_test_carrier);
+
+ // Packet Test
+ view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewPacket);
+ subghz_test_packet_free(app->subghz_test_packet);
+
+ // Static
+ view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewStatic);
+ subghz_test_static_free(app->subghz_test_static);
+
+ // View dispatcher
+ view_dispatcher_free(app->view_dispatcher);
+ scene_manager_free(app->scene_manager);
+
+ // Notifications
+ furi_record_close(RECORD_NOTIFICATION);
+ app->notifications = NULL;
+
+ // Close records
+ furi_record_close(RECORD_GUI);
+
+ free(app);
+}
+
+int32_t subghz_test_app(void* p) {
+ UNUSED(p);
+ SubGhzTestApp* subghz_test_app = subghz_test_app_alloc();
+
+ view_dispatcher_run(subghz_test_app->view_dispatcher);
+
+ subghz_test_app_free(subghz_test_app);
+
+ return 0;
+}
diff --git a/applications/debug/subghz_test/subghz_test_app_i.c b/applications/debug/subghz_test/subghz_test_app_i.c
new file mode 100644
index 00000000000..0ec6635a0eb
--- /dev/null
+++ b/applications/debug/subghz_test/subghz_test_app_i.c
@@ -0,0 +1,5 @@
+#include "subghz_test_app_i.h"
+
+#include
+
+#define TAG "SubGhzTest"
diff --git a/applications/debug/subghz_test/subghz_test_app_i.h b/applications/debug/subghz_test/subghz_test_app_i.h
new file mode 100644
index 00000000000..c96f9c4ee4c
--- /dev/null
+++ b/applications/debug/subghz_test/subghz_test_app_i.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "helpers/subghz_test_types.h"
+#include "helpers/subghz_test_event.h"
+
+#include "scenes/subghz_test_scene.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "views/subghz_test_static.h"
+#include "views/subghz_test_carrier.h"
+#include "views/subghz_test_packet.h"
+
+typedef struct SubGhzTestApp SubGhzTestApp;
+
+struct SubGhzTestApp {
+ Gui* gui;
+ ViewDispatcher* view_dispatcher;
+ SceneManager* scene_manager;
+ NotificationApp* notifications;
+ Submenu* submenu;
+ Widget* widget;
+ Popup* popup;
+ SubGhzTestStatic* subghz_test_static;
+ SubGhzTestCarrier* subghz_test_carrier;
+ SubGhzTestPacket* subghz_test_packet;
+};
diff --git a/applications/subghz/views/subghz_test_carrier.c b/applications/debug/subghz_test/views/subghz_test_carrier.c
similarity index 92%
rename from applications/subghz/views/subghz_test_carrier.c
rename to applications/debug/subghz_test/views/subghz_test_carrier.c
index 6729eaad8ea..53e309b7c98 100644
--- a/applications/subghz/views/subghz_test_carrier.c
+++ b/applications/debug/subghz_test/views/subghz_test_carrier.c
@@ -1,6 +1,7 @@
#include "subghz_test_carrier.h"
-#include "../subghz_i.h"
-#include "../helpers/subghz_testing.h"
+#include "../subghz_test_app_i.h"
+#include "../helpers/subghz_test_frequency.h"
+#include
#include
#include
@@ -89,7 +90,9 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) {
}
with_view_model(
- subghz_test_carrier->view, (SubGhzTestCarrierModel * model) {
+ subghz_test_carrier->view,
+ SubGhzTestCarrierModel * model,
+ {
furi_hal_subghz_idle();
if(event->key == InputKeyLeft) {
@@ -125,9 +128,8 @@ bool subghz_test_carrier_input(InputEvent* event, void* context) {
SubGhzTestCarrierEventOnlyRx, subghz_test_carrier->context);
}
}
-
- return true;
- });
+ },
+ true);
return true;
}
@@ -137,20 +139,22 @@ void subghz_test_carrier_enter(void* context) {
SubGhzTestCarrier* subghz_test_carrier = context;
furi_hal_subghz_reset();
- furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async);
+ furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
with_view_model(
- subghz_test_carrier->view, (SubGhzTestCarrierModel * model) {
+ subghz_test_carrier->view,
+ SubGhzTestCarrierModel * model,
+ {
model->frequency = subghz_frequencies_433_92_testing; // 433
model->real_frequency =
furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]);
model->path = FuriHalSubGhzPathIsolate; // isolate
model->rssi = 0.0f;
model->status = SubGhzTestCarrierModelStatusRx;
- return true;
- });
+ },
+ true);
furi_hal_subghz_rx();
@@ -172,13 +176,14 @@ void subghz_test_carrier_rssi_timer_callback(void* context) {
SubGhzTestCarrier* subghz_test_carrier = context;
with_view_model(
- subghz_test_carrier->view, (SubGhzTestCarrierModel * model) {
+ subghz_test_carrier->view,
+ SubGhzTestCarrierModel * model,
+ {
if(model->status == SubGhzTestCarrierModelStatusRx) {
model->rssi = furi_hal_subghz_get_rssi();
- return true;
}
- return false;
- });
+ },
+ false);
}
SubGhzTestCarrier* subghz_test_carrier_alloc() {
diff --git a/applications/subghz/views/subghz_test_carrier.h b/applications/debug/subghz_test/views/subghz_test_carrier.h
similarity index 100%
rename from applications/subghz/views/subghz_test_carrier.h
rename to applications/debug/subghz_test/views/subghz_test_carrier.h
diff --git a/applications/subghz/views/subghz_test_packet.c b/applications/debug/subghz_test/views/subghz_test_packet.c
similarity index 92%
rename from applications/subghz/views/subghz_test_packet.c
rename to applications/debug/subghz_test/views/subghz_test_packet.c
index c83aebec9c3..1f345829617 100644
--- a/applications/subghz/views/subghz_test_packet.c
+++ b/applications/debug/subghz_test/views/subghz_test_packet.c
@@ -1,13 +1,14 @@
#include "subghz_test_packet.h"
-#include "../subghz_i.h"
-#include "../helpers/subghz_testing.h"
+#include "../subghz_test_app_i.h"
+#include "../helpers/subghz_test_frequency.h"
+#include
#include
#include
#include
#include
#include
-#include
+#include "../protocol/princeton_for_testing.h"
#define SUBGHZ_TEST_PACKET_COUNT 500
@@ -55,7 +56,6 @@ static void subghz_test_packet_rx_callback(bool level, uint32_t duration, void*
subghz_decoder_princeton_for_testing_parse(instance->decoder, level, duration);
}
-//todo
static void subghz_test_packet_rx_pt_callback(SubGhzDecoderPrinceton* parser, void* context) {
UNUSED(parser);
furi_assert(context);
@@ -68,7 +68,9 @@ static void subghz_test_packet_rssi_timer_callback(void* context) {
SubGhzTestPacket* instance = context;
with_view_model(
- instance->view, (SubGhzTestPacketModel * model) {
+ instance->view,
+ SubGhzTestPacketModel * model,
+ {
if(model->status == SubGhzTestPacketModelStatusRx) {
model->rssi = furi_hal_subghz_get_rssi();
model->packets = instance->packet_rx;
@@ -77,8 +79,8 @@ static void subghz_test_packet_rssi_timer_callback(void* context) {
SUBGHZ_TEST_PACKET_COUNT -
subghz_encoder_princeton_for_testing_get_repeat_left(instance->encoder);
}
- return true;
- });
+ },
+ true);
}
static void subghz_test_packet_draw(Canvas* canvas, SubGhzTestPacketModel* model) {
@@ -112,7 +114,7 @@ static void subghz_test_packet_draw(Canvas* canvas, SubGhzTestPacketModel* model
snprintf(buffer, sizeof(buffer), "Path: %d - %s", model->path, path_name);
canvas_draw_str(canvas, 0, 31, buffer);
- snprintf(buffer, sizeof(buffer), "Packets: %d", model->packets);
+ snprintf(buffer, sizeof(buffer), "Packets: %zu", model->packets);
canvas_draw_str(canvas, 0, 42, buffer);
if(model->status == SubGhzTestPacketModelStatusRx) {
@@ -137,7 +139,9 @@ static bool subghz_test_packet_input(InputEvent* event, void* context) {
}
with_view_model(
- instance->view, (SubGhzTestPacketModel * model) {
+ instance->view,
+ SubGhzTestPacketModel * model,
+ {
if(model->status == SubGhzTestPacketModelStatusRx) {
furi_hal_subghz_stop_async_rx();
} else if(model->status == SubGhzTestPacketModelStatusTx) {
@@ -179,9 +183,8 @@ static bool subghz_test_packet_input(InputEvent* event, void* context) {
instance->callback(SubGhzTestPacketEventOnlyRx, instance->context);
}
}
-
- return true;
- });
+ },
+ true);
return true;
}
@@ -191,18 +194,20 @@ void subghz_test_packet_enter(void* context) {
SubGhzTestPacket* instance = context;
furi_hal_subghz_reset();
- furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async);
+ furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs);
with_view_model(
- instance->view, (SubGhzTestPacketModel * model) {
+ instance->view,
+ SubGhzTestPacketModel * model,
+ {
model->frequency = subghz_frequencies_433_92_testing;
model->real_frequency =
furi_hal_subghz_set_frequency(subghz_frequencies_testing[model->frequency]);
model->path = FuriHalSubGhzPathIsolate; // isolate
model->rssi = 0.0f;
model->status = SubGhzTestPacketModelStatusRx;
- return true;
- });
+ },
+ true);
furi_hal_subghz_start_async_rx(subghz_test_packet_rx_callback, instance);
@@ -217,15 +222,17 @@ void subghz_test_packet_exit(void* context) {
// Reinitialize IC to default state
with_view_model(
- instance->view, (SubGhzTestPacketModel * model) {
+ instance->view,
+ SubGhzTestPacketModel * model,
+ {
if(model->status == SubGhzTestPacketModelStatusRx) {
furi_hal_subghz_stop_async_rx();
} else if(model->status == SubGhzTestPacketModelStatusTx) {
subghz_encoder_princeton_for_testing_stop(instance->encoder, furi_get_tick());
furi_hal_subghz_stop_async_tx();
}
- return true;
- });
+ },
+ true);
furi_hal_subghz_sleep();
}
diff --git a/applications/subghz/views/subghz_test_packet.h b/applications/debug/subghz_test/views/subghz_test_packet.h
similarity index 100%
rename from applications/subghz/views/subghz_test_packet.h
rename to applications/debug/subghz_test/views/subghz_test_packet.h
diff --git a/applications/subghz/views/subghz_test_static.c b/applications/debug/subghz_test/views/subghz_test_static.c
similarity index 93%
rename from applications/subghz/views/subghz_test_static.c
rename to applications/debug/subghz_test/views/subghz_test_static.c
index 7af54c3c05a..6764fd5ca9f 100644
--- a/applications/subghz/views/subghz_test_static.c
+++ b/applications/debug/subghz_test/views/subghz_test_static.c
@@ -1,13 +1,14 @@
#include "subghz_test_static.h"
-#include "../subghz_i.h"
-#include "../helpers/subghz_testing.h"
+#include "../subghz_test_app_i.h"
+#include "../helpers/subghz_test_frequency.h"
+#include
#include
#include
#include
#include
#include
-#include
+#include "../protocol/princeton_for_testing.h"
#define TAG "SubGhzTestStatic"
@@ -77,7 +78,9 @@ bool subghz_test_static_input(InputEvent* event, void* context) {
}
with_view_model(
- instance->view, (SubGhzTestStaticModel * model) {
+ instance->view,
+ SubGhzTestStaticModel * model,
+ {
if(event->type == InputTypeShort) {
if(event->key == InputKeyLeft) {
if(model->frequency > 0) model->frequency--;
@@ -128,9 +131,8 @@ bool subghz_test_static_input(InputEvent* event, void* context) {
}
furi_record_close(RECORD_NOTIFICATION);
}
-
- return true;
- });
+ },
+ true);
return true;
}
@@ -140,20 +142,21 @@ void subghz_test_static_enter(void* context) {
SubGhzTestStatic* instance = context;
furi_hal_subghz_reset();
- furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async);
+ furi_hal_subghz_load_custom_preset(subghz_device_cc1101_preset_ook_650khz_async_regs);
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_cc1101_g0, false);
instance->status_tx = SubGhzTestStaticStatusIDLE;
with_view_model(
- instance->view, (SubGhzTestStaticModel * model) {
+ instance->view,
+ SubGhzTestStaticModel * model,
+ {
model->frequency = subghz_frequencies_433_92_testing;
model->real_frequency = subghz_frequencies_testing[model->frequency];
model->button = 0;
-
- return true;
- });
+ },
+ true);
}
void subghz_test_static_exit(void* context) {
diff --git a/applications/subghz/views/subghz_test_static.h b/applications/debug/subghz_test/views/subghz_test_static.h
similarity index 100%
rename from applications/subghz/views/subghz_test_static.h
rename to applications/debug/subghz_test/views/subghz_test_static.h
diff --git a/applications/debug/text_box_test/application.fam b/applications/debug/text_box_test/application.fam
new file mode 100644
index 00000000000..823c21d0688
--- /dev/null
+++ b/applications/debug/text_box_test/application.fam
@@ -0,0 +1,10 @@
+App(
+ appid="text_box_test",
+ name="Text Box Test",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="text_box_test_app",
+ requires=["gui"],
+ stack_size=1 * 1024,
+ order=140,
+ fap_category="Debug",
+)
diff --git a/applications/debug_tools/text_box_test.c b/applications/debug/text_box_test/text_box_test.c
similarity index 83%
rename from applications/debug_tools/text_box_test.c
rename to applications/debug/text_box_test/text_box_test.c
index d7194ffee41..b980f686e18 100644
--- a/applications/debug_tools/text_box_test.c
+++ b/applications/debug/text_box_test/text_box_test.c
@@ -53,15 +53,17 @@ static void (*text_box_test_render[])(Canvas* canvas) = {
typedef struct {
uint32_t idx;
+ FuriMutex* mutex;
} TextBoxTestState;
static void text_box_test_render_callback(Canvas* canvas, void* ctx) {
- TextBoxTestState* state = acquire_mutex((ValueMutex*)ctx, 25);
+ TextBoxTestState* state = ctx;
+ furi_mutex_acquire(state->mutex, FuriWaitForever);
canvas_clear(canvas);
text_box_test_render[state->idx](canvas);
- release_mutex((ValueMutex*)ctx, state);
+ furi_mutex_release(state->mutex);
}
static void text_box_test_input_callback(InputEvent* input_event, void* ctx) {
@@ -74,17 +76,17 @@ int32_t text_box_test_app(void* p) {
FuriMessageQueue* event_queue = furi_message_queue_alloc(32, sizeof(InputEvent));
furi_check(event_queue);
- TextBoxTestState _state = {.idx = 0};
+ TextBoxTestState state = {.idx = 0, .mutex = NULL};
+ state.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
- ValueMutex state_mutex;
- if(!init_mutex(&state_mutex, &_state, sizeof(TextBoxTestState))) {
+ if(!state.mutex) {
FURI_LOG_E(TAG, "Cannot create mutex");
return 0;
}
ViewPort* view_port = view_port_alloc();
- view_port_draw_callback_set(view_port, text_box_test_render_callback, &state_mutex);
+ view_port_draw_callback_set(view_port, text_box_test_render_callback, &state);
view_port_input_callback_set(view_port, text_box_test_input_callback, event_queue);
// Open GUI and register view_port
@@ -94,24 +96,24 @@ int32_t text_box_test_app(void* p) {
uint32_t test_renders_num = COUNT_OF(text_box_test_render);
InputEvent event;
while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) {
- TextBoxTestState* state = acquire_mutex_block(&state_mutex);
+ furi_mutex_acquire(state.mutex, FuriWaitForever);
if(event.type == InputTypeShort) {
if(event.key == InputKeyRight) {
- if(state->idx < test_renders_num - 1) {
- state->idx++;
+ if(state.idx < test_renders_num - 1) {
+ state.idx++;
}
} else if(event.key == InputKeyLeft) {
- if(state->idx > 0) {
- state->idx--;
+ if(state.idx > 0) {
+ state.idx--;
}
} else if(event.key == InputKeyBack) {
- release_mutex(&state_mutex, state);
+ furi_mutex_release(state.mutex);
break;
}
}
- release_mutex(&state_mutex, state);
+ furi_mutex_release(state.mutex);
view_port_update(view_port);
}
@@ -119,7 +121,7 @@ int32_t text_box_test_app(void* p) {
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
- delete_mutex(&state_mutex);
+ furi_mutex_free(state.mutex);
furi_record_close(RECORD_GUI);
diff --git a/applications/debug/uart_echo/application.fam b/applications/debug/uart_echo/application.fam
new file mode 100644
index 00000000000..7b030bcfa68
--- /dev/null
+++ b/applications/debug/uart_echo/application.fam
@@ -0,0 +1,10 @@
+App(
+ appid="uart_echo",
+ name="UART Echo",
+ apptype=FlipperAppType.DEBUG,
+ entry_point="uart_echo_app",
+ requires=["gui"],
+ stack_size=2 * 1024,
+ order=70,
+ fap_category="Debug",
+)
diff --git a/applications/debug_tools/uart_echo.c b/applications/debug/uart_echo/uart_echo.c
similarity index 77%
rename from applications/debug_tools/uart_echo.c
rename to applications/debug/uart_echo/uart_echo.c
index 7a0f5c3933c..4bede9ab45f 100644
--- a/applications/debug_tools/uart_echo.c
+++ b/applications/debug/uart_echo/uart_echo.c
@@ -1,10 +1,8 @@
#include
-#include
#include
#include
#include
#include
-#include
#include
#include
#include
@@ -12,6 +10,8 @@
#define LINES_ON_SCREEN 6
#define COLUMNS_ON_SCREEN 21
+#define TAG "UartEcho"
+#define DEFAULT_BAUD_RATE 230400
typedef struct UartDumpModel UartDumpModel;
@@ -21,11 +21,11 @@ typedef struct {
ViewDispatcher* view_dispatcher;
View* view;
FuriThread* worker_thread;
- StreamBufferHandle_t rx_stream;
+ FuriStreamBuffer* rx_stream;
} UartEchoApp;
typedef struct {
- string_t text;
+ FuriString* text;
} ListElement;
struct UartDumpModel {
@@ -64,10 +64,11 @@ static void uart_echo_view_draw_callback(Canvas* canvas, void* _model) {
canvas,
0,
(i + 1) * (canvas_current_font_height(canvas) - 1),
- string_get_cstr(model->list[i]->text));
+ furi_string_get_cstr(model->list[i]->text));
if(i == model->line) {
- uint8_t width = canvas_string_width(canvas, string_get_cstr(model->list[i]->text));
+ uint8_t width =
+ canvas_string_width(canvas, furi_string_get_cstr(model->list[i]->text));
canvas_draw_box(
canvas,
@@ -92,13 +93,11 @@ static uint32_t uart_echo_exit(void* context) {
static void uart_echo_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
furi_assert(context);
- BaseType_t xHigherPriorityTaskWoken = pdFALSE;
UartEchoApp* app = context;
if(ev == UartIrqEventRXNE) {
- xStreamBufferSendFromISR(app->rx_stream, &data, 1, &xHigherPriorityTaskWoken);
+ furi_stream_buffer_send(app->rx_stream, &data, 1, 0);
furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventRx);
- portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
@@ -113,7 +112,7 @@ static void uart_echo_push_to_list(UartDumpModel* model, const char data) {
model->escape = true;
} else if((data >= ' ' && data <= '~') || (data == '\n' || data == '\r')) {
bool new_string_needed = false;
- if(string_size(model->list[model->line]->text) >= COLUMNS_ON_SCREEN) {
+ if(furi_string_size(model->list[model->line]->text) >= COLUMNS_ON_SCREEN) {
new_string_needed = true;
} else if((data == '\n' || data == '\r')) {
// pack line breaks
@@ -132,13 +131,13 @@ static void uart_echo_push_to_list(UartDumpModel* model, const char data) {
model->list[i - 1] = model->list[i];
}
- string_reset(first->text);
+ furi_string_reset(first->text);
model->list[model->line] = first;
}
}
if(data != '\n' && data != '\r') {
- string_push_back(model->list[model->line]->text, data);
+ furi_string_push_back(model->list[model->line]->text, data);
}
}
model->last_char = data;
@@ -158,35 +157,34 @@ static int32_t uart_echo_worker(void* context) {
size_t length = 0;
do {
uint8_t data[64];
- length = xStreamBufferReceive(app->rx_stream, data, 64, 0);
+ length = furi_stream_buffer_receive(app->rx_stream, data, 64, 0);
if(length > 0) {
furi_hal_uart_tx(FuriHalUartIdUSART1, data, length);
with_view_model(
- app->view, (UartDumpModel * model) {
+ app->view,
+ UartDumpModel * model,
+ {
for(size_t i = 0; i < length; i++) {
uart_echo_push_to_list(model, data[i]);
}
- return false;
- });
+ },
+ false);
}
} while(length > 0);
notification_message(app->notification, &sequence_notification);
with_view_model(
- app->view, (UartDumpModel * model) {
- UNUSED(model);
- return true;
- });
+ app->view, UartDumpModel * model, { UNUSED(model); }, true);
}
}
return 0;
}
-static UartEchoApp* uart_echo_app_alloc() {
+static UartEchoApp* uart_echo_app_alloc(uint32_t baudrate) {
UartEchoApp* app = malloc(sizeof(UartEchoApp));
- app->rx_stream = xStreamBufferCreate(2048, 1);
+ app->rx_stream = furi_stream_buffer_alloc(2048, 1);
// Gui
app->gui = furi_record_open(RECORD_GUI);
@@ -203,55 +201,55 @@ static UartEchoApp* uart_echo_app_alloc() {
view_set_input_callback(app->view, uart_echo_view_input_callback);
view_allocate_model(app->view, ViewModelTypeLocking, sizeof(UartDumpModel));
with_view_model(
- app->view, (UartDumpModel * model) {
+ app->view,
+ UartDumpModel * model,
+ {
for(size_t i = 0; i < LINES_ON_SCREEN; i++) {
model->line = 0;
model->escape = false;
model->list[i] = malloc(sizeof(ListElement));
- string_init(model->list[i]->text);
+ model->list[i]->text = furi_string_alloc();
}
- return true;
- });
+ },
+ true);
view_set_previous_callback(app->view, uart_echo_exit);
view_dispatcher_add_view(app->view_dispatcher, 0, app->view);
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
+ app->worker_thread = furi_thread_alloc_ex("UsbUartWorker", 1024, uart_echo_worker, app);
+ furi_thread_start(app->worker_thread);
+
// Enable uart listener
furi_hal_console_disable();
- furi_hal_uart_set_br(FuriHalUartIdUSART1, 115200);
+ furi_hal_uart_set_br(FuriHalUartIdUSART1, baudrate);
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_echo_on_irq_cb, app);
- app->worker_thread = furi_thread_alloc();
- furi_thread_set_name(app->worker_thread, "UsbUartWorker");
- furi_thread_set_stack_size(app->worker_thread, 1024);
- furi_thread_set_context(app->worker_thread, app);
- furi_thread_set_callback(app->worker_thread, uart_echo_worker);
- furi_thread_start(app->worker_thread);
-
return app;
}
static void uart_echo_app_free(UartEchoApp* app) {
furi_assert(app);
+ furi_hal_console_enable(); // this will also clear IRQ callback so thread is no longer referenced
+
furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop);
furi_thread_join(app->worker_thread);
furi_thread_free(app->worker_thread);
- furi_hal_console_enable();
-
// Free views
view_dispatcher_remove_view(app->view_dispatcher, 0);
with_view_model(
- app->view, (UartDumpModel * model) {
+ app->view,
+ UartDumpModel * model,
+ {
for(size_t i = 0; i < LINES_ON_SCREEN; i++) {
- string_clear(model->list[i]->text);
+ furi_string_free(model->list[i]->text);
free(model->list[i]);
}
- return true;
- });
+ },
+ true);
view_free(app->view);
view_dispatcher_free(app->view_dispatcher);
@@ -260,15 +258,25 @@ static void uart_echo_app_free(UartEchoApp* app) {
furi_record_close(RECORD_NOTIFICATION);
app->gui = NULL;
- vStreamBufferDelete(app->rx_stream);
+ furi_stream_buffer_free(app->rx_stream);
// Free rest
free(app);
}
int32_t uart_echo_app(void* p) {
- UNUSED(p);
- UartEchoApp* app = uart_echo_app_alloc();
+ uint32_t baudrate = DEFAULT_BAUD_RATE;
+ if(p) {
+ const char* baudrate_str = p;
+ if(sscanf(baudrate_str, "%lu", &baudrate) != 1) {
+ FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str);
+ baudrate = DEFAULT_BAUD_RATE;
+ }
+ }
+
+ FURI_LOG_I(TAG, "Using baudrate: %lu", baudrate);
+
+ UartEchoApp* app = uart_echo_app_alloc(baudrate);
view_dispatcher_run(app->view_dispatcher);
uart_echo_app_free(app);
return 0;
diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam
new file mode 100644
index 00000000000..aa25dab279b
--- /dev/null
+++ b/applications/debug/unit_tests/application.fam
@@ -0,0 +1,20 @@
+App(
+ appid="unit_tests",
+ apptype=FlipperAppType.STARTUP,
+ entry_point="unit_tests_on_system_start",
+ cdefines=["APP_UNIT_TESTS"],
+ requires=["system_settings"],
+ provides=["delay_test"],
+ resources="resources",
+ order=100,
+)
+
+App(
+ appid="delay_test",
+ name="Delay Test",
+ apptype=FlipperAppType.SYSTEM,
+ entry_point="delay_test_app",
+ stack_size=1 * 1024,
+ requires=["unit_tests"],
+ order=110,
+)
diff --git a/applications/debug/unit_tests/bt/bt_test.c b/applications/debug/unit_tests/bt/bt_test.c
new file mode 100644
index 00000000000..32cf6533f88
--- /dev/null
+++ b/applications/debug/unit_tests/bt/bt_test.c
@@ -0,0 +1,110 @@
+#include
+#include
+#include "../minunit.h"
+
+#include
+#include
+
+#define BT_TEST_KEY_STORAGE_FILE_PATH EXT_PATH("unit_tests/bt_test.keys")
+#define BT_TEST_NVM_RAM_BUFF_SIZE (507 * 4) // The same as in ble NVM storage
+
+typedef struct {
+ Storage* storage;
+ BtKeysStorage* bt_keys_storage;
+ uint8_t* nvm_ram_buff_dut;
+ uint8_t* nvm_ram_buff_ref;
+} BtTest;
+
+BtTest* bt_test = NULL;
+
+void bt_test_alloc() {
+ bt_test = malloc(sizeof(BtTest));
+ bt_test->storage = furi_record_open(RECORD_STORAGE);
+ bt_test->nvm_ram_buff_dut = malloc(BT_TEST_NVM_RAM_BUFF_SIZE);
+ bt_test->nvm_ram_buff_ref = malloc(BT_TEST_NVM_RAM_BUFF_SIZE);
+ bt_test->bt_keys_storage = bt_keys_storage_alloc(BT_TEST_KEY_STORAGE_FILE_PATH);
+ bt_keys_storage_set_ram_params(
+ bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, BT_TEST_NVM_RAM_BUFF_SIZE);
+}
+
+void bt_test_free() {
+ furi_check(bt_test);
+ free(bt_test->nvm_ram_buff_ref);
+ free(bt_test->nvm_ram_buff_dut);
+ bt_keys_storage_free(bt_test->bt_keys_storage);
+ furi_record_close(RECORD_STORAGE);
+ free(bt_test);
+ bt_test = NULL;
+}
+
+static void bt_test_keys_storage_profile() {
+ // Emulate nvm change on initial connection
+ const int nvm_change_size_on_connection = 88;
+ for(size_t i = 0; i < nvm_change_size_on_connection; i++) {
+ bt_test->nvm_ram_buff_dut[i] = rand();
+ bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i];
+ }
+ // Emulate update storage on initial connect
+ mu_assert(
+ bt_keys_storage_update(
+ bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection),
+ "Failed to update key storage on initial connect");
+ memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE);
+ mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM");
+ mu_assert(
+ memcmp(
+ bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection) ==
+ 0,
+ "Wrong buffer loaded");
+
+ const int nvm_disconnect_update_offset = 84;
+ const int nvm_disconnect_update_size = 324;
+ const int nvm_total_size = nvm_change_size_on_connection -
+ (nvm_change_size_on_connection - nvm_disconnect_update_offset) +
+ nvm_disconnect_update_size;
+ // Emulate update storage on initial disconnect
+ for(size_t i = nvm_disconnect_update_offset;
+ i < nvm_disconnect_update_offset + nvm_disconnect_update_size;
+ i++) {
+ bt_test->nvm_ram_buff_dut[i] = rand();
+ bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i];
+ }
+ mu_assert(
+ bt_keys_storage_update(
+ bt_test->bt_keys_storage,
+ &bt_test->nvm_ram_buff_dut[nvm_disconnect_update_offset],
+ nvm_disconnect_update_size),
+ "Failed to update key storage on initial disconnect");
+ memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE);
+ mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM");
+ mu_assert(
+ memcmp(bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_total_size) == 0,
+ "Wrong buffer loaded");
+}
+
+static void bt_test_keys_remove_test_file() {
+ mu_assert(
+ storage_simply_remove(bt_test->storage, BT_TEST_KEY_STORAGE_FILE_PATH),
+ "Can't remove test file");
+}
+
+MU_TEST(bt_test_keys_storage_serial_profile) {
+ furi_check(bt_test);
+
+ bt_test_keys_remove_test_file();
+ bt_test_keys_storage_profile();
+ bt_test_keys_remove_test_file();
+}
+
+MU_TEST_SUITE(test_bt) {
+ bt_test_alloc();
+
+ MU_RUN_TEST(bt_test_keys_storage_serial_profile);
+
+ bt_test_free();
+}
+
+int run_minunit_test_bt() {
+ MU_RUN_SUITE(test_bt);
+ return MU_EXIT_CODE;
+}
diff --git a/applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c b/applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c
new file mode 100644
index 00000000000..2d5bad4c8a5
--- /dev/null
+++ b/applications/debug/unit_tests/dialogs/dialogs_file_browser_options.c
@@ -0,0 +1,32 @@
+#include
+
+#include "../minunit.h"
+
+MU_TEST(test_dialog_file_browser_set_basic_options_should_init_all_fields) {
+ mu_assert(
+ sizeof(DialogsFileBrowserOptions) == 28,
+ "Changes to `DialogsFileBrowserOptions` should also be reflected in `dialog_file_browser_set_basic_options`");
+
+ DialogsFileBrowserOptions options;
+ dialog_file_browser_set_basic_options(&options, ".fap", NULL);
+ // note: this assertions can safely be changed, their primary purpose is to remind the maintainer
+ // to update `dialog_file_browser_set_basic_options` by including all structure fields in it
+ mu_assert_string_eq(".fap", options.extension);
+ mu_assert_null(options.base_path);
+ mu_assert(options.skip_assets, "`skip_assets` should default to `true");
+ mu_assert(options.hide_dot_files, "`hide_dot_files` should default to `true");
+ mu_assert_null(options.icon);
+ mu_assert(options.hide_ext, "`hide_ext` should default to `true");
+ mu_assert_null(options.item_loader_callback);
+ mu_assert_null(options.item_loader_context);
+}
+
+MU_TEST_SUITE(dialogs_file_browser_options) {
+ MU_RUN_TEST(test_dialog_file_browser_set_basic_options_should_init_all_fields);
+}
+
+int run_minunit_test_dialogs_file_browser_options() {
+ MU_RUN_SUITE(dialogs_file_browser_options);
+
+ return MU_EXIT_CODE;
+}
diff --git a/applications/unit_tests/flipper_format/flipper_format_string_test.c b/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c
similarity index 95%
rename from applications/unit_tests/flipper_format/flipper_format_string_test.c
rename to applications/debug/unit_tests/flipper_format/flipper_format_string_test.c
index b22b333a394..920a22a43bf 100644
--- a/applications/unit_tests/flipper_format/flipper_format_string_test.c
+++ b/applications/debug/unit_tests/flipper_format/flipper_format_string_test.c
@@ -58,7 +58,7 @@ static const char* test_data_win = "Filetype: Flipper Format test\r\n"
#define ARRAY_W_BSIZE(x) (x), (sizeof(x))
MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) {
- string_t tmpstr;
+ FuriString* tmpstr;
uint32_t version;
uint32_t uint32_data[COUNT_OF(test_uint_data)];
int32_t int32_data[COUNT_OF(test_int_data)];
@@ -101,14 +101,14 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) {
mu_assert_int_eq(position_before, stream_tell(flipper_format_get_raw_stream(flipper_format)));
// read test
- string_init(tmpstr);
+ tmpstr = furi_string_alloc();
mu_check(flipper_format_read_header(flipper_format, tmpstr, &version));
- mu_assert_string_eq(test_filetype, string_get_cstr(tmpstr));
+ mu_assert_string_eq(test_filetype, furi_string_get_cstr(tmpstr));
mu_assert_int_eq(test_version, version);
mu_check(flipper_format_read_string(flipper_format, test_string_key, tmpstr));
- mu_assert_string_eq(test_string_data, string_get_cstr(tmpstr));
+ mu_assert_string_eq(test_string_data, furi_string_get_cstr(tmpstr));
mu_check(flipper_format_get_value_count(flipper_format, test_int_key, &count));
mu_assert_int_eq(COUNT_OF(test_int_data), count);
@@ -133,7 +133,7 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) {
mu_check(!flipper_format_read_string(flipper_format, "Key that doesn't exist", tmpstr));
- string_clear(tmpstr);
+ furi_string_free(tmpstr);
// update data
mu_check(flipper_format_rewind(flipper_format));
@@ -155,14 +155,14 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) {
uint8_t hex_updated_data[COUNT_OF(test_hex_updated_data)];
mu_check(flipper_format_rewind(flipper_format));
- string_init(tmpstr);
+ tmpstr = furi_string_alloc();
mu_check(flipper_format_read_header(flipper_format, tmpstr, &version));
- mu_assert_string_eq(test_filetype, string_get_cstr(tmpstr));
+ mu_assert_string_eq(test_filetype, furi_string_get_cstr(tmpstr));
mu_assert_int_eq(test_version, version);
mu_check(flipper_format_read_string(flipper_format, test_string_key, tmpstr));
- mu_assert_string_eq(test_string_updated_data, string_get_cstr(tmpstr));
+ mu_assert_string_eq(test_string_updated_data, furi_string_get_cstr(tmpstr));
mu_check(flipper_format_get_value_count(flipper_format, test_int_key, &count));
mu_assert_int_eq(COUNT_OF(test_int_updated_data), count);
@@ -190,7 +190,7 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) {
mu_check(!flipper_format_read_string(flipper_format, "Key that doesn't exist", tmpstr));
- string_clear(tmpstr);
+ furi_string_free(tmpstr);
// update data
mu_check(flipper_format_rewind(flipper_format));
@@ -214,14 +214,14 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) {
uint8_t hex_new_data[COUNT_OF(test_hex_new_data)];
mu_check(flipper_format_rewind(flipper_format));
- string_init(tmpstr);
+ tmpstr = furi_string_alloc();
mu_check(flipper_format_read_header(flipper_format, tmpstr, &version));
- mu_assert_string_eq(test_filetype, string_get_cstr(tmpstr));
+ mu_assert_string_eq(test_filetype, furi_string_get_cstr(tmpstr));
mu_assert_int_eq(test_version, version);
mu_check(flipper_format_read_string(flipper_format, test_string_key, tmpstr));
- mu_assert_string_eq(test_string_updated_2_data, string_get_cstr(tmpstr));
+ mu_assert_string_eq(test_string_updated_2_data, furi_string_get_cstr(tmpstr));
mu_check(flipper_format_get_value_count(flipper_format, test_int_key, &count));
mu_assert_int_eq(COUNT_OF(test_int_updated_2_data), count);
@@ -255,7 +255,7 @@ MU_TEST_1(flipper_format_read_and_update_test, FlipperFormat* flipper_format) {
mu_check(!flipper_format_read_string(flipper_format, "Key that doesn't exist", tmpstr));
- string_clear(tmpstr);
+ furi_string_free(tmpstr);
// delete key test
mu_check(flipper_format_rewind(flipper_format));
diff --git a/applications/unit_tests/flipper_format/flipper_format_test.c b/applications/debug/unit_tests/flipper_format/flipper_format_test.c
similarity index 90%
rename from applications/unit_tests/flipper_format/flipper_format_test.c
rename to applications/debug/unit_tests/flipper_format/flipper_format_test.c
index 86e2df21eff..012e905b690 100644
--- a/applications/unit_tests/flipper_format/flipper_format_test.c
+++ b/applications/debug/unit_tests/flipper_format/flipper_format_test.c
@@ -57,6 +57,23 @@ static const char* test_data_win = "Filetype: Flipper File test\r\n"
"Hex data: DE AD BE";
#define READ_TEST_FLP "ff_flp.test"
+#define READ_TEST_ODD "ff_oddities.test"
+static const char* test_data_odd = "Filetype: Flipper File test\n"
+ // Tabs before newline
+ "Version: 666\t\t\n"
+ "# This is comment\n"
+ // Windows newline in a UNIX file
+ "String data: String\r\n"
+ // Trailing whitespace
+ "Int32 data: 1234 -6345 7813 0 \n"
+ // Extra whitespace
+ "Uint32 data: 1234 0 5678 9098 7654321 \n"
+ // Mixed whitespace
+ "Float data: 1.5\t \t1000.0\n"
+ // Leading tabs after key
+ "Bool data:\t\ttrue false\n"
+ // Mixed trailing whitespace
+ "Hex data: DE AD BE\t ";
// data created by user on linux machine
static const char* test_file_linux = TEST_DIR READ_TEST_NIX;
@@ -64,6 +81,8 @@ static const char* test_file_linux = TEST_DIR READ_TEST_NIX;
static const char* test_file_windows = TEST_DIR READ_TEST_WIN;
// data created by flipper itself
static const char* test_file_flipper = TEST_DIR READ_TEST_FLP;
+// data containing odd user input
+static const char* test_file_oddities = TEST_DIR READ_TEST_ODD;
static bool storage_write_string(const char* path, const char* data) {
Storage* storage = furi_record_open(RECORD_STORAGE);
@@ -102,8 +121,8 @@ static bool test_read(const char* file_name) {
bool result = false;
FlipperFormat* file = flipper_format_file_alloc(storage);
- string_t string_value;
- string_init(string_value);
+ FuriString* string_value;
+ string_value = furi_string_alloc();
uint32_t uint32_value;
void* scratchpad = malloc(512);
@@ -111,11 +130,11 @@ static bool test_read(const char* file_name) {
if(!flipper_format_file_open_existing(file, file_name)) break;
if(!flipper_format_read_header(file, string_value, &uint32_value)) break;
- if(string_cmp_str(string_value, test_filetype) != 0) break;
+ if(furi_string_cmp_str(string_value, test_filetype) != 0) break;
if(uint32_value != test_version) break;
if(!flipper_format_read_string(file, test_string_key, string_value)) break;
- if(string_cmp_str(string_value, test_string_data) != 0) break;
+ if(furi_string_cmp_str(string_value, test_string_data) != 0) break;
if(!flipper_format_get_value_count(file, test_int_key, &uint32_value)) break;
if(uint32_value != COUNT_OF(test_int_data)) break;
@@ -150,7 +169,7 @@ static bool test_read(const char* file_name) {
} while(false);
free(scratchpad);
- string_clear(string_value);
+ furi_string_free(string_value);
flipper_format_free(file);
@@ -164,8 +183,8 @@ static bool test_read_updated(const char* file_name) {
bool result = false;
FlipperFormat* file = flipper_format_file_alloc(storage);
- string_t string_value;
- string_init(string_value);
+ FuriString* string_value;
+ string_value = furi_string_alloc();
uint32_t uint32_value;
void* scratchpad = malloc(512);
@@ -173,11 +192,11 @@ static bool test_read_updated(const char* file_name) {
if(!flipper_format_file_open_existing(file, file_name)) break;
if(!flipper_format_read_header(file, string_value, &uint32_value)) break;
- if(string_cmp_str(string_value, test_filetype) != 0) break;
+ if(furi_string_cmp_str(string_value, test_filetype) != 0) break;
if(uint32_value != test_version) break;
if(!flipper_format_read_string(file, test_string_key, string_value)) break;
- if(string_cmp_str(string_value, test_string_updated_data) != 0) break;
+ if(furi_string_cmp_str(string_value, test_string_updated_data) != 0) break;
if(!flipper_format_get_value_count(file, test_int_key, &uint32_value)) break;
if(uint32_value != COUNT_OF(test_int_updated_data)) break;
@@ -228,7 +247,7 @@ static bool test_read_updated(const char* file_name) {
} while(false);
free(scratchpad);
- string_clear(string_value);
+ furi_string_free(string_value);
flipper_format_free(file);
@@ -401,14 +420,14 @@ static bool test_read_multikey(const char* file_name) {
bool result = false;
FlipperFormat* file = flipper_format_file_alloc(storage);
- string_t string_value;
- string_init(string_value);
+ FuriString* string_value;
+ string_value = furi_string_alloc();
uint32_t uint32_value;
do {
if(!flipper_format_file_open_existing(file, file_name)) break;
if(!flipper_format_read_header(file, string_value, &uint32_value)) break;
- if(string_cmp_str(string_value, test_filetype) != 0) break;
+ if(furi_string_cmp_str(string_value, test_filetype) != 0) break;
if(uint32_value != test_version) break;
bool error = false;
@@ -429,7 +448,7 @@ static bool test_read_multikey(const char* file_name) {
result = true;
} while(false);
- string_clear(string_value);
+ furi_string_free(string_value);
flipper_format_free(file);
furi_record_close(RECORD_STORAGE);
@@ -503,6 +522,12 @@ MU_TEST(flipper_format_multikey_test) {
mu_assert(test_read_multikey(TEST_DIR "ff_multiline.test"), "Multikey read test error");
}
+MU_TEST(flipper_format_oddities_test) {
+ mu_assert(
+ storage_write_string(test_file_oddities, test_data_odd), "Write test error [Oddities]");
+ mu_assert(test_read(test_file_linux), "Read test error [Oddities]");
+}
+
MU_TEST_SUITE(flipper_format) {
tests_setup();
MU_RUN_TEST(flipper_format_write_test);
@@ -516,6 +541,7 @@ MU_TEST_SUITE(flipper_format) {
MU_RUN_TEST(flipper_format_update_2_test);
MU_RUN_TEST(flipper_format_update_2_result_test);
MU_RUN_TEST(flipper_format_multikey_test);
+ MU_RUN_TEST(flipper_format_oddities_test);
tests_teardown();
}
diff --git a/applications/debug/unit_tests/float_tools/float_tools_test.c b/applications/debug/unit_tests/float_tools/float_tools_test.c
new file mode 100644
index 00000000000..fc5b4ecfd8b
--- /dev/null
+++ b/applications/debug/unit_tests/float_tools/float_tools_test.c
@@ -0,0 +1,60 @@
+#include
+#include
+
+#include "../minunit.h"
+
+MU_TEST(float_tools_equal_test) {
+ mu_check(float_is_equal(FLT_MAX, FLT_MAX));
+ mu_check(float_is_equal(FLT_MIN, FLT_MIN));
+ mu_check(float_is_equal(-FLT_MAX, -FLT_MAX));
+ mu_check(float_is_equal(-FLT_MIN, -FLT_MIN));
+
+ mu_check(!float_is_equal(FLT_MIN, FLT_MAX));
+ mu_check(!float_is_equal(-FLT_MIN, FLT_MAX));
+ mu_check(!float_is_equal(FLT_MIN, -FLT_MAX));
+ mu_check(!float_is_equal(-FLT_MIN, -FLT_MAX));
+
+ const float pi = 3.14159f;
+ mu_check(float_is_equal(pi, pi));
+ mu_check(float_is_equal(-pi, -pi));
+ mu_check(!float_is_equal(pi, -pi));
+ mu_check(!float_is_equal(-pi, pi));
+
+ const float one_third = 1.f / 3.f;
+ const float one_third_dec = 0.3333333f;
+ mu_check(one_third != one_third_dec);
+ mu_check(float_is_equal(one_third, one_third_dec));
+
+ const float big_num = 1.e12f;
+ const float med_num = 95.389f;
+ const float smol_num = 1.e-12f;
+ mu_check(float_is_equal(big_num, big_num));
+ mu_check(float_is_equal(med_num, med_num));
+ mu_check(float_is_equal(smol_num, smol_num));
+ mu_check(!float_is_equal(smol_num, big_num));
+ mu_check(!float_is_equal(med_num, smol_num));
+ mu_check(!float_is_equal(big_num, med_num));
+
+ const float more_than_one = 1.f + FLT_EPSILON;
+ const float less_than_one = 1.f - FLT_EPSILON;
+ mu_check(!float_is_equal(more_than_one, less_than_one));
+ mu_check(!float_is_equal(more_than_one, -less_than_one));
+ mu_check(!float_is_equal(-more_than_one, less_than_one));
+ mu_check(!float_is_equal(-more_than_one, -less_than_one));
+
+ const float slightly_more_than_one = 1.f + FLT_EPSILON / 2.f;
+ const float slightly_less_than_one = 1.f - FLT_EPSILON / 2.f;
+ mu_check(float_is_equal(slightly_more_than_one, slightly_less_than_one));
+ mu_check(float_is_equal(-slightly_more_than_one, -slightly_less_than_one));
+ mu_check(!float_is_equal(slightly_more_than_one, -slightly_less_than_one));
+ mu_check(!float_is_equal(-slightly_more_than_one, slightly_less_than_one));
+}
+
+MU_TEST_SUITE(float_tools_suite) {
+ MU_RUN_TEST(float_tools_equal_test);
+}
+
+int run_minunit_test_float_tools() {
+ MU_RUN_SUITE(float_tools_suite);
+ return MU_EXIT_CODE;
+}
diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c
new file mode 100644
index 00000000000..a28632cf4da
--- /dev/null
+++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c
@@ -0,0 +1,38 @@
+#include "../minunit.h"
+#include