mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-17 20:19:43 -07:00
V42 Release Candidate Changes (#155)
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,22 @@
|
||||
## ⬇️ Download
|
||||
>### [🐬 qFlipper Package (.tgz)](https://github.com/ClaraCrazy/Flipper-Xtreme/releases/download/{release_tag}/{release_tag}.tgz) [recommended]
|
||||
|
||||
>### [📦 Zipped Archive (.zip)](https://github.com/ClaraCrazy/Flipper-Xtreme/releases/download/{release_tag}/{release_tag}.zip)
|
||||
|
||||
>### [🖥️ Web Updater (chrome)](https://lab.flipper.net/?url={webupdater_url}&channel=XFW-Updater&version={release_tag}) [not recommended]
|
||||
|
||||
**Remember to delete your `apps` (and `update` if using "Install from file...") folders before updating!**\
|
||||
**Check the [install guide](https://github.com/ClaraCrazy/Flipper-Xtreme#install) if you're not sure, or [join our Discord](https://discord.gg/flipper-xtreme) if you have questions!**
|
||||
|
||||
## 🚀 Changelog
|
||||
{changelog}
|
||||
|
||||
## ❤️ Support
|
||||
If you like what you're seeing, **please consider donating to us**. We won't ever put this behind a paywall, but we'd still appreciate a few bucks!
|
||||
|
||||
- **[Direct transfer to my bank](https://bunq.me/ClaraK)**: No account needed, they have a convenient online pay thingy (that hates americans, sowwy)
|
||||
- **[Patreon](https://patreon.com/CynthiaLabs)**
|
||||
- **[Paypal](https://paypal.me/RdX2020)**
|
||||
- **Monero**: 41kyWeeoVdK4quzQ4M9ikVGs6tCQCLfdx8jLExTNsAu2SF1QAyDqRdjfGM6EL8L9NpXwt89HJeAoGf1aoArk7nDr4AMMV4T
|
||||
|
||||
**Thanks for all your support <3**
|
||||
@@ -1,28 +1,22 @@
|
||||
name: 'Build'
|
||||
name: 'Build artifact'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TARGETS: f7 f18
|
||||
TARGETS: f7
|
||||
DEFAULT_TARGET: f7
|
||||
FBT_TOOLCHAIN_PATH: /home/runner/work
|
||||
|
||||
jobs:
|
||||
main:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
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@v3
|
||||
@@ -30,70 +24,24 @@ jobs:
|
||||
fetch-depth: 0
|
||||
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"
|
||||
echo random_hash=$(openssl rand -base64 40 | shasum -a 256 | awk '{print $1}') >> $GITHUB_OUTPUT
|
||||
echo "event_type=$TYPE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 'Make artifacts directory'
|
||||
run: |
|
||||
rm -rf artifacts
|
||||
mkdir artifacts
|
||||
|
||||
- name: 'Bundle scripts'
|
||||
run: |
|
||||
tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts debug
|
||||
|
||||
- name: 'Build the firmware'
|
||||
run: |
|
||||
set -e
|
||||
for TARGET in ${TARGETS}; do
|
||||
TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
|
||||
./fbt TARGET_HW=$TARGET copro_dist updater_package \
|
||||
${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }}
|
||||
done
|
||||
|
||||
- name: 'Move upload files'
|
||||
run: |
|
||||
set -e
|
||||
for TARGET in ${TARGETS}; do
|
||||
mv dist/${TARGET}-*/* artifacts/
|
||||
./fbt TARGET_HW=$TARGET updater_package
|
||||
done
|
||||
|
||||
- name: "Check for uncommitted changes"
|
||||
run: |
|
||||
git diff --exit-code
|
||||
|
||||
- name: 'Bundle resources'
|
||||
run: |
|
||||
tar czpf "artifacts/flipper-z-any-resources-${SUFFIX}.tgz" -C assets resources
|
||||
|
||||
- name: 'Bundle core2 firmware'
|
||||
run: |
|
||||
cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz"
|
||||
|
||||
- name: 'Updater artifact'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: updater
|
||||
path: |
|
||||
artifacts/f7-*
|
||||
|
||||
- name: 'Firmware artifact'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: firmware
|
||||
path: |
|
||||
artifacts
|
||||
dist/${{ env.DEFAULT_TARGET }}-*
|
||||
|
||||
# - name: 'Find Previous Comment'
|
||||
# if: ${{ github.event.pull_request }}
|
||||
@@ -102,7 +50,7 @@ jobs:
|
||||
# with:
|
||||
# issue-number: ${{ github.event.pull_request.number }}
|
||||
# comment-author: 'github-actions[bot]'
|
||||
# body-includes: 'Compiled firmware for commit'
|
||||
# body-includes: 'Compiled firmware:'
|
||||
|
||||
# - name: Artifact info
|
||||
# id: artifact-info
|
||||
@@ -117,44 +65,6 @@ jobs:
|
||||
# comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
# issue-number: ${{ github.event.pull_request.number }}
|
||||
# body: |
|
||||
# **Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:**
|
||||
# **Compiled firmware:**
|
||||
# - [📦 Update package](${{steps.artifact-info.outputs.artifacts[0].archive_download_url}})
|
||||
# edit-mode: replace
|
||||
|
||||
compact:
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags') }}
|
||||
runs-on: ubuntu-latest
|
||||
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@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
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"
|
||||
|
||||
- name: 'Build the firmware'
|
||||
run: |
|
||||
set -e
|
||||
for TARGET in ${TARGETS}; do
|
||||
TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
|
||||
./fbt TARGET_HW=$TARGET DEBUG=0 COMPACT=1 updater_package
|
||||
done
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
name: 'Check submodules branch'
|
||||
name: 'Check submodules'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
@@ -13,11 +13,6 @@ jobs:
|
||||
check_protobuf:
|
||||
runs-on: ubuntu-latest
|
||||
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@v3
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: 'Lint formatting'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
SET_GH_OUTPUT: 1
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: 'Check code formatting'
|
||||
run: ./fbt lint lint_py
|
||||
@@ -1,46 +0,0 @@
|
||||
name: 'Lint C/C++ with clang-format'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TARGETS: f7
|
||||
SET_GH_OUTPUT: 1
|
||||
|
||||
jobs:
|
||||
lint_c_cpp:
|
||||
runs-on: ubuntu-latest
|
||||
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@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: 'Check code formatting'
|
||||
id: syntax_check
|
||||
run: ./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.
|
||||
@@ -1,93 +0,0 @@
|
||||
name: 'Static C/C++ analysis with PVS-Studio'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TARGETS: f7
|
||||
DEFAULT_TARGET: f7
|
||||
FBT_TOOLCHAIN_PATH: /runner/_work
|
||||
|
||||
jobs:
|
||||
analyse_c_cpp:
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
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@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
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"
|
||||
|
||||
- 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
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
name: "Release integration"
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
env:
|
||||
TARGETS: f7
|
||||
DEFAULT_TARGET: f7
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: |
|
||||
github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name &&
|
||||
endsWith(github.event.pull_request.title, ' Release Candidate Changes') &&
|
||||
github.event.review.author_association == 'OWNER' &&
|
||||
startsWith(github.event.pull_request.title, 'V') &&
|
||||
github.event.pull_request.base.ref == 'main' &&
|
||||
github.event.pull_request.head.ref == 'dev' &&
|
||||
github.event.pull_request.state == 'open' &&
|
||||
github.event.pull_request.draft == false &&
|
||||
github.event.review.state == 'APPROVED'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- name: "Update version number"
|
||||
run: |
|
||||
VERSION="$(python -c "print('%04d' % int('${{ github.event.pull_request.title }}'.removeprefix('V').removesuffix(' Release Candidate Changes')), end='')")"
|
||||
DATE="$(python -c "from datetime import date;print(date.today().strftime('%d%m%Y'), end='')")"
|
||||
RELEASE_NAME="XFW-${VERSION}"
|
||||
RELEASE_TAG="${RELEASE_NAME}_${DATE}"
|
||||
echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_ENV
|
||||
|
||||
sed "s/VERSION = \"XFW-[[:digit:]]\{4\}\"/VERSION = \"${RELEASE_NAME}\"/g" ./scripts/version.py > ./scripts/version.py.new
|
||||
rm ./scripts/version.py
|
||||
mv ./scripts/version.py.new ./scripts/version.py
|
||||
|
||||
sed "s/DIST_SUFFIX = \"XFW-[[:digit:]]\{4\}_[[:digit:]]\{8\}\"/DIST_SUFFIX = \"${RELEASE_TAG}\"/g" ./fbt_options.py > ./fbt_options.py.new
|
||||
rm ./fbt_options.py
|
||||
mv ./fbt_options.py.new ./fbt_options.py
|
||||
|
||||
- name: 'Build the firmware'
|
||||
run: |
|
||||
set -e
|
||||
for TARGET in ${TARGETS}; do
|
||||
TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
|
||||
./fbt TARGET_HW=$TARGET updater_package
|
||||
done
|
||||
|
||||
- name: "Make tgz, zip and webupdater"
|
||||
run: |
|
||||
cd ./dist/${DEFAULT_TARGET}-*
|
||||
mv flipper-z-${DEFAULT_TARGET}-update-*.tgz ${RELEASE_TAG}.tgz
|
||||
cd ./${DEFAULT_TARGET}-update-*
|
||||
7z a ../${RELEASE_TAG}.zip .
|
||||
cd ..
|
||||
python -m pip install pyncclient
|
||||
WEBUPDATER_URL="$(python -c "import nextcloud_client as n;c = n.Client('https://cloud.cynthialabs.net/');c.login('${{ secrets.NC_USER }}', '${{ secrets.NC_PASS }}');c.put_file('XFW-Updater/${RELEASE_TAG}.tgz', '${RELEASE_TAG}.tgz');print(c.share_file_with_link('XFW-Updater/${RELEASE_TAG}.tgz').get_link().rstrip('/') + '/download/${RELEASE_TAG}.tgz', end='')")"
|
||||
echo "WEBUPDATER_URL=${WEBUPDATER_URL}" >> $GITHUB_ENV
|
||||
|
||||
- name: "Update release notes"
|
||||
run: |
|
||||
python -c "import json, os;f = '.github/workflow_data/release.md';c = json.load(open(os.environ['GITHUB_EVENT_PATH']))['pull_request']['body'];f_ = open(f);n = f_.read().format(release_tag='${RELEASE_TAG}', webupdater_url='${WEBUPDATER_URL}', changelog=c);f_.close();f_ = open(f, 'w');f_.write(n);f_.close()"
|
||||
|
||||
- name: "Commit new version number"
|
||||
uses: EndBug/add-and-commit@v9
|
||||
with:
|
||||
default_author: github_actions
|
||||
message: Update version number
|
||||
add: './fbt_options.py ./scripts/version.py'
|
||||
|
||||
- name: "Merge pull request"
|
||||
uses: "pascalgn/automerge-action@v0.15.6"
|
||||
env:
|
||||
MERGE_LABELS: ""
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: "Make release"
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body_path: ".github/workflow_data/release.md"
|
||||
draft: false
|
||||
prerelease: false
|
||||
files: |
|
||||
${{ env.RELEASE_TAG }}.tgz
|
||||
${{ env.RELEASE_TAG }}.zip
|
||||
name: "${{ env.RELEASE_TAG }}"
|
||||
tag_name: "${{ env.RELEASE_TAG }}"
|
||||
target_commitish: main
|
||||
@@ -1,33 +1,36 @@
|
||||
name: SonarCloud
|
||||
name: 'SonarCloud analysis'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
env:
|
||||
TARGETS: f7
|
||||
DEFAULT_TARGET: f7
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and analyze
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SONAR_SCANNER_VERSION: 4.7.0.2747
|
||||
SONAR_SERVER_URL: "https://sonarcloud.io"
|
||||
BUILD_WRAPPER_OUT_DIR: "$HOME/.sonar/build_wrapper_output" # Directory where build-wrapper output will be placed
|
||||
FBT_NO_SYNC: "true"
|
||||
TARGETS: f7
|
||||
DEFAULT_TARGET: f7
|
||||
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
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive' # FBT_NO_SYNC is on, get submodules now
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
submodules: 'recursive'
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
|
||||
- name: Download and set up sonar-scanner
|
||||
env:
|
||||
SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip
|
||||
@@ -36,6 +39,7 @@ jobs:
|
||||
curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }}
|
||||
unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
|
||||
echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Download and set up build-wrapper
|
||||
env:
|
||||
BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip
|
||||
@@ -43,10 +47,16 @@ jobs:
|
||||
curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }}
|
||||
unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/
|
||||
echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH
|
||||
|
||||
- name: Run build-wrapper
|
||||
run: |
|
||||
mkdir $HOME/.sonar/build_wrapper_output
|
||||
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} ./sonar-build
|
||||
mkdir ${{ env.BUILD_WRAPPER_OUT_DIR }}
|
||||
set -e
|
||||
for TARGET in ${TARGETS}; do
|
||||
TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
|
||||
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} ./sonar-build "./fbt TARGET_HW=$TARGET updater_package"
|
||||
done
|
||||
|
||||
- name: Run sonar-scanner
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+5
-2
@@ -34,6 +34,10 @@ Brewfile.lock.json
|
||||
# Visual Studio
|
||||
.vs/
|
||||
|
||||
# Kate
|
||||
.kateproject
|
||||
.kateconfig
|
||||
|
||||
# legendary cmake's
|
||||
build
|
||||
CMakeLists.txt
|
||||
@@ -68,9 +72,8 @@ PVS-Studio.log
|
||||
# Automate files, etc
|
||||
automate.py
|
||||
deployments/
|
||||
fbt_options.py
|
||||
commitnotes.md
|
||||
lib/STM32CubeWB
|
||||
fbt_options.py
|
||||
|
||||
# Asset packs
|
||||
assets/dolphin/custom/*
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
[submodule "lib/cxxheaderparser"]
|
||||
path = lib/cxxheaderparser
|
||||
url = https://github.com/robotpy/cxxheaderparser.git
|
||||
[submodule "applications/plugins/subbrute"]
|
||||
path = applications/plugins/subbrute
|
||||
url = https://github.com/derskythe/flipperzero-subbrute.git
|
||||
[submodule "applications/plugins/dap_link/lib/free-dap"]
|
||||
path = applications/plugins/dap_link/lib/free-dap
|
||||
url = https://github.com/ataradov/free-dap.git
|
||||
|
||||
@@ -44,3 +44,6 @@
|
||||
|
||||
# Functions that always return the same error code
|
||||
//-V:picopass_device_decrypt:1048
|
||||
|
||||
# Examples
|
||||
//V_EXCLUDE_PATH applications/examples/
|
||||
Vendored
+3
-2
@@ -11,9 +11,10 @@
|
||||
"args": {
|
||||
"useSingleResult": true,
|
||||
"env": {
|
||||
"PATH": "${workspaceFolder};${env:PATH}"
|
||||
"PATH": "${workspaceFolder};${env:PATH}",
|
||||
"FBT_QUIET": 1
|
||||
},
|
||||
"command": "./fbt get_blackmagic",
|
||||
"command": "fbt get_blackmagic",
|
||||
"description": "Get Blackmagic device",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<h1 align="center">XFW - <code>Xtreme Firmware</code> for the Flipper Zero</h1>
|
||||
<h3 align="center">Feature-rich, stable, customizable</h3>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/55334727/215170306-051eeb8f-8f72-437f-8c2d-0e4be009bdad.png">
|
||||
</p>
|
||||
|
||||
[Intro](https://github.com/ClaraCrazy/Flipper-Xtreme#What-makes-it-special) | [Animations](https://github.com/ClaraCrazy/Flipper-Xtreme#Animations--Asset-Packs) | [Docs](https://github.com/ClaraCrazy/Flipper-Xtreme/wiki) | [Changelog](https://github.com/ClaraCrazy/Flipper-Xtreme#list-of-changes) | [Known bugs](https://github.com/ClaraCrazy/Flipper-Xtreme/issues?q=is%3Aissue+is%3Aopen+label%3Arelease-pending) | [Install](https://github.com/ClaraCrazy/Flipper-Xtreme#Install) | [Build](https://github.com/ClaraCrazy/Flipper-Xtreme#build-it-yourself) | [Discord](https://discord.gg/flipper-xtreme)
|
||||
[Intro](https://github.com/ClaraCrazy/Flipper-Xtreme#What-makes-it-special) | [Animations](https://github.com/ClaraCrazy/Flipper-Xtreme#Animations--Asset-Packs) | [Wiki](https://github.com/ClaraCrazy/Flipper-Xtreme/wiki) | [Changelog](https://github.com/ClaraCrazy/Flipper-Xtreme#list-of-changes) | [Known bugs](https://github.com/ClaraCrazy/Flipper-Xtreme/issues?q=is%3Aissue+is%3Aopen+label%3Arelease-pending) | [Install](https://github.com/ClaraCrazy/Flipper-Xtreme#Install) | [Build](https://github.com/ClaraCrazy/Flipper-Xtreme#build-it-yourself) | [Discord](https://discord.gg/flipper-xtreme)
|
||||
-----
|
||||
|
||||
This firmware is a complete overhaul of the [Official Firmware](https://github.com/flipperdevices/flipperzero-firmware), it also features lots of awesome code-bits from [Unleashed](https://github.com/DarkFlippers/unleashed-firmware).
|
||||
This firmware is a complete overhaul of the [Official Firmware](https://github.com/flipperdevices/flipperzero-firmware), and also features lots of awesome code-bits from [Unleashed](https://github.com/DarkFlippers/unleashed-firmware).
|
||||
|
||||
-----
|
||||
<br>
|
||||
@@ -17,35 +18,29 @@ We have spent many hours perfecting this code even further, and getting the most
|
||||
|
||||
The goal of this Firmware is to regularly bring out amazing updates based on what the community wants, with an actual understanding of whats going on. Fixing bugs that are regularly talked about, removing unstable / broken applications (.FAP) and actually using the level system that just sits abandoned everywhere else.
|
||||
<br><br>
|
||||
- The focus of this firmware is functionality & stability: If an App doesnt work, its gone
|
||||
- <h4>Feature-rich: We include all commonly found apps in the firmware, as long as they work.</h4>
|
||||
|
||||
- Asset Packs: Are you tired of having to remake your custom animations after every update, switching manifests and having a hard time sharing them, especially once you modify scanning assets too? Those times are over. Scroll down to learn more
|
||||
|
||||
- Giving the level system a purpose: Right now, each level unlocks a new wallpaper. More on that below
|
||||
|
||||
- Clean upgraded code: Some people wrote updates to certain files. These are... painful, to say the least. Here its all built with perfection in mind and integrated in a mostly clean way. I invite you all to compare the code with theirs.
|
||||
|
||||
- Up2Date: This firmware receives updates from a few repositories, not just from its Upstream. If there are functional, but yet un-merged Pull requests on another flipper firmware that are good, they will be in here!
|
||||
- <h4>Stable: Many hours have been spent rewriting core parts of the Flippers firmware as well as some of its apps to ensure stability. A task that was long needed on all Firmware, so we tackled it right away.</h4>
|
||||
|
||||
- <h4>Customizable: Dont like the Animations, want to turn on/off the Home screen icons (battery, SD card etc), change the flippers name or anything like that? You absolutely can. No need to mess with code or deal with weird manifest files. Its all done with an App.</h4>
|
||||
|
||||
-----
|
||||
<br>
|
||||
<h2 align="center">Xtreme Settings:</h2>
|
||||
|
||||
|
||||
We wrote a powerful yet easy-to-use application specifically for our Firmware, that gives you easy-access to all the fancy things we implemented:
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/55334727/215137728-529274e8-ab95-4164-a2a0-9ff712c9d3c7.gif" align="left" width="400px"/>
|
||||
<img src="https://user-images.githubusercontent.com/55334727/222941141-32e3ef23-1dad-473f-86ee-45bef66ebd77.gif" align="left" width="400px"/>
|
||||
|
||||
<code>Base Graphics:</code> Change the fallback assets used. Its either SFW (default) or NSFW
|
||||
<br><code>Asset Pack:</code> Allows you to easily customize your firmware, more on that below
|
||||
<br><code>Anim Speed:</code> Speed in which the animations play
|
||||
<br><code>Cycle Anims:</code> Duration of how long animations are played before switching to next
|
||||
<br><code>Unlock Anims:</code> Custom setting just for NSFW fallback animations. Figure it out ;)
|
||||
<br><code>Battery Icon:</code> Classic Firmware battery style toggle, just at a more convenient place
|
||||
<br><code>XP Level:</code> Changes your Flippers level
|
||||
<br><code>SubGhz Extend:</code> Allows you to extend the subghz range beyond what FZ devs planned
|
||||
<br><code>SubGhz Bypass:</code> Allows you to bypass the subghz region locks of the Flipper
|
||||
<details><summary><code>Graphics:</code></summary>Change the animation package (more on that below), the play speed of them, cycle duration and bypass level-bassed animations</details>
|
||||
|
||||
<details><summary><code>Statusbar:</code></summary>Modify the design of the statusbar seen on the main Screen. Toggle Icons, their background, the top line and modify the battery icon to your liking.</details>
|
||||
|
||||
<details><summary><code>Protocols:</code></summary>Here you can cycle between USB & Bluetooth mode for our Bad-Keyboard app, and toggle Subghz settings.</details>
|
||||
|
||||
<details><summary><code>Dolphin:</code></summary>Two simple yet sought after features: Simply change the level of your Flipper and disable / change the "Butthurt timer", aka. the time it takes for the Flipper to get sad when its not used.</details>
|
||||
|
||||
<details><summary><code>Misc:</code></summary>All the other options that dont fit elsewhere. Toggles for our custom dark mode & left-handed mode (yes, we thought about you :3 ), an option to change the Flippers name and a switch for file sorting.</details>
|
||||
|
||||
<br clear="left"/>
|
||||
|
||||
@@ -56,7 +51,7 @@ We wrote a powerful yet easy-to-use application specifically for our Firmware, t
|
||||
We created our own, new & improved Animation / Asset system, that we can finally reveal. It lets you to create and cycle through your own `Asset Packs` with only a few button presses, allowing you to easily load custom Animations and Icons like never before.
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/55334727/214010675-9eddb8f5-1dd6-4cf4-a0ee-e37af8b6c933.PNG" align="left" width="200px"/>
|
||||
You can easily create your own pack, or find some user made ones in the discord channel. Check <a href="https://github.com/ClaraCrazy/Flipper-Xtreme/wiki/1.-File-Formats">here</a> for a tutorial on creating your own. Essentially, we got our own <code>Anims</code> & <code>Icons</code> folders, inside each <code>Asset Pack</code>.
|
||||
You can easily create your own pack, or find some user made ones in the discord channel. Check <a href="https://github.com/ClaraCrazy/Flipper-Xtreme/wiki/Asset-Packs">here</a> for a tutorial on creating your own. Essentially, we got our own <code>Anims</code> & <code>Icons</code> folders, inside each <code>Asset Pack</code>.
|
||||
|
||||
<br clear="left"/>
|
||||
|
||||
@@ -71,10 +66,20 @@ Once you have some packs, upload them to your Flipper in <code>SD/dolphin_custom
|
||||
<br>
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/55334727/214013624-25dad48e-72ea-4a90-9060-66e137e0d61a.png" align="left" width="200px"/>
|
||||
After installing the packs to Flipper, hit the <code>Arrow UP</code> button on the main menu and go to <code>Xtreme Settings</code>. Here choose which pack you want and tweak the other settings how you prefer, then press back to reboot and enjoy your new assets & animations!
|
||||
After installing the packs to Flipper, hit the <code>Arrow UP</code> button on the main menu and go to <code>Xtreme Settings</code>. Here choose which pack you want and tweak the other settings how you prefer, then press back to reboot and enjoy your new assets for all apps (e.g. Subghz scanning asset) & animations!
|
||||
|
||||
<br clear="left"/>
|
||||
|
||||
-----
|
||||
<br>
|
||||
<h2 align="center">Bad Keyboard:</h2>
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/49810075/223855940-b8ee6770-4520-4bcc-a4cc-089196cf904b.png" align="left" width="250px"/>
|
||||
<! -- This fuckshit needs a captured image, but bc of blockage, i cant get one. someone do some magic plz -- !>
|
||||
BadUSB is a wonderful app, but it lacks bluetooth capabilities. Now some might argue that its useless as you will always need authentication from both sides, but what if I told you that we found a solution to this problem?
|
||||
<br><br>
|
||||
Bad-KB allows you to toggle between USB and Bluetooth mode for your attacks. In Bluetooth mode it allows you to spoof the name & MAC of the device to whatever you want. Being a JBL speaker or a wireless razer keyboard is easily doable, allowing you to trick people so you can run your payloads without needing a cable at hand.
|
||||
|
||||
-----
|
||||
<br>
|
||||
<h2 align="center">Levels:</h2>
|
||||
@@ -86,7 +91,7 @@ With this new system in place, it allows for some cool stuff like locking animat
|
||||
<details>
|
||||
<summary>Our example</summary>
|
||||
|
||||
In our case, this is used with the NSFW animations. Dont worry, these are disabled by default because I know not everyone likes to see my / anime tits and thats fine. Anyways.. each level gives a brand new background animation, they also become more and more lewd over time. (Funfact for those reading.. thats why the repository has this warning. Github doesnt like my tits :c)
|
||||
In our example case, this is used with the NSFW animation pack you can select in the Xtreme app. Dont worry, this is not used by default because I know not everyone likes to see my / anime tits and thats fine. Anyways.. each level gives a brand new background animation, they also become more and more lewd over time.
|
||||
|
||||
| Level | Animations |
|
||||
| ------------- | ------------- |
|
||||
@@ -108,6 +113,7 @@ Note: This repo is always updated with OFW & Unleashed. No need to mention all t
|
||||
- Xtreme App
|
||||
- Asset Packs
|
||||
- More UI options
|
||||
- Bad-Keyboard App
|
||||
- A new battery display-type
|
||||
- Scrolling view for long file names in browser
|
||||
- NSFW Animations tied to the level system. Read more above
|
||||
@@ -117,7 +123,6 @@ Note: This repo is always updated with OFW & Unleashed. No need to mention all t
|
||||
- Multiple NFC protocols
|
||||
- Multiple Sub-Ghz protocols | Merged from Unleashed, thanks @xMasterX
|
||||
- Subghz and IR signal replication via gpio | Credits to @ankris812
|
||||
- Honda Keys (CVE-2022-27254) & Ford blockers
|
||||
|
||||
- New API Routes for Locale settings
|
||||
```
|
||||
@@ -129,12 +134,13 @@ Note: This repo is always updated with OFW & Unleashed. No need to mention all t
|
||||
- Tons of apps
|
||||
- File browser
|
||||
- Massive compiler re-do
|
||||
- About 1k files to speed things up a lot
|
||||
- About 4k files to speed things up a lot
|
||||
- Applications to now use the new Locale setting
|
||||
```
|
||||
```txt
|
||||
[Fixed]
|
||||
|
||||
- Keyboard issues on first char
|
||||
- Passport crash on high level
|
||||
- SFW / Dummy_mode getting you XP
|
||||
- Leveling system
|
||||
@@ -152,17 +158,21 @@ Note: This repo is always updated with OFW & Unleashed. No need to mention all t
|
||||
<br>
|
||||
<h2 align="center">Install:</h2>
|
||||
|
||||
**NOTE: If you are coming from a different FW, it is recommended to delete / clear your "apps" folder on the SD card prior to updating. This folder houses all the .fap files, which do not update to the correct API versions by default if old ones are present (Thanks flipper devs). This does `NOT` remove any of your saved files!**
|
||||
**This is the recommended install procedure. Please follow these steps EXACTLY and CAREFULLY to ensure you install correctly.**
|
||||
**This process will NOT delete any saved files and simply ensures the install goes smoothly.**
|
||||
<br><br>
|
||||
|
||||
- Download the latest release (.zip) from [The releases tab](https://github.com/ClaraCrazy/Flipper-Xtreme/releases/latest)
|
||||
- Extract the archive. This is now your new Firmware folder
|
||||
- Open [qFlipper](https://flipperzero.one/update), head to `SD/Update` and simply move the firmware folder there
|
||||
- On the Flipper, hit the `Arrow Down` button, this will get you to the file menu. Hit `Arrow Left` once, and then simply search for your updates folder
|
||||
- Inside that folder, select the Firmware you just moved onto it, and run the file thats simply called `Update`
|
||||
- Download the latest release (`.tgz`) from [the release page](https://github.com/ClaraCrazy/Flipper-Xtreme/releases/latest)
|
||||
- Open [qFlipper](https://flipperzero.one/update) and connect your Flipper Zero
|
||||
- Go to the files tab, into SD Card, and DELETE THE `apps` AND `update` FOLDERS
|
||||
- Go back to the device tab
|
||||
- Click `Install from file...` and select the downloaded file (`.tgz`)
|
||||
|
||||
- Enjoy!
|
||||
|
||||
**If you have issues or crashes with that process, you can try to use `Settings > Storage > Factory Reset` then retry the install.**
|
||||
**Doing that will NOT remove your saved files, it will only forget some settings and paired devices.**
|
||||
|
||||
----
|
||||
<br>
|
||||
<h2 align="center">Build it yourself:</h2>
|
||||
|
||||
+1
-1
@@ -139,7 +139,7 @@ if GetOption("fullenv") or any(
|
||||
basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"])
|
||||
distenv.Default(basic_dist)
|
||||
|
||||
dist_dir = distenv.GetProjetDirName()
|
||||
dist_dir = distenv.GetProjectDirName()
|
||||
fap_dist = [
|
||||
distenv.Install(
|
||||
distenv.Dir(f"#/dist/{dist_dir}/apps/debug_elf"),
|
||||
|
||||
@@ -86,4 +86,4 @@ Small applications providing configuration for basic firmware and its services.
|
||||
Utility apps not visible in other menus.
|
||||
|
||||
- `storage_move_to_sd` - Data migration tool for internal storage
|
||||
- `updater` - updater service & application
|
||||
- `updater` - Update service & application
|
||||
|
||||
@@ -34,7 +34,7 @@ void AccessorApp::run(void) {
|
||||
AccessorApp::AccessorApp()
|
||||
: text_store{0} {
|
||||
notification = static_cast<NotificationApp*>(furi_record_open(RECORD_NOTIFICATION));
|
||||
onewire_host = onewire_host_alloc();
|
||||
onewire_host = onewire_host_alloc(&ibutton_gpio);
|
||||
furi_hal_power_enable_otg();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -99,4 +99,4 @@ int32_t locale_test_app(void* p) {
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
locale_test_free(app);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
App(
|
||||
appid="UART_Echo",
|
||||
name="UART Echo",
|
||||
name="[GPIO] UART Echo",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="uart_echo_app",
|
||||
cdefines=["APP_UART_ECHO"],
|
||||
@@ -8,5 +8,5 @@ App(
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_icon="uart_10px.png",
|
||||
fap_category="Misc",
|
||||
fap_category="GPIO",
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
// v2 tests
|
||||
void test_furi_create_open();
|
||||
void test_furi_valuemutex();
|
||||
void test_furi_concurrent_access();
|
||||
void test_furi_pubsub();
|
||||
|
||||
@@ -30,10 +29,6 @@ MU_TEST(mu_test_furi_create_open) {
|
||||
test_furi_create_open();
|
||||
}
|
||||
|
||||
MU_TEST(mu_test_furi_valuemutex) {
|
||||
test_furi_valuemutex();
|
||||
}
|
||||
|
||||
MU_TEST(mu_test_furi_pubsub) {
|
||||
test_furi_pubsub();
|
||||
}
|
||||
@@ -51,7 +46,6 @@ MU_TEST_SUITE(test_suite) {
|
||||
|
||||
// v2 tests
|
||||
MU_RUN_TEST(mu_test_furi_create_open);
|
||||
MU_RUN_TEST(mu_test_furi_valuemutex);
|
||||
MU_RUN_TEST(mu_test_furi_pubsub);
|
||||
MU_RUN_TEST(mu_test_furi_memmgr);
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include "../minunit.h"
|
||||
|
||||
void test_furi_valuemutex() {
|
||||
const int init_value = 0xdeadbeef;
|
||||
const int changed_value = 0x12345678;
|
||||
|
||||
int value = init_value;
|
||||
bool result;
|
||||
ValueMutex valuemutex;
|
||||
|
||||
// init mutex case
|
||||
result = init_mutex(&valuemutex, &value, sizeof(value));
|
||||
mu_assert(result, "init mutex failed");
|
||||
|
||||
// acquire mutex case
|
||||
int* value_pointer = acquire_mutex(&valuemutex, 100);
|
||||
mu_assert_pointers_eq(value_pointer, &value);
|
||||
|
||||
// second acquire mutex case
|
||||
int* value_pointer_second = acquire_mutex(&valuemutex, 100);
|
||||
mu_assert_pointers_eq(value_pointer_second, NULL);
|
||||
|
||||
// change value case
|
||||
*value_pointer = changed_value;
|
||||
mu_assert_int_eq(value, changed_value);
|
||||
|
||||
// release mutex case
|
||||
result = release_mutex(&valuemutex, &value);
|
||||
mu_assert(result, "release mutex failed");
|
||||
|
||||
// TODO
|
||||
//acquire mutex blocking case
|
||||
//write mutex blocking case
|
||||
//read mutex blocking case
|
||||
|
||||
mu_check(delete_mutex(&valuemutex));
|
||||
}
|
||||
@@ -3,56 +3,63 @@
|
||||
#include "../minunit.h"
|
||||
|
||||
static void power_test_deinit(void) {
|
||||
// Try to reset to default charging voltage
|
||||
furi_hal_power_set_battery_charging_voltage(4.208f);
|
||||
// Try to reset to default charge voltage limit
|
||||
furi_hal_power_set_battery_charge_voltage_limit(4.208f);
|
||||
}
|
||||
|
||||
MU_TEST(test_power_charge_voltage_exact) {
|
||||
// Power of 16mV charge voltages get applied exactly
|
||||
MU_TEST(test_power_charge_voltage_limit_exact) {
|
||||
// Power of 16mV charge voltage limits get applied exactly
|
||||
// (bq25896 charge controller works in 16mV increments)
|
||||
//
|
||||
// This test may need adapted if other charge controllers are used in the future.
|
||||
for(uint16_t charge_mv = 3840; charge_mv <= 4208; charge_mv += 16) {
|
||||
float charge_volt = (float)charge_mv / 1000.0f;
|
||||
furi_hal_power_set_battery_charging_voltage(charge_volt);
|
||||
mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charging_voltage());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(charge_volt);
|
||||
mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
}
|
||||
}
|
||||
|
||||
MU_TEST(test_power_charge_voltage_floating_imprecision) {
|
||||
MU_TEST(test_power_charge_voltage_limit_floating_imprecision) {
|
||||
// 4.016f should act as 4.016 V, even with floating point imprecision
|
||||
furi_hal_power_set_battery_charging_voltage(4.016f);
|
||||
mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charging_voltage());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(4.016f);
|
||||
mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
}
|
||||
|
||||
MU_TEST(test_power_charge_voltage_inexact) {
|
||||
// Charge voltages that are not power of 16mV get truncated down
|
||||
furi_hal_power_set_battery_charging_voltage(3.841f);
|
||||
mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage());
|
||||
MU_TEST(test_power_charge_voltage_limit_inexact) {
|
||||
// Charge voltage limits that are not power of 16mV get truncated down
|
||||
furi_hal_power_set_battery_charge_voltage_limit(3.841f);
|
||||
mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
|
||||
furi_hal_power_set_battery_charging_voltage(3.900f);
|
||||
mu_assert_double_eq(3.888, furi_hal_power_get_battery_charging_voltage());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(3.900f);
|
||||
mu_assert_double_eq(3.888, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
|
||||
furi_hal_power_set_battery_charging_voltage(4.200f);
|
||||
mu_assert_double_eq(4.192, furi_hal_power_get_battery_charging_voltage());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(4.200f);
|
||||
mu_assert_double_eq(4.192, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
}
|
||||
|
||||
MU_TEST(test_power_charge_voltage_invalid_clamped) {
|
||||
// Out-of-range charge voltages get clamped to 3.840 V and 4.208 V
|
||||
furi_hal_power_set_battery_charging_voltage(3.808f);
|
||||
mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage());
|
||||
MU_TEST(test_power_charge_voltage_limit_invalid_clamped) {
|
||||
// Out-of-range charge voltage limits get clamped to 3.840 V and 4.208 V
|
||||
furi_hal_power_set_battery_charge_voltage_limit(3.808f);
|
||||
mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(1.0f);
|
||||
mu_assert_double_eq(3.840, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
|
||||
// NOTE: Intentionally picking a small increment above 4.208 V to reduce the risk of an
|
||||
// unhappy battery if this fails.
|
||||
furi_hal_power_set_battery_charging_voltage(4.240f);
|
||||
mu_assert_double_eq(4.208, furi_hal_power_get_battery_charging_voltage());
|
||||
furi_hal_power_set_battery_charge_voltage_limit(4.240f);
|
||||
mu_assert_double_eq(4.208, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
// Likewise, picking a number that the uint8_t wraparound in the driver would result in a
|
||||
// VREG value under 23 if this test fails.
|
||||
// E.g. (uint8_t)((8105-3840)/16) -> 10
|
||||
furi_hal_power_set_battery_charge_voltage_limit(8.105f);
|
||||
mu_assert_double_eq(4.208, furi_hal_power_get_battery_charge_voltage_limit());
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_power_suite) {
|
||||
MU_RUN_TEST(test_power_charge_voltage_exact);
|
||||
MU_RUN_TEST(test_power_charge_voltage_floating_imprecision);
|
||||
MU_RUN_TEST(test_power_charge_voltage_inexact);
|
||||
MU_RUN_TEST(test_power_charge_voltage_invalid_clamped);
|
||||
MU_RUN_TEST(test_power_charge_voltage_limit_exact);
|
||||
MU_RUN_TEST(test_power_charge_voltage_limit_floating_imprecision);
|
||||
MU_RUN_TEST(test_power_charge_voltage_limit_inexact);
|
||||
MU_RUN_TEST(test_power_charge_voltage_limit_invalid_clamped);
|
||||
power_test_deinit();
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ static void test_rpc_setup(void) {
|
||||
}
|
||||
furi_check(rpc_session[0].session);
|
||||
|
||||
rpc_session[0].output_stream = furi_stream_buffer_alloc(1000, 1);
|
||||
rpc_session[0].output_stream = furi_stream_buffer_alloc(4096, 1);
|
||||
rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback);
|
||||
rpc_session[0].close_session_semaphore = xSemaphoreCreateBinary();
|
||||
rpc_session[0].terminate_semaphore = xSemaphoreCreateBinary();
|
||||
@@ -191,7 +191,7 @@ static void clean_directory(Storage* fs_api, const char* clean_dir) {
|
||||
size_t size = strlen(clean_dir) + strlen(name) + 1 + 1;
|
||||
char* fullname = malloc(size);
|
||||
snprintf(fullname, size, "%s/%s", clean_dir, name);
|
||||
if(fileinfo.flags & FSF_DIRECTORY) {
|
||||
if(file_info_is_dir(&fileinfo)) {
|
||||
clean_directory(fs_api, fullname);
|
||||
}
|
||||
FS_Error error = storage_common_remove(fs_api, fullname);
|
||||
@@ -608,9 +608,8 @@ static void test_rpc_storage_list_create_expected_list(
|
||||
}
|
||||
|
||||
if(path_contains_only_ascii(name)) {
|
||||
list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ?
|
||||
PB_Storage_File_FileType_DIR :
|
||||
PB_Storage_File_FileType_FILE;
|
||||
list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR :
|
||||
PB_Storage_File_FileType_FILE;
|
||||
list->file[i].size = fileinfo.size;
|
||||
list->file[i].data = NULL;
|
||||
/* memory free inside rpc_encode_and_send() -> pb_release() */
|
||||
@@ -873,7 +872,7 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) {
|
||||
if(error == FSE_OK) {
|
||||
response->which_content = PB_Main_storage_stat_response_tag;
|
||||
response->content.storage_stat_response.has_file = true;
|
||||
response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ?
|
||||
response->content.storage_stat_response.file.type = file_info_is_dir(&fileinfo) ?
|
||||
PB_Storage_File_FileType_DIR :
|
||||
PB_Storage_File_FileType_FILE;
|
||||
response->content.storage_stat_response.file.size = fileinfo.size;
|
||||
|
||||
@@ -179,7 +179,7 @@ MU_TEST_1(test_dirwalk_full, Storage* storage) {
|
||||
|
||||
while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
|
||||
furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
|
||||
mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
|
||||
mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo)));
|
||||
}
|
||||
|
||||
dir_walk_free(dir_walk);
|
||||
@@ -204,7 +204,7 @@ MU_TEST_1(test_dirwalk_no_recursive, Storage* storage) {
|
||||
|
||||
while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
|
||||
furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
|
||||
mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
|
||||
mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo)));
|
||||
}
|
||||
|
||||
dir_walk_free(dir_walk);
|
||||
@@ -219,7 +219,7 @@ static bool test_dirwalk_filter_no_folder_ext(const char* name, FileInfo* filein
|
||||
UNUSED(ctx);
|
||||
|
||||
// only files
|
||||
if(!(fileinfo->flags & FSF_DIRECTORY)) {
|
||||
if(!file_info_is_dir(fileinfo)) {
|
||||
// with ".test" in name
|
||||
if(strstr(name, ".test") != NULL) {
|
||||
return true;
|
||||
@@ -243,7 +243,7 @@ MU_TEST_1(test_dirwalk_filter, Storage* storage) {
|
||||
|
||||
while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
|
||||
furi_string_right(path, strlen(EXT_PATH("dirwalk/")));
|
||||
mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
|
||||
mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo)));
|
||||
}
|
||||
|
||||
dir_walk_free(dir_walk);
|
||||
|
||||
@@ -2,9 +2,40 @@
|
||||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
// DO NOT USE THIS IN PRODUCTION CODE
|
||||
// This is a hack to access internal storage functions and definitions
|
||||
#include <storage/storage_i.h>
|
||||
|
||||
#define UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/" path)
|
||||
|
||||
#define STORAGE_LOCKED_FILE EXT_PATH("locked_file.test")
|
||||
#define STORAGE_LOCKED_DIR STORAGE_INT_PATH_PREFIX
|
||||
|
||||
#define STORAGE_TEST_DIR UNIT_TESTS_PATH("test_dir")
|
||||
|
||||
static bool storage_file_create(Storage* storage, const char* path, const char* data) {
|
||||
File* file = storage_file_alloc(storage);
|
||||
bool result = false;
|
||||
do {
|
||||
if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_NEW)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(storage_file_write(file, data, strlen(data)) != strlen(data)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!storage_file_close(file)) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = true;
|
||||
} while(0);
|
||||
|
||||
storage_file_free(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void storage_file_open_lock_setup() {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
@@ -115,7 +146,7 @@ static int32_t storage_dir_locker(void* ctx) {
|
||||
File* file = storage_file_alloc(storage);
|
||||
furi_check(storage_dir_open(file, STORAGE_LOCKED_DIR));
|
||||
furi_semaphore_release(semaphore);
|
||||
furi_delay_ms(1000);
|
||||
furi_delay_ms(100);
|
||||
|
||||
furi_check(storage_dir_close(file));
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
@@ -152,9 +183,21 @@ MU_TEST(storage_dir_open_lock) {
|
||||
mu_assert(result, "cannot open locked dir");
|
||||
}
|
||||
|
||||
MU_TEST(storage_dir_exists_test) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
mu_check(!storage_dir_exists(storage, STORAGE_TEST_DIR));
|
||||
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, STORAGE_TEST_DIR));
|
||||
mu_check(storage_dir_exists(storage, STORAGE_TEST_DIR));
|
||||
mu_assert_int_eq(FSE_OK, storage_common_remove(storage, STORAGE_TEST_DIR));
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(storage_dir) {
|
||||
MU_RUN_TEST(storage_dir_open_close);
|
||||
MU_RUN_TEST(storage_dir_open_lock);
|
||||
MU_RUN_TEST(storage_dir_exists_test);
|
||||
}
|
||||
|
||||
static const char* const storage_copy_test_paths[] = {
|
||||
@@ -303,9 +346,256 @@ MU_TEST_SUITE(storage_rename) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
#define APPSDATA_APP_PATH(path) APPS_DATA_PATH "/" path
|
||||
|
||||
static const char* storage_test_apps[] = {
|
||||
"-_twilight_-",
|
||||
"-_rainbow_-",
|
||||
"-_pinkie_-",
|
||||
"-_apple_-",
|
||||
"-_flutter_-",
|
||||
"-_rare_-",
|
||||
};
|
||||
|
||||
static size_t storage_test_apps_count = COUNT_OF(storage_test_apps);
|
||||
|
||||
static int32_t storage_test_app(void* arg) {
|
||||
UNUSED(arg);
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_common_remove(storage, "/app/test");
|
||||
int32_t ret = storage_file_create(storage, "/app/test", "test");
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
MU_TEST(test_storage_data_path_apps) {
|
||||
for(size_t i = 0; i < storage_test_apps_count; i++) {
|
||||
FuriThread* thread =
|
||||
furi_thread_alloc_ex(storage_test_apps[i], 1024, storage_test_app, NULL);
|
||||
furi_thread_set_appid(thread, storage_test_apps[i]);
|
||||
furi_thread_start(thread);
|
||||
furi_thread_join(thread);
|
||||
|
||||
mu_assert_int_eq(true, furi_thread_get_return_code(thread));
|
||||
|
||||
// Check if app data dir and file exists
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FuriString* expected = furi_string_alloc();
|
||||
furi_string_printf(expected, APPSDATA_APP_PATH("%s"), storage_test_apps[i]);
|
||||
|
||||
mu_check(storage_dir_exists(storage, furi_string_get_cstr(expected)));
|
||||
furi_string_cat(expected, "/test");
|
||||
mu_check(storage_file_exists(storage, furi_string_get_cstr(expected)));
|
||||
|
||||
furi_string_printf(expected, APPSDATA_APP_PATH("%s"), storage_test_apps[i]);
|
||||
storage_simply_remove_recursive(storage, furi_string_get_cstr(expected));
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
furi_string_free(expected);
|
||||
furi_thread_free(thread);
|
||||
}
|
||||
}
|
||||
|
||||
MU_TEST(test_storage_data_path) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
File* file = storage_file_alloc(storage);
|
||||
mu_check(storage_dir_open(file, "/app"));
|
||||
mu_check(storage_dir_close(file));
|
||||
storage_file_free(file);
|
||||
|
||||
// check that appsdata folder exists
|
||||
mu_check(storage_dir_exists(storage, APPS_DATA_PATH));
|
||||
|
||||
// check that cli folder exists
|
||||
mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli")));
|
||||
|
||||
storage_simply_remove(storage, APPSDATA_APP_PATH("cli"));
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
MU_TEST(test_storage_common_migrate) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
// Setup test folders
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
|
||||
|
||||
// Test migration from non existing
|
||||
mu_assert_int_eq(
|
||||
FSE_OK,
|
||||
storage_common_migrate(
|
||||
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
|
||||
|
||||
// Test migration from existing folder to non existing
|
||||
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file1"), "test1"));
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file2.ext"), "test2"));
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file3.ext.ext"), "test3"));
|
||||
mu_assert_int_eq(
|
||||
FSE_OK,
|
||||
storage_common_migrate(
|
||||
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
|
||||
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file1")));
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file2.ext")));
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext.ext")));
|
||||
mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
|
||||
mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
|
||||
// Test migration from existing folder to existing folder
|
||||
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file1"), "test1"));
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file2.ext"), "test2"));
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file3.ext.ext"), "test3"));
|
||||
|
||||
mu_assert_int_eq(
|
||||
FSE_OK,
|
||||
storage_common_migrate(
|
||||
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
|
||||
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file1")));
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file2.ext")));
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext.ext")));
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file11")));
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file21.ext")));
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext1.ext")));
|
||||
mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
|
||||
mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
|
||||
|
||||
// Test migration from empty folder to existing file
|
||||
// Expected result: FSE_OK, folder removed, file untouched
|
||||
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new"), "test1"));
|
||||
|
||||
mu_assert_int_eq(
|
||||
FSE_OK,
|
||||
storage_common_migrate(
|
||||
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
|
||||
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new")));
|
||||
mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
|
||||
|
||||
// Test migration from empty folder to existing folder
|
||||
// Expected result: FSE_OK, old folder removed, new folder untouched
|
||||
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_new")));
|
||||
|
||||
mu_assert_int_eq(
|
||||
FSE_OK,
|
||||
storage_common_migrate(
|
||||
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
|
||||
|
||||
mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
|
||||
mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
|
||||
|
||||
// Test migration from existing file to non existing, no extension
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1"));
|
||||
|
||||
mu_assert_int_eq(
|
||||
FSE_OK,
|
||||
storage_common_migrate(
|
||||
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
|
||||
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new")));
|
||||
mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
|
||||
|
||||
// Test migration from existing file to non existing, with extension
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old.file"), "test1"));
|
||||
|
||||
mu_assert_int_eq(
|
||||
FSE_OK,
|
||||
storage_common_migrate(
|
||||
storage, UNIT_TESTS_PATH("migrate_old.file"), UNIT_TESTS_PATH("migrate_new.file")));
|
||||
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new.file")));
|
||||
mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old.file")));
|
||||
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old.file"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new.file"));
|
||||
|
||||
// Test migration from existing file to existing file, no extension
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1"));
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new"), "test2"));
|
||||
|
||||
mu_assert_int_eq(
|
||||
FSE_OK,
|
||||
storage_common_migrate(
|
||||
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
|
||||
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new")));
|
||||
mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1")));
|
||||
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1"));
|
||||
|
||||
// Test migration from existing file to existing file, with extension
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old.file"), "test1"));
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new.file"), "test2"));
|
||||
|
||||
mu_assert_int_eq(
|
||||
FSE_OK,
|
||||
storage_common_migrate(
|
||||
storage, UNIT_TESTS_PATH("migrate_old.file"), UNIT_TESTS_PATH("migrate_new.file")));
|
||||
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new.file")));
|
||||
mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old.file")));
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1.file")));
|
||||
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old.file"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new.file"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1.file"));
|
||||
|
||||
// Test migration from existing file to existing folder
|
||||
mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1"));
|
||||
mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_new")));
|
||||
|
||||
mu_assert_int_eq(
|
||||
FSE_OK,
|
||||
storage_common_migrate(
|
||||
storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new")));
|
||||
|
||||
mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new")));
|
||||
mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old")));
|
||||
mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1")));
|
||||
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new"));
|
||||
storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1"));
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_data_path) {
|
||||
MU_RUN_TEST(test_storage_data_path);
|
||||
MU_RUN_TEST(test_storage_data_path_apps);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_storage_common) {
|
||||
MU_RUN_TEST(test_storage_common_migrate);
|
||||
}
|
||||
|
||||
int run_minunit_test_storage() {
|
||||
MU_RUN_SUITE(storage_file);
|
||||
MU_RUN_SUITE(storage_dir);
|
||||
MU_RUN_SUITE(storage_rename);
|
||||
MU_RUN_SUITE(test_data_path);
|
||||
MU_RUN_SUITE(test_storage_common);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
#define KEYSTORE_DIR_NAME EXT_PATH("subghz/assets/keeloq_mfcodes")
|
||||
#define CAME_ATOMO_DIR_NAME EXT_PATH("subghz/assets/came_atomo")
|
||||
#define NICE_FLOR_S_DIR_NAME EXT_PATH("subghz/assets/nice_flor_s")
|
||||
#define ALUTECH_AT_4N_DIR_NAME EXT_PATH("subghz/assets/alutech_at_4n")
|
||||
#define TEST_RANDOM_DIR_NAME EXT_PATH("unit_tests/subghz/test_random_raw.sub")
|
||||
#define TEST_RANDOM_COUNT_PARSE 273
|
||||
#define TEST_RANDOM_COUNT_PARSE 329
|
||||
#define TEST_TIMEOUT 10000
|
||||
|
||||
static SubGhzEnvironment* environment_handler;
|
||||
@@ -43,6 +44,8 @@ static void subghz_test_init(void) {
|
||||
environment_handler, CAME_ATOMO_DIR_NAME);
|
||||
subghz_environment_set_nice_flor_s_rainbow_table_file_name(
|
||||
environment_handler, NICE_FLOR_S_DIR_NAME);
|
||||
subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
|
||||
environment_handler, ALUTECH_AT_4N_DIR_NAME);
|
||||
subghz_environment_set_protocol_registry(
|
||||
environment_handler, (void*)&subghz_protocol_registry);
|
||||
|
||||
@@ -489,6 +492,14 @@ MU_TEST(subghz_decoder_linear_test) {
|
||||
"Test decoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_linear_delta3_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
EXT_PATH("unit_tests/subghz/linear_delta3_raw.sub"),
|
||||
SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_megacode_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
@@ -590,12 +601,6 @@ MU_TEST(subghz_decoder_ansonic_test) {
|
||||
"Test decoder " SUBGHZ_PROTOCOL_ANSONIC_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_pocsag_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(EXT_PATH("unit_tests/subghz/pocsag.sub"), SUBGHZ_PROTOCOL_POCSAG_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_POCSAG_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_smc5326_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
@@ -610,6 +615,36 @@ MU_TEST(subghz_decoder_holtek_ht12x_test) {
|
||||
"Test decoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_dooya_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
EXT_PATH("unit_tests/subghz/dooya_raw.sub"), SUBGHZ_PROTOCOL_DOOYA_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_alutech_at_4n_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
EXT_PATH("unit_tests/subghz/alutech_at_4n_raw.sub"),
|
||||
SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_ALUTECH_AT_4N_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_nice_one_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
EXT_PATH("unit_tests/subghz/nice_one_raw.sub"), SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_kinggates_stylo4k_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
EXT_PATH("unit_tests/subghz/kinggates_stylo4k_raw.sub"),
|
||||
SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME " error\r\n");
|
||||
}
|
||||
|
||||
//test encoders
|
||||
MU_TEST(subghz_encoder_princeton_test) {
|
||||
mu_assert(
|
||||
@@ -653,6 +688,12 @@ MU_TEST(subghz_encoder_linear_test) {
|
||||
"Test encoder " SUBGHZ_PROTOCOL_LINEAR_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_encoder_linear_delta3_test) {
|
||||
mu_assert(
|
||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/linear_delta3.sub")),
|
||||
"Test encoder " SUBGHZ_PROTOCOL_LINEAR_DELTA3_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_encoder_megacode_test) {
|
||||
mu_assert(
|
||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/megacode.sub")),
|
||||
@@ -749,6 +790,12 @@ MU_TEST(subghz_encoder_holtek_ht12x_test) {
|
||||
"Test encoder " SUBGHZ_PROTOCOL_HOLTEK_HT12X_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_encoder_dooya_test) {
|
||||
mu_assert(
|
||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/dooya.sub")),
|
||||
"Test encoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_random_test) {
|
||||
mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
|
||||
}
|
||||
@@ -778,6 +825,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_decoder_somfy_telis_test);
|
||||
MU_RUN_TEST(subghz_decoder_star_line_test);
|
||||
MU_RUN_TEST(subghz_decoder_linear_test);
|
||||
MU_RUN_TEST(subghz_decoder_linear_delta3_test);
|
||||
MU_RUN_TEST(subghz_decoder_megacode_test);
|
||||
MU_RUN_TEST(subghz_decoder_secplus_v1_test);
|
||||
MU_RUN_TEST(subghz_decoder_secplus_v2_test);
|
||||
@@ -792,9 +840,12 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_decoder_intertechno_v3_test);
|
||||
MU_RUN_TEST(subghz_decoder_clemsa_test);
|
||||
MU_RUN_TEST(subghz_decoder_ansonic_test);
|
||||
MU_RUN_TEST(subghz_decoder_pocsag_test);
|
||||
MU_RUN_TEST(subghz_decoder_smc5326_test);
|
||||
MU_RUN_TEST(subghz_decoder_holtek_ht12x_test);
|
||||
MU_RUN_TEST(subghz_decoder_dooya_test);
|
||||
MU_RUN_TEST(subghz_decoder_alutech_at_4n_test);
|
||||
MU_RUN_TEST(subghz_decoder_nice_one_test);
|
||||
MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test);
|
||||
|
||||
MU_RUN_TEST(subghz_encoder_princeton_test);
|
||||
MU_RUN_TEST(subghz_encoder_came_test);
|
||||
@@ -803,6 +854,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_encoder_nice_flo_test);
|
||||
MU_RUN_TEST(subghz_encoder_keelog_test);
|
||||
MU_RUN_TEST(subghz_encoder_linear_test);
|
||||
MU_RUN_TEST(subghz_encoder_linear_delta3_test);
|
||||
MU_RUN_TEST(subghz_encoder_megacode_test);
|
||||
MU_RUN_TEST(subghz_encoder_holtek_test);
|
||||
MU_RUN_TEST(subghz_encoder_secplus_v1_test);
|
||||
@@ -819,6 +871,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_encoder_ansonic_test);
|
||||
MU_RUN_TEST(subghz_encoder_smc5326_test);
|
||||
MU_RUN_TEST(subghz_encoder_holtek_ht12x_test);
|
||||
MU_RUN_TEST(subghz_encoder_dooya_test);
|
||||
|
||||
MU_RUN_TEST(subghz_random_test);
|
||||
subghz_test_deinit();
|
||||
|
||||
@@ -70,7 +70,7 @@ void minunit_print_progress() {
|
||||
}
|
||||
|
||||
void minunit_print_fail(const char* str) {
|
||||
printf(FURI_LOG_CLR_E "%s\r\n" FURI_LOG_CLR_RESET, str);
|
||||
printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str);
|
||||
}
|
||||
|
||||
void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Apps Data folder Example
|
||||
|
||||
This example demonstrates how to utilize the Apps Data folder to store data that is not part of the app itself, such as user data, configuration files, and so forth.
|
||||
|
||||
## What is the Apps Data Folder?
|
||||
|
||||
The **Apps Data** folder is a folder used to store data for external apps that are not part of the main firmware.
|
||||
|
||||
The path to the current application folder is related to the `appid` of the app. The `appid` is used to identify the app in the app store and is stored in the `application.fam` file.
|
||||
The Apps Data folder is located only on the external storage, the SD card.
|
||||
|
||||
For example, if the `appid` of the app is `snake_game`, the path to the Apps Data folder will be `/ext/apps_data/snake_game`. But using raw paths is not recommended, because the path to the Apps Data folder can change in the future. Use the `/app` alias instead.
|
||||
|
||||
## How to get the path to the Apps Data folder?
|
||||
|
||||
You can use `/app` alias to get the path to the current application data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `/app/config.txt`. But this way is not recommended, because even the `/app` alias can change in the future.
|
||||
|
||||
We recommend to use the `APP_DATA_PATH` macro to get the path to the Apps Data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `APP_DATA_PATH("config.txt")`.
|
||||
@@ -0,0 +1,9 @@
|
||||
App(
|
||||
appid="example_apps_data",
|
||||
name="Example: Apps Data",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="example_apps_data_main",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Examples",
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
// Define log tag
|
||||
#define TAG "example_apps_data"
|
||||
|
||||
// Application entry point
|
||||
int32_t example_apps_data_main(void* p) {
|
||||
// Mark argument as unused
|
||||
UNUSED(p);
|
||||
|
||||
// Open storage
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
// Allocate file
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
// Get the path to the current application data folder
|
||||
// That is: /ext/apps_data/<app_name>
|
||||
// And it will create folders in the path if they don't exist
|
||||
// In this example it will create /ext/apps_data/example_apps_data
|
||||
// And file will be /ext/apps_data/example_apps_data/test.txt
|
||||
|
||||
// Open file, write data and close it
|
||||
if(!storage_file_open(file, APP_DATA_PATH("test.txt"), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
FURI_LOG_E(TAG, "Failed to open file");
|
||||
}
|
||||
if(!storage_file_write(file, "Hello World!", strlen("Hello World!"))) {
|
||||
FURI_LOG_E(TAG, "Failed to write to file");
|
||||
}
|
||||
storage_file_close(file);
|
||||
|
||||
// Deallocate file
|
||||
storage_file_free(file);
|
||||
|
||||
// Close storage
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -3,36 +3,16 @@ App(
|
||||
name="Basic applications for main menu",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"fap_loader",
|
||||
"subghz",
|
||||
"subghz_remote",
|
||||
"lfrfid",
|
||||
"nfc",
|
||||
"infrared",
|
||||
"gpio",
|
||||
"ibutton",
|
||||
"infrared",
|
||||
"lfrfid",
|
||||
"nfc",
|
||||
"subghz",
|
||||
"bad_kb",
|
||||
"u2f",
|
||||
"fap_loader",
|
||||
"sub_playlist",
|
||||
"archive",
|
||||
"clock",
|
||||
"unirfremix",
|
||||
],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="main_apps_default",
|
||||
name="Basic applications for main menu",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"gpio",
|
||||
# "ibutton",
|
||||
"infrared",
|
||||
"lfrfid",
|
||||
"nfc",
|
||||
"subghz",
|
||||
# "bad_kb",
|
||||
# "u2f",
|
||||
"fap_loader",
|
||||
"archive",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <gui/modules/file_browser_worker.h>
|
||||
#include <fap_loader/fap_loader_app.h>
|
||||
#include <math.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
static void
|
||||
archive_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root) {
|
||||
@@ -450,7 +451,7 @@ static bool archive_is_dir_exists(FuriString* path) {
|
||||
FileInfo file_info;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) {
|
||||
if(file_info.flags & FSF_DIRECTORY) {
|
||||
if(file_info_is_dir(&file_info)) {
|
||||
state = true;
|
||||
}
|
||||
}
|
||||
@@ -464,14 +465,17 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
|
||||
|
||||
browser->last_tab_switch_dir = key;
|
||||
|
||||
for(int i = 0; i < 2; i++) {
|
||||
if(key == InputKeyLeft) {
|
||||
tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal;
|
||||
} else {
|
||||
tab = (tab + 1) % ArchiveTabTotal;
|
||||
}
|
||||
if(tab == ArchiveTabInternal && !furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
if(key == InputKeyLeft) {
|
||||
tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal;
|
||||
} else {
|
||||
tab = (tab + 1) % ArchiveTabTotal;
|
||||
}
|
||||
if(tab == ArchiveTabInternal && !furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) continue;
|
||||
break;
|
||||
}
|
||||
|
||||
browser->is_root = true;
|
||||
@@ -528,12 +532,16 @@ void archive_enter_dir(ArchiveBrowserView* browser, FuriString* path) {
|
||||
browser->view, ArchiveBrowserViewModel * model, { idx_temp = model->item_idx; }, false);
|
||||
|
||||
furi_string_set(browser->path, path);
|
||||
|
||||
file_browser_worker_folder_enter(browser->worker, path, idx_temp);
|
||||
}
|
||||
|
||||
void archive_leave_dir(ArchiveBrowserView* browser) {
|
||||
furi_assert(browser);
|
||||
|
||||
size_t dirname_start = furi_string_search_rchar(browser->path, '/');
|
||||
furi_string_left(browser->path, dirname_start);
|
||||
|
||||
file_browser_worker_folder_exit(browser->worker);
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ bool archive_favorites_read(void* context) {
|
||||
if(storage_file_exists(storage, furi_string_get_cstr(buffer))) {
|
||||
storage_common_stat(storage, furi_string_get_cstr(buffer), &file_info);
|
||||
archive_add_file_item(
|
||||
browser, (file_info.flags & FSF_DIRECTORY), furi_string_get_cstr(buffer));
|
||||
browser, file_info_is_dir(&file_info), furi_string_get_cstr(buffer));
|
||||
file_count++;
|
||||
} else {
|
||||
need_refresh = true;
|
||||
|
||||
@@ -96,7 +96,7 @@ void archive_delete_file(void* context, const char* format, ...) {
|
||||
|
||||
bool res = false;
|
||||
|
||||
if(fileinfo.flags & FSF_DIRECTORY) {
|
||||
if(file_info_is_dir(&fileinfo)) {
|
||||
res = storage_simply_remove_recursive(fs_api, furi_string_get_cstr(filename));
|
||||
} else {
|
||||
res = (storage_common_remove(fs_api, furi_string_get_cstr(filename)) == FSE_OK);
|
||||
|
||||
@@ -84,7 +84,7 @@ static void ArchiveFile_t_clear(ArchiveFile_t* obj) {
|
||||
}
|
||||
|
||||
static int ArchiveFile_t_cmp(const ArchiveFile_t* a, const ArchiveFile_t* b) {
|
||||
if(!XTREME_SETTINGS()->sort_ignore_dirs) {
|
||||
if(XTREME_SETTINGS()->sort_dirs_first) {
|
||||
if(a->type == ArchiveFileTypeFolder && b->type != ArchiveFileTypeFolder) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -37,9 +37,26 @@ static void bad_kb_load_settings(BadKbApp* app) {
|
||||
!storage_file_eof(settings_file) && !isspace(chr)) {
|
||||
furi_string_push_back(app->keyboard_layout, chr);
|
||||
}
|
||||
} else {
|
||||
furi_string_reset(app->keyboard_layout);
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
|
||||
if(!furi_string_empty(app->keyboard_layout)) {
|
||||
Storage* fs_api = furi_record_open(RECORD_STORAGE);
|
||||
FileInfo layout_file_info;
|
||||
FS_Error file_check_err = storage_common_stat(
|
||||
fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
if(file_check_err != FSE_OK) {
|
||||
furi_string_reset(app->keyboard_layout);
|
||||
return;
|
||||
}
|
||||
if(layout_file_info.size != 256) {
|
||||
furi_string_reset(app->keyboard_layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bad_kb_save_settings(BadKbApp* app) {
|
||||
@@ -55,17 +72,6 @@ static void bad_kb_save_settings(BadKbApp* app) {
|
||||
storage_file_free(settings_file);
|
||||
}
|
||||
|
||||
void bad_kb_set_name(BadKbApp* app, const char* fmt, ...) {
|
||||
furi_assert(app);
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
vsnprintf(app->name, BAD_KB_ADV_NAME_MAX_LEN, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
BadKbApp* bad_kb_app_alloc(char* arg) {
|
||||
BadKbApp* app = malloc(sizeof(BadKbApp));
|
||||
|
||||
@@ -77,6 +83,18 @@ BadKbApp* bad_kb_app_alloc(char* arg) {
|
||||
furi_string_set(app->file_path, arg);
|
||||
}
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
// Remove old pre-included files to avoid duplicates on migrate
|
||||
storage_simply_remove(storage, EXT_PATH("badusb/layouts"));
|
||||
storage_simply_remove(storage, EXT_PATH("badusb/.badusb.settings"));
|
||||
storage_simply_remove(storage, EXT_PATH("badusb/Kiosk-Evasion-Bruteforce.txt"));
|
||||
storage_simply_remove(storage, EXT_PATH("badusb/Wifi-Stealer_ORG.txt"));
|
||||
storage_simply_remove(storage, EXT_PATH("badusb/demo_macos.txt"));
|
||||
storage_simply_remove(storage, EXT_PATH("badusb/demo_windows.txt"));
|
||||
storage_common_migrate(storage, EXT_PATH("badusb"), BAD_KB_APP_BASE_FOLDER);
|
||||
storage_simply_mkdir(storage, BAD_KB_APP_BASE_FOLDER);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
bad_kb_load_settings(app);
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
@@ -96,14 +114,19 @@ BadKbApp* bad_kb_app_alloc(char* arg) {
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, bad_kb_app_back_event_callback);
|
||||
|
||||
app->connection_init = false;
|
||||
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
app->bt = bt;
|
||||
app->bt->suppress_pin_screen = true;
|
||||
app->is_bt = XTREME_SETTINGS()->bad_bt;
|
||||
const char* adv_name = bt_get_profile_adv_name(bt);
|
||||
app->bt_remember = XTREME_SETTINGS()->bad_bt_remember;
|
||||
const char* adv_name = furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard);
|
||||
memcpy(app->name, adv_name, BAD_KB_ADV_NAME_MAX_LEN);
|
||||
memcpy(app->bt_old_config.name, adv_name, BAD_KB_ADV_NAME_MAX_LEN);
|
||||
|
||||
const uint8_t* mac_addr = bt_get_profile_mac_address(bt);
|
||||
// need to be done before bt init (where mac address get modified if bounding is activated)
|
||||
const uint8_t* mac_addr = furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard);
|
||||
memcpy(app->mac, mac_addr, BAD_KB_MAC_ADDRESS_LEN);
|
||||
memcpy(app->bt_old_config.mac, mac_addr, BAD_KB_MAC_ADDRESS_LEN);
|
||||
|
||||
@@ -139,8 +162,12 @@ BadKbApp* bad_kb_app_alloc(char* arg) {
|
||||
|
||||
if(furi_hal_usb_is_locked()) {
|
||||
app->error = BadKbAppErrorCloseRpc;
|
||||
app->conn_init_thread = NULL;
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneError);
|
||||
} else {
|
||||
app->conn_init_thread = furi_thread_alloc_ex(
|
||||
"BadKbConnInit", 512, (FuriThreadCallback)bad_kb_connection_init, app);
|
||||
furi_thread_start(app->conn_init_thread);
|
||||
if(!furi_string_empty(app->file_path)) {
|
||||
app->bad_kb_script = bad_kb_script_open(app->file_path, app->is_bt ? app->bt : NULL);
|
||||
bad_kb_script_set_keyboard_layout(app->bad_kb_script, app->keyboard_layout);
|
||||
@@ -188,16 +215,16 @@ void bad_kb_app_free(BadKbApp* app) {
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
scene_manager_free(app->scene_manager);
|
||||
|
||||
// restores bt config
|
||||
// BtProfile have already been switched to the previous one
|
||||
// so we directly modify the right profile
|
||||
bad_kb_connection_deinit(app->bt);
|
||||
// Restore bt config
|
||||
// BtProfile has already been switched to the previous one
|
||||
// So we directly modify the right profile
|
||||
if(strcmp(app->bt_old_config.name, app->name) != 0) {
|
||||
furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->bt_old_config.name);
|
||||
}
|
||||
if(memcmp(app->bt_old_config.mac, app->mac, BAD_KB_MAC_ADDRESS_LEN) != 0) {
|
||||
furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->bt_old_config.mac);
|
||||
}
|
||||
app->bt->suppress_pin_screen = false;
|
||||
|
||||
// Close records
|
||||
furi_record_close(RECORD_GUI);
|
||||
@@ -210,6 +237,12 @@ void bad_kb_app_free(BadKbApp* app) {
|
||||
furi_string_free(app->file_path);
|
||||
furi_string_free(app->keyboard_layout);
|
||||
|
||||
if(app->conn_init_thread) {
|
||||
furi_thread_join(app->conn_init_thread);
|
||||
furi_thread_free(app->conn_init_thread);
|
||||
}
|
||||
bad_kb_connection_deinit(app);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ extern "C" {
|
||||
|
||||
typedef struct BadKbApp BadKbApp;
|
||||
|
||||
void bad_kb_set_name(BadKbApp* app, const char* fmt, ...);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -16,14 +16,19 @@
|
||||
#include <gui/modules/byte_input.h>
|
||||
#include "views/bad_kb_view.h"
|
||||
|
||||
#define BAD_KB_APP_BASE_FOLDER ANY_PATH("badkb")
|
||||
#define BAD_KB_APP_PATH_LAYOUT_FOLDER BAD_KB_APP_BASE_FOLDER "/layouts"
|
||||
#define BAD_KB_APP_BASE_FOLDER EXT_PATH("badkb")
|
||||
#define BAD_KB_APP_PATH_LAYOUT_FOLDER BAD_KB_APP_BASE_FOLDER "/assets/layouts"
|
||||
#define BAD_KB_APP_PATH_BOUND_KEYS_FOLDER EXT_PATH("badkb/.bt_keys")
|
||||
#define BAD_KB_APP_PATH_BOUND_KEYS_FILE BAD_KB_APP_PATH_BOUND_KEYS_FOLDER "/.devices.keys"
|
||||
#define BAD_KB_APP_SCRIPT_EXTENSION ".txt"
|
||||
#define BAD_KB_APP_LAYOUT_EXTENSION ".kl"
|
||||
|
||||
#define BAD_KB_MAC_ADDRESS_LEN 6 // need replace with MAC size maccro
|
||||
#define BAD_KB_ADV_NAME_MAX_LEN 18
|
||||
|
||||
// this is the MAC address used when we do not forget paired device (BOUND STATE)
|
||||
#define BAD_KB_BOUND_MAC_ADDRESS {0x41, 0x4a, 0xef, 0xb6, 0xa9, 0xd4};
|
||||
|
||||
typedef enum {
|
||||
BadKbAppErrorNoFiles,
|
||||
BadKbAppErrorCloseRpc,
|
||||
@@ -36,12 +41,9 @@ typedef enum BadKbCustomEvent {
|
||||
} BadKbCustomEvent;
|
||||
|
||||
typedef struct {
|
||||
//uint8_t bounded_mac[BAD_KB_MAC_ADDRESS_LEN];
|
||||
uint8_t mac[BAD_KB_MAC_ADDRESS_LEN];
|
||||
char name[BAD_KB_ADV_NAME_MAX_LEN + 1];
|
||||
|
||||
// number of bt keys before starting the app (all keys added in
|
||||
// the bt keys file then will be removed)
|
||||
uint16_t n_keys;
|
||||
} BadKbBtConfig;
|
||||
|
||||
struct BadKbApp {
|
||||
@@ -59,6 +61,7 @@ struct BadKbApp {
|
||||
ByteInput* byte_input;
|
||||
uint8_t mac[BAD_KB_MAC_ADDRESS_LEN];
|
||||
char name[BAD_KB_ADV_NAME_MAX_LEN + 1];
|
||||
bool bt_remember; // weither we remember paired devices or not
|
||||
BadKbBtConfig bt_old_config;
|
||||
|
||||
BadKbAppError error;
|
||||
@@ -68,6 +71,12 @@ struct BadKbApp {
|
||||
BadKbScript* bad_kb_script;
|
||||
|
||||
bool is_bt;
|
||||
|
||||
bool connection_init;
|
||||
FuriHalUsbInterface* usb_prev_mode;
|
||||
GapPairing bt_prev_mode;
|
||||
|
||||
FuriThread* conn_init_thread;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#include <bt/bt_service/bt.h>
|
||||
|
||||
#include "bad_kb_app_i.h"
|
||||
|
||||
#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys")
|
||||
|
||||
#define TAG "BadKB"
|
||||
@@ -41,12 +43,6 @@ typedef enum {
|
||||
LevelRssiError = 0xFF,
|
||||
} LevelRssiRange;
|
||||
|
||||
typedef enum {
|
||||
BadKbConnectionModeNone,
|
||||
BadKbConnectionModeUsb,
|
||||
BadKbConnectionModeBt,
|
||||
} BadKbConnectionMode;
|
||||
|
||||
/**
|
||||
* Delays for waiting between HID key press and key release
|
||||
*/
|
||||
@@ -65,6 +61,7 @@ struct BadKbScript {
|
||||
FuriString* keyboard_layout;
|
||||
uint32_t defdelay;
|
||||
uint16_t layout[128];
|
||||
uint32_t stringdelay;
|
||||
FuriThread* thread;
|
||||
uint8_t file_buf[FILE_BUFFER_LEN + 1];
|
||||
uint8_t buf_start;
|
||||
@@ -148,6 +145,8 @@ 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_stringdelay_1[] = {"STRINGDELAY "};
|
||||
static const char ducky_cmd_stringdelay_2[] = {"STRING_DELAY "};
|
||||
static const char ducky_cmd_repeat[] = {"REPEAT "};
|
||||
static const char ducky_cmd_sysrq[] = {"SYSRQ "};
|
||||
|
||||
@@ -170,27 +169,22 @@ static const uint8_t numpad_keys[10] = {
|
||||
HID_KEYPAD_9,
|
||||
};
|
||||
|
||||
BadKbConnectionMode connection_mode = BadKbConnectionModeNone;
|
||||
FuriHalUsbInterface* usb_mode_prev = NULL;
|
||||
GapPairing bt_mode_prev = GapPairingNone;
|
||||
bool bt_connected = false;
|
||||
bool usb_connected = false;
|
||||
uint8_t bt_timeout = 0;
|
||||
|
||||
static LevelRssiRange bt_remote_rssi_range(Bt* bt) {
|
||||
BtRssi rssi_data = {0};
|
||||
uint8_t rssi;
|
||||
|
||||
if(!bt_remote_rssi(bt, &rssi_data)) return LevelRssiError;
|
||||
if(!bt_remote_rssi(bt, &rssi)) return LevelRssiError;
|
||||
|
||||
if(rssi_data.rssi <= 39)
|
||||
if(rssi <= 39)
|
||||
return LevelRssi39_0;
|
||||
else if(rssi_data.rssi <= 59)
|
||||
else if(rssi <= 59)
|
||||
return LevelRssi59_40;
|
||||
else if(rssi_data.rssi <= 79)
|
||||
else if(rssi <= 79)
|
||||
return LevelRssi79_60;
|
||||
else if(rssi_data.rssi <= 99)
|
||||
else if(rssi <= 99)
|
||||
return LevelRssi99_80;
|
||||
else if(rssi_data.rssi <= 122)
|
||||
else if(rssi <= 122)
|
||||
return LevelRssi122_100;
|
||||
|
||||
return LevelRssiError;
|
||||
@@ -304,6 +298,7 @@ static bool ducky_altstring(BadKbScript* bad_kb, const char* param) {
|
||||
|
||||
static bool ducky_string(BadKbScript* bad_kb, const char* param) {
|
||||
uint32_t i = 0;
|
||||
|
||||
while(param[i] != '\0') {
|
||||
uint16_t keycode = BADKB_ASCII_TO_KEY(bad_kb, param[i]);
|
||||
if(keycode != HID_KEYBOARD_NONE) {
|
||||
@@ -315,9 +310,13 @@ static bool ducky_string(BadKbScript* bad_kb, const char* param) {
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
}
|
||||
if(bad_kb->stringdelay > 0) {
|
||||
furi_delay_ms(bad_kb->stringdelay);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
bad_kb->stringdelay = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -379,6 +378,19 @@ static int32_t
|
||||
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(
|
||||
(strncmp(line_tmp, ducky_cmd_stringdelay_1, strlen(ducky_cmd_stringdelay_1)) == 0) ||
|
||||
(strncmp(line_tmp, ducky_cmd_stringdelay_2, strlen(ducky_cmd_stringdelay_2)) == 0)) {
|
||||
//STRINGDELAY, finally it's here
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
state = ducky_get_number(line_tmp, &bad_kb->stringdelay);
|
||||
if((state) && (bad_kb->stringdelay > 0)) {
|
||||
return state;
|
||||
}
|
||||
if(error != NULL) {
|
||||
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||
}
|
||||
return 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];
|
||||
@@ -607,10 +619,8 @@ static void bad_kb_bt_hid_state_callback(BtStatus status, void* context) {
|
||||
if(r != LevelRssiError) {
|
||||
bt_timeout = bt_hid_delays[r];
|
||||
}
|
||||
bt_connected = true;
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtConnect);
|
||||
} else {
|
||||
bt_connected = false;
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtDisconnect);
|
||||
}
|
||||
}
|
||||
@@ -620,83 +630,116 @@ static void bad_kb_usb_hid_state_callback(bool state, void* context) {
|
||||
BadKbScript* bad_kb = context;
|
||||
|
||||
if(state == true) {
|
||||
usb_connected = true;
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtConnect);
|
||||
} else {
|
||||
usb_connected = false;
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_kb->thread), WorkerEvtDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
void bad_kb_bt_init(Bt* bt) {
|
||||
bt_timeout = bt_hid_delays[LevelRssi39_0];
|
||||
bt_disconnect(bt);
|
||||
furi_delay_ms(200);
|
||||
bt_keys_storage_set_storage_path(bt, HID_BT_KEYS_STORAGE_PATH);
|
||||
furi_assert(bt_set_profile(bt, BtProfileHidKeyboard));
|
||||
bt_mode_prev = bt_get_profile_pairing_method(bt);
|
||||
bt_set_profile_pairing_method(bt, GapPairingNone);
|
||||
furi_hal_bt_start_advertising();
|
||||
// disable peer key adding to bt SRAM storage
|
||||
bt_disable_peer_key_update(bt);
|
||||
|
||||
connection_mode = BadKbConnectionModeBt;
|
||||
void bad_kb_reload_worker(BadKbApp* app) {
|
||||
bad_kb_script_close(app->bad_kb_script);
|
||||
app->bad_kb_script = bad_kb_script_open(app->file_path, app->is_bt ? app->bt : NULL);
|
||||
bad_kb_script_set_keyboard_layout(app->bad_kb_script, app->keyboard_layout);
|
||||
}
|
||||
|
||||
void bad_kb_bt_deinit(Bt* bt) {
|
||||
// release all keys
|
||||
// bt_hid_hold_while_keyboard_buffer_full(6, 3000);
|
||||
void bad_kb_config_switch_mode(BadKbApp* app) {
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
if(app->is_bt) {
|
||||
furi_hal_bt_start_advertising();
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneConfigBt);
|
||||
} else {
|
||||
furi_hal_bt_stop_advertising();
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneConfigUsb);
|
||||
}
|
||||
bad_kb_reload_worker(app);
|
||||
}
|
||||
|
||||
// stop ble
|
||||
bt_disconnect(bt);
|
||||
void bad_kb_config_switch_remember_mode(BadKbApp* app) {
|
||||
if(app->bt_remember) {
|
||||
// set bouding mac
|
||||
uint8_t mac[6] = BAD_KB_BOUND_MAC_ADDRESS;
|
||||
furi_hal_bt_set_profile_pairing_method(
|
||||
FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo);
|
||||
bt_set_profile_mac_address(app->bt, mac); // this also restart bt
|
||||
// enable keys storage
|
||||
bt_enable_peer_key_update(app->bt);
|
||||
} else {
|
||||
// set back user defined mac address
|
||||
furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, GapPairingNone);
|
||||
bt_set_profile_mac_address(app->bt, app->mac);
|
||||
// disable key storage
|
||||
bt_disable_peer_key_update(app->bt);
|
||||
}
|
||||
bad_kb_reload_worker(app);
|
||||
}
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
int32_t bad_kb_connection_init(BadKbApp* app) {
|
||||
app->usb_prev_mode = furi_hal_usb_get_config();
|
||||
furi_hal_usb_set_config(NULL, NULL);
|
||||
|
||||
bt_keys_storage_set_default_path(bt);
|
||||
bt_timeout = bt_hid_delays[LevelRssi39_0];
|
||||
bt_disconnect(app->bt);
|
||||
// furi_delay_ms(200);
|
||||
bt_keys_storage_set_storage_path(app->bt, BAD_KB_APP_PATH_BOUND_KEYS_FILE);
|
||||
app->bt_prev_mode = furi_hal_bt_get_profile_pairing_method(FuriHalBtProfileHidKeyboard);
|
||||
if(app->bt_remember) {
|
||||
uint8_t mac[6] = BAD_KB_BOUND_MAC_ADDRESS;
|
||||
furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, mac);
|
||||
// using GapPairingNone breaks bounding between devices
|
||||
furi_hal_bt_set_profile_pairing_method(
|
||||
FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo);
|
||||
} else {
|
||||
furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, GapPairingNone);
|
||||
}
|
||||
|
||||
bt_set_profile_pairing_method(bt, bt_mode_prev);
|
||||
bt_set_profile(app->bt, BtProfileHidKeyboard);
|
||||
if(app->is_bt) {
|
||||
furi_hal_bt_start_advertising();
|
||||
if(app->bt_remember) {
|
||||
bt_enable_peer_key_update(app->bt);
|
||||
} else {
|
||||
bt_disable_peer_key_update(app->bt); // disable peer key adding to bt SRAM storage
|
||||
}
|
||||
} else {
|
||||
furi_hal_bt_stop_advertising();
|
||||
}
|
||||
|
||||
app->connection_init = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bad_kb_connection_deinit(BadKbApp* app) {
|
||||
if(!app->connection_init) return;
|
||||
|
||||
furi_hal_usb_set_config(app->usb_prev_mode, NULL);
|
||||
|
||||
// bt_hid_hold_while_keyboard_buffer_full(6, 3000); // release all keys
|
||||
bt_disconnect(app->bt); // stop ble
|
||||
// furi_delay_ms(200); // Wait 2nd core to update nvm storage
|
||||
bt_keys_storage_set_default_path(app->bt);
|
||||
if(app->bt_remember) {
|
||||
// hal primitives doesn't restarts ble, that's what we want cuz we are shutting down
|
||||
furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->mac);
|
||||
}
|
||||
bt_enable_peer_key_update(app->bt); // starts saving peer keys (bounded devices)
|
||||
// fails if ble radio stack isn't ready when switching profile
|
||||
// if it happens, maybe we should increase the delay after bt_disconnect
|
||||
bt_set_profile(bt, BtProfileSerial);
|
||||
|
||||
// starts saving peer keys (bounded devices)
|
||||
bt_enable_peer_key_update(bt);
|
||||
|
||||
connection_mode = BadKbConnectionModeNone;
|
||||
bt_set_profile(app->bt, BtProfileSerial);
|
||||
furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, app->bt_prev_mode);
|
||||
}
|
||||
|
||||
void bad_kb_usb_init() {
|
||||
usb_mode_prev = furi_hal_usb_get_config();
|
||||
|
||||
connection_mode = BadKbConnectionModeUsb;
|
||||
}
|
||||
|
||||
void bad_kb_usb_deinit() {
|
||||
furi_hal_usb_set_config(usb_mode_prev, NULL);
|
||||
|
||||
connection_mode = BadKbConnectionModeNone;
|
||||
}
|
||||
|
||||
void bad_kb_connection_init(Bt* bt) {
|
||||
if(connection_mode != BadKbConnectionModeNone) return;
|
||||
|
||||
if(bt) {
|
||||
bad_kb_bt_init(bt);
|
||||
static uint32_t bad_kb_flags_get(uint32_t flags_mask, uint32_t timeout) {
|
||||
uint32_t flags = furi_thread_flags_get();
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags == 0) {
|
||||
flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout);
|
||||
furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout));
|
||||
} else {
|
||||
bad_kb_usb_init();
|
||||
}
|
||||
}
|
||||
|
||||
void bad_kb_connection_deinit(Bt* bt) {
|
||||
if(connection_mode == BadKbConnectionModeNone) return;
|
||||
|
||||
if(connection_mode == BadKbConnectionModeBt) {
|
||||
bad_kb_bt_deinit(bt);
|
||||
} else {
|
||||
bad_kb_usb_deinit();
|
||||
uint32_t state = furi_thread_flags_clear(flags);
|
||||
furi_check((state & FuriFlagError) == 0);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
static int32_t bad_kb_worker(void* context) {
|
||||
@@ -705,8 +748,6 @@ static int32_t bad_kb_worker(void* context) {
|
||||
BadKbWorkerState worker_state = BadKbStateInit;
|
||||
int32_t delay_val = 0;
|
||||
|
||||
bad_kb_connection_init(bad_kb->bt);
|
||||
|
||||
if(bad_kb->bt) {
|
||||
bt_set_status_changed_callback(bad_kb->bt, bad_kb_bt_hid_state_callback, bad_kb);
|
||||
} else {
|
||||
@@ -727,7 +768,11 @@ static int32_t bad_kb_worker(void* context) {
|
||||
FSOM_OPEN_EXISTING)) {
|
||||
if((ducky_script_preload(bad_kb, script_file)) && (bad_kb->st.line_nb > 0)) {
|
||||
if(bad_kb->bt) {
|
||||
worker_state = BadKbStateNotConnected; // Ready to run
|
||||
if(furi_hal_bt_is_connected()) {
|
||||
worker_state = BadKbStateIdle; // Ready to run
|
||||
} else {
|
||||
worker_state = BadKbStateNotConnected; // Not connected
|
||||
}
|
||||
} else {
|
||||
if(furi_hal_hid_is_connected()) {
|
||||
worker_state = BadKbStateIdle; // Ready to run
|
||||
@@ -745,30 +790,20 @@ static int32_t bad_kb_worker(void* context) {
|
||||
bad_kb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadKbStateNotConnected) { // State: Not connected
|
||||
if((bad_kb->bt && bt_connected) || (!bad_kb->bt && usb_connected)) {
|
||||
uint32_t flags = bad_kb_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtConnect) {
|
||||
worker_state = BadKbStateIdle; // Ready to run
|
||||
} else {
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtConnect) {
|
||||
worker_state = BadKbStateIdle; // Ready to run
|
||||
} else if(flags & WorkerEvtToggle) {
|
||||
worker_state = BadKbStateWillRun; // Will run when connected
|
||||
}
|
||||
} else if(flags & WorkerEvtToggle) {
|
||||
worker_state = BadKbStateWillRun; // Will run when connected
|
||||
}
|
||||
bad_kb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadKbStateIdle) { // State: ready to start
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
uint32_t flags = bad_kb_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtToggle | WorkerEvtDisconnect, FuriWaitForever);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtToggle) { // Start executing script
|
||||
@@ -777,6 +812,7 @@ static int32_t bad_kb_worker(void* context) {
|
||||
bad_kb->buf_len = 0;
|
||||
bad_kb->st.line_cur = 0;
|
||||
bad_kb->defdelay = 0;
|
||||
bad_kb->stringdelay = 0;
|
||||
bad_kb->repeat_cnt = 0;
|
||||
bad_kb->file_end = false;
|
||||
storage_file_seek(script_file, 0, true);
|
||||
@@ -788,11 +824,8 @@ static int32_t bad_kb_worker(void* context) {
|
||||
bad_kb->st.state = worker_state;
|
||||
|
||||
} else if(worker_state == BadKbStateWillRun) { // State: start on connection
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle,
|
||||
FuriFlagWaitAny,
|
||||
FuriWaitForever);
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
uint32_t flags = bad_kb_flags_get(
|
||||
WorkerEvtEnd | WorkerEvtConnect | WorkerEvtToggle, FuriWaitForever);
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
} else if(flags & WorkerEvtConnect) { // Start executing script
|
||||
@@ -801,16 +834,26 @@ static int32_t bad_kb_worker(void* context) {
|
||||
bad_kb->buf_len = 0;
|
||||
bad_kb->st.line_cur = 0;
|
||||
bad_kb->defdelay = 0;
|
||||
bad_kb->stringdelay = 0;
|
||||
bad_kb->repeat_cnt = 0;
|
||||
bad_kb->file_end = false;
|
||||
storage_file_seek(script_file, 0, true);
|
||||
// extra time for PC to recognize Flipper as keyboard
|
||||
furi_thread_flags_wait(0, FuriFlagWaitAny, 1500);
|
||||
flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtToggle,
|
||||
FuriFlagWaitAny | FuriFlagNoClear,
|
||||
1500);
|
||||
if(flags == (unsigned)FuriFlagErrorTimeout) {
|
||||
// If nothing happened - start script execution
|
||||
worker_state = BadKbStateRunning;
|
||||
} else if(flags & WorkerEvtToggle) {
|
||||
worker_state = BadKbStateIdle;
|
||||
furi_thread_flags_clear(WorkerEvtToggle);
|
||||
}
|
||||
if(bad_kb->bt) {
|
||||
update_bt_timeout(bad_kb->bt);
|
||||
}
|
||||
bad_kb_script_set_keyboard_layout(bad_kb, bad_kb->keyboard_layout);
|
||||
worker_state = BadKbStateRunning;
|
||||
} else if(flags & WorkerEvtToggle) { // Cancel scheduled execution
|
||||
worker_state = BadKbStateNotConnected;
|
||||
}
|
||||
@@ -875,9 +918,8 @@ static int32_t bad_kb_worker(void* context) {
|
||||
} else if(
|
||||
(worker_state == BadKbStateFileError) ||
|
||||
(worker_state == BadKbStateScriptError)) { // State: error
|
||||
uint32_t flags = furi_thread_flags_wait(
|
||||
WorkerEvtEnd, FuriFlagWaitAny, FuriWaitForever); // Waiting for exit command
|
||||
furi_check((flags & FuriFlagError) == 0);
|
||||
uint32_t flags =
|
||||
bad_kb_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command
|
||||
if(flags & WorkerEvtEnd) {
|
||||
break;
|
||||
}
|
||||
@@ -921,6 +963,7 @@ BadKbScript* bad_kb_script_open(FuriString* file_path, Bt* bt) {
|
||||
|
||||
bad_kb->st.state = BadKbStateInit;
|
||||
bad_kb->st.error[0] = '\0';
|
||||
bad_kb->st.is_bt = !!bt;
|
||||
|
||||
bad_kb->bt = bt;
|
||||
|
||||
@@ -949,7 +992,7 @@ void bad_kb_script_set_keyboard_layout(BadKbScript* bad_kb, FuriString* layout_p
|
||||
}
|
||||
|
||||
File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(!furi_string_empty(layout_path)) {
|
||||
if(!furi_string_empty(layout_path)) { //-V1051
|
||||
furi_string_set(bad_kb->keyboard_layout, layout_path);
|
||||
if(storage_file_open(
|
||||
layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
|
||||
@@ -7,6 +7,8 @@ extern "C" {
|
||||
#include <furi.h>
|
||||
#include <bt/bt_service/bt_i.h>
|
||||
|
||||
typedef struct BadKbApp BadKbApp;
|
||||
|
||||
typedef struct BadKbScript BadKbScript;
|
||||
|
||||
typedef enum {
|
||||
@@ -23,6 +25,8 @@ typedef enum {
|
||||
|
||||
typedef struct {
|
||||
BadKbWorkerState state;
|
||||
bool is_bt;
|
||||
uint32_t pin;
|
||||
uint16_t line_cur;
|
||||
uint16_t line_nb;
|
||||
uint32_t delay_remain;
|
||||
@@ -30,9 +34,13 @@ typedef struct {
|
||||
char error[64];
|
||||
} BadKbState;
|
||||
|
||||
void bad_kb_connection_init(Bt* bt);
|
||||
void bad_kb_config_switch_mode(BadKbApp* app);
|
||||
|
||||
void bad_kb_connection_deinit(Bt* bt);
|
||||
void bad_kb_config_switch_remember_mode(BadKbApp* app);
|
||||
|
||||
int32_t bad_kb_connection_init(BadKbApp* app);
|
||||
|
||||
void bad_kb_connection_deinit(BadKbApp* app);
|
||||
|
||||
BadKbScript* bad_kb_script_open(FuriString* file_path, Bt* bt);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
enum VarItemListIndex {
|
||||
VarItemListIndexConnection,
|
||||
VarItemListIndexRemember,
|
||||
VarItemListIndexKeyboardLayout,
|
||||
VarItemListIndexAdvertisementName,
|
||||
VarItemListIndexMacAddress,
|
||||
@@ -19,6 +20,15 @@ void bad_kb_scene_config_bt_connection_callback(VariableItem* item) {
|
||||
view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexConnection);
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_bt_bounding_callback(VariableItem* item) {
|
||||
BadKbApp* bad_kb = variable_item_get_context(item);
|
||||
bad_kb->bt_remember = variable_item_get_current_value_index(item);
|
||||
XTREME_SETTINGS()->bad_bt_remember = bad_kb->bt_remember;
|
||||
XTREME_SETTINGS_SAVE();
|
||||
variable_item_set_current_value_text(item, bad_kb->bt_remember ? "ON" : "OFF");
|
||||
view_dispatcher_send_custom_event(bad_kb->view_dispatcher, VarItemListIndexRemember);
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_bt_var_item_list_callback(void* context, uint32_t index) {
|
||||
BadKbApp* bad_kb = context;
|
||||
view_dispatcher_send_custom_event(bad_kb->view_dispatcher, index);
|
||||
@@ -34,11 +44,19 @@ void bad_kb_scene_config_bt_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, bad_kb->is_bt);
|
||||
variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB");
|
||||
|
||||
item = variable_item_list_add(
|
||||
var_item_list, "Remember", 2, bad_kb_scene_config_bt_bounding_callback, bad_kb);
|
||||
variable_item_set_current_value_index(item, bad_kb->bt_remember);
|
||||
variable_item_set_current_value_text(item, bad_kb->bt_remember ? "ON" : "OFF");
|
||||
|
||||
item = variable_item_list_add(var_item_list, "Keyboard layout", 0, NULL, bad_kb);
|
||||
|
||||
item = variable_item_list_add(var_item_list, "BT device name", 0, NULL, bad_kb);
|
||||
|
||||
item = variable_item_list_add(var_item_list, "BT MAC address", 0, NULL, bad_kb);
|
||||
// this doesn't update instantly when toggling between Bounding modes
|
||||
if(!bad_kb->bt_remember) {
|
||||
item = variable_item_list_add(var_item_list, "BT MAC address", 0, NULL, bad_kb);
|
||||
}
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
var_item_list, bad_kb_scene_config_bt_var_item_list_callback, bad_kb);
|
||||
@@ -56,23 +74,15 @@ bool bad_kb_scene_config_bt_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.event == VarItemListIndexKeyboardLayout) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigLayout);
|
||||
} else if(event.event == VarItemListIndexConnection) {
|
||||
bad_kb_script_close(bad_kb->bad_kb_script);
|
||||
bad_kb_connection_deinit(bad_kb->bt);
|
||||
bad_kb->bad_kb_script =
|
||||
bad_kb_script_open(bad_kb->file_path, bad_kb->is_bt ? bad_kb->bt : NULL);
|
||||
bad_kb_script_set_keyboard_layout(bad_kb->bad_kb_script, bad_kb->keyboard_layout);
|
||||
bad_kb_config_switch_mode(bad_kb);
|
||||
} else if(event.event == VarItemListIndexRemember) {
|
||||
bad_kb_config_switch_remember_mode(bad_kb);
|
||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||
if(bad_kb->is_bt) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBt);
|
||||
} else {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigUsb);
|
||||
}
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBt);
|
||||
} else if(event.event == VarItemListIndexAdvertisementName) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigName);
|
||||
} else if(event.event == VarItemListIndexMacAddress) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigMac);
|
||||
// } else {
|
||||
// furi_crash("Unknown key type");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include "furi_hal_usb.h"
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define KEYBOARD_FOLDER "/ext/badkb/layouts"
|
||||
|
||||
static bool bad_kb_layout_select(BadKbApp* bad_kb) {
|
||||
furi_assert(bad_kb);
|
||||
|
||||
@@ -19,7 +17,8 @@ static bool bad_kb_layout_select(BadKbApp* bad_kb) {
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, BAD_KB_APP_LAYOUT_EXTENSION, &I_keyboard_10px);
|
||||
browser_options.base_path = KEYBOARD_FOLDER;
|
||||
browser_options.base_path = BAD_KB_APP_PATH_LAYOUT_FOLDER;
|
||||
browser_options.skip_assets = false;
|
||||
|
||||
// Input events and views are managed by file_browser
|
||||
bool res = dialog_file_browser_show(
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
#define TAG "BadKbConfigMac"
|
||||
|
||||
static uint8_t* reverse_mac_addr(uint8_t* mac) {
|
||||
static uint8_t* reverse_mac_addr(uint8_t* mac, uint8_t* out) {
|
||||
uint8_t tmp;
|
||||
for(int i = 0; i < 3; i++) {
|
||||
tmp = mac[i];
|
||||
mac[i] = mac[5 - i];
|
||||
mac[5 - i] = tmp;
|
||||
out[i] = mac[5 - i];
|
||||
out[5 - i] = tmp;
|
||||
}
|
||||
return mac;
|
||||
return out;
|
||||
}
|
||||
|
||||
void bad_kb_scene_config_mac_byte_input_callback(void* context) {
|
||||
@@ -23,13 +23,13 @@ void bad_kb_scene_config_mac_on_enter(void* context) {
|
||||
|
||||
// Setup view
|
||||
ByteInput* byte_input = bad_kb->byte_input;
|
||||
byte_input_set_header_text(byte_input, "Enter new MAC address");
|
||||
byte_input_set_header_text(byte_input, "Set BT MAC address");
|
||||
byte_input_set_result_callback(
|
||||
byte_input,
|
||||
bad_kb_scene_config_mac_byte_input_callback,
|
||||
NULL,
|
||||
bad_kb,
|
||||
reverse_mac_addr(bad_kb->mac),
|
||||
reverse_mac_addr(bad_kb->mac, bad_kb->mac),
|
||||
GAP_MAC_ADDR_SIZE);
|
||||
view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewConfigMac);
|
||||
}
|
||||
@@ -40,7 +40,8 @@ bool bad_kb_scene_config_mac_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == BadKbAppCustomEventByteInputDone) {
|
||||
bt_set_profile_mac_address(bad_kb->bt, reverse_mac_addr(bad_kb->mac));
|
||||
uint8_t mac[GAP_MAC_ADDR_SIZE];
|
||||
bt_set_profile_mac_address(bad_kb->bt, reverse_mac_addr(bad_kb->mac, mac));
|
||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||
consumed = true;
|
||||
}
|
||||
@@ -54,4 +55,7 @@ void bad_kb_scene_config_mac_on_exit(void* context) {
|
||||
// Clear view
|
||||
byte_input_set_result_callback(bad_kb->byte_input, NULL, NULL, NULL, NULL, 0);
|
||||
byte_input_set_header_text(bad_kb->byte_input, "");
|
||||
|
||||
// reverse back addr (in case it didn't get modified)
|
||||
reverse_mac_addr(bad_kb->mac, bad_kb->mac);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ void bad_kb_scene_config_name_on_enter(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
TextInput* text_input = bad_kb->text_input;
|
||||
|
||||
text_input_set_header_text(text_input, "Set BLE adv name");
|
||||
text_input_set_header_text(text_input, "Set BT device name");
|
||||
|
||||
text_input_set_result_callback(
|
||||
text_input,
|
||||
|
||||
@@ -50,19 +50,7 @@ bool bad_kb_scene_config_usb_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.event == VarItemListIndexKeyboardLayout) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigLayout);
|
||||
} else if(event.event == VarItemListIndexConnection) {
|
||||
bad_kb_script_close(bad_kb->bad_kb_script);
|
||||
bad_kb_connection_deinit(bad_kb->bt);
|
||||
bad_kb->bad_kb_script =
|
||||
bad_kb_script_open(bad_kb->file_path, bad_kb->is_bt ? bad_kb->bt : NULL);
|
||||
bad_kb_script_set_keyboard_layout(bad_kb->bad_kb_script, bad_kb->keyboard_layout);
|
||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||
if(bad_kb->is_bt) {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBt);
|
||||
} else {
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigUsb);
|
||||
}
|
||||
// } else {
|
||||
// furi_crash("Unknown key type");
|
||||
bad_kb_config_switch_mode(bad_kb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ static bool bad_kb_file_select(BadKbApp* bad_kb) {
|
||||
void bad_kb_scene_file_select_on_enter(void* context) {
|
||||
BadKbApp* bad_kb = context;
|
||||
|
||||
furi_hal_usb_disable();
|
||||
if(bad_kb->bad_kb_script) {
|
||||
bad_kb_script_close(bad_kb->bad_kb_script);
|
||||
bad_kb->bad_kb_script = NULL;
|
||||
@@ -35,7 +34,6 @@ void bad_kb_scene_file_select_on_enter(void* context) {
|
||||
|
||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneWork);
|
||||
} else {
|
||||
furi_hal_usb_enable();
|
||||
view_dispatcher_stop(bad_kb->view_dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ bool bad_kb_scene_work_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == InputKeyLeft) {
|
||||
if(app->is_bt) {
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneConfigBt);
|
||||
} else {
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneConfigUsb);
|
||||
if(bad_kb_is_idle_state(app->bad_kb_view)) {
|
||||
if(app->is_bt) {
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneConfigBt);
|
||||
} else {
|
||||
scene_manager_next_scene(app->scene_manager, BadKbSceneConfigUsb);
|
||||
}
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == InputKeyOk) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "bad_kb_view.h"
|
||||
#include "../bad_kb_script.h"
|
||||
#include "../bad_kb_app_i.h"
|
||||
#include <toolbox/path.h>
|
||||
#include <gui/elements.h>
|
||||
#include <assets_icons.h>
|
||||
@@ -24,7 +25,8 @@ static void bad_kb_draw_callback(Canvas* canvas, void* _model) {
|
||||
BadKbModel* model = _model;
|
||||
|
||||
FuriString* disp_str;
|
||||
disp_str = furi_string_alloc_set(model->file_name);
|
||||
disp_str = furi_string_alloc_set(model->state.is_bt ? "(BT) " : "(USB) ");
|
||||
furi_string_cat_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, furi_string_get_cstr(disp_str));
|
||||
@@ -38,6 +40,9 @@ static void bad_kb_draw_callback(Canvas* canvas, void* _model) {
|
||||
furi_string_push_back(disp_str, model->layout[i]);
|
||||
furi_string_push_back(disp_str, ')');
|
||||
}
|
||||
if(model->state.pin) {
|
||||
furi_string_cat_printf(disp_str, " PIN: %ld", model->state.pin);
|
||||
}
|
||||
elements_string_fit_width(canvas, disp_str, 128 - 2);
|
||||
canvas_draw_str(
|
||||
canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str));
|
||||
@@ -51,19 +56,15 @@ static void bad_kb_draw_callback(Canvas* canvas, void* _model) {
|
||||
if(XTREME_ASSETS()->is_nsfw) {
|
||||
elements_button_center(canvas, "Cum");
|
||||
} else {
|
||||
elements_button_center(canvas, "Start");
|
||||
elements_button_center(canvas, "Run");
|
||||
}
|
||||
elements_button_left(canvas, "Config");
|
||||
} else if((model->state.state == BadKbStateRunning) || (model->state.state == BadKbStateDelay)) {
|
||||
elements_button_center(canvas, "Stop");
|
||||
} else if(model->state.state == BadKbStateWillRun) {
|
||||
elements_button_center(canvas, "Cancel");
|
||||
}
|
||||
|
||||
if((model->state.state == BadKbStateNotConnected) || (model->state.state == BadKbStateIdle) ||
|
||||
(model->state.state == BadKbStateDone)) {
|
||||
elements_button_left(canvas, "Config");
|
||||
}
|
||||
|
||||
if(model->state.state == BadKbStateNotConnected) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
@@ -214,6 +215,14 @@ void bad_kb_set_layout(BadKb* bad_kb, const char* layout) {
|
||||
|
||||
void bad_kb_set_state(BadKb* bad_kb, BadKbState* st) {
|
||||
furi_assert(st);
|
||||
uint32_t pin = 0;
|
||||
if(bad_kb->context != NULL) {
|
||||
BadKbApp* app = bad_kb->context;
|
||||
if(app->bt != NULL) {
|
||||
pin = app->bt->pin;
|
||||
}
|
||||
}
|
||||
st->pin = pin;
|
||||
with_view_model(
|
||||
bad_kb->view,
|
||||
BadKbModel * model,
|
||||
@@ -223,3 +232,18 @@ void bad_kb_set_state(BadKb* bad_kb, BadKbState* st) {
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
bool bad_kb_is_idle_state(BadKb* bad_kb) {
|
||||
bool is_idle = false;
|
||||
with_view_model(
|
||||
bad_kb->view,
|
||||
BadKbModel * model,
|
||||
{
|
||||
if((model->state.state == BadKbStateIdle) || (model->state.state == BadKbStateDone) ||
|
||||
(model->state.state == BadKbStateNotConnected)) {
|
||||
is_idle = true;
|
||||
}
|
||||
},
|
||||
false);
|
||||
return is_idle;
|
||||
}
|
||||
|
||||
@@ -19,3 +19,5 @@ void bad_kb_set_file_name(BadKb* bad_kb, const char* name);
|
||||
void bad_kb_set_layout(BadKb* bad_kb, const char* layout);
|
||||
|
||||
void bad_kb_set_state(BadKb* bad_kb, BadKbState* st);
|
||||
|
||||
bool bad_kb_is_idle_state(BadKb* bad_kb);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <storage/storage.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include "elf_cpp/elf_hashtable.h"
|
||||
#include "fap_loader_app.h"
|
||||
@@ -35,7 +36,7 @@ bool fap_loader_load_name_and_icon(
|
||||
|
||||
if(preload_res == FlipperApplicationPreloadStatusSuccess) {
|
||||
const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app);
|
||||
if(manifest->has_icon) {
|
||||
if(manifest->has_icon && icon_ptr != NULL) {
|
||||
memcpy(*icon_ptr, manifest->icon, FAP_MANIFEST_MAX_ICON_SIZE);
|
||||
}
|
||||
furi_string_set(item_name, manifest->name);
|
||||
@@ -105,6 +106,12 @@ static bool fap_loader_run_selected_app(FapLoader* loader) {
|
||||
FURI_LOG_I(TAG, "FAP Loader is starting app");
|
||||
|
||||
FuriThread* thread = flipper_application_spawn(loader->app, NULL);
|
||||
|
||||
FuriString* app_name = furi_string_alloc();
|
||||
path_extract_filename_no_ext(furi_string_get_cstr(loader->fap_path), app_name);
|
||||
furi_thread_set_appid(thread, furi_string_get_cstr(app_name));
|
||||
furi_string_free(app_name);
|
||||
|
||||
furi_thread_start(thread);
|
||||
furi_thread_join(thread);
|
||||
|
||||
@@ -146,6 +153,7 @@ static bool fap_loader_select_app(FapLoader* loader) {
|
||||
.skip_assets = true,
|
||||
.icon = &I_unknown_10px,
|
||||
.hide_ext = true,
|
||||
.hide_dot_files = true,
|
||||
.item_loader_callback = fap_loader_item_callback,
|
||||
.item_loader_context = loader,
|
||||
.base_path = EXT_PATH("apps"),
|
||||
|
||||
@@ -107,6 +107,7 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(event.event == GpioStartEventUsbUart) {
|
||||
scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemUsbUart);
|
||||
if(!furi_hal_usb_is_locked()) {
|
||||
DOLPHIN_DEED(DolphinDeedGpioUartBridge);
|
||||
scene_manager_next_scene(app->scene_manager, GpioSceneUsbUart);
|
||||
} else {
|
||||
scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCloseRpc);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#include "ibutton.h"
|
||||
#include "assets_icons.h"
|
||||
#include "ibutton_i.h"
|
||||
#include "ibutton/scenes/ibutton_scene.h"
|
||||
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <rpc/rpc_app.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define TAG "iButtonApp"
|
||||
@@ -34,50 +30,13 @@ static const NotificationSequence* ibutton_notification_sequences[] = {
|
||||
};
|
||||
|
||||
static void ibutton_make_app_folder(iButton* ibutton) {
|
||||
if(!storage_simply_mkdir(ibutton->storage, IBUTTON_APP_FOLDER)) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
if(!storage_simply_mkdir(storage, IBUTTON_APP_FOLDER)) {
|
||||
dialog_message_show_storage_error(ibutton->dialogs, "Cannot create\napp folder");
|
||||
}
|
||||
}
|
||||
|
||||
bool ibutton_load_key_data(iButton* ibutton, FuriString* key_path, bool show_dialog) {
|
||||
FlipperFormat* file = flipper_format_file_alloc(ibutton->storage);
|
||||
bool result = false;
|
||||
FuriString* data;
|
||||
data = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_existing(file, furi_string_get_cstr(key_path))) break;
|
||||
|
||||
// header
|
||||
uint32_t version;
|
||||
if(!flipper_format_read_header(file, data, &version)) break;
|
||||
if(furi_string_cmp_str(data, IBUTTON_APP_FILE_TYPE) != 0) break;
|
||||
if(version != 1) break;
|
||||
|
||||
// key type
|
||||
iButtonKeyType type;
|
||||
if(!flipper_format_read_string(file, "Key type", data)) break;
|
||||
if(!ibutton_key_get_type_by_string(furi_string_get_cstr(data), &type)) break;
|
||||
|
||||
// key data
|
||||
uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0};
|
||||
if(!flipper_format_read_hex(file, "Data", key_data, ibutton_key_get_size_by_type(type)))
|
||||
break;
|
||||
|
||||
ibutton_key_set_type(ibutton->key, type);
|
||||
ibutton_key_set_data(ibutton->key, key_data, IBUTTON_KEY_DATA_SIZE);
|
||||
|
||||
result = true;
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(file);
|
||||
furi_string_free(data);
|
||||
|
||||
if((!result) && (show_dialog)) {
|
||||
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
|
||||
}
|
||||
|
||||
return result;
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context) {
|
||||
@@ -87,14 +46,14 @@ static void ibutton_rpc_command_callback(RpcAppSystemEvent event, void* context)
|
||||
if(event == RpcAppEventSessionClose) {
|
||||
view_dispatcher_send_custom_event(
|
||||
ibutton->view_dispatcher, iButtonCustomEventRpcSessionClose);
|
||||
rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
|
||||
ibutton->rpc_ctx = NULL;
|
||||
rpc_system_app_set_callback(ibutton->rpc, NULL, NULL);
|
||||
ibutton->rpc = NULL;
|
||||
} else if(event == RpcAppEventAppExit) {
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcExit);
|
||||
} else if(event == RpcAppEventLoadFile) {
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventRpcLoad);
|
||||
} else {
|
||||
rpc_system_app_confirm(ibutton->rpc_ctx, event, false);
|
||||
rpc_system_app_confirm(ibutton->rpc, event, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,13 +94,13 @@ iButton* ibutton_alloc() {
|
||||
|
||||
ibutton->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
ibutton->storage = furi_record_open(RECORD_STORAGE);
|
||||
ibutton->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
ibutton->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
ibutton->key = ibutton_key_alloc();
|
||||
ibutton->key_worker = ibutton_worker_alloc();
|
||||
ibutton_worker_start_thread(ibutton->key_worker);
|
||||
ibutton->protocols = ibutton_protocols_alloc();
|
||||
ibutton->key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(ibutton->protocols));
|
||||
ibutton->worker = ibutton_worker_alloc(ibutton->protocols);
|
||||
ibutton_worker_start_thread(ibutton->worker);
|
||||
|
||||
ibutton->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
@@ -163,9 +122,9 @@ iButton* ibutton_alloc() {
|
||||
view_dispatcher_add_view(
|
||||
ibutton->view_dispatcher, iButtonViewWidget, widget_get_view(ibutton->widget));
|
||||
|
||||
ibutton->dialog_ex = dialog_ex_alloc();
|
||||
ibutton->loading = loading_alloc();
|
||||
view_dispatcher_add_view(
|
||||
ibutton->view_dispatcher, iButtonViewDialogEx, dialog_ex_get_view(ibutton->dialog_ex));
|
||||
ibutton->view_dispatcher, iButtonViewLoading, loading_get_view(ibutton->loading));
|
||||
|
||||
return ibutton;
|
||||
}
|
||||
@@ -173,8 +132,8 @@ iButton* ibutton_alloc() {
|
||||
void ibutton_free(iButton* ibutton) {
|
||||
furi_assert(ibutton);
|
||||
|
||||
view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewDialogEx);
|
||||
dialog_ex_free(ibutton->dialog_ex);
|
||||
view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewLoading);
|
||||
loading_free(ibutton->loading);
|
||||
|
||||
view_dispatcher_remove_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
widget_free(ibutton->widget);
|
||||
@@ -194,9 +153,6 @@ void ibutton_free(iButton* ibutton) {
|
||||
view_dispatcher_free(ibutton->view_dispatcher);
|
||||
scene_manager_free(ibutton->scene_manager);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
ibutton->storage = NULL;
|
||||
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
ibutton->notifications = NULL;
|
||||
|
||||
@@ -206,103 +162,83 @@ void ibutton_free(iButton* ibutton) {
|
||||
furi_record_close(RECORD_GUI);
|
||||
ibutton->gui = NULL;
|
||||
|
||||
ibutton_worker_stop_thread(ibutton->key_worker);
|
||||
ibutton_worker_free(ibutton->key_worker);
|
||||
ibutton_worker_stop_thread(ibutton->worker);
|
||||
ibutton_worker_free(ibutton->worker);
|
||||
ibutton_key_free(ibutton->key);
|
||||
ibutton_protocols_free(ibutton->protocols);
|
||||
|
||||
furi_string_free(ibutton->file_path);
|
||||
|
||||
free(ibutton);
|
||||
}
|
||||
|
||||
bool ibutton_file_select(iButton* ibutton) {
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px);
|
||||
browser_options.base_path = IBUTTON_APP_FOLDER;
|
||||
bool ibutton_load_key(iButton* ibutton) {
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading);
|
||||
|
||||
bool success = dialog_file_browser_show(
|
||||
ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options);
|
||||
const bool success = ibutton_protocols_load(
|
||||
ibutton->protocols, ibutton->key, furi_string_get_cstr(ibutton->file_path));
|
||||
|
||||
if(success) {
|
||||
success = ibutton_load_key_data(ibutton, ibutton->file_path, true);
|
||||
if(!success) {
|
||||
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
|
||||
|
||||
} else {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
path_extract_filename(ibutton->file_path, tmp, true);
|
||||
strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE);
|
||||
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ibutton_save_key(iButton* ibutton, const char* key_name) {
|
||||
// Create ibutton directory if necessary
|
||||
bool ibutton_select_and_load_key(iButton* ibutton) {
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(&browser_options, IBUTTON_APP_EXTENSION, &I_ibutt_10px);
|
||||
browser_options.base_path = IBUTTON_APP_FOLDER;
|
||||
|
||||
if(furi_string_empty(ibutton->file_path)) {
|
||||
furi_string_set(ibutton->file_path, browser_options.base_path);
|
||||
}
|
||||
|
||||
return dialog_file_browser_show(
|
||||
ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options) &&
|
||||
ibutton_load_key(ibutton);
|
||||
}
|
||||
|
||||
bool ibutton_save_key(iButton* ibutton) {
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading);
|
||||
|
||||
ibutton_make_app_folder(ibutton);
|
||||
|
||||
FlipperFormat* file = flipper_format_file_alloc(ibutton->storage);
|
||||
iButtonKey* key = ibutton->key;
|
||||
const bool success =
|
||||
ibutton_protocols_save(ibutton->protocols, key, furi_string_get_cstr(ibutton->file_path));
|
||||
|
||||
bool result = false;
|
||||
|
||||
do {
|
||||
// Check if we has old key
|
||||
if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
|
||||
// First remove old key
|
||||
ibutton_delete_key(ibutton);
|
||||
|
||||
// Remove old key name from path
|
||||
size_t filename_start = furi_string_search_rchar(ibutton->file_path, '/');
|
||||
furi_string_left(ibutton->file_path, filename_start);
|
||||
}
|
||||
|
||||
furi_string_cat_printf(ibutton->file_path, "/%s%s", key_name, IBUTTON_APP_EXTENSION);
|
||||
|
||||
// Open file for write
|
||||
if(!flipper_format_file_open_always(file, furi_string_get_cstr(ibutton->file_path))) break;
|
||||
|
||||
// Write header
|
||||
if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break;
|
||||
|
||||
// Write key type
|
||||
if(!flipper_format_write_comment_cstr(file, "Key type can be Cyfral, Dallas or Metakom"))
|
||||
break;
|
||||
const char* key_type = ibutton_key_get_string_by_type(ibutton_key_get_type(key));
|
||||
if(!flipper_format_write_string_cstr(file, "Key type", key_type)) break;
|
||||
|
||||
// Write data
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
file, "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8"))
|
||||
break;
|
||||
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Data", ibutton_key_get_data_p(key), ibutton_key_get_data_size(key)))
|
||||
break;
|
||||
result = true;
|
||||
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(file);
|
||||
|
||||
if(!result) { //-V547
|
||||
if(!success) {
|
||||
dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file");
|
||||
}
|
||||
|
||||
return result;
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ibutton_delete_key(iButton* ibutton) {
|
||||
bool result = false;
|
||||
result = storage_simply_remove(ibutton->storage, furi_string_get_cstr(ibutton->file_path));
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
result = storage_simply_remove(storage, furi_string_get_cstr(ibutton->file_path));
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
ibutton_reset_key(ibutton);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ibutton_text_store_set(iButton* ibutton, const char* text, ...) {
|
||||
va_list args;
|
||||
va_start(args, text);
|
||||
|
||||
vsnprintf(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE, text, args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void ibutton_text_store_clear(iButton* ibutton) {
|
||||
memset(ibutton->text_store, 0, IBUTTON_TEXT_STORE_SIZE + 1);
|
||||
void ibutton_reset_key(iButton* ibutton) {
|
||||
memset(ibutton->key_name, 0, IBUTTON_KEY_NAME_SIZE + 1);
|
||||
furi_string_reset(ibutton->file_path);
|
||||
ibutton_key_reset(ibutton->key);
|
||||
}
|
||||
|
||||
void ibutton_notification_message(iButton* ibutton, uint32_t message) {
|
||||
@@ -310,36 +246,44 @@ void ibutton_notification_message(iButton* ibutton, uint32_t message) {
|
||||
notification_message(ibutton->notifications, ibutton_notification_sequences[message]);
|
||||
}
|
||||
|
||||
int32_t ibutton_app(void* p) {
|
||||
void ibutton_submenu_callback(void* context, uint32_t index) {
|
||||
iButton* ibutton = context;
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void ibutton_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
iButton* ibutton = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t ibutton_app(void* arg) {
|
||||
iButton* ibutton = ibutton_alloc();
|
||||
|
||||
ibutton_make_app_folder(ibutton);
|
||||
|
||||
bool key_loaded = false;
|
||||
bool rpc_mode = false;
|
||||
|
||||
if(p && strlen(p)) {
|
||||
uint32_t rpc_ctx = 0;
|
||||
if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
|
||||
if((arg != NULL) && (strlen(arg) != 0)) {
|
||||
if(sscanf(arg, "RPC %lX", (uint32_t*)&ibutton->rpc) == 1) {
|
||||
FURI_LOG_D(TAG, "Running in RPC mode");
|
||||
ibutton->rpc_ctx = (void*)rpc_ctx;
|
||||
rpc_mode = true;
|
||||
rpc_system_app_set_callback(ibutton->rpc_ctx, ibutton_rpc_command_callback, ibutton);
|
||||
rpc_system_app_send_started(ibutton->rpc_ctx);
|
||||
|
||||
rpc_system_app_set_callback(ibutton->rpc, ibutton_rpc_command_callback, ibutton);
|
||||
rpc_system_app_send_started(ibutton->rpc);
|
||||
|
||||
} else {
|
||||
furi_string_set(ibutton->file_path, (const char*)p);
|
||||
if(ibutton_load_key_data(ibutton, ibutton->file_path, true)) {
|
||||
key_loaded = true;
|
||||
// TODO: Display an error if the key from p could not be loaded
|
||||
}
|
||||
furi_string_set(ibutton->file_path, (const char*)arg);
|
||||
key_loaded = ibutton_load_key(ibutton);
|
||||
}
|
||||
}
|
||||
|
||||
if(rpc_mode) {
|
||||
if(ibutton->rpc != NULL) {
|
||||
view_dispatcher_attach_to_gui(
|
||||
ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeDesktop);
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRpc);
|
||||
DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
|
||||
|
||||
} else {
|
||||
view_dispatcher_attach_to_gui(
|
||||
ibutton->view_dispatcher, ibutton->gui, ViewDispatcherTypeFullscreen);
|
||||
@@ -353,9 +297,9 @@ int32_t ibutton_app(void* p) {
|
||||
|
||||
view_dispatcher_run(ibutton->view_dispatcher);
|
||||
|
||||
if(ibutton->rpc_ctx) {
|
||||
rpc_system_app_set_callback(ibutton->rpc_ctx, NULL, NULL);
|
||||
rpc_system_app_send_exited(ibutton->rpc_ctx);
|
||||
if(ibutton->rpc) {
|
||||
rpc_system_app_set_callback(ibutton->rpc, NULL, NULL);
|
||||
rpc_system_app_send_exited(ibutton->rpc);
|
||||
}
|
||||
ibutton_free(ibutton);
|
||||
return 0;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
#include <one_wire/ibutton/ibutton_worker.h>
|
||||
#include <toolbox/args.h>
|
||||
|
||||
#include <one_wire/one_wire_host.h>
|
||||
|
||||
#include <one_wire/ibutton/ibutton_key.h>
|
||||
#include <one_wire/ibutton/ibutton_worker.h>
|
||||
#include <one_wire/ibutton/ibutton_protocols.h>
|
||||
|
||||
static void ibutton_cli(Cli* cli, FuriString* args, void* context);
|
||||
static void onewire_cli(Cli* cli, FuriString* args, void* context);
|
||||
|
||||
@@ -22,7 +26,7 @@ void ibutton_on_system_start() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void ibutton_cli_print_usage() {
|
||||
static void ibutton_cli_print_usage() {
|
||||
printf("Usage:\r\n");
|
||||
printf("ikey read\r\n");
|
||||
printf("ikey emulate <key_type> <key_data>\r\n");
|
||||
@@ -34,30 +38,52 @@ void ibutton_cli_print_usage() {
|
||||
printf("\t<key_data> are hex-formatted\r\n");
|
||||
};
|
||||
|
||||
bool ibutton_cli_get_key_type(FuriString* data, iButtonKeyType* type) {
|
||||
static bool ibutton_cli_parse_key(iButtonProtocols* protocols, iButtonKey* key, FuriString* args) {
|
||||
bool result = false;
|
||||
FuriString* name = furi_string_alloc();
|
||||
|
||||
if(furi_string_cmp_str(data, "Dallas") == 0 || furi_string_cmp_str(data, "dallas") == 0) {
|
||||
result = true;
|
||||
*type = iButtonKeyDS1990;
|
||||
} else if(furi_string_cmp_str(data, "Cyfral") == 0 || furi_string_cmp_str(data, "cyfral") == 0) {
|
||||
result = true;
|
||||
*type = iButtonKeyCyfral;
|
||||
} else if(furi_string_cmp_str(data, "Metakom") == 0 || furi_string_cmp_str(data, "metakom") == 0) {
|
||||
result = true;
|
||||
*type = iButtonKeyMetakom;
|
||||
}
|
||||
do {
|
||||
// Read protocol name
|
||||
if(!args_read_string_and_trim(args, name)) break;
|
||||
|
||||
// Make the protocol name uppercase
|
||||
const char first = furi_string_get_char(name, 0);
|
||||
furi_string_set_char(name, 0, toupper((int)first));
|
||||
|
||||
const iButtonProtocolId id =
|
||||
ibutton_protocols_get_id_by_name(protocols, furi_string_get_cstr(name));
|
||||
if(id == iButtonProtocolIdInvalid) break;
|
||||
|
||||
ibutton_key_set_protocol_id(key, id);
|
||||
|
||||
// Get the data pointer
|
||||
iButtonEditableData data;
|
||||
ibutton_protocols_get_editable_data(protocols, key, &data);
|
||||
|
||||
// Read data
|
||||
if(!args_read_hex_bytes(args, data.ptr, data.size)) break;
|
||||
|
||||
result = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(name);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ibutton_cli_print_key_data(iButtonKey* key) {
|
||||
const uint8_t* key_data = ibutton_key_get_data_p(key);
|
||||
iButtonKeyType type = ibutton_key_get_type(key);
|
||||
static void ibutton_cli_print_key(iButtonProtocols* protocols, iButtonKey* key) {
|
||||
const char* name = ibutton_protocols_get_name(protocols, ibutton_key_get_protocol_id(key));
|
||||
|
||||
printf("%s ", ibutton_key_get_string_by_type(type));
|
||||
for(size_t i = 0; i < ibutton_key_get_size_by_type(type); i++) {
|
||||
printf("%02X", key_data[i]);
|
||||
if(strncmp(name, "DS", 2) == 0) {
|
||||
name = "Dallas";
|
||||
}
|
||||
|
||||
printf("%s ", name);
|
||||
|
||||
iButtonEditableData data;
|
||||
ibutton_protocols_get_editable_data(protocols, key, &data);
|
||||
|
||||
for(size_t i = 0; i < data.size; i++) {
|
||||
printf("%02X", data.ptr[i]);
|
||||
}
|
||||
|
||||
printf("\r\n");
|
||||
@@ -71,9 +97,10 @@ static void ibutton_cli_worker_read_cb(void* context) {
|
||||
furi_event_flag_set(event, EVENT_FLAG_IBUTTON_COMPLETE);
|
||||
}
|
||||
|
||||
void ibutton_cli_read(Cli* cli) {
|
||||
iButtonKey* key = ibutton_key_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc();
|
||||
static void ibutton_cli_read(Cli* cli) {
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
FuriEventFlag* event = furi_event_flag_alloc();
|
||||
|
||||
ibutton_worker_start_thread(worker);
|
||||
@@ -81,32 +108,25 @@ void ibutton_cli_read(Cli* cli) {
|
||||
|
||||
printf("Reading iButton...\r\nPress Ctrl+C to abort\r\n");
|
||||
ibutton_worker_read_start(worker, key);
|
||||
|
||||
while(true) {
|
||||
uint32_t flags =
|
||||
furi_event_flag_wait(event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100);
|
||||
|
||||
if(flags & EVENT_FLAG_IBUTTON_COMPLETE) {
|
||||
ibutton_cli_print_key_data(key);
|
||||
|
||||
if(ibutton_key_get_type(key) == iButtonKeyDS1990) {
|
||||
if(!ibutton_key_dallas_crc_is_valid(key)) {
|
||||
printf("Warning: invalid CRC\r\n");
|
||||
}
|
||||
|
||||
if(!ibutton_key_dallas_is_1990_key(key)) {
|
||||
printf("Warning: not a key\r\n");
|
||||
}
|
||||
}
|
||||
ibutton_cli_print_key(protocols, key);
|
||||
break;
|
||||
}
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
}
|
||||
ibutton_worker_stop(worker);
|
||||
|
||||
ibutton_worker_stop(worker);
|
||||
ibutton_worker_stop_thread(worker);
|
||||
ibutton_worker_free(worker);
|
||||
|
||||
ibutton_key_free(key);
|
||||
ibutton_worker_free(worker);
|
||||
ibutton_protocols_free(protocols);
|
||||
|
||||
furi_event_flag_free(event);
|
||||
};
|
||||
@@ -124,48 +144,33 @@ static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult
|
||||
}
|
||||
|
||||
void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
iButtonKey* key = ibutton_key_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc();
|
||||
iButtonKeyType type;
|
||||
iButtonWriteContext write_context;
|
||||
uint8_t key_data[IBUTTON_KEY_DATA_SIZE];
|
||||
FuriString* data;
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
|
||||
iButtonWriteContext write_context;
|
||||
write_context.event = furi_event_flag_alloc();
|
||||
|
||||
data = furi_string_alloc();
|
||||
ibutton_worker_start_thread(worker);
|
||||
ibutton_worker_write_set_callback(worker, ibutton_cli_worker_write_cb, &write_context);
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, data)) {
|
||||
if(!ibutton_cli_parse_key(protocols, key, args)) {
|
||||
ibutton_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(!ibutton_cli_get_key_type(data, &type)) {
|
||||
if(!(ibutton_protocols_get_features(protocols, ibutton_key_get_protocol_id(key)) &
|
||||
iButtonProtocolFeatureWriteBlank)) {
|
||||
ibutton_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(type != iButtonKeyDS1990) {
|
||||
ibutton_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(!args_read_hex_bytes(args, key_data, ibutton_key_get_size_by_type(type))) {
|
||||
ibutton_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
ibutton_key_set_type(key, type);
|
||||
ibutton_key_set_data(key, key_data, ibutton_key_get_size_by_type(type));
|
||||
|
||||
printf("Writing key ");
|
||||
ibutton_cli_print_key_data(key);
|
||||
ibutton_cli_print_key(protocols, key);
|
||||
printf("Press Ctrl+C to abort\r\n");
|
||||
|
||||
ibutton_worker_write_start(worker, key);
|
||||
ibutton_worker_write_blank_start(worker, key);
|
||||
while(true) {
|
||||
uint32_t flags = furi_event_flag_wait(
|
||||
write_context.event, EVENT_FLAG_IBUTTON_COMPLETE, FuriFlagWaitAny, 100);
|
||||
@@ -183,64 +188,53 @@ void ibutton_cli_write(Cli* cli, FuriString* args) {
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
}
|
||||
ibutton_worker_stop(worker);
|
||||
} while(false);
|
||||
|
||||
furi_string_free(data);
|
||||
ibutton_worker_stop(worker);
|
||||
ibutton_worker_stop_thread(worker);
|
||||
ibutton_worker_free(worker);
|
||||
|
||||
ibutton_key_free(key);
|
||||
ibutton_worker_free(worker);
|
||||
ibutton_protocols_free(protocols);
|
||||
|
||||
furi_event_flag_free(write_context.event);
|
||||
};
|
||||
}
|
||||
|
||||
void ibutton_cli_emulate(Cli* cli, FuriString* args) {
|
||||
iButtonKey* key = ibutton_key_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc();
|
||||
iButtonKeyType type;
|
||||
uint8_t key_data[IBUTTON_KEY_DATA_SIZE];
|
||||
FuriString* data;
|
||||
iButtonProtocols* protocols = ibutton_protocols_alloc();
|
||||
iButtonWorker* worker = ibutton_worker_alloc(protocols);
|
||||
iButtonKey* key = ibutton_key_alloc(ibutton_protocols_get_max_data_size(protocols));
|
||||
|
||||
data = furi_string_alloc();
|
||||
ibutton_worker_start_thread(worker);
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, data)) {
|
||||
if(!ibutton_cli_parse_key(protocols, key, args)) {
|
||||
ibutton_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(!ibutton_cli_get_key_type(data, &type)) {
|
||||
ibutton_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(!args_read_hex_bytes(args, key_data, ibutton_key_get_size_by_type(type))) {
|
||||
ibutton_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
ibutton_key_set_type(key, type);
|
||||
ibutton_key_set_data(key, key_data, ibutton_key_get_size_by_type(type));
|
||||
|
||||
printf("Emulating key ");
|
||||
ibutton_cli_print_key_data(key);
|
||||
ibutton_cli_print_key(protocols, key);
|
||||
printf("Press Ctrl+C to abort\r\n");
|
||||
|
||||
ibutton_worker_emulate_start(worker, key);
|
||||
|
||||
while(!cli_cmd_interrupt_received(cli)) {
|
||||
furi_delay_ms(100);
|
||||
};
|
||||
ibutton_worker_stop(worker);
|
||||
|
||||
} while(false);
|
||||
|
||||
furi_string_free(data);
|
||||
ibutton_worker_stop(worker);
|
||||
ibutton_worker_stop_thread(worker);
|
||||
ibutton_worker_free(worker);
|
||||
|
||||
ibutton_key_free(key);
|
||||
ibutton_worker_free(worker);
|
||||
ibutton_protocols_free(protocols);
|
||||
};
|
||||
|
||||
static void ibutton_cli(Cli* cli, FuriString* args, void* context) {
|
||||
void ibutton_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(context);
|
||||
FuriString* cmd;
|
||||
cmd = furi_string_alloc();
|
||||
@@ -264,14 +258,14 @@ static void ibutton_cli(Cli* cli, FuriString* args, void* context) {
|
||||
furi_string_free(cmd);
|
||||
}
|
||||
|
||||
void onewire_cli_print_usage() {
|
||||
static void onewire_cli_print_usage() {
|
||||
printf("Usage:\r\n");
|
||||
printf("onewire search\r\n");
|
||||
};
|
||||
|
||||
static void onewire_cli_search(Cli* cli) {
|
||||
UNUSED(cli);
|
||||
OneWireHost* onewire = onewire_host_alloc();
|
||||
OneWireHost* onewire = onewire_host_alloc(&ibutton_gpio);
|
||||
uint8_t address[8];
|
||||
bool done = false;
|
||||
|
||||
@@ -281,7 +275,7 @@ static void onewire_cli_search(Cli* cli) {
|
||||
furi_hal_power_enable_otg();
|
||||
|
||||
while(!done) {
|
||||
if(onewire_host_search(onewire, address, NORMAL_SEARCH) != 1) {
|
||||
if(onewire_host_search(onewire, address, OneWireHostSearchModeNormal) != 1) {
|
||||
printf("Search finished\r\n");
|
||||
onewire_host_reset_search(onewire);
|
||||
done = true;
|
||||
|
||||
@@ -6,6 +6,7 @@ enum iButtonCustomEvent {
|
||||
|
||||
iButtonCustomEventBack,
|
||||
iButtonCustomEventTextEditResult,
|
||||
iButtonCustomEventByteEditChanged,
|
||||
iButtonCustomEventByteEditResult,
|
||||
iButtonCustomEventWorkerEmulated,
|
||||
iButtonCustomEventWorkerRead,
|
||||
|
||||
@@ -4,31 +4,40 @@
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
|
||||
#include <one_wire/ibutton/ibutton_worker.h>
|
||||
#include <one_wire/ibutton/ibutton_protocols.h>
|
||||
|
||||
#include <rpc/rpc_app.h>
|
||||
#include <storage/storage.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/byte_input.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/loading.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#include "ibutton_custom_event.h"
|
||||
#include "scenes/ibutton_scene.h"
|
||||
|
||||
#define IBUTTON_FILE_NAME_SIZE 100
|
||||
#define IBUTTON_TEXT_STORE_SIZE 128
|
||||
|
||||
#define IBUTTON_APP_FOLDER ANY_PATH("ibutton")
|
||||
#define IBUTTON_APP_EXTENSION ".ibtn"
|
||||
#define IBUTTON_APP_FILE_TYPE "Flipper iButton key"
|
||||
|
||||
#define IBUTTON_KEY_NAME_SIZE 22
|
||||
|
||||
typedef enum {
|
||||
iButtonWriteModeInvalid,
|
||||
iButtonWriteModeBlank,
|
||||
iButtonWriteModeCopy,
|
||||
} iButtonWriteMode;
|
||||
|
||||
struct iButton {
|
||||
SceneManager* scene_manager;
|
||||
@@ -38,21 +47,22 @@ struct iButton {
|
||||
Storage* storage;
|
||||
DialogsApp* dialogs;
|
||||
NotificationApp* notifications;
|
||||
RpcAppSystem* rpc;
|
||||
|
||||
iButtonWorker* key_worker;
|
||||
iButtonKey* key;
|
||||
iButtonWorker* worker;
|
||||
iButtonProtocols* protocols;
|
||||
iButtonWriteMode write_mode;
|
||||
|
||||
FuriString* file_path;
|
||||
char text_store[IBUTTON_TEXT_STORE_SIZE + 1];
|
||||
char key_name[IBUTTON_KEY_NAME_SIZE + 1];
|
||||
|
||||
Submenu* submenu;
|
||||
ByteInput* byte_input;
|
||||
TextInput* text_input;
|
||||
Popup* popup;
|
||||
Widget* widget;
|
||||
DialogEx* dialog_ex;
|
||||
|
||||
void* rpc_ctx;
|
||||
Loading* loading;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
@@ -61,7 +71,7 @@ typedef enum {
|
||||
iButtonViewTextInput,
|
||||
iButtonViewPopup,
|
||||
iButtonViewWidget,
|
||||
iButtonViewDialogEx,
|
||||
iButtonViewLoading,
|
||||
} iButtonView;
|
||||
|
||||
typedef enum {
|
||||
@@ -78,10 +88,12 @@ typedef enum {
|
||||
iButtonNotificationMessageBlinkStop,
|
||||
} iButtonNotificationMessage;
|
||||
|
||||
bool ibutton_file_select(iButton* ibutton);
|
||||
bool ibutton_load_key_data(iButton* ibutton, FuriString* key_path, bool show_dialog);
|
||||
bool ibutton_save_key(iButton* ibutton, const char* key_name);
|
||||
bool ibutton_select_and_load_key(iButton* ibutton);
|
||||
bool ibutton_load_key(iButton* ibutton);
|
||||
bool ibutton_save_key(iButton* ibutton);
|
||||
bool ibutton_delete_key(iButton* ibutton);
|
||||
void ibutton_text_store_set(iButton* ibutton, const char* text, ...);
|
||||
void ibutton_text_store_clear(iButton* ibutton);
|
||||
void ibutton_reset_key(iButton* ibutton);
|
||||
void ibutton_notification_message(iButton* ibutton, uint32_t message);
|
||||
|
||||
void ibutton_submenu_callback(void* context, uint32_t index);
|
||||
void ibutton_widget_callback(GuiButtonType result, InputType type, void* context);
|
||||
|
||||
@@ -1,54 +1,57 @@
|
||||
#include "../ibutton_i.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexCyfral,
|
||||
SubmenuIndexDallas,
|
||||
SubmenuIndexMetakom,
|
||||
};
|
||||
|
||||
void ibutton_scene_add_type_submenu_callback(void* context, uint32_t index) {
|
||||
iButton* ibutton = context;
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void ibutton_scene_add_type_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
Submenu* submenu = ibutton->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "Cyfral", SubmenuIndexCyfral, ibutton_scene_add_type_submenu_callback, ibutton);
|
||||
submenu_add_item(
|
||||
submenu, "Dallas", SubmenuIndexDallas, ibutton_scene_add_type_submenu_callback, ibutton);
|
||||
submenu_add_item(
|
||||
submenu, "Metakom", SubmenuIndexMetakom, ibutton_scene_add_type_submenu_callback, ibutton);
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddType));
|
||||
for(uint32_t protocol_id = 0; protocol_id < ibutton_protocols_get_protocol_count();
|
||||
++protocol_id) {
|
||||
if((strcmp(
|
||||
ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id),
|
||||
ibutton_protocols_get_name(ibutton->protocols, protocol_id)) != 0) &&
|
||||
(strcmp(ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id), "N/A") !=
|
||||
0)) {
|
||||
furi_string_printf(
|
||||
tmp,
|
||||
"%s %s",
|
||||
ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id),
|
||||
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
|
||||
} else {
|
||||
furi_string_printf(
|
||||
tmp, "%s", ibutton_protocols_get_name(ibutton->protocols, protocol_id));
|
||||
}
|
||||
|
||||
submenu_add_item(
|
||||
submenu, furi_string_get_cstr(tmp), protocol_id, ibutton_submenu_callback, context);
|
||||
}
|
||||
|
||||
const uint32_t prev_protocol_id =
|
||||
scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddType);
|
||||
submenu_set_selected_item(submenu, prev_protocol_id);
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) {
|
||||
iButton* ibutton = context;
|
||||
iButtonKey* key = ibutton->key;
|
||||
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneAddType, event.event);
|
||||
consumed = true;
|
||||
if(event.event == SubmenuIndexCyfral) {
|
||||
ibutton_key_set_type(key, iButtonKeyCyfral);
|
||||
} else if(event.event == SubmenuIndexDallas) {
|
||||
ibutton_key_set_type(key, iButtonKeyDS1990);
|
||||
} else if(event.event == SubmenuIndexMetakom) {
|
||||
ibutton_key_set_type(key, iButtonKeyMetakom);
|
||||
} else {
|
||||
furi_crash("Unknown key type");
|
||||
}
|
||||
const iButtonProtocolId protocol_id = event.event;
|
||||
|
||||
furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
|
||||
ibutton_key_clear_data(key);
|
||||
ibutton_key_reset(key);
|
||||
ibutton_key_set_protocol_id(key, protocol_id);
|
||||
ibutton_protocols_apply_edits(ibutton->protocols, key);
|
||||
|
||||
scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneAddType, protocol_id);
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue);
|
||||
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
|
||||
@@ -1,42 +1,52 @@
|
||||
#include "../ibutton_i.h"
|
||||
|
||||
void ibutton_scene_add_type_byte_input_callback(void* context) {
|
||||
static void ibutton_scene_add_type_byte_input_callback(void* context) {
|
||||
iButton* ibutton = context;
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditResult);
|
||||
}
|
||||
|
||||
static void ibutton_scene_add_type_byte_changed_callback(void* context) {
|
||||
iButton* ibutton = context;
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, iButtonCustomEventByteEditChanged);
|
||||
}
|
||||
|
||||
void ibutton_scene_add_value_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
iButtonKey* key = ibutton->key;
|
||||
uint8_t* new_key_data = malloc(IBUTTON_KEY_DATA_SIZE);
|
||||
byte_input_set_header_text(ibutton->byte_input, "Enter the key");
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
ibutton->scene_manager, iButtonSceneAddValue, (uint32_t)new_key_data);
|
||||
memcpy(new_key_data, ibutton_key_get_data_p(key), ibutton_key_get_data_size(key));
|
||||
iButtonEditableData editable_data;
|
||||
ibutton_protocols_get_editable_data(ibutton->protocols, ibutton->key, &editable_data);
|
||||
|
||||
byte_input_set_result_callback(
|
||||
ibutton->byte_input,
|
||||
ibutton_scene_add_type_byte_input_callback,
|
||||
NULL,
|
||||
ibutton,
|
||||
new_key_data,
|
||||
ibutton_key_get_data_size(key));
|
||||
ibutton_scene_add_type_byte_changed_callback,
|
||||
context,
|
||||
editable_data.ptr,
|
||||
editable_data.size);
|
||||
|
||||
byte_input_set_header_text(ibutton->byte_input, "Enter the key");
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewByteInput);
|
||||
}
|
||||
|
||||
bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
|
||||
iButton* ibutton = context;
|
||||
uint8_t* new_key_data =
|
||||
(uint8_t*)scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddValue);
|
||||
SceneManager* scene_manager = ibutton->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == iButtonCustomEventByteEditResult) {
|
||||
ibutton_key_set_data(ibutton->key, new_key_data, IBUTTON_KEY_DATA_SIZE);
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName);
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneSaveName);
|
||||
} else if(event.event == iButtonCustomEventByteEditChanged) {
|
||||
ibutton_protocols_apply_edits(ibutton->protocols, ibutton->key);
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
// User cancelled editing, reload the key from storage
|
||||
if(scene_manager_has_previous_scene(scene_manager, iButtonSceneSavedKeyMenu)) {
|
||||
if(!ibutton_load_key(ibutton)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
scene_manager, iButtonSceneStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +55,7 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
void ibutton_scene_add_value_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
uint8_t* new_key_data =
|
||||
(uint8_t*)scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneAddValue);
|
||||
|
||||
byte_input_set_result_callback(ibutton->byte_input, NULL, NULL, NULL, NULL, 0);
|
||||
byte_input_set_header_text(ibutton->byte_input, NULL);
|
||||
free(new_key_data);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ ADD_SCENE(ibutton, info, Info)
|
||||
ADD_SCENE(ibutton, read, Read)
|
||||
ADD_SCENE(ibutton, read_key_menu, ReadKeyMenu)
|
||||
ADD_SCENE(ibutton, read_success, ReadSuccess)
|
||||
ADD_SCENE(ibutton, read_crc_error, ReadCRCError)
|
||||
ADD_SCENE(ibutton, read_not_key_error, ReadNotKeyError)
|
||||
ADD_SCENE(ibutton, read_error, ReadError)
|
||||
ADD_SCENE(ibutton, select_key, SelectKey)
|
||||
ADD_SCENE(ibutton, add_type, AddType)
|
||||
ADD_SCENE(ibutton, add_value, AddValue)
|
||||
@@ -18,4 +17,5 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm)
|
||||
ADD_SCENE(ibutton, delete_success, DeleteSuccess)
|
||||
ADD_SCENE(ibutton, retry_confirm, RetryConfirm)
|
||||
ADD_SCENE(ibutton, exit_confirm, ExitConfirm)
|
||||
ADD_SCENE(ibutton, view_data, ViewData)
|
||||
ADD_SCENE(ibutton, rpc, Rpc)
|
||||
|
||||
@@ -1,74 +1,29 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include <toolbox/path.h>
|
||||
|
||||
static void ibutton_scene_delete_confirm_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
iButton* ibutton = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void ibutton_scene_delete_confirm_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
Widget* widget = ibutton->widget;
|
||||
iButtonKey* key = ibutton->key;
|
||||
const uint8_t* key_data = ibutton_key_get_data_p(key);
|
||||
Widget* widget = ibutton->widget;
|
||||
|
||||
FuriString* key_name;
|
||||
key_name = furi_string_alloc();
|
||||
path_extract_filename(ibutton->file_path, key_name, true);
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", furi_string_get_cstr(key_name));
|
||||
widget_add_text_box_element(
|
||||
widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, true);
|
||||
widget_add_button_element(widget, GuiButtonTypeLeft, "Back", ibutton_widget_callback, context);
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeLeft, "Cancel", ibutton_scene_delete_confirm_widget_callback, ibutton);
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
GuiButtonTypeRight,
|
||||
"Delete",
|
||||
ibutton_scene_delete_confirm_widget_callback,
|
||||
ibutton);
|
||||
widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context);
|
||||
|
||||
switch(ibutton_key_get_type(key)) {
|
||||
case iButtonKeyDS1990:
|
||||
ibutton_text_store_set(
|
||||
ibutton,
|
||||
"%02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
key_data[0],
|
||||
key_data[1],
|
||||
key_data[2],
|
||||
key_data[3],
|
||||
key_data[4],
|
||||
key_data[5],
|
||||
key_data[6],
|
||||
key_data[7]);
|
||||
widget_add_string_element(
|
||||
widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Dallas");
|
||||
break;
|
||||
|
||||
case iButtonKeyCyfral:
|
||||
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
|
||||
widget_add_string_element(
|
||||
widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Cyfral");
|
||||
break;
|
||||
|
||||
case iButtonKeyMetakom:
|
||||
ibutton_text_store_set(
|
||||
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
|
||||
widget_add_string_element(
|
||||
widget, 64, 34, AlignCenter, AlignBottom, FontSecondary, "Metakom");
|
||||
break;
|
||||
}
|
||||
furi_string_printf(tmp, "Delete %s?", ibutton->key_name);
|
||||
widget_add_string_element(
|
||||
widget, 64, 46, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store);
|
||||
widget, 128 / 2, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
|
||||
|
||||
furi_string_reset(tmp);
|
||||
ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
|
||||
furi_string_free(key_name);
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -81,8 +36,10 @@ bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent even
|
||||
if(event.event == GuiButtonTypeRight) {
|
||||
if(ibutton_delete_key(ibutton)) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneDeleteSuccess);
|
||||
} else {
|
||||
dialog_message_show_storage_error(ibutton->dialogs, "Cannot delete\nkey file");
|
||||
scene_manager_previous_scene(scene_manager);
|
||||
}
|
||||
//TODO: What if the key could not be deleted?
|
||||
} else if(event.event == GuiButtonTypeLeft) {
|
||||
scene_manager_previous_scene(scene_manager);
|
||||
}
|
||||
@@ -93,6 +50,5 @@ bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent even
|
||||
|
||||
void ibutton_scene_delete_confirm_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
ibutton_text_store_clear(ibutton);
|
||||
widget_reset(ibutton->widget);
|
||||
}
|
||||
|
||||
@@ -14,61 +14,32 @@ static void ibutton_scene_emulate_callback(void* context, bool emulated) {
|
||||
|
||||
void ibutton_scene_emulate_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
Widget* widget = ibutton->widget;
|
||||
iButtonKey* key = ibutton->key;
|
||||
|
||||
const uint8_t* key_data = ibutton_key_get_data_p(key);
|
||||
Widget* widget = ibutton->widget;
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
FuriString* key_name;
|
||||
key_name = furi_string_alloc();
|
||||
if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
|
||||
path_extract_filename(ibutton->file_path, key_name, true);
|
||||
}
|
||||
widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
|
||||
|
||||
// check that stored key has name
|
||||
if(!furi_string_empty(key_name)) {
|
||||
ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
|
||||
} else {
|
||||
// if not, show key data
|
||||
switch(ibutton_key_get_type(key)) {
|
||||
case iButtonKeyDS1990:
|
||||
ibutton_text_store_set(
|
||||
ibutton,
|
||||
"%02X %02X %02X %02X\n%02X %02X %02X %02X",
|
||||
key_data[0],
|
||||
key_data[1],
|
||||
key_data[2],
|
||||
key_data[3],
|
||||
key_data[4],
|
||||
key_data[5],
|
||||
key_data[6],
|
||||
key_data[7]);
|
||||
break;
|
||||
case iButtonKeyCyfral:
|
||||
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
|
||||
break;
|
||||
case iButtonKeyMetakom:
|
||||
ibutton_text_store_set(
|
||||
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
furi_string_printf(
|
||||
tmp,
|
||||
"%s\n[%s]",
|
||||
furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name,
|
||||
ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key)));
|
||||
|
||||
widget_add_text_box_element(
|
||||
widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating");
|
||||
widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
|
||||
widget_add_text_box_element(
|
||||
widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true);
|
||||
widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating");
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
|
||||
ibutton_worker_emulate_set_callback(
|
||||
ibutton->key_worker, ibutton_scene_emulate_callback, ibutton);
|
||||
ibutton_worker_emulate_start(ibutton->key_worker, key);
|
||||
|
||||
furi_string_free(key_name);
|
||||
ibutton_worker_emulate_set_callback(ibutton->worker, ibutton_scene_emulate_callback, ibutton);
|
||||
ibutton_worker_emulate_start(ibutton->worker, key);
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -78,8 +49,7 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeTick) {
|
||||
uint32_t cnt = scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneEmulate);
|
||||
if(cnt > 0) {
|
||||
cnt--;
|
||||
if(cnt == 0) {
|
||||
if(--cnt == 0) {
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateBlink);
|
||||
}
|
||||
scene_manager_set_scene_state(ibutton->scene_manager, iButtonSceneEmulate, cnt);
|
||||
@@ -101,7 +71,7 @@ bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
void ibutton_scene_emulate_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
ibutton_worker_stop(ibutton->key_worker);
|
||||
ibutton_worker_stop(ibutton->worker);
|
||||
widget_reset(ibutton->widget);
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ void ibutton_scene_exit_confirm_on_enter(void* context) {
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeRight, "Stay", ibutton_scene_exit_confirm_widget_callback, ibutton);
|
||||
widget_add_string_element(
|
||||
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Exit to iButton menu?");
|
||||
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Exit to iButton Menu?");
|
||||
widget_add_string_element(
|
||||
widget, 64, 31, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!");
|
||||
|
||||
|
||||
@@ -1,66 +1,54 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include <toolbox/path.h>
|
||||
|
||||
void ibutton_scene_info_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
Widget* widget = ibutton->widget;
|
||||
iButtonKey* key = ibutton->key;
|
||||
Widget* widget = ibutton->widget;
|
||||
|
||||
const uint8_t* key_data = ibutton_key_get_data_p(key);
|
||||
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
|
||||
|
||||
FuriString* key_name;
|
||||
key_name = furi_string_alloc();
|
||||
path_extract_filename(ibutton->file_path, key_name, true);
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
furi_string_printf(
|
||||
tmp,
|
||||
"\e#%s [%s]\e#",
|
||||
ibutton->key_name,
|
||||
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
|
||||
|
||||
ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
|
||||
widget_add_text_box_element(
|
||||
widget, 0, 0, 128, 23, AlignCenter, AlignCenter, ibutton->text_store, true);
|
||||
widget, 0, 2, 128, 12, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true);
|
||||
|
||||
switch(ibutton_key_get_type(key)) {
|
||||
case iButtonKeyDS1990:
|
||||
ibutton_text_store_set(
|
||||
ibutton,
|
||||
"%02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
key_data[0],
|
||||
key_data[1],
|
||||
key_data[2],
|
||||
key_data[3],
|
||||
key_data[4],
|
||||
key_data[5],
|
||||
key_data[6],
|
||||
key_data[7]);
|
||||
widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Dallas");
|
||||
break;
|
||||
furi_string_reset(tmp);
|
||||
ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
|
||||
|
||||
case iButtonKeyMetakom:
|
||||
ibutton_text_store_set(
|
||||
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
|
||||
widget_add_string_element(
|
||||
widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Metakom");
|
||||
break;
|
||||
widget_add_string_multiline_element(
|
||||
widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
|
||||
|
||||
case iButtonKeyCyfral:
|
||||
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
|
||||
widget_add_string_element(widget, 64, 36, AlignCenter, AlignBottom, FontPrimary, "Cyfral");
|
||||
break;
|
||||
if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) &
|
||||
iButtonProtocolFeatureExtData) {
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context);
|
||||
}
|
||||
|
||||
widget_add_string_element(
|
||||
widget, 64, 50, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store);
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
|
||||
furi_string_free(key_name);
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
iButton* ibutton = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneViewData);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void ibutton_scene_info_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
ibutton_text_store_clear(ibutton);
|
||||
widget_reset(ibutton->widget);
|
||||
}
|
||||
|
||||
@@ -11,14 +11,13 @@ void ibutton_scene_read_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
Popup* popup = ibutton->popup;
|
||||
iButtonKey* key = ibutton->key;
|
||||
iButtonWorker* worker = ibutton->key_worker;
|
||||
iButtonWorker* worker = ibutton->worker;
|
||||
|
||||
popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom);
|
||||
popup_set_text(popup, "Waiting\nfor key ...", 95, 30, AlignCenter, AlignTop);
|
||||
popup_set_text(popup, "Apply key to\nFlipper's back", 95, 30, AlignCenter, AlignTop);
|
||||
popup_set_icon(popup, 0, 5, XTREME_ASSETS()->I_DolphinWait_61x59);
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
|
||||
furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
|
||||
|
||||
ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton);
|
||||
ibutton_worker_read_start(worker, key);
|
||||
@@ -36,25 +35,14 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == iButtonCustomEventWorkerRead) {
|
||||
bool success = false;
|
||||
iButtonKey* key = ibutton->key;
|
||||
|
||||
if(ibutton_key_get_type(key) == iButtonKeyDS1990) {
|
||||
if(!ibutton_key_dallas_crc_is_valid(key)) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneReadCRCError);
|
||||
} else if(!ibutton_key_dallas_is_1990_key(key)) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneReadNotKeyError);
|
||||
} else {
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
success = true;
|
||||
}
|
||||
|
||||
if(success) {
|
||||
if(ibutton_protocols_is_valid(ibutton->protocols, ibutton->key)) {
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageSuccess);
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneReadSuccess);
|
||||
|
||||
DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess);
|
||||
|
||||
} else {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneReadError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +53,7 @@ bool ibutton_scene_read_on_event(void* context, SceneManagerEvent event) {
|
||||
void ibutton_scene_read_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
Popup* popup = ibutton->popup;
|
||||
ibutton_worker_stop(ibutton->key_worker);
|
||||
ibutton_worker_stop(ibutton->worker);
|
||||
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
|
||||
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
|
||||
popup_set_icon(popup, 0, 0, NULL);
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include <one_wire/maxim_crc.h>
|
||||
|
||||
static void ibutton_scene_read_crc_error_dialog_ex_callback(DialogExResult result, void* context) {
|
||||
iButton* ibutton = context;
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void ibutton_scene_read_crc_error_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
DialogEx* dialog_ex = ibutton->dialog_ex;
|
||||
iButtonKey* key = ibutton->key;
|
||||
const uint8_t* key_data = ibutton_key_get_data_p(key);
|
||||
|
||||
ibutton_text_store_set(
|
||||
ibutton,
|
||||
"%02X %02X %02X %02X %02X %02X %02X %02X\nExpected CRC: %X",
|
||||
key_data[0],
|
||||
key_data[1],
|
||||
key_data[2],
|
||||
key_data[3],
|
||||
key_data[4],
|
||||
key_data[5],
|
||||
key_data[6],
|
||||
key_data[7],
|
||||
maxim_crc8(key_data, 7, MAXIM_CRC8_INIT));
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "CRC ERROR", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, ibutton->text_store, 64, 19, AlignCenter, AlignTop);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Retry");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "More");
|
||||
dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_crc_error_dialog_ex_callback);
|
||||
dialog_ex_set_context(dialog_ex, ibutton);
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx);
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageError);
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn);
|
||||
}
|
||||
|
||||
bool ibutton_scene_read_crc_error_on_event(void* context, SceneManagerEvent event) {
|
||||
iButton* ibutton = context;
|
||||
SceneManager* scene_manager = ibutton->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = true;
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == DialogExResultRight) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
|
||||
} else if(event.event == DialogExResultLeft) {
|
||||
scene_manager_previous_scene(scene_manager);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void ibutton_scene_read_crc_error_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
DialogEx* dialog_ex = ibutton->dialog_ex;
|
||||
|
||||
ibutton_text_store_clear(ibutton);
|
||||
|
||||
dialog_ex_reset(dialog_ex);
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include <one_wire/maxim_crc.h>
|
||||
|
||||
void ibutton_scene_read_error_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
iButtonKey* key = ibutton->key;
|
||||
|
||||
Widget* widget = ibutton->widget;
|
||||
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeLeft, "Retry", ibutton_widget_callback, context);
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context);
|
||||
|
||||
widget_add_string_element(
|
||||
widget, 128 / 2, 2, AlignCenter, AlignTop, FontPrimary, "Read Error");
|
||||
|
||||
ibutton_protocols_render_error(ibutton->protocols, key, tmp);
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageError);
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn);
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
bool ibutton_scene_read_error_on_event(void* context, SceneManagerEvent event) {
|
||||
iButton* ibutton = context;
|
||||
SceneManager* scene_manager = ibutton->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = true;
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
|
||||
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
scene_manager_previous_scene(scene_manager);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void ibutton_scene_read_error_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff);
|
||||
widget_reset(ibutton->widget);
|
||||
}
|
||||
@@ -4,7 +4,9 @@
|
||||
typedef enum {
|
||||
SubmenuIndexSave,
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexWrite,
|
||||
SubmenuIndexViewData,
|
||||
SubmenuIndexWriteBlank,
|
||||
SubmenuIndexWriteCopy,
|
||||
} SubmenuIndex;
|
||||
|
||||
void ibutton_scene_read_key_menu_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -16,6 +18,9 @@ void ibutton_scene_read_key_menu_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
Submenu* submenu = ibutton->submenu;
|
||||
|
||||
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(ibutton->key);
|
||||
const uint32_t features = ibutton_protocols_get_features(ibutton->protocols, protocol_id);
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "Save", SubmenuIndexSave, ibutton_scene_read_key_menu_submenu_callback, ibutton);
|
||||
submenu_add_item(
|
||||
@@ -24,36 +29,66 @@ void ibutton_scene_read_key_menu_on_enter(void* context) {
|
||||
SubmenuIndexEmulate,
|
||||
ibutton_scene_read_key_menu_submenu_callback,
|
||||
ibutton);
|
||||
if(ibutton_key_get_type(ibutton->key) == iButtonKeyDS1990) {
|
||||
|
||||
if(features & iButtonProtocolFeatureExtData) {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Write",
|
||||
SubmenuIndexWrite,
|
||||
"View Data",
|
||||
SubmenuIndexViewData,
|
||||
ibutton_scene_read_key_menu_submenu_callback,
|
||||
ibutton);
|
||||
}
|
||||
|
||||
if(features & iButtonProtocolFeatureWriteBlank) {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Write Blank",
|
||||
SubmenuIndexWriteBlank,
|
||||
ibutton_scene_read_key_menu_submenu_callback,
|
||||
ibutton);
|
||||
}
|
||||
|
||||
if(features & iButtonProtocolFeatureWriteCopy) {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Write Copy",
|
||||
SubmenuIndexWriteCopy,
|
||||
ibutton_scene_read_key_menu_submenu_callback,
|
||||
ibutton);
|
||||
}
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneReadKeyMenu));
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
|
||||
}
|
||||
|
||||
bool ibutton_scene_read_key_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
iButton* ibutton = context;
|
||||
SceneManager* scene_manager = ibutton->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(
|
||||
ibutton->scene_manager, iButtonSceneReadKeyMenu, event.event);
|
||||
scene_manager_set_scene_state(scene_manager, iButtonSceneReadKeyMenu, event.event);
|
||||
consumed = true;
|
||||
|
||||
if(event.event == SubmenuIndexSave) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveName);
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneSaveName);
|
||||
} else if(event.event == SubmenuIndexEmulate) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneEmulate);
|
||||
DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
|
||||
} else if(event.event == SubmenuIndexWrite) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite);
|
||||
} else if(event.event == SubmenuIndexViewData) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneViewData);
|
||||
} else if(event.event == SubmenuIndexWriteBlank) {
|
||||
ibutton->write_mode = iButtonWriteModeBlank;
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
|
||||
} else if(event.event == SubmenuIndexWriteCopy) {
|
||||
ibutton->write_mode = iButtonWriteModeCopy;
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
|
||||
}
|
||||
} else if(event.event == SceneManagerEventTypeBack) {
|
||||
scene_manager_set_scene_state(
|
||||
ibutton->scene_manager, iButtonSceneReadKeyMenu, SubmenuIndexSave);
|
||||
// Event is not consumed
|
||||
}
|
||||
|
||||
return consumed;
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include <one_wire/maxim_crc.h>
|
||||
|
||||
static void
|
||||
ibutton_scene_read_not_key_error_dialog_ex_callback(DialogExResult result, void* context) {
|
||||
iButton* ibutton = context;
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void ibutton_scene_read_not_key_error_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
DialogEx* dialog_ex = ibutton->dialog_ex;
|
||||
iButtonKey* key = ibutton->key;
|
||||
const uint8_t* key_data = ibutton_key_get_data_p(key);
|
||||
|
||||
ibutton_text_store_set(
|
||||
ibutton,
|
||||
"THIS IS NOT A KEY\n%02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
key_data[0],
|
||||
key_data[1],
|
||||
key_data[2],
|
||||
key_data[3],
|
||||
key_data[4],
|
||||
key_data[5],
|
||||
key_data[6],
|
||||
key_data[7],
|
||||
maxim_crc8(key_data, 7, MAXIM_CRC8_INIT));
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "CRC ERROR", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, ibutton->text_store, 64, 19, AlignCenter, AlignTop);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Retry");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "More");
|
||||
dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_not_key_error_dialog_ex_callback);
|
||||
dialog_ex_set_context(dialog_ex, ibutton);
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx);
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageError);
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn);
|
||||
}
|
||||
|
||||
bool ibutton_scene_read_not_key_error_on_event(void* context, SceneManagerEvent event) {
|
||||
iButton* ibutton = context;
|
||||
SceneManager* scene_manager = ibutton->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = true;
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == DialogExResultRight) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
|
||||
} else if(event.event == DialogExResultLeft) {
|
||||
scene_manager_previous_scene(scene_manager);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void ibutton_scene_read_not_key_error_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
DialogEx* dialog_ex = ibutton->dialog_ex;
|
||||
|
||||
ibutton_text_store_clear(ibutton);
|
||||
|
||||
dialog_ex_reset(dialog_ex);
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageRedOff);
|
||||
}
|
||||
@@ -1,55 +1,40 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
static void ibutton_scene_read_success_dialog_ex_callback(DialogExResult result, void* context) {
|
||||
iButton* ibutton = context;
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, result);
|
||||
}
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void ibutton_scene_read_success_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
DialogEx* dialog_ex = ibutton->dialog_ex;
|
||||
iButtonKey* key = ibutton->key;
|
||||
const uint8_t* key_data = ibutton_key_get_data_p(key);
|
||||
Widget* widget = ibutton->widget;
|
||||
|
||||
switch(ibutton_key_get_type(key)) {
|
||||
case iButtonKeyDS1990:
|
||||
ibutton_text_store_set(
|
||||
ibutton,
|
||||
"Dallas\n%02X %02X %02X %02X\n%02X %02X %02X %02X",
|
||||
key_data[0],
|
||||
key_data[1],
|
||||
key_data[2],
|
||||
key_data[3],
|
||||
key_data[4],
|
||||
key_data[5],
|
||||
key_data[6],
|
||||
key_data[7]);
|
||||
break;
|
||||
case iButtonKeyCyfral:
|
||||
ibutton_text_store_set(ibutton, "Cyfral\n%02X %02X", key_data[0], key_data[1]);
|
||||
break;
|
||||
case iButtonKeyMetakom:
|
||||
ibutton_text_store_set(
|
||||
ibutton,
|
||||
"Metakom\n%02X %02X %02X %02X",
|
||||
key_data[0],
|
||||
key_data[1],
|
||||
key_data[2],
|
||||
key_data[3]);
|
||||
break;
|
||||
}
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
dialog_ex_set_text(dialog_ex, ibutton->text_store, 95, 30, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Retry");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "More");
|
||||
dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63);
|
||||
dialog_ex_set_result_callback(dialog_ex, ibutton_scene_read_success_dialog_ex_callback);
|
||||
dialog_ex_set_context(dialog_ex, ibutton);
|
||||
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewDialogEx);
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeLeft, "Retry", ibutton_widget_callback, context);
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context);
|
||||
|
||||
furi_string_printf(
|
||||
tmp,
|
||||
"%s[%s]",
|
||||
ibutton_protocols_get_name(ibutton->protocols, protocol_id),
|
||||
ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id));
|
||||
|
||||
widget_add_string_element(
|
||||
widget, 0, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
|
||||
|
||||
furi_string_reset(tmp);
|
||||
ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOn);
|
||||
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -62,9 +47,9 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event)
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm);
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == DialogExResultRight) {
|
||||
if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneReadKeyMenu);
|
||||
} else if(event.event == DialogExResultLeft) {
|
||||
} else if(event.event == GuiButtonTypeLeft) {
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneRetryConfirm);
|
||||
}
|
||||
}
|
||||
@@ -74,11 +59,8 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event)
|
||||
|
||||
void ibutton_scene_read_success_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
DialogEx* dialog_ex = ibutton->dialog_ex;
|
||||
|
||||
ibutton_text_store_clear(ibutton);
|
||||
|
||||
dialog_ex_reset(dialog_ex);
|
||||
widget_reset(ibutton->widget);
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageGreenOff);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ void ibutton_scene_retry_confirm_on_enter(void* context) {
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeRight, "Stay", ibutton_scene_retry_confirm_widget_callback, ibutton);
|
||||
widget_add_string_element(
|
||||
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Return to reading?");
|
||||
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?");
|
||||
widget_add_string_element(
|
||||
widget, 64, 29, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!");
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include <toolbox/path.h>
|
||||
#include <rpc/rpc_app.h>
|
||||
|
||||
void ibutton_scene_rpc_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
@@ -17,8 +15,6 @@ void ibutton_scene_rpc_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
iButton* ibutton = context;
|
||||
Popup* popup = ibutton->popup;
|
||||
|
||||
@@ -26,40 +22,32 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == iButtonCustomEventRpcLoad) {
|
||||
const char* arg = rpc_system_app_get_data(ibutton->rpc_ctx);
|
||||
bool result = false;
|
||||
if(arg && (furi_string_empty(ibutton->file_path))) {
|
||||
furi_string_set(ibutton->file_path, arg);
|
||||
if(ibutton_load_key_data(ibutton, ibutton->file_path, false)) {
|
||||
ibutton_worker_emulate_start(ibutton->key_worker, ibutton->key);
|
||||
FuriString* key_name;
|
||||
key_name = furi_string_alloc();
|
||||
if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
|
||||
path_extract_filename(ibutton->file_path, key_name, true);
|
||||
}
|
||||
|
||||
if(!furi_string_empty(key_name)) {
|
||||
ibutton_text_store_set(
|
||||
ibutton, "emulating\n%s", furi_string_get_cstr(key_name));
|
||||
} else {
|
||||
ibutton_text_store_set(ibutton, "emulating");
|
||||
}
|
||||
popup_set_text(popup, ibutton->text_store, 82, 32, AlignCenter, AlignTop);
|
||||
if(event.event == iButtonCustomEventRpcLoad) {
|
||||
bool result = false;
|
||||
const char* file_path = rpc_system_app_get_data(ibutton->rpc);
|
||||
|
||||
if(file_path && (furi_string_empty(ibutton->file_path))) {
|
||||
furi_string_set(ibutton->file_path, file_path);
|
||||
|
||||
if(ibutton_load_key(ibutton)) {
|
||||
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
|
||||
ibutton_worker_emulate_start(ibutton->worker, ibutton->key);
|
||||
|
||||
furi_string_free(key_name);
|
||||
result = true;
|
||||
} else {
|
||||
furi_string_reset(ibutton->file_path);
|
||||
}
|
||||
}
|
||||
rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventLoadFile, result);
|
||||
|
||||
rpc_system_app_confirm(ibutton->rpc, RpcAppEventLoadFile, result);
|
||||
|
||||
} else if(event.event == iButtonCustomEventRpcExit) {
|
||||
rpc_system_app_confirm(ibutton->rpc_ctx, RpcAppEventAppExit, true);
|
||||
rpc_system_app_confirm(ibutton->rpc, RpcAppEventAppExit, true);
|
||||
scene_manager_stop(ibutton->scene_manager);
|
||||
view_dispatcher_stop(ibutton->view_dispatcher);
|
||||
|
||||
} else if(event.event == iButtonCustomEventRpcSessionClose) {
|
||||
scene_manager_stop(ibutton->scene_manager);
|
||||
view_dispatcher_stop(ibutton->view_dispatcher);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include <lib/toolbox/random_name.h>
|
||||
|
||||
#include <toolbox/random_name.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
static void ibutton_scene_save_name_text_input_callback(void* context) {
|
||||
@@ -12,17 +14,10 @@ void ibutton_scene_save_name_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
TextInput* text_input = ibutton->text_input;
|
||||
|
||||
FuriString* key_name;
|
||||
key_name = furi_string_alloc();
|
||||
if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
|
||||
path_extract_filename(ibutton->file_path, key_name, true);
|
||||
}
|
||||
const bool is_new_file = furi_string_empty(ibutton->file_path);
|
||||
|
||||
const bool key_name_is_empty = furi_string_empty(key_name);
|
||||
if(key_name_is_empty) {
|
||||
set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE);
|
||||
} else {
|
||||
ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
|
||||
if(is_new_file) {
|
||||
set_random_name(ibutton->key_name, IBUTTON_KEY_NAME_SIZE);
|
||||
}
|
||||
|
||||
text_input_set_header_text(text_input, "Name the key");
|
||||
@@ -30,23 +25,15 @@ void ibutton_scene_save_name_on_enter(void* context) {
|
||||
text_input,
|
||||
ibutton_scene_save_name_text_input_callback,
|
||||
ibutton,
|
||||
ibutton->text_store,
|
||||
ibutton->key_name,
|
||||
IBUTTON_KEY_NAME_SIZE,
|
||||
key_name_is_empty);
|
||||
is_new_file);
|
||||
|
||||
FuriString* folder_path;
|
||||
folder_path = furi_string_alloc();
|
||||
|
||||
path_extract_dirname(furi_string_get_cstr(ibutton->file_path), folder_path);
|
||||
|
||||
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
|
||||
furi_string_get_cstr(folder_path), IBUTTON_APP_EXTENSION, furi_string_get_cstr(key_name));
|
||||
ValidatorIsFile* validator_is_file =
|
||||
validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, ibutton->key_name);
|
||||
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput);
|
||||
|
||||
furi_string_free(key_name);
|
||||
furi_string_free(folder_path);
|
||||
}
|
||||
|
||||
bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -56,8 +43,16 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == iButtonCustomEventTextEditResult) {
|
||||
if(ibutton_save_key(ibutton, ibutton->text_store)) {
|
||||
furi_string_printf(
|
||||
ibutton->file_path,
|
||||
"%s/%s%s",
|
||||
IBUTTON_APP_FOLDER,
|
||||
ibutton->key_name,
|
||||
IBUTTON_APP_EXTENSION);
|
||||
|
||||
if(ibutton_save_key(ibutton)) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
|
||||
|
||||
if(scene_manager_has_previous_scene(
|
||||
ibutton->scene_manager, iButtonSceneSavedKeyMenu)) {
|
||||
// Nothing, do not count editing as saving
|
||||
@@ -67,6 +62,7 @@ bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
} else {
|
||||
DOLPHIN_DEED(DolphinDeedIbuttonSave);
|
||||
}
|
||||
|
||||
} else {
|
||||
const uint32_t possible_scenes[] = {
|
||||
iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType};
|
||||
|
||||
@@ -3,72 +3,70 @@
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexWrite,
|
||||
SubmenuIndexWriteBlank,
|
||||
SubmenuIndexWriteCopy,
|
||||
SubmenuIndexEdit,
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexInfo,
|
||||
};
|
||||
|
||||
void ibutton_scene_saved_key_menu_submenu_callback(void* context, uint32_t index) {
|
||||
iButton* ibutton = context;
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void ibutton_scene_saved_key_menu_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
Submenu* submenu = ibutton->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Emulate",
|
||||
SubmenuIndexEmulate,
|
||||
ibutton_scene_saved_key_menu_submenu_callback,
|
||||
ibutton);
|
||||
if(ibutton_key_get_type(ibutton->key) == iButtonKeyDS1990) {
|
||||
const uint32_t features = ibutton_protocols_get_features(
|
||||
ibutton->protocols, ibutton_key_get_protocol_id(ibutton->key));
|
||||
|
||||
submenu_add_item(submenu, "Emulate", SubmenuIndexEmulate, ibutton_submenu_callback, ibutton);
|
||||
|
||||
if(features & iButtonProtocolFeatureWriteBlank) {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Write",
|
||||
SubmenuIndexWrite,
|
||||
ibutton_scene_saved_key_menu_submenu_callback,
|
||||
ibutton);
|
||||
submenu, "Write Blank", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton);
|
||||
}
|
||||
submenu_add_item(
|
||||
submenu, "Edit", SubmenuIndexEdit, ibutton_scene_saved_key_menu_submenu_callback, ibutton);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Delete",
|
||||
SubmenuIndexDelete,
|
||||
ibutton_scene_saved_key_menu_submenu_callback,
|
||||
ibutton);
|
||||
submenu_add_item(
|
||||
submenu, "Info", SubmenuIndexInfo, ibutton_scene_saved_key_menu_submenu_callback, ibutton);
|
||||
|
||||
if(features & iButtonProtocolFeatureWriteCopy) {
|
||||
submenu_add_item(
|
||||
submenu, "Write Copy", SubmenuIndexWriteCopy, ibutton_submenu_callback, ibutton);
|
||||
}
|
||||
|
||||
submenu_add_item(submenu, "Edit", SubmenuIndexEdit, ibutton_submenu_callback, ibutton);
|
||||
submenu_add_item(submenu, "Delete", SubmenuIndexDelete, ibutton_submenu_callback, ibutton);
|
||||
submenu_add_item(submenu, "Info", SubmenuIndexInfo, ibutton_submenu_callback, ibutton);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneSavedKeyMenu));
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewSubmenu);
|
||||
}
|
||||
|
||||
bool ibutton_scene_saved_key_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
iButton* ibutton = context;
|
||||
SceneManager* scene_manager = ibutton->scene_manager;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(
|
||||
ibutton->scene_manager, iButtonSceneSavedKeyMenu, event.event);
|
||||
scene_manager_set_scene_state(scene_manager, iButtonSceneSavedKeyMenu, event.event);
|
||||
consumed = true;
|
||||
if(event.event == SubmenuIndexEmulate) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate);
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneEmulate);
|
||||
DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
|
||||
} else if(event.event == SubmenuIndexWrite) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneWrite);
|
||||
} else if(event.event == SubmenuIndexWriteBlank) {
|
||||
ibutton->write_mode = iButtonWriteModeBlank;
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
|
||||
} else if(event.event == SubmenuIndexWriteCopy) {
|
||||
ibutton->write_mode = iButtonWriteModeCopy;
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneWrite);
|
||||
} else if(event.event == SubmenuIndexEdit) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue);
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneAddValue);
|
||||
} else if(event.event == SubmenuIndexDelete) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneDeleteConfirm);
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneDeleteConfirm);
|
||||
} else if(event.event == SubmenuIndexInfo) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneInfo);
|
||||
scene_manager_next_scene(scene_manager, iButtonSceneInfo);
|
||||
}
|
||||
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_set_scene_state(
|
||||
scene_manager, iButtonSceneSavedKeyMenu, SubmenuIndexEmulate);
|
||||
// Event is not consumed
|
||||
}
|
||||
|
||||
return consumed;
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
void ibutton_scene_select_key_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
|
||||
if(!ibutton_file_select(ibutton)) {
|
||||
if(ibutton_select_and_load_key(ibutton)) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSavedKeyMenu);
|
||||
} else {
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
ibutton->scene_manager, iButtonSceneStart);
|
||||
} else {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSavedKeyMenu);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,21 +8,15 @@ enum SubmenuIndex {
|
||||
SubmenuIndexAdd,
|
||||
};
|
||||
|
||||
void ibutton_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||
iButton* ibutton = context;
|
||||
view_dispatcher_send_custom_event(ibutton->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void ibutton_scene_start_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
Submenu* submenu = ibutton->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "Read", SubmenuIndexRead, ibutton_scene_start_submenu_callback, ibutton);
|
||||
submenu_add_item(
|
||||
submenu, "Saved", SubmenuIndexSaved, ibutton_scene_start_submenu_callback, ibutton);
|
||||
submenu_add_item(
|
||||
submenu, "Add Manually", SubmenuIndexAdd, ibutton_scene_start_submenu_callback, ibutton);
|
||||
ibutton_reset_key(ibutton);
|
||||
|
||||
submenu_add_item(submenu, "Read", SubmenuIndexRead, ibutton_submenu_callback, ibutton);
|
||||
submenu_add_item(submenu, "Saved", SubmenuIndexSaved, ibutton_submenu_callback, ibutton);
|
||||
submenu_add_item(submenu, "Add Manually", SubmenuIndexAdd, ibutton_submenu_callback, ibutton);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(ibutton->scene_manager, iButtonSceneStart));
|
||||
@@ -39,9 +33,8 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
consumed = true;
|
||||
if(event.event == SubmenuIndexRead) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead);
|
||||
//DOLPHIN_DEED(DolphinDeedIbuttonRead);
|
||||
DOLPHIN_DEED(DolphinDeedIbuttonRead);
|
||||
} else if(event.event == SubmenuIndexSaved) {
|
||||
furi_string_set(ibutton->file_path, IBUTTON_APP_FOLDER);
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey);
|
||||
} else if(event.event == SubmenuIndexAdd) {
|
||||
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "../ibutton_i.h"
|
||||
|
||||
void ibutton_scene_view_data_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
iButtonKey* key = ibutton->key;
|
||||
Widget* widget = ibutton->widget;
|
||||
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
ibutton_protocols_render_data(ibutton->protocols, key, tmp);
|
||||
|
||||
widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(tmp));
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
bool ibutton_scene_view_data_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void ibutton_scene_view_data_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
widget_reset(ibutton->widget);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "../ibutton_i.h"
|
||||
#include "toolbox/path.h"
|
||||
|
||||
typedef enum {
|
||||
iButtonSceneWriteStateDefault,
|
||||
@@ -13,61 +12,46 @@ static void ibutton_scene_write_callback(void* context, iButtonWorkerWriteResult
|
||||
|
||||
void ibutton_scene_write_on_enter(void* context) {
|
||||
iButton* ibutton = context;
|
||||
furi_assert(ibutton->write_mode != iButtonWriteModeInvalid);
|
||||
|
||||
iButtonKey* key = ibutton->key;
|
||||
iButtonWorker* worker = ibutton->worker;
|
||||
const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key);
|
||||
|
||||
Widget* widget = ibutton->widget;
|
||||
iButtonWorker* worker = ibutton->key_worker;
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
const uint8_t* key_data = ibutton_key_get_data_p(key);
|
||||
widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
|
||||
|
||||
FuriString* key_name;
|
||||
key_name = furi_string_alloc();
|
||||
if(furi_string_end_with(ibutton->file_path, IBUTTON_APP_EXTENSION)) {
|
||||
path_extract_filename(ibutton->file_path, key_name, true);
|
||||
}
|
||||
furi_string_printf(
|
||||
tmp,
|
||||
"%s\n[%s]",
|
||||
ibutton->key_name,
|
||||
ibutton_protocols_get_name(ibutton->protocols, protocol_id));
|
||||
|
||||
// check that stored key has name
|
||||
if(!furi_string_empty(key_name)) {
|
||||
ibutton_text_store_set(ibutton, "%s", furi_string_get_cstr(key_name));
|
||||
} else {
|
||||
// if not, show key data
|
||||
switch(ibutton_key_get_type(key)) {
|
||||
case iButtonKeyDS1990:
|
||||
ibutton_text_store_set(
|
||||
ibutton,
|
||||
"%02X %02X %02X %02X\n%02X %02X %02X %02X",
|
||||
key_data[0],
|
||||
key_data[1],
|
||||
key_data[2],
|
||||
key_data[3],
|
||||
key_data[4],
|
||||
key_data[5],
|
||||
key_data[6],
|
||||
key_data[7]);
|
||||
break;
|
||||
case iButtonKeyCyfral:
|
||||
ibutton_text_store_set(ibutton, "%02X %02X", key_data[0], key_data[1]);
|
||||
break;
|
||||
case iButtonKeyMetakom:
|
||||
ibutton_text_store_set(
|
||||
ibutton, "%02X %02X %02X %02X", key_data[0], key_data[1], key_data[2], key_data[3]);
|
||||
break;
|
||||
}
|
||||
widget_add_text_box_element(
|
||||
widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true);
|
||||
|
||||
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
|
||||
|
||||
furi_string_set(tmp, "iButton\nwriting ");
|
||||
|
||||
if(ibutton->write_mode == iButtonWriteModeBlank) {
|
||||
furi_string_cat(tmp, "Blank");
|
||||
ibutton_worker_write_blank_start(worker, key);
|
||||
|
||||
} else if(ibutton->write_mode == iButtonWriteModeCopy) {
|
||||
furi_string_cat(tmp, "Copy");
|
||||
ibutton_worker_write_copy_start(worker, key);
|
||||
}
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
widget, 90, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nwriting");
|
||||
widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44);
|
||||
widget_add_text_box_element(
|
||||
widget, 54, 39, 75, 22, AlignCenter, AlignCenter, ibutton->text_store, true);
|
||||
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
|
||||
ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton);
|
||||
ibutton_worker_write_start(worker, key);
|
||||
|
||||
furi_string_free(key_name);
|
||||
widget, 88, 10, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageEmulateStart);
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
|
||||
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -94,7 +78,9 @@ bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
void ibutton_scene_write_on_exit(void* context) {
|
||||
iButton* ibutton = context;
|
||||
ibutton_worker_stop(ibutton->key_worker);
|
||||
ibutton->write_mode = iButtonWriteModeInvalid;
|
||||
|
||||
ibutton_worker_stop(ibutton->worker);
|
||||
widget_reset(ibutton->widget);
|
||||
|
||||
ibutton_notification_message(ibutton, iButtonNotificationMessageBlinkStop);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <string.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define INFRARED_TX_MIN_INTERVAL_MS 50U
|
||||
|
||||
static const NotificationSequence* infrared_notification_sequences[] = {
|
||||
&sequence_success,
|
||||
&sequence_set_only_green_255,
|
||||
@@ -148,6 +150,12 @@ static Infrared* infrared_alloc() {
|
||||
view_dispatcher_add_view(
|
||||
view_dispatcher, InfraredViewTextInput, text_input_get_view(infrared->text_input));
|
||||
|
||||
infrared->variable_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
infrared->view_dispatcher,
|
||||
InfraredViewVariableItemList,
|
||||
variable_item_list_get_view(infrared->variable_item_list));
|
||||
|
||||
infrared->dialog_ex = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
view_dispatcher, InfraredViewDialogEx, dialog_ex_get_view(infrared->dialog_ex));
|
||||
@@ -195,6 +203,9 @@ static void infrared_free(Infrared* infrared) {
|
||||
view_dispatcher_remove_view(view_dispatcher, InfraredViewTextInput);
|
||||
text_input_free(infrared->text_input);
|
||||
|
||||
view_dispatcher_remove_view(infrared->view_dispatcher, InfraredViewVariableItemList);
|
||||
variable_item_list_free(infrared->variable_item_list);
|
||||
|
||||
view_dispatcher_remove_view(view_dispatcher, InfraredViewDialogEx);
|
||||
dialog_ex_free(infrared->dialog_ex);
|
||||
|
||||
@@ -299,10 +310,13 @@ bool infrared_rename_current_remote(Infrared* infrared, const char* name) {
|
||||
|
||||
void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
|
||||
if(infrared->app_state.is_transmitting) {
|
||||
FURI_LOG_D(INFRARED_LOG_TAG, "Transmitter is already active");
|
||||
return;
|
||||
} else {
|
||||
infrared->app_state.is_transmitting = true;
|
||||
}
|
||||
|
||||
const uint32_t time_elapsed = furi_get_tick() - infrared->app_state.last_transmit_time;
|
||||
|
||||
if(time_elapsed < INFRARED_TX_MIN_INTERVAL_MS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(infrared_signal_is_raw(signal)) {
|
||||
@@ -319,6 +333,8 @@ void infrared_tx_start_signal(Infrared* infrared, InfraredSignal* signal) {
|
||||
infrared_worker_tx_set_get_signal_callback(
|
||||
infrared->worker, infrared_worker_tx_get_signal_steady_callback, infrared);
|
||||
infrared_worker_tx_start(infrared->worker);
|
||||
|
||||
infrared->app_state.is_transmitting = true;
|
||||
}
|
||||
|
||||
void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
|
||||
@@ -328,26 +344,24 @@ void infrared_tx_start_button_index(Infrared* infrared, size_t button_index) {
|
||||
InfraredSignal* signal = infrared_remote_button_get_signal(button);
|
||||
|
||||
infrared_tx_start_signal(infrared, signal);
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
|
||||
}
|
||||
|
||||
void infrared_tx_start_received(Infrared* infrared) {
|
||||
infrared_tx_start_signal(infrared, infrared->received_signal);
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
|
||||
}
|
||||
|
||||
void infrared_tx_stop(Infrared* infrared) {
|
||||
if(!infrared->app_state.is_transmitting) {
|
||||
FURI_LOG_D(INFRARED_LOG_TAG, "Transmitter is already stopped");
|
||||
return;
|
||||
} else {
|
||||
infrared->app_state.is_transmitting = false;
|
||||
}
|
||||
|
||||
infrared_worker_tx_stop(infrared->worker);
|
||||
infrared_worker_tx_set_get_signal_callback(infrared->worker, NULL, NULL);
|
||||
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
|
||||
|
||||
infrared->app_state.is_transmitting = false;
|
||||
infrared->app_state.last_transmit_time = furi_get_tick();
|
||||
}
|
||||
|
||||
void infrared_text_store_set(Infrared* infrared, uint32_t bank, const char* text, ...) {
|
||||
|
||||
@@ -86,7 +86,7 @@ static void infrared_cli_print_usage(void) {
|
||||
printf("\tir universal <remote_name> <signal_name>\r\n");
|
||||
printf("\tir universal list <remote_name>\r\n");
|
||||
// TODO: Do not hardcode universal remote names
|
||||
printf("\tAvailable universal remotes: tv audio ac\r\n");
|
||||
printf("\tAvailable universal remotes: tv audio ac projector\r\n");
|
||||
}
|
||||
|
||||
static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_stack.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
@@ -9,6 +10,7 @@
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/button_menu.h>
|
||||
@@ -32,8 +34,6 @@
|
||||
|
||||
#include "rpc/rpc_app.h"
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define INFRARED_FILE_NAME_SIZE 100
|
||||
#define INFRARED_TEXT_STORE_NUM 2
|
||||
#define INFRARED_TEXT_STORE_SIZE 128
|
||||
@@ -70,6 +70,7 @@ typedef struct {
|
||||
InfraredEditTarget edit_target : 8;
|
||||
InfraredEditMode edit_mode : 8;
|
||||
int32_t current_button_index;
|
||||
uint32_t last_transmit_time;
|
||||
} InfraredAppState;
|
||||
|
||||
struct Infrared {
|
||||
@@ -87,6 +88,7 @@ struct Infrared {
|
||||
|
||||
Submenu* submenu;
|
||||
TextInput* text_input;
|
||||
VariableItemList* variable_item_list;
|
||||
DialogEx* dialog_ex;
|
||||
ButtonMenu* button_menu;
|
||||
Popup* popup;
|
||||
@@ -108,6 +110,7 @@ struct Infrared {
|
||||
typedef enum {
|
||||
InfraredViewSubmenu,
|
||||
InfraredViewTextInput,
|
||||
InfraredViewVariableItemList,
|
||||
InfraredViewDialogEx,
|
||||
InfraredViewButtonMenu,
|
||||
InfraredViewPopup,
|
||||
|
||||
@@ -10,13 +10,13 @@ void infrared_scene_ask_back_on_enter(void* context) {
|
||||
DialogEx* dialog_ex = infrared->dialog_ex;
|
||||
|
||||
if(infrared->app_state.is_learning_new_remote) {
|
||||
dialog_ex_set_header(dialog_ex, "Exit to Infrared Menu?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_header(dialog_ex, "Exit to Infrared Menu?", 64, 11, AlignCenter, AlignTop);
|
||||
} else {
|
||||
dialog_ex_set_header(dialog_ex, "Exit to Remote Menu?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_header(dialog_ex, "Exit to Remote Menu?", 64, 11, AlignCenter, AlignTop);
|
||||
}
|
||||
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, "All unsaved data\nwill be lost!", 64, 31, AlignCenter, AlignCenter);
|
||||
dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Exit");
|
||||
dialog_ex_set_center_button_text(dialog_ex, NULL);
|
||||
|
||||
@@ -9,9 +9,9 @@ void infrared_scene_ask_retry_on_enter(void* context) {
|
||||
Infrared* infrared = context;
|
||||
DialogEx* dialog_ex = infrared->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "Return to Reading?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_header(dialog_ex, "Retry Reading?", 64, 11, AlignCenter, AlignTop);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, "All unsaved data\nwill be lost!", 64, 31, AlignCenter, AlignCenter);
|
||||
dialog_ex, "All unsaved data\nwill be lost!", 64, 25, AlignCenter, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Exit");
|
||||
dialog_ex_set_center_button_text(dialog_ex, NULL);
|
||||
|
||||
@@ -21,4 +21,5 @@ ADD_SCENE(infrared, universal_audio, UniversalAudio)
|
||||
ADD_SCENE(infrared, universal_projector, UniversalProjector)
|
||||
ADD_SCENE(infrared, debug, Debug)
|
||||
ADD_SCENE(infrared, error_databases, ErrorDatabases)
|
||||
ADD_SCENE(infrared, debug_settings, DebugSettings)
|
||||
ADD_SCENE(infrared, rpc, Rpc)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "../infrared_i.h"
|
||||
#include <furi_hal_infrared.h>
|
||||
|
||||
uint8_t value_index_ir;
|
||||
|
||||
#define DEB_PINS_COUNT (sizeof(infrared_debug_cfg_variables_text) / sizeof(char* const))
|
||||
const char* const infrared_debug_cfg_variables_text[] = {
|
||||
"Internal",
|
||||
"2 (A7)",
|
||||
};
|
||||
|
||||
static void infrared_scene_debug_settings_changed(VariableItem* item) {
|
||||
Infrared* infrared = variable_item_get_context(item);
|
||||
value_index_ir = variable_item_get_current_value_index(item);
|
||||
UNUSED(infrared);
|
||||
|
||||
variable_item_set_current_value_text(item, infrared_debug_cfg_variables_text[value_index_ir]);
|
||||
|
||||
furi_hal_infrared_set_debug_out(value_index_ir);
|
||||
}
|
||||
static void infrared_debug_settings_start_var_list_enter_callback(void* context, uint32_t index) {
|
||||
Infrared* infrared = context;
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void infrared_scene_debug_settings_on_enter(void* context) {
|
||||
Infrared* infrared = context;
|
||||
|
||||
VariableItemList* variable_item_list = infrared->variable_item_list;
|
||||
|
||||
value_index_ir = furi_hal_infrared_get_debug_out_status();
|
||||
VariableItem* item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Send signal to",
|
||||
DEB_PINS_COUNT,
|
||||
infrared_scene_debug_settings_changed,
|
||||
infrared);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
variable_item_list, infrared_debug_settings_start_var_list_enter_callback, infrared);
|
||||
|
||||
variable_item_set_current_value_index(item, value_index_ir);
|
||||
variable_item_set_current_value_text(item, infrared_debug_cfg_variables_text[value_index_ir]);
|
||||
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewVariableItemList);
|
||||
}
|
||||
|
||||
bool infrared_scene_debug_settings_on_event(void* context, SceneManagerEvent event) {
|
||||
Infrared* infrared = context;
|
||||
UNUSED(infrared);
|
||||
UNUSED(event);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void infrared_scene_debug_settings_on_exit(void* context) {
|
||||
Infrared* infrared = context;
|
||||
variable_item_list_reset(infrared->variable_item_list);
|
||||
}
|
||||
@@ -5,7 +5,8 @@ enum SubmenuIndex {
|
||||
SubmenuIndexLearnNewRemote,
|
||||
SubmenuIndexLearnNewRemoteRaw,
|
||||
SubmenuIndexSavedRemotes,
|
||||
SubmenuIndexDebug
|
||||
SubmenuIndexDebug,
|
||||
SubmenuIndexDebugSettings
|
||||
};
|
||||
|
||||
static void infrared_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -45,7 +46,17 @@ void infrared_scene_start_on_enter(void* context) {
|
||||
infrared_scene_start_submenu_callback,
|
||||
infrared);
|
||||
submenu_add_item(
|
||||
submenu, "Debug", SubmenuIndexDebug, infrared_scene_start_submenu_callback, infrared);
|
||||
submenu,
|
||||
"Debug RX",
|
||||
SubmenuIndexDebug,
|
||||
infrared_scene_start_submenu_callback,
|
||||
infrared);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Debug Settings",
|
||||
SubmenuIndexDebugSettings,
|
||||
infrared_scene_start_submenu_callback,
|
||||
infrared);
|
||||
}
|
||||
|
||||
const uint32_t submenu_index =
|
||||
@@ -85,6 +96,9 @@ bool infrared_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(submenu_index == SubmenuIndexDebug) {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneDebug);
|
||||
consumed = true;
|
||||
} else if(submenu_index == SubmenuIndexDebugSettings) {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneDebugSettings);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,8 @@ void infrared_scene_universal_on_enter(void* context) {
|
||||
infrared_scene_universal_submenu_callback,
|
||||
context);
|
||||
|
||||
submenu_set_selected_item(submenu, 0);
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneUniversal));
|
||||
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
|
||||
}
|
||||
@@ -79,6 +80,7 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneUniversalAC);
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(scene_manager, InfraredSceneUniversal, event.event);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Anonumous instance */
|
||||
/** Anonymous instance */
|
||||
typedef struct InfraredProgressView InfraredProgressView;
|
||||
|
||||
/** Callback for back button handling */
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <assets_icons.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <cli/cli.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
@@ -32,8 +34,6 @@
|
||||
#include <lfrfid/lfrfid_worker.h>
|
||||
|
||||
#include <lfrfid/scenes/lfrfid_scene.h>
|
||||
#include <assets_icons.h>
|
||||
// #include <lfrfid_icons.h>
|
||||
|
||||
#define LFRFID_KEY_NAME_SIZE 22
|
||||
#define LFRFID_TEXT_STORE_SIZE 40
|
||||
|
||||
@@ -16,7 +16,7 @@ static void lfrfid_clear_t5577_password_and_config_to_EM(LfRfid* app) {
|
||||
Popup* popup = app->popup;
|
||||
char curr_buf[32] = {};
|
||||
|
||||
//TODO: use .txt file in resourses for passwords.
|
||||
//TODO: use .txt file in resources for passwords.
|
||||
const uint32_t default_passwords[] = {
|
||||
0x51243648, 0x000D8787, 0x19920427, 0x50524F58, 0xF9DCEBA0, 0x65857569, 0x05D73B9F,
|
||||
0x89A69E60, 0x314159E0, 0xAA55BBBB, 0xA5B4C3D2, 0x1C0B5848, 0x00434343, 0x444E4752,
|
||||
|
||||
@@ -20,7 +20,7 @@ void lfrfid_scene_extra_actions_on_enter(void* context) {
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Read ASK (Animal, FDX)",
|
||||
"Read ASK (FDX,Regular)",
|
||||
SubmenuIndexASK,
|
||||
lfrfid_scene_extra_actions_submenu_callback,
|
||||
app);
|
||||
|
||||
@@ -7,7 +7,7 @@ void lfrfid_scene_retry_confirm_on_enter(void* context) {
|
||||
widget_add_button_element(widget, GuiButtonTypeLeft, "Exit", lfrfid_widget_callback, app);
|
||||
widget_add_button_element(widget, GuiButtonTypeRight, "Stay", lfrfid_widget_callback, app);
|
||||
widget_add_string_element(
|
||||
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Return to reading?");
|
||||
widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?");
|
||||
widget_add_string_element(
|
||||
widget, 64, 29, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!");
|
||||
|
||||
|
||||
@@ -47,21 +47,28 @@ bool lfrfid_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexRead) {
|
||||
scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, SubmenuIndexRead);
|
||||
scene_manager_next_scene(app->scene_manager, LfRfidSceneRead);
|
||||
DOLPHIN_DEED(DolphinDeedRfidRead);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexSaved) {
|
||||
// Like in the other apps, explicitly save the scene state
|
||||
// in each branch in case the user cancels loading a file.
|
||||
scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, SubmenuIndexSaved);
|
||||
furi_string_set(app->file_path, LFRFID_APP_FOLDER);
|
||||
scene_manager_next_scene(app->scene_manager, LfRfidSceneSelectKey);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexAddManually) {
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, LfRfidSceneStart, SubmenuIndexAddManually);
|
||||
scene_manager_next_scene(app->scene_manager, LfRfidSceneSaveType);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexExtraActions) {
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, LfRfidSceneStart, SubmenuIndexExtraActions);
|
||||
scene_manager_next_scene(app->scene_manager, LfRfidSceneExtraActions);
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(app->scene_manager, LfRfidSceneStart, event.event);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* @param aid - AID number array
|
||||
* @param aid_len - AID length
|
||||
* @param aid_name - string to keep AID name
|
||||
* @return - true if AID found, false otherwies
|
||||
* @return - true if AID found, false otherwise
|
||||
*/
|
||||
bool nfc_emv_parser_get_aid_name(
|
||||
Storage* storage,
|
||||
@@ -21,7 +21,7 @@ bool nfc_emv_parser_get_aid_name(
|
||||
* @param storage Storage instance
|
||||
* @param country_code - ISO 3166 country code
|
||||
* @param country_name - string to keep country name
|
||||
* @return - true if country found, false otherwies
|
||||
* @return - true if country found, false otherwise
|
||||
*/
|
||||
bool nfc_emv_parser_get_country_name(
|
||||
Storage* storage,
|
||||
@@ -32,7 +32,7 @@ bool nfc_emv_parser_get_country_name(
|
||||
* @param storage Storage instance
|
||||
* @param currency_code - ISO 3166 currency code
|
||||
* @param currency_name - string to keep currency name
|
||||
* @return - true if currency found, false otherwies
|
||||
* @return - true if currency found, false otherwise
|
||||
*/
|
||||
bool nfc_emv_parser_get_currency_name(
|
||||
Storage* storage,
|
||||
|
||||
@@ -32,7 +32,7 @@ static void nfc_cli_detect(Cli* cli, FuriString* args) {
|
||||
while(!cmd_exit) {
|
||||
cmd_exit |= cli_cmd_interrupt_received(cli);
|
||||
if(furi_hal_nfc_detect(&dev_data, 400)) {
|
||||
printf("found: %s ", nfc_get_dev_type(dev_data.type));
|
||||
printf("Found: %s ", nfc_get_dev_type(dev_data.type));
|
||||
printf("UID length: %d, UID:", dev_data.uid_len);
|
||||
for(size_t i = 0; i < dev_data.uid_len; i++) {
|
||||
printf("%02X", dev_data.uid[i]);
|
||||
|
||||
@@ -34,6 +34,7 @@ ADD_SCENE(nfc, mf_desfire_menu, MfDesfireMenu)
|
||||
ADD_SCENE(nfc, mf_desfire_data, MfDesfireData)
|
||||
ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp)
|
||||
ADD_SCENE(nfc, mf_classic_read_success, MfClassicReadSuccess)
|
||||
ADD_SCENE(nfc, mf_classic_data, MfClassicData)
|
||||
ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu)
|
||||
ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate)
|
||||
ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys)
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#include "../nfc_i.h"
|
||||
|
||||
void nfc_scene_mf_classic_data_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
MfClassicType type = nfc->dev->dev_data.mf_classic_data.type;
|
||||
MfClassicData* data = &nfc->dev->dev_data.mf_classic_data;
|
||||
TextBox* text_box = nfc->text_box;
|
||||
|
||||
text_box_set_font(text_box, TextBoxFontHex);
|
||||
|
||||
int card_blocks = 0;
|
||||
if(type == MfClassicType1k) {
|
||||
card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4;
|
||||
} else if(type == MfClassicType4k) {
|
||||
// 16 sectors of 4 blocks each plus 8 sectors of 16 blocks each
|
||||
card_blocks = MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4 + 8 * 16;
|
||||
} else if(type == MfClassicTypeMini) {
|
||||
card_blocks = MF_MINI_TOTAL_SECTORS_NUM * 4;
|
||||
}
|
||||
|
||||
int bytes_written = 0;
|
||||
for(int block_num = 0; block_num < card_blocks; block_num++) {
|
||||
bool is_sec_trailer = mf_classic_is_sector_trailer(block_num);
|
||||
if(is_sec_trailer) {
|
||||
uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
|
||||
MfClassicSectorTrailer* sec_tr =
|
||||
mf_classic_get_sector_trailer_by_sector(data, sector_num);
|
||||
// Key A
|
||||
for(size_t i = 0; i < sizeof(sec_tr->key_a); i += 2) {
|
||||
if((bytes_written % 8 == 0) && (bytes_written != 0)) {
|
||||
furi_string_push_back(nfc->text_box_store, '\n');
|
||||
}
|
||||
if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) {
|
||||
furi_string_cat_printf(
|
||||
nfc->text_box_store, "%02X%02X ", sec_tr->key_a[i], sec_tr->key_a[i + 1]);
|
||||
} else {
|
||||
furi_string_cat_printf(nfc->text_box_store, "???? ");
|
||||
}
|
||||
bytes_written += 2;
|
||||
}
|
||||
// Access bytes
|
||||
for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i += 2) {
|
||||
if((bytes_written % 8 == 0) && (bytes_written != 0)) {
|
||||
furi_string_push_back(nfc->text_box_store, '\n');
|
||||
}
|
||||
if(mf_classic_is_block_read(data, block_num)) {
|
||||
furi_string_cat_printf(
|
||||
nfc->text_box_store,
|
||||
"%02X%02X ",
|
||||
sec_tr->access_bits[i],
|
||||
sec_tr->access_bits[i + 1]);
|
||||
} else {
|
||||
furi_string_cat_printf(nfc->text_box_store, "???? ");
|
||||
}
|
||||
bytes_written += 2;
|
||||
}
|
||||
// Key B
|
||||
for(size_t i = 0; i < sizeof(sec_tr->key_b); i += 2) {
|
||||
if((bytes_written % 8 == 0) && (bytes_written != 0)) {
|
||||
furi_string_push_back(nfc->text_box_store, '\n');
|
||||
}
|
||||
if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) {
|
||||
furi_string_cat_printf(
|
||||
nfc->text_box_store, "%02X%02X ", sec_tr->key_b[i], sec_tr->key_b[i + 1]);
|
||||
} else {
|
||||
furi_string_cat_printf(nfc->text_box_store, "???? ");
|
||||
}
|
||||
bytes_written += 2;
|
||||
}
|
||||
} else {
|
||||
// Write data block
|
||||
for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i += 2) {
|
||||
if((bytes_written % 8 == 0) && (bytes_written != 0)) {
|
||||
furi_string_push_back(nfc->text_box_store, '\n');
|
||||
}
|
||||
if(mf_classic_is_block_read(data, block_num)) {
|
||||
furi_string_cat_printf(
|
||||
nfc->text_box_store,
|
||||
"%02X%02X ",
|
||||
data->block[block_num].value[i],
|
||||
data->block[block_num].value[i + 1]);
|
||||
} else {
|
||||
furi_string_cat_printf(nfc->text_box_store, "???? ");
|
||||
}
|
||||
bytes_written += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
text_box_set_text(text_box, furi_string_get_cstr(nfc->text_box_store));
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_classic_data_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void nfc_scene_mf_classic_data_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clean view
|
||||
text_box_reset(nfc->text_box);
|
||||
furi_string_reset(nfc->text_box_store);
|
||||
}
|
||||
@@ -25,7 +25,7 @@ void nfc_scene_mf_classic_menu_on_enter(void* context) {
|
||||
if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Detect reader",
|
||||
"Detect Reader",
|
||||
SubmenuIndexDetectReader,
|
||||
nfc_scene_mf_classic_menu_submenu_callback,
|
||||
nfc);
|
||||
|
||||
@@ -26,7 +26,7 @@ void nfc_scene_nfc_data_info_on_enter(void* context) {
|
||||
NfcProtocol protocol = dev_data->protocol;
|
||||
uint8_t text_scroll_height = 0;
|
||||
if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl) ||
|
||||
(protocol == NfcDeviceProtocolNfcV)) {
|
||||
(protocol == NfcDeviceProtocolNfcV) || (protocol == NfcDeviceProtocolMifareClassic)) {
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc);
|
||||
text_scroll_height = 52;
|
||||
@@ -290,6 +290,9 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(protocol == NfcDeviceProtocolNfcV) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVMenu);
|
||||
consumed = true;
|
||||
} else if(protocol == NfcDeviceProtocolMifareClassic) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicData);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user