diff --git a/.github/workflow_data/discord.py b/.github/workflow_data/discord.py
new file mode 100644
index 000000000..17659f8f7
--- /dev/null
+++ b/.github/workflow_data/discord.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python
+import requests
+import json
+import sys
+import os
+
+
+if __name__ == "__main__":
+ with open(os.environ["GITHUB_EVENT_PATH"], "r") as f:
+ event = json.load(f)
+
+ webhook = "DEV_DISCORD_WEBHOOK"
+ title = desc = url = ""
+ color = 0
+ fields = []
+
+ match os.environ["GITHUB_EVENT_NAME"]:
+ case "push":
+ count = len(event["commits"])
+ branch = event["ref"].removeprefix("refs/heads/")
+ change = (
+ "Force Push"
+ if event["forced"] and not count
+ else f"{count} New Commit{'' if count == 1 else 's'}"
+ )
+ desc = f"[**{change}**]({event['compare']}) | [{branch}]({event['repository']['html_url']}/tree/{branch})\n"
+ for commit in event["commits"][:10]:
+ msg = commit['message'].splitlines()[0]
+ msg = msg[:50] + ("..." if len(msg) > 50 else "")
+ desc += f"\n[`{commit['id'][:7]}`]({commit['url']}): {msg} - [__{commit['author']['username']}__](https://github.com/{commit['author']['username']})"
+ if count > 10:
+ desc += f"\n+ {count - 10} more commits"
+ url = event["compare"]
+ color = 16723712 if event["forced"] else 3669797
+
+ case "pull_request":
+ pr = event["pull_request"]
+ url = pr["html_url"]
+ branch = pr["base"]["ref"] + (
+ ""
+ if pr["base"]["full_name"] != pr["head"]["full_name"]
+ else f" <- {pr['head']['ref']}"
+ )
+ name = pr["title"][:50] + ("..." if len(pr["title"]) > 50 else "")
+ title = f"Pull Request {event['action'].title()} ({branch}): {name}"
+ match event["action"]:
+ case "opened":
+ desc = (event["body"][:2045] + "...") if len(event["body"]) > 2048 else event["body"]
+ color = 3669797
+
+ fields.append(
+ {
+ "name": "Changed Files:",
+ "value": str(pr["changed_files"]),
+ "inline": True,
+ }
+ )
+ fields.append(
+ {
+ "name": "Added:",
+ "value": "+" + str(pr["additions"]),
+ "inline": True,
+ }
+ )
+ fields.append(
+ {
+ "name": "Removed:",
+ "value": "-" + str(pr["deletions"]),
+ "inline": True,
+ }
+ )
+
+ case "closed":
+ color = 16723712
+ case "reopened":
+ color = 16751872
+ case _:
+ sys.exit(1)
+
+ case "release":
+ match event["action"]:
+ case "published":
+ webhook = "DEV_DISCORD_WEBHOOK"
+ color = 13845998
+ title = f"New Release published: {event['name']}"
+ desc += f"Changelog:"
+
+ changelog = "".join(
+ event["body"]
+ .split("Changelog")[1]
+ .split("", "")
+ insert = body.find("\n [//]: \n")
+ body = body[:insert] + hotfix + body[:insert]
+
+ req = requests.patch(
+ release["url"],
+ headers={
+ "Accept": "application/vnd.github.v3+json",
+ "Authorization": f"token {os.environ['GITHUB_TOKEN']}"
+ },
+ json={
+ "body": body
+ }
+ )
+ if not req.ok:
+ print(f"{req.url = }\n{req.status_code = }\n{req.content = }")
+ sys.exit(1)
diff --git a/.github/workflow_data/package.sh b/.github/workflow_data/package.sh
new file mode 100644
index 000000000..e2d48733d
--- /dev/null
+++ b/.github/workflow_data/package.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+export ARTIFACT_DIR="${VERSION_TAG}"
+
+export ARTIFACT_TGZ="${VERSION_TAG}.tgz"
+export ARTIFACT_ZIP="${VERSION_TAG}.zip"
+cd dist/${DEFAULT_TARGET}-*
+mv ${DEFAULT_TARGET}-update-* ${ARTIFACT_DIR}
+tar --format=ustar -czvf ../../${ARTIFACT_TGZ} ${ARTIFACT_DIR}
+cd ${ARTIFACT_DIR}
+7z a ../../../${ARTIFACT_ZIP} .
+cd ../../..
+
+python -m pip install pyncclient
+export ARTIFACT_WEB="$(NC_FILE=${ARTIFACT_TGZ} NC_PATH=XFW-Updater python .github/workflow_data/webupdater.py)"
+
+echo "ARTIFACT_TGZ=${ARTIFACT_TGZ}" >> $GITHUB_ENV
+echo "ARTIFACT_WEB=${ARTIFACT_WEB}" >> $GITHUB_ENV
+echo "ARTIFACT_ZIP=${ARTIFACT_ZIP}" >> $GITHUB_ENV
diff --git a/.github/workflow_data/release.md b/.github/workflow_data/release.md
index b83fb1f9c..2d2da876a 100644
--- a/.github/workflow_data/release.md
+++ b/.github/workflow_data/release.md
@@ -1,20 +1,28 @@
## ⬇️ Download
->### [🖥️ Web Updater (chrome)](https://lab.flipper.net/?url={webupdater_url}&channel=XFW-Updater&version={release_tag}) [recommended]
+>### [🖥️ Web Updater (chrome)](https://lab.flipper.net/?url={ARTIFACT_WEB}&channel=XFW-Updater&version={VERSION_TAG}) [recommended]
->### [🐬 qFlipper Package (.tgz)](https://github.com/ClaraCrazy/Flipper-Xtreme/releases/download/{release_tag}/{release_tag}.tgz)
+>### [🐬 qFlipper Package (.tgz)](https://github.com/ClaraCrazy/Flipper-Xtreme/releases/download/{VERSION_TAG}/{ARTIFACT_TGZ})
->### [📦 Zipped Archive (.zip)](https://github.com/ClaraCrazy/Flipper-Xtreme/releases/download/{release_tag}/{release_tag}.zip)
+>### [📦 Zipped Archive (.zip)](https://github.com/ClaraCrazy/Flipper-Xtreme/releases/download/{VERSION_TAG}/{ARTIFACT_ZIP})
**Remember to delete your `apps` folders before updating!**\
**Check the [install guide](https://github.com/ClaraCrazy/Flipper-Xtreme#install) if you're not sure, or [join our Discord](https://discord.gg/flipper-xtreme) if you have questions or encounter issues!**
## 🚀 Changelog
-{changelog}
+{CHANGELOG}
+
+
## ❤️ Support
If you like what you're seeing, **please consider donating to us**. We won't ever put this behind a paywall, but we'd still appreciate a few bucks!
-- **[Direct transfer to my bank](https://bunq.me/ClaraK)**: No account needed, they have a convenient online pay thingy (that hates americans, sowwy)
+- **[Direct Wire-transfer](https://bunq.me/ClaraK)**: No account needed, just specify amount and hit send
- **[Patreon](https://patreon.com/CynthiaLabs)**
- **[Paypal](https://paypal.me/RdX2020)**
- **Monero**: `41kyWeeoVdK4quzQ4M9ikVGs6tCQCLfdx8jLExTNsAu2SF1QAyDqRdjfGM6EL8L9NpXwt89HJeAoGf1aoArk7nDr4AMMV4T`
diff --git a/.github/workflow_data/release.py b/.github/workflow_data/release.py
new file mode 100644
index 000000000..52e6c595b
--- /dev/null
+++ b/.github/workflow_data/release.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+import json
+import os
+
+if __name__ == "__main__":
+ notes_path = '.github/workflow_data/release.md'
+ with open(os.environ['GITHUB_EVENT_PATH'], "r") as f:
+ changelog = json.load(f)['pull_request']['body']
+ with open(notes_path, "r") as f:
+ template = f.read()
+ notes = template.format(
+ ARTIFACT_TGZ=os.environ['ARTIFACT_TGZ'],
+ ARTIFACT_WEB=os.environ['ARTIFACT_WEB'],
+ ARTIFACT_ZIP=os.environ['ARTIFACT_ZIP'],
+ VERSION_TAG=os.environ['VERSION_TAG'],
+ CHANGELOG=changelog
+ )
+ with open(notes_path, "w") as f:
+ f.write(notes)
diff --git a/.github/workflow_data/version.sh b/.github/workflow_data/version.sh
new file mode 100644
index 000000000..0e6f4eb17
--- /dev/null
+++ b/.github/workflow_data/version.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+export VERSION_TAG="$(grep -o "DIST_SUFFIX = .*" fbt_options.py | cut -d'"' -f2)"
+echo "VERSION_TAG=${VERSION_TAG}" >> $GITHUB_ENV
diff --git a/.github/workflow_data/webupdater.py b/.github/workflow_data/webupdater.py
new file mode 100644
index 000000000..eecbf2282
--- /dev/null
+++ b/.github/workflow_data/webupdater.py
@@ -0,0 +1,16 @@
+import nextcloud_client
+import os
+
+if __name__ == "__main__":
+ client = nextcloud_client.Client(os.environ["NC_HOST"])
+ client.login(os.environ["NC_USER"], os.environ["NC_PASS"])
+ file = os.environ["NC_FILE"]
+ path = os.environ["NC_PATH"] + file
+ try:
+ client.delete(path)
+ except Exception:
+ pass
+ client.put_file(path, file)
+ share_link = client.share_file_with_link(path).get_link()
+ download_link = share_link.rstrip("/") + "/download/" + file
+ print(download_link, end="")
diff --git a/.github/workflows/discord.yml b/.github/workflows/discord.yml
new file mode 100644
index 000000000..8ff4775ac
--- /dev/null
+++ b/.github/workflows/discord.yml
@@ -0,0 +1,36 @@
+name: Discord webhook
+
+on:
+ push:
+ pull_request:
+ types:
+ - "opened"
+ - "closed"
+ - "reopened"
+ release:
+ types:
+ - "published"
+ check_suite:
+ types:
+ - "completed"
+ issues:
+ types:
+ - "opened"
+ - "closed"
+ - "reopened"
+ issue_comment:
+ types:
+ - "created"
+
+jobs:
+ webhook:
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: 'Checkout code'
+ uses: actions/checkout@v3
+
+ - name: Send webhook
+ env:
+ DEV_DISCORD_WEBHOOK: "https://discord.com/api/webhooks/${{ secrets.DEV_WEBHOOK_ID }}/${{ secrets.DEV_WEBHOOK_TOKEN }}"
+ run: python .github/workflow_data/discord.py
diff --git a/.github/workflows/hotfix.yml b/.github/workflows/hotfix.yml
new file mode 100644
index 000000000..82fb6fa30
--- /dev/null
+++ b/.github/workflows/hotfix.yml
@@ -0,0 +1,63 @@
+name: "Hotfix integration"
+
+on:
+ pull_request_review:
+ types: [submitted]
+
+env:
+ TARGETS: f7
+ DEFAULT_TARGET: f7
+
+jobs:
+ hotfix:
+ if: |
+ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name &&
+ endsWith(github.event.pull_request.title, ' Hotfix') &&
+ github.event.review.author_association == 'OWNER' &&
+ startsWith(github.event.pull_request.title, 'V') &&
+ github.event.pull_request.base.ref == 'main' &&
+ github.event.pull_request.head.ref == 'dev' &&
+ github.event.pull_request.state == 'open' &&
+ github.event.pull_request.draft == false &&
+ github.event.review.state == 'APPROVED'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+
+ - name: 'Checkout code'
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: 'Build the firmware'
+ run: |
+ set -e
+ for TARGET in ${TARGETS}; do
+ TARGET="$(echo "${TARGET}" | sed 's/f//')"; \
+ ./fbt TARGET_HW=$TARGET updater_package
+ done
+
+ - name: "Check for uncommitted changes"
+ run: |
+ git diff --exit-code
+
+ - name: "Read version tag"
+ run: bash .github/workflow_data/version.sh
+
+ - name: "Make tgz, zip and webupdater"
+ run: bash .github/workflow_data/package.sh
+ env:
+ NC_HOST: "https://cloud.cynthialabs.net/"
+ NC_USER: "${{ secrets.NC_USER }}"
+ NC_PASS: "${{ secrets.NC_PASS }}"
+
+ - name: "Upload hotfix"
+ run: python .github/workflow_data/hotfix.py
+
+ - name: "Merge pull request"
+ uses: "pascalgn/automerge-action@v0.15.6"
+ env:
+ MERGE_LABELS: ""
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d6ee6c9e6..db717f6f2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -29,13 +29,7 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
- repository: ${{ github.event.pull_request.head.repo.full_name }}
- ref: ${{ github.event.pull_request.head.ref }}
-
- - name: "Read version number"
- run: |
- RELEASE_TAG="$(grep -o "DIST_SUFFIX = .*" fbt_options.py | cut -d'"' -f2)"
- echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_ENV
+ ref: ${{ github.event.pull_request.head.sha }}
- name: 'Build the firmware'
run: |
@@ -45,21 +39,22 @@ jobs:
./fbt TARGET_HW=$TARGET updater_package
done
- - name: "Make tgz, zip and webupdater"
+ - name: "Check for uncommitted changes"
run: |
- cd dist/${DEFAULT_TARGET}-*
- mv ${DEFAULT_TARGET}-update-* ${RELEASE_TAG}
- tar --format=ustar -czvf ../${RELEASE_TAG}.tgz ${RELEASE_TAG}
- cd ${RELEASE_TAG}
- 7z a ../../${RELEASE_TAG}.zip .
- cd ../..
- python -m pip install pyncclient
- WEBUPDATER_URL="$(python -c "import nextcloud_client as n;c = n.Client('https://cloud.cynthialabs.net/');c.login('${{ secrets.NC_USER }}', '${{ secrets.NC_PASS }}');c.put_file('XFW-Updater/${RELEASE_TAG}.tgz', '${RELEASE_TAG}.tgz');print(c.share_file_with_link('XFW-Updater/${RELEASE_TAG}.tgz').get_link().rstrip('/') + '/download/${RELEASE_TAG}.tgz', end='')")"
- echo "WEBUPDATER_URL=${WEBUPDATER_URL}" >> $GITHUB_ENV
+ git diff --exit-code
+
+ - name: "Read version tag"
+ run: bash .github/workflow_data/version.sh
+
+ - name: "Make tgz, zip and webupdater"
+ run: bash .github/workflow_data/package.sh
+ env:
+ NC_HOST: "https://cloud.cynthialabs.net/"
+ NC_USER: "${{ secrets.NC_USER }}"
+ NC_PASS: "${{ secrets.NC_PASS }}"
- name: "Update release notes"
- run: |
- python -c "import json, os;f = '.github/workflow_data/release.md';c = json.load(open(os.environ['GITHUB_EVENT_PATH']))['pull_request']['body'];f_ = open(f);n = f_.read().format(release_tag='${RELEASE_TAG}', webupdater_url='${WEBUPDATER_URL}', changelog=c);f_.close();f_ = open(f, 'w');f_.write(n);f_.close()"
+ run: python .github/workflow_data/release.py
- name: "Merge pull request"
uses: "pascalgn/automerge-action@v0.15.6"
@@ -74,8 +69,8 @@ jobs:
draft: false
prerelease: false
files: |
- dist/${{ env.RELEASE_TAG }}.tgz
- dist/${{ env.RELEASE_TAG }}.zip
- name: "${{ env.RELEASE_TAG }}"
- tag_name: "${{ env.RELEASE_TAG }}"
+ dist/${{ env.ARTIFACT_TGZ }}
+ dist/${{ env.ARTIFACT_ZIP }}
+ name: "${{ env.VERSION_TAG }}"
+ tag_name: "${{ env.VERSION_TAG }}"
target_commitish: ${{ github.event.pull_request.base.ref }}
diff --git a/.gitignore b/.gitignore
index 067264322..e33c10125 100644
--- a/.gitignore
+++ b/.gitignore
@@ -64,6 +64,7 @@ openocd.log
# PVS Studio temporary files
.PVS-Studio/
PVS-Studio.log
+*.PVS-Studio.*
.gdbinit
diff --git a/ReadMe.md b/ReadMe.md
index b729d1e6e..c9773fdbc 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -5,7 +5,7 @@
-[Intro](https://github.com/ClaraCrazy/Flipper-Xtreme#What-makes-it-special) | [Animations](https://github.com/ClaraCrazy/Flipper-Xtreme#Animations--Asset-Packs) | [Wiki](https://github.com/ClaraCrazy/Flipper-Xtreme/wiki) | [Changelog](https://github.com/ClaraCrazy/Flipper-Xtreme#list-of-changes) | [Known bugs](https://github.com/ClaraCrazy/Flipper-Xtreme/issues?q=is%3Aissue+is%3Aopen+label%3Arelease-pending) | [Install](https://github.com/ClaraCrazy/Flipper-Xtreme#Install) | [Build](https://github.com/ClaraCrazy/Flipper-Xtreme#build-it-yourself) | [Discord](https://discord.gg/flipper-xtreme)
+[Intro](https://github.com/ClaraCrazy/Flipper-Xtreme#What-makes-it-special) | [Animations](https://github.com/ClaraCrazy/Flipper-Xtreme#Animations--Asset-Packs) | [Wiki](https://github.com/ClaraCrazy/Flipper-Xtreme/wiki) | [Changelog](https://github.com/ClaraCrazy/Flipper-Xtreme#list-of-changes) | [Known bugs](https://github.com/ClaraCrazy/Flipper-Xtreme/issues?q=is%3Aissue+is%3Aopen+label%3Arelease-pending) | [Install](https://github.com/ClaraCrazy/Flipper-Xtreme#Install) | [Build](https://github.com/ClaraCrazy/Flipper-Xtreme#build-it-yourself) | [Discord](https://discord.gg/flipper-xtreme) | [Donate](https://github.com/ClaraCrazy/Flipper-Xtreme#%EF%B8%8F-support)
-----
This firmware is a complete overhaul of the [Official Firmware](https://github.com/flipperdevices/flipperzero-firmware), and also features lots of awesome code-bits from [Unleashed](https://github.com/DarkFlippers/unleashed-firmware).
@@ -32,17 +32,16 @@ Note, the below mentioned changes are only a few things we did. For a full list
We wrote a powerful yet easy-to-use application specifically for our Firmware, that gives you easy-access to all the fancy things we implemented:
-
+
-Graphics: Change the animation package (more on that below), the play speed of them, cycle duration and bypass level-bassed animations
+Interface:
+Customize every bit of your Flipper, from the desktop animations, to the main menu apps, lockscreen style, statusbar items and screen options like dark mode and lefty mode(yes, we thought about you :3 ).
-Statusbar: Modify the design of the statusbar seen on the main Screen. Toggle Icons, their background, the top line and modify the battery icon to your liking.
+Protocols:
+Here you can toggle USB & Bluetooth mode for our Bad-Keyboard app, and manage Subghz settings like custom frequencies and extend options.
-Protocols: Here you can cycle between USB & Bluetooth mode for our Bad-Keyboard app, and toggle Subghz settings.
-
-Dolphin: Two simple yet sought after features: Simply change the level of your Flipper and disable / change the "Butthurt timer", aka. the time it takes for the Flipper to get sad when its not used.
-
-Misc: All the other options that dont fit elsewhere. Toggles for our custom dark mode & left-handed mode (yes, we thought about you :3 ), an option to change the Flippers name and a switch for file sorting.
+Misc:
+All the other options that don't fit elsewhere. Change your Flipper's name, change xp level, and manage settings for RGB backlight.
@@ -225,12 +224,15 @@ $ ./fbt resources icons dolphin_ext
[](https://github.com/ClaraCrazy/Flipper-Xtreme/graphs/contributors)
-----
-## SAST Tools
+## ❤️ Support
+If you like what you're seeing, **please consider donating to us**. We won't ever put this behind a paywall, but we'd still appreciate a few bucks!
-This helps us a lot, thanks for the free license for this project!
+- **[Direct Wire-transfer](https://bunq.me/ClaraK)**: No account needed, just specify amount and hit send
+- **[Patreon](https://patreon.com/CynthiaLabs)**
+- **[Paypal](https://paypal.me/RdX2020)**
+- **Monero**: `41kyWeeoVdK4quzQ4M9ikVGs6tCQCLfdx8jLExTNsAu2SF1QAyDqRdjfGM6EL8L9NpXwt89HJeAoGf1aoArk7nDr4AMMV4T`
-[PVS-Studio](https://pvs-studio.com/en/pvs-studio/?utm_source=github&utm_medium=organic&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code.
+**Thanks for all your support <3**
----
"What we do for ourselves dies with us. What we do for others and the world remains and is immortal.” ― Albert Pine
diff --git a/applications/debug/accessor/accessor_app.cpp b/applications/debug/accessor/accessor_app.cpp
index 337437d0e..2e40b3c35 100644
--- a/applications/debug/accessor/accessor_app.cpp
+++ b/applications/debug/accessor/accessor_app.cpp
@@ -34,7 +34,7 @@ void AccessorApp::run(void) {
AccessorApp::AccessorApp()
: text_store{0} {
notification = static_cast(furi_record_open(RECORD_NOTIFICATION));
- onewire_host = onewire_host_alloc(&ibutton_gpio);
+ onewire_host = onewire_host_alloc(&gpio_ibutton);
furi_hal_power_enable_otg();
}
diff --git a/applications/examples/example_thermo/README.md b/applications/examples/example_thermo/README.md
index fa00264dc..08240a1f8 100644
--- a/applications/examples/example_thermo/README.md
+++ b/applications/examples/example_thermo/README.md
@@ -33,10 +33,10 @@ It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it
- gpio_ext_pa4
- gpio_ext_pa6
- gpio_ext_pa7
- - ibutton_gpio
+ - gpio_ibutton
*/
-#define THERMO_GPIO_PIN (ibutton_gpio)
+#define THERMO_GPIO_PIN (gpio_ibutton)
```
Do not forget about the external pull-up resistor as these pins do not have one built-in.
diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c
index 4241cb59d..5cb8863a4 100644
--- a/applications/examples/example_thermo/example_thermo.c
+++ b/applications/examples/example_thermo/example_thermo.c
@@ -43,10 +43,10 @@
- gpio_ext_pa4
- gpio_ext_pa6
- gpio_ext_pa7
- - ibutton_gpio
+ - gpio_ibutton
*/
-#define THERMO_GPIO_PIN (ibutton_gpio)
+#define THERMO_GPIO_PIN (gpio_ibutton)
/* Flags which the reader thread responds to */
typedef enum {
diff --git a/applications/external/avr_isp_programmer/application.fam b/applications/external/avr_isp_programmer/application.fam
new file mode 100644
index 000000000..3b396e83d
--- /dev/null
+++ b/applications/external/avr_isp_programmer/application.fam
@@ -0,0 +1,17 @@
+App(
+ appid="avr_isp",
+ name="[AVR] Flasher",
+ apptype=FlipperAppType.EXTERNAL,
+ entry_point="avr_isp_app",
+ requires=["gui"],
+ stack_size=4 * 1024,
+ order=20,
+ fap_icon="avr_app_icon_10x10.png",
+ fap_category="GPIO",
+ fap_icon_assets="images",
+ fap_private_libs=[
+ Lib(
+ name="driver",
+ ),
+ ],
+)
diff --git a/applications/external/avr_isp_programmer/avr_app_icon_10x10.png b/applications/external/avr_isp_programmer/avr_app_icon_10x10.png
new file mode 100644
index 000000000..533787fe3
Binary files /dev/null and b/applications/external/avr_isp_programmer/avr_app_icon_10x10.png differ
diff --git a/applications/external/avr_isp_programmer/avr_isp_app.c b/applications/external/avr_isp_programmer/avr_isp_app.c
new file mode 100644
index 000000000..740dc3610
--- /dev/null
+++ b/applications/external/avr_isp_programmer/avr_isp_app.c
@@ -0,0 +1,179 @@
+#include "avr_isp_app_i.h"
+
+static bool avr_isp_app_custom_event_callback(void* context, uint32_t event) {
+ furi_assert(context);
+ AvrIspApp* app = context;
+ return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool avr_isp_app_back_event_callback(void* context) {
+ furi_assert(context);
+ AvrIspApp* app = context;
+ return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static void avr_isp_app_tick_event_callback(void* context) {
+ furi_assert(context);
+ AvrIspApp* app = context;
+ scene_manager_handle_tick_event(app->scene_manager);
+}
+
+AvrIspApp* avr_isp_app_alloc() {
+ AvrIspApp* app = malloc(sizeof(AvrIspApp));
+
+ app->file_path = furi_string_alloc();
+ furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX);
+ app->error = AvrIspErrorNoError;
+
+ // GUI
+ app->gui = furi_record_open(RECORD_GUI);
+
+ // View Dispatcher
+ app->view_dispatcher = view_dispatcher_alloc();
+ app->scene_manager = scene_manager_alloc(&avr_isp_scene_handlers, app);
+ view_dispatcher_enable_queue(app->view_dispatcher);
+
+ view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+ view_dispatcher_set_custom_event_callback(
+ app->view_dispatcher, avr_isp_app_custom_event_callback);
+ view_dispatcher_set_navigation_event_callback(
+ app->view_dispatcher, avr_isp_app_back_event_callback);
+ view_dispatcher_set_tick_event_callback(
+ app->view_dispatcher, avr_isp_app_tick_event_callback, 100);
+
+ view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+ // Open Notification record
+ app->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+ // SubMenu
+ app->submenu = submenu_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, AvrIspViewSubmenu, submenu_get_view(app->submenu));
+
+ // Widget
+ app->widget = widget_alloc();
+ view_dispatcher_add_view(app->view_dispatcher, AvrIspViewWidget, widget_get_view(app->widget));
+
+ // Text Input
+ app->text_input = text_input_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, AvrIspViewTextInput, text_input_get_view(app->text_input));
+
+ // Popup
+ app->popup = popup_alloc();
+ view_dispatcher_add_view(app->view_dispatcher, AvrIspViewPopup, popup_get_view(app->popup));
+
+ //Dialog
+ app->dialogs = furi_record_open(RECORD_DIALOGS);
+
+ // Programmer view
+ app->avr_isp_programmer_view = avr_isp_programmer_view_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ AvrIspViewProgrammer,
+ avr_isp_programmer_view_get_view(app->avr_isp_programmer_view));
+
+ // Reader view
+ app->avr_isp_reader_view = avr_isp_reader_view_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ AvrIspViewReader,
+ avr_isp_reader_view_get_view(app->avr_isp_reader_view));
+
+ // Writer view
+ app->avr_isp_writer_view = avr_isp_writer_view_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ AvrIspViewWriter,
+ avr_isp_writer_view_get_view(app->avr_isp_writer_view));
+
+ // Chip detect view
+ app->avr_isp_chip_detect_view = avr_isp_chip_detect_view_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher,
+ AvrIspViewChipDetect,
+ avr_isp_chip_detect_view_get_view(app->avr_isp_chip_detect_view));
+
+ // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering
+ uint8_t attempts = 0;
+ while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
+ furi_hal_power_enable_otg();
+ furi_delay_ms(10);
+ }
+
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneStart);
+
+ return app;
+} //-V773
+
+void avr_isp_app_free(AvrIspApp* app) {
+ furi_assert(app);
+
+ // Disable 5v power
+ if(furi_hal_power_is_otg_enabled()) {
+ furi_hal_power_disable_otg();
+ }
+
+ // Submenu
+ view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewSubmenu);
+ submenu_free(app->submenu);
+
+ // Widget
+ view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWidget);
+ widget_free(app->widget);
+
+ // TextInput
+ view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewTextInput);
+ text_input_free(app->text_input);
+
+ // Popup
+ view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewPopup);
+ popup_free(app->popup);
+
+ //Dialog
+ furi_record_close(RECORD_DIALOGS);
+
+ // Programmer view
+ view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewProgrammer);
+ avr_isp_programmer_view_free(app->avr_isp_programmer_view);
+
+ // Reader view
+ view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewReader);
+ avr_isp_reader_view_free(app->avr_isp_reader_view);
+
+ // Writer view
+ view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWriter);
+ avr_isp_writer_view_free(app->avr_isp_writer_view);
+
+ // Chip detect view
+ view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewChipDetect);
+ avr_isp_chip_detect_view_free(app->avr_isp_chip_detect_view);
+
+ // View dispatcher
+ view_dispatcher_free(app->view_dispatcher);
+ scene_manager_free(app->scene_manager);
+
+ // Notifications
+ furi_record_close(RECORD_NOTIFICATION);
+ app->notifications = NULL;
+
+ // Close records
+ furi_record_close(RECORD_GUI);
+
+ // Path strings
+ furi_string_free(app->file_path);
+
+ free(app);
+}
+
+int32_t avr_isp_app(void* p) {
+ UNUSED(p);
+ AvrIspApp* avr_isp_app = avr_isp_app_alloc();
+
+ view_dispatcher_run(avr_isp_app->view_dispatcher);
+
+ avr_isp_app_free(avr_isp_app);
+
+ return 0;
+}
diff --git a/applications/external/avr_isp_programmer/avr_isp_app_i.c b/applications/external/avr_isp_programmer/avr_isp_app_i.c
new file mode 100644
index 000000000..7a7fa6d7f
--- /dev/null
+++ b/applications/external/avr_isp_programmer/avr_isp_app_i.c
@@ -0,0 +1,31 @@
+#include "avr_isp_app_i.h"
+#include
+#include
+
+#define TAG "AvrIsp"
+
+bool avr_isp_load_from_file(AvrIspApp* app) {
+ furi_assert(app);
+
+ FuriString* file_path = furi_string_alloc();
+ FuriString* file_name = furi_string_alloc();
+
+ DialogsFileBrowserOptions browser_options;
+ dialog_file_browser_set_basic_options(
+ &browser_options, AVR_ISP_APP_EXTENSION, &I_avr_app_icon_10x10);
+ browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
+
+ // Input events and views are managed by file_select
+ bool res = dialog_file_browser_show(app->dialogs, file_path, app->file_path, &browser_options);
+
+ if(res) {
+ path_extract_dirname(furi_string_get_cstr(file_path), app->file_path);
+ path_extract_filename(file_path, file_name, true);
+ strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME);
+ }
+
+ furi_string_free(file_name);
+ furi_string_free(file_path);
+
+ return res;
+}
diff --git a/applications/external/avr_isp_programmer/avr_isp_app_i.h b/applications/external/avr_isp_programmer/avr_isp_app_i.h
new file mode 100644
index 000000000..17c69f8f2
--- /dev/null
+++ b/applications/external/avr_isp_programmer/avr_isp_app_i.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "helpers/avr_isp_types.h"
+#include
+
+#include "scenes/avr_isp_scene.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "views/avr_isp_view_programmer.h"
+#include "views/avr_isp_view_reader.h"
+#include "views/avr_isp_view_writer.h"
+#include "views/avr_isp_view_chip_detect.h"
+
+#define AVR_ISP_MAX_LEN_NAME 64
+
+typedef struct {
+ Gui* gui;
+ ViewDispatcher* view_dispatcher;
+ SceneManager* scene_manager;
+ NotificationApp* notifications;
+ DialogsApp* dialogs;
+ Popup* popup;
+ Submenu* submenu;
+ Widget* widget;
+ TextInput* text_input;
+ FuriString* file_path;
+ char file_name_tmp[AVR_ISP_MAX_LEN_NAME];
+ AvrIspProgrammerView* avr_isp_programmer_view;
+ AvrIspReaderView* avr_isp_reader_view;
+ AvrIspWriterView* avr_isp_writer_view;
+ AvrIspChipDetectView* avr_isp_chip_detect_view;
+ AvrIspError error;
+} AvrIspApp;
+
+bool avr_isp_load_from_file(AvrIspApp* app);
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.c b/applications/external/avr_isp_programmer/helpers/avr_isp.c
new file mode 100644
index 000000000..51b4f8846
--- /dev/null
+++ b/applications/external/avr_isp_programmer/helpers/avr_isp.c
@@ -0,0 +1,491 @@
+#include "avr_isp.h"
+#include "../lib/driver/avr_isp_prog_cmd.h"
+#include "../lib/driver/avr_isp_spi_sw.h"
+
+#include
+
+#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320
+#define TAG "AvrIsp"
+
+struct AvrIsp {
+ AvrIspSpiSw* spi;
+ bool pmode;
+ AvrIspCallback callback;
+ void* context;
+};
+
+AvrIsp* avr_isp_alloc(void) {
+ AvrIsp* instance = malloc(sizeof(AvrIsp));
+ return instance;
+}
+
+void avr_isp_free(AvrIsp* instance) {
+ furi_assert(instance);
+
+ if(instance->spi) avr_isp_end_pmode(instance);
+ free(instance);
+}
+
+void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context) {
+ furi_assert(instance);
+ furi_assert(context);
+
+ instance->callback = callback;
+ instance->context = context;
+}
+
+uint8_t avr_isp_spi_transaction(
+ AvrIsp* instance,
+ uint8_t cmd,
+ uint8_t addr_hi,
+ uint8_t addr_lo,
+ uint8_t data) {
+ furi_assert(instance);
+
+ avr_isp_spi_sw_txrx(instance->spi, cmd);
+ avr_isp_spi_sw_txrx(instance->spi, addr_hi);
+ avr_isp_spi_sw_txrx(instance->spi, addr_lo);
+ return avr_isp_spi_sw_txrx(instance->spi, data);
+}
+
+static bool avr_isp_set_pmode(AvrIsp* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
+ furi_assert(instance);
+
+ uint8_t res = 0;
+ avr_isp_spi_sw_txrx(instance->spi, a);
+ avr_isp_spi_sw_txrx(instance->spi, b);
+ res = avr_isp_spi_sw_txrx(instance->spi, c);
+ avr_isp_spi_sw_txrx(instance->spi, d);
+ return res == 0x53;
+}
+
+void avr_isp_end_pmode(AvrIsp* instance) {
+ furi_assert(instance);
+
+ if(instance->pmode) {
+ avr_isp_spi_sw_res_set(instance->spi, true);
+ // We're about to take the target out of reset
+ // so configure SPI pins as input
+ if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+ instance->spi = NULL;
+ }
+
+ instance->pmode = false;
+}
+
+static bool avr_isp_start_pmode(AvrIsp* instance, AvrIspSpiSwSpeed spi_speed) {
+ furi_assert(instance);
+
+ // Reset target before driving PIN_SCK or PIN_MOSI
+
+ // SPI.begin() will configure SS as output,
+ // so SPI master mode is selected.
+ // We have defined RESET as pin 10,
+ // which for many arduino's is not the SS pin.
+ // So we have to configure RESET as output here,
+ // (reset_target() first sets the correct level)
+ if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+ instance->spi = avr_isp_spi_sw_init(spi_speed);
+
+ avr_isp_spi_sw_res_set(instance->spi, false);
+ // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm":
+
+ // Pulse RESET after PIN_SCK is low:
+ avr_isp_spi_sw_sck_set(instance->spi, false);
+
+ // discharge PIN_SCK, value arbitrally chosen
+ furi_delay_ms(20);
+ avr_isp_spi_sw_res_set(instance->spi, true);
+
+ // Pulse must be minimum 2 target CPU speed cycles
+ // so 100 usec is ok for CPU speeds above 20KHz
+ furi_delay_ms(1);
+
+ avr_isp_spi_sw_res_set(instance->spi, false);
+
+ // Send the enable programming command:
+ // datasheet: must be > 20 msec
+ furi_delay_ms(50);
+ if(avr_isp_set_pmode(instance, AVR_ISP_SET_PMODE)) {
+ instance->pmode = true;
+ return true;
+ }
+ return false;
+}
+
+bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) {
+ furi_assert(instance);
+
+ AvrIspSpiSwSpeed spi_speed[] = {
+ AvrIspSpiSwSpeed1Mhz,
+ AvrIspSpiSwSpeed400Khz,
+ AvrIspSpiSwSpeed250Khz,
+ AvrIspSpiSwSpeed125Khz,
+ AvrIspSpiSwSpeed60Khz,
+ AvrIspSpiSwSpeed40Khz,
+ AvrIspSpiSwSpeed20Khz,
+ AvrIspSpiSwSpeed10Khz,
+ AvrIspSpiSwSpeed5Khz,
+ AvrIspSpiSwSpeed1Khz,
+ };
+ for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) {
+ if(avr_isp_start_pmode(instance, spi_speed[i])) {
+ AvrIspSignature sig = avr_isp_read_signature(instance);
+ AvrIspSignature sig_examination = avr_isp_read_signature(instance); //-V656
+ uint8_t y = 0;
+ while(y < 8) {
+ if(memcmp((uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspSignature)) !=
+ 0)
+ break;
+ sig_examination = avr_isp_read_signature(instance);
+ y++;
+ }
+ if(y == 8) {
+ if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) {
+ if(i < (COUNT_OF(spi_speed) - 1)) {
+ avr_isp_end_pmode(instance);
+ i++;
+ return avr_isp_start_pmode(instance, spi_speed[i]);
+ }
+ }
+ return true;
+ }
+ }
+ }
+ if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+ return false;
+}
+
+static void avr_isp_commit(AvrIsp* instance, uint16_t addr, uint8_t data) {
+ furi_assert(instance);
+
+ avr_isp_spi_transaction(instance, AVR_ISP_COMMIT(addr));
+ /* polling flash */
+ if(data == 0xFF) {
+ furi_delay_ms(5);
+ } else {
+ /* polling flash */
+ uint32_t starttime = furi_get_tick();
+ while((furi_get_tick() - starttime) < 30) {
+ if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) {
+ break;
+ };
+ }
+ }
+}
+
+static uint16_t avr_isp_current_page(AvrIsp* instance, uint32_t addr, uint16_t page_size) {
+ furi_assert(instance);
+
+ uint16_t page = 0;
+ switch(page_size) {
+ case 32:
+ page = addr & 0xFFFFFFF0;
+ break;
+ case 64:
+ page = addr & 0xFFFFFFE0;
+ break;
+ case 128:
+ page = addr & 0xFFFFFFC0;
+ break;
+ case 256:
+ page = addr & 0xFFFFFF80;
+ break;
+
+ default:
+ page = addr;
+ break;
+ }
+
+ return page;
+}
+
+static bool avr_isp_flash_write_pages(
+ AvrIsp* instance,
+ uint16_t addr,
+ uint16_t page_size,
+ uint8_t* data,
+ uint32_t data_size) {
+ furi_assert(instance);
+
+ size_t x = 0;
+ uint16_t page = avr_isp_current_page(instance, addr, page_size);
+
+ while(x < data_size) {
+ if(page != avr_isp_current_page(instance, addr, page_size)) {
+ avr_isp_commit(instance, page, data[x - 1]);
+ page = avr_isp_current_page(instance, addr, page_size);
+ }
+ avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_LO(addr, data[x++]));
+ avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_HI(addr, data[x++]));
+ addr++;
+ }
+ avr_isp_commit(instance, page, data[x - 1]);
+ return true;
+}
+
+bool avr_isp_erase_chip(AvrIsp* instance) {
+ furi_assert(instance);
+
+ bool ret = false;
+ if(!instance->pmode) avr_isp_auto_set_spi_speed_start_pmode(instance);
+ if(instance->pmode) {
+ avr_isp_spi_transaction(instance, AVR_ISP_ERASE_CHIP);
+ furi_delay_ms(100);
+ avr_isp_end_pmode(instance);
+ ret = true;
+ }
+ return ret;
+}
+
+static bool
+ avr_isp_eeprom_write(AvrIsp* instance, uint16_t addr, uint8_t* data, uint32_t data_size) {
+ furi_assert(instance);
+
+ for(uint16_t i = 0; i < data_size; i++) {
+ avr_isp_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, data[i]));
+ furi_delay_ms(10);
+ addr++;
+ }
+ return true;
+}
+
+bool avr_isp_write_page(
+ AvrIsp* instance,
+ uint32_t mem_type,
+ uint32_t mem_size,
+ uint16_t addr,
+ uint16_t page_size,
+ uint8_t* data,
+ uint32_t data_size) {
+ furi_assert(instance);
+
+ bool ret = false;
+ switch(mem_type) {
+ case STK_SET_FLASH_TYPE:
+ if((addr + data_size / 2) <= mem_size) {
+ ret = avr_isp_flash_write_pages(instance, addr, page_size, data, data_size);
+ }
+ break;
+
+ case STK_SET_EEPROM_TYPE:
+ if((addr + data_size) <= mem_size) {
+ ret = avr_isp_eeprom_write(instance, addr, data, data_size);
+ }
+ break;
+
+ default:
+ furi_crash(TAG " Incorrect mem type.");
+ break;
+ }
+
+ return ret;
+}
+
+static bool avr_isp_flash_read_page(
+ AvrIsp* instance,
+ uint16_t addr,
+ uint16_t page_size,
+ uint8_t* data,
+ uint32_t data_size) {
+ furi_assert(instance);
+
+ if(page_size > data_size) return false;
+ for(uint16_t i = 0; i < page_size; i += 2) {
+ data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(addr));
+ data[i + 1] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr));
+ addr++;
+ }
+ return true;
+}
+
+static bool avr_isp_eeprom_read_page(
+ AvrIsp* instance,
+ uint16_t addr,
+ uint16_t page_size,
+ uint8_t* data,
+ uint32_t data_size) {
+ furi_assert(instance);
+
+ if(page_size > data_size) return false;
+ for(uint16_t i = 0; i < page_size; i++) {
+ data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr));
+ addr++;
+ }
+ return true;
+}
+
+bool avr_isp_read_page(
+ AvrIsp* instance,
+ uint32_t mem_type,
+ uint16_t addr,
+ uint16_t page_size,
+ uint8_t* data,
+ uint32_t data_size) {
+ furi_assert(instance);
+
+ bool res = false;
+ if(mem_type == STK_SET_FLASH_TYPE)
+ res = avr_isp_flash_read_page(instance, addr, page_size, data, data_size);
+ if(mem_type == STK_SET_EEPROM_TYPE)
+ res = avr_isp_eeprom_read_page(instance, addr, page_size, data, data_size);
+
+ return res;
+}
+
+AvrIspSignature avr_isp_read_signature(AvrIsp* instance) {
+ furi_assert(instance);
+
+ AvrIspSignature signature;
+ signature.vendor = avr_isp_spi_transaction(instance, AVR_ISP_READ_VENDOR);
+ signature.part_family = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY);
+ signature.part_number = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER);
+ return signature;
+}
+
+uint8_t avr_isp_read_lock_byte(AvrIsp* instance) {
+ furi_assert(instance);
+
+ uint8_t data = 0;
+ uint32_t starttime = furi_get_tick();
+ while((furi_get_tick() - starttime) < 300) {
+ data = avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE);
+ if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == data) {
+ break;
+ };
+ data = 0x00;
+ }
+ return data;
+}
+
+bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock) {
+ furi_assert(instance);
+
+ bool ret = false;
+ if(avr_isp_read_lock_byte(instance) == lock) {
+ ret = true;
+ } else {
+ avr_isp_spi_transaction(instance, AVR_ISP_WRITE_LOCK_BYTE(lock));
+ /* polling lock byte */
+ uint32_t starttime = furi_get_tick();
+ while((furi_get_tick() - starttime) < 30) {
+ if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == lock) {
+ ret = true;
+ break;
+ };
+ }
+ }
+ return ret;
+}
+
+uint8_t avr_isp_read_fuse_low(AvrIsp* instance) {
+ furi_assert(instance);
+
+ uint8_t data = 0;
+ uint32_t starttime = furi_get_tick();
+ while((furi_get_tick() - starttime) < 300) {
+ data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW);
+ if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == data) {
+ break;
+ };
+ data = 0x00;
+ }
+ return data;
+}
+
+bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse) {
+ furi_assert(instance);
+
+ bool ret = false;
+ if(avr_isp_read_fuse_low(instance) == lfuse) {
+ ret = true;
+ } else {
+ avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_LOW(lfuse));
+ /* polling fuse */
+ uint32_t starttime = furi_get_tick();
+ while((furi_get_tick() - starttime) < 30) {
+ if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == lfuse) {
+ ret = true;
+ break;
+ };
+ }
+ }
+ return ret;
+}
+
+uint8_t avr_isp_read_fuse_high(AvrIsp* instance) {
+ furi_assert(instance);
+
+ uint8_t data = 0;
+ uint32_t starttime = furi_get_tick();
+ while((furi_get_tick() - starttime) < 300) {
+ data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH);
+ if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == data) {
+ break;
+ };
+ data = 0x00;
+ }
+ return data;
+}
+
+bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse) {
+ furi_assert(instance);
+
+ bool ret = false;
+ if(avr_isp_read_fuse_high(instance) == hfuse) {
+ ret = true;
+ } else {
+ avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_HIGH(hfuse));
+ /* polling fuse */
+ uint32_t starttime = furi_get_tick();
+ while((furi_get_tick() - starttime) < 30) {
+ if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == hfuse) {
+ ret = true;
+ break;
+ };
+ }
+ }
+ return ret;
+}
+
+uint8_t avr_isp_read_fuse_extended(AvrIsp* instance) {
+ furi_assert(instance);
+
+ uint8_t data = 0;
+ uint32_t starttime = furi_get_tick();
+ while((furi_get_tick() - starttime) < 300) {
+ data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED);
+ if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == data) {
+ break;
+ };
+ data = 0x00;
+ }
+ return data;
+}
+
+bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse) {
+ furi_assert(instance);
+
+ bool ret = false;
+ if(avr_isp_read_fuse_extended(instance) == efuse) {
+ ret = true;
+ } else {
+ avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_EXTENDED(efuse));
+ /* polling fuse */
+ uint32_t starttime = furi_get_tick();
+ while((furi_get_tick() - starttime) < 30) {
+ if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == efuse) {
+ ret = true;
+ break;
+ };
+ }
+ }
+ return ret;
+}
+
+void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr) {
+ furi_assert(instance);
+
+ avr_isp_spi_transaction(instance, AVR_ISP_EXTENDED_ADDR(extended_addr));
+ furi_delay_ms(10);
+}
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp.h b/applications/external/avr_isp_programmer/helpers/avr_isp.h
new file mode 100644
index 000000000..476fc3d64
--- /dev/null
+++ b/applications/external/avr_isp_programmer/helpers/avr_isp.h
@@ -0,0 +1,70 @@
+#pragma once
+
+#include
+
+typedef struct AvrIsp AvrIsp;
+typedef void (*AvrIspCallback)(void* context);
+
+struct AvrIspSignature {
+ uint8_t vendor;
+ uint8_t part_family;
+ uint8_t part_number;
+};
+
+typedef struct AvrIspSignature AvrIspSignature;
+
+AvrIsp* avr_isp_alloc(void);
+
+void avr_isp_free(AvrIsp* instance);
+
+void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context);
+
+bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance);
+
+AvrIspSignature avr_isp_read_signature(AvrIsp* instance);
+
+void avr_isp_end_pmode(AvrIsp* instance);
+
+bool avr_isp_erase_chip(AvrIsp* instance);
+
+uint8_t avr_isp_spi_transaction(
+ AvrIsp* instance,
+ uint8_t cmd,
+ uint8_t addr_hi,
+ uint8_t addr_lo,
+ uint8_t data);
+
+bool avr_isp_read_page(
+ AvrIsp* instance,
+ uint32_t memtype,
+ uint16_t addr,
+ uint16_t page_size,
+ uint8_t* data,
+ uint32_t data_size);
+
+bool avr_isp_write_page(
+ AvrIsp* instance,
+ uint32_t mem_type,
+ uint32_t mem_size,
+ uint16_t addr,
+ uint16_t page_size,
+ uint8_t* data,
+ uint32_t data_size);
+
+uint8_t avr_isp_read_lock_byte(AvrIsp* instance);
+
+bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock);
+
+uint8_t avr_isp_read_fuse_low(AvrIsp* instance);
+
+bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse);
+
+uint8_t avr_isp_read_fuse_high(AvrIsp* instance);
+
+bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse);
+
+uint8_t avr_isp_read_fuse_extended(AvrIsp* instance);
+
+bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse);
+
+void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr);
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_event.h b/applications/external/avr_isp_programmer/helpers/avr_isp_event.h
new file mode 100644
index 000000000..c704b5b35
--- /dev/null
+++ b/applications/external/avr_isp_programmer/helpers/avr_isp_event.h
@@ -0,0 +1,23 @@
+#pragma once
+
+typedef enum {
+ //SubmenuIndex
+ SubmenuIndexAvrIspProgrammer = 10,
+ SubmenuIndexAvrIspReader,
+ SubmenuIndexAvrIspWriter,
+ SubmenuIndexAvrIsWiring,
+ SubmenuIndexAvrIspAbout,
+
+ //AvrIspCustomEvent
+ AvrIspCustomEventSceneChipDetectOk = 100,
+ AvrIspCustomEventSceneReadingOk,
+ AvrIspCustomEventSceneWritingOk,
+ AvrIspCustomEventSceneErrorVerification,
+ AvrIspCustomEventSceneErrorReading,
+ AvrIspCustomEventSceneErrorWriting,
+ AvrIspCustomEventSceneErrorWritingFuse,
+ AvrIspCustomEventSceneInputName,
+ AvrIspCustomEventSceneSuccess,
+ AvrIspCustomEventSceneExit,
+ AvrIspCustomEventSceneExitStartMenu,
+} AvrIspCustomEvent;
diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_types.h b/applications/external/avr_isp_programmer/helpers/avr_isp_types.h
new file mode 100644
index 000000000..5e174ec3b
--- /dev/null
+++ b/applications/external/avr_isp_programmer/helpers/avr_isp_types.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include
+#include
+
+#define AVR_ISP_VERSION_APP "0.1"
+#define AVR_ISP_DEVELOPED "SkorP"
+#define AVR_ISP_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
+
+#define AVR_ISP_APP_FILE_VERSION 1
+#define AVR_ISP_APP_FILE_TYPE "Flipper Dump AVR"
+#define AVR_ISP_APP_EXTENSION ".avr"
+
+typedef enum {
+ //AvrIspViewVariableItemList,
+ AvrIspViewSubmenu,
+ AvrIspViewProgrammer,
+ AvrIspViewReader,
+ AvrIspViewWriter,
+ AvrIspViewWidget,
+ AvrIspViewPopup,
+ AvrIspViewTextInput,
+ AvrIspViewChipDetect,
+} AvrIspView;
+
+typedef enum {
+ AvrIspErrorNoError,
+ AvrIspErrorReading,
+ AvrIspErrorWriting,
+ AvrIspErrorVerification,
+ AvrIspErrorWritingFuse,
+} AvrIspError;
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c
new file mode 100644
index 000000000..dfe1f43c2
--- /dev/null
+++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.c
@@ -0,0 +1,266 @@
+#include "avr_isp_worker.h"
+#include
+#include "../lib/driver/avr_isp_prog.h"
+#include "../lib/driver/avr_isp_prog_cmd.h"
+#include "../lib/driver/avr_isp_chip_arr.h"
+
+#include
+
+#define TAG "AvrIspWorker"
+
+typedef enum {
+ AvrIspWorkerEvtStop = (1 << 0),
+
+ AvrIspWorkerEvtRx = (1 << 1),
+ AvrIspWorkerEvtTxCoplete = (1 << 2),
+ AvrIspWorkerEvtTx = (1 << 3),
+ AvrIspWorkerEvtState = (1 << 4),
+
+ //AvrIspWorkerEvtCfg = (1 << 5),
+
+} AvrIspWorkerEvt;
+
+struct AvrIspWorker {
+ FuriThread* thread;
+ volatile bool worker_running;
+ uint8_t connect_usb;
+ AvrIspWorkerCallback callback;
+ void* context;
+};
+
+#define AVR_ISP_WORKER_PROG_ALL_EVENTS (AvrIspWorkerEvtStop)
+#define AVR_ISP_WORKER_ALL_EVENTS \
+ (AvrIspWorkerEvtTx | AvrIspWorkerEvtTxCoplete | AvrIspWorkerEvtRx | AvrIspWorkerEvtStop | \
+ AvrIspWorkerEvtState)
+
+//########################/* VCP CDC */#############################################
+#include "usb_cdc.h"
+#include
+#include
+#include
+
+#define AVR_ISP_VCP_CDC_CH 1
+#define AVR_ISP_VCP_CDC_PKT_LEN CDC_DATA_SZ
+#define AVR_ISP_VCP_UART_RX_BUF_SIZE (AVR_ISP_VCP_CDC_PKT_LEN * 5)
+
+static void vcp_on_cdc_tx_complete(void* context);
+static void vcp_on_cdc_rx(void* context);
+static void vcp_state_callback(void* context, uint8_t state);
+static void vcp_on_cdc_control_line(void* context, uint8_t state);
+static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config);
+
+static const CdcCallbacks cdc_cb = {
+ vcp_on_cdc_tx_complete,
+ vcp_on_cdc_rx,
+ vcp_state_callback,
+ vcp_on_cdc_control_line,
+ vcp_on_line_config,
+};
+
+/* VCP callbacks */
+
+static void vcp_on_cdc_tx_complete(void* context) {
+ furi_assert(context);
+ AvrIspWorker* instance = context;
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTxCoplete);
+}
+
+static void vcp_on_cdc_rx(void* context) {
+ furi_assert(context);
+ AvrIspWorker* instance = context;
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx);
+}
+
+static void vcp_state_callback(void* context, uint8_t state) {
+ UNUSED(context);
+
+ AvrIspWorker* instance = context;
+ instance->connect_usb = state;
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtState);
+}
+
+static void vcp_on_cdc_control_line(void* context, uint8_t state) {
+ UNUSED(context);
+ UNUSED(state);
+}
+
+static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config) {
+ UNUSED(context);
+ UNUSED(config);
+}
+
+static void avr_isp_worker_vcp_cdc_init(void* context) {
+ furi_hal_usb_unlock();
+ Cli* cli = furi_record_open(RECORD_CLI);
+ //close cli
+ cli_session_close(cli);
+ //disable callbacks VCP_CDC=0
+ furi_hal_cdc_set_callbacks(0, NULL, NULL);
+ //set 2 cdc
+ furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true);
+ //open cli VCP_CDC=0
+ cli_session_open(cli, &cli_vcp);
+ furi_record_close(RECORD_CLI);
+
+ furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, (CdcCallbacks*)&cdc_cb, context);
+}
+
+static void avr_isp_worker_vcp_cdc_deinit(void) {
+ //disable callbacks AVR_ISP_VCP_CDC_CH
+ furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, NULL, NULL);
+
+ Cli* cli = furi_record_open(RECORD_CLI);
+ //close cli
+ cli_session_close(cli);
+ furi_hal_usb_unlock();
+ //set 1 cdc
+ furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
+ //open cli VCP_CDC=0
+ cli_session_open(cli, &cli_vcp);
+ furi_record_close(RECORD_CLI);
+}
+
+//#################################################################################
+
+static int32_t avr_isp_worker_prog_thread(void* context) {
+ AvrIspProg* prog = context;
+ FURI_LOG_D(TAG, "AvrIspProgWorker Start");
+ while(1) {
+ if(furi_thread_flags_get() & AvrIspWorkerEvtStop) break;
+ avr_isp_prog_avrisp(prog);
+ }
+ FURI_LOG_D(TAG, "AvrIspProgWorker Stop");
+ return 0;
+}
+
+static void avr_isp_worker_prog_tx_data(void* context) {
+ furi_assert(context);
+ AvrIspWorker* instance = context;
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTx);
+}
+
+/** Worker thread
+ *
+ * @param context
+ * @return exit code
+ */
+static int32_t avr_isp_worker_thread(void* context) {
+ AvrIspWorker* instance = context;
+ avr_isp_worker_vcp_cdc_init(instance);
+
+ /* start PWM on &gpio_ext_pa4 */
+ furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
+
+ AvrIspProg* prog = avr_isp_prog_init();
+ avr_isp_prog_set_tx_callback(prog, avr_isp_worker_prog_tx_data, instance);
+
+ uint8_t buf[AVR_ISP_VCP_UART_RX_BUF_SIZE];
+ size_t len = 0;
+
+ FuriThread* prog_thread =
+ furi_thread_alloc_ex("AvrIspProgWorker", 1024, avr_isp_worker_prog_thread, prog);
+ furi_thread_start(prog_thread);
+
+ FURI_LOG_D(TAG, "Start");
+
+ while(instance->worker_running) {
+ uint32_t events =
+ furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever);
+
+ if(events & AvrIspWorkerEvtRx) {
+ if(avr_isp_prog_spaces_rx(prog) >= AVR_ISP_VCP_CDC_PKT_LEN) {
+ len = furi_hal_cdc_receive(AVR_ISP_VCP_CDC_CH, buf, AVR_ISP_VCP_CDC_PKT_LEN);
+ // for(uint8_t i = 0; i < len; i++) {
+ // FURI_LOG_I(TAG, "--> %X", buf[i]);
+ // }
+ avr_isp_prog_rx(prog, buf, len);
+ } else {
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx);
+ }
+ }
+
+ if((events & AvrIspWorkerEvtTxCoplete) || (events & AvrIspWorkerEvtTx)) {
+ len = avr_isp_prog_tx(prog, buf, AVR_ISP_VCP_CDC_PKT_LEN);
+
+ // for(uint8_t i = 0; i < len; i++) {
+ // FURI_LOG_I(TAG, "<-- %X", buf[i]);
+ // }
+
+ if(len > 0) furi_hal_cdc_send(AVR_ISP_VCP_CDC_CH, buf, len);
+ }
+
+ if(events & AvrIspWorkerEvtStop) {
+ break;
+ }
+
+ if(events & AvrIspWorkerEvtState) {
+ if(instance->callback)
+ instance->callback(instance->context, (bool)instance->connect_usb);
+ }
+ }
+
+ FURI_LOG_D(TAG, "Stop");
+
+ furi_thread_flags_set(furi_thread_get_id(prog_thread), AvrIspWorkerEvtStop);
+ avr_isp_prog_exit(prog);
+ furi_delay_ms(10);
+ furi_thread_join(prog_thread);
+ furi_thread_free(prog_thread);
+
+ avr_isp_prog_free(prog);
+ furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
+ avr_isp_worker_vcp_cdc_deinit();
+ return 0;
+}
+
+AvrIspWorker* avr_isp_worker_alloc(void* context) {
+ furi_assert(context);
+ UNUSED(context);
+ AvrIspWorker* instance = malloc(sizeof(AvrIspWorker));
+
+ instance->thread = furi_thread_alloc_ex("AvrIspWorker", 2048, avr_isp_worker_thread, instance);
+ return instance;
+}
+
+void avr_isp_worker_free(AvrIspWorker* instance) {
+ furi_assert(instance);
+
+ furi_check(!instance->worker_running);
+ furi_thread_free(instance->thread);
+ free(instance);
+}
+
+void avr_isp_worker_set_callback(
+ AvrIspWorker* instance,
+ AvrIspWorkerCallback callback,
+ void* context) {
+ furi_assert(instance);
+
+ instance->callback = callback;
+ instance->context = context;
+}
+
+void avr_isp_worker_start(AvrIspWorker* instance) {
+ furi_assert(instance);
+ furi_assert(!instance->worker_running);
+
+ instance->worker_running = true;
+
+ furi_thread_start(instance->thread);
+}
+
+void avr_isp_worker_stop(AvrIspWorker* instance) {
+ furi_assert(instance);
+ furi_assert(instance->worker_running);
+
+ instance->worker_running = false;
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtStop);
+
+ furi_thread_join(instance->thread);
+}
+
+bool avr_isp_worker_is_running(AvrIspWorker* instance) {
+ furi_assert(instance);
+
+ return instance->worker_running;
+}
diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h
new file mode 100644
index 000000000..cd9897dff
--- /dev/null
+++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include
+
+typedef struct AvrIspWorker AvrIspWorker;
+
+typedef void (*AvrIspWorkerCallback)(void* context, bool connect_usb);
+
+/** Allocate AvrIspWorker
+ *
+ * @param context AvrIsp* context
+ * @return AvrIspWorker*
+ */
+AvrIspWorker* avr_isp_worker_alloc(void* context);
+
+/** Free AvrIspWorker
+ *
+ * @param instance AvrIspWorker instance
+ */
+void avr_isp_worker_free(AvrIspWorker* instance);
+
+/** Callback AvrIspWorker
+ *
+ * @param instance AvrIspWorker instance
+ * @param callback AvrIspWorkerOverrunCallback callback
+ * @param context
+ */
+void avr_isp_worker_set_callback(
+ AvrIspWorker* instance,
+ AvrIspWorkerCallback callback,
+ void* context);
+
+/** Start AvrIspWorker
+ *
+ * @param instance AvrIspWorker instance
+ */
+void avr_isp_worker_start(AvrIspWorker* instance);
+
+/** Stop AvrIspWorker
+ *
+ * @param instance AvrIspWorker instance
+ */
+void avr_isp_worker_stop(AvrIspWorker* instance);
+
+/** Check if worker is running
+ * @param instance AvrIspWorker instance
+ * @return bool - true if running
+ */
+bool avr_isp_worker_is_running(AvrIspWorker* instance);
diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c
new file mode 100644
index 000000000..209551a47
--- /dev/null
+++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.c
@@ -0,0 +1,1146 @@
+#include "avr_isp_worker_rw.h"
+#include
+#include "avr_isp_types.h"
+#include "avr_isp.h"
+#include "../lib/driver/avr_isp_prog_cmd.h"
+#include "../lib/driver/avr_isp_chip_arr.h"
+
+#include "flipper_i32hex_file.h"
+#include
+
+#include
+
+#define TAG "AvrIspWorkerRW"
+
+#define NAME_PATERN_FLASH_FILE "flash.hex"
+#define NAME_PATERN_EEPROM_FILE "eeprom.hex"
+
+struct AvrIspWorkerRW {
+ AvrIsp* avr_isp;
+ FuriThread* thread;
+ volatile bool worker_running;
+
+ uint32_t chip_arr_ind;
+ bool chip_detect;
+ uint8_t lfuse;
+ uint8_t hfuse;
+ uint8_t efuse;
+ uint8_t lock;
+ float progress_flash;
+ float progress_eeprom;
+ const char* file_path;
+ const char* file_name;
+ AvrIspSignature signature;
+ AvrIspWorkerRWCallback callback;
+ void* context;
+
+ AvrIspWorkerRWStatusCallback callback_status;
+ void* context_status;
+};
+
+typedef enum {
+ AvrIspWorkerRWEvtStop = (1 << 0),
+
+ AvrIspWorkerRWEvtReading = (1 << 1),
+ AvrIspWorkerRWEvtVerification = (1 << 2),
+ AvrIspWorkerRWEvtWriting = (1 << 3),
+ AvrIspWorkerRWEvtWritingFuse = (1 << 4),
+
+} AvrIspWorkerRWEvt;
+#define AVR_ISP_WORKER_ALL_EVENTS \
+ (AvrIspWorkerRWEvtWritingFuse | AvrIspWorkerRWEvtWriting | AvrIspWorkerRWEvtVerification | \
+ AvrIspWorkerRWEvtReading | AvrIspWorkerRWEvtStop)
+
+/** Worker thread
+ *
+ * @param context
+ * @return exit code
+ */
+static int32_t avr_isp_worker_rw_thread(void* context) {
+ AvrIspWorkerRW* instance = context;
+
+ /* start PWM on &gpio_ext_pa4 */
+ furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
+
+ FURI_LOG_D(TAG, "Start");
+
+ while(1) {
+ uint32_t events =
+ furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever);
+
+ if(events & AvrIspWorkerRWEvtStop) {
+ break;
+ }
+
+ if(events & AvrIspWorkerRWEvtWritingFuse) {
+ if(avr_isp_worker_rw_write_fuse(instance, instance->file_path, instance->file_name)) {
+ if(instance->callback_status)
+ instance->callback_status(
+ instance->context_status, AvrIspWorkerRWStatusEndWritingFuse);
+ } else {
+ if(instance->callback_status)
+ instance->callback_status(
+ instance->context_status, AvrIspWorkerRWStatusErrorWritingFuse);
+ }
+ }
+
+ if(events & AvrIspWorkerRWEvtWriting) {
+ if(avr_isp_worker_rw_write_dump(instance, instance->file_path, instance->file_name)) {
+ if(instance->callback_status)
+ instance->callback_status(
+ instance->context_status, AvrIspWorkerRWStatusEndWriting);
+ } else {
+ if(instance->callback_status)
+ instance->callback_status(
+ instance->context_status, AvrIspWorkerRWStatusErrorWriting);
+ }
+ }
+
+ if(events & AvrIspWorkerRWEvtVerification) {
+ if(avr_isp_worker_rw_verification(instance, instance->file_path, instance->file_name)) {
+ if(instance->callback_status)
+ instance->callback_status(
+ instance->context_status, AvrIspWorkerRWStatusEndVerification);
+ } else {
+ if(instance->callback_status)
+ instance->callback_status(
+ instance->context_status, AvrIspWorkerRWStatusErrorVerification);
+ }
+ }
+
+ if(events & AvrIspWorkerRWEvtReading) {
+ if(avr_isp_worker_rw_read_dump(instance, instance->file_path, instance->file_name)) {
+ if(instance->callback_status)
+ instance->callback_status(
+ instance->context_status, AvrIspWorkerRWStatusEndReading);
+ } else {
+ if(instance->callback_status)
+ instance->callback_status(
+ instance->context_status, AvrIspWorkerRWStatusErrorReading);
+ }
+ }
+ }
+ FURI_LOG_D(TAG, "Stop");
+
+ furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
+
+ return 0;
+}
+
+bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) {
+ furi_assert(instance);
+
+ FURI_LOG_D(TAG, "Detecting AVR chip");
+
+ instance->chip_detect = false;
+ instance->chip_arr_ind = avr_isp_chip_arr_size + 1;
+
+ /* start PWM on &gpio_ext_pa4 */
+ furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
+
+ do {
+ if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+ FURI_LOG_E(TAG, "Well, I managed to enter the mod program");
+ break;
+ }
+ instance->signature = avr_isp_read_signature(instance->avr_isp);
+
+ if(instance->signature.vendor != 0x1E) {
+ //No detect chip
+ } else {
+ for(uint32_t ind = 0; ind < avr_isp_chip_arr_size; ind++) {
+ if(avr_isp_chip_arr[ind].avrarch != F_AVR8) continue;
+ if(avr_isp_chip_arr[ind].sigs[1] == instance->signature.part_family) {
+ if(avr_isp_chip_arr[ind].sigs[2] == instance->signature.part_number) {
+ FURI_LOG_D(TAG, "Detect AVR chip = \"%s\"", avr_isp_chip_arr[ind].name);
+ FURI_LOG_D(
+ TAG,
+ "Signature = 0x%02X 0x%02X 0x%02X",
+ instance->signature.vendor,
+ instance->signature.part_family,
+ instance->signature.part_number);
+
+ switch(avr_isp_chip_arr[ind].nfuses) {
+ case 1:
+ instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp);
+ FURI_LOG_D(TAG, "Lfuse = %02X", instance->lfuse);
+ break;
+ case 2:
+ instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp);
+ instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp);
+ FURI_LOG_D(
+ TAG, "Lfuse = %02X Hfuse = %02X", instance->lfuse, instance->hfuse);
+ break;
+ case 3:
+ instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp);
+ instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp);
+ instance->efuse = avr_isp_read_fuse_extended(instance->avr_isp);
+ FURI_LOG_D(
+ TAG,
+ "Lfuse = %02X Hfuse = %02X Efuse = %02X",
+ instance->lfuse,
+ instance->hfuse,
+ instance->efuse);
+ break;
+ default:
+ break;
+ }
+ if(avr_isp_chip_arr[ind].nlocks == 1) {
+ instance->lock = avr_isp_read_lock_byte(instance->avr_isp);
+ FURI_LOG_D(TAG, "Lock = %02X", instance->lock);
+ }
+ instance->chip_detect = true;
+ instance->chip_arr_ind = ind;
+ break;
+ }
+ }
+ }
+ }
+ avr_isp_end_pmode(instance->avr_isp);
+
+ } while(0);
+
+ furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
+
+ if(instance->callback) {
+ if(instance->chip_arr_ind > avr_isp_chip_arr_size) {
+ instance->callback(instance->context, "No detect", instance->chip_detect, 0);
+ } else if(instance->chip_arr_ind < avr_isp_chip_arr_size) {
+ instance->callback(
+ instance->context,
+ avr_isp_chip_arr[instance->chip_arr_ind].name,
+ instance->chip_detect,
+ avr_isp_chip_arr[instance->chip_arr_ind].flashsize);
+ } else {
+ instance->callback(instance->context, "Unknown", instance->chip_detect, 0);
+ }
+ }
+
+ return instance->chip_detect;
+}
+
+AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context) {
+ furi_assert(context);
+ UNUSED(context);
+
+ AvrIspWorkerRW* instance = malloc(sizeof(AvrIspWorkerRW));
+ instance->avr_isp = avr_isp_alloc();
+
+ instance->thread =
+ furi_thread_alloc_ex("AvrIspWorkerRW", 4096, avr_isp_worker_rw_thread, instance);
+
+ instance->chip_detect = false;
+ instance->lfuse = 0;
+ instance->hfuse = 0;
+ instance->efuse = 0;
+ instance->lock = 0;
+ instance->progress_flash = 0.0f;
+ instance->progress_eeprom = 0.0f;
+
+ return instance;
+}
+
+void avr_isp_worker_rw_free(AvrIspWorkerRW* instance) {
+ furi_assert(instance);
+
+ avr_isp_free(instance->avr_isp);
+
+ furi_check(!instance->worker_running);
+ furi_thread_free(instance->thread);
+
+ free(instance);
+}
+
+void avr_isp_worker_rw_start(AvrIspWorkerRW* instance) {
+ furi_assert(instance);
+ furi_assert(!instance->worker_running);
+
+ instance->worker_running = true;
+
+ furi_thread_start(instance->thread);
+}
+
+void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance) {
+ furi_assert(instance);
+ furi_assert(instance->worker_running);
+
+ instance->worker_running = false;
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtStop);
+
+ furi_thread_join(instance->thread);
+}
+
+bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance) {
+ furi_assert(instance);
+
+ return instance->worker_running;
+}
+
+void avr_isp_worker_rw_set_callback(
+ AvrIspWorkerRW* instance,
+ AvrIspWorkerRWCallback callback,
+ void* context) {
+ furi_assert(instance);
+
+ instance->callback = callback;
+ instance->context = context;
+}
+
+void avr_isp_worker_rw_set_callback_status(
+ AvrIspWorkerRW* instance,
+ AvrIspWorkerRWStatusCallback callback_status,
+ void* context_status) {
+ furi_assert(instance);
+
+ instance->callback_status = callback_status;
+ instance->context_status = context_status;
+}
+
+float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance) {
+ furi_assert(instance);
+
+ return instance->progress_flash;
+}
+
+float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance) {
+ furi_assert(instance);
+
+ return instance->progress_eeprom;
+}
+
+static void avr_isp_worker_rw_get_dump_flash(AvrIspWorkerRW* instance, const char* file_path) {
+ furi_assert(instance);
+ furi_check(instance->avr_isp);
+
+ FURI_LOG_D(TAG, "Dump FLASH %s", file_path);
+
+ FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_write(
+ file_path, avr_isp_chip_arr[instance->chip_arr_ind].flashoffset);
+
+ uint8_t data[272] = {0};
+ bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000);
+ uint8_t extended_addr = 0;
+
+ for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset;
+ i < avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2;
+ i += avr_isp_chip_arr[instance->chip_arr_ind].pagesize / 2) {
+ if(send_extended_addr) {
+ if(extended_addr <= ((i >> 16) & 0xFF)) {
+ avr_isp_write_extended_addr(instance->avr_isp, extended_addr);
+ extended_addr = ((i >> 16) & 0xFF) + 1;
+ }
+ }
+ avr_isp_read_page(
+ instance->avr_isp,
+ STK_SET_FLASH_TYPE,
+ (uint16_t)i,
+ avr_isp_chip_arr[instance->chip_arr_ind].pagesize,
+ data,
+ sizeof(data));
+ flipper_i32hex_file_bin_to_i32hex_set_data(
+ flipper_hex_flash, data, avr_isp_chip_arr[instance->chip_arr_ind].pagesize);
+ //FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash));
+ instance->progress_flash =
+ (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f);
+ }
+ flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_flash);
+ //FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash));
+ flipper_i32hex_file_close(flipper_hex_flash);
+ instance->progress_flash = 1.0f;
+}
+
+static void avr_isp_worker_rw_get_dump_eeprom(AvrIspWorkerRW* instance, const char* file_path) {
+ furi_assert(instance);
+ furi_check(instance->avr_isp);
+
+ FURI_LOG_D(TAG, "Dump EEPROM %s", file_path);
+
+ FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_write(
+ file_path, avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset);
+
+ int32_t size_data = 32;
+ uint8_t data[256] = {0};
+
+ if(size_data > avr_isp_chip_arr[instance->chip_arr_ind].eepromsize)
+ size_data = avr_isp_chip_arr[instance->chip_arr_ind].eepromsize;
+
+ for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset;
+ i < avr_isp_chip_arr[instance->chip_arr_ind].eepromsize;
+ i += size_data) {
+ avr_isp_read_page(
+ instance->avr_isp, STK_SET_EEPROM_TYPE, (uint16_t)i, size_data, data, sizeof(data));
+ flipper_i32hex_file_bin_to_i32hex_set_data(flipper_hex_eeprom, data, size_data);
+ FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom));
+ instance->progress_eeprom =
+ (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize);
+ }
+ flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_eeprom);
+ FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom));
+ flipper_i32hex_file_close(flipper_hex_eeprom);
+ instance->progress_eeprom = 1.0f;
+}
+
+bool avr_isp_worker_rw_read_dump(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name) {
+ furi_assert(instance);
+ furi_assert(file_path);
+ furi_assert(file_name);
+
+ FURI_LOG_D(TAG, "Read dump chip");
+
+ instance->progress_flash = 0.0f;
+ instance->progress_eeprom = 0.0f;
+ bool ret = false;
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+ FuriString* file_path_name = furi_string_alloc();
+
+ if(!avr_isp_worker_rw_detect_chip(instance)) {
+ FURI_LOG_E(TAG, "No detect AVR chip");
+ } else {
+ do {
+ furi_string_printf(
+ file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION);
+ if(!flipper_format_file_open_always(
+ flipper_format, furi_string_get_cstr(file_path_name))) {
+ FURI_LOG_E(TAG, "flipper_format_file_open_always");
+ break;
+ }
+ if(!flipper_format_write_header_cstr(
+ flipper_format, AVR_ISP_APP_FILE_TYPE, AVR_ISP_APP_FILE_VERSION)) {
+ FURI_LOG_E(TAG, "flipper_format_write_header_cstr");
+ break;
+ }
+ if(!flipper_format_write_string_cstr(
+ flipper_format, "Chip name", avr_isp_chip_arr[instance->chip_arr_ind].name)) {
+ FURI_LOG_E(TAG, "Chip name");
+ break;
+ }
+ if(!flipper_format_write_hex(
+ flipper_format,
+ "Signature",
+ (uint8_t*)&instance->signature,
+ sizeof(AvrIspSignature))) {
+ FURI_LOG_E(TAG, "Unable to add Signature");
+ break;
+ }
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) {
+ if(!flipper_format_write_hex(flipper_format, "Lfuse", &instance->lfuse, 1)) {
+ FURI_LOG_E(TAG, "Unable to add Lfuse");
+ break;
+ }
+ }
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) {
+ if(!flipper_format_write_hex(flipper_format, "Hfuse", &instance->hfuse, 1)) {
+ FURI_LOG_E(TAG, "Unable to add Hfuse");
+ break;
+ }
+ }
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) {
+ if(!flipper_format_write_hex(flipper_format, "Efuse", &instance->efuse, 1)) {
+ FURI_LOG_E(TAG, "Unable to add Efuse");
+ break;
+ }
+ }
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) {
+ if(!flipper_format_write_hex(flipper_format, "Lock", &instance->lock, 1)) {
+ FURI_LOG_E(TAG, "Unable to add Lock");
+ break;
+ }
+ }
+ furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_FLASH_FILE);
+ if(!flipper_format_write_string_cstr(
+ flipper_format, "Dump_flash", furi_string_get_cstr(file_path_name))) {
+ FURI_LOG_E(TAG, "Unable to add Dump_flash");
+ break;
+ }
+
+ if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+ furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_EEPROM_FILE);
+ if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+ if(!flipper_format_write_string_cstr(
+ flipper_format, "Dump_eeprom", furi_string_get_cstr(file_path_name))) {
+ FURI_LOG_E(TAG, "Unable to add Dump_eeprom");
+ break;
+ }
+ }
+ }
+ ret = true;
+ } while(false);
+ }
+
+ flipper_format_free(flipper_format);
+ furi_record_close(RECORD_STORAGE);
+
+ if(ret) {
+ if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+ //Dump flash
+ furi_string_printf(
+ file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE);
+ avr_isp_worker_rw_get_dump_flash(instance, furi_string_get_cstr(file_path_name));
+ //Dump eeprom
+ if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+ furi_string_printf(
+ file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE);
+ avr_isp_worker_rw_get_dump_eeprom(instance, furi_string_get_cstr(file_path_name));
+ }
+
+ avr_isp_end_pmode(instance->avr_isp);
+ }
+ }
+
+ furi_string_free(file_path_name);
+
+ return true;
+}
+
+void avr_isp_worker_rw_read_dump_start(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name) {
+ furi_assert(instance);
+
+ instance->file_path = file_path;
+ instance->file_name = file_name;
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtReading);
+}
+
+static bool avr_isp_worker_rw_verification_flash(AvrIspWorkerRW* instance, const char* file_path) {
+ furi_assert(instance);
+ furi_assert(file_path);
+
+ FURI_LOG_D(TAG, "Verification flash %s", file_path);
+
+ instance->progress_flash = 0.0;
+ bool ret = true;
+
+ FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path);
+
+ uint8_t data_read_flash[272] = {0};
+ uint8_t data_read_hex[272] = {0};
+
+ uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset;
+ bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000);
+ uint8_t extended_addr = 0;
+
+ FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+ flipper_hex_flash, data_read_hex, sizeof(data_read_hex));
+
+ while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+ (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) &&
+ ret) {
+ switch(flipper_hex_ret.status) {
+ case FlipperI32HexFileStatusData:
+
+ if(send_extended_addr) {
+ if(extended_addr <= ((addr >> 16) & 0xFF)) {
+ avr_isp_write_extended_addr(instance->avr_isp, extended_addr);
+ extended_addr = ((addr >> 16) & 0xFF) + 1;
+ }
+ }
+
+ avr_isp_read_page(
+ instance->avr_isp,
+ STK_SET_FLASH_TYPE,
+ (uint16_t)addr,
+ flipper_hex_ret.data_size,
+ data_read_flash,
+ sizeof(data_read_flash));
+
+ if(memcmp(data_read_hex, data_read_flash, flipper_hex_ret.data_size) != 0) {
+ ret = false;
+
+ FURI_LOG_E(TAG, "Verification flash error");
+ FURI_LOG_E(TAG, "Addr: 0x%04lX", addr);
+ for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+ FURI_LOG_RAW_E("%02X ", data_read_hex[i]);
+ }
+ FURI_LOG_RAW_E("\r\n");
+ for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+ FURI_LOG_RAW_E("%02X ", data_read_flash[i]);
+ }
+ FURI_LOG_RAW_E("\r\n");
+ }
+
+ addr += flipper_hex_ret.data_size / 2;
+ instance->progress_flash =
+ (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f);
+ break;
+
+ case FlipperI32HexFileStatusUdateAddr:
+ addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16) / 2;
+ break;
+
+ default:
+ furi_crash(TAG " Incorrect status.");
+ break;
+ }
+
+ flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+ flipper_hex_flash, data_read_hex, sizeof(data_read_hex));
+ }
+
+ flipper_i32hex_file_close(flipper_hex_flash);
+ instance->progress_flash = 1.0f;
+
+ return ret;
+}
+
+static bool
+ avr_isp_worker_rw_verification_eeprom(AvrIspWorkerRW* instance, const char* file_path) {
+ furi_assert(instance);
+ furi_assert(file_path);
+
+ FURI_LOG_D(TAG, "Verification eeprom %s", file_path);
+
+ instance->progress_eeprom = 0.0;
+ bool ret = true;
+
+ FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_read(file_path);
+
+ uint8_t data_read_eeprom[272] = {0};
+ uint8_t data_read_hex[272] = {0};
+
+ uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset;
+
+ FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+ flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex));
+
+ while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+ (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) &&
+ ret) {
+ switch(flipper_hex_ret.status) {
+ case FlipperI32HexFileStatusData:
+ avr_isp_read_page(
+ instance->avr_isp,
+ STK_SET_EEPROM_TYPE,
+ (uint16_t)addr,
+ flipper_hex_ret.data_size,
+ data_read_eeprom,
+ sizeof(data_read_eeprom));
+
+ if(memcmp(data_read_hex, data_read_eeprom, flipper_hex_ret.data_size) != 0) {
+ ret = false;
+ FURI_LOG_E(TAG, "Verification eeprom error");
+ FURI_LOG_E(TAG, "Addr: 0x%04lX", addr);
+ for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+ FURI_LOG_RAW_E("%02X ", data_read_hex[i]);
+ }
+ FURI_LOG_RAW_E("\r\n");
+ for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+ FURI_LOG_RAW_E("%02X ", data_read_eeprom[i]);
+ }
+ FURI_LOG_RAW_E("\r\n");
+ }
+
+ addr += flipper_hex_ret.data_size;
+ instance->progress_eeprom =
+ (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize);
+ break;
+
+ case FlipperI32HexFileStatusUdateAddr:
+ addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16);
+ break;
+
+ default:
+ furi_crash(TAG " Incorrect status.");
+ break;
+ }
+
+ flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+ flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex));
+ }
+
+ flipper_i32hex_file_close(flipper_hex_eeprom);
+ instance->progress_eeprom = 1.0f;
+
+ return ret;
+}
+
+bool avr_isp_worker_rw_verification(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name) {
+ furi_assert(instance);
+ furi_assert(file_path);
+ furi_assert(file_name);
+
+ FURI_LOG_D(TAG, "Verification chip");
+
+ instance->progress_flash = 0.0f;
+ instance->progress_eeprom = 0.0f;
+ FuriString* file_path_name = furi_string_alloc();
+
+ bool ret = false;
+
+ if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+ do {
+ furi_string_printf(
+ file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE);
+ if(!avr_isp_worker_rw_verification_flash(
+ instance, furi_string_get_cstr(file_path_name)))
+ break;
+
+ if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+ furi_string_printf(
+ file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE);
+
+ if(!avr_isp_worker_rw_verification_eeprom(
+ instance, furi_string_get_cstr(file_path_name)))
+ break;
+ }
+ ret = true;
+ } while(false);
+ avr_isp_end_pmode(instance->avr_isp);
+ furi_string_free(file_path_name);
+ }
+ return ret;
+}
+
+void avr_isp_worker_rw_verification_start(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name) {
+ furi_assert(instance);
+
+ instance->file_path = file_path;
+ instance->file_name = file_name;
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtVerification);
+}
+
+static void avr_isp_worker_rw_write_flash(AvrIspWorkerRW* instance, const char* file_path) {
+ furi_assert(instance);
+ furi_check(instance->avr_isp);
+
+ instance->progress_flash = 0.0;
+
+ FURI_LOG_D(TAG, "Write Flash %s", file_path);
+
+ uint8_t data[288] = {0};
+
+ FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path);
+
+ uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset;
+ bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000);
+ uint8_t extended_addr = 0;
+
+ FlipperI32HexFileRet flipper_hex_ret =
+ flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data));
+
+ while((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+ (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) {
+ switch(flipper_hex_ret.status) {
+ case FlipperI32HexFileStatusData:
+
+ if(send_extended_addr) {
+ if(extended_addr <= ((addr >> 16) & 0xFF)) {
+ avr_isp_write_extended_addr(instance->avr_isp, extended_addr);
+ extended_addr = ((addr >> 16) & 0xFF) + 1;
+ }
+ }
+
+ if(!avr_isp_write_page(
+ instance->avr_isp,
+ STK_SET_FLASH_TYPE,
+ avr_isp_chip_arr[instance->chip_arr_ind].flashsize,
+ (uint16_t)addr,
+ avr_isp_chip_arr[instance->chip_arr_ind].pagesize,
+ data,
+ flipper_hex_ret.data_size)) {
+ break;
+ }
+ addr += flipper_hex_ret.data_size / 2;
+ instance->progress_flash =
+ (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f);
+ break;
+
+ case FlipperI32HexFileStatusUdateAddr:
+ addr = (data[0] << 24 | data[1] << 16) / 2;
+ break;
+
+ default:
+ furi_crash(TAG " Incorrect status.");
+ break;
+ }
+
+ flipper_hex_ret =
+ flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data));
+ }
+
+ flipper_i32hex_file_close(flipper_hex_flash);
+ instance->progress_flash = 1.0f;
+}
+
+static void avr_isp_worker_rw_write_eeprom(AvrIspWorkerRW* instance, const char* file_path) {
+ furi_assert(instance);
+ furi_check(instance->avr_isp);
+
+ instance->progress_eeprom = 0.0;
+ uint8_t data[288] = {0};
+
+ FURI_LOG_D(TAG, "Write EEPROM %s", file_path);
+
+ FlipperI32HexFile* flipper_hex_eeprom_read = flipper_i32hex_file_open_read(file_path);
+
+ uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset;
+ FlipperI32HexFileRet flipper_hex_ret =
+ flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_eeprom_read, data, sizeof(data));
+
+ while((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+ (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) {
+ switch(flipper_hex_ret.status) {
+ case FlipperI32HexFileStatusData:
+ if(!avr_isp_write_page(
+ instance->avr_isp,
+ STK_SET_EEPROM_TYPE,
+ avr_isp_chip_arr[instance->chip_arr_ind].eepromsize,
+ (uint16_t)addr,
+ avr_isp_chip_arr[instance->chip_arr_ind].eeprompagesize,
+ data,
+ flipper_hex_ret.data_size)) {
+ break;
+ }
+ addr += flipper_hex_ret.data_size;
+ instance->progress_eeprom =
+ (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize);
+ break;
+
+ case FlipperI32HexFileStatusUdateAddr:
+ addr = data[0] << 24 | data[1] << 16;
+ break;
+
+ default:
+ furi_crash(TAG " Incorrect status.");
+ break;
+ }
+
+ flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+ flipper_hex_eeprom_read, data, sizeof(data));
+ }
+
+ flipper_i32hex_file_close(flipper_hex_eeprom_read);
+ instance->progress_eeprom = 1.0f;
+}
+
+bool avr_isp_worker_rw_write_dump(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name) {
+ furi_assert(instance);
+ furi_assert(file_path);
+ furi_assert(file_name);
+
+ FURI_LOG_D(TAG, "Write dump chip");
+
+ instance->progress_flash = 0.0f;
+ instance->progress_eeprom = 0.0f;
+ bool ret = false;
+
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+ FuriString* file_path_name = furi_string_alloc();
+
+ FuriString* temp_str_1 = furi_string_alloc();
+ FuriString* temp_str_2 = furi_string_alloc();
+ uint32_t temp_data32;
+
+ if(!avr_isp_worker_rw_detect_chip(instance)) {
+ FURI_LOG_E(TAG, "No detect AVR chip");
+ } else {
+ //upload file with description
+ do {
+ furi_string_printf(
+ file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION);
+ if(!flipper_format_file_open_existing(
+ flipper_format, furi_string_get_cstr(file_path_name))) {
+ FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(file_path_name));
+ break;
+ }
+
+ if(!flipper_format_read_header(flipper_format, temp_str_1, &temp_data32)) {
+ FURI_LOG_E(TAG, "Missing or incorrect header");
+ break;
+ }
+
+ if((!strcmp(furi_string_get_cstr(temp_str_1), AVR_ISP_APP_FILE_TYPE)) &&
+ temp_data32 == AVR_ISP_APP_FILE_VERSION) {
+ } else {
+ FURI_LOG_E(TAG, "Type or version mismatch");
+ break;
+ }
+
+ AvrIspSignature sig_read = {0};
+
+ if(!flipper_format_read_hex(
+ flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) {
+ FURI_LOG_E(TAG, "Missing Signature");
+ break;
+ }
+
+ if(memcmp(
+ (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) !=
+ 0) {
+ FURI_LOG_E(
+ TAG,
+ "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)",
+ instance->signature.vendor,
+ instance->signature.part_family,
+ instance->signature.part_number,
+ sig_read.vendor,
+ sig_read.part_family,
+ sig_read.part_number);
+ break;
+ }
+
+ if(!flipper_format_read_string(flipper_format, "Dump_flash", temp_str_1)) {
+ FURI_LOG_E(TAG, "Missing Dump_flash");
+ break;
+ }
+
+ //may not be
+ flipper_format_read_string(flipper_format, "Dump_eeprom", temp_str_2);
+ ret = true;
+ } while(false);
+ }
+ flipper_format_free(flipper_format);
+ furi_record_close(RECORD_STORAGE);
+
+ if(ret) {
+ do {
+ //checking .hex files for errors
+
+ furi_string_printf(
+ file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1));
+
+ FURI_LOG_D(TAG, "Check flash file");
+ FlipperI32HexFile* flipper_hex_flash_read =
+ flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name));
+ if(flipper_i32hex_file_check(flipper_hex_flash_read)) {
+ FURI_LOG_D(TAG, "Check flash file: OK");
+ } else {
+ FURI_LOG_E(TAG, "Check flash file: Error");
+ ret = false;
+ }
+ flipper_i32hex_file_close(flipper_hex_flash_read);
+
+ if(furi_string_size(temp_str_2) > 4) {
+ furi_string_printf(
+ file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2));
+
+ FURI_LOG_D(TAG, "Check eeprom file");
+ FlipperI32HexFile* flipper_hex_eeprom_read =
+ flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name));
+ if(flipper_i32hex_file_check(flipper_hex_eeprom_read)) {
+ FURI_LOG_D(TAG, "Check eeprom file: OK");
+ } else {
+ FURI_LOG_E(TAG, "Check eeprom file: Error");
+ ret = false;
+ }
+ flipper_i32hex_file_close(flipper_hex_eeprom_read);
+ }
+
+ if(!ret) break;
+ ret = false;
+
+ //erase chip
+ FURI_LOG_D(TAG, "Erase chip");
+ if(!avr_isp_erase_chip(instance->avr_isp)) {
+ FURI_LOG_E(TAG, "Erase chip: Error");
+ break;
+ }
+
+ if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+ FURI_LOG_E(TAG, "Well, I managed to enter the mod program");
+ break;
+ }
+
+ //write flash
+ furi_string_printf(
+ file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1));
+ avr_isp_worker_rw_write_flash(instance, furi_string_get_cstr(file_path_name));
+
+ //write eeprom
+ if(furi_string_size(temp_str_2) > 4) {
+ furi_string_printf(
+ file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2));
+ avr_isp_worker_rw_write_eeprom(instance, furi_string_get_cstr(file_path_name));
+ }
+ ret = true;
+ avr_isp_end_pmode(instance->avr_isp);
+ } while(false);
+ }
+
+ furi_string_free(file_path_name);
+ furi_string_free(temp_str_1);
+ furi_string_free(temp_str_2);
+
+ return ret;
+}
+
+void avr_isp_worker_rw_write_dump_start(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name) {
+ furi_assert(instance);
+
+ instance->file_path = file_path;
+ instance->file_name = file_name;
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWriting);
+}
+
+bool avr_isp_worker_rw_write_fuse(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name) {
+ furi_assert(instance);
+ furi_assert(file_path);
+ furi_assert(file_name);
+
+ FURI_LOG_D(TAG, "Write fuse chip");
+
+ bool ret = false;
+ uint8_t lfuse;
+ uint8_t hfuse;
+ uint8_t efuse;
+ uint8_t lock;
+
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+ FuriString* temp_str = furi_string_alloc();
+
+ uint32_t temp_data32;
+
+ if(!avr_isp_worker_rw_detect_chip(instance)) {
+ FURI_LOG_E(TAG, "No detect AVR chip");
+ } else {
+ //upload file with description
+ do {
+ furi_string_printf(temp_str, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION);
+ if(!flipper_format_file_open_existing(flipper_format, furi_string_get_cstr(temp_str))) {
+ FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(temp_str));
+ break;
+ }
+
+ if(!flipper_format_read_header(flipper_format, temp_str, &temp_data32)) {
+ FURI_LOG_E(TAG, "Missing or incorrect header");
+ break;
+ }
+
+ if((!strcmp(furi_string_get_cstr(temp_str), AVR_ISP_APP_FILE_TYPE)) &&
+ temp_data32 == AVR_ISP_APP_FILE_VERSION) {
+ } else {
+ FURI_LOG_E(TAG, "Type or version mismatch");
+ break;
+ }
+
+ AvrIspSignature sig_read = {0};
+
+ if(!flipper_format_read_hex(
+ flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) {
+ FURI_LOG_E(TAG, "Missing Signature");
+ break;
+ }
+
+ if(memcmp(
+ (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) !=
+ 0) {
+ FURI_LOG_E(
+ TAG,
+ "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)",
+ instance->signature.vendor,
+ instance->signature.part_family,
+ instance->signature.part_number,
+ sig_read.vendor,
+ sig_read.part_family,
+ sig_read.part_number);
+ break;
+ }
+
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) {
+ if(!flipper_format_read_hex(flipper_format, "Lfuse", &lfuse, 1)) {
+ FURI_LOG_E(TAG, "Missing Lfuse");
+ break;
+ }
+ }
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) {
+ if(!flipper_format_read_hex(flipper_format, "Hfuse", &hfuse, 1)) {
+ FURI_LOG_E(TAG, "Missing Hfuse");
+ break;
+ }
+ }
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) {
+ if(!flipper_format_read_hex(flipper_format, "Efuse", &efuse, 1)) {
+ FURI_LOG_E(TAG, "Missing Efuse");
+ break;
+ }
+ }
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) {
+ if(!flipper_format_read_hex(flipper_format, "Lock", &lock, 1)) {
+ FURI_LOG_E(TAG, "Missing Lock");
+ break;
+ }
+ }
+
+ if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+ FURI_LOG_E(TAG, "Well, I managed to enter the mod program");
+ break;
+ }
+
+ ret = true;
+
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) {
+ if(instance->lfuse != lfuse) {
+ if(!avr_isp_write_fuse_low(instance->avr_isp, lfuse)) {
+ FURI_LOG_E(TAG, "Write Lfuse: error");
+ ret = false;
+ }
+ }
+ }
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) {
+ if(instance->hfuse != hfuse) {
+ if(!avr_isp_write_fuse_high(instance->avr_isp, hfuse)) {
+ FURI_LOG_E(TAG, "Write Hfuse: error");
+ ret = false;
+ }
+ }
+ }
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) {
+ if(instance->efuse != efuse) {
+ if(!avr_isp_write_fuse_extended(instance->avr_isp, efuse)) {
+ FURI_LOG_E(TAG, "Write Efuse: error");
+ ret = false;
+ }
+ }
+ }
+
+ if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) {
+ FURI_LOG_D(TAG, "Write lock byte");
+ if(instance->lock != lock) {
+ if(!avr_isp_write_lock_byte(instance->avr_isp, lock)) {
+ FURI_LOG_E(TAG, "Write Lock byte: error");
+ ret = false;
+ }
+ }
+ }
+ avr_isp_end_pmode(instance->avr_isp);
+ } while(false);
+ }
+
+ flipper_format_free(flipper_format);
+ furi_record_close(RECORD_STORAGE);
+ furi_string_free(temp_str);
+ return ret;
+}
+
+void avr_isp_worker_rw_write_fuse_start(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name) {
+ furi_assert(instance);
+
+ instance->file_path = file_path;
+ instance->file_name = file_name;
+ furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWritingFuse);
+}
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h
new file mode 100644
index 000000000..2c52a8700
--- /dev/null
+++ b/applications/external/avr_isp_programmer/helpers/avr_isp_worker_rw.h
@@ -0,0 +1,99 @@
+#pragma once
+
+#include
+
+typedef struct AvrIspWorkerRW AvrIspWorkerRW;
+
+typedef void (*AvrIspWorkerRWCallback)(
+ void* context,
+ const char* name,
+ bool detect_chip,
+ uint32_t flash_size);
+
+typedef enum {
+ AvrIspWorkerRWStatusILDE = 0,
+ AvrIspWorkerRWStatusEndReading = 1,
+ AvrIspWorkerRWStatusEndVerification = 2,
+ AvrIspWorkerRWStatusEndWriting = 3,
+ AvrIspWorkerRWStatusEndWritingFuse = 4,
+
+ AvrIspWorkerRWStatusErrorReading = (-1),
+ AvrIspWorkerRWStatusErrorVerification = (-2),
+ AvrIspWorkerRWStatusErrorWriting = (-3),
+ AvrIspWorkerRWStatusErrorWritingFuse = (-4),
+
+ AvrIspWorkerRWStatusReserved = 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization.
+} AvrIspWorkerRWStatus;
+
+typedef void (*AvrIspWorkerRWStatusCallback)(void* context, AvrIspWorkerRWStatus status);
+
+AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context);
+
+void avr_isp_worker_rw_free(AvrIspWorkerRW* instance);
+
+void avr_isp_worker_rw_start(AvrIspWorkerRW* instance);
+
+void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance);
+
+bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance);
+
+void avr_isp_worker_rw_set_callback(
+ AvrIspWorkerRW* instance,
+ AvrIspWorkerRWCallback callback,
+ void* context);
+
+void avr_isp_worker_rw_set_callback_status(
+ AvrIspWorkerRW* instance,
+ AvrIspWorkerRWStatusCallback callback_status,
+ void* context_status);
+
+bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance);
+
+float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance);
+
+float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance);
+
+bool avr_isp_worker_rw_read_dump(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name);
+
+void avr_isp_worker_rw_read_dump_start(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name);
+
+bool avr_isp_worker_rw_verification(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name);
+
+void avr_isp_worker_rw_verification_start(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name);
+
+bool avr_isp_worker_rw_check_hex(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name);
+
+bool avr_isp_worker_rw_write_dump(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name);
+
+void avr_isp_worker_rw_write_dump_start(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name);
+
+bool avr_isp_worker_rw_write_fuse(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name);
+
+void avr_isp_worker_rw_write_fuse_start(
+ AvrIspWorkerRW* instance,
+ const char* file_path,
+ const char* file_name);
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c
new file mode 100644
index 000000000..a8c20a0bd
--- /dev/null
+++ b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.c
@@ -0,0 +1,321 @@
+#include "flipper_i32hex_file.h"
+#include
+#include
+#include
+#include
+#include
+
+//https://en.wikipedia.org/wiki/Intel_HEX
+
+#define TAG "FlipperI32HexFile"
+
+#define COUNT_BYTE_PAYLOAD 32 //how much payload will be used
+
+#define I32HEX_TYPE_DATA 0x00
+#define I32HEX_TYPE_END_OF_FILE 0x01
+#define I32HEX_TYPE_EXT_LINEAR_ADDR 0x04
+#define I32HEX_TYPE_START_LINEAR_ADDR 0x05
+
+struct FlipperI32HexFile {
+ uint32_t addr;
+ uint32_t addr_last;
+ Storage* storage;
+ Stream* stream;
+ FuriString* str_data;
+ FlipperI32HexFileStatus file_open;
+};
+
+FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr) {
+ furi_assert(name);
+
+ FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile));
+ instance->addr = start_addr;
+ instance->addr_last = 0;
+ instance->storage = furi_record_open(RECORD_STORAGE);
+ instance->stream = file_stream_alloc(instance->storage);
+
+ if(file_stream_open(instance->stream, name, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+ instance->file_open = FlipperI32HexFileStatusOpenFileWrite;
+ FURI_LOG_D(TAG, "Open write file %s", name);
+ } else {
+ FURI_LOG_E(TAG, "Failed to open file %s", name);
+ instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile;
+ }
+ instance->str_data = furi_string_alloc(instance->storage);
+
+ return instance;
+}
+
+FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name) {
+ furi_assert(name);
+
+ FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile));
+ instance->addr = 0;
+ instance->addr_last = 0;
+ instance->storage = furi_record_open(RECORD_STORAGE);
+ instance->stream = file_stream_alloc(instance->storage);
+
+ if(file_stream_open(instance->stream, name, FSAM_READ, FSOM_OPEN_EXISTING)) {
+ instance->file_open = FlipperI32HexFileStatusOpenFileRead;
+ FURI_LOG_D(TAG, "Open read file %s", name);
+ } else {
+ FURI_LOG_E(TAG, "Failed to open file %s", name);
+ instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile;
+ }
+ instance->str_data = furi_string_alloc(instance->storage);
+
+ return instance;
+}
+
+void flipper_i32hex_file_close(FlipperI32HexFile* instance) {
+ furi_assert(instance);
+
+ furi_string_free(instance->str_data);
+ file_stream_close(instance->stream);
+ stream_free(instance->stream);
+ furi_record_close(RECORD_STORAGE);
+}
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data(
+ FlipperI32HexFile* instance,
+ uint8_t* data,
+ uint32_t data_size) {
+ furi_assert(instance);
+ furi_assert(data);
+
+ FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0};
+ if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) {
+ ret.status = FlipperI32HexFileStatusErrorFileWrite;
+ }
+ uint8_t count_byte = 0;
+ uint32_t ind = 0;
+ uint8_t crc = 0;
+
+ furi_string_reset(instance->str_data);
+
+ if((instance->addr_last & 0xFF0000) < (instance->addr & 0xFF0000)) {
+ crc = 0x02 + 0x04 + ((instance->addr >> 24) & 0xFF) + ((instance->addr >> 16) & 0xFF);
+ crc = 0x01 + ~crc;
+ //I32HEX_TYPE_EXT_LINEAR_ADDR
+ furi_string_cat_printf(
+ instance->str_data, ":02000004%04lX%02X\r\n", (instance->addr >> 16), crc);
+ instance->addr_last = instance->addr;
+ }
+
+ while(ind < data_size) {
+ if((ind + COUNT_BYTE_PAYLOAD) > data_size) {
+ count_byte = data_size - ind;
+ } else {
+ count_byte = COUNT_BYTE_PAYLOAD;
+ }
+ //I32HEX_TYPE_DATA
+ furi_string_cat_printf(
+ instance->str_data, ":%02X%04lX00", count_byte, (instance->addr & 0xFFFF));
+ crc = count_byte + ((instance->addr >> 8) & 0xFF) + (instance->addr & 0xFF);
+
+ for(uint32_t i = 0; i < count_byte; i++) {
+ furi_string_cat_printf(instance->str_data, "%02X", *data);
+ crc += *data++;
+ }
+ crc = 0x01 + ~crc;
+ furi_string_cat_printf(instance->str_data, "%02X\r\n", crc);
+
+ ind += count_byte;
+ instance->addr += count_byte;
+ }
+ if(instance->file_open) stream_write_string(instance->stream, instance->str_data);
+ return ret;
+}
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance) {
+ furi_assert(instance);
+
+ FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0};
+ if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) {
+ ret.status = FlipperI32HexFileStatusErrorFileWrite;
+ }
+ furi_string_reset(instance->str_data);
+ //I32HEX_TYPE_END_OF_FILE
+ furi_string_cat_printf(instance->str_data, ":00000001FF\r\n");
+ if(instance->file_open) stream_write_string(instance->stream, instance->str_data);
+ return ret;
+}
+
+void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr) {
+ furi_assert(instance);
+
+ instance->addr = addr;
+}
+
+const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance) {
+ furi_assert(instance);
+
+ return furi_string_get_cstr(instance->str_data);
+}
+
+static FlipperI32HexFileRet flipper_i32hex_file_parse_line(
+ FlipperI32HexFile* instance,
+ const char* str,
+ uint8_t* data,
+ uint32_t data_size) {
+ furi_assert(instance);
+ furi_assert(data);
+
+ char* str1;
+ uint32_t data_wrire_ind = 0;
+ uint32_t data_len = 0;
+ FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusErrorData, .data_size = 0};
+
+ //Search for start of data I32HEX
+ str1 = strstr(str, ":");
+ do {
+ if(str1 == NULL) {
+ ret.status = FlipperI32HexFileStatusErrorData;
+ break;
+ }
+ str1++;
+ if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) {
+ ret.status = FlipperI32HexFileStatusErrorData;
+ break;
+ }
+ str1++;
+ if(++data_wrire_ind > data_size) {
+ ret.status = FlipperI32HexFileStatusErrorOverflow;
+ break;
+ }
+ data_len = 5 + data[0]; // +5 bytes per header and crc
+ while(data_len > data_wrire_ind) {
+ str1++;
+ if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) {
+ ret.status = FlipperI32HexFileStatusErrorData;
+ break;
+ }
+ str1++;
+ if(++data_wrire_ind > data_size) {
+ ret.status = FlipperI32HexFileStatusErrorOverflow;
+ break;
+ }
+ }
+ ret.status = FlipperI32HexFileStatusOK;
+ ret.data_size = data_wrire_ind;
+
+ } while(0);
+ return ret;
+}
+
+static bool flipper_i32hex_file_check_data(uint8_t* data, uint32_t data_size) {
+ furi_assert(data);
+
+ uint8_t crc = 0;
+ uint32_t data_read_ind = 0;
+ if(data[0] > data_size) return false;
+ while(data_read_ind < data_size - 1) {
+ crc += data[data_read_ind++];
+ }
+ return data[data_size - 1] == ((1 + ~crc) & 0xFF);
+}
+
+static FlipperI32HexFileRet flipper_i32hex_file_parse(
+ FlipperI32HexFile* instance,
+ const char* str,
+ uint8_t* data,
+ uint32_t data_size) {
+ furi_assert(instance);
+ furi_assert(data);
+
+ FlipperI32HexFileRet ret = flipper_i32hex_file_parse_line(instance, str, data, data_size);
+
+ if((ret.status == FlipperI32HexFileStatusOK) && (ret.data_size > 4)) {
+ switch(data[3]) {
+ case I32HEX_TYPE_DATA:
+ if(flipper_i32hex_file_check_data(data, ret.data_size)) {
+ ret.data_size -= 5;
+ memcpy(data, data + 4, ret.data_size);
+ ret.status = FlipperI32HexFileStatusData;
+ } else {
+ ret.status = FlipperI32HexFileStatusErrorCrc;
+ ret.data_size = 0;
+ }
+ break;
+ case I32HEX_TYPE_END_OF_FILE:
+ if(flipper_i32hex_file_check_data(data, ret.data_size)) {
+ ret.status = FlipperI32HexFileStatusEofFile;
+ ret.data_size = 0;
+ } else {
+ ret.status = FlipperI32HexFileStatusErrorCrc;
+ ret.data_size = 0;
+ }
+ break;
+ case I32HEX_TYPE_EXT_LINEAR_ADDR:
+ if(flipper_i32hex_file_check_data(data, ret.data_size)) {
+ data[0] = data[4];
+ data[1] = data[5];
+ data[3] = 0;
+ data[4] = 0;
+ ret.status = FlipperI32HexFileStatusUdateAddr;
+ ret.data_size = 4;
+ } else {
+ ret.status = FlipperI32HexFileStatusErrorCrc;
+ ret.data_size = 0;
+ }
+ break;
+ case I32HEX_TYPE_START_LINEAR_ADDR:
+ ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand;
+ ret.data_size = 0;
+ break;
+ default:
+ ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand;
+ ret.data_size = 0;
+ break;
+ }
+ } else {
+ ret.status = FlipperI32HexFileStatusErrorData;
+ ret.data_size = 0;
+ }
+ return ret;
+}
+
+bool flipper_i32hex_file_check(FlipperI32HexFile* instance) {
+ furi_assert(instance);
+
+ uint32_t data_size = 280;
+ uint8_t data[280] = {0};
+ bool ret = true;
+
+ if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) {
+ FURI_LOG_E(TAG, "File is not open");
+ ret = false;
+ } else {
+ stream_rewind(instance->stream);
+
+ while(stream_read_line(instance->stream, instance->str_data)) {
+ FlipperI32HexFileRet parse_ret = flipper_i32hex_file_parse(
+ instance, furi_string_get_cstr(instance->str_data), data, data_size);
+
+ if(parse_ret.status < 0) {
+ ret = false;
+ }
+ }
+ stream_rewind(instance->stream);
+ }
+ return ret;
+}
+
+FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data(
+ FlipperI32HexFile* instance,
+ uint8_t* data,
+ uint32_t data_size) {
+ furi_assert(instance);
+ furi_assert(data);
+
+ FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0};
+ if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) {
+ ret.status = FlipperI32HexFileStatusErrorFileRead;
+ } else {
+ stream_read_line(instance->stream, instance->str_data);
+ ret = flipper_i32hex_file_parse(
+ instance, furi_string_get_cstr(instance->str_data), data, data_size);
+ }
+
+ return ret;
+}
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h
new file mode 100644
index 000000000..765b94beb
--- /dev/null
+++ b/applications/external/avr_isp_programmer/helpers/flipper_i32hex_file.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include
+
+typedef struct FlipperI32HexFile FlipperI32HexFile;
+
+typedef enum {
+ FlipperI32HexFileStatusOK = 0,
+ FlipperI32HexFileStatusData = 2,
+ FlipperI32HexFileStatusUdateAddr = 3,
+ FlipperI32HexFileStatusEofFile = 4,
+ FlipperI32HexFileStatusOpenFileWrite = 5,
+ FlipperI32HexFileStatusOpenFileRead = 6,
+
+ // Errors
+ FlipperI32HexFileStatusErrorCrc = (-1),
+ FlipperI32HexFileStatusErrorOverflow = (-2),
+ FlipperI32HexFileStatusErrorData = (-3),
+ FlipperI32HexFileStatusErrorUnsupportedCommand = (-4),
+ FlipperI32HexFileStatusErrorNoOpenFile = (-5),
+ FlipperI32HexFileStatusErrorFileWrite = (-6),
+ FlipperI32HexFileStatusErrorFileRead = (-7),
+
+ FlipperI32HexFileStatusReserved =
+ 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization.
+} FlipperI32HexFileStatus;
+
+typedef struct {
+ FlipperI32HexFileStatus status;
+ uint32_t data_size;
+} FlipperI32HexFileRet;
+
+FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr);
+
+FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name);
+
+void flipper_i32hex_file_close(FlipperI32HexFile* instance);
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data(
+ FlipperI32HexFile* instance,
+ uint8_t* data,
+ uint32_t data_size);
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance);
+
+const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance);
+
+void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr);
+
+bool flipper_i32hex_file_check(FlipperI32HexFile* instance);
+
+FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data(
+ FlipperI32HexFile* instance,
+ uint8_t* data,
+ uint32_t data_size);
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png b/applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png
new file mode 100644
index 000000000..533787fe3
Binary files /dev/null and b/applications/external/avr_isp_programmer/images/avr_app_icon_10x10.png differ
diff --git a/applications/external/avr_isp_programmer/images/avr_wiring.png b/applications/external/avr_isp_programmer/images/avr_wiring.png
new file mode 100644
index 000000000..957012405
Binary files /dev/null and b/applications/external/avr_isp_programmer/images/avr_wiring.png differ
diff --git a/applications/external/avr_isp_programmer/images/chif_not_found_83x37.png b/applications/external/avr_isp_programmer/images/chif_not_found_83x37.png
new file mode 100644
index 000000000..b03bf3567
Binary files /dev/null and b/applications/external/avr_isp_programmer/images/chif_not_found_83x37.png differ
diff --git a/applications/external/avr_isp_programmer/images/chip_error_70x22.png b/applications/external/avr_isp_programmer/images/chip_error_70x22.png
new file mode 100644
index 000000000..16f81178c
Binary files /dev/null and b/applications/external/avr_isp_programmer/images/chip_error_70x22.png differ
diff --git a/applications/external/avr_isp_programmer/images/chip_long_70x22.png b/applications/external/avr_isp_programmer/images/chip_long_70x22.png
new file mode 100644
index 000000000..3edfff82d
Binary files /dev/null and b/applications/external/avr_isp_programmer/images/chip_long_70x22.png differ
diff --git a/applications/external/avr_isp_programmer/images/chip_not_found_83x37.png b/applications/external/avr_isp_programmer/images/chip_not_found_83x37.png
new file mode 100644
index 000000000..6d56dcfa0
Binary files /dev/null and b/applications/external/avr_isp_programmer/images/chip_not_found_83x37.png differ
diff --git a/applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png b/applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png
new file mode 100644
index 000000000..a299d3630
Binary files /dev/null and b/applications/external/avr_isp_programmer/images/dolphin_nice_96x59.png differ
diff --git a/applications/external/avr_isp_programmer/images/isp_active_128x53.png b/applications/external/avr_isp_programmer/images/isp_active_128x53.png
new file mode 100644
index 000000000..843792123
Binary files /dev/null and b/applications/external/avr_isp_programmer/images/isp_active_128x53.png differ
diff --git a/applications/external/avr_isp_programmer/images/link_waiting_77x56.png b/applications/external/avr_isp_programmer/images/link_waiting_77x56.png
new file mode 100644
index 000000000..d7d32aed5
Binary files /dev/null and b/applications/external/avr_isp_programmer/images/link_waiting_77x56.png differ
diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.c
new file mode 100644
index 000000000..2be54de6a
--- /dev/null
+++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.c
@@ -0,0 +1,386 @@
+#include "avr_isp_chip_arr.h"
+
+#include
+
+//https://github.com/avrdudes/avrdude/blob/master/src/avrintel.c
+
+const AvrIspChipArr avr_isp_chip_arr[] = { // Value of -1 typically means unknown
+ //{mcu_name, mcuid, family, {sig, na, ture}, flstart, flsize, pgsiz, nb, bootsz, eestart, eesize, ep, rambeg, ramsiz, nf, nl, ni}, // Source
+ {"ATtiny4", 0, F_AVR8L, {0x1E, 0x8F, 0x0A}, 0, 0x00200, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATtiny5", 1, F_AVR8L, {0x1E, 0x8F, 0x09}, 0, 0x00200, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATtiny9", 2, F_AVR8L, {0x1E, 0x90, 0x08}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATtiny10", 3, F_AVR8L, {0x1E, 0x90, 0x03}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATtiny20", 4, F_AVR8L, {0x1E, 0x91, 0x0F}, 0, 0x00800, 0x020, 0, 0, 0, 0, 0, 0x0040, 0x0080, 1, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATtiny40", 5, F_AVR8L, {0x1E, 0x92, 0x0E}, 0, 0x01000, 0x040, 0, 0, 0, 0, 0, 0x0040, 0x0100, 1, 1, 18}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATtiny102", 6, F_AVR8L, {0x1E, 0x90, 0x0C}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 16}, // atdf, avrdude, boot size (manual)
+ {"ATtiny104", 7, F_AVR8L, {0x1E, 0x90, 0x0B}, 0, 0x00400, 0x010, 0, 0, 0, 0, 0, 0x0040, 0x0020, 1, 1, 16}, // atdf, avrdude, boot size (manual)
+
+ {"ATtiny11", 8, F_AVR8, {0x1E, 0x90, 0x04}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 1, 0x0060, 0x0020, 1, 1, 5}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny12", 9, F_AVR8, {0x1E, 0x90, 0x05}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 2, 0x0060, 0x0020, 1, 1, 6}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny13", 10, F_AVR8, {0x1E, 0x90, 0x07}, 0, 0x00400, 0x020, 0, 0, 0, 0x0040, 4, 0x0060, 0x0040, 2, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny13A", 11, F_AVR8, {0x1E, 0x90, 0x07}, 0, 0x00400, 0x020, 0, 0, 0, 0x0040, 4, 0x0060, 0x0040, 2, 1, 10}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny15", 12, F_AVR8, {0x1E, 0x90, 0x06}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 2, 0x0060, 0x0020, 1, 1, 9}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny22", 13, F_AVR8, {0x1E, 0x91, 0x06}, 0, 0x00800, -1, 0, 0, -1, -1, -1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, boot size (manual)
+ {"ATtiny24", 14, F_AVR8, {0x1E, 0x91, 0x0B}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny24A", 15, F_AVR8, {0x1E, 0x91, 0x0B}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny25", 16, F_AVR8, {0x1E, 0x91, 0x08}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny26", 17, F_AVR8, {0x1E, 0x91, 0x09}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 2, 1, 12}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny28", 18, F_AVR8, {0x1E, 0x91, 0x07}, 0, 0x00800, 0x002, 0, 0, 0, 0, 0, 0x0060, 0x0020, 1, 1, 6}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny43U", 19, F_AVR8, {0x1E, 0x92, 0x0C}, 0, 0x01000, 0x040, 0, 0, 0, 0x0040, 4, 0x0060, 0x0100, 3, 1, 16}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny44", 20, F_AVR8, {0x1E, 0x92, 0x07}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny44A", 21, F_AVR8, {0x1E, 0x92, 0x07}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny45", 22, F_AVR8, {0x1E, 0x92, 0x06}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny48", 23, F_AVR8, {0x1E, 0x92, 0x09}, 0, 0x01000, 0x040, 0, 0, 0, 0x0040, 4, 0x0100, 0x0100, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny84", 24, F_AVR8, {0x1E, 0x93, 0x0C}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny84A", 25, F_AVR8, {0x1E, 0x93, 0x0C}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny85", 26, F_AVR8, {0x1E, 0x93, 0x0B}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 15}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny87", 27, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny88", 28, F_AVR8, {0x1E, 0x93, 0x11}, 0, 0x02000, 0x040, 0, 0, 0, 0x0040, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny167", 29, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny261", 30, F_AVR8, {0x1E, 0x91, 0x0C}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny261A", 31, F_AVR8, {0x1E, 0x91, 0x0C}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny441", 32, F_AVR8, {0x1E, 0x92, 0x15}, 0, 0x01000, 0x010, 0, 0, 0, 0x0100, 4, 0x0100, 0x0100, 3, 1, 30}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny461", 33, F_AVR8, {0x1E, 0x92, 0x08}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny461A", 34, F_AVR8, {0x1E, 0x92, 0x08}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny828", 35, F_AVR8, {0x1E, 0x93, 0x14}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny828R", 36, F_AVR8, {0x1E, 0x93, 0x14}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // avrdude, from ATtiny828
+ {"ATtiny841", 37, F_AVR8, {0x1E, 0x93, 0x15}, 0, 0x02000, 0x010, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 30}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny861", 38, F_AVR8, {0x1E, 0x93, 0x0D}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny861A", 39, F_AVR8, {0x1E, 0x93, 0x0D}, 0, 0x02000, 0x040, 0, 0, 0, 0x0200, 4, 0x0060, 0x0200, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny1634", 40, F_AVR8, {0x1E, 0x94, 0x12}, 0, 0x04000, 0x020, 0, 0, 0, 0x0100, 4, 0x0100, 0x0400, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny1634R", 41, F_AVR8, {0x1E, 0x94, 0x12}, 0, 0x04000, 0x020, 0, 0, 0, 0x0100, 4, 0x0100, 0x0400, 3, 1, 28}, // avrdude, from ATtiny1634
+ {"ATtiny2313", 42, F_AVR8, {0x1E, 0x91, 0x0A}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny2313A", 43, F_AVR8, {0x1E, 0x91, 0x0A}, 0, 0x00800, 0x020, 0, 0, 0, 0x0080, 4, 0x0060, 0x0080, 3, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny4313", 44, F_AVR8, {0x1E, 0x92, 0x0D}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0060, 0x0100, 3, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega8", 45, F_AVR8, {0x1E, 0x93, 0x07}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega8A", 46, F_AVR8, {0x1E, 0x93, 0x07}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 19}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega8HVA", 47, F_AVR8, {0x1E, 0x93, 0x10}, 0, 0x02000, 0x080, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 1, 1, 21}, // atdf, avr-gcc 12.2.0
+ {"ATmega8U2", 48, F_AVR8, {0x1E, 0x93, 0x89}, 0, 0x02000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega16", 49, F_AVR8, {0x1E, 0x94, 0x03}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega16A", 50, F_AVR8, {0x1E, 0x94, 0x03}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0400, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega16HVA", 51, F_AVR8, {0x1E, 0x94, 0x0C}, 0, 0x04000, 0x080, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 1, 1, 21}, // atdf, avr-gcc 12.2.0
+ {"ATmega16HVB", 52, F_AVR8, {0x1E, 0x94, 0x0D}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 2, 1, 29}, // atdf, avr-gcc 12.2.0
+ {"ATmega16HVBrevB", 53, F_AVR8, {0x1E, 0x94, 0x0D}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 2, 1, 29}, // atdf, avr-gcc 12.2.0
+ {"ATmega16M1", 54, F_AVR8, {0x1E, 0x94, 0x84}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0
+ {"ATmega16HVA2", 55, F_AVR8, {0x1E, 0x94, 0x0E}, 0, 0x04000, 0x080, -1, -1, -1, -1, -1, 0x0100, 0x0400, 2, 1, 22}, // avr-gcc 12.2.0
+ {"ATmega16U2", 56, F_AVR8, {0x1E, 0x94, 0x89}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega16U4", 57, F_AVR8, {0x1E, 0x94, 0x88}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0500, 3, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega32", 58, F_AVR8, {0x1E, 0x95, 0x02}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0060, 0x0800, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega32A", 59, F_AVR8, {0x1E, 0x95, 0x02}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0060, 0x0800, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega32HVB", 60, F_AVR8, {0x1E, 0x95, 0x10}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 2, 1, 29}, // atdf, avr-gcc 12.2.0
+ {"ATmega32HVBrevB", 61, F_AVR8, {0x1E, 0x95, 0x10}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 2, 1, 29}, // atdf, avr-gcc 12.2.0
+ {"ATmega32C1", 62, F_AVR8, {0x1E, 0x95, 0x86}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0
+ {"ATmega32M1", 63, F_AVR8, {0x1E, 0x95, 0x84}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega32U2", 64, F_AVR8, {0x1E, 0x95, 0x8A}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0400, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega32U4", 65, F_AVR8, {0x1E, 0x95, 0x87}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0a00, 3, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega32U6", 66, F_AVR8, {0x1E, 0x95, 0x88}, 0, 0x08000, 0x080, 4, 0x0200, -1, -1, -1, 0x0100, 0x0a00, 3, 1, 38}, // avr-gcc 12.2.0, boot size (manual)
+ {"ATmega48", 67, F_AVR8, {0x1E, 0x92, 0x05}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega48A", 68, F_AVR8, {0x1E, 0x92, 0x05}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega48P", 69, F_AVR8, {0x1E, 0x92, 0x0A}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega48PA", 70, F_AVR8, {0x1E, 0x92, 0x0A}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega48PB", 71, F_AVR8, {0x1E, 0x92, 0x10}, 0, 0x01000, 0x040, 0, 0, 0, 0x0100, 4, 0x0100, 0x0200, 3, 1, 27}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega64", 72, F_AVR8, {0x1E, 0x96, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega64A", 73, F_AVR8, {0x1E, 0x96, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega64HVE", 74, F_AVR8, {0x1E, 0x96, 0x10}, 0, 0x10000, 0x080, 4, 0x0400, -1, -1, -1, 0x0100, 0x1000, 2, 1, 25}, // avr-gcc 12.2.0, boot size (manual)
+ {"ATmega64C1", 75, F_AVR8, {0x1E, 0x96, 0x86}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0
+ {"ATmega64M1", 76, F_AVR8, {0x1E, 0x96, 0x84}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega64HVE2", 77, F_AVR8, {0x1E, 0x96, 0x10}, 0, 0x10000, 0x080, 4, 0x0400, 0, 0x0400, 4, 0x0100, 0x1000, 2, 1, 25}, // atdf, avr-gcc 12.2.0
+ {"ATmega64RFR2", 78, F_AVR8, {0x1E, 0xA6, 0x02}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0200, 0x2000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega88", 79, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega88A", 80, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega88P", 81, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega88PA", 82, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega88PB", 83, F_AVR8, {0x1E, 0x93, 0x16}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 27}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega103", 84, F_AVR8, {0x1E, 0x97, 0x01}, 0, 0x20000, 0x100, 0, 0, 0, 0x1000, 1, 0x0060, 0x0fa0, 1, 1, 24}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATmega128", 85, F_AVR8, {0x1E, 0x97, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega128A", 86, F_AVR8, {0x1E, 0x97, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega128RFA1", 87, F_AVR8, {0x1E, 0xA7, 0x01}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 72}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega128RFR2", 88, F_AVR8, {0x1E, 0xA7, 0x02}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega161", 89, F_AVR8, {0x1E, 0x94, 0x01}, 0, 0x04000, 0x080, 1, 0x0400, 0, 0x0200, 1, 0x0060, 0x0400, 1, 1, 21}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATmega162", 90, F_AVR8, {0x1E, 0x94, 0x04}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega163", 91, F_AVR8, {0x1E, 0x94, 0x02}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 1, 0x0060, 0x0400, 2, 1, 18}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATmega164A", 92, F_AVR8, {0x1E, 0x94, 0x0F}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega164P", 93, F_AVR8, {0x1E, 0x94, 0x0A}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega164PA", 94, F_AVR8, {0x1E, 0x94, 0x0A}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega165", 95, F_AVR8, {0x1E, 0x94, 0x10}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATmega165A", 96, F_AVR8, {0x1E, 0x94, 0x10}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega165P", 97, F_AVR8, {0x1E, 0x94, 0x07}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega165PA", 98, F_AVR8, {0x1E, 0x94, 0x07}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega168", 99, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega168A", 100, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega168P", 101, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega168PA", 102, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega168PB", 103, F_AVR8, {0x1E, 0x94, 0x15}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 27}, // atdf, avr-gcc 7.3.0, avrdude
+ {"ATmega169", 104, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"ATmega169A", 105, F_AVR8, {0x1E, 0x94, 0x11}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega169P", 106, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega169PA", 107, F_AVR8, {0x1E, 0x94, 0x05}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega256RFR2", 108, F_AVR8, {0x1E, 0xA8, 0x02}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x2000, 8, 0x0200, 0x8000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega323", 109, F_AVR8, {0x1E, 0x95, 0x01}, 0, 0x08000, 0x080, 4, 0x0200, -1, -1, -1, 0x0060, 0x0800, 2, 1, 21}, // avr-gcc 12.2.0, boot size (manual)
+ {"ATmega324A", 110, F_AVR8, {0x1E, 0x95, 0x15}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega324P", 111, F_AVR8, {0x1E, 0x95, 0x08}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega324PA", 112, F_AVR8, {0x1E, 0x95, 0x11}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega324PB", 113, F_AVR8, {0x1E, 0x95, 0x17}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 51}, // atdf, avrdude
+ {"ATmega325", 114, F_AVR8, {0x1E, 0x95, 0x05}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega325A", 115, F_AVR8, {0x1E, 0x95, 0x05}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega325P", 116, F_AVR8, {0x1E, 0x95, 0x0D}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega325PA", 117, F_AVR8, {0x1E, 0x95, 0x0D}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega328", 118, F_AVR8, {0x1E, 0x95, 0x14}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega328P", 119, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega328PB", 120, F_AVR8, {0x1E, 0x95, 0x16}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 45}, // atdf, avr-gcc 7.3.0, avrdude
+ {"ATmega329", 121, F_AVR8, {0x1E, 0x95, 0x03}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega329A", 122, F_AVR8, {0x1E, 0x95, 0x03}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega329P", 123, F_AVR8, {0x1E, 0x95, 0x0B}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega329PA", 124, F_AVR8, {0x1E, 0x95, 0x0B}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega406", 125, F_AVR8, {0x1E, 0x95, 0x07}, 0, 0x0a000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0800, 2, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega640", 126, F_AVR8, {0x1E, 0x96, 0x08}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega644", 127, F_AVR8, {0x1E, 0x96, 0x09}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 28}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega644A", 128, F_AVR8, {0x1E, 0x96, 0x09}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega644P", 129, F_AVR8, {0x1E, 0x96, 0x0A}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega644PA", 130, F_AVR8, {0x1E, 0x96, 0x0A}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega644RFR2", 131, F_AVR8, {0x1E, 0xA6, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0200, 0x2000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega645", 132, F_AVR8, {0x1E, 0x96, 0x05}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega645A", 133, F_AVR8, {0x1E, 0x96, 0x05}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega645P", 134, F_AVR8, {0x1E, 0x96, 0x0D}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 22}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega649", 135, F_AVR8, {0x1E, 0x96, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega649A", 136, F_AVR8, {0x1E, 0x96, 0x03}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega649P", 137, F_AVR8, {0x1E, 0x96, 0x0B}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 23}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega1280", 138, F_AVR8, {0x1E, 0x97, 0x03}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega1281", 139, F_AVR8, {0x1E, 0x97, 0x04}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega1284", 140, F_AVR8, {0x1E, 0x97, 0x06}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x4000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega1284P", 141, F_AVR8, {0x1E, 0x97, 0x05}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x4000, 3, 1, 35}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega1284RFR2", 142, F_AVR8, {0x1E, 0xA7, 0x03}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x4000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega2560", 143, F_AVR8, {0x1E, 0x98, 0x01}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega2561", 144, F_AVR8, {0x1E, 0x98, 0x02}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0200, 0x2000, 3, 1, 57}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega2564RFR2", 145, F_AVR8, {0x1E, 0xA8, 0x03}, 0, 0x40000, 0x100, 4, 0x0400, 0, 0x2000, 8, 0x0200, 0x8000, 3, 1, 77}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega3250", 146, F_AVR8, {0x1E, 0x95, 0x06}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega3250A", 147, F_AVR8, {0x1E, 0x95, 0x06}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega3250P", 148, F_AVR8, {0x1E, 0x95, 0x0E}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega3250PA", 149, F_AVR8, {0x1E, 0x95, 0x0E}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega3290", 150, F_AVR8, {0x1E, 0x95, 0x04}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega3290A", 151, F_AVR8, {0x1E, 0x95, 0x04}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega3290P", 152, F_AVR8, {0x1E, 0x95, 0x0C}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega3290PA", 153, F_AVR8, {0x1E, 0x95, 0x0C}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega6450", 154, F_AVR8, {0x1E, 0x96, 0x06}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega6450A", 155, F_AVR8, {0x1E, 0x96, 0x06}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega6450P", 156, F_AVR8, {0x1E, 0x96, 0x0E}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega6490", 157, F_AVR8, {0x1E, 0x96, 0x04}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega6490A", 158, F_AVR8, {0x1E, 0x96, 0x04}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega6490P", 159, F_AVR8, {0x1E, 0x96, 0x0C}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 25}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega8515", 160, F_AVR8, {0x1E, 0x93, 0x06}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0200, 2, 1, 17}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega8535", 161, F_AVR8, {0x1E, 0x93, 0x08}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0060, 0x0200, 2, 1, 21}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT43USB320", 162, F_AVR8, {0xff, -1, -1}, 0, 0x10000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0200, -1, -1, 0}, // avr-gcc 12.2.0
+ {"AT43USB355", 163, F_AVR8, {0xff, -1, -1}, 0, 0x06000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0400, -1, -1, 0}, // avr-gcc 12.2.0
+ {"AT76C711", 164, F_AVR8, {0xff, -1, -1}, 0, 0x04000, -1, -1, -1, -1, -1, -1, 0x0060, 0x07a0, -1, -1, 0}, // avr-gcc 12.2.0
+ {"AT86RF401", 165, F_AVR8, {0x1E, 0x91, 0x81}, 0, 0x00800, -1, -1, -1, -1, -1, -1, 0x0060, 0x0080, 0, 1, 3}, // avr-gcc 12.2.0
+ {"AT90PWM1", 166, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0
+ {"AT90PWM2", 167, F_AVR8, {0x1E, 0x93, 0x81}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"AT90PWM2B", 168, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90PWM3", 169, F_AVR8, {0x1E, 0x93, 0x81}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90PWM3B", 170, F_AVR8, {0x1E, 0x93, 0x83}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90CAN32", 171, F_AVR8, {0x1E, 0x95, 0x81}, 0, 0x08000, 0x100, 4, 0x0400, 0, 0x0400, 8, 0x0100, 0x0800, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90CAN64", 172, F_AVR8, {0x1E, 0x96, 0x81}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90PWM81", 173, F_AVR8, {0x1E, 0x93, 0x88}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0100, 3, 1, 20}, // atdf, avr-gcc 12.2.0
+ {"AT90USB82", 174, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90SCR100", 175, F_AVR8, {0x1E, 0x96, 0xC1}, 0, 0x10000, 0x100, 4, 0x0200, -1, -1, -1, 0x0100, 0x1000, 3, 1, 38}, // avr-gcc 12.2.0, boot size (manual)
+ {"AT90CAN128", 176, F_AVR8, {0x1E, 0x97, 0x81}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x1000, 3, 1, 37}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90PWM161", 177, F_AVR8, {0x1E, 0x94, 0x8B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 20}, // atdf, avr-gcc 12.2.0
+ {"AT90USB162", 178, F_AVR8, {0x1E, 0x94, 0x82}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 29}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90PWM216", 179, F_AVR8, {0x1E, 0x94, 0x83}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90PWM316", 180, F_AVR8, {0x1E, 0x94, 0x83}, 0, 0x04000, 0x080, 4, 0x0200, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 32}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90USB646", 181, F_AVR8, {0x1E, 0x96, 0x82}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90USB647", 182, F_AVR8, {0x1E, 0x96, 0x82}, 0, 0x10000, 0x100, 4, 0x0400, 0, 0x0800, 8, 0x0100, 0x1000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90S1200", 183, F_AVR8, {0x1E, 0x90, 0x01}, 0, 0x00400, 0x001, 0, 0, 0, 0x0040, 1, 0x0060, 0x0020, 1, 1, 4}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"AT90USB1286", 184, F_AVR8, {0x1E, 0x97, 0x82}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x2000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90USB1287", 185, F_AVR8, {0x1E, 0x97, 0x82}, 0, 0x20000, 0x100, 4, 0x0400, 0, 0x1000, 8, 0x0100, 0x2000, 3, 1, 38}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AT90S2313", 186, F_AVR8, {0x1E, 0x91, 0x01}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, 1, 1, 11}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"AT90S2323", 187, F_AVR8, {0x1E, 0x91, 0x02}, 0, 0x00800, -1, 0, 0, -1, -1, -1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, boot size (manual)
+ {"AT90S2333", 188, F_AVR8, {0x1E, 0x91, 0x05}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, -1, -1, 14}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"AT90S2343", 189, F_AVR8, {0x1E, 0x91, 0x03}, 0, 0x00800, 0x001, 0, 0, 0, 0x0080, 1, 0x0060, 0x0080, 1, 1, 3}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"AT90S4414", 190, F_AVR8, {0x1E, 0x92, 0x01}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0100, 1, 1, 13}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"AT90S4433", 191, F_AVR8, {0x1E, 0x92, 0x03}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0080, 1, 1, 14}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"AT90S4434", 192, F_AVR8, {0x1E, 0x92, 0x02}, 0, 0x01000, 0x001, 0, 0, 0, 0x0100, 1, 0x0060, 0x0100, 1, 1, 17}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"AT90S8515", 193, F_AVR8, {0x1E, 0x93, 0x01}, 0, 0x02000, 0x001, 0, 0, 0, 0x0200, 1, 0x0060, 0x0200, 1, 1, 13}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"AT90C8534", 194, F_AVR8, {0xff, -1, -1}, 0, 0x02000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0100, -1, -1, 0}, // avr-gcc 12.2.0
+ {"AT90S8535", 195, F_AVR8, {0x1E, 0x93, 0x03}, 0, 0x02000, 0x001, 0, 0, 0, 0x0200, 1, 0x0060, 0x0200, 1, 1, 17}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+ {"AT94K", 196, F_AVR8, {0xff, -1, -1}, 0, 0x08000, -1, -1, -1, -1, -1, -1, 0x0060, 0x0fa0, -1, -1, 0}, // avr-gcc 12.2.0
+ {"ATA5272", 197, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 37}, // atdf, avr-gcc 12.2.0
+ {"ATA5505", 198, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0
+ {"ATA5700M322", 199, F_AVR8, {0x1E, 0x95, 0x67}, 0x08000, 0x08000, 0x040, 0, 0, 0, 0x0880, 16, 0x0200, 0x0400, 1, 1, 51}, // atdf
+ {"ATA5702M322", 200, F_AVR8, {0x1E, 0x95, 0x69}, 0x08000, 0x08000, 0x040, 0, 0, 0, 0x0880, 16, 0x0200, 0x0400, 1, 1, 51}, // atdf, avr-gcc 12.2.0
+ {"ATA5781", 201, F_AVR8, {0x1E, 0x95, 0x64}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf
+ {"ATA5782", 202, F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 12.2.0
+ {"ATA5783", 203, F_AVR8, {0x1E, 0x95, 0x66}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf
+ {"ATA5787", 204, F_AVR8, {0x1E, 0x94, 0x6C}, 0x08000, 0x05200, 0x040, 0, 0, 0, 0x0400, 16, 0x0200, 0x0800, 1, 1, 44}, // atdf
+ {"ATA5790", 205, F_AVR8, {0x1E, 0x94, 0x61}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 30}, // atdf, avr-gcc 12.2.0
+ {"ATA5790N", 206, F_AVR8, {0x1E, 0x94, 0x62}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 31}, // atdf, avr-gcc 12.2.0
+ {"ATA5791", 207, F_AVR8, {0x1E, 0x94, 0x62}, 0, 0x04000, 0x080, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 31}, // atdf, avr-gcc 7.3.0
+ {"ATA5795", 208, F_AVR8, {0x1E, 0x93, 0x61}, 0, 0x02000, 0x040, 1, 0x0800, 0, 0x0800, 16, 0x0100, 0x0200, 1, 1, 23}, // atdf, avr-gcc 12.2.0
+ {"ATA5831", 209, F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 12.2.0
+ {"ATA5832", 210, F_AVR8, {0x1E, 0x95, 0x62}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf
+ {"ATA5833", 211, F_AVR8, {0x1E, 0x95, 0x63}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf
+ {"ATA5835", 212, F_AVR8, {0x1E, 0x94, 0x6B}, 0x08000, 0x05200, 0x040, 0, 0, 0, 0x0400, 16, 0x0200, 0x0800, 1, 1, 44}, // atdf
+ {"ATA6285", 213, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0140, 4, 0x0100, 0x0200, 2, 1, 27}, // atdf, avr-gcc 12.2.0
+ {"ATA6286", 214, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0140, 4, 0x0100, 0x0200, 2, 1, 27}, // atdf, avr-gcc 12.2.0
+ {"ATA6289", 215, F_AVR8, {0x1E, 0x93, 0x82}, 0, 0x02000, 0x040, 4, 0x0100, -1, -1, -1, 0x0100, 0x0200, 2, 1, 27}, // avr-gcc 12.2.0, boot size (manual)
+ {"ATA6612C", 216, F_AVR8, {0x1E, 0x93, 0x0A}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0
+ {"ATA6613C", 217, F_AVR8, {0x1E, 0x94, 0x06}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // atdf, avr-gcc 12.2.0
+ {"ATA6614Q", 218, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // atdf, avr-gcc 12.2.0
+ {"ATA6616C", 219, F_AVR8, {0x1E, 0x93, 0x87}, 0, 0x02000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0
+ {"ATA6617C", 220, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0
+ {"ATA8210", 221, F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 7.3.0
+ {"ATA8215", 222, F_AVR8, {0x1E, 0x95, 0x64}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf
+ {"ATA8510", 223, F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040, 1, 0x5000, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf, avr-gcc 7.3.0
+ {"ATA8515", 224, F_AVR8, {0x1E, 0x95, 0x63}, -1, -1, -1, 0, 0, 0, 0x0400, 16, 0x0200, 0x0400, 1, 1, 42}, // atdf
+ {"ATA664251", 225, F_AVR8, {0x1E, 0x94, 0x87}, 0, 0x04000, 0x080, 0, 0, 0, 0x0200, 4, 0x0100, 0x0200, 3, 1, 20}, // atdf, avr-gcc 12.2.0
+ {"M3000", 226, F_AVR8, {0xff, -1, -1}, 0, 0x10000, -1, -1, -1, -1, -1, -1, 0x1000, 0x1000, -1, -1, 0}, // avr-gcc 12.2.0
+ {"LGT8F88P", 227, F_AVR8, {0x1E, 0x93, 0x0F}, 0, 0x02000, 0x040, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // avrdude, from ATmega88
+ {"LGT8F168P", 228, F_AVR8, {0x1E, 0x94, 0x0B}, 0, 0x04000, 0x080, 4, 0x0100, 0, 0x0200, 4, 0x0100, 0x0400, 3, 1, 26}, // avrdude, from ATmega168P
+ {"LGT8F328P", 229, F_AVR8, {0x1E, 0x95, 0x0F}, 0, 0x08000, 0x080, 4, 0x0200, 0, 0x0400, 4, 0x0100, 0x0800, 3, 1, 26}, // avrdude, from ATmega328P
+
+ {"ATxmega8E5", 230, F_XMEGA, {0x1E, 0x93, 0x41}, 0, 0x02800, 0x080, 1, 0x0800, 0, 0x0200, 32, 0x2000, 0x0400, 7, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega16A4", 231, F_XMEGA, {0x1E, 0x94, 0x41}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 94}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega16A4U", 232, F_XMEGA, {0x1E, 0x94, 0x41}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega16C4", 233, F_XMEGA, {0x1E, 0x94, 0x43}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega16D4", 234, F_XMEGA, {0x1E, 0x94, 0x42}, 0, 0x05000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x0800, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega16E5", 235, F_XMEGA, {0x1E, 0x94, 0x45}, 0, 0x05000, 0x080, 1, 0x1000, 0, 0x0200, 32, 0x2000, 0x0800, 7, 1, 43}, // atdf, avr-gcc 7.3.0, avrdude
+ {"ATxmega32C3", 236, F_XMEGA, {0x1E, 0x95, 0x49}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0
+ {"ATxmega32D3", 237, F_XMEGA, {0x1E, 0x95, 0x4A}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 114}, // atdf, avr-gcc 12.2.0
+ {"ATxmega32A4", 238, F_XMEGA, {0x1E, 0x95, 0x41}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 94}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega32A4U", 239, F_XMEGA, {0x1E, 0x95, 0x41}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega32C4", 240, F_XMEGA, {0x1E, 0x95, 0x44}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega32D4", 241, F_XMEGA, {0x1E, 0x95, 0x42}, 0, 0x09000, 0x100, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega32E5", 242, F_XMEGA, {0x1E, 0x95, 0x4C}, 0, 0x09000, 0x080, 1, 0x1000, 0, 0x0400, 32, 0x2000, 0x1000, 7, 1, 43}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega64A1", 243, F_XMEGA, {0x1E, 0x96, 0x4E}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 125}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega64A1U", 244, F_XMEGA, {0x1E, 0x96, 0x4E}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega64B1", 245, F_XMEGA, {0x1E, 0x96, 0x52}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 81}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega64A3", 246, F_XMEGA, {0x1E, 0x96, 0x42}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega64A3U", 247, F_XMEGA, {0x1E, 0x96, 0x42}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega64B3", 248, F_XMEGA, {0x1E, 0x96, 0x51}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 54}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega64C3", 249, F_XMEGA, {0x1E, 0x96, 0x49}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega64D3", 250, F_XMEGA, {0x1E, 0x96, 0x4A}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega64A4", 251, F_XMEGA, {0x1E, 0x96, 0x46}, 0, 0x11000, 0x100, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude
+ {"ATxmega64A4U", 252, F_XMEGA, {0x1E, 0x96, 0x46}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega64D4", 253, F_XMEGA, {0x1E, 0x96, 0x47}, 0, 0x11000, 0x100, 1, 0x1000, 0, 0x0800, 32, 0x2000, 0x1000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega128A1", 254, F_XMEGA, {0x1E, 0x97, 0x4C}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 125}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega128A1revD", 255, F_XMEGA, {0x1E, 0x97, 0x41}, 0, 0x22000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude
+ {"ATxmega128A1U", 256, F_XMEGA, {0x1E, 0x97, 0x4C}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega128B1", 257, F_XMEGA, {0x1E, 0x97, 0x4D}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 81}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega128A3", 258, F_XMEGA, {0x1E, 0x97, 0x42}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega128A3U", 259, F_XMEGA, {0x1E, 0x97, 0x42}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega128B3", 260, F_XMEGA, {0x1E, 0x97, 0x4B}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 54}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega128C3", 261, F_XMEGA, {0x1E, 0x97, 0x52}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega128D3", 262, F_XMEGA, {0x1E, 0x97, 0x48}, 0, 0x22000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega128A4", 263, F_XMEGA, {0x1E, 0x97, 0x46}, 0, 0x22000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude
+ {"ATxmega128A4U", 264, F_XMEGA, {0x1E, 0x97, 0x46}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega128D4", 265, F_XMEGA, {0x1E, 0x97, 0x47}, 0, 0x22000, 0x100, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x2000, 6, 1, 91}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega192A1", 266, F_XMEGA, {0x1E, 0x97, 0x4E}, 0, 0x32000, 0x200, -1, -1, 0, 0x0800, 32, -1, -1, -1, -1, 0}, // avrdude
+ {"ATxmega192A3", 267, F_XMEGA, {0x1E, 0x97, 0x44}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega192A3U", 268, F_XMEGA, {0x1E, 0x97, 0x44}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega192C3", 269, F_XMEGA, {0x1E, 0x97, 0x51}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega192D3", 270, F_XMEGA, {0x1E, 0x97, 0x49}, 0, 0x32000, 0x200, 1, 0x2000, 0, 0x0800, 32, 0x2000, 0x4000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega256A1", 271, F_XMEGA, {0x1E, 0x98, 0x46}, 0, 0x42000, 0x200, -1, -1, 0, 0x1000, 32, -1, -1, -1, -1, 0}, // avrdude
+ {"ATxmega256A3", 272, F_XMEGA, {0x1E, 0x98, 0x42}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega256A3B", 273, F_XMEGA, {0x1E, 0x98, 0x43}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega256A3BU", 274, F_XMEGA, {0x1E, 0x98, 0x43}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega256A3U", 275, F_XMEGA, {0x1E, 0x98, 0x42}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega256C3", 276, F_XMEGA, {0x1E, 0x98, 0x46}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega256D3", 277, F_XMEGA, {0x1E, 0x98, 0x44}, 0, 0x42000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x4000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega384C3", 278, F_XMEGA, {0x1E, 0x98, 0x45}, 0, 0x62000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x8000, 6, 1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATxmega384D3", 279, F_XMEGA, {0x1E, 0x98, 0x47}, 0, 0x62000, 0x200, 1, 0x2000, 0, 0x1000, 32, 0x2000, 0x8000, 6, 1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+
+ {"ATtiny202", 280, F_AVR8X, {0x1E, 0x91, 0x23}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny204", 281, F_AVR8X, {0x1E, 0x91, 0x22}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny212", 282, F_AVR8X, {0x1E, 0x91, 0x21}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny214", 283, F_AVR8X, {0x1E, 0x91, 0x20}, 0, 0x00800, 0x040, 1, 0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny402", 284, F_AVR8X, {0x1E, 0x92, 0x27}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny404", 285, F_AVR8X, {0x1E, 0x92, 0x26}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny406", 286, F_AVR8X, {0x1E, 0x92, 0x25}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny412", 287, F_AVR8X, {0x1E, 0x92, 0x23}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny414", 288, F_AVR8X, {0x1E, 0x92, 0x22}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny416", 289, F_AVR8X, {0x1E, 0x92, 0x21}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny416auto", 290, F_AVR8X, {0x1E, 0x92, 0x28}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf
+ {"ATtiny417", 291, F_AVR8X, {0x1E, 0x92, 0x20}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny424", 292, F_AVR8X, {0x1E, 0x92, 0x2C}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny426", 293, F_AVR8X, {0x1E, 0x92, 0x2B}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny427", 294, F_AVR8X, {0x1E, 0x92, 0x2A}, 0, 0x01000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny804", 295, F_AVR8X, {0x1E, 0x93, 0x25}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny806", 296, F_AVR8X, {0x1E, 0x93, 0x24}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny807", 297, F_AVR8X, {0x1E, 0x93, 0x23}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny814", 298, F_AVR8X, {0x1E, 0x93, 0x22}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny816", 299, F_AVR8X, {0x1E, 0x93, 0x21}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny817", 300, F_AVR8X, {0x1E, 0x93, 0x20}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10, 1, 26}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny824", 301, F_AVR8X, {0x1E, 0x93, 0x29}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny826", 302, F_AVR8X, {0x1E, 0x93, 0x28}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny827", 303, F_AVR8X, {0x1E, 0x93, 0x27}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny1604", 304, F_AVR8X, {0x1E, 0x94, 0x25}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny1606", 305, F_AVR8X, {0x1E, 0x94, 0x24}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny1607", 306, F_AVR8X, {0x1E, 0x94, 0x23}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny1614", 307, F_AVR8X, {0x1E, 0x94, 0x22}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny1616", 308, F_AVR8X, {0x1E, 0x94, 0x21}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny1617", 309, F_AVR8X, {0x1E, 0x94, 0x20}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny1624", 310, F_AVR8X, {0x1E, 0x94, 0x2A}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny1626", 311, F_AVR8X, {0x1E, 0x94, 0x29}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny1627", 312, F_AVR8X, {0x1E, 0x94, 0x28}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny3214", 313, F_AVR8X, {0x1E, 0x95, 0x20}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // avr-gcc 12.2.0
+ {"ATtiny3216", 314, F_AVR8X, {0x1E, 0x95, 0x21}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny3217", 315, F_AVR8X, {0x1E, 0x95, 0x22}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10, 1, 31}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATtiny3224", 316, F_AVR8X, {0x1E, 0x95, 0x28}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny3226", 317, F_AVR8X, {0x1E, 0x95, 0x27}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude
+ {"ATtiny3227", 318, F_AVR8X, {0x1E, 0x95, 0x26}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10, 1, 30}, // atdf, avrdude
+ {"ATmega808", 319, F_AVR8X, {0x1E, 0x93, 0x26}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega809", 320, F_AVR8X, {0x1E, 0x93, 0x2A}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega1608", 321, F_AVR8X, {0x1E, 0x94, 0x27}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega1609", 322, F_AVR8X, {0x1E, 0x94, 0x26}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega3208", 323, F_AVR8X, {0x1E, 0x95, 0x30}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega3209", 324, F_AVR8X, {0x1E, 0x95, 0x31}, 0, 0x08000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega4808", 325, F_AVR8X, {0x1E, 0x96, 0x50}, 0, 0x0c000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10, 1, 36}, // atdf, avr-gcc 12.2.0, avrdude
+ {"ATmega4809", 326, F_AVR8X, {0x1E, 0x96, 0x51}, 0, 0x0c000, 0x080, 1, 0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10, 1, 40}, // atdf, avr-gcc 12.2.0, avrdude
+ {"AVR8EA28", 327, F_AVR8X, {0x1E, 0x93, 0x2C}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude
+ {"AVR8EA32", 328, F_AVR8X, {0x1E, 0x93, 0x2B}, 0, 0x02000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude
+ {"AVR16DD14", 329, F_AVR8X, {0x1E, 0x94, 0x34}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude
+ {"AVR16DD20", 330, F_AVR8X, {0x1E, 0x94, 0x33}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude
+ {"AVR16DD28", 331, F_AVR8X, {0x1E, 0x94, 0x32}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude
+ {"AVR16EA28", 332, F_AVR8X, {0x1E, 0x94, 0x37}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude
+ {"AVR16DD32", 333, F_AVR8X, {0x1E, 0x94, 0x31}, 0, 0x04000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7800, 0x0800, 16, 4, 36}, // atdf, avrdude
+ {"AVR16EA32", 334, F_AVR8X, {0x1E, 0x94, 0x36}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude
+ {"AVR16EA48", 335, F_AVR8X, {0x1E, 0x94, 0x35}, 0, 0x04000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude
+ {"AVR32DD14", 336, F_AVR8X, {0x1E, 0x95, 0x3B}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude
+ {"AVR32DD20", 337, F_AVR8X, {0x1E, 0x95, 0x3A}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude
+ {"AVR32DA28", 338, F_AVR8X, {0x1E, 0x95, 0x34}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 41}, // atdf, avrdude
+ {"AVR32DB28", 339, F_AVR8X, {0x1E, 0x95, 0x37}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 42}, // atdf, avrdude
+ {"AVR32DD28", 340, F_AVR8X, {0x1E, 0x95, 0x39}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude
+ {"AVR32EA28", 341, F_AVR8X, {0x1E, 0x95, 0x3E}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude
+ {"AVR32DA32", 342, F_AVR8X, {0x1E, 0x95, 0x33}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 44}, // atdf, avrdude
+ {"AVR32DB32", 343, F_AVR8X, {0x1E, 0x95, 0x36}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 44}, // atdf, avrdude
+ {"AVR32DD32", 344, F_AVR8X, {0x1E, 0x95, 0x38}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x7000, 0x1000, 16, 4, 36}, // atdf, avrdude
+ {"AVR32EA32", 345, F_AVR8X, {0x1E, 0x95, 0x3D}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude
+ {"AVR32DA48", 346, F_AVR8X, {0x1E, 0x95, 0x32}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 58}, // atdf, avrdude
+ {"AVR32DB48", 347, F_AVR8X, {0x1E, 0x95, 0x35}, 0, 0x08000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x7000, 0x1000, 16, 4, 61}, // atdf, avrdude
+ {"AVR32EA48", 348, F_AVR8X, {0x1E, 0x95, 0x3C}, 0, 0x08000, 0x040, 1, 0, 0x01400, 0x0200, 8, -1, -1, -1, -1, 0}, // avrdude
+ {"AVR64DD14", 349, F_AVR8X, {0x1E, 0x96, 0x1D}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude
+ {"AVR64DD20", 350, F_AVR8X, {0x1E, 0x96, 0x1C}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude
+ {"AVR64DA28", 351, F_AVR8X, {0x1E, 0x96, 0x15}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 41}, // atdf, avrdude
+ {"AVR64DB28", 352, F_AVR8X, {0x1E, 0x96, 0x19}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 42}, // atdf, avrdude
+ {"AVR64DD28", 353, F_AVR8X, {0x1E, 0x96, 0x1B}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude
+ {"AVR64EA28", 354, F_AVR8X, {0x1E, 0x96, 0x20}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 37}, // atdf, avrdude
+ {"AVR64DA32", 355, F_AVR8X, {0x1E, 0x96, 0x14}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 44}, // atdf, avrdude
+ {"AVR64DB32", 356, F_AVR8X, {0x1E, 0x96, 0x18}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 44}, // atdf, avrdude
+ {"AVR64DD32", 357, F_AVR8X, {0x1E, 0x96, 0x1A}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0100, 1, 0x6000, 0x2000, 16, 4, 36}, // atdf, avrdude
+ {"AVR64EA32", 358, F_AVR8X, {0x1E, 0x96, 0x1F}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 37}, // atdf, avrdude
+ {"AVR64DA48", 359, F_AVR8X, {0x1E, 0x96, 0x13}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 58}, // atdf, avrdude
+ {"AVR64DB48", 360, F_AVR8X, {0x1E, 0x96, 0x17}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 61}, // atdf, avrdude
+ {"AVR64EA48", 361, F_AVR8X, {0x1E, 0x96, 0x1E}, 0, 0x10000, 0x080, 1, 0, 0x01400, 0x0200, 8, 0x6800, 0x1800, 16, 4, 45}, // atdf, avrdude
+ {"AVR64DA64", 362, F_AVR8X, {0x1E, 0x96, 0x12}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 64}, // atdf, avrdude
+ {"AVR64DB64", 363, F_AVR8X, {0x1E, 0x96, 0x16}, 0, 0x10000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x6000, 0x2000, 16, 4, 65}, // atdf, avrdude
+ {"AVR128DA28", 364, F_AVR8X, {0x1E, 0x97, 0x0A}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 41}, // atdf, avrdude
+ {"AVR128DB28", 365, F_AVR8X, {0x1E, 0x97, 0x0E}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 42}, // atdf, avrdude
+ {"AVR128DA32", 366, F_AVR8X, {0x1E, 0x97, 0x09}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 44}, // atdf, avrdude
+ {"AVR128DB32", 367, F_AVR8X, {0x1E, 0x97, 0x0D}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 44}, // atdf, avrdude
+ {"AVR128DA48", 368, F_AVR8X, {0x1E, 0x97, 0x08}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 58}, // atdf, avrdude
+ {"AVR128DB48", 369, F_AVR8X, {0x1E, 0x97, 0x0C}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 61}, // atdf, avrdude
+ {"AVR128DA64", 370, F_AVR8X, {0x1E, 0x97, 0x07}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 64}, // atdf, avrdude
+ {"AVR128DB64", 371, F_AVR8X, {0x1E, 0x97, 0x0B}, 0, 0x20000, 0x200, 1, 0, 0x01400, 0x0200, 1, 0x4000, 0x4000, 16, 4, 65}, // atdf, avrdude
+};
+
+const size_t avr_isp_chip_arr_size = COUNT_OF(avr_isp_chip_arr);
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h
new file mode 100644
index 000000000..66f16a7b9
--- /dev/null
+++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include
+
+#define F_AVR8L 1 // TPI programming, ATtiny(4|5|9|10|20|40|102|104)
+#define F_AVR8 2 // ISP programming with SPI, "classic" AVRs
+#define F_XMEGA 4 // PDI programming, ATxmega family
+#define F_AVR8X 8 // UPDI programming, newer 8-bit MCUs
+
+struct AvrIspChipArr { // Value of -1 typically means unknown
+ const char* name; // Name of part
+ uint16_t mcuid; // ID of MCU in 0..2039
+ uint8_t avrarch; // F_AVR8L, F_AVR8, F_XMEGA or F_AVR8X
+ uint8_t sigs[3]; // Signature bytes
+ int32_t flashoffset; // Flash offset
+ int32_t flashsize; // Flash size
+ int16_t pagesize; // Flash page size
+ int8_t nboots; // Number of supported boot sectors
+ int16_t bootsize; // Size of (smallest) boot sector
+ int32_t eepromoffset; // EEPROM offset
+ int32_t eepromsize; // EEPROM size
+ int32_t eeprompagesize; // EEPROM page size
+ int32_t sramstart; // SRAM offset
+ int32_t sramsize; // SRAM size
+ int8_t nfuses; // Number of fuse bytes
+ int8_t nlocks; // Number of lock bytes
+ uint8_t ninterrupts; // Number of vectors in interrupt vector table
+};
+
+typedef struct AvrIspChipArr AvrIspChipArr;
+
+extern const AvrIspChipArr avr_isp_chip_arr[];
+extern const size_t avr_isp_chip_arr_size;
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c
new file mode 100644
index 000000000..b3c81f3b1
--- /dev/null
+++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.c
@@ -0,0 +1,634 @@
+#include "avr_isp_prog.h"
+#include "avr_isp_prog_cmd.h"
+
+#include
+
+#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320
+#define TAG "AvrIspProg"
+
+struct AvrIspProgSignature {
+ uint8_t vendor;
+ uint8_t part_family;
+ uint8_t part_number;
+};
+
+typedef struct AvrIspProgSignature AvrIspProgSignature;
+
+struct AvrIspProgCfgDevice {
+ uint8_t devicecode;
+ uint8_t revision;
+ uint8_t progtype;
+ uint8_t parmode;
+ uint8_t polling;
+ uint8_t selftimed;
+ uint8_t lockbytes;
+ uint8_t fusebytes;
+ uint8_t flashpoll;
+ uint16_t eeprompoll;
+ uint16_t pagesize;
+ uint16_t eepromsize;
+ uint32_t flashsize;
+};
+
+typedef struct AvrIspProgCfgDevice AvrIspProgCfgDevice;
+
+struct AvrIspProg {
+ AvrIspSpiSw* spi;
+ AvrIspProgCfgDevice* cfg;
+ FuriStreamBuffer* stream_rx;
+ FuriStreamBuffer* stream_tx;
+
+ uint16_t error;
+ uint16_t addr;
+ bool pmode;
+ bool exit;
+ bool rst_active_high;
+ uint8_t buff[AVR_ISP_PROG_TX_RX_BUF_SIZE];
+
+ AvrIspProgCallback callback;
+ void* context;
+};
+
+static void avr_isp_prog_end_pmode(AvrIspProg* instance);
+
+AvrIspProg* avr_isp_prog_init(void) {
+ AvrIspProg* instance = malloc(sizeof(AvrIspProg));
+ instance->cfg = malloc(sizeof(AvrIspProgCfgDevice));
+ instance->stream_rx =
+ furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t));
+ instance->stream_tx =
+ furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t));
+ instance->rst_active_high = false;
+ instance->exit = false;
+ return instance;
+}
+
+void avr_isp_prog_free(AvrIspProg* instance) {
+ furi_assert(instance);
+ if(instance->spi) avr_isp_prog_end_pmode(instance);
+ furi_stream_buffer_free(instance->stream_tx);
+ furi_stream_buffer_free(instance->stream_rx);
+ free(instance->cfg);
+ free(instance);
+}
+
+size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) {
+ return furi_stream_buffer_spaces_available(instance->stream_rx);
+}
+
+bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len) {
+ furi_assert(instance);
+ furi_assert(data);
+ furi_assert(len != 0);
+ size_t ret = furi_stream_buffer_send(instance->stream_rx, data, sizeof(uint8_t) * len, 0);
+ return ret == sizeof(uint8_t) * len;
+}
+
+size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len) {
+ furi_assert(instance);
+ return furi_stream_buffer_receive(instance->stream_tx, data, sizeof(int8_t) * max_len, 0);
+}
+
+void avr_isp_prog_exit(AvrIspProg* instance) {
+ furi_assert(instance);
+ instance->exit = true;
+}
+
+void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context) {
+ furi_assert(instance);
+ furi_assert(context);
+ instance->callback = callback;
+ instance->context = context;
+}
+
+static void avr_isp_prog_tx_ch(AvrIspProg* instance, uint8_t data) {
+ furi_assert(instance);
+ furi_stream_buffer_send(instance->stream_tx, &data, sizeof(uint8_t), FuriWaitForever);
+}
+
+static uint8_t avr_isp_prog_getch(AvrIspProg* instance) {
+ furi_assert(instance);
+ uint8_t data[1] = {0};
+ while(furi_stream_buffer_receive(instance->stream_rx, &data, sizeof(int8_t), 30) == 0) {
+ if(instance->exit) break;
+ };
+ return data[0];
+}
+
+static void avr_isp_prog_fill(AvrIspProg* instance, size_t len) {
+ furi_assert(instance);
+ for(size_t x = 0; x < len; x++) {
+ instance->buff[x] = avr_isp_prog_getch(instance);
+ }
+}
+
+static void avr_isp_prog_reset_target(AvrIspProg* instance, bool reset) {
+ furi_assert(instance);
+ avr_isp_spi_sw_res_set(instance->spi, (reset == instance->rst_active_high) ? true : false);
+}
+
+static uint8_t avr_isp_prog_spi_transaction(
+ AvrIspProg* instance,
+ uint8_t cmd,
+ uint8_t addr_hi,
+ uint8_t addr_lo,
+ uint8_t data) {
+ furi_assert(instance);
+
+ avr_isp_spi_sw_txrx(instance->spi, cmd);
+ avr_isp_spi_sw_txrx(instance->spi, addr_hi);
+ avr_isp_spi_sw_txrx(instance->spi, addr_lo);
+ return avr_isp_spi_sw_txrx(instance->spi, data);
+}
+
+static void avr_isp_prog_empty_reply(AvrIspProg* instance) {
+ furi_assert(instance);
+ if(avr_isp_prog_getch(instance) == CRC_EOP) {
+ avr_isp_prog_tx_ch(instance, STK_INSYNC);
+ avr_isp_prog_tx_ch(instance, STK_OK);
+ } else {
+ instance->error++;
+ avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+ }
+}
+
+static void avr_isp_prog_breply(AvrIspProg* instance, uint8_t data) {
+ furi_assert(instance);
+ if(avr_isp_prog_getch(instance) == CRC_EOP) {
+ avr_isp_prog_tx_ch(instance, STK_INSYNC);
+ avr_isp_prog_tx_ch(instance, data);
+ avr_isp_prog_tx_ch(instance, STK_OK);
+ } else {
+ instance->error++;
+ avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+ }
+}
+
+static void avr_isp_prog_get_version(AvrIspProg* instance, uint8_t data) {
+ furi_assert(instance);
+ switch(data) {
+ case STK_HW_VER:
+ avr_isp_prog_breply(instance, AVR_ISP_HWVER);
+ break;
+ case STK_SW_MAJOR:
+ avr_isp_prog_breply(instance, AVR_ISP_SWMAJ);
+ break;
+ case STK_SW_MINOR:
+ avr_isp_prog_breply(instance, AVR_ISP_SWMIN);
+ break;
+ case AVP_ISP_CONNECT_TYPE:
+ avr_isp_prog_breply(instance, AVP_ISP_SERIAL_CONNECT_TYPE);
+ break;
+ default:
+ avr_isp_prog_breply(instance, AVR_ISP_RESP_0);
+ }
+}
+
+static void avr_isp_prog_set_cfg(AvrIspProg* instance) {
+ furi_assert(instance);
+ // call this after reading cfg packet into buff[]
+ instance->cfg->devicecode = instance->buff[0];
+ instance->cfg->revision = instance->buff[1];
+ instance->cfg->progtype = instance->buff[2];
+ instance->cfg->parmode = instance->buff[3];
+ instance->cfg->polling = instance->buff[4];
+ instance->cfg->selftimed = instance->buff[5];
+ instance->cfg->lockbytes = instance->buff[6];
+ instance->cfg->fusebytes = instance->buff[7];
+ instance->cfg->flashpoll = instance->buff[8];
+ // ignore (instance->buff[9] == instance->buff[8]) //FLASH polling value. Same as flashpoll
+ instance->cfg->eeprompoll = instance->buff[10] << 8 | instance->buff[11];
+ instance->cfg->pagesize = instance->buff[12] << 8 | instance->buff[13];
+ instance->cfg->eepromsize = instance->buff[14] << 8 | instance->buff[15];
+ instance->cfg->flashsize = instance->buff[16] << 24 | instance->buff[17] << 16 |
+ instance->buff[18] << 8 | instance->buff[19];
+
+ // avr devices have active low reset, at89sx are active high
+ instance->rst_active_high = (instance->cfg->devicecode >= 0xe0);
+}
+static bool
+ avr_isp_prog_set_pmode(AvrIspProg* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
+ furi_assert(instance);
+ uint8_t res = 0;
+ avr_isp_spi_sw_txrx(instance->spi, a);
+ avr_isp_spi_sw_txrx(instance->spi, b);
+ res = avr_isp_spi_sw_txrx(instance->spi, c);
+ avr_isp_spi_sw_txrx(instance->spi, d);
+ return res == 0x53;
+}
+
+static void avr_isp_prog_end_pmode(AvrIspProg* instance) {
+ furi_assert(instance);
+ if(instance->pmode) {
+ avr_isp_prog_reset_target(instance, false);
+ // We're about to take the target out of reset
+ // so configure SPI pins as input
+
+ if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+ instance->spi = NULL;
+ }
+
+ instance->pmode = false;
+}
+
+static bool avr_isp_prog_start_pmode(AvrIspProg* instance, AvrIspSpiSwSpeed spi_speed) {
+ furi_assert(instance);
+ // Reset target before driving PIN_SCK or PIN_MOSI
+
+ // SPI.begin() will configure SS as output,
+ // so SPI master mode is selected.
+ // We have defined RESET as pin 10,
+ // which for many arduino's is not the SS pin.
+ // So we have to configure RESET as output here,
+ // (reset_target() first sets the correct level)
+ if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+ instance->spi = avr_isp_spi_sw_init(spi_speed);
+
+ avr_isp_prog_reset_target(instance, true);
+ // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm":
+
+ // Pulse RESET after PIN_SCK is low:
+ avr_isp_spi_sw_sck_set(instance->spi, false);
+
+ // discharge PIN_SCK, value arbitrally chosen
+ furi_delay_ms(20);
+ avr_isp_prog_reset_target(instance, false);
+
+ // Pulse must be minimum 2 target CPU speed cycles
+ // so 100 usec is ok for CPU speeds above 20KHz
+ furi_delay_ms(1);
+
+ avr_isp_prog_reset_target(instance, true);
+
+ // Send the enable programming command:
+ // datasheet: must be > 20 msec
+ furi_delay_ms(50);
+ if(avr_isp_prog_set_pmode(instance, AVR_ISP_SET_PMODE)) {
+ instance->pmode = true;
+ return true;
+ }
+ return false;
+}
+
+static AvrIspProgSignature avr_isp_prog_check_signature(AvrIspProg* instance) {
+ furi_assert(instance);
+ AvrIspProgSignature signature;
+ signature.vendor = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR);
+ signature.part_family = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY);
+ signature.part_number = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER);
+ return signature;
+}
+
+static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) {
+ AvrIspSpiSwSpeed spi_speed[] = {
+ AvrIspSpiSwSpeed1Mhz,
+ AvrIspSpiSwSpeed400Khz,
+ AvrIspSpiSwSpeed250Khz,
+ AvrIspSpiSwSpeed125Khz,
+ AvrIspSpiSwSpeed60Khz,
+ AvrIspSpiSwSpeed40Khz,
+ AvrIspSpiSwSpeed20Khz,
+ AvrIspSpiSwSpeed10Khz,
+ AvrIspSpiSwSpeed5Khz,
+ AvrIspSpiSwSpeed1Khz,
+ };
+ for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) {
+ if(avr_isp_prog_start_pmode(instance, spi_speed[i])) {
+ AvrIspProgSignature sig = avr_isp_prog_check_signature(instance);
+ AvrIspProgSignature sig_examination = avr_isp_prog_check_signature(instance); //-V656
+ uint8_t y = 0;
+ while(y < 8) {
+ if(memcmp(
+ (uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspProgSignature)) !=
+ 0)
+ break;
+ sig_examination = avr_isp_prog_check_signature(instance);
+ y++;
+ }
+ if(y == 8) {
+ if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) {
+ if(i < (COUNT_OF(spi_speed) - 1)) {
+ avr_isp_prog_end_pmode(instance);
+ i++;
+ return avr_isp_prog_start_pmode(instance, spi_speed[i]);
+ }
+ }
+ return true;
+ }
+ }
+ }
+ if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+ return false;
+}
+
+static void avr_isp_prog_universal(AvrIspProg* instance) {
+ furi_assert(instance);
+ uint8_t data;
+
+ avr_isp_prog_fill(instance, 4);
+ data = avr_isp_prog_spi_transaction(
+ instance, instance->buff[0], instance->buff[1], instance->buff[2], instance->buff[3]);
+ avr_isp_prog_breply(instance, data);
+}
+
+static void avr_isp_prog_commit(AvrIspProg* instance, uint16_t addr, uint8_t data) {
+ furi_assert(instance);
+ avr_isp_prog_spi_transaction(instance, AVR_ISP_COMMIT(addr));
+ /* polling flash */
+ if(data == 0xFF) {
+ furi_delay_ms(5);
+ } else {
+ /* polling flash */
+ uint32_t starttime = furi_get_tick();
+ while((furi_get_tick() - starttime) < 30) {
+ if(avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) {
+ break;
+ };
+ }
+ }
+}
+
+static uint16_t avr_isp_prog_current_page(AvrIspProg* instance) {
+ furi_assert(instance);
+ uint16_t page = 0;
+ switch(instance->cfg->pagesize) {
+ case 32:
+ page = instance->addr & 0xFFFFFFF0;
+ break;
+ case 64:
+ page = instance->addr & 0xFFFFFFE0;
+ break;
+ case 128:
+ page = instance->addr & 0xFFFFFFC0;
+ break;
+ case 256:
+ page = instance->addr & 0xFFFFFF80;
+ break;
+
+ default:
+ page = instance->addr;
+ break;
+ }
+
+ return page;
+}
+
+static uint8_t avr_isp_prog_write_flash_pages(AvrIspProg* instance, size_t length) {
+ furi_assert(instance);
+ size_t x = 0;
+ uint16_t page = avr_isp_prog_current_page(instance);
+ while(x < length) {
+ if(page != avr_isp_prog_current_page(instance)) {
+ --x;
+ avr_isp_prog_commit(instance, page, instance->buff[x++]);
+ page = avr_isp_prog_current_page(instance);
+ }
+ avr_isp_prog_spi_transaction(
+ instance, AVR_ISP_WRITE_FLASH_LO(instance->addr, instance->buff[x++]));
+
+ avr_isp_prog_spi_transaction(
+ instance, AVR_ISP_WRITE_FLASH_HI(instance->addr, instance->buff[x++]));
+ instance->addr++;
+ }
+
+ avr_isp_prog_commit(instance, page, instance->buff[--x]);
+ return STK_OK;
+}
+
+static void avr_isp_prog_write_flash(AvrIspProg* instance, size_t length) {
+ furi_assert(instance);
+ avr_isp_prog_fill(instance, length);
+ if(avr_isp_prog_getch(instance) == CRC_EOP) {
+ avr_isp_prog_tx_ch(instance, STK_INSYNC);
+ avr_isp_prog_tx_ch(instance, avr_isp_prog_write_flash_pages(instance, length));
+ } else {
+ instance->error++;
+ avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+ }
+}
+
+// write (length) bytes, (start) is a byte address
+static uint8_t
+ avr_isp_prog_write_eeprom_chunk(AvrIspProg* instance, uint16_t start, uint16_t length) {
+ furi_assert(instance);
+ // this writes byte-by-byte,
+ // page writing may be faster (4 bytes at a time)
+ avr_isp_prog_fill(instance, length);
+ for(uint16_t x = 0; x < length; x++) {
+ uint16_t addr = start + x;
+ avr_isp_prog_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, instance->buff[x]));
+ furi_delay_ms(10);
+ }
+ return STK_OK;
+}
+
+static uint8_t avr_isp_prog_write_eeprom(AvrIspProg* instance, size_t length) {
+ furi_assert(instance);
+ // here is a word address, get the byte address
+ uint16_t start = instance->addr * 2;
+ uint16_t remaining = length;
+ if(length > instance->cfg->eepromsize) {
+ instance->error++;
+ return STK_FAILED;
+ }
+ while(remaining > AVR_ISP_EECHUNK) {
+ avr_isp_prog_write_eeprom_chunk(instance, start, AVR_ISP_EECHUNK);
+ start += AVR_ISP_EECHUNK;
+ remaining -= AVR_ISP_EECHUNK;
+ }
+ avr_isp_prog_write_eeprom_chunk(instance, start, remaining);
+ return STK_OK;
+}
+
+static void avr_isp_prog_program_page(AvrIspProg* instance) {
+ furi_assert(instance);
+ uint8_t result = STK_FAILED;
+ uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance);
+ uint8_t memtype = avr_isp_prog_getch(instance);
+ // flash memory @addr, (length) bytes
+ if(memtype == STK_SET_FLASH_TYPE) {
+ avr_isp_prog_write_flash(instance, length);
+ return;
+ }
+ if(memtype == STK_SET_EEPROM_TYPE) {
+ result = avr_isp_prog_write_eeprom(instance, length);
+ if(avr_isp_prog_getch(instance) == CRC_EOP) {
+ avr_isp_prog_tx_ch(instance, STK_INSYNC);
+ avr_isp_prog_tx_ch(instance, result);
+
+ } else {
+ instance->error++;
+ avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+ }
+ return;
+ }
+ avr_isp_prog_tx_ch(instance, STK_FAILED);
+ return;
+}
+
+static uint8_t avr_isp_prog_flash_read_page(AvrIspProg* instance, uint16_t length) {
+ furi_assert(instance);
+ for(uint16_t x = 0; x < length; x += 2) {
+ avr_isp_prog_tx_ch(
+ instance,
+ avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(instance->addr)));
+ avr_isp_prog_tx_ch(
+ instance,
+ avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(instance->addr)));
+ instance->addr++;
+ }
+ return STK_OK;
+}
+
+static uint8_t avr_isp_prog_eeprom_read_page(AvrIspProg* instance, uint16_t length) {
+ furi_assert(instance);
+ // here again we have a word address
+ uint16_t start = instance->addr * 2;
+ for(uint16_t x = 0; x < length; x++) {
+ uint16_t addr = start + x;
+ avr_isp_prog_tx_ch(
+ instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr)));
+ }
+ return STK_OK;
+}
+
+static void avr_isp_prog_read_page(AvrIspProg* instance) {
+ furi_assert(instance);
+ uint8_t result = STK_FAILED;
+ uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance);
+ uint8_t memtype = avr_isp_prog_getch(instance);
+ if(avr_isp_prog_getch(instance) != CRC_EOP) {
+ instance->error++;
+ avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+ return;
+ }
+ avr_isp_prog_tx_ch(instance, STK_INSYNC);
+ if(memtype == STK_SET_FLASH_TYPE) result = avr_isp_prog_flash_read_page(instance, length);
+ if(memtype == STK_SET_EEPROM_TYPE) result = avr_isp_prog_eeprom_read_page(instance, length);
+ avr_isp_prog_tx_ch(instance, result);
+}
+
+static void avr_isp_prog_read_signature(AvrIspProg* instance) {
+ furi_assert(instance);
+ if(avr_isp_prog_getch(instance) != CRC_EOP) {
+ instance->error++;
+ avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+ return;
+ }
+ avr_isp_prog_tx_ch(instance, STK_INSYNC);
+
+ avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR));
+ avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY));
+ avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER));
+
+ avr_isp_prog_tx_ch(instance, STK_OK);
+}
+
+void avr_isp_prog_avrisp(AvrIspProg* instance) {
+ furi_assert(instance);
+ uint8_t ch = avr_isp_prog_getch(instance);
+
+ switch(ch) {
+ case STK_GET_SYNC:
+ FURI_LOG_D(TAG, "cmd STK_GET_SYNC");
+ instance->error = 0;
+ avr_isp_prog_empty_reply(instance);
+ break;
+ case STK_GET_SIGN_ON:
+ FURI_LOG_D(TAG, "cmd STK_GET_SIGN_ON");
+ if(avr_isp_prog_getch(instance) == CRC_EOP) {
+ avr_isp_prog_tx_ch(instance, STK_INSYNC);
+
+ avr_isp_prog_tx_ch(instance, 'A');
+ avr_isp_prog_tx_ch(instance, 'V');
+ avr_isp_prog_tx_ch(instance, 'R');
+ avr_isp_prog_tx_ch(instance, ' ');
+ avr_isp_prog_tx_ch(instance, 'I');
+ avr_isp_prog_tx_ch(instance, 'S');
+ avr_isp_prog_tx_ch(instance, 'P');
+
+ avr_isp_prog_tx_ch(instance, STK_OK);
+ } else {
+ instance->error++;
+ avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+ }
+ break;
+ case STK_GET_PARAMETER:
+ FURI_LOG_D(TAG, "cmd STK_GET_PARAMETER");
+ avr_isp_prog_get_version(instance, avr_isp_prog_getch(instance));
+ break;
+ case STK_SET_DEVICE:
+ FURI_LOG_D(TAG, "cmd STK_SET_DEVICE");
+ avr_isp_prog_fill(instance, 20);
+ avr_isp_prog_set_cfg(instance);
+ avr_isp_prog_empty_reply(instance);
+ break;
+ case STK_SET_DEVICE_EXT: // ignore for now
+ FURI_LOG_D(TAG, "cmd STK_SET_DEVICE_EXT");
+ avr_isp_prog_fill(instance, 5);
+ avr_isp_prog_empty_reply(instance);
+ break;
+ case STK_ENTER_PROGMODE:
+ FURI_LOG_D(TAG, "cmd STK_ENTER_PROGMODE");
+ if(!instance->pmode) avr_isp_prog_auto_set_spi_speed_start_pmode(instance);
+ avr_isp_prog_empty_reply(instance);
+ break;
+ case STK_LOAD_ADDRESS:
+ FURI_LOG_D(TAG, "cmd STK_LOAD_ADDRESS");
+ instance->addr = avr_isp_prog_getch(instance) | avr_isp_prog_getch(instance) << 8;
+ avr_isp_prog_empty_reply(instance);
+ break;
+ case STK_PROG_FLASH: // ignore for now
+ FURI_LOG_D(TAG, "cmd STK_PROG_FLASH");
+ avr_isp_prog_getch(instance);
+ avr_isp_prog_getch(instance);
+ avr_isp_prog_empty_reply(instance);
+ break;
+ case STK_PROG_DATA: // ignore for now
+ FURI_LOG_D(TAG, "cmd STK_PROG_DATA");
+ avr_isp_prog_getch(instance);
+ avr_isp_prog_empty_reply(instance);
+ break;
+ case STK_PROG_PAGE:
+ FURI_LOG_D(TAG, "cmd STK_PROG_PAGE");
+ avr_isp_prog_program_page(instance);
+ break;
+ case STK_READ_PAGE:
+ FURI_LOG_D(TAG, "cmd STK_READ_PAGE");
+ avr_isp_prog_read_page(instance);
+ break;
+ case STK_UNIVERSAL:
+ FURI_LOG_D(TAG, "cmd STK_UNIVERSAL");
+ avr_isp_prog_universal(instance);
+ break;
+ case STK_LEAVE_PROGMODE:
+ FURI_LOG_D(TAG, "cmd STK_LEAVE_PROGMODE");
+ instance->error = 0;
+ if(instance->pmode) avr_isp_prog_end_pmode(instance);
+ avr_isp_prog_empty_reply(instance);
+ break;
+ case STK_READ_SIGN:
+ FURI_LOG_D(TAG, "cmd STK_READ_SIGN");
+ avr_isp_prog_read_signature(instance);
+ break;
+ // expecting a command, not CRC_EOP
+ // this is how we can get back in sync
+ case CRC_EOP:
+ FURI_LOG_D(TAG, "cmd CRC_EOP");
+ instance->error++;
+ avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+ break;
+ // anything else we will return STK_UNKNOWN
+ default:
+ FURI_LOG_D(TAG, "cmd STK_ERROR_CMD");
+ instance->error++;
+ if(avr_isp_prog_getch(instance) == CRC_EOP)
+ avr_isp_prog_tx_ch(instance, STK_UNKNOWN);
+ else
+ avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+ }
+
+ if(instance->callback) {
+ instance->callback(instance->context);
+ }
+}
diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h
new file mode 100644
index 000000000..2c15ab066
--- /dev/null
+++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "avr_isp_spi_sw.h"
+#include
+
+typedef struct AvrIspProg AvrIspProg;
+typedef void (*AvrIspProgCallback)(void* context);
+
+AvrIspProg* avr_isp_prog_init(void);
+void avr_isp_prog_free(AvrIspProg* instance);
+size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) ;
+bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len);
+size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len);
+void avr_isp_prog_avrisp(AvrIspProg* instance);
+void avr_isp_prog_exit(AvrIspProg* instance);
+void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context);
diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h
new file mode 100644
index 000000000..f8b07203e
--- /dev/null
+++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h
@@ -0,0 +1,97 @@
+#pragma once
+
+// http://ww1.microchip.com/downloads/en/appnotes/atmel-0943-in-system-programming_applicationnote_avr910.pdf
+// AVR ISP Definitions
+#define AVR_ISP_HWVER 0X02
+#define AVR_ISP_SWMAJ 0X01
+#define AVR_ISP_SWMIN 0X12
+#define AVP_ISP_SERIAL_CONNECT_TYPE 0X53
+#define AVP_ISP_CONNECT_TYPE 0x93
+#define AVR_ISP_RESP_0 0X00
+
+#define AVR_ISP_SET_PMODE 0xAC, 0x53, 0x00, 0x00
+#define AVR_ISP_READ_VENDOR 0x30, 0x00, 0x00, 0x00
+#define AVR_ISP_READ_PART_FAMILY 0x30, 0x00, 0x01, 0x00
+#define AVR_ISP_READ_PART_NUMBER 0x30, 0x00, 0x02, 0x00
+#define AVR_ISP_ERASE_CHIP \
+ 0xAC, 0x80, 0x00, 0x00 //Erase Chip, Wait N ms, Release RESET to end the erase.
+//The only way to end a Chip Erase cycle is by temporarily releasing the Reset line
+
+#define AVR_ISP_EXTENDED_ADDR(data) 0x4D, 0x00, data, 0x00
+#define AVR_ISP_WRITE_FLASH_LO(add, data) 0x40, (add >> 8) & 0xFF, add & 0xFF, data
+#define AVR_ISP_WRITE_FLASH_HI(add, data) 0x48, (add >> 8) & 0xFF, add & 0xFF, data
+#define AVR_ISP_READ_FLASH_LO(add) 0x20, (add >> 8) & 0xFF, add & 0xFF, 0x00
+#define AVR_ISP_READ_FLASH_HI(add) 0x28, (add >> 8) & 0xFF, add & 0xFF, 0x00
+
+#define AVR_ISP_WRITE_EEPROM(add, data) \
+ 0xC0, (add >> 8) & 0xFF, add & 0xFF, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_EEPROM(add) 0xA0, (add >> 8) & 0xFF, add & 0xFF, 0xFF
+
+#define AVR_ISP_COMMIT(add) \
+ 0x4C, (add >> 8) & 0xFF, add & 0xFF, 0x00 //Send cmd, polling read last addr page
+
+#define AVR_ISP_OSCCAL(add) 0x38, 0x00, add, 0x00
+
+#define AVR_ISP_WRITE_LOCK_BYTE(data) 0xAC, 0xE0, 0x00, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_LOCK_BYTE 0x58, 0x00, 0x00, 0x00
+#define AVR_ISP_WRITE_FUSE_LOW(data) 0xAC, 0xA0, 0x00, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_FUSE_LOW 0x50, 0x00, 0x00, 0x00
+#define AVR_ISP_WRITE_FUSE_HIGH(data) 0xAC, 0xA8, 0x00, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_FUSE_HIGH 0x58, 0x08, 0x00, 0x00
+#define AVR_ISP_WRITE_FUSE_EXTENDED(data) 0xAC, 0xA4, 0x00, data //Send cmd, Wait N ms (~write)
+#define AVR_ISP_READ_FUSE_EXTENDED 0x50, 0x08, 0x00, 0x00
+
+#define AVR_ISP_EECHUNK 0x20
+
+// https://www.microchip.com/content/dam/mchp/documents/OTH/ApplicationNotes/ApplicationNotes/doc2525.pdf
+// STK Definitions
+#define STK_OK 0x10
+#define STK_FAILED 0x11
+#define STK_UNKNOWN 0x12
+#define STK_INSYNC 0x14
+#define STK_NOSYNC 0x15
+#define CRC_EOP 0x20
+
+#define STK_GET_SYNC 0x30
+#define STK_GET_SIGN_ON 0x31
+#define STK_SET_PARAMETER 0x40
+#define STK_GET_PARAMETER 0x41
+#define STK_SET_DEVICE 0x42
+#define STK_SET_DEVICE_EXT 0x45
+#define STK_ENTER_PROGMODE 0x50
+#define STK_LEAVE_PROGMODE 0x51
+#define STK_CHIP_ERASE 0x52
+#define STK_CHECK_AUTOINC 0x53
+#define STK_LOAD_ADDRESS 0x55
+#define STK_UNIVERSAL 0x56
+#define STK_UNIVERSAL_MULTI 0x57
+#define STK_PROG_FLASH 0x60
+#define STK_PROG_DATA 0x61
+#define STK_PROG_FUSE 0x62
+#define STK_PROG_FUSE_EXT 0x65
+#define STK_PROG_LOCK 0x63
+#define STK_PROG_PAGE 0x64
+#define STK_READ_FLASH 0x70
+#define STK_READ_DATA 0x71
+#define STK_READ_FUSE 0x72
+#define STK_READ_LOCK 0x73
+#define STK_READ_PAGE 0x74
+#define STK_READ_SIGN 0x75
+#define STK_READ_OSCCAL 0x76
+#define STK_READ_FUSE_EXT 0x77
+#define STK_READ_OSCCAL_EXT 0x78
+#define STK_HW_VER 0x80
+#define STK_SW_MAJOR 0x81
+#define STK_SW_MINOR 0x82
+#define STK_LEDS 0x83
+#define STK_VTARGET 0x84
+#define STK_VADJUST 0x85
+#define STK_OSC_PSCALE 0x86
+#define STK_OSC_CMATCH 0x87
+#define STK_SCK_DURATION 0x89
+#define STK_BUFSIZEL 0x90
+#define STK_BUFSIZEH 0x91
+#define STK_STK500_TOPCARD_DETECT 0x98
+
+#define STK_SET_EEPROM_TYPE 0X45
+#define STK_SET_FLASH_TYPE 0X46
diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c
new file mode 100644
index 000000000..c6d9d54c8
--- /dev/null
+++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c
@@ -0,0 +1,71 @@
+#include "avr_isp_spi_sw.h"
+
+#include
+
+#define AVR_ISP_SPI_SW_MISO &gpio_ext_pa6
+#define AVR_ISP_SPI_SW_MOSI &gpio_ext_pa7
+#define AVR_ISP_SPI_SW_SCK &gpio_ext_pb3
+#define AVR_ISP_RESET &gpio_ext_pb2
+
+struct AvrIspSpiSw {
+ AvrIspSpiSwSpeed speed_wait_time;
+ const GpioPin* miso;
+ const GpioPin* mosi;
+ const GpioPin* sck;
+ const GpioPin* res;
+};
+
+AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed) {
+ AvrIspSpiSw* instance = malloc(sizeof(AvrIspSpiSw));
+ instance->speed_wait_time = speed;
+ instance->miso = AVR_ISP_SPI_SW_MISO;
+ instance->mosi = AVR_ISP_SPI_SW_MOSI;
+ instance->sck = AVR_ISP_SPI_SW_SCK;
+ instance->res = AVR_ISP_RESET;
+
+ furi_hal_gpio_init(instance->miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
+ furi_hal_gpio_write(instance->mosi, false);
+ furi_hal_gpio_init(instance->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+ furi_hal_gpio_write(instance->sck, false);
+ furi_hal_gpio_init(instance->sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+ furi_hal_gpio_init(instance->res, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+
+ return instance;
+}
+
+void avr_isp_spi_sw_free(AvrIspSpiSw* instance) {
+ furi_assert(instance);
+ furi_hal_gpio_init(instance->res, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_init(instance->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_init(instance->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_init(instance->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+ free(instance);
+}
+
+uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data) {
+ furi_assert(instance);
+ for(uint8_t i = 0; i < 8; ++i) {
+ furi_hal_gpio_write(instance->mosi, (data & 0x80) ? true : false);
+
+ furi_hal_gpio_write(instance->sck, true);
+ if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz)
+ furi_delay_us(instance->speed_wait_time - 1);
+
+ data = (data << 1) | furi_hal_gpio_read(instance->miso); //-V792
+
+ furi_hal_gpio_write(instance->sck, false);
+ if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz)
+ furi_delay_us(instance->speed_wait_time - 1);
+ }
+ return data;
+}
+
+void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state) {
+ furi_assert(instance);
+ furi_hal_gpio_write(instance->res, state);
+}
+
+void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state) {
+ furi_assert(instance);
+ furi_hal_gpio_write(instance->sck, state);
+}
\ No newline at end of file
diff --git a/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h
new file mode 100644
index 000000000..44de5ff79
--- /dev/null
+++ b/applications/external/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include
+
+typedef enum {
+ AvrIspSpiSwSpeed1Mhz = 0,
+ AvrIspSpiSwSpeed400Khz = 1,
+ AvrIspSpiSwSpeed250Khz = 2,
+ AvrIspSpiSwSpeed125Khz = 4,
+ AvrIspSpiSwSpeed60Khz = 8,
+ AvrIspSpiSwSpeed40Khz = 12,
+ AvrIspSpiSwSpeed20Khz = 24,
+ AvrIspSpiSwSpeed10Khz = 48,
+ AvrIspSpiSwSpeed5Khz = 96,
+ AvrIspSpiSwSpeed1Khz = 480,
+} AvrIspSpiSwSpeed;
+
+typedef struct AvrIspSpiSw AvrIspSpiSw;
+
+AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed);
+void avr_isp_spi_sw_free(AvrIspSpiSw* instance);
+uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data);
+void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state);
+void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state);
\ No newline at end of file
diff --git a/applications/external/subghz_remote/images/Pin_arrow_right_9x7.png b/applications/external/avr_isp_programmer/lib/driver/clock.png
similarity index 83%
rename from applications/external/subghz_remote/images/Pin_arrow_right_9x7.png
rename to applications/external/avr_isp_programmer/lib/driver/clock.png
index 97648d176..93a59fe68 100644
Binary files a/applications/external/subghz_remote/images/Pin_arrow_right_9x7.png and b/applications/external/avr_isp_programmer/lib/driver/clock.png differ
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c
new file mode 100644
index 000000000..4af125aee
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.c
@@ -0,0 +1,30 @@
+#include "../avr_isp_app_i.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const avr_isp_scene_on_enter_handlers[])(void*) = {
+#include "avr_isp_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const avr_isp_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "avr_isp_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const avr_isp_scene_on_exit_handlers[])(void* context) = {
+#include "avr_isp_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers avr_isp_scene_handlers = {
+ .on_enter_handlers = avr_isp_scene_on_enter_handlers,
+ .on_event_handlers = avr_isp_scene_on_event_handlers,
+ .on_exit_handlers = avr_isp_scene_on_exit_handlers,
+ .scene_num = AvrIspSceneNum,
+};
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h
new file mode 100644
index 000000000..658ee7432
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) AvrIspScene##id,
+typedef enum {
+#include "avr_isp_scene_config.h"
+ AvrIspSceneNum,
+} AvrIspScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers avr_isp_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "avr_isp_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+ bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "avr_isp_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "avr_isp_scene_config.h"
+#undef ADD_SCENE
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c
new file mode 100644
index 000000000..e5f530fec
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_about.c
@@ -0,0 +1,99 @@
+#include "../avr_isp_app_i.h"
+#include "../helpers/avr_isp_types.h"
+
+void avr_isp_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ if(type == InputTypeShort) {
+ view_dispatcher_send_custom_event(app->view_dispatcher, result);
+ }
+}
+
+void avr_isp_scene_about_on_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ FuriString* temp_str = furi_string_alloc();
+ furi_string_printf(temp_str, "\e#%s\n", "Information");
+
+ furi_string_cat_printf(temp_str, "Version: %s\n", AVR_ISP_VERSION_APP);
+ furi_string_cat_printf(temp_str, "Developed by: %s\n", AVR_ISP_DEVELOPED);
+ furi_string_cat_printf(temp_str, "Github: %s\n\n", AVR_ISP_GITHUB);
+
+ furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
+ furi_string_cat_printf(
+ temp_str,
+ "This application is an AVR in-system programmer based on stk500mk1. It is compatible with AVR-based"
+ " microcontrollers including Arduino. You can also use it to repair the chip if you accidentally"
+ " corrupt the bootloader.\n\n");
+
+ furi_string_cat_printf(temp_str, "\e#%s\n", "What it can do:");
+ furi_string_cat_printf(temp_str, "- Create a dump of your chip on an SD card\n");
+ furi_string_cat_printf(temp_str, "- Flash your chip firmware from the SD card\n");
+ furi_string_cat_printf(temp_str, "- Act as a wired USB ISP using avrdude software\n\n");
+
+ furi_string_cat_printf(temp_str, "\e#%s\n", "Supported chip series:");
+ furi_string_cat_printf(
+ temp_str,
+ "Example command for avrdude flashing: avrdude.exe -p m328p -c stk500v1 -P COMxx -U flash:r:"
+ "X:\\sketch_sample.hex"
+ ":i\n");
+ furi_string_cat_printf(
+ temp_str,
+ "Where: "
+ "-p m328p"
+ " brand of your chip, "
+ "-P COMxx"
+ " com port number in the system when "
+ "ISP Programmer"
+ " is enabled\n\n");
+
+ furi_string_cat_printf(temp_str, "\e#%s\n", "Info");
+ furi_string_cat_printf(
+ temp_str,
+ "ATtinyXXXX\nATmegaXXXX\nAT43Uxxx\nAT76C711\nAT86RF401\nAT90xxxxx\nAT94K\n"
+ "ATAxxxxx\nATA664251\nM3000\nLGT8F88P\nLGT8F168P\nLGT8F328P\n");
+
+ furi_string_cat_printf(
+ temp_str, "For a more detailed list of supported chips, see AVRDude help\n");
+
+ widget_add_text_box_element(
+ app->widget,
+ 0,
+ 0,
+ 128,
+ 14,
+ AlignCenter,
+ AlignBottom,
+ "\e#\e! \e!\n",
+ false);
+ widget_add_text_box_element(
+ app->widget,
+ 0,
+ 2,
+ 128,
+ 14,
+ AlignCenter,
+ AlignBottom,
+ "\e#\e! ISP Programmer \e!\n",
+ false);
+ widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str));
+ furi_string_free(temp_str);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget);
+}
+
+bool avr_isp_scene_about_on_event(void* context, SceneManagerEvent event) {
+ UNUSED(context);
+ UNUSED(event);
+ return false;
+}
+
+void avr_isp_scene_about_on_exit(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ // Clear views
+ widget_reset(app->widget);
+}
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c
new file mode 100644
index 000000000..79c239390
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c
@@ -0,0 +1,72 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_chip_detect_callback(AvrIspCustomEvent event, void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_chip_detect_on_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ switch(app->error) {
+ case AvrIspErrorReading:
+ case AvrIspErrorWriting:
+ case AvrIspErrorWritingFuse:
+ avr_isp_chip_detect_set_state(
+ app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorOccured);
+ break;
+ case AvrIspErrorVerification:
+ avr_isp_chip_detect_set_state(
+ app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorVerification);
+ break;
+
+ default:
+ avr_isp_chip_detect_set_state(
+ app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateNoDetect);
+ break;
+ }
+ app->error = AvrIspErrorNoError;
+ avr_isp_chip_detect_view_set_callback(
+ app->avr_isp_chip_detect_view, avr_isp_scene_chip_detect_callback, app);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewChipDetect);
+}
+
+bool avr_isp_scene_chip_detect_on_event(void* context, SceneManagerEvent event) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ bool consumed = false;
+ if(event.type == SceneManagerEventTypeCustom) {
+ switch(event.event) {
+ case AvrIspCustomEventSceneChipDetectOk:
+
+ if(scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
+ AvrIspViewProgrammer) {
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneProgrammer);
+ } else if(
+ scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
+ AvrIspViewReader) {
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneInputName);
+ } else if(
+ scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
+ AvrIspViewWriter) {
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneLoad);
+ }
+
+ consumed = true;
+ break;
+ default:
+ break;
+ }
+ } else if(event.type == SceneManagerEventTypeTick) {
+ }
+ return consumed;
+}
+
+void avr_isp_scene_chip_detect_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h
new file mode 100644
index 000000000..6f22511db
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_config.h
@@ -0,0 +1,10 @@
+ADD_SCENE(avr_isp, start, Start)
+ADD_SCENE(avr_isp, about, About)
+ADD_SCENE(avr_isp, programmer, Programmer)
+ADD_SCENE(avr_isp, reader, Reader)
+ADD_SCENE(avr_isp, input_name, InputName)
+ADD_SCENE(avr_isp, load, Load)
+ADD_SCENE(avr_isp, writer, Writer)
+ADD_SCENE(avr_isp, wiring, Wiring)
+ADD_SCENE(avr_isp, chip_detect, ChipDetect)
+ADD_SCENE(avr_isp, success, Success)
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c
new file mode 100644
index 000000000..3394f4362
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_input_name.c
@@ -0,0 +1,89 @@
+#include "../avr_isp_app_i.h"
+#include
+
+#define MAX_TEXT_INPUT_LEN 22
+
+void avr_isp_scene_input_name_text_callback(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneInputName);
+}
+
+void avr_isp_scene_input_name_get_timefilename(FuriString* name) {
+ FuriHalRtcDateTime datetime = {0};
+ furi_hal_rtc_get_datetime(&datetime);
+ furi_string_printf(
+ name,
+ "AVR_dump-%.4d%.2d%.2d-%.2d%.2d%.2d",
+ datetime.year,
+ datetime.month,
+ datetime.day,
+ datetime.hour,
+ datetime.minute,
+ datetime.second);
+}
+
+void avr_isp_scene_input_name_on_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ // Setup view
+ TextInput* text_input = app->text_input;
+ bool dev_name_empty = false;
+
+ FuriString* file_name = furi_string_alloc();
+
+ avr_isp_scene_input_name_get_timefilename(file_name);
+ furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX);
+ //highlighting the entire filename by default
+ dev_name_empty = true;
+
+ strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME);
+ text_input_set_header_text(text_input, "Name dump");
+ text_input_set_result_callback(
+ text_input,
+ avr_isp_scene_input_name_text_callback,
+ app,
+ app->file_name_tmp,
+ MAX_TEXT_INPUT_LEN, // buffer size
+ dev_name_empty);
+
+ ValidatorIsFile* validator_is_file =
+ validator_is_file_alloc_init(STORAGE_APP_DATA_PATH_PREFIX, AVR_ISP_APP_EXTENSION, "");
+ text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+ furi_string_free(file_name);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewTextInput);
+}
+
+bool avr_isp_scene_input_name_on_event(void* context, SceneManagerEvent event) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ if(event.type == SceneManagerEventTypeBack) {
+ scene_manager_previous_scene(app->scene_manager);
+ return true;
+ } else if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == AvrIspCustomEventSceneInputName) {
+ if(strcmp(app->file_name_tmp, "") != 0) {
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneReader);
+ } else {
+ }
+ }
+ }
+ return false;
+}
+
+void avr_isp_scene_input_name_on_exit(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ // Clear validator
+ void* validator_context = text_input_get_validator_callback_context(app->text_input);
+ text_input_set_validator(app->text_input, NULL, NULL);
+ validator_is_file_free(validator_context);
+ // Clear view
+ text_input_reset(app->text_input);
+}
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c
new file mode 100644
index 000000000..e8890e373
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_load.c
@@ -0,0 +1,22 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_load_on_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ if(avr_isp_load_from_file(app)) {
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneWriter);
+ } else {
+ scene_manager_previous_scene(app->scene_manager);
+ }
+}
+
+bool avr_isp_scene_load_on_event(void* context, SceneManagerEvent event) {
+ UNUSED(context);
+ UNUSED(event);
+ return false;
+}
+
+void avr_isp_scene_load_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c
new file mode 100644
index 000000000..0915e1e8a
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_programmer.c
@@ -0,0 +1,28 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_programmer_callback(AvrIspCustomEvent event, void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_programmer_on_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ avr_isp_programmer_view_set_callback(
+ app->avr_isp_programmer_view, avr_isp_scene_programmer_callback, app);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewProgrammer);
+}
+
+bool avr_isp_scene_programmer_on_event(void* context, SceneManagerEvent event) {
+ UNUSED(context);
+ UNUSED(event);
+ return false;
+}
+
+void avr_isp_scene_programmer_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c
new file mode 100644
index 000000000..8dcb47597
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_reader.c
@@ -0,0 +1,64 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_reader_callback(AvrIspCustomEvent event, void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_reader_on_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ avr_isp_reader_set_file_path(
+ app->avr_isp_reader_view, furi_string_get_cstr(app->file_path), app->file_name_tmp);
+ avr_isp_reader_view_set_callback(app->avr_isp_reader_view, avr_isp_scene_reader_callback, app);
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewReader);
+}
+
+bool avr_isp_scene_reader_on_event(void* context, SceneManagerEvent event) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ UNUSED(app);
+ bool consumed = false;
+ if(event.type == SceneManagerEventTypeBack) {
+ //do not handle exit on "Back"
+ consumed = true;
+ } else if(event.type == SceneManagerEventTypeCustom) {
+ switch(event.event) {
+ case AvrIspCustomEventSceneReadingOk:
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneSuccess);
+ consumed = true;
+ break;
+ case AvrIspCustomEventSceneExit:
+ scene_manager_search_and_switch_to_previous_scene(
+ app->scene_manager, AvrIspSceneChipDetect);
+ consumed = true;
+ break;
+ case AvrIspCustomEventSceneErrorVerification:
+ app->error = AvrIspErrorVerification;
+ scene_manager_search_and_switch_to_previous_scene(
+ app->scene_manager, AvrIspSceneChipDetect);
+ consumed = true;
+ break;
+ case AvrIspCustomEventSceneErrorReading:
+ app->error = AvrIspErrorReading;
+ scene_manager_search_and_switch_to_previous_scene(
+ app->scene_manager, AvrIspSceneChipDetect);
+ consumed = true;
+ break;
+ default:
+ break;
+ }
+ } else if(event.type == SceneManagerEventTypeTick) {
+ avr_isp_reader_update_progress(app->avr_isp_reader_view);
+ }
+ return consumed;
+}
+
+void avr_isp_scene_reader_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c
new file mode 100644
index 000000000..b00bfefce
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_start.c
@@ -0,0 +1,75 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_start_submenu_callback(void* context, uint32_t index) {
+ furi_assert(context);
+ AvrIspApp* app = context;
+
+ view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void avr_isp_scene_start_on_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ Submenu* submenu = app->submenu;
+ submenu_add_item(
+ submenu, "Dump AVR", SubmenuIndexAvrIspReader, avr_isp_scene_start_submenu_callback, app);
+ submenu_add_item(
+ submenu, "Flash AVR", SubmenuIndexAvrIspWriter, avr_isp_scene_start_submenu_callback, app);
+ submenu_add_item(
+ submenu,
+ "ISP Programmer",
+ SubmenuIndexAvrIspProgrammer,
+ avr_isp_scene_start_submenu_callback,
+ app);
+ submenu_add_item(
+ submenu, "Wiring", SubmenuIndexAvrIsWiring, avr_isp_scene_start_submenu_callback, app);
+ submenu_add_item(
+ submenu, "About", SubmenuIndexAvrIspAbout, avr_isp_scene_start_submenu_callback, app);
+
+ submenu_set_selected_item(
+ submenu, scene_manager_get_scene_state(app->scene_manager, AvrIspSceneStart));
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewSubmenu);
+}
+
+bool avr_isp_scene_start_on_event(void* context, SceneManagerEvent event) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ bool consumed = false;
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == SubmenuIndexAvrIspAbout) {
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneAbout);
+ consumed = true;
+ } else if(event.event == SubmenuIndexAvrIspProgrammer) {
+ scene_manager_set_scene_state(
+ app->scene_manager, AvrIspSceneChipDetect, AvrIspViewProgrammer);
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
+ consumed = true;
+ } else if(event.event == SubmenuIndexAvrIspReader) {
+ scene_manager_set_scene_state(
+ app->scene_manager, AvrIspSceneChipDetect, AvrIspViewReader);
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
+ consumed = true;
+ } else if(event.event == SubmenuIndexAvrIspWriter) {
+ scene_manager_set_scene_state(
+ app->scene_manager, AvrIspSceneChipDetect, AvrIspViewWriter);
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
+ consumed = true;
+ } else if(event.event == SubmenuIndexAvrIsWiring) {
+ scene_manager_next_scene(app->scene_manager, AvrIspSceneWiring);
+ consumed = true;
+ }
+ scene_manager_set_scene_state(app->scene_manager, AvrIspSceneStart, event.event);
+ }
+
+ return consumed;
+}
+
+void avr_isp_scene_start_on_exit(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ submenu_reset(app->submenu);
+}
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c
new file mode 100644
index 000000000..a88ed28aa
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_success.c
@@ -0,0 +1,44 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_success_popup_callback(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneSuccess);
+}
+
+void avr_isp_scene_success_on_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ Popup* popup = app->popup;
+ popup_set_icon(popup, 32, 5, &I_dolphin_nice_96x59);
+ popup_set_header(popup, "Success!", 8, 22, AlignLeft, AlignBottom);
+ popup_set_timeout(popup, 1500);
+ popup_set_context(popup, app);
+ popup_set_callback(popup, avr_isp_scene_success_popup_callback);
+ popup_enable_timeout(popup);
+ view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewPopup);
+}
+
+bool avr_isp_scene_success_on_event(void* context, SceneManagerEvent event) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == AvrIspCustomEventSceneSuccess) {
+ scene_manager_search_and_switch_to_previous_scene(
+ app->scene_manager, AvrIspSceneStart);
+ return true;
+ }
+ }
+ return false;
+}
+
+void avr_isp_scene_success_on_exit(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ Popup* popup = app->popup;
+ popup_reset(popup);
+}
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c
new file mode 100644
index 000000000..787ed5673
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_wiring.c
@@ -0,0 +1,21 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_wiring_on_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ widget_add_icon_element(app->widget, 0, 0, &I_avr_wiring);
+ view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget);
+}
+
+bool avr_isp_scene_wiring_on_event(void* context, SceneManagerEvent event) {
+ UNUSED(context);
+ UNUSED(event);
+ return false;
+}
+void avr_isp_scene_wiring_on_exit(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ widget_reset(app->widget);
+}
diff --git a/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c
new file mode 100644
index 000000000..39c944fd5
--- /dev/null
+++ b/applications/external/avr_isp_programmer/scenes/avr_isp_scene_writer.c
@@ -0,0 +1,69 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_writer_callback(AvrIspCustomEvent event, void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_writer_on_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ avr_isp_writer_set_file_path(
+ app->avr_isp_writer_view, furi_string_get_cstr(app->file_path), app->file_name_tmp);
+ avr_isp_writer_view_set_callback(app->avr_isp_writer_view, avr_isp_scene_writer_callback, app);
+ view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWriter);
+}
+
+bool avr_isp_scene_writer_on_event(void* context, SceneManagerEvent event) {
+ furi_assert(context);
+
+ AvrIspApp* app = context;
+ bool consumed = false;
+ if(event.type == SceneManagerEventTypeBack) {
+ //do not handle exit on "Back"
+ consumed = true;
+ } else if(event.type == SceneManagerEventTypeCustom) {
+ switch(event.event) {
+ case AvrIspCustomEventSceneExitStartMenu:
+ scene_manager_search_and_switch_to_previous_scene(
+ app->scene_manager, AvrIspSceneStart);
+ consumed = true;
+ break;
+ case AvrIspCustomEventSceneExit:
+ scene_manager_search_and_switch_to_previous_scene(
+ app->scene_manager, AvrIspSceneChipDetect);
+ consumed = true;
+ break;
+ case AvrIspCustomEventSceneErrorVerification:
+ app->error = AvrIspErrorVerification;
+ scene_manager_search_and_switch_to_previous_scene(
+ app->scene_manager, AvrIspSceneChipDetect);
+ consumed = true;
+ break;
+ case AvrIspCustomEventSceneErrorWriting:
+ app->error = AvrIspErrorWriting;
+ scene_manager_search_and_switch_to_previous_scene(
+ app->scene_manager, AvrIspSceneChipDetect);
+ consumed = true;
+ break;
+ case AvrIspCustomEventSceneErrorWritingFuse:
+ app->error = AvrIspErrorWritingFuse;
+ scene_manager_search_and_switch_to_previous_scene(
+ app->scene_manager, AvrIspSceneChipDetect);
+ consumed = true;
+ break;
+ default:
+ break;
+ }
+ } else if(event.type == SceneManagerEventTypeTick) {
+ avr_isp_writer_update_progress(app->avr_isp_writer_view);
+ }
+ return consumed;
+}
+
+void avr_isp_scene_writer_on_exit(void* context) {
+ UNUSED(context);
+}
diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c
new file mode 100644
index 000000000..fdcf71c36
--- /dev/null
+++ b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.c
@@ -0,0 +1,213 @@
+#include "avr_isp_view_chip_detect.h"
+#include
+#include
+
+#include "../helpers/avr_isp_worker_rw.h"
+
+struct AvrIspChipDetectView {
+ View* view;
+ AvrIspWorkerRW* avr_isp_worker_rw;
+ AvrIspChipDetectViewCallback callback;
+ void* context;
+};
+
+typedef struct {
+ uint16_t idx;
+ const char* name_chip;
+ uint32_t flash_size;
+ AvrIspChipDetectViewState state;
+} AvrIspChipDetectViewModel;
+
+void avr_isp_chip_detect_view_set_callback(
+ AvrIspChipDetectView* instance,
+ AvrIspChipDetectViewCallback callback,
+ void* context) {
+ furi_assert(instance);
+ furi_assert(callback);
+
+ instance->callback = callback;
+ instance->context = context;
+}
+
+void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state) {
+ furi_assert(instance);
+
+ with_view_model(
+ instance->view, AvrIspChipDetectViewModel * model, { model->state = state; }, true);
+}
+
+void avr_isp_chip_detect_view_draw(Canvas* canvas, AvrIspChipDetectViewModel* model) {
+ canvas_clear(canvas);
+
+ char str_buf[64] = {0};
+ canvas_set_font(canvas, FontPrimary);
+
+ switch(model->state) {
+ case AvrIspChipDetectViewStateDetected:
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip detected!");
+ canvas_draw_icon(canvas, 29, 14, &I_chip_long_70x22);
+ canvas_set_font(canvas, FontSecondary);
+ snprintf(str_buf, sizeof(str_buf), "%ld Kb", model->flash_size / 1024);
+ canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignCenter, str_buf);
+ canvas_draw_str_aligned(canvas, 64, 45, AlignCenter, AlignCenter, model->name_chip);
+ elements_button_right(canvas, "Next");
+ break;
+ case AvrIspChipDetectViewStateErrorOccured:
+ canvas_draw_str_aligned(
+ canvas, 64, 5, AlignCenter, AlignCenter, "Error occured, try again!");
+ canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22);
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str_aligned(
+ canvas, 64, 45, AlignCenter, AlignCenter, "Check the wiring and retry");
+ break;
+ case AvrIspChipDetectViewStateErrorVerification:
+ canvas_draw_str_aligned(
+ canvas, 64, 5, AlignCenter, AlignCenter, "Data verification failed");
+ canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22);
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str_aligned(
+ canvas, 64, 45, AlignCenter, AlignCenter, "Try to restart the process");
+ break;
+
+ default:
+ //AvrIspChipDetectViewStateNoDetect
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip not found!");
+ canvas_draw_icon(canvas, 29, 12, &I_chif_not_found_83x37);
+
+ break;
+ }
+ canvas_set_font(canvas, FontSecondary);
+ elements_button_left(canvas, "Retry");
+}
+
+bool avr_isp_chip_detect_view_input(InputEvent* event, void* context) {
+ furi_assert(context);
+
+ AvrIspChipDetectView* instance = context;
+
+ if(event->type == InputTypeShort) {
+ if(event->key == InputKeyBack) {
+ return false;
+ } else if(event->key == InputKeyRight) {
+ with_view_model(
+ instance->view,
+ AvrIspChipDetectViewModel * model,
+ {
+ if(model->state == AvrIspChipDetectViewStateDetected) {
+ if(instance->callback)
+ instance->callback(
+ AvrIspCustomEventSceneChipDetectOk, instance->context);
+ }
+ },
+ false);
+
+ } else if(event->key == InputKeyLeft) {
+ bool detect_chip = false;
+ with_view_model(
+ instance->view,
+ AvrIspChipDetectViewModel * model,
+ {
+ if(model->state != AvrIspChipDetectViewStateDetecting) {
+ model->state = AvrIspChipDetectViewStateDetecting;
+ detect_chip = true;
+ }
+ },
+ false);
+ if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw);
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+static void avr_isp_chip_detect_detect_chip_callback(
+ void* context,
+ const char* name,
+ bool detect_chip,
+ uint32_t flash_size) {
+ furi_assert(context);
+
+ AvrIspChipDetectView* instance = context;
+ with_view_model(
+ instance->view,
+ AvrIspChipDetectViewModel * model,
+ {
+ model->name_chip = name;
+ model->flash_size = flash_size;
+ if(detect_chip) {
+ model->state = AvrIspChipDetectViewStateDetected;
+ } else {
+ model->state = AvrIspChipDetectViewStateNoDetect;
+ }
+ },
+ true);
+}
+void avr_isp_chip_detect_view_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspChipDetectView* instance = context;
+ bool detect_chip = false;
+ with_view_model(
+ instance->view,
+ AvrIspChipDetectViewModel * model,
+ {
+ if(model->state == AvrIspChipDetectViewStateNoDetect ||
+ model->state == AvrIspChipDetectViewStateDetected) {
+ detect_chip = true;
+ }
+ },
+ false);
+
+ //Start avr_isp_worker_rw
+ instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context);
+
+ avr_isp_worker_rw_set_callback(
+ instance->avr_isp_worker_rw, avr_isp_chip_detect_detect_chip_callback, instance);
+
+ if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw);
+}
+
+void avr_isp_chip_detect_view_exit(void* context) {
+ furi_assert(context);
+
+ AvrIspChipDetectView* instance = context;
+
+ avr_isp_worker_rw_set_callback(instance->avr_isp_worker_rw, NULL, NULL);
+ avr_isp_worker_rw_free(instance->avr_isp_worker_rw);
+}
+
+AvrIspChipDetectView* avr_isp_chip_detect_view_alloc() {
+ AvrIspChipDetectView* instance = malloc(sizeof(AvrIspChipDetectView));
+
+ // View allocation and configuration
+ instance->view = view_alloc();
+
+ view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspChipDetectViewModel));
+ view_set_context(instance->view, instance);
+ view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_chip_detect_view_draw);
+ view_set_input_callback(instance->view, avr_isp_chip_detect_view_input);
+ view_set_enter_callback(instance->view, avr_isp_chip_detect_view_enter);
+ view_set_exit_callback(instance->view, avr_isp_chip_detect_view_exit);
+
+ with_view_model(
+ instance->view,
+ AvrIspChipDetectViewModel * model,
+ { model->state = AvrIspChipDetectViewStateNoDetect; },
+ false);
+ return instance;
+}
+
+void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance) {
+ furi_assert(instance);
+
+ view_free(instance->view);
+ free(instance);
+}
+
+View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance) {
+ furi_assert(instance);
+
+ return instance->view;
+}
diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h
new file mode 100644
index 000000000..37f2ae233
--- /dev/null
+++ b/applications/external/avr_isp_programmer/views/avr_isp_view_chip_detect.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspChipDetectView AvrIspChipDetectView;
+
+typedef void (*AvrIspChipDetectViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+ AvrIspChipDetectViewStateNoDetect,
+ AvrIspChipDetectViewStateDetecting,
+ AvrIspChipDetectViewStateDetected,
+ AvrIspChipDetectViewStateErrorOccured,
+ AvrIspChipDetectViewStateErrorVerification,
+} AvrIspChipDetectViewState;
+
+void avr_isp_chip_detect_view_set_callback(
+ AvrIspChipDetectView* instance,
+ AvrIspChipDetectViewCallback callback,
+ void* context);
+
+void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state);
+
+AvrIspChipDetectView* avr_isp_chip_detect_view_alloc();
+
+void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance);
+
+View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance);
+
+void avr_isp_chip_detect_view_exit(void* context);
diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c
new file mode 100644
index 000000000..34e18770b
--- /dev/null
+++ b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.c
@@ -0,0 +1,134 @@
+#include "avr_isp_view_programmer.h"
+#include
+
+#include "../helpers/avr_isp_worker.h"
+#include
+
+struct AvrIspProgrammerView {
+ View* view;
+ AvrIspWorker* worker;
+ AvrIspProgrammerViewCallback callback;
+ void* context;
+};
+
+typedef struct {
+ AvrIspProgrammerViewStatus status;
+} AvrIspProgrammerViewModel;
+
+void avr_isp_programmer_view_set_callback(
+ AvrIspProgrammerView* instance,
+ AvrIspProgrammerViewCallback callback,
+ void* context) {
+ furi_assert(instance);
+ furi_assert(callback);
+
+ instance->callback = callback;
+ instance->context = context;
+}
+
+void avr_isp_programmer_view_draw(Canvas* canvas, AvrIspProgrammerViewModel* model) {
+ canvas_clear(canvas);
+
+ if(model->status == AvrIspProgrammerViewStatusUSBConnect) {
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_icon(canvas, 0, 0, &I_isp_active_128x53);
+ elements_multiline_text(canvas, 45, 10, "ISP mode active");
+ } else {
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_icon(canvas, 51, 6, &I_link_waiting_77x56);
+ elements_multiline_text(canvas, 0, 25, "Waiting for\nsoftware\nconnection");
+ }
+}
+
+bool avr_isp_programmer_view_input(InputEvent* event, void* context) {
+ furi_assert(context);
+ UNUSED(context);
+
+ if(event->key == InputKeyBack || event->type != InputTypeShort) {
+ return false;
+ }
+
+ return true;
+}
+
+static void avr_isp_programmer_usb_connect_callback(void* context, bool status_connect) {
+ furi_assert(context);
+ AvrIspProgrammerView* instance = context;
+
+ with_view_model(
+ instance->view,
+ AvrIspProgrammerViewModel * model,
+ {
+ if(status_connect) {
+ model->status = AvrIspProgrammerViewStatusUSBConnect;
+ } else {
+ model->status = AvrIspProgrammerViewStatusNoUSBConnect;
+ }
+ },
+ true);
+}
+
+void avr_isp_programmer_view_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspProgrammerView* instance = context;
+ with_view_model(
+ instance->view,
+ AvrIspProgrammerViewModel * model,
+ { model->status = AvrIspProgrammerViewStatusNoUSBConnect; },
+ true);
+
+ //Start worker
+ instance->worker = avr_isp_worker_alloc(instance->context);
+
+ avr_isp_worker_set_callback(
+ instance->worker, avr_isp_programmer_usb_connect_callback, instance);
+
+ avr_isp_worker_start(instance->worker);
+}
+
+void avr_isp_programmer_view_exit(void* context) {
+ furi_assert(context);
+
+ AvrIspProgrammerView* instance = context;
+ //Stop worker
+ if(avr_isp_worker_is_running(instance->worker)) {
+ avr_isp_worker_stop(instance->worker);
+ }
+ avr_isp_worker_set_callback(instance->worker, NULL, NULL);
+ avr_isp_worker_free(instance->worker);
+}
+
+AvrIspProgrammerView* avr_isp_programmer_view_alloc() {
+ AvrIspProgrammerView* instance = malloc(sizeof(AvrIspProgrammerView));
+
+ // View allocation and configuration
+ instance->view = view_alloc();
+
+ view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspProgrammerViewModel));
+ view_set_context(instance->view, instance);
+ view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_programmer_view_draw);
+ view_set_input_callback(instance->view, avr_isp_programmer_view_input);
+ view_set_enter_callback(instance->view, avr_isp_programmer_view_enter);
+ view_set_exit_callback(instance->view, avr_isp_programmer_view_exit);
+
+ with_view_model(
+ instance->view,
+ AvrIspProgrammerViewModel * model,
+ { model->status = AvrIspProgrammerViewStatusNoUSBConnect; },
+ false);
+ return instance;
+}
+
+void avr_isp_programmer_view_free(AvrIspProgrammerView* instance) {
+ furi_assert(instance);
+
+ view_free(instance->view);
+ free(instance);
+}
+
+View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance) {
+ furi_assert(instance);
+
+ return instance->view;
+}
diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h
new file mode 100644
index 000000000..9f005b026
--- /dev/null
+++ b/applications/external/avr_isp_programmer/views/avr_isp_view_programmer.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspProgrammerView AvrIspProgrammerView;
+
+typedef void (*AvrIspProgrammerViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+ AvrIspProgrammerViewStatusNoUSBConnect,
+ AvrIspProgrammerViewStatusUSBConnect,
+} AvrIspProgrammerViewStatus;
+
+void avr_isp_programmer_view_set_callback(
+ AvrIspProgrammerView* instance,
+ AvrIspProgrammerViewCallback callback,
+ void* context);
+
+AvrIspProgrammerView* avr_isp_programmer_view_alloc();
+
+void avr_isp_programmer_view_free(AvrIspProgrammerView* instance);
+
+View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance);
+
+void avr_isp_programmer_view_exit(void* context);
diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c
new file mode 100644
index 000000000..92d15bd7f
--- /dev/null
+++ b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.c
@@ -0,0 +1,215 @@
+#include "avr_isp_view_reader.h"
+#include
+
+#include "../helpers/avr_isp_worker_rw.h"
+
+struct AvrIspReaderView {
+ View* view;
+ AvrIspWorkerRW* avr_isp_worker_rw;
+ const char* file_path;
+ const char* file_name;
+ AvrIspReaderViewCallback callback;
+ void* context;
+};
+
+typedef struct {
+ AvrIspReaderViewStatus status;
+ float progress_flash;
+ float progress_eeprom;
+} AvrIspReaderViewModel;
+
+void avr_isp_reader_update_progress(AvrIspReaderView* instance) {
+ with_view_model(
+ instance->view,
+ AvrIspReaderViewModel * model,
+ {
+ model->progress_flash =
+ avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw);
+ model->progress_eeprom =
+ avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw);
+ },
+ true);
+}
+
+void avr_isp_reader_view_set_callback(
+ AvrIspReaderView* instance,
+ AvrIspReaderViewCallback callback,
+ void* context) {
+ furi_assert(instance);
+ furi_assert(callback);
+
+ instance->callback = callback;
+ instance->context = context;
+}
+
+void avr_isp_reader_set_file_path(
+ AvrIspReaderView* instance,
+ const char* file_path,
+ const char* file_name) {
+ furi_assert(instance);
+
+ instance->file_path = file_path;
+ instance->file_name = file_name;
+}
+
+void avr_isp_reader_view_draw(Canvas* canvas, AvrIspReaderViewModel* model) {
+ canvas_clear(canvas);
+ char str_buf[64] = {0};
+
+ canvas_set_font(canvas, FontPrimary);
+ switch(model->status) {
+ case AvrIspReaderViewStatusIDLE:
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to dump");
+ canvas_set_font(canvas, FontSecondary);
+ elements_button_center(canvas, "Start");
+ break;
+ case AvrIspReaderViewStatusReading:
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Reading dump");
+ break;
+ case AvrIspReaderViewStatusVerification:
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifyng dump");
+ break;
+
+ default:
+ break;
+ }
+
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str(canvas, 0, 27, "Flash");
+ snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100));
+ elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_buf);
+ canvas_draw_str(canvas, 0, 43, "EEPROM");
+ snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+ elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_buf);
+}
+
+bool avr_isp_reader_view_input(InputEvent* event, void* context) {
+ furi_assert(context);
+ AvrIspReaderView* instance = context;
+
+ bool ret = true;
+ if(event->key == InputKeyBack && event->type == InputTypeShort) {
+ with_view_model(
+ instance->view,
+ AvrIspReaderViewModel * model,
+ {
+ if(model->status == AvrIspReaderViewStatusIDLE) {
+ if(instance->callback)
+ instance->callback(AvrIspCustomEventSceneExit, instance->context);
+ ret = false;
+ }
+ },
+ false);
+ } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+ with_view_model(
+ instance->view,
+ AvrIspReaderViewModel * model,
+ {
+ if(model->status == AvrIspReaderViewStatusIDLE) {
+ model->status = AvrIspReaderViewStatusReading;
+ avr_isp_worker_rw_read_dump_start(
+ instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+ }
+ },
+ false);
+ }
+ return ret;
+}
+
+static void avr_isp_reader_callback_status(void* context, AvrIspWorkerRWStatus status) {
+ furi_assert(context);
+ AvrIspReaderView* instance = context;
+
+ with_view_model(
+ instance->view,
+ AvrIspReaderViewModel * model,
+ {
+ switch(status) {
+ case AvrIspWorkerRWStatusEndReading:
+ model->status = AvrIspReaderViewStatusVerification;
+ avr_isp_worker_rw_verification_start(
+ instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+ model->status = AvrIspReaderViewStatusVerification;
+ break;
+ case AvrIspWorkerRWStatusEndVerification:
+ if(instance->callback)
+ instance->callback(AvrIspCustomEventSceneReadingOk, instance->context);
+ break;
+ case AvrIspWorkerRWStatusErrorVerification:
+ if(instance->callback)
+ instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context);
+ break;
+
+ default:
+ //AvrIspWorkerRWStatusErrorReading;
+ if(instance->callback)
+ instance->callback(AvrIspCustomEventSceneErrorReading, instance->context);
+ break;
+ }
+ },
+ true);
+}
+
+void avr_isp_reader_view_enter(void* context) {
+ furi_assert(context);
+ AvrIspReaderView* instance = context;
+
+ with_view_model(
+ instance->view,
+ AvrIspReaderViewModel * model,
+ {
+ model->status = AvrIspReaderViewStatusIDLE;
+ model->progress_flash = 0.0f;
+ model->progress_eeprom = 0.0f;
+ },
+ true);
+
+ //Start avr_isp_worker_rw
+ instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context);
+
+ avr_isp_worker_rw_set_callback_status(
+ instance->avr_isp_worker_rw, avr_isp_reader_callback_status, instance);
+
+ avr_isp_worker_rw_start(instance->avr_isp_worker_rw);
+}
+
+void avr_isp_reader_view_exit(void* context) {
+ furi_assert(context);
+
+ AvrIspReaderView* instance = context;
+ //Stop avr_isp_worker_rw
+ if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) {
+ avr_isp_worker_rw_stop(instance->avr_isp_worker_rw);
+ }
+
+ avr_isp_worker_rw_free(instance->avr_isp_worker_rw);
+}
+
+AvrIspReaderView* avr_isp_reader_view_alloc() {
+ AvrIspReaderView* instance = malloc(sizeof(AvrIspReaderView));
+
+ // View allocation and configuration
+ instance->view = view_alloc();
+
+ view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspReaderViewModel));
+ view_set_context(instance->view, instance);
+ view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_reader_view_draw);
+ view_set_input_callback(instance->view, avr_isp_reader_view_input);
+ view_set_enter_callback(instance->view, avr_isp_reader_view_enter);
+ view_set_exit_callback(instance->view, avr_isp_reader_view_exit);
+
+ return instance;
+}
+
+void avr_isp_reader_view_free(AvrIspReaderView* instance) {
+ furi_assert(instance);
+
+ view_free(instance->view);
+ free(instance);
+}
+
+View* avr_isp_reader_view_get_view(AvrIspReaderView* instance) {
+ furi_assert(instance);
+
+ return instance->view;
+}
diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h
new file mode 100644
index 000000000..44a439948
--- /dev/null
+++ b/applications/external/avr_isp_programmer/views/avr_isp_view_reader.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspReaderView AvrIspReaderView;
+
+typedef void (*AvrIspReaderViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+ AvrIspReaderViewStatusIDLE,
+ AvrIspReaderViewStatusReading,
+ AvrIspReaderViewStatusVerification,
+} AvrIspReaderViewStatus;
+
+void avr_isp_reader_update_progress(AvrIspReaderView* instance);
+
+void avr_isp_reader_set_file_path(
+ AvrIspReaderView* instance,
+ const char* file_path,
+ const char* file_name);
+
+void avr_isp_reader_view_set_callback(
+ AvrIspReaderView* instance,
+ AvrIspReaderViewCallback callback,
+ void* context);
+
+AvrIspReaderView* avr_isp_reader_view_alloc();
+
+void avr_isp_reader_view_free(AvrIspReaderView* instance);
+
+View* avr_isp_reader_view_get_view(AvrIspReaderView* instance);
+
+void avr_isp_reader_view_exit(void* context);
diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c
new file mode 100644
index 000000000..a06b78535
--- /dev/null
+++ b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.c
@@ -0,0 +1,268 @@
+#include "avr_isp_view_writer.h"
+#include
+
+#include "../helpers/avr_isp_worker_rw.h"
+#include
+
+struct AvrIspWriterView {
+ View* view;
+ AvrIspWorkerRW* avr_isp_worker_rw;
+ const char* file_path;
+ const char* file_name;
+ AvrIspWriterViewCallback callback;
+ void* context;
+};
+
+typedef struct {
+ AvrIspWriterViewStatus status;
+ float progress_flash;
+ float progress_eeprom;
+} AvrIspWriterViewModel;
+
+void avr_isp_writer_update_progress(AvrIspWriterView* instance) {
+ with_view_model(
+ instance->view,
+ AvrIspWriterViewModel * model,
+ {
+ model->progress_flash =
+ avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw);
+ model->progress_eeprom =
+ avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw);
+ },
+ true);
+}
+
+void avr_isp_writer_view_set_callback(
+ AvrIspWriterView* instance,
+ AvrIspWriterViewCallback callback,
+ void* context) {
+ furi_assert(instance);
+ furi_assert(callback);
+
+ instance->callback = callback;
+ instance->context = context;
+}
+
+void avr_isp_writer_set_file_path(
+ AvrIspWriterView* instance,
+ const char* file_path,
+ const char* file_name) {
+ furi_assert(instance);
+
+ instance->file_path = file_path;
+ instance->file_name = file_name;
+}
+
+void avr_isp_writer_view_draw(Canvas* canvas, AvrIspWriterViewModel* model) {
+ canvas_clear(canvas);
+ char str_flash[32] = {0};
+ char str_eeprom[32] = {0};
+
+ canvas_set_font(canvas, FontPrimary);
+
+ switch(model->status) {
+ case AvrIspWriterViewStatusIDLE:
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to write");
+ canvas_set_font(canvas, FontSecondary);
+ elements_button_center(canvas, "Start");
+ snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+ snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+ break;
+ case AvrIspWriterViewStatusWriting:
+ if(float_is_equal(model->progress_flash, 0.f)) {
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying firmware");
+ snprintf(str_flash, sizeof(str_flash), "***");
+ snprintf(str_eeprom, sizeof(str_eeprom), "***");
+ } else {
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing dump");
+ snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+ snprintf(
+ str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+ }
+ break;
+ case AvrIspWriterViewStatusVerification:
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying dump");
+ snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+ snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+ break;
+ case AvrIspWriterViewStatusWritingFuse:
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing fuse");
+ snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+ snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+ break;
+ case AvrIspWriterViewStatusWritingFuseOk:
+ canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Done!");
+ snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+ snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+ canvas_set_font(canvas, FontSecondary);
+ elements_button_center(canvas, "Reflash");
+ elements_button_right(canvas, "Exit");
+ break;
+
+ default:
+ break;
+ }
+
+ canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str(canvas, 0, 27, "Flash");
+ // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100));
+ elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_flash);
+ canvas_draw_str(canvas, 0, 43, "EEPROM");
+ // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+ elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_eeprom);
+}
+
+bool avr_isp_writer_view_input(InputEvent* event, void* context) {
+ furi_assert(context);
+ AvrIspWriterView* instance = context;
+
+ bool ret = true;
+ if(event->key == InputKeyBack && event->type == InputTypeShort) {
+ with_view_model(
+ instance->view,
+ AvrIspWriterViewModel * model,
+ {
+ if((model->status == AvrIspWriterViewStatusIDLE) ||
+ (model->status == AvrIspWriterViewStatusWritingFuseOk)) {
+ if(instance->callback)
+ instance->callback(AvrIspCustomEventSceneExit, instance->context);
+ ret = false;
+ }
+ },
+ false);
+ } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+ with_view_model(
+ instance->view,
+ AvrIspWriterViewModel * model,
+ {
+ if((model->status == AvrIspWriterViewStatusIDLE) ||
+ (model->status == AvrIspWriterViewStatusWritingFuseOk)) {
+ model->status = AvrIspWriterViewStatusWriting;
+
+ avr_isp_worker_rw_write_dump_start(
+ instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+ }
+ },
+ false);
+ } else if(event->key == InputKeyRight && event->type == InputTypeShort) {
+ with_view_model(
+ instance->view,
+ AvrIspWriterViewModel * model,
+ {
+ if((model->status == AvrIspWriterViewStatusIDLE) ||
+ (model->status == AvrIspWriterViewStatusWritingFuseOk)) {
+ if(instance->callback)
+ instance->callback(AvrIspCustomEventSceneExitStartMenu, instance->context);
+ ret = false;
+ }
+ },
+ false);
+ }
+ return ret;
+}
+
+static void avr_isp_writer_callback_status(void* context, AvrIspWorkerRWStatus status) {
+ furi_assert(context);
+
+ AvrIspWriterView* instance = context;
+ with_view_model(
+ instance->view,
+ AvrIspWriterViewModel * model,
+ {
+ switch(status) {
+ case AvrIspWorkerRWStatusEndWriting:
+ model->status = AvrIspWriterViewStatusVerification;
+ avr_isp_worker_rw_verification_start(
+ instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+ model->status = AvrIspWriterViewStatusVerification;
+ break;
+ case AvrIspWorkerRWStatusErrorVerification:
+ if(instance->callback)
+ instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context);
+ break;
+ case AvrIspWorkerRWStatusEndVerification:
+ avr_isp_worker_rw_write_fuse_start(
+ instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+ model->status = AvrIspWriterViewStatusWritingFuse;
+ break;
+ case AvrIspWorkerRWStatusErrorWritingFuse:
+ if(instance->callback)
+ instance->callback(AvrIspCustomEventSceneErrorWritingFuse, instance->context);
+ break;
+ case AvrIspWorkerRWStatusEndWritingFuse:
+ model->status = AvrIspWriterViewStatusWritingFuseOk;
+ break;
+
+ default:
+ //AvrIspWorkerRWStatusErrorWriting;
+ if(instance->callback)
+ instance->callback(AvrIspCustomEventSceneErrorWriting, instance->context);
+ break;
+ }
+ },
+ true);
+}
+
+void avr_isp_writer_view_enter(void* context) {
+ furi_assert(context);
+
+ AvrIspWriterView* instance = context;
+ with_view_model(
+ instance->view,
+ AvrIspWriterViewModel * model,
+ {
+ model->status = AvrIspWriterViewStatusIDLE;
+ model->progress_flash = 0.0f;
+ model->progress_eeprom = 0.0f;
+ },
+ true);
+
+ //Start avr_isp_worker_rw
+ instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context);
+
+ avr_isp_worker_rw_set_callback_status(
+ instance->avr_isp_worker_rw, avr_isp_writer_callback_status, instance);
+
+ avr_isp_worker_rw_start(instance->avr_isp_worker_rw);
+}
+
+void avr_isp_writer_view_exit(void* context) {
+ furi_assert(context);
+ AvrIspWriterView* instance = context;
+
+ //Stop avr_isp_worker_rw
+ if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) {
+ avr_isp_worker_rw_stop(instance->avr_isp_worker_rw);
+ }
+
+ avr_isp_worker_rw_free(instance->avr_isp_worker_rw);
+}
+
+AvrIspWriterView* avr_isp_writer_view_alloc() {
+ AvrIspWriterView* instance = malloc(sizeof(AvrIspWriterView));
+
+ // View allocation and configuration
+ instance->view = view_alloc();
+
+ view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspWriterViewModel));
+ view_set_context(instance->view, instance);
+ view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_writer_view_draw);
+ view_set_input_callback(instance->view, avr_isp_writer_view_input);
+ view_set_enter_callback(instance->view, avr_isp_writer_view_enter);
+ view_set_exit_callback(instance->view, avr_isp_writer_view_exit);
+
+ return instance;
+}
+
+void avr_isp_writer_view_free(AvrIspWriterView* instance) {
+ furi_assert(instance);
+
+ view_free(instance->view);
+ free(instance);
+}
+
+View* avr_isp_writer_view_get_view(AvrIspWriterView* instance) {
+ furi_assert(instance);
+
+ return instance->view;
+}
diff --git a/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h
new file mode 100644
index 000000000..1ff728387
--- /dev/null
+++ b/applications/external/avr_isp_programmer/views/avr_isp_view_writer.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspWriterView AvrIspWriterView;
+
+typedef void (*AvrIspWriterViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+ AvrIspWriterViewStatusIDLE,
+ AvrIspWriterViewStatusWriting,
+ AvrIspWriterViewStatusVerification,
+ AvrIspWriterViewStatusWritingFuse,
+ AvrIspWriterViewStatusWritingFuseOk,
+} AvrIspWriterViewStatus;
+
+void avr_isp_writer_update_progress(AvrIspWriterView* instance);
+
+void avr_isp_writer_set_file_path(
+ AvrIspWriterView* instance,
+ const char* file_path,
+ const char* file_name);
+
+void avr_isp_writer_view_set_callback(
+ AvrIspWriterView* instance,
+ AvrIspWriterViewCallback callback,
+ void* context);
+
+AvrIspWriterView* avr_isp_writer_view_alloc();
+
+void avr_isp_writer_view_free(AvrIspWriterView* instance);
+
+View* avr_isp_writer_view_get_view(AvrIspWriterView* instance);
+
+void avr_isp_writer_view_exit(void* context);
diff --git a/applications/external/cli_bridge/cligui_main.c b/applications/external/cli_bridge/cligui_main.c
index 4e7987c89..6b6ec2797 100644
--- a/applications/external/cli_bridge/cligui_main.c
+++ b/applications/external/cli_bridge/cligui_main.c
@@ -65,6 +65,9 @@ static void input_callback_wrapper(InputEvent* event, void* context) {
int32_t cligui_main(void* p) {
UNUSED(p);
+ loader_unlock(furi_record_open(RECORD_LOADER));
+ furi_record_close(RECORD_LOADER);
+
CliguiApp* cligui = malloc(sizeof(CliguiApp));
cligui->data = malloc(sizeof(CliguiData));
@@ -130,4 +133,4 @@ int32_t cligui_main(void* p) {
free(cligui);
return 0;
-}
\ No newline at end of file
+}
diff --git a/applications/external/dap_link/gui/views/dap_main_view.c b/applications/external/dap_link/gui/views/dap_main_view.c
index c5c8f9dff..f54c5e3d5 100644
--- a/applications/external/dap_link/gui/views/dap_main_view.c
+++ b/applications/external/dap_link/gui/views/dap_main_view.c
@@ -51,10 +51,10 @@ static void dap_main_view_draw_callback(Canvas* canvas, void* _model) {
canvas_set_color(canvas, ColorBlack);
if(model->dap_active) {
canvas_draw_icon(canvas, 14, 16, &I_ArrowUpFilled_12x18);
- canvas_draw_icon(canvas, 28, 16, &I_ArrowDownFilled_12x18);
+ canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpFilled_12x18, IconRotation180);
} else {
canvas_draw_icon(canvas, 14, 16, &I_ArrowUpEmpty_12x18);
- canvas_draw_icon(canvas, 28, 16, &I_ArrowDownEmpty_12x18);
+ canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpEmpty_12x18, IconRotation180);
}
switch(model->mode) {
@@ -76,9 +76,9 @@ static void dap_main_view_draw_callback(Canvas* canvas, void* _model) {
}
if(model->rx_active) {
- canvas_draw_icon(canvas, 101, 16, &I_ArrowDownFilled_12x18);
+ canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpFilled_12x18, IconRotation180);
} else {
- canvas_draw_icon(canvas, 101, 16, &I_ArrowDownEmpty_12x18);
+ canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpEmpty_12x18, IconRotation180);
}
canvas_draw_str_aligned(canvas, 100, 38, AlignCenter, AlignTop, "UART");
diff --git a/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png b/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png
deleted file mode 100644
index 6007f74ab..000000000
Binary files a/applications/external/dap_link/icons/ArrowDownEmpty_12x18.png and /dev/null differ
diff --git a/applications/external/dap_link/icons/ArrowDownFilled_12x18.png b/applications/external/dap_link/icons/ArrowDownFilled_12x18.png
deleted file mode 100644
index 5541e7723..000000000
Binary files a/applications/external/dap_link/icons/ArrowDownFilled_12x18.png and /dev/null differ
diff --git a/applications/external/gpio_reader_b/views/gpio_usb_uart.c b/applications/external/gpio_reader_b/views/gpio_usb_uart.c
index c7406d29b..f71dcccab 100644
--- a/applications/external/gpio_reader_b/views/gpio_usb_uart.c
+++ b/applications/external/gpio_reader_b/views/gpio_usb_uart.c
@@ -80,9 +80,9 @@ static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) {
canvas_draw_icon(canvas, 48, 14, &I_ArrowUpEmpty_14x15);
if(model->rx_active)
- canvas_draw_icon(canvas, 48, 34, &I_ArrowDownFilled_14x15);
+ canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180);
else
- canvas_draw_icon(canvas, 48, 34, &I_ArrowDownEmpty_14x15);
+ canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180);
}
static bool gpio_usb_uart_input_callback(InputEvent* event, void* context) {
diff --git a/applications/external/gps_nmea_uart/gps.c b/applications/external/gps_nmea_uart/gps.c
index b36fd080b..0f4a7a1d5 100644
--- a/applications/external/gps_nmea_uart/gps.c
+++ b/applications/external/gps_nmea_uart/gps.c
@@ -16,45 +16,58 @@ typedef struct {
static void render_callback(Canvas* const canvas, void* context) {
furi_assert(context);
- const GpsUart* gps_uart = context;
+ GpsUart* gps_uart = context;
furi_mutex_acquire(gps_uart->mutex, FuriWaitForever);
- canvas_set_font(canvas, FontPrimary);
- canvas_draw_str_aligned(canvas, 32, 8, AlignCenter, AlignBottom, "Latitude");
- canvas_draw_str_aligned(canvas, 96, 8, AlignCenter, AlignBottom, "Longitude");
- canvas_draw_str_aligned(canvas, 21, 30, AlignCenter, AlignBottom, "Course");
- canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignBottom, "Speed");
- canvas_draw_str_aligned(canvas, 107, 30, AlignCenter, AlignBottom, "Altitude");
- canvas_draw_str_aligned(canvas, 32, 52, AlignCenter, AlignBottom, "Satellites");
- canvas_draw_str_aligned(canvas, 96, 52, AlignCenter, AlignBottom, "Last Fix");
+ if(!gps_uart->changing_baudrate) {
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str_aligned(canvas, 32, 8, AlignCenter, AlignBottom, "Latitude");
+ canvas_draw_str_aligned(canvas, 96, 8, AlignCenter, AlignBottom, "Longitude");
+ canvas_draw_str_aligned(canvas, 21, 30, AlignCenter, AlignBottom, "Course");
+ canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignBottom, "Speed");
+ canvas_draw_str_aligned(canvas, 107, 30, AlignCenter, AlignBottom, "Altitude");
+ canvas_draw_str_aligned(canvas, 32, 52, AlignCenter, AlignBottom, "Satellites");
+ canvas_draw_str_aligned(canvas, 96, 52, AlignCenter, AlignBottom, "Last Fix");
- canvas_set_font(canvas, FontSecondary);
- char buffer[64];
- snprintf(buffer, 64, "%f", (double)gps_uart->status.latitude);
- canvas_draw_str_aligned(canvas, 32, 18, AlignCenter, AlignBottom, buffer);
- snprintf(buffer, 64, "%f", (double)gps_uart->status.longitude);
- canvas_draw_str_aligned(canvas, 96, 18, AlignCenter, AlignBottom, buffer);
- snprintf(buffer, 64, "%.1f", (double)gps_uart->status.course);
- canvas_draw_str_aligned(canvas, 21, 40, AlignCenter, AlignBottom, buffer);
- snprintf(buffer, 64, "%.2f kn", (double)gps_uart->status.speed);
- canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignBottom, buffer);
- snprintf(
- buffer,
- 64,
- "%.1f %c",
- (double)gps_uart->status.altitude,
- tolower(gps_uart->status.altitude_units));
- canvas_draw_str_aligned(canvas, 107, 40, AlignCenter, AlignBottom, buffer);
- snprintf(buffer, 64, "%d", gps_uart->status.satellites_tracked);
- canvas_draw_str_aligned(canvas, 32, 62, AlignCenter, AlignBottom, buffer);
- snprintf(
- buffer,
- 64,
- "%02d:%02d:%02d UTC",
- gps_uart->status.time_hours,
- gps_uart->status.time_minutes,
- gps_uart->status.time_seconds);
- canvas_draw_str_aligned(canvas, 96, 62, AlignCenter, AlignBottom, buffer);
+ canvas_set_font(canvas, FontSecondary);
+ char buffer[64];
+ snprintf(buffer, 64, "%f", (double)gps_uart->status.latitude);
+ canvas_draw_str_aligned(canvas, 32, 18, AlignCenter, AlignBottom, buffer);
+ snprintf(buffer, 64, "%f", (double)gps_uart->status.longitude);
+ canvas_draw_str_aligned(canvas, 96, 18, AlignCenter, AlignBottom, buffer);
+ snprintf(buffer, 64, "%.1f", (double)gps_uart->status.course);
+ canvas_draw_str_aligned(canvas, 21, 40, AlignCenter, AlignBottom, buffer);
+ if(!gps_uart->speed_in_kms) {
+ snprintf(buffer, 64, "%.2f kn", (double)gps_uart->status.speed);
+ } else {
+ snprintf(buffer, 64, "%.2f km", (double)(gps_uart->status.speed * 1.852));
+ }
+ canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignBottom, buffer);
+ snprintf(
+ buffer,
+ 64,
+ "%.1f %c",
+ (double)gps_uart->status.altitude,
+ tolower(gps_uart->status.altitude_units));
+ canvas_draw_str_aligned(canvas, 107, 40, AlignCenter, AlignBottom, buffer);
+ snprintf(buffer, 64, "%d", gps_uart->status.satellites_tracked);
+ canvas_draw_str_aligned(canvas, 32, 62, AlignCenter, AlignBottom, buffer);
+ snprintf(
+ buffer,
+ 64,
+ "%02d:%02d:%02d UTC",
+ gps_uart->status.time_hours,
+ gps_uart->status.time_minutes,
+ gps_uart->status.time_seconds);
+ canvas_draw_str_aligned(canvas, 96, 62, AlignCenter, AlignBottom, buffer);
+ } else {
+ char buffer[64];
+ canvas_set_font(canvas, FontPrimary);
+ canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignBottom, "Baudrate set to:");
+
+ snprintf(buffer, 64, "%ld baud", gps_uart->baudrate);
+ canvas_draw_str_aligned(canvas, 64, 47, AlignCenter, AlignBottom, buffer);
+ }
furi_mutex_release(gps_uart->mutex);
}
@@ -98,13 +111,59 @@ int32_t gps_app(void* p) {
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
- if(event.input.type == InputTypePress) {
+ if(event.input.type == InputTypeShort) {
switch(event.input.key) {
case InputKeyUp:
case InputKeyDown:
case InputKeyRight:
case InputKeyLeft:
+ case InputKeyBack:
+ break;
case InputKeyOk:
+ if(!gps_uart->backlight_on) {
+ notification_message_block(
+ gps_uart->notifications, &sequence_display_backlight_enforce_on);
+ gps_uart->backlight_on = true;
+ } else {
+ notification_message_block(
+ gps_uart->notifications, &sequence_display_backlight_enforce_auto);
+ notification_message(
+ gps_uart->notifications, &sequence_display_backlight_off);
+ gps_uart->backlight_on = false;
+ }
+ break;
+ default:
+ break;
+ }
+ } else if(event.input.type == InputTypeLong) {
+ switch(event.input.key) {
+ case InputKeyUp:
+ gps_uart_deinit_thread(gps_uart);
+ switch(gps_uart->baudrate) {
+ case GPS_BAUDRATE_9k:
+ gps_uart->baudrate = GPS_BAUDRATE_57k;
+ break;
+ case GPS_BAUDRATE_57k:
+ gps_uart->baudrate = GPS_BAUDRATE_115k;
+ break;
+ case GPS_BAUDRATE_115k:
+ gps_uart->baudrate = GPS_BAUDRATE_9k;
+ break;
+
+ default:
+ break;
+ }
+ gps_uart_init_thread(gps_uart);
+ gps_uart->changing_baudrate = true;
+ view_port_update(view_port);
+ furi_mutex_release(gps_uart->mutex);
+ break;
+ case InputKeyRight:
+ if(gps_uart->speed_in_kms) {
+ gps_uart->speed_in_kms = false;
+ } else {
+ gps_uart->speed_in_kms = true;
+ }
break;
case InputKeyBack:
processing = false;
@@ -115,11 +174,16 @@ int32_t gps_app(void* p) {
}
}
}
-
- view_port_update(view_port);
- furi_mutex_release(gps_uart->mutex);
+ if(!gps_uart->changing_baudrate) {
+ view_port_update(view_port);
+ furi_mutex_release(gps_uart->mutex);
+ } else {
+ furi_delay_ms(1000);
+ gps_uart->changing_baudrate = false;
+ }
}
+ notification_message_block(gps_uart->notifications, &sequence_display_backlight_enforce_auto);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
diff --git a/applications/external/gps_nmea_uart/gps_uart.c b/applications/external/gps_nmea_uart/gps_uart.c
index 52ba660bc..39538b74b 100644
--- a/applications/external/gps_nmea_uart/gps_uart.c
+++ b/applications/external/gps_nmea_uart/gps_uart.c
@@ -22,7 +22,7 @@ static void gps_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
static void gps_uart_serial_init(GpsUart* gps_uart) {
furi_hal_console_disable();
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, gps_uart_on_irq_cb, gps_uart);
- furi_hal_uart_set_br(FuriHalUartIdUSART1, GPS_BAUDRATE);
+ furi_hal_uart_set_br(FuriHalUartIdUSART1, gps_uart->baudrate);
}
static void gps_uart_serial_deinit(GpsUart* gps_uart) {
@@ -133,9 +133,8 @@ static int32_t gps_uart_worker(void* context) {
return 0;
}
-GpsUart* gps_uart_enable() {
- GpsUart* gps_uart = malloc(sizeof(GpsUart));
-
+void gps_uart_init_thread(GpsUart* gps_uart) {
+ furi_assert(gps_uart);
gps_uart->status.valid = false;
gps_uart->status.latitude = 0.0;
gps_uart->status.longitude = 0.0;
@@ -149,8 +148,6 @@ GpsUart* gps_uart_enable() {
gps_uart->status.time_minutes = 0;
gps_uart->status.time_seconds = 0;
- gps_uart->notifications = furi_record_open(RECORD_NOTIFICATION);
-
gps_uart->thread = furi_thread_alloc();
furi_thread_set_name(gps_uart->thread, "GpsUartWorker");
furi_thread_set_stack_size(gps_uart->thread, 1024);
@@ -158,15 +155,30 @@ GpsUart* gps_uart_enable() {
furi_thread_set_callback(gps_uart->thread, gps_uart_worker);
furi_thread_start(gps_uart->thread);
+}
+
+void gps_uart_deinit_thread(GpsUart* gps_uart) {
+ furi_assert(gps_uart);
+ furi_thread_flags_set(furi_thread_get_id(gps_uart->thread), WorkerEvtStop);
+ furi_thread_join(gps_uart->thread);
+ furi_thread_free(gps_uart->thread);
+}
+
+GpsUart* gps_uart_enable() {
+ GpsUart* gps_uart = malloc(sizeof(GpsUart));
+
+ gps_uart->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+ gps_uart->baudrate = GPS_BAUDRATE_57k;
+
+ gps_uart_init_thread(gps_uart);
+
return gps_uart;
}
void gps_uart_disable(GpsUart* gps_uart) {
furi_assert(gps_uart);
- furi_thread_flags_set(furi_thread_get_id(gps_uart->thread), WorkerEvtStop);
- furi_thread_join(gps_uart->thread);
- furi_thread_free(gps_uart->thread);
-
+ gps_uart_deinit_thread(gps_uart);
furi_record_close(RECORD_NOTIFICATION);
free(gps_uart);
diff --git a/applications/external/gps_nmea_uart/gps_uart.h b/applications/external/gps_nmea_uart/gps_uart.h
index fd33eb245..5a42b9c58 100644
--- a/applications/external/gps_nmea_uart/gps_uart.h
+++ b/applications/external/gps_nmea_uart/gps_uart.h
@@ -3,7 +3,9 @@
#include
#include
-#define GPS_BAUDRATE 9600
+#define GPS_BAUDRATE_9k 9600
+#define GPS_BAUDRATE_57k 57600
+#define GPS_BAUDRATE_115k 115200
#define RX_BUF_SIZE 1024
typedef struct {
@@ -28,10 +30,17 @@ typedef struct {
uint8_t rx_buf[RX_BUF_SIZE];
NotificationApp* notifications;
+ uint32_t baudrate;
+ bool changing_baudrate;
+ bool backlight_on;
+ bool speed_in_kms;
GpsStatus status;
} GpsUart;
+void gps_uart_init_thread(GpsUart* gps_uart);
+void gps_uart_deinit_thread(GpsUart* gps_uart);
+
GpsUart* gps_uart_enable();
void gps_uart_disable(GpsUart* gps_uart);
diff --git a/applications/external/ir_scope/application.fam b/applications/external/ir_scope/application.fam
new file mode 100644
index 000000000..f99e14515
--- /dev/null
+++ b/applications/external/ir_scope/application.fam
@@ -0,0 +1,11 @@
+App(
+ appid="ir_scope",
+ name="IR Scope",
+ apptype=FlipperAppType.EXTERNAL,
+ entry_point="ir_scope_app",
+ cdefines=["APP_IR_SCOPE"],
+ requires=["gui"],
+ stack_size=2 * 1024,
+ fap_icon="ir_scope.png",
+ fap_category="Tools",
+)
diff --git a/applications/external/ir_scope/ir_scope.c b/applications/external/ir_scope/ir_scope.c
new file mode 100644
index 000000000..408ba3cc7
--- /dev/null
+++ b/applications/external/ir_scope/ir_scope.c
@@ -0,0 +1,183 @@
+// Author: github.com/kallanreed
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define TAG "IR Scope"
+#define COLS 128
+#define ROWS 8
+
+typedef struct {
+ bool autoscale;
+ uint16_t us_per_sample;
+ size_t timings_cnt;
+ uint32_t* timings;
+ uint32_t timings_sum;
+ FuriMutex* mutex;
+} IRScopeState;
+
+static void state_set_autoscale(IRScopeState* state) {
+ if(state->autoscale) state->us_per_sample = state->timings_sum / (ROWS * COLS);
+}
+
+static void canvas_draw_str_outline(Canvas* canvas, int x, int y, const char* str) {
+ canvas_set_color(canvas, ColorWhite);
+ for(int y1 = -1; y1 <= 1; ++y1)
+ for(int x1 = -1; x1 <= 1; ++x1) canvas_draw_str(canvas, x + x1, y + y1, str);
+
+ canvas_set_color(canvas, ColorBlack);
+ canvas_draw_str(canvas, x, y, str);
+}
+
+static void render_callback(Canvas* canvas, void* ctx) {
+ const IRScopeState* state = (IRScopeState*)ctx;
+
+ furi_mutex_acquire(state->mutex, FuriWaitForever);
+
+ canvas_clear(canvas);
+ canvas_draw_frame(canvas, 0, 0, 128, 64);
+
+ // Draw the signal chart.
+ bool on = false;
+ bool done = false;
+ size_t ix = 0;
+ int timing_cols = -1; // Count of columns used to draw the current timing
+ for(size_t row = 0; row < ROWS && !done; ++row) {
+ for(size_t col = 0; col < COLS && !done; ++col) {
+ done = ix >= state->timings_cnt;
+
+ if(!done && timing_cols < 0) {
+ timing_cols = state->timings[ix] / state->us_per_sample;
+ on = !on;
+ }
+
+ if(timing_cols == 0) ++ix;
+
+ int y = row * 8 + 7;
+ canvas_draw_line(canvas, col, y, col, y - (on ? 5 : 0));
+ --timing_cols;
+ }
+ }
+
+ canvas_set_font(canvas, FontSecondary);
+ if(state->autoscale)
+ canvas_draw_str_outline(canvas, 100, 64, "Auto");
+ else {
+ char buf[20];
+ snprintf(buf, sizeof(buf), "%uus", state->us_per_sample);
+ canvas_draw_str_outline(canvas, 100, 64, buf);
+ }
+
+ furi_mutex_release(state->mutex);
+}
+
+static void input_callback(InputEvent* input_event, void* ctx) {
+ FuriMessageQueue* event_queue = ctx;
+ furi_message_queue_put(event_queue, input_event, FuriWaitForever);
+}
+
+static void ir_received_callback(void* ctx, InfraredWorkerSignal* signal) {
+ furi_check(signal);
+ IRScopeState* state = (IRScopeState*)ctx;
+
+ furi_mutex_acquire(state->mutex, FuriWaitForever);
+
+ const uint32_t* timings;
+ infrared_worker_get_raw_signal(signal, &timings, &state->timings_cnt);
+
+ if(state->timings) {
+ free(state->timings);
+ state->timings_sum = 0;
+ }
+
+ state->timings = malloc(state->timings_cnt * sizeof(uint32_t));
+
+ // Copy and sum.
+ for(size_t i = 0; i < state->timings_cnt; ++i) {
+ state->timings[i] = timings[i];
+ state->timings_sum += timings[i];
+ }
+
+ state_set_autoscale(state);
+
+ furi_mutex_release(state->mutex);
+}
+
+int32_t ir_scope_app(void* p) {
+ UNUSED(p);
+
+ FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
+ furi_check(event_queue);
+
+ if(furi_hal_infrared_is_busy()) {
+ FURI_LOG_E(TAG, "Infrared is busy.");
+ return -1;
+ }
+
+ IRScopeState state = {
+ .autoscale = false, .us_per_sample = 200, .timings = NULL, .timings_cnt = 0, .mutex = NULL};
+ state.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+ if(!state.mutex) {
+ FURI_LOG_E(TAG, "Cannot create mutex.");
+ return -1;
+ }
+
+ ViewPort* view_port = view_port_alloc();
+ view_port_draw_callback_set(view_port, render_callback, &state);
+ view_port_input_callback_set(view_port, input_callback, event_queue);
+
+ Gui* gui = furi_record_open("gui");
+ gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+ InfraredWorker* worker = infrared_worker_alloc();
+ infrared_worker_rx_enable_signal_decoding(worker, false);
+ infrared_worker_rx_enable_blink_on_receiving(worker, true);
+ infrared_worker_rx_set_received_signal_callback(worker, ir_received_callback, &state);
+ infrared_worker_rx_start(worker);
+
+ InputEvent event;
+ bool processing = true;
+ while(processing &&
+ furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) {
+ if(event.type == InputTypeRelease) {
+ furi_mutex_acquire(state.mutex, FuriWaitForever);
+
+ if(event.key == InputKeyBack) {
+ processing = false;
+ } else if(event.key == InputKeyUp) {
+ state.us_per_sample = MIN(1000, state.us_per_sample + 25);
+ state.autoscale = false;
+ } else if(event.key == InputKeyDown) {
+ state.us_per_sample = MAX(25, state.us_per_sample - 25);
+ state.autoscale = false;
+ } else if(event.key == InputKeyOk) {
+ state.autoscale = !state.autoscale;
+ if(state.autoscale)
+ state_set_autoscale(&state);
+ else
+ state.us_per_sample = 200;
+ }
+
+ view_port_update(view_port);
+ furi_mutex_release(state.mutex);
+ }
+ }
+
+ // Clean up.
+ infrared_worker_rx_stop(worker);
+ infrared_worker_free(worker);
+
+ if(state.timings) free(state.timings);
+
+ 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);
+ furi_mutex_free(state.mutex);
+
+ return 0;
+}
diff --git a/applications/external/ir_scope/ir_scope.png b/applications/external/ir_scope/ir_scope.png
new file mode 100644
index 000000000..c0d7eaba0
Binary files /dev/null and b/applications/external/ir_scope/ir_scope.png differ
diff --git a/applications/external/nightstand_clock/clock_app.c b/applications/external/nightstand_clock/clock_app.c
index 33dbb49a4..eddc2b080 100644
--- a/applications/external/nightstand_clock/clock_app.c
+++ b/applications/external/nightstand_clock/clock_app.c
@@ -277,6 +277,13 @@ int32_t clock_app(void* p) {
clock_state_init(plugin_state);
+ notif = furi_record_open(RECORD_NOTIFICATION);
+ float tmpBrightness = notif->settings.display_brightness;
+ brightness = tmpBrightness * 100; // Keep current brightness by default
+
+ notification_message(notif, &sequence_display_backlight_enforce_on);
+ notification_message(notif, &led_off);
+
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, clock_render_callback, plugin_state);
@@ -301,13 +308,6 @@ int32_t clock_app(void* p) {
furi_timer_start(timer, furi_kernel_get_tick_frequency());
//FURI_LOG_D(TAG, "Timer started");
- notif = furi_record_open(RECORD_NOTIFICATION);
- float tmpBrightness = notif->settings.display_brightness;
- brightness = tmpBrightness * 100; // Keep current brightness by default
-
- notification_message(notif, &sequence_display_backlight_enforce_on);
- notification_message(notif, &led_off);
-
// Main loop
PluginEvent event;
for(bool processing = true; processing;) {
@@ -354,6 +354,7 @@ int32_t clock_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(plugin_state->event_queue);
furi_mutex_free(plugin_state->mutex);
diff --git a/applications/external/picopass/scenes/picopass_scene_device_info.c b/applications/external/picopass/scenes/picopass_scene_device_info.c
index 046e9c8e4..41caeabf5 100644
--- a/applications/external/picopass/scenes/picopass_scene_device_info.c
+++ b/applications/external/picopass/scenes/picopass_scene_device_info.c
@@ -14,43 +14,69 @@ void picopass_scene_device_info_widget_callback(
void picopass_scene_device_info_on_enter(void* context) {
Picopass* picopass = context;
- FuriString* credential_str;
- FuriString* wiegand_str;
- credential_str = furi_string_alloc();
- wiegand_str = furi_string_alloc();
+ FuriString* csn_str = furi_string_alloc_set("CSN:");
+ FuriString* credential_str = furi_string_alloc();
+ FuriString* wiegand_str = furi_string_alloc();
+ FuriString* sio_str = furi_string_alloc();
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
// Setup view
+ PicopassBlock* AA1 = picopass->dev->dev_data.AA1;
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
Widget* widget = picopass->widget;
- size_t bytesLength = 1 + pacs->record.bitLength / 8;
- furi_string_set(credential_str, "");
- for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) {
- furi_string_cat_printf(credential_str, " %02X", pacs->credential[i]);
+ uint8_t csn[PICOPASS_BLOCK_LEN] = {0};
+ memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN);
+ for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
+ furi_string_cat_printf(csn_str, "%02X ", csn[i]);
}
- if(pacs->record.valid) {
- furi_string_cat_printf(
- wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber);
+ if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) {
+ // Neither of these are valid. Indicates the block was all 0x00 or all 0xff
+ furi_string_cat_printf(wiegand_str, "Invalid PACS");
} else {
- furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength);
+ size_t bytesLength = pacs->record.bitLength / 8;
+ if(pacs->record.bitLength % 8 > 0) {
+ // Add extra byte if there are bits remaining
+ bytesLength++;
+ }
+ furi_string_set(credential_str, "");
+ for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) {
+ furi_string_cat_printf(credential_str, " %02X", pacs->credential[i]);
+ }
+
+ if(pacs->record.valid) {
+ furi_string_cat_printf(
+ wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber);
+ } else {
+ furi_string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength);
+ }
+
+ if(pacs->sio) {
+ furi_string_cat_printf(sio_str, "+SIO");
+ }
}
widget_add_string_element(
- widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str));
+ widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str));
+ widget_add_string_element(
+ widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(wiegand_str));
widget_add_string_element(
widget,
64,
- 32,
+ 36,
AlignCenter,
AlignCenter,
FontSecondary,
furi_string_get_cstr(credential_str));
+ widget_add_string_element(
+ widget, 64, 46, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str));
+ furi_string_free(csn_str);
furi_string_free(credential_str);
furi_string_free(wiegand_str);
+ furi_string_free(sio_str);
widget_add_button_element(
picopass->widget,
diff --git a/applications/external/pocsag_pager/images/Fishing_123x52.png b/applications/external/pocsag_pager/images/Fishing_123x52.png
new file mode 100644
index 000000000..1e365de8f
Binary files /dev/null and b/applications/external/pocsag_pager/images/Fishing_123x52.png differ
diff --git a/applications/external/pocsag_pager/views/pocsag_pager_receiver.c b/applications/external/pocsag_pager/views/pocsag_pager_receiver.c
index 62a2b2968..532f41984 100644
--- a/applications/external/pocsag_pager/views/pocsag_pager_receiver.c
+++ b/applications/external/pocsag_pager/views/pocsag_pager_receiver.c
@@ -207,6 +207,8 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
FuriString* str_buff;
str_buff = furi_string_alloc();
+ bool ext_module = furi_hal_subghz_get_radio_type();
+
PCSGReceiverMenuItem* item_menu;
for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) {
@@ -232,11 +234,11 @@ void pcsg_view_receiver_draw(Canvas* canvas, PCSGReceiverModel* model) {
canvas_set_color(canvas, ColorBlack);
if(model->history_item == 0) {
- canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
+ canvas_draw_icon(canvas, 0, 0, ext_module ? &I_Fishing_123x52 : &I_Scanning_123x52);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Scanning...");
- //canvas_draw_line(canvas, 46, 51, 125, 51);
canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str(canvas, 44, 10, ext_module ? "Ext" : "Int");
}
// Draw RSSI
diff --git a/applications/external/rc2014_coleco/coleco.c b/applications/external/rc2014_coleco/coleco.c
index d5b1b0d46..311b0ceac 100644
--- a/applications/external/rc2014_coleco/coleco.c
+++ b/applications/external/rc2014_coleco/coleco.c
@@ -24,7 +24,7 @@ const GpioPin* const pin_right = &gpio_ext_pb2;
const GpioPin* const pin_left = &gpio_ext_pc3;
const GpioPin* const pin_code0 = &gpio_ext_pa7;
const GpioPin* const pin_code1 = &gpio_ext_pa4;
-const GpioPin* const pin_code2 = &ibutton_gpio;
+const GpioPin* const pin_code2 = &gpio_ibutton;
const GpioPin* const pin_code3 = &gpio_ext_pc1;
const GpioPin* const pin_fire = &gpio_ext_pb3;
const GpioPin* const pin_alt = &gpio_usart_tx;
diff --git a/applications/external/subghz_remote/application.fam b/applications/external/subghz_remote/application.fam
index e89f55b82..e70e4050a 100644
--- a/applications/external/subghz_remote/application.fam
+++ b/applications/external/subghz_remote/application.fam
@@ -10,7 +10,7 @@ App(
],
stack_size=4 * 1024,
order=11,
+ fap_libs=["assets"],
fap_icon="subghz_remote_10px.png",
fap_category="Sub-GHz",
- fap_icon_assets="images",
)
diff --git a/applications/external/subghz_remote/images/ButtonDown_7x4.png b/applications/external/subghz_remote/images/ButtonDown_7x4.png
deleted file mode 100644
index 2954bb6a6..000000000
Binary files a/applications/external/subghz_remote/images/ButtonDown_7x4.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/ButtonLeft_4x7.png b/applications/external/subghz_remote/images/ButtonLeft_4x7.png
deleted file mode 100644
index 0b4655d43..000000000
Binary files a/applications/external/subghz_remote/images/ButtonLeft_4x7.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/ButtonRight_4x7.png b/applications/external/subghz_remote/images/ButtonRight_4x7.png
deleted file mode 100644
index 8e1c74c1c..000000000
Binary files a/applications/external/subghz_remote/images/ButtonRight_4x7.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/ButtonUp_7x4.png b/applications/external/subghz_remote/images/ButtonUp_7x4.png
deleted file mode 100644
index 1be79328b..000000000
Binary files a/applications/external/subghz_remote/images/ButtonUp_7x4.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/Ok_btn_9x9.png b/applications/external/subghz_remote/images/Ok_btn_9x9.png
deleted file mode 100644
index 9a1539da2..000000000
Binary files a/applications/external/subghz_remote/images/Ok_btn_9x9.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/Pin_arrow_down_7x9.png b/applications/external/subghz_remote/images/Pin_arrow_down_7x9.png
deleted file mode 100644
index 9687397af..000000000
Binary files a/applications/external/subghz_remote/images/Pin_arrow_down_7x9.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/Pin_arrow_left_9x7.png b/applications/external/subghz_remote/images/Pin_arrow_left_9x7.png
deleted file mode 100644
index fb4ded78f..000000000
Binary files a/applications/external/subghz_remote/images/Pin_arrow_left_9x7.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/Pin_arrow_up_7x9.png b/applications/external/subghz_remote/images/Pin_arrow_up_7x9.png
deleted file mode 100644
index a91a6fd5e..000000000
Binary files a/applications/external/subghz_remote/images/Pin_arrow_up_7x9.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/Pin_cell_13x13.png b/applications/external/subghz_remote/images/Pin_cell_13x13.png
deleted file mode 100644
index 1b1ff0c2f..000000000
Binary files a/applications/external/subghz_remote/images/Pin_cell_13x13.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/Pin_star_7x7.png b/applications/external/subghz_remote/images/Pin_star_7x7.png
deleted file mode 100644
index 42fdea86e..000000000
Binary files a/applications/external/subghz_remote/images/Pin_star_7x7.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/back_10px.png b/applications/external/subghz_remote/images/back_10px.png
deleted file mode 100644
index f9c615a99..000000000
Binary files a/applications/external/subghz_remote/images/back_10px.png and /dev/null differ
diff --git a/applications/external/subghz_remote/images/sub1_10px.png b/applications/external/subghz_remote/images/sub1_10px.png
deleted file mode 100644
index 5a25fdf4e..000000000
Binary files a/applications/external/subghz_remote/images/sub1_10px.png and /dev/null differ
diff --git a/applications/external/subghz_remote/subghz_remote_app.c b/applications/external/subghz_remote/subghz_remote_app.c
index df4b8465a..940968592 100644
--- a/applications/external/subghz_remote/subghz_remote_app.c
+++ b/applications/external/subghz_remote/subghz_remote_app.c
@@ -8,7 +8,7 @@
#include
#include
-#include
+#include
#include
#include
@@ -19,12 +19,10 @@
#include
#include
#include
-#include
-#include
-#include
-#include
#include
+#include
+
#define SUBREMOTEMAP_FOLDER "/ext/subghz/remote"
#define SUBREMOTEMAP_EXTENSION ".txt"
@@ -388,17 +386,35 @@ bool subghz_remote_key_load(
FURI_LOG_E(TAG, "Could not read Protocol.");
break;
}
+
if(!furi_string_cmp_str(preset->protocol, "RAW")) {
subghz_protocol_raw_gen_fff_data(fff_data, path);
+ // repeat
+ if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) {
+ FURI_LOG_E(TAG, "Unable to insert or update Repeat");
+ break;
+ }
+ if(!flipper_format_rewind(fff_data)) {
+ FURI_LOG_E(TAG, "Rewind error");
+ return false;
+ }
} else {
stream_copy_full(
flipper_format_get_raw_stream(fff_file), flipper_format_get_raw_stream(fff_data));
+ // repeat
+ if(!flipper_format_insert_or_update_uint32(fff_data, "Repeat", &preset->repeat, 1)) {
+ FURI_LOG_E(TAG, "Unable to insert or update Repeat");
+ break;
+ }
+ if(!flipper_format_rewind(fff_data)) {
+ FURI_LOG_E(TAG, "Rewind error");
+ return false;
+ }
}
- // repeat
- if(!flipper_format_insert_or_update_uint32(fff_file, "Repeat", &preset->repeat, 1)) {
- FURI_LOG_E(TAG, "Unable to insert or update Repeat");
- break;
+ if(!flipper_format_rewind(fff_file)) {
+ FURI_LOG_E(TAG, "Rewind error");
+ return false;
}
preset->decoder = subghz_receiver_search_decoder_base_by_name(
@@ -437,9 +453,6 @@ bool subghz_remote_save_protocol_to_file(FlipperFormat* fff_file, const char* de
path_extract_dirname(dev_file_name, file_dir);
do {
- flipper_format_delete_key(fff_file, "Repeat");
- //flipper_format_delete_key(fff_file, "Manufacture");
-
if(!storage_simply_mkdir(storage, furi_string_get_cstr(file_dir))) {
FURI_LOG_E(TAG, "(save) Cannot mkdir");
break;
@@ -477,23 +490,24 @@ void subghz_remote_tx_stop(SubGHzRemote* app) {
//FURI_LOG_I(TAG, "TX Done!");
subghz_transmitter_stop(app->tx_transmitter);
- FURI_LOG_D(TAG, "Checking if protocol is dynamic");
+ //FURI_LOG_D(TAG, "Checking if protocol is dynamic");
const SubGhzProtocolRegistry* protocol_registry_items =
subghz_environment_get_protocol_registry(app->environment);
const SubGhzProtocol* proto = subghz_protocol_registry_get_by_name(
protocol_registry_items, furi_string_get_cstr(app->txpreset->protocol));
- FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type);
+ //FURI_LOG_D(TAG, "Protocol-TYPE %d", proto->type);
+
if(proto && proto->type == SubGhzProtocolTypeDynamic) {
- FURI_LOG_D(TAG, "Protocol is dynamic. Saving key");
+ //FURI_LOG_D(TAG, "Protocol is dynamic. Saving key");
+ // Remove repeat if it was present
+ flipper_format_delete_key(app->tx_fff_data, "Repeat");
+
subghz_remote_save_protocol_to_file(app->tx_fff_data, app->tx_file_path);
keeloq_reset_mfname();
keeloq_reset_kl_type();
keeloq_reset_original_btn();
- alutech_reset_original_btn();
- nice_flors_reset_original_btn();
- somfy_telis_reset_original_btn();
- secplus2_reset_original_btn();
+ subghz_custom_btns_reset();
star_line_reset_mfname();
star_line_reset_kl_type();
}
@@ -687,15 +701,15 @@ static void render_callback(Canvas* canvas, void* ctx) {
break;
case 2:
canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13);
- canvas_draw_icon(canvas, 116, 17, &I_Pin_arrow_down_7x9);
+ canvas_draw_icon_ex(canvas, 116, 17, &I_Pin_arrow_up_7x9, IconRotation180);
break;
case 3:
canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13);
- canvas_draw_icon(canvas, 115, 18, &I_Pin_arrow_right_9x7);
+ canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation90);
break;
case 4:
canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13);
- canvas_draw_icon(canvas, 115, 18, &I_Pin_arrow_left_9x7);
+ canvas_draw_icon_ex(canvas, 115, 18, &I_Pin_arrow_up_7x9, IconRotation270);
break;
case 5:
canvas_draw_icon(canvas, 113, 15, &I_Pin_cell_13x13);
@@ -820,6 +834,13 @@ int32_t subghz_remote_app(void* p) {
app->file_result = 3;
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+
+ if(!storage_simply_mkdir(storage, SUBREMOTEMAP_FOLDER)) {
+ FURI_LOG_E(TAG, "Could not create folder %s", SUBREMOTEMAP_FOLDER);
+ }
+ furi_record_close(RECORD_STORAGE);
+
furi_string_set(app->file_path, SUBREMOTEMAP_FOLDER);
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
diff --git a/applications/external/totp/application.fam b/applications/external/totp/application.fam
index 096716e74..8e56bbe76 100644
--- a/applications/external/totp/application.fam
+++ b/applications/external/totp/application.fam
@@ -3,6 +3,7 @@ App(
name="Authenticator",
apptype=FlipperAppType.EXTERNAL,
entry_point="totp_app",
+ cdefines=["APP_TOTP"],
requires=["gui", "cli", "dialogs", "storage", "input", "notification", "bt"],
stack_size=2 * 1024,
order=20,
@@ -17,8 +18,9 @@ App(
name="base32",
),
Lib(
- name="list",
+ name="base64",
),
+ Lib(name="linked_list"),
Lib(
name="timezone_utils",
),
diff --git a/applications/external/totp/cli/cli.c b/applications/external/totp/cli/cli.c
index ce2530804..30876c7e4 100644
--- a/applications/external/totp/cli/cli.c
+++ b/applications/external/totp/cli/cli.c
@@ -5,6 +5,7 @@
#include "cli_helpers.h"
#include "commands/list/list.h"
#include "commands/add/add.h"
+#include "commands/update/update.h"
#include "commands/delete/delete.h"
#include "commands/timezone/timezone.h"
#include "commands/help/help.h"
@@ -13,6 +14,7 @@
#include "commands/notification/notification.h"
#include "commands/reset/reset.h"
#include "commands/automation/automation.h"
+#include "commands/details/details.h"
static void totp_cli_print_unknown_command(const FuriString* unknown_command) {
TOTP_CLI_PRINTF_ERROR(
@@ -62,6 +64,12 @@ static void totp_cli_handler(Cli* cli, FuriString* args, void* context) {
totp_cli_command_automation_handle(plugin_state, args, cli);
} else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_RESET) == 0) {
totp_cli_command_reset_handle(cli, cli_context->event_queue);
+ } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_UPDATE) == 0) {
+ totp_cli_command_update_handle(plugin_state, args, cli);
+ } else if(
+ furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_DETAILS) == 0 ||
+ furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_DETAILS_ALT) == 0) {
+ totp_cli_command_details_handle(plugin_state, args, cli);
} else {
totp_cli_print_unknown_command(cmd);
}
diff --git a/applications/external/totp/cli/cli_helpers.c b/applications/external/totp/cli/cli_helpers.c
index 984637164..36b98cf65 100644
--- a/applications/external/totp/cli/cli_helpers.c
+++ b/applications/external/totp/cli/cli_helpers.c
@@ -1,19 +1,22 @@
#include "cli_helpers.h"
#include
+#include
#include "../types/plugin_event.h"
bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) {
if(plugin_state->current_scene == TotpSceneAuthentication) {
TOTP_CLI_PRINTF("Pleases enter PIN on your flipper device\r\n");
- while(plugin_state->current_scene == TotpSceneAuthentication &&
+ while((plugin_state->current_scene == TotpSceneAuthentication ||
+ plugin_state->current_scene == TotpSceneNone) &&
!cli_cmd_interrupt_received(cli)) {
furi_delay_ms(100);
}
TOTP_CLI_DELETE_LAST_LINE();
- if(plugin_state->current_scene == TotpSceneAuthentication) { //-V547
+ if(plugin_state->current_scene == TotpSceneAuthentication || //-V560
+ plugin_state->current_scene == TotpSceneNone) { //-V560
return false;
}
}
@@ -38,7 +41,9 @@ bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input) {
} else if(c == CliSymbolAsciiETX) {
cli_nl();
return false;
- } else if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+ } else if(
+ (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+ c == '/' || c == '=' || c == '+') {
if(mask_user_input) {
putc('*', stdout);
} else {
@@ -59,4 +64,22 @@ bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input) {
}
return true;
-}
\ No newline at end of file
+}
+
+bool args_read_uint8_and_trim(FuriString* args, uint8_t* value) {
+ int int_value;
+ if(!args_read_int_and_trim(args, &int_value) || int_value < 0 || int_value > UINT8_MAX) {
+ return false;
+ }
+
+ *value = (uint8_t)int_value;
+ return true;
+}
+
+void furi_string_secure_free(FuriString* str) {
+ for(long i = furi_string_size(str) - 1; i >= 0; i--) {
+ furi_string_set_char(str, i, '\0');
+ }
+
+ furi_string_free(str);
+}
diff --git a/applications/external/totp/cli/cli_helpers.h b/applications/external/totp/cli/cli_helpers.h
index a35a2e599..dd5a282d4 100644
--- a/applications/external/totp/cli/cli_helpers.h
+++ b/applications/external/totp/cli/cli_helpers.h
@@ -14,24 +14,13 @@
#define DOCOPT_OPTIONS "[options]"
#define DOCOPT_DEFAULT(val) "[default: " val "]"
-#define TOTP_CLI_PRINTF(format, ...) \
- do { \
- _Pragma(STRINGIFY(GCC diagnostic push)) \
- _Pragma(STRINGIFY(GCC diagnostic ignored "-Wdouble-promotion")) \
- printf(format, ##__VA_ARGS__); \
- _Pragma(STRINGIFY(GCC diagnostic pop)) \
- } while(false)
+#define TOTP_CLI_PRINTF(format, ...) printf(format, ##__VA_ARGS__)
-#define TOTP_CLI_PRINTF_COLORFUL(color, format, ...) \
- do { \
- _Pragma(STRINGIFY(GCC diagnostic push)) \
- _Pragma(STRINGIFY(GCC diagnostic ignored "-Wdouble-promotion")) \
- printf("\e[%s", color); \
- printf(format, ##__VA_ARGS__); \
- printf("\e[0m"); \
- fflush(stdout); \
- _Pragma(STRINGIFY(GCC diagnostic pop)) \
- } while(false)
+#define TOTP_CLI_PRINTF_COLORFUL(color, format, ...) \
+ printf("\e[%s", color); \
+ printf(format, ##__VA_ARGS__); \
+ printf("\e[0m"); \
+ fflush(stdout)
#define TOTP_CLI_COLOR_ERROR "91m"
#define TOTP_CLI_COLOR_WARNING "93m"
@@ -89,3 +78,17 @@ void totp_cli_force_close_app(FuriMessageQueue* event_queue);
* @return \c true if line successfully read and confirmed; \c false otherwise
*/
bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input);
+
+/**
+ * @brief Extracts \c uint8_t value and trims arguments string
+ * @param args arguments string
+ * @param[out] value parsed value
+ * @return \c true if value successfully read and parsed as \c uint8_t ; \c false otherwise
+ */
+bool args_read_uint8_and_trim(FuriString* args, uint8_t* value);
+
+/**
+ * @brief Free \c FuriString instance in a secure manner by clearing it first
+ * @param str instance to free
+ */
+void furi_string_secure_free(FuriString* str);
diff --git a/applications/external/totp/cli/commands/add/add.c b/applications/external/totp/cli/commands/add/add.c
index 91f9256b2..3549e785b 100644
--- a/applications/external/totp/cli/commands/add/add.c
+++ b/applications/external/totp/cli/commands/add/add.c
@@ -1,50 +1,13 @@
#include "add.h"
#include
#include
-#include "../../../lib/list/list.h"
+#include
#include "../../../types/token_info.h"
#include "../../../services/config/config.h"
#include "../../../services/convert/convert.h"
#include "../../cli_helpers.h"
#include "../../../ui/scene_director.h"
-
-#define TOTP_CLI_COMMAND_ADD_ARG_NAME "name"
-#define TOTP_CLI_COMMAND_ADD_ARG_ALGO "algo"
-#define TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX "-a"
-#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS "digits"
-#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX "-d"
-#define TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX "-u"
-#define TOTP_CLI_COMMAND_ADD_ARG_DURATION "duration"
-#define TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX "-l"
-
-static bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) {
- if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) {
- token_info->algo = SHA1;
- return true;
- }
-
- if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME) == 0) {
- token_info->algo = SHA256;
- return true;
- }
-
- if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME) == 0) {
- token_info->algo = SHA512;
- return true;
- }
-
- return false;
-}
-
-static bool args_read_uint8_and_trim(FuriString* args, uint8_t* value) {
- int int_value;
- if(!args_read_int_and_trim(args, &int_value) || int_value < 0 || int_value > UINT8_MAX) {
- return false;
- }
-
- *value = (uint8_t)int_value;
- return true;
-}
+#include "../../common_command_arguments.h"
void totp_cli_command_add_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT
@@ -54,42 +17,61 @@ void totp_cli_command_add_docopt_commands() {
void totp_cli_command_add_docopt_usage() {
TOTP_CLI_PRINTF(
" " TOTP_CLI_COMMAND_NAME
- " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_NAME) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_ALGO))) " " DOCOPT_OPTIONAL(
+ " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING))) " " DOCOPT_OPTIONAL(
DOCOPT_OPTION(
- TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX,
+ TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX,
DOCOPT_ARGUMENT(
- TOTP_CLI_COMMAND_ADD_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX)) "\r\n");
+ TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n");
}
void totp_cli_command_add_docopt_arguments() {
- TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD_ARG_NAME " Token name\r\n");
+ TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ARG_NAME " Token name\r\n");
}
void totp_cli_command_add_docopt_options() {
TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
- TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX,
- DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_ALGO)) " Token hashing algorithm.\r\n");
- TOTP_CLI_PRINTF(
- " Could be one of: sha1, sha256, sha512 " DOCOPT_DEFAULT("sha1") "\r\n");
- cli_nl();
- TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
- TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX,
+ TOTP_CLI_COMMAND_ARG_ALGO_PREFIX,
DOCOPT_ARGUMENT(
- TOTP_CLI_COMMAND_ADD_ARG_DIGITS)) " Number of digits to generate, one of: 6, 8 " DOCOPT_DEFAULT("6") "\r\n");
+ TOTP_CLI_COMMAND_ARG_ALGO)) " Token hashing algorithm. Must be one of: " TOTP_TOKEN_ALGO_SHA1_NAME
+ ", " TOTP_TOKEN_ALGO_SHA256_NAME
+ ", " TOTP_TOKEN_ALGO_SHA512_NAME
+ ", " TOTP_TOKEN_ALGO_STEAM_NAME
+ " " DOCOPT_DEFAULT(TOTP_TOKEN_ALGO_SHA1_NAME) "\r\n");
TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
- TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX,
+ TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX,
DOCOPT_ARGUMENT(
- TOTP_CLI_COMMAND_ADD_ARG_DURATION)) " Token lifetime duration in seconds, between: 15 and 255 " DOCOPT_DEFAULT("30") "\r\n");
+ TOTP_CLI_COMMAND_ARG_DIGITS)) " Number of digits to generate, one of: 5, 6, 8 " DOCOPT_DEFAULT("6") "\r\n");
+
+ TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
+ TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX,
+ DOCOPT_ARGUMENT(
+ TOTP_CLI_COMMAND_ARG_SECRET_ENCODING)) " Token secret encoding, one of " PLAIN_TOKEN_ENCODING_BASE32_NAME
+ ", " PLAIN_TOKEN_ENCODING_BASE64_NAME
+ " " DOCOPT_DEFAULT(
+ PLAIN_TOKEN_ENCODING_BASE32_NAME) "\r\n");
+
+ TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
+ TOTP_CLI_COMMAND_ARG_DURATION_PREFIX,
+ DOCOPT_ARGUMENT(
+ TOTP_CLI_COMMAND_ARG_DURATION)) " Token lifetime duration in seconds, between: 15 and 255 " DOCOPT_DEFAULT("30") "\r\n");
TOTP_CLI_PRINTF(" " DOCOPT_SWITCH(
- TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) " Show console user input as-is without masking\r\n");
-}
-
-static void furi_string_secure_free(FuriString* str) {
- for(long i = furi_string_size(str) - 1; i >= 0; i--) {
- furi_string_set_char(str, i, '\0');
- }
-
- furi_string_free(str);
+ TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX) " Show console user input as-is without masking\r\n");
+ TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
+ TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX,
+ DOCOPT_ARGUMENT(
+ TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)) " Token automation features to be enabled. Must be one of: " TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME
+ ", " TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME
+ ", " TOTP_TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME
+ " " DOCOPT_DEFAULT(
+ TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME) "\r\n");
+ TOTP_CLI_PRINTF(" # " TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME
+ " - No features\r\n");
+ TOTP_CLI_PRINTF(" # " TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME
+ " - Type key at the end of token input automation\r\n");
+ TOTP_CLI_PRINTF(" # " TOTP_TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME
+ " - Type key at the end of token input automation\r\n");
+ TOTP_CLI_PRINTF(" # " TOTP_TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER_NAME
+ " - Type slower\r\n");
}
void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
@@ -111,55 +93,17 @@ void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cl
// Read optional arguments
bool mask_user_input = true;
+ PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32;
while(args_read_string_and_trim(args, temp_str)) {
bool parsed = false;
- if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX) == 0) {
- if(!args_read_string_and_trim(args, temp_str)) {
- TOTP_CLI_PRINTF_ERROR(
- "Missed value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX "\"\r\n");
- } else if(!token_info_set_algo_from_str(token_info, temp_str)) {
- TOTP_CLI_PRINTF_ERROR(
- "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX
- "\"\r\n",
- furi_string_get_cstr(temp_str));
- } else {
- parsed = true;
- }
- } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX) == 0) {
- uint8_t digit_value;
- if(!args_read_uint8_and_trim(args, &digit_value)) {
- TOTP_CLI_PRINTF_ERROR(
- "Missed or incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX
- "\"\r\n");
- } else if(!token_info_set_digits_from_int(token_info, digit_value)) {
- TOTP_CLI_PRINTF_ERROR(
- "\"%" PRIu8
- "\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX
- "\"\r\n",
- digit_value);
- } else {
- parsed = true;
- }
- } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX) == 0) {
- uint8_t duration_value;
- if(!args_read_uint8_and_trim(args, &duration_value)) {
- TOTP_CLI_PRINTF_ERROR(
- "Missed or incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX
- "\"\r\n");
- } else if(!token_info_set_duration_from_int(token_info, duration_value)) {
- TOTP_CLI_PRINTF_ERROR(
- "\"%" PRIu8
- "\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX
- "\"\r\n",
- duration_value);
- } else {
- parsed = true;
- }
- } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) == 0) {
- mask_user_input = false;
- parsed = true;
- } else {
- TOTP_CLI_PRINTF_ERROR("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str));
+ if(!totp_cli_try_read_algo(token_info, temp_str, args, &parsed) &&
+ !totp_cli_try_read_digits(token_info, temp_str, args, &parsed) &&
+ !totp_cli_try_read_duration(token_info, temp_str, args, &parsed) &&
+ !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) &&
+ !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed) &&
+ !totp_cli_try_read_plain_token_secret_encoding(
+ temp_str, args, &parsed, &token_secret_encoding)) {
+ totp_cli_printf_unknown_argument(temp_str);
}
if(!parsed) {
@@ -184,31 +128,34 @@ void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cl
TOTP_CLI_DELETE_LAST_LINE();
- if(!token_info_set_secret(
- token_info,
- furi_string_get_cstr(temp_str),
- furi_string_size(temp_str),
- plugin_state->iv)) {
- TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n");
- furi_string_secure_free(temp_str);
- token_info_free(token_info);
- return;
- }
-
- furi_string_secure_free(temp_str);
-
bool load_generate_token_scene = false;
if(plugin_state->current_scene == TotpSceneGenerateToken) {
totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
load_generate_token_scene = true;
}
- TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, token_info, furi_check);
- plugin_state->tokens_count++;
- if(totp_config_file_save_new_token(token_info) == TotpConfigFileUpdateSuccess) {
- TOTP_CLI_PRINTF_SUCCESS("Token \"%s\" has been successfully added\r\n", token_info->name);
+ bool secret_set = token_info_set_secret(
+ token_info,
+ furi_string_get_cstr(temp_str),
+ furi_string_size(temp_str),
+ token_secret_encoding,
+ plugin_state->iv);
+
+ furi_string_secure_free(temp_str);
+
+ if(secret_set) {
+ TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, token_info, furi_check);
+ plugin_state->tokens_count++;
+
+ if(totp_config_file_save_new_token(token_info) == TotpConfigFileUpdateSuccess) {
+ TOTP_CLI_PRINTF_SUCCESS(
+ "Token \"%s\" has been successfully added\r\n", token_info->name);
+ } else {
+ TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+ }
} else {
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+ token_info_free(token_info);
+ TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n");
}
if(load_generate_token_scene) {
diff --git a/applications/external/totp/cli/commands/automation/automation.c b/applications/external/totp/cli/commands/automation/automation.c
index 1fd87f456..1af2e5dd7 100644
--- a/applications/external/totp/cli/commands/automation/automation.c
+++ b/applications/external/totp/cli/commands/automation/automation.c
@@ -23,12 +23,12 @@ void totp_cli_command_automation_docopt_usage() {
void totp_cli_command_automation_docopt_arguments() {
TOTP_CLI_PRINTF(
" " TOTP_CLI_COMMAND_AUTOMATION_ARG_METHOD
- " Automation method to be set. Must be one of [" TOTP_CLI_COMMAND_AUTOMATION_METHOD_NONE
+ " Automation method to be set. Must be one of: " TOTP_CLI_COMMAND_AUTOMATION_METHOD_NONE
", " TOTP_CLI_COMMAND_AUTOMATION_METHOD_USB
#ifdef TOTP_BADBT_TYPE_ENABLED
", " TOTP_CLI_COMMAND_AUTOMATION_METHOD_BT
#endif
- "]\r\n");
+ "\r\n");
}
static void totp_cli_command_automation_print_method(AutomationMethod method, char* color) {
diff --git a/applications/external/totp/cli/commands/delete/delete.c b/applications/external/totp/cli/commands/delete/delete.c
index 04cc815a4..a45525e4b 100644
--- a/applications/external/totp/cli/commands/delete/delete.c
+++ b/applications/external/totp/cli/commands/delete/delete.c
@@ -3,13 +3,13 @@
#include
#include
#include
-#include "../../../lib/list/list.h"
+#include
#include "../../../services/config/config.h"
#include "../../cli_helpers.h"
#include "../../../ui/scene_director.h"
+#include "../../common_command_arguments.h"
-#define TOTP_CLI_COMMAND_DELETE_ARG_INDEX "index"
-#define TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX "-f"
+#define TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX "-f"
void totp_cli_command_delete_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_DELETE ", " TOTP_CLI_COMMAND_DELETE_ALT
@@ -20,19 +20,23 @@ void totp_cli_command_delete_docopt_usage() {
TOTP_CLI_PRINTF(
" " TOTP_CLI_COMMAND_NAME
" " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_DELETE " | " TOTP_CLI_COMMAND_DELETE_ALT) " " DOCOPT_ARGUMENT(
- TOTP_CLI_COMMAND_DELETE_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX)) "\r\n");
+ TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX)) "\r\n");
}
void totp_cli_command_delete_docopt_arguments() {
- TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_DELETE_ARG_INDEX " Token index in the list\r\n");
+ TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ARG_INDEX " Token index in the list\r\n");
}
void totp_cli_command_delete_docopt_options() {
TOTP_CLI_PRINTF(" " DOCOPT_SWITCH(
- TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX) " Force command to do not ask user for interactive confirmation\r\n");
+ TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX) " Force command to do not ask user for interactive confirmation\r\n");
}
void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
+ if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
+ return;
+ }
+
int token_number;
if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
token_number > plugin_state->tokens_count) {
@@ -43,10 +47,10 @@ void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args,
FuriString* temp_str = furi_string_alloc();
bool confirm_needed = true;
if(args_read_string_and_trim(args, temp_str)) {
- if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX) == 0) {
+ if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX) == 0) {
confirm_needed = false;
} else {
- TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str));
+ totp_cli_printf_unknown_argument(temp_str);
TOTP_CLI_PRINT_INVALID_ARGUMENTS();
furi_string_free(temp_str);
return;
@@ -54,10 +58,6 @@ void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args,
}
furi_string_free(temp_str);
- if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
- return;
- }
-
ListNode* list_node = list_element_at(plugin_state->tokens_list, token_number - 1);
TokenInfo* token_info = list_node->data;
diff --git a/applications/external/totp/cli/commands/details/details.c b/applications/external/totp/cli/commands/details/details.c
new file mode 100644
index 000000000..1b9289454
--- /dev/null
+++ b/applications/external/totp/cli/commands/details/details.c
@@ -0,0 +1,78 @@
+#include "details.h"
+#include
+#include
+#include
+#include "../../../types/token_info.h"
+#include "../../../services/config/constants.h"
+#include "../../cli_helpers.h"
+#include "../../common_command_arguments.h"
+
+#define TOTP_CLI_PRINTF_AUTOMATION_FEATURE(description, header_printed) \
+ do { \
+ TOTP_CLI_PRINTF( \
+ "| %-20s | %-28.28s |\r\n", \
+ header_printed ? "" : "Automation features", \
+ description); \
+ header_printed = true; \
+ } while(false)
+
+static void print_automation_features(const TokenInfo* token_info) {
+ if(token_info->automation_features == TOKEN_AUTOMATION_FEATURE_NONE) {
+ TOTP_CLI_PRINTF("| %-20s | %-28.28s |\r\n", "Automation features", "None");
+ return;
+ }
+
+ bool header_printed = false;
+ if(token_info->automation_features & TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END) {
+ TOTP_CLI_PRINTF_AUTOMATION_FEATURE("Type key at the end", header_printed);
+ }
+
+ if(token_info->automation_features & TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END) {
+ TOTP_CLI_PRINTF_AUTOMATION_FEATURE("Type key at the end", header_printed);
+ }
+
+ if(token_info->automation_features & TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER) {
+ TOTP_CLI_PRINTF_AUTOMATION_FEATURE("Type slower", header_printed);
+ }
+}
+
+void totp_cli_command_details_docopt_commands() {
+ TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_DETAILS ", " TOTP_CLI_COMMAND_DETAILS_ALT
+ " Displays token details\r\n");
+}
+
+void totp_cli_command_details_docopt_usage() {
+ TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " DOCOPT_REQUIRED(
+ TOTP_CLI_COMMAND_DETAILS
+ " | " TOTP_CLI_COMMAND_DETAILS_ALT) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) "\r\n");
+}
+
+void totp_cli_command_details_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
+ if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
+ return;
+ }
+
+ int token_number;
+ if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
+ token_number > plugin_state->tokens_count) {
+ TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ return;
+ }
+
+ ListNode* list_node = list_element_at(plugin_state->tokens_list, token_number - 1);
+
+ TokenInfo* token_info = list_node->data;
+
+ TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+ TOTP_CLI_PRINTF("| %-20s | %-28s |\r\n", "Property", "Value");
+ TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+ TOTP_CLI_PRINTF("| %-20s | %-28d |\r\n", "Index", token_number);
+ TOTP_CLI_PRINTF("| %-20s | %-28.28s |\r\n", "Name", token_info->name);
+ TOTP_CLI_PRINTF(
+ "| %-20s | %-28s |\r\n", "Hashing algorithm", token_info_get_algo_as_cstr(token_info));
+ TOTP_CLI_PRINTF("| %-20s | %-28" PRIu8 " |\r\n", "Number of digits", token_info->digits);
+ TOTP_CLI_PRINTF(
+ "| %-20s | %" PRIu8 " sec.%-21s |\r\n", "Token lifetime", token_info->duration, " ");
+ print_automation_features(token_info);
+ TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/details/details.h b/applications/external/totp/cli/commands/details/details.h
new file mode 100644
index 000000000..d141705a5
--- /dev/null
+++ b/applications/external/totp/cli/commands/details/details.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include
+#include "../../../types/plugin_state.h"
+
+#define TOTP_CLI_COMMAND_DETAILS "lsattr"
+#define TOTP_CLI_COMMAND_DETAILS_ALT "cat"
+
+void totp_cli_command_details_handle(PluginState* plugin_state, FuriString* args, Cli* cli);
+void totp_cli_command_details_docopt_commands();
+void totp_cli_command_details_docopt_usage();
diff --git a/applications/external/totp/cli/commands/help/help.c b/applications/external/totp/cli/commands/help/help.c
index 34b44debd..cc47db11f 100644
--- a/applications/external/totp/cli/commands/help/help.c
+++ b/applications/external/totp/cli/commands/help/help.c
@@ -1,6 +1,7 @@
#include "help.h"
#include "../../cli_helpers.h"
#include "../add/add.h"
+#include "../update/update.h"
#include "../delete/delete.h"
#include "../list/list.h"
#include "../timezone/timezone.h"
@@ -9,6 +10,7 @@
#include "../notification/notification.h"
#include "../reset/reset.h"
#include "../automation/automation.h"
+#include "../details/details.h"
void totp_cli_command_help_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_HELP ", " TOTP_CLI_COMMAND_HELP_ALT
@@ -25,7 +27,9 @@ void totp_cli_command_help_handle() {
TOTP_CLI_PRINTF("Usage:\r\n");
totp_cli_command_help_docopt_usage();
totp_cli_command_list_docopt_usage();
+ totp_cli_command_details_docopt_usage();
totp_cli_command_add_docopt_usage();
+ totp_cli_command_update_docopt_usage();
totp_cli_command_delete_docopt_usage();
totp_cli_command_timezone_docopt_usage();
totp_cli_command_move_docopt_usage();
@@ -37,7 +41,9 @@ void totp_cli_command_help_handle() {
TOTP_CLI_PRINTF("Commands:\r\n");
totp_cli_command_help_docopt_commands();
totp_cli_command_list_docopt_commands();
+ totp_cli_command_details_docopt_commands();
totp_cli_command_add_docopt_commands();
+ totp_cli_command_update_docopt_commands();
totp_cli_command_delete_docopt_commands();
totp_cli_command_timezone_docopt_commands();
totp_cli_command_move_docopt_commands();
@@ -49,12 +55,13 @@ void totp_cli_command_help_handle() {
TOTP_CLI_PRINTF("Arguments:\r\n");
totp_cli_command_add_docopt_arguments();
totp_cli_command_delete_docopt_arguments();
+ totp_cli_command_move_docopt_arguments();
totp_cli_command_timezone_docopt_arguments();
totp_cli_command_notification_docopt_arguments();
totp_cli_command_automation_docopt_arguments();
cli_nl();
TOTP_CLI_PRINTF("Options:\r\n");
totp_cli_command_add_docopt_options();
+ totp_cli_command_update_docopt_options();
totp_cli_command_delete_docopt_options();
- totp_cli_command_move_docopt_options();
}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/list/list.c b/applications/external/totp/cli/commands/list/list.c
index 8ed9a136d..951c102a0 100644
--- a/applications/external/totp/cli/commands/list/list.c
+++ b/applications/external/totp/cli/commands/list/list.c
@@ -1,25 +1,10 @@
#include "list.h"
#include
-#include "../../../lib/list/list.h"
+#include
#include "../../../types/token_info.h"
#include "../../../services/config/constants.h"
#include "../../cli_helpers.h"
-static char* get_algo_as_cstr(TokenHashAlgo algo) {
- switch(algo) {
- case SHA1:
- return TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME;
- case SHA256:
- return TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME;
- case SHA512:
- return TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME;
- default:
- break;
- }
-
- return "UNKNOWN";
-}
-
void totp_cli_command_list_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_LIST ", " TOTP_CLI_COMMAND_LIST_ALT
" List all available tokens\r\n");
@@ -41,8 +26,7 @@ void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) {
}
TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
- TOTP_CLI_PRINTF(
- "| %-*s | %-*s | %-*s | %-s | %-s |\r\n", 3, "#", 25, "Name", 6, "Algo", "Ln", "Dur");
+ TOTP_CLI_PRINTF("| %-3s | %-25s | %-6s | %-s | %-s |\r\n", "#", "Name", "Algo", "Ln", "Dur");
TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
uint16_t index = 1;
TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
@@ -51,7 +35,7 @@ void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) {
"| %-3" PRIu16 " | %-25.25s | %-6s | %-2" PRIu8 " | %-3" PRIu8 " |\r\n",
index,
token_info->name,
- get_algo_as_cstr(token_info->algo),
+ token_info_get_algo_as_cstr(token_info),
token_info->digits,
token_info->duration);
index++;
diff --git a/applications/external/totp/cli/commands/move/move.c b/applications/external/totp/cli/commands/move/move.c
index 1d5a23bbf..5c4bdfcd6 100644
--- a/applications/external/totp/cli/commands/move/move.c
+++ b/applications/external/totp/cli/commands/move/move.c
@@ -2,122 +2,49 @@
#include
#include
-#include "../../../lib/list/list.h"
+#include
#include "../../../types/token_info.h"
#include "../../../services/config/config.h"
#include "../../cli_helpers.h"
#include "../../../ui/scene_director.h"
+#include "../../common_command_arguments.h"
-#define TOTP_CLI_COMMAND_MOVE_ARG_INDEX "index"
-
-#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME "name"
-#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX "-n"
-
-#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX "index"
-#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX "-i"
+#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX "new_index"
void totp_cli_command_move_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_MOVE ", " TOTP_CLI_COMMAND_MOVE_ALT
- " Move\\rename token\r\n");
+ " Move token\r\n");
}
void totp_cli_command_move_docopt_usage() {
TOTP_CLI_PRINTF(
" " TOTP_CLI_COMMAND_NAME
- " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_MOVE " | " TOTP_CLI_COMMAND_MOVE_ALT) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_INDEX) " " DOCOPT_OPTIONAL(
- DOCOPT_OPTION(
- TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX,
- DOCOPT_ARGUMENT(
- TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX))) "\r\n");
+ " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_MOVE " | " TOTP_CLI_COMMAND_MOVE_ALT) " " DOCOPT_ARGUMENT(
+ TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX) "\r\n");
}
-void totp_cli_command_move_docopt_options() {
- TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
- TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX,
- DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME)) " New token name\r\n");
- TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
- TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX,
- DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX)) " New token index\r\n");
+void totp_cli_command_move_docopt_arguments() {
+ TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX
+ " New token index in the list\r\n");
}
void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
- int token_index;
- if(!args_read_int_and_trim(args, &token_index)) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
- return;
- }
-
if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
return;
}
- if(token_index < 1 || token_index > plugin_state->tokens_count) {
+ int token_index;
+ if(!args_read_int_and_trim(args, &token_index) || token_index < 1 ||
+ token_index > plugin_state->tokens_count) {
TOTP_CLI_PRINT_INVALID_ARGUMENTS();
return;
}
- FuriString* temp_str = furi_string_alloc();
-
- char* new_token_name = NULL;
int new_token_index = 0;
- while(args_read_string_and_trim(args, temp_str)) {
- bool parsed = false;
- if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX) == 0) {
- if(!args_read_string_and_trim(args, temp_str)) {
- TOTP_CLI_PRINTF_ERROR(
- "Missed value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX
- "\"\r\n");
- } else {
- if(new_token_name != NULL) {
- free(new_token_name);
- }
-
- new_token_name = malloc(furi_string_size(temp_str) + 1);
- if(new_token_name == NULL) {
- furi_string_free(temp_str);
- return;
- }
-
- strlcpy(
- new_token_name,
- furi_string_get_cstr(temp_str),
- furi_string_size(temp_str) + 1);
- parsed = true;
- }
- } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX) == 0) {
- if(!args_read_int_and_trim(args, &new_token_index)) {
- TOTP_CLI_PRINTF_ERROR(
- "Missed value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX
- "\"\r\n");
- } else if(new_token_index < 1 || new_token_index > plugin_state->tokens_count) {
- TOTP_CLI_PRINTF_ERROR(
- "\"%" PRId16
- "\" is incorrect value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX
- "\"\r\n",
- new_token_index);
- } else {
- parsed = true;
- }
- } else {
- TOTP_CLI_PRINTF_ERROR("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str));
- }
-
- if(!parsed) {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
- furi_string_free(temp_str);
- if(new_token_name != NULL) {
- free(new_token_name);
- }
- return;
- }
- }
-
- if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
- furi_string_free(temp_str);
- if(new_token_name != NULL) {
- free(new_token_name);
- }
+ if(!args_read_int_and_trim(args, &new_token_index) || new_token_index < 1 ||
+ new_token_index > plugin_state->tokens_count) {
+ TOTP_CLI_PRINT_INVALID_ARGUMENTS();
return;
}
@@ -127,39 +54,21 @@ void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, C
activate_generate_token_scene = true;
}
- bool token_updated = false;
TokenInfo* token_info = NULL;
- if(new_token_index > 0) {
- plugin_state->tokens_list =
- list_remove_at(plugin_state->tokens_list, token_index - 1, (void**)&token_info);
- furi_check(token_info != NULL);
- plugin_state->tokens_list =
- list_insert_at(plugin_state->tokens_list, new_token_index - 1, token_info);
- token_updated = true;
- } else {
- token_info = list_element_at(plugin_state->tokens_list, token_index - 1)->data;
- }
+ plugin_state->tokens_list =
+ list_remove_at(plugin_state->tokens_list, token_index - 1, (void**)&token_info);
+ furi_check(token_info != NULL);
+ plugin_state->tokens_list =
+ list_insert_at(plugin_state->tokens_list, new_token_index - 1, token_info);
- if(new_token_name != NULL) {
- free(token_info->name);
- token_info->name = new_token_name;
- token_updated = true;
- }
-
- if(token_updated) {
- if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
- TOTP_CLI_PRINTF_SUCCESS(
- "Token \"%s\" has been successfully updated\r\n", token_info->name);
- } else {
- TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
- }
+ if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
+ TOTP_CLI_PRINTF_SUCCESS(
+ "Token \"%s\" has been successfully updated\r\n", token_info->name);
} else {
- TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
}
if(activate_generate_token_scene) {
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
}
-
- furi_string_free(temp_str);
}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/move/move.h b/applications/external/totp/cli/commands/move/move.h
index 9eaad5319..da84f2a62 100644
--- a/applications/external/totp/cli/commands/move/move.h
+++ b/applications/external/totp/cli/commands/move/move.h
@@ -9,4 +9,4 @@
void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, Cli* cli);
void totp_cli_command_move_docopt_commands();
void totp_cli_command_move_docopt_usage();
-void totp_cli_command_move_docopt_options();
\ No newline at end of file
+void totp_cli_command_move_docopt_arguments();
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/notification/notification.c b/applications/external/totp/cli/commands/notification/notification.c
index 016b83d0d..b81b7371a 100644
--- a/applications/external/totp/cli/commands/notification/notification.c
+++ b/applications/external/totp/cli/commands/notification/notification.c
@@ -23,9 +23,9 @@ void totp_cli_command_notification_docopt_usage() {
void totp_cli_command_notification_docopt_arguments() {
TOTP_CLI_PRINTF(
" " TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD
- " Notification method to be set. Must be one of [" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE
+ " Notification method to be set. Must be one of: " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE
", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND
- ", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "]\r\n");
+ ", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "\r\n");
}
static void totp_cli_command_notification_print_method(NotificationMethod method, char* color) {
diff --git a/applications/external/totp/cli/commands/pin/pin.c b/applications/external/totp/cli/commands/pin/pin.c
index b5b4943d6..9b9038ae7 100644
--- a/applications/external/totp/cli/commands/pin/pin.c
+++ b/applications/external/totp/cli/commands/pin/pin.c
@@ -2,11 +2,12 @@
#include
#include
+#include
#include "../../../types/token_info.h"
#include "../../../types/user_pin_codes.h"
#include "../../../services/config/config.h"
#include "../../cli_helpers.h"
-#include "../../../lib/polyfills/memset_s.h"
+#include
#include "../../../services/crypto/crypto.h"
#include "../../../ui/scene_director.h"
@@ -120,6 +121,19 @@ void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cl
memset(&new_pin[0], 0, TOTP_IV_SIZE);
}
+ char* backup_path = totp_config_file_backup();
+ if(backup_path != NULL) {
+ TOTP_CLI_PRINTF_WARNING("Backup conf file %s has been created\r\n", backup_path);
+ TOTP_CLI_PRINTF_WARNING(
+ "Once you make sure everything is fine and works as expected, please delete this backup file\r\n");
+ free(backup_path);
+ } else {
+ memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE);
+ TOTP_CLI_PRINTF_ERROR(
+ "An error has occurred during taking backup of config file\r\n");
+ break;
+ }
+
if(plugin_state->current_scene == TotpSceneGenerateToken) {
totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
load_generate_token_scene = true;
diff --git a/applications/external/totp/cli/commands/timezone/timezone.c b/applications/external/totp/cli/commands/timezone/timezone.c
index 265d80e53..61e4fa065 100644
--- a/applications/external/totp/cli/commands/timezone/timezone.c
+++ b/applications/external/totp/cli/commands/timezone/timezone.c
@@ -35,7 +35,7 @@ void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* arg
if(*strtof_endptr == 0 && tz >= -12.75f && tz <= 12.75f) {
plugin_state->timezone_offset = tz;
if(totp_config_file_update_timezone_offset(tz) == TotpConfigFileUpdateSuccess) {
- TOTP_CLI_PRINTF_SUCCESS("Timezone is set to %f\r\n", tz);
+ TOTP_CLI_PRINTF_SUCCESS("Timezone is set to %f\r\n", (double)tz);
} else {
TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
}
@@ -50,7 +50,8 @@ void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* arg
TOTP_CLI_PRINTF_ERROR("Invalid timezone offset\r\n");
}
} else {
- TOTP_CLI_PRINTF_INFO("Current timezone offset is %f\r\n", plugin_state->timezone_offset);
+ TOTP_CLI_PRINTF_INFO(
+ "Current timezone offset is %f\r\n", (double)plugin_state->timezone_offset);
}
furi_string_free(temp_str);
}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/update/update.c b/applications/external/totp/cli/commands/update/update.c
new file mode 100644
index 000000000..bba7cad35
--- /dev/null
+++ b/applications/external/totp/cli/commands/update/update.c
@@ -0,0 +1,169 @@
+#include "update.h"
+#include
+#include
+#include
+#include "../../../types/token_info.h"
+#include "../../../services/config/config.h"
+#include "../../../services/convert/convert.h"
+#include "../../cli_helpers.h"
+#include "../../../ui/scene_director.h"
+#include "../../common_command_arguments.h"
+
+#define TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX "-s"
+
+void totp_cli_command_update_docopt_commands() {
+ TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_UPDATE " Update existing token\r\n");
+}
+
+void totp_cli_command_update_docopt_usage() {
+ TOTP_CLI_PRINTF(
+ " " TOTP_CLI_COMMAND_NAME
+ " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_UPDATE) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_NAME_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME))) " " DOCOPT_OPTIONAL(
+ DOCOPT_OPTION(
+ TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX,
+ DOCOPT_ARGUMENT(
+ TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n");
+}
+
+void totp_cli_command_update_docopt_options() {
+ TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
+ TOTP_CLI_COMMAND_ARG_NAME_PREFIX,
+ DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME)) " Token name\r\n");
+
+ TOTP_CLI_PRINTF(" " DOCOPT_SWITCH(
+ TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) " Update token secret\r\n");
+}
+
+static bool
+ totp_cli_try_read_name(TokenInfo* token_info, FuriString* arg, FuriString* args, bool* parsed) {
+ if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_NAME_PREFIX) == 0) {
+ if(!args_read_probably_quoted_string_and_trim(args, arg) || furi_string_empty(arg)) {
+ totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_NAME_PREFIX);
+ } else {
+ if(token_info->name != NULL) {
+ free(token_info->name);
+ }
+
+ size_t temp_cstr_len = furi_string_size(arg);
+ token_info->name = malloc(temp_cstr_len + 1);
+ furi_check(token_info->name != NULL);
+ strlcpy(token_info->name, furi_string_get_cstr(arg), temp_cstr_len + 1);
+ *parsed = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool totp_cli_try_read_change_secret_flag(const FuriString* arg, bool* parsed, bool* flag) {
+ if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) == 0) {
+ *flag = true;
+ *parsed = true;
+ return true;
+ }
+
+ return false;
+}
+
+void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
+ FuriString* temp_str = furi_string_alloc();
+
+ if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
+ return;
+ }
+
+ int token_number;
+ if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
+ token_number > plugin_state->tokens_count) {
+ TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ return;
+ }
+
+ ListNode* list_item = list_element_at(plugin_state->tokens_list, token_number - 1);
+ TokenInfo* existing_token_info = list_item->data;
+ TokenInfo* token_info = token_info_clone(existing_token_info);
+
+ // Read optional arguments
+ bool mask_user_input = true;
+ bool update_token_secret = false;
+ PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32;
+ while(args_read_string_and_trim(args, temp_str)) {
+ bool parsed = false;
+ if(!totp_cli_try_read_name(token_info, temp_str, args, &parsed) &&
+ !totp_cli_try_read_algo(token_info, temp_str, args, &parsed) &&
+ !totp_cli_try_read_digits(token_info, temp_str, args, &parsed) &&
+ !totp_cli_try_read_duration(token_info, temp_str, args, &parsed) &&
+ !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) &&
+ !totp_cli_try_read_change_secret_flag(temp_str, &parsed, &update_token_secret) &&
+ !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed) &&
+ !totp_cli_try_read_plain_token_secret_encoding(
+ temp_str, args, &parsed, &token_secret_encoding)) {
+ totp_cli_printf_unknown_argument(temp_str);
+ }
+
+ if(!parsed) {
+ TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+ furi_string_free(temp_str);
+ token_info_free(token_info);
+ return;
+ }
+ }
+
+ bool token_secret_read = false;
+ if(update_token_secret) {
+ // Reading token secret
+ furi_string_reset(temp_str);
+ TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n");
+ token_secret_read = totp_cli_read_line(cli, temp_str, mask_user_input);
+ TOTP_CLI_DELETE_LAST_LINE();
+ if(!token_secret_read) {
+ TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
+ }
+ }
+
+ bool load_generate_token_scene = false;
+ if(plugin_state->current_scene == TotpSceneGenerateToken) {
+ totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
+ load_generate_token_scene = true;
+ }
+
+ bool token_secret_set = false;
+ if(update_token_secret && token_secret_read) {
+ if(token_info->token != NULL) {
+ free(token_info->token);
+ }
+ token_secret_set = token_info_set_secret(
+ token_info,
+ furi_string_get_cstr(temp_str),
+ furi_string_size(temp_str),
+ token_secret_encoding,
+ plugin_state->iv);
+ if(!token_secret_set) {
+ TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n");
+ }
+ }
+
+ furi_string_secure_free(temp_str);
+
+ if(!update_token_secret || (token_secret_read && token_secret_set)) {
+ list_item->data = token_info;
+
+ if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
+ TOTP_CLI_PRINTF_SUCCESS(
+ "Token \"%s\" has been successfully updated\r\n", token_info->name);
+ token_info_free(existing_token_info);
+ } else {
+ TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+ list_item->data = existing_token_info;
+ token_info_free(token_info);
+ }
+ } else {
+ token_info_free(token_info);
+ }
+
+ if(load_generate_token_scene) {
+ totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+ }
+}
\ No newline at end of file
diff --git a/applications/external/totp/cli/commands/update/update.h b/applications/external/totp/cli/commands/update/update.h
new file mode 100644
index 000000000..3b5b442e8
--- /dev/null
+++ b/applications/external/totp/cli/commands/update/update.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include
+#include "../../../types/plugin_state.h"
+
+#define TOTP_CLI_COMMAND_UPDATE "update"
+
+void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, Cli* cli);
+void totp_cli_command_update_docopt_commands();
+void totp_cli_command_update_docopt_usage();
+void totp_cli_command_update_docopt_options();
\ No newline at end of file
diff --git a/applications/external/totp/cli/common_command_arguments.c b/applications/external/totp/cli/common_command_arguments.c
new file mode 100644
index 000000000..9ed9f0126
--- /dev/null
+++ b/applications/external/totp/cli/common_command_arguments.c
@@ -0,0 +1,141 @@
+#include "common_command_arguments.h"
+#include
+
+inline void totp_cli_printf_missed_argument_value(char* arg) {
+ TOTP_CLI_PRINTF_ERROR("Missed or incorrect value for argument \"%s\"\r\n", arg);
+}
+
+inline void totp_cli_printf_unknown_argument(const FuriString* arg) {
+ TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(arg));
+}
+
+bool totp_cli_try_read_algo(TokenInfo* token_info, FuriString* arg, FuriString* args, bool* parsed) {
+ if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_ALGO_PREFIX) == 0) {
+ if(!args_read_string_and_trim(args, arg)) {
+ totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX);
+ } else if(!token_info_set_algo_from_str(token_info, arg)) {
+ TOTP_CLI_PRINTF_ERROR(
+ "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_ALGO_PREFIX
+ "\"\r\n",
+ furi_string_get_cstr(arg));
+ } else {
+ *parsed = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool totp_cli_try_read_digits(
+ TokenInfo* token_info,
+ const FuriString* arg,
+ FuriString* args,
+ bool* parsed) {
+ if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX) == 0) {
+ uint8_t digit_value;
+ if(!args_read_uint8_and_trim(args, &digit_value)) {
+ totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX);
+ } else if(!token_info_set_digits_from_int(token_info, digit_value)) {
+ TOTP_CLI_PRINTF_ERROR(
+ "\"%" PRIu8
+ "\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX
+ "\"\r\n",
+ digit_value);
+ } else {
+ *parsed = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool totp_cli_try_read_duration(
+ TokenInfo* token_info,
+ const FuriString* arg,
+ FuriString* args,
+ bool* parsed) {
+ if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_DURATION_PREFIX) == 0) {
+ uint8_t duration_value;
+ if(!args_read_uint8_and_trim(args, &duration_value)) {
+ totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX);
+ } else if(!token_info_set_duration_from_int(token_info, duration_value)) {
+ TOTP_CLI_PRINTF_ERROR(
+ "\"%" PRIu8
+ "\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_DURATION_PREFIX
+ "\"\r\n",
+ duration_value);
+ } else {
+ *parsed = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool totp_cli_try_read_automation_features(
+ TokenInfo* token_info,
+ FuriString* arg,
+ FuriString* args,
+ bool* parsed) {
+ if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX) == 0) {
+ if(!args_read_string_and_trim(args, arg)) {
+ totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX);
+ } else if(!token_info_set_automation_feature_from_str(token_info, arg)) {
+ TOTP_CLI_PRINTF_ERROR(
+ "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX
+ "\"\r\n",
+ furi_string_get_cstr(arg));
+ } else {
+ *parsed = true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* unsecure_flag) {
+ if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX) == 0) {
+ *unsecure_flag = false;
+ *parsed = true;
+ return true;
+ }
+
+ return false;
+}
+
+bool totp_cli_try_read_plain_token_secret_encoding(
+ FuriString* arg,
+ FuriString* args,
+ bool* parsed,
+ PlainTokenSecretEncoding* secret_encoding) {
+ if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX) == 0) {
+ if(!args_read_string_and_trim(args, arg)) {
+ totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX);
+ } else {
+ if(furi_string_cmpi_str(arg, PLAIN_TOKEN_ENCODING_BASE32_NAME) == 0) {
+ *secret_encoding = PLAIN_TOKEN_ENCODING_BASE32;
+ *parsed = true;
+ } else if(furi_string_cmpi_str(arg, PLAIN_TOKEN_ENCODING_BASE64_NAME) == 0) {
+ *secret_encoding = PLAIN_TOKEN_ENCODING_BASE64;
+ *parsed = true;
+ } else {
+ TOTP_CLI_PRINTF_ERROR(
+ "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX
+ "\"\r\n",
+ furi_string_get_cstr(arg));
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
\ No newline at end of file
diff --git a/applications/external/totp/cli/common_command_arguments.h b/applications/external/totp/cli/common_command_arguments.h
new file mode 100644
index 000000000..be01c216d
--- /dev/null
+++ b/applications/external/totp/cli/common_command_arguments.h
@@ -0,0 +1,45 @@
+#pragma once
+#include
+#include "../types/token_info.h"
+#include "cli_helpers.h"
+
+#define TOTP_CLI_COMMAND_ARG_NAME "name"
+#define TOTP_CLI_COMMAND_ARG_NAME_PREFIX "-n"
+#define TOTP_CLI_COMMAND_ARG_ALGO "algo"
+#define TOTP_CLI_COMMAND_ARG_ALGO_PREFIX "-a"
+#define TOTP_CLI_COMMAND_ARG_DIGITS "digits"
+#define TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX "-d"
+#define TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX "-u"
+#define TOTP_CLI_COMMAND_ARG_DURATION "duration"
+#define TOTP_CLI_COMMAND_ARG_DURATION_PREFIX "-l"
+#define TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX "-b"
+#define TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE "feature"
+#define TOTP_CLI_COMMAND_ARG_INDEX "index"
+#define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX "-e"
+#define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING "encoding"
+
+void totp_cli_printf_unknown_argument(const FuriString* arg);
+void totp_cli_printf_missed_argument_value(char* arg);
+bool totp_cli_try_read_algo(TokenInfo* token_info, FuriString* arg, FuriString* args, bool* parsed);
+bool totp_cli_try_read_digits(
+ TokenInfo* token_info,
+ const FuriString* arg,
+ FuriString* args,
+ bool* parsed);
+bool totp_cli_try_read_duration(
+ TokenInfo* token_info,
+ const FuriString* arg,
+ FuriString* args,
+ bool* parsed);
+bool totp_cli_try_read_automation_features(
+ TokenInfo* token_info,
+ FuriString* arg,
+ FuriString* args,
+ bool* parsed);
+bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* unsecure_flag);
+
+bool totp_cli_try_read_plain_token_secret_encoding(
+ FuriString* arg,
+ FuriString* args,
+ bool* parsed,
+ PlainTokenSecretEncoding* secret_encoding);
\ No newline at end of file
diff --git a/applications/external/totp/features_config.h b/applications/external/totp/features_config.h
index d3b30aee0..3489950bf 100644
--- a/applications/external/totp/features_config.h
+++ b/applications/external/totp/features_config.h
@@ -1,2 +1,16 @@
+// Include Bluetooth token input automation
#define TOTP_BADBT_TYPE_ENABLED
-#define TOTP_BADBT_TYPE_ICON_ENABLED
\ No newline at end of file
+
+// Include token input automation icons on the main screen
+#define TOTP_AUTOMATION_ICONS_ENABLED
+
+// List of compatible firmwares
+#define TOTP_FIRMWARE_OFFICIAL_STABLE (1)
+#define TOTP_FIRMWARE_OFFICIAL_DEV (2)
+#define TOTP_FIRMWARE_XTREME (3)
+// End of list
+
+// Target firmware to build for
+#ifndef TOTP_TARGET_FIRMWARE
+#define TOTP_TARGET_FIRMWARE TOTP_FIRMWARE_XTREME
+#endif
\ No newline at end of file
diff --git a/applications/external/totp/images/hid_ble_10x7.png b/applications/external/totp/images/hid_ble_10x7.png
deleted file mode 100644
index 3cd1ff95c..000000000
Binary files a/applications/external/totp/images/hid_ble_10x7.png and /dev/null differ
diff --git a/applications/external/totp/images/hid_ble_31x9.png b/applications/external/totp/images/hid_ble_31x9.png
new file mode 100644
index 000000000..fb999231f
Binary files /dev/null and b/applications/external/totp/images/hid_ble_31x9.png differ
diff --git a/applications/external/totp/images/hid_usb_31x9.png b/applications/external/totp/images/hid_usb_31x9.png
new file mode 100644
index 000000000..c6b8aa228
Binary files /dev/null and b/applications/external/totp/images/hid_usb_31x9.png differ
diff --git a/applications/external/totp/lib/base32/base32.c b/applications/external/totp/lib/base32/base32.c
index 9781c831f..827aa1e94 100644
--- a/applications/external/totp/lib/base32/base32.c
+++ b/applications/external/totp/lib/base32/base32.c
@@ -17,10 +17,10 @@
#include "base32.h"
-int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize) {
+size_t base32_decode(const uint8_t* encoded, uint8_t* result, size_t bufSize) {
int buffer = 0;
int bitsLeft = 0;
- int count = 0;
+ size_t count = 0;
for(const uint8_t* ptr = encoded; count < bufSize && *ptr; ++ptr) {
uint8_t ch = *ptr;
if(ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') {
@@ -43,7 +43,7 @@ int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize) {
} else if(ch >= '2' && ch <= '7') {
ch -= '2' - 26;
} else {
- return -1;
+ return 0;
}
buffer |= ch;
diff --git a/applications/external/totp/lib/base32/base32.h b/applications/external/totp/lib/base32/base32.h
index dea1a1c81..a0ec86e82 100644
--- a/applications/external/totp/lib/base32/base32.h
+++ b/applications/external/totp/lib/base32/base32.h
@@ -27,6 +27,7 @@
#pragma once
+#include
#include
/**
@@ -34,6 +35,6 @@
* @param encoded Base-32 encoded bytes
* @param[out] result result output buffer
* @param bufSize result output buffer size
- * @return Decoded result length in bytes if successfully decoded; \c -1 otherwise
+ * @return Decoded result length in bytes if successfully decoded; \c 0 otherwise
*/
-int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize);
+size_t base32_decode(const uint8_t* encoded, uint8_t* result, size_t bufSize);
diff --git a/applications/external/totp/lib/base64/base64.c b/applications/external/totp/lib/base64/base64.c
new file mode 100644
index 000000000..dd0a12b5e
--- /dev/null
+++ b/applications/external/totp/lib/base64/base64.c
@@ -0,0 +1,72 @@
+/*
+ * Base64 encoding/decoding (RFC1341)
+ * Copyright (c) 2005, Jouni Malinen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ */
+
+#include "base64.h"
+#include
+
+static const uint8_t dtable[] = {0x3e, 0x80, 0x80, 0x80, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38,
+ 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x80, 0x80, 0x80, 0x0, 0x80,
+ 0x80, 0x80, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
+ 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11,
+ 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33};
+
+static uint8_t get_dtable_value(uint8_t index) {
+ return (index < 43 || index > 122) ? 0x80 : dtable[index - 43];
+}
+
+uint8_t* base64_decode(const uint8_t* src, size_t len, size_t* out_len, size_t* out_size) {
+ uint8_t* out;
+ uint8_t* pos;
+ uint8_t in[4];
+ uint8_t block[4];
+ uint8_t tmp;
+ size_t i;
+ size_t count;
+ size_t olen;
+
+ count = 0;
+ for(i = 0; i < len; i++) {
+ if(get_dtable_value(src[i]) != 0x80) count++;
+ }
+
+ if(count == 0 || count % 4) return NULL;
+ olen = count / 4 * 3;
+ pos = out = malloc(olen);
+ *out_size = olen;
+ if(out == NULL) return NULL;
+ count = 0;
+ for(i = 0; i < len; i++) {
+ tmp = get_dtable_value(src[i]);
+ if(tmp == 0x80) continue;
+ in[count] = src[i];
+ block[count] = tmp;
+ count++;
+ if(count == 4) {
+ *pos++ = (block[0] << 2) | (block[1] >> 4);
+ *pos++ = (block[1] << 4) | (block[2] >> 2);
+ *pos++ = (block[2] << 6) | block[3];
+ count = 0;
+ }
+ }
+ if(pos > out) {
+ if(in[2] == '=')
+ pos -= 2;
+ else if(in[3] == '=')
+ pos--;
+ }
+ *out_len = pos - out;
+ return out;
+}
\ No newline at end of file
diff --git a/applications/external/totp/lib/base64/base64.h b/applications/external/totp/lib/base64/base64.h
new file mode 100644
index 000000000..059ec5a90
--- /dev/null
+++ b/applications/external/totp/lib/base64/base64.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+#include
+
+/**
+ * @brief Decodes Base-64 encoded bytes into plain bytes.
+ * @param src Base-64 encoded bytes
+ * @param len Base-64 encoded bytes count
+ * @param[out] out_len decoded buffer length
+ * @param[out] out_size decoded buffer allocated size
+ * @return Decoded result buffer if successfully decoded; \c NULL otherwise
+ */
+uint8_t* base64_decode(const uint8_t* src, size_t len, size_t* out_len, size_t* out_size);
\ No newline at end of file
diff --git a/applications/external/totp/lib/list/list.c b/applications/external/totp/lib/linked_list/linked_list.c
similarity index 99%
rename from applications/external/totp/lib/list/list.c
rename to applications/external/totp/lib/linked_list/linked_list.c
index f7abb6c8e..23d10c548 100644
--- a/applications/external/totp/lib/list/list.c
+++ b/applications/external/totp/lib/linked_list/linked_list.c
@@ -1,4 +1,4 @@
-#include "list.h"
+#include "linked_list.h"
ListNode* list_init_head(void* data) {
ListNode* new = malloc(sizeof(ListNode));
diff --git a/applications/external/totp/lib/list/list.h b/applications/external/totp/lib/linked_list/linked_list.h
similarity index 79%
rename from applications/external/totp/lib/list/list.h
rename to applications/external/totp/lib/linked_list/linked_list.h
index c52d4c25a..3c938e59a 100644
--- a/applications/external/totp/lib/list/list.h
+++ b/applications/external/totp/lib/linked_list/linked_list.h
@@ -84,19 +84,15 @@ ListNode* list_insert_at(ListNode* head, uint16_t index, void* data);
void list_free(ListNode* head);
#define TOTP_LIST_INIT_OR_ADD(head, item, assert) \
- do { \
- if(head == NULL) { \
- head = list_init_head(item); \
- assert(head != NULL); \
- } else { \
- assert(list_add(head, item) != NULL); \
- } \
- } while(false)
+ if(head == NULL) { \
+ head = list_init_head(item); \
+ assert(head != NULL); \
+ } else { \
+ assert(list_add(head, item) != NULL); \
+ }
#define TOTP_LIST_FOREACH(head, node, action) \
- do { \
- ListNode* node = head; \
- while(node != NULL) { \
- action node = node->next; \
- } \
- } while(false)
+ ListNode* node = head; \
+ while(node != NULL) { \
+ action node = node->next; \
+ }
diff --git a/applications/external/totp/services/config/config.c b/applications/external/totp/services/config/config.c
index ac7093dde..6d5838b28 100644
--- a/applications/external/totp/services/config/config.c
+++ b/applications/external/totp/services/config/config.c
@@ -1,45 +1,19 @@
#include "config.h"
#include
#include
-#include "../list/list.h"
+#include
#include "../../types/common.h"
#include "../../types/token_info.h"
#include "../../features_config.h"
-#include "migrations/config_migration_v1_to_v2.h"
-#include "migrations/config_migration_v2_to_v3.h"
+#include "migrations/common_migration.h"
#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/authenticator")
#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf"
-#define CONFIG_FILE_BACKUP_PATH CONFIG_FILE_PATH ".backup"
+#define CONFIG_FILE_BACKUP_BASE_PATH CONFIG_FILE_PATH ".backup"
#define CONFIG_FILE_TEMP_PATH CONFIG_FILE_PATH ".tmp"
#define CONFIG_FILE_ORIG_PATH CONFIG_FILE_PATH ".orig"
#define CONFIG_FILE_PATH_PREVIOUS EXT_PATH("apps/Misc") "/totp.conf"
-static char* token_info_get_algo_as_cstr(const TokenInfo* token_info) {
- switch(token_info->algo) {
- case SHA1:
- return TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME;
- case SHA256:
- return TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME;
- case SHA512:
- return TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME;
- default:
- break;
- }
-
- return NULL;
-}
-
-static void token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) {
- if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) {
- token_info->algo = SHA1;
- } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME) == 0) {
- token_info->algo = SHA256;
- } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME) == 0) {
- token_info->algo = SHA512;
- }
-}
-
/**
* @brief Opens storage record
* @return Storage record
@@ -65,6 +39,34 @@ static void totp_close_config_file(FlipperFormat* file) {
flipper_format_free(file);
}
+/**
+ * @brief Tries to take a config file backup
+ * @param storage storage record
+ * @return backup path if backup successfully taken; \c NULL otherwise
+ */
+static char* totp_config_file_backup_i(Storage* storage) {
+ uint8_t backup_path_size = sizeof(CONFIG_FILE_BACKUP_BASE_PATH) + 5;
+ char* backup_path = malloc(backup_path_size);
+ furi_check(backup_path != NULL);
+ memcpy(backup_path, CONFIG_FILE_BACKUP_BASE_PATH, sizeof(CONFIG_FILE_BACKUP_BASE_PATH));
+ uint16_t i = 1;
+ bool backup_file_exists;
+ while((backup_file_exists = storage_common_exists(storage, backup_path)) && i <= 9999) {
+ snprintf(backup_path, backup_path_size, CONFIG_FILE_BACKUP_BASE_PATH ".%" PRIu16, i);
+ i++;
+ }
+
+ if(backup_file_exists ||
+ !storage_common_copy(storage, CONFIG_FILE_PATH, backup_path) == FSE_OK) {
+ FURI_LOG_E(LOGGING_TAG, "Unable to take a backup");
+ free(backup_path);
+ return NULL;
+ }
+
+ FURI_LOG_I(LOGGING_TAG, "Took config file backup to %s", backup_path);
+ return backup_path;
+}
+
/**
* @brief Opens or creates TOTP application standard config file
* @param storage storage record to use
@@ -165,20 +167,21 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
furi_string_printf(
temp_str,
- " # Token hashing algorithm to use during code generation. Supported options are %s, %s and %s. If you are not use which one to use - use %s",
- TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME,
- TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME,
- TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME,
- TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
+ " # Token hashing algorithm to use during code generation. Supported options are %s, %s, %s, and %s. If you are not use which one to use - use %s",
+ TOTP_TOKEN_ALGO_SHA1_NAME,
+ TOTP_TOKEN_ALGO_SHA256_NAME,
+ TOTP_TOKEN_ALGO_SHA512_NAME,
+ TOTP_TOKEN_ALGO_STEAM_NAME,
+ TOTP_TOKEN_ALGO_SHA1_NAME);
flipper_format_write_comment(fff_data_file, temp_str);
furi_string_printf(
- temp_str, "%s: %s", TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
+ temp_str, "%s: %s", TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_TOKEN_ALGO_SHA1_NAME);
flipper_format_write_comment(fff_data_file, temp_str);
flipper_format_write_comment_cstr(fff_data_file, " ");
flipper_format_write_comment_cstr(
fff_data_file,
- "# How many digits there should be in generated code. Available options are 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6");
+ "# How many digits there should be in generated code. Available options are 5, 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6");
furi_string_printf(temp_str, "%s: 6", TOTP_CONFIG_KEY_TOKEN_DIGITS);
flipper_format_write_comment(fff_data_file, temp_str);
flipper_format_write_comment_cstr(fff_data_file, " ");
@@ -190,6 +193,13 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
flipper_format_write_comment(fff_data_file, temp_str);
flipper_format_write_comment_cstr(fff_data_file, " ");
+ flipper_format_write_comment_cstr(
+ fff_data_file,
+ "# Token input automation features (0 - None, 1 - press \"Enter\" key at the end of automation)");
+ furi_string_printf(temp_str, "%s: 0", TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES);
+ flipper_format_write_comment(fff_data_file, temp_str);
+ flipper_format_write_comment_cstr(fff_data_file, " ");
+
flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE END ===");
flipper_format_write_comment_cstr(fff_data_file, " ");
@@ -255,12 +265,26 @@ static TotpConfigFileUpdateResult
break;
}
+ tmp_uint32 = token_info->automation_features;
+ if(!flipper_format_write_uint32(
+ file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &tmp_uint32, 1)) {
+ update_result = TotpConfigFileUpdateError;
+ break;
+ }
+
update_result = TotpConfigFileUpdateSuccess;
} while(false);
return update_result;
}
+char* totp_config_file_backup() {
+ Storage* storage = totp_open_storage();
+ char* result = totp_config_file_backup_i(storage);
+ totp_close_storage();
+ return result;
+}
+
TotpConfigFileUpdateResult totp_config_file_save_new_token(const TokenInfo* token_info) {
Storage* cfg_storage = totp_open_storage();
FlipperFormat* file;
@@ -524,59 +548,46 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
CONFIG_FILE_ACTUAL_VERSION);
totp_close_config_file(fff_data_file);
- if(storage_common_stat(storage, CONFIG_FILE_BACKUP_PATH, NULL) == FSE_OK) {
- storage_simply_remove(storage, CONFIG_FILE_BACKUP_PATH);
- }
+ char* backup_path = totp_config_file_backup_i(storage);
- if(storage_common_copy(storage, CONFIG_FILE_PATH, CONFIG_FILE_BACKUP_PATH) == FSE_OK) {
- FURI_LOG_I(LOGGING_TAG, "Took config file backup to %s", CONFIG_FILE_BACKUP_PATH);
+ if(backup_path != NULL) {
if(totp_open_config_file(storage, &fff_data_file) != TotpConfigFileOpenSuccess) {
result = TotpConfigFileOpenError;
break;
}
FlipperFormat* fff_backup_data_file = flipper_format_file_alloc(storage);
- if(!flipper_format_file_open_existing(
- fff_backup_data_file, CONFIG_FILE_BACKUP_PATH)) {
+ if(!flipper_format_file_open_existing(fff_backup_data_file, backup_path)) {
flipper_format_file_close(fff_backup_data_file);
flipper_format_free(fff_backup_data_file);
result = TotpConfigFileOpenError;
break;
}
- if(file_version == 1) {
- if(totp_config_migrate_v1_to_v2(fff_data_file, fff_backup_data_file)) {
- FURI_LOG_I(LOGGING_TAG, "Applied migration from v1 to v2");
- file_version = 2;
- } else {
- FURI_LOG_W(
- LOGGING_TAG, "An error occurred during migration from v1 to v2");
- result = TotpConfigFileOpenError;
- break;
- }
- }
-
- if(file_version == 2) {
- if(totp_config_migrate_v2_to_v3(fff_data_file, fff_backup_data_file)) {
- FURI_LOG_I(LOGGING_TAG, "Applied migration from v2 to v3");
- file_version = 3;
- } else {
- FURI_LOG_W(
- LOGGING_TAG, "An error occurred during migration from v2 to v3");
- result = TotpConfigFileOpenError;
- break;
- }
+ if(totp_config_migrate_to_latest(fff_data_file, fff_backup_data_file)) {
+ FURI_LOG_I(
+ LOGGING_TAG,
+ "Applied migration to version %" PRId16,
+ CONFIG_FILE_ACTUAL_VERSION);
+ file_version = CONFIG_FILE_ACTUAL_VERSION;
+ } else {
+ FURI_LOG_W(
+ LOGGING_TAG,
+ "An error occurred during migration to version %" PRId16,
+ CONFIG_FILE_ACTUAL_VERSION);
+ result = TotpConfigFileOpenError;
+ break;
}
flipper_format_file_close(fff_backup_data_file);
flipper_format_free(fff_backup_data_file);
flipper_format_rewind(fff_data_file);
+ free(backup_path);
} else {
FURI_LOG_E(
LOGGING_TAG,
- "An error occurred during taking backup of %s into %s before migration",
- CONFIG_FILE_PATH,
- CONFIG_FILE_BACKUP_PATH);
+ "An error occurred during taking backup of %s before migration",
+ CONFIG_FILE_PATH);
result = TotpConfigFileOpenError;
break;
}
@@ -707,6 +718,7 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state)
tokenInfo,
furi_string_get_cstr(temp_str),
furi_string_size(temp_str),
+ PLAIN_TOKEN_ENCODING_BASE32,
&plugin_state->iv[0])) {
FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has plain secret", tokenInfo->name);
} else {
@@ -743,9 +755,8 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state)
}
}
- if(flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str)) {
- token_info_set_algo_from_str(tokenInfo, temp_str);
- } else {
+ if(!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str) ||
+ !token_info_set_algo_from_str(tokenInfo, temp_str)) {
tokenInfo->algo = SHA1;
}
@@ -761,6 +772,13 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state)
tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
}
+ if(flipper_format_read_uint32(
+ fff_data_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &temp_data32, 1)) {
+ tokenInfo->automation_features = temp_data32;
+ } else {
+ tokenInfo->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+ }
+
FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name);
TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
diff --git a/applications/external/totp/services/config/config.h b/applications/external/totp/services/config/config.h
index 3d325368d..dabeb373a 100644
--- a/applications/external/totp/services/config/config.h
+++ b/applications/external/totp/services/config/config.h
@@ -60,6 +60,12 @@ enum TotpConfigFileUpdateResults {
TotpConfigFileUpdateError
};
+/**
+ * @brief Tries to take a config file backup
+ * @return backup path if backup successfully taken; \c NULL otherwise
+ */
+char* totp_config_file_backup();
+
/**
* @brief Saves all the settings and tokens to an application config file
* @param plugin_state application state
diff --git a/applications/external/totp/services/config/constants.h b/applications/external/totp/services/config/constants.h
index 526179f41..7137e2374 100644
--- a/applications/external/totp/services/config/constants.h
+++ b/applications/external/totp/services/config/constants.h
@@ -1,7 +1,7 @@
#pragma once
#define CONFIG_FILE_HEADER "Flipper TOTP plugin config file"
-#define CONFIG_FILE_ACTUAL_VERSION 3
+#define CONFIG_FILE_ACTUAL_VERSION (4)
#define TOTP_CONFIG_KEY_TIMEZONE "Timezone"
#define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName"
@@ -9,12 +9,9 @@
#define TOTP_CONFIG_KEY_TOKEN_ALGO "TokenAlgo"
#define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits"
#define TOTP_CONFIG_KEY_TOKEN_DURATION "TokenDuration"
+#define TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES "TokenAutomationFeatures"
#define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto"
#define TOTP_CONFIG_KEY_BASE_IV "BaseIV"
#define TOTP_CONFIG_KEY_PINSET "PinIsSet"
#define TOTP_CONFIG_KEY_NOTIFICATION_METHOD "NotificationMethod"
#define TOTP_CONFIG_KEY_AUTOMATION_METHOD "AutomationMethod"
-
-#define TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME "sha1"
-#define TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME "sha256"
-#define TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME "sha512"
diff --git a/applications/external/totp/services/config/migrations/common_migration.c b/applications/external/totp/services/config/migrations/common_migration.c
new file mode 100644
index 000000000..073eaab12
--- /dev/null
+++ b/applications/external/totp/services/config/migrations/common_migration.c
@@ -0,0 +1,129 @@
+#include "common_migration.h"
+#include "../constants.h"
+#include "../../../types/token_info.h"
+
+bool totp_config_migrate_to_latest(
+ FlipperFormat* fff_data_file,
+ FlipperFormat* fff_backup_data_file) {
+ FuriString* temp_str = furi_string_alloc();
+ uint32_t current_version = 0;
+ bool result = false;
+ do {
+ flipper_format_write_header_cstr(
+ fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION);
+
+ if(!flipper_format_read_header(fff_backup_data_file, temp_str, ¤t_version)) {
+ break;
+ }
+
+ if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) {
+ flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str);
+ }
+
+ flipper_format_rewind(fff_backup_data_file);
+
+ if(flipper_format_read_string(
+ fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) {
+ flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str);
+ }
+
+ flipper_format_rewind(fff_backup_data_file);
+
+ if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) {
+ flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str);
+ }
+
+ flipper_format_rewind(fff_backup_data_file);
+
+ if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_PINSET, temp_str)) {
+ flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_PINSET, temp_str);
+ }
+
+ flipper_format_rewind(fff_backup_data_file);
+
+ if(flipper_format_read_string(
+ fff_backup_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str)) {
+ flipper_format_write_string(
+ fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str);
+ }
+
+ flipper_format_rewind(fff_backup_data_file);
+
+ if(flipper_format_read_string(
+ fff_backup_data_file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, temp_str)) {
+ flipper_format_write_string(
+ fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, temp_str);
+ }
+
+ flipper_format_rewind(fff_backup_data_file);
+
+ FuriString* comment_str = furi_string_alloc();
+
+ while(true) {
+ if(!flipper_format_read_string(
+ fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
+ break;
+ }
+
+ furi_string_printf(
+ comment_str, "=== BEGIN \"%s\" ===", furi_string_get_cstr(temp_str));
+ flipper_format_write_comment(fff_data_file, comment_str);
+ furi_string_printf(comment_str, "=== END \"%s\" ===", furi_string_get_cstr(temp_str));
+ flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str);
+
+ flipper_format_read_string(
+ fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
+ flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
+
+ if(current_version > 1) {
+ flipper_format_read_string(
+ fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
+ flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
+
+ flipper_format_read_string(
+ fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
+ flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
+ } else {
+ flipper_format_write_string_cstr(
+ fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_TOKEN_ALGO_SHA1_NAME);
+ const uint32_t default_digits = TOTP_6_DIGITS;
+ flipper_format_write_uint32(
+ fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1);
+ }
+
+ if(current_version > 2) {
+ flipper_format_read_string(
+ fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, temp_str);
+ flipper_format_write_string(
+ fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, temp_str);
+ } else {
+ const uint32_t default_duration = TOTP_TOKEN_DURATION_DEFAULT;
+ flipper_format_write_uint32(
+ fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &default_duration, 1);
+ }
+
+ if(current_version > 3) {
+ flipper_format_read_string(
+ fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, temp_str);
+ flipper_format_write_string(
+ fff_data_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, temp_str);
+ } else {
+ const uint32_t default_automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+ flipper_format_write_uint32(
+ fff_data_file,
+ TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES,
+ &default_automation_features,
+ 1);
+ }
+
+ flipper_format_write_comment(fff_data_file, comment_str);
+ }
+
+ furi_string_free(comment_str);
+
+ result = true;
+ } while(false);
+
+ furi_string_free(temp_str);
+ return result;
+}
\ No newline at end of file
diff --git a/applications/external/totp/services/config/migrations/config_migration_v1_to_v2.h b/applications/external/totp/services/config/migrations/common_migration.h
similarity index 54%
rename from applications/external/totp/services/config/migrations/config_migration_v1_to_v2.h
rename to applications/external/totp/services/config/migrations/common_migration.h
index 99470f04d..71defc384 100644
--- a/applications/external/totp/services/config/migrations/config_migration_v1_to_v2.h
+++ b/applications/external/totp/services/config/migrations/common_migration.h
@@ -2,6 +2,6 @@
#include
-bool totp_config_migrate_v1_to_v2(
+bool totp_config_migrate_to_latest(
FlipperFormat* fff_data_file,
- FlipperFormat* fff_backup_data_file);
+ FlipperFormat* fff_backup_data_file);
\ No newline at end of file
diff --git a/applications/external/totp/services/config/migrations/config_migration_v1_to_v2.c b/applications/external/totp/services/config/migrations/config_migration_v1_to_v2.c
deleted file mode 100644
index c2ca8f905..000000000
--- a/applications/external/totp/services/config/migrations/config_migration_v1_to_v2.c
+++ /dev/null
@@ -1,47 +0,0 @@
-#include "config_migration_v1_to_v2.h"
-#include
-#include "../constants.h"
-#include "../../../types/token_info.h"
-
-#define NEW_VERSION 2
-
-bool totp_config_migrate_v1_to_v2(
- FlipperFormat* fff_data_file,
- FlipperFormat* fff_backup_data_file) {
- flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, NEW_VERSION);
-
- FuriString* temp_str = furi_string_alloc();
-
- if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) {
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str);
- }
-
- if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) {
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str);
- }
-
- if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) {
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str);
- }
-
- while(true) {
- if(!flipper_format_read_string(
- fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
- break;
- }
-
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str);
-
- flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
-
- flipper_format_write_string_cstr(
- fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
- const uint32_t default_digits = TOTP_6_DIGITS;
- flipper_format_write_uint32(
- fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1);
- }
-
- furi_string_free(temp_str);
- return true;
-}
diff --git a/applications/external/totp/services/config/migrations/config_migration_v2_to_v3.c b/applications/external/totp/services/config/migrations/config_migration_v2_to_v3.c
deleted file mode 100644
index 995b3b02e..000000000
--- a/applications/external/totp/services/config/migrations/config_migration_v2_to_v3.c
+++ /dev/null
@@ -1,70 +0,0 @@
-#include "config_migration_v2_to_v3.h"
-#include
-#include "../constants.h"
-#include "../../../types/token_info.h"
-
-#define NEW_VERSION 3
-
-bool totp_config_migrate_v2_to_v3(
- FlipperFormat* fff_data_file,
- FlipperFormat* fff_backup_data_file) {
- flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, NEW_VERSION);
-
- FuriString* temp_str = furi_string_alloc();
-
- if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) {
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str);
- }
-
- flipper_format_rewind(fff_backup_data_file);
-
- if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) {
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str);
- }
-
- flipper_format_rewind(fff_backup_data_file);
-
- if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) {
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str);
- }
-
- flipper_format_rewind(fff_backup_data_file);
-
- if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_PINSET, temp_str)) {
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_PINSET, temp_str);
- }
-
- flipper_format_rewind(fff_backup_data_file);
-
- if(flipper_format_read_string(
- fff_backup_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str)) {
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str);
- }
-
- flipper_format_rewind(fff_backup_data_file);
-
- while(true) {
- if(!flipper_format_read_string(
- fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
- break;
- }
-
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str);
-
- flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
-
- flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
-
- flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
- flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
-
- const uint32_t default_duration = TOTP_TOKEN_DURATION_DEFAULT;
- flipper_format_write_uint32(
- fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &default_duration, 1);
- }
-
- furi_string_free(temp_str);
- return true;
-}
diff --git a/applications/external/totp/services/config/migrations/config_migration_v2_to_v3.h b/applications/external/totp/services/config/migrations/config_migration_v2_to_v3.h
deleted file mode 100644
index e3078db14..000000000
--- a/applications/external/totp/services/config/migrations/config_migration_v2_to_v3.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-
-#include
-
-bool totp_config_migrate_v2_to_v3(
- FlipperFormat* fff_data_file,
- FlipperFormat* fff_backup_data_file);
diff --git a/applications/external/totp/services/crypto/crypto.c b/applications/external/totp/services/crypto/crypto.c
index ed4775dfb..55fb1df6a 100644
--- a/applications/external/totp/services/crypto/crypto.c
+++ b/applications/external/totp/services/crypto/crypto.c
@@ -5,10 +5,10 @@
#include "../../types/common.h"
#include "memset_s.h"
-#define CRYPTO_KEY_SLOT 2
+#define CRYPTO_KEY_SLOT (2)
#define CRYPTO_VERIFY_KEY "FFF_Crypto_pass"
-#define CRYPTO_VERIFY_KEY_LENGTH 16
-#define CRYPTO_ALIGNMENT_FACTOR 16
+#define CRYPTO_VERIFY_KEY_LENGTH (16)
+#define CRYPTO_ALIGNMENT_FACTOR (16)
uint8_t* totp_crypto_encrypt(
const uint8_t* plain_data,
diff --git a/applications/external/totp/services/hmac/hmac_common.h b/applications/external/totp/services/hmac/hmac_common.h
index 9c5b5828f..0cd56ed99 100644
--- a/applications/external/totp/services/hmac/hmac_common.h
+++ b/applications/external/totp/services/hmac/hmac_common.h
@@ -9,11 +9,7 @@
#define _GLHMAC_CONCAT_(prefix, suffix) prefix##suffix
#define _GLHMAC_CONCAT(prefix, suffix) _GLHMAC_CONCAT_(prefix, suffix)
-#if GL_HMAC_NAME == 5
-#define HMAC_ALG md5
-#else
#define HMAC_ALG _GLHMAC_CONCAT(sha, GL_HMAC_NAME)
-#endif
#define GL_HMAC_CTX _GLHMAC_CONCAT(HMAC_ALG, _ctx)
#define GL_HMAC_FN _GLHMAC_CONCAT(hmac_, HMAC_ALG)
diff --git a/applications/external/totp/services/hmac/sha512.c b/applications/external/totp/services/hmac/sha512.c
index f04c4d593..b56dd0f2e 100644
--- a/applications/external/totp/services/hmac/sha512.c
+++ b/applications/external/totp/services/hmac/sha512.c
@@ -26,12 +26,9 @@
#include
#include
-#ifdef WORDS_BIGENDIAN
-#define SWAP(n) (n)
-#else
#include "byteswap.h"
+
#define SWAP(n) swap_uint64(n)
-#endif
/* This array contains the bytes used to pad the buffer to the next
128-byte boundary. */
diff --git a/applications/external/totp/services/totp/totp.c b/applications/external/totp/services/totp/totp.c
index 1a20d58c1..f6e0401e6 100644
--- a/applications/external/totp/services/totp/totp.c
+++ b/applications/external/totp/services/totp/totp.c
@@ -11,7 +11,7 @@
#include "../hmac/byteswap.h"
#include "../../lib/timezone_utils/timezone_utils.h"
-#define HMAC_MAX_SIZE 64
+#define HMAC_MAX_RESULT_SIZE HMAC_SHA512_RESULT_SIZE
/**
* @brief Generates the timeblock for a time in seconds.
@@ -29,19 +29,17 @@ uint64_t totp_timecode(uint8_t interval, uint64_t for_time) {
/**
* @brief Generates an OTP (One Time Password)
* @param algo hashing algorithm to be used
- * @param digits desired TOTP code length
* @param plain_secret plain token secret
* @param plain_secret_length plain token secret length
* @param input input data for OTP code generation
* @return OTP code if code was successfully generated; 0 otherwise
*/
-uint32_t otp_generate(
+uint64_t otp_generate(
TOTP_ALGO algo,
- uint8_t digits,
const uint8_t* plain_secret,
size_t plain_secret_length,
uint64_t input) {
- uint8_t hmac[HMAC_MAX_SIZE] = {0};
+ uint8_t hmac[HMAC_MAX_RESULT_SIZE] = {0};
uint64_t input_swapped = swap_uint64(input);
@@ -55,14 +53,12 @@ uint32_t otp_generate(
uint64_t i_code =
((hmac[offset] & 0x7F) << 24 | (hmac[offset + 1] & 0xFF) << 16 |
(hmac[offset + 2] & 0xFF) << 8 | (hmac[offset + 3] & 0xFF));
- i_code %= (uint64_t)pow(10, digits);
return i_code;
}
-uint32_t totp_at(
+uint64_t totp_at(
TOTP_ALGO algo,
- uint8_t digits,
const uint8_t* plain_secret,
size_t plain_secret_length,
uint64_t for_time,
@@ -71,11 +67,7 @@ uint32_t totp_at(
uint64_t for_time_adjusted =
timezone_offset_apply(for_time, timezone_offset_from_hours(timezone));
return otp_generate(
- algo,
- digits,
- plain_secret,
- plain_secret_length,
- totp_timecode(interval, for_time_adjusted));
+ algo, plain_secret, plain_secret_length, totp_timecode(interval, for_time_adjusted));
}
static int totp_algo_sha1(
diff --git a/applications/external/totp/services/totp/totp.h b/applications/external/totp/services/totp/totp.h
index 3f45a0223..d578f6ea9 100644
--- a/applications/external/totp/services/totp/totp.h
+++ b/applications/external/totp/services/totp/totp.h
@@ -39,7 +39,6 @@ extern const TOTP_ALGO TOTP_ALGO_SHA512;
/**
* @brief Generates a OTP key using the totp algorithm.
* @param algo hashing algorithm to be used
- * @param digits desired TOTP code length
* @param plain_secret plain token secret
* @param plain_secret_length plain token secret length
* @param for_time the time the generated key will be created for
@@ -47,9 +46,8 @@ extern const TOTP_ALGO TOTP_ALGO_SHA512;
* @param interval token lifetime in seconds
* @return TOTP code if code was successfully generated; 0 otherwise
*/
-uint32_t totp_at(
+uint64_t totp_at(
TOTP_ALGO algo,
- uint8_t digits,
const uint8_t* plain_secret,
size_t plain_secret_length,
uint64_t for_time,
diff --git a/applications/external/totp/totp_app.c b/applications/external/totp/totp_app.c
index e5c101e5f..8a014628d 100644
--- a/applications/external/totp/totp_app.c
+++ b/applications/external/totp/totp_app.c
@@ -20,7 +20,7 @@
#include "services/crypto/crypto.h"
#include "cli/cli.h"
-#define IDLE_TIMEOUT 60000
+#define IDLE_TIMEOUT (60000)
static void render_callback(Canvas* const canvas, void* ctx) {
furi_assert(ctx);
@@ -96,6 +96,7 @@ static bool totp_plugin_state_init(PluginState* const plugin_state) {
plugin_state->gui = furi_record_open(RECORD_GUI);
plugin_state->notification_app = furi_record_open(RECORD_NOTIFICATION);
plugin_state->dialogs_app = furi_record_open(RECORD_DIALOGS);
+ memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
if(totp_config_file_load_base(plugin_state) != TotpConfigFileOpenSuccess) {
totp_dialogs_config_loading_error(plugin_state);
@@ -161,7 +162,7 @@ int32_t totp_app() {
}
TotpCliContext* cli_context = totp_cli_register_command_handler(plugin_state, event_queue);
- totp_scene_director_init_scenes(plugin_state);
+
if(!totp_activate_initial_scene(plugin_state)) {
FURI_LOG_E(LOGGING_TAG, "An error ocurred during activating initial scene\r\n");
totp_plugin_state_free(plugin_state);
@@ -206,7 +207,6 @@ int32_t totp_app() {
totp_cli_unregister_command_handler(cli_context);
totp_scene_director_deactivate_active_scene(plugin_state);
- totp_scene_director_dispose(plugin_state);
view_port_enabled_set(view_port, false);
gui_remove_view_port(plugin_state->gui, view_port);
diff --git a/applications/external/totp/types/plugin_state.h b/applications/external/totp/types/plugin_state.h
index 59a218ce3..cacf68426 100644
--- a/applications/external/totp/types/plugin_state.h
+++ b/applications/external/totp/types/plugin_state.h
@@ -4,7 +4,7 @@
#include
#include
#include "../features_config.h"
-#include "../lib/list/list.h"
+#include
#include "../ui/totp_scenes_enum.h"
#include "notification_method.h"
#include "automation_method.h"
@@ -12,7 +12,7 @@
#include "../workers/bt_type_code/bt_type_code.h"
#endif
-#define TOTP_IV_SIZE 16
+#define TOTP_IV_SIZE (16)
/**
* @brief Application state structure
diff --git a/applications/external/totp/types/token_info.c b/applications/external/totp/types/token_info.c
index b69979cc7..b8196c56b 100644
--- a/applications/external/totp/types/token_info.c
+++ b/applications/external/totp/types/token_info.c
@@ -1,11 +1,11 @@
-#include
-#include
#include "token_info.h"
-#include "stdlib.h"
+#include
+#include
+#include
+#include
+#include
#include "common.h"
-#include "../lib/base32/base32.h"
#include "../services/crypto/crypto.h"
-#include "../lib/polyfills/memset_s.h"
TokenInfo* token_info_alloc() {
TokenInfo* tokenInfo = malloc(sizeof(TokenInfo));
@@ -13,6 +13,7 @@ TokenInfo* token_info_alloc() {
tokenInfo->algo = SHA1;
tokenInfo->digits = TOTP_6_DIGITS;
tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
+ tokenInfo->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
return tokenInfo;
}
@@ -25,15 +26,32 @@ void token_info_free(TokenInfo* token_info) {
bool token_info_set_secret(
TokenInfo* token_info,
- const char* base32_token_secret,
+ const char* plain_token_secret,
size_t token_secret_length,
+ PlainTokenSecretEncoding plain_token_secret_encoding,
const uint8_t* iv) {
if(token_secret_length == 0) return false;
+ uint8_t* plain_secret;
+ size_t plain_secret_length;
+ size_t plain_secret_size;
+ if(plain_token_secret_encoding == PLAIN_TOKEN_ENCODING_BASE32) {
+ plain_secret_size = token_secret_length;
+ plain_secret = malloc(plain_secret_size);
+ furi_check(plain_secret != NULL);
+ plain_secret_length =
+ base32_decode((const uint8_t*)plain_token_secret, plain_secret, plain_secret_size);
+ } else if(plain_token_secret_encoding == PLAIN_TOKEN_ENCODING_BASE64) {
+ plain_secret_length = 0;
+ plain_secret = base64_decode(
+ (const uint8_t*)plain_token_secret,
+ token_secret_length,
+ &plain_secret_length,
+ &plain_secret_size);
+ furi_check(plain_secret != NULL);
+ } else {
+ return false;
+ }
- uint8_t* plain_secret = malloc(token_secret_length);
- furi_check(plain_secret != NULL);
- int plain_secret_length =
- base32_decode((const uint8_t*)base32_token_secret, plain_secret, token_secret_length);
bool result;
if(plain_secret_length > 0) {
token_info->token =
@@ -43,13 +61,16 @@ bool token_info_set_secret(
result = false;
}
- memset_s(plain_secret, token_secret_length, 0, token_secret_length);
+ memset_s(plain_secret, plain_secret_size, 0, plain_secret_size);
free(plain_secret);
return result;
}
bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) {
switch(digits) {
+ case 5:
+ token_info->digits = TOTP_5_DIGITS;
+ return true;
case 6:
token_info->digits = TOTP_6_DIGITS;
return true;
@@ -70,4 +91,85 @@ bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration) {
}
return false;
+}
+
+bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) {
+ if(furi_string_cmpi_str(str, TOTP_TOKEN_ALGO_SHA1_NAME) == 0) {
+ token_info->algo = SHA1;
+ return true;
+ }
+
+ if(furi_string_cmpi_str(str, TOTP_TOKEN_ALGO_SHA256_NAME) == 0) {
+ token_info->algo = SHA256;
+ return true;
+ }
+
+ if(furi_string_cmpi_str(str, TOTP_TOKEN_ALGO_SHA512_NAME) == 0) {
+ token_info->algo = SHA512;
+ return true;
+ }
+
+ if(furi_string_cmpi_str(str, TOTP_TOKEN_ALGO_STEAM_NAME) == 0) {
+ token_info->algo = STEAM;
+ return true;
+ }
+
+ return false;
+}
+
+char* token_info_get_algo_as_cstr(const TokenInfo* token_info) {
+ switch(token_info->algo) {
+ case SHA1:
+ return TOTP_TOKEN_ALGO_SHA1_NAME;
+ case SHA256:
+ return TOTP_TOKEN_ALGO_SHA256_NAME;
+ case SHA512:
+ return TOTP_TOKEN_ALGO_SHA512_NAME;
+ case STEAM:
+ return TOTP_TOKEN_ALGO_STEAM_NAME;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+bool token_info_set_automation_feature_from_str(TokenInfo* token_info, const FuriString* str) {
+ if(furi_string_cmpi_str(str, TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME) == 0) {
+ token_info->automation_features |= TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END;
+ return true;
+ }
+
+ if(furi_string_cmpi_str(str, TOTP_TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME) == 0) {
+ token_info->automation_features |= TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END;
+ return true;
+ }
+
+ if(furi_string_cmpi_str(str, TOTP_TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER_NAME) == 0) {
+ token_info->automation_features |= TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER;
+ return true;
+ }
+
+ if(furi_string_cmpi_str(str, TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME) == 0) {
+ token_info->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+ return true;
+ }
+
+ return false;
+}
+
+TokenInfo* token_info_clone(const TokenInfo* src) {
+ TokenInfo* clone = token_info_alloc();
+ memcpy(clone, src, sizeof(TokenInfo));
+
+ clone->token = malloc(src->token_length);
+ furi_check(clone->token != NULL);
+ memcpy(clone->token, src->token, src->token_length);
+
+ int name_length = strnlen(src->name, TOTP_TOKEN_MAX_LENGTH);
+ clone->name = malloc(name_length + 1);
+ furi_check(clone->name != NULL);
+ strlcpy(clone->name, src->name, name_length + 1);
+
+ return clone;
}
\ No newline at end of file
diff --git a/applications/external/totp/types/token_info.h b/applications/external/totp/types/token_info.h
index d04564c42..9ca3528dc 100644
--- a/applications/external/totp/types/token_info.h
+++ b/applications/external/totp/types/token_info.h
@@ -1,11 +1,29 @@
#pragma once
#include
+#include
+#include
-#define TOTP_TOKEN_DURATION_DEFAULT 30
+#define TOTP_TOKEN_DURATION_DEFAULT (30)
+
+#define TOTP_TOKEN_ALGO_SHA1_NAME "sha1"
+#define TOTP_TOKEN_ALGO_STEAM_NAME "steam"
+#define TOTP_TOKEN_ALGO_SHA256_NAME "sha256"
+#define TOTP_TOKEN_ALGO_SHA512_NAME "sha512"
+#define TOTP_TOKEN_MAX_LENGTH (255)
+
+#define PLAIN_TOKEN_ENCODING_BASE32_NAME "base32"
+#define PLAIN_TOKEN_ENCODING_BASE64_NAME "base64"
+
+#define TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME "none"
+#define TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME "enter"
+#define TOTP_TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME "tab"
+#define TOTP_TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER_NAME "slower"
typedef uint8_t TokenHashAlgo;
typedef uint8_t TokenDigitsCount;
+typedef uint8_t TokenAutomationFeature;
+typedef uint8_t PlainTokenSecretEncoding;
/**
* @brief Hashing algorithm to be used to generate token
@@ -24,13 +42,23 @@ enum TokenHashAlgos {
/**
* @brief SHA512 hashing algorithm
*/
- SHA512
+ SHA512,
+
+ /**
+ * @brief Algorithm used by Steam (Valve)
+ */
+ STEAM
};
/**
* @brief Token digits count to be generated.
*/
enum TokenDigitsCounts {
+ /**
+ * @brief 6 digits
+ */
+ TOTP_5_DIGITS = 5,
+
/**
* @brief 6 digits
*/
@@ -42,7 +70,48 @@ enum TokenDigitsCounts {
TOTP_8_DIGITS = 8
};
-#define TOTP_TOKEN_DIGITS_MAX_COUNT 8
+/**
+ * @brief Token automation features.
+ */
+enum TokenAutomationFeatures {
+ /**
+ * @brief No features enabled
+ */
+ TOKEN_AUTOMATION_FEATURE_NONE = 0b000,
+
+ /**
+ * @brief Press "Enter" key at the end as a part of token input automation
+ */
+ TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END = 0b001,
+
+ /**
+ * @brief Press "Tab" key at the end as a part of token input automation
+ */
+ TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END = 0b010,
+
+ /**
+ * @brief Press keys slower and wait longer between keystrokes
+ */
+ TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER = 0b100
+};
+
+/**
+ * @brief Plain token secret encodings.
+ */
+enum PlainTokenSecretEncodings {
+
+ /**
+ * @brief Base32 encoding
+ */
+ PLAIN_TOKEN_ENCODING_BASE32 = 0,
+
+ /**
+ * @brief Base64 encoding
+ */
+ PLAIN_TOKEN_ENCODING_BASE64 = 1
+};
+
+#define TOTP_TOKEN_DIGITS_MAX_COUNT (8)
/**
* @brief TOTP token information
@@ -77,6 +146,11 @@ typedef struct {
* @brief Desired TOTP token duration in seconds
*/
uint8_t duration;
+
+ /**
+ * @brief Token input automation features
+ */
+ TokenAutomationFeature automation_features;
} TokenInfo;
/**
@@ -96,13 +170,15 @@ void token_info_free(TokenInfo* token_info);
* @param token_info instance where secret should be updated
* @param base32_token_secret plain token secret in Base32 format
* @param token_secret_length plain token secret length
+ * @param plain_token_secret_encoding plain token secret encoding
* @param iv initialization vecor (IV) to be used for encryption
* @return \c true if token successfully set; \c false otherwise
*/
bool token_info_set_secret(
TokenInfo* token_info,
- const char* base32_token_secret,
+ const char* plain_token_secret,
size_t token_secret_length,
+ PlainTokenSecretEncoding plain_token_secret_encoding,
const uint8_t* iv);
/**
@@ -120,3 +196,33 @@ bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits);
* @return \c true if token duration has been updated; \c false otherwise
*/
bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration);
+
+/**
+ * @brief Sets token hashing algorithm from \c str value
+ * @param token_info instance whichs token hashing algorithm should be updated
+ * @param str desired token algorithm
+ * @return \c true if token hahsing algorithm has been updated; \c false otherwise
+ */
+bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str);
+
+/**
+ * @brief Gets token hahsing algorithm name as C-string
+ * @param token_info instance which token hahsing algorithm name should be returned
+ * @return token hashing algorithm name as C-string
+ */
+char* token_info_get_algo_as_cstr(const TokenInfo* token_info);
+
+/**
+ * @brief Sets token automation feature from \c str value
+ * @param token_info instance whichs token automation feature should be updated
+ * @param str desired token automation feature
+ * @return \c true if token automation feature has been set; \c false otherwise
+ */
+bool token_info_set_automation_feature_from_str(TokenInfo* token_info, const FuriString* str);
+
+/**
+ * @brief Clones \c TokenInfo instance
+ * @param src instance to clone
+ * @return cloned instance
+ */
+TokenInfo* token_info_clone(const TokenInfo* src);
\ No newline at end of file
diff --git a/applications/external/totp/ui/constants.h b/applications/external/totp/ui/constants.h
index 9caf90c4e..81c2edf92 100644
--- a/applications/external/totp/ui/constants.h
+++ b/applications/external/totp/ui/constants.h
@@ -1,6 +1,6 @@
#pragma once
-#define SCREEN_WIDTH 128
-#define SCREEN_HEIGHT 64
+#define SCREEN_WIDTH (128)
+#define SCREEN_HEIGHT (64)
#define SCREEN_WIDTH_CENTER (SCREEN_WIDTH >> 1)
#define SCREEN_HEIGHT_CENTER (SCREEN_HEIGHT >> 1)
diff --git a/applications/external/totp/ui/fonts/font_info.h b/applications/external/totp/ui/fonts/font_info.h
new file mode 100644
index 000000000..86c131ec9
--- /dev/null
+++ b/applications/external/totp/ui/fonts/font_info.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/* GENERATED BY https://github.com/pavius/the-dot-factory */
+
+#include
+
+// This structure describes a single character's display information
+typedef struct {
+ const uint8_t width; // width, in bits (or pixels), of the character
+ const uint16_t
+ offset; // offset of the character's bitmap, in bytes, into the the FONT_INFO's data array
+
+} FONT_CHAR_INFO;
+
+// Describes a single font
+typedef struct {
+ const uint8_t height; // height, in pages (8 pixels), of the font's characters
+ const uint8_t startChar; // the first character in the font (e.g. in charInfo and data)
+ const uint8_t endChar; // the last character in the font
+ const uint8_t spacePixels; // number of pixels that a space character takes up
+ const FONT_CHAR_INFO* charInfo; // pointer to array of char information
+ const uint8_t* data; // pointer to generated array of character visual representation
+
+} FONT_INFO;
\ No newline at end of file
diff --git a/applications/external/totp/ui/fonts/mode_nine/mode_nine.c b/applications/external/totp/ui/fonts/mode_nine/mode_nine.c
new file mode 100644
index 000000000..ca2191789
--- /dev/null
+++ b/applications/external/totp/ui/fonts/mode_nine/mode_nine.c
@@ -0,0 +1,941 @@
+#include "mode_nine.h"
+#include
+
+/* GENERATED BY https://github.com/pavius/the-dot-factory */
+
+/*
+** Font data for ModeNine 15pt
+*/
+
+/* Character bitmaps for ModeNine 15pt */
+const uint8_t modeNine_15ptBitmaps[] = {
+ /* @0 '-' (10 pixels wide) */
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+
+ /* @28 '0' (10 pixels wide) */
+ 0xFC,
+ 0x00,
+ 0xFE,
+ 0x01,
+ 0x87,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x33,
+ 0x03,
+ 0x33,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xFE,
+ 0x01,
+ 0xFC,
+ 0x00,
+
+ /* @56 '1' (10 pixels wide) */
+ 0x30,
+ 0x00,
+ 0x38,
+ 0x00,
+ 0x3C,
+ 0x00,
+ 0x3C,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0xFC,
+ 0x00,
+ 0xFC,
+ 0x00,
+
+ /* @84 '2' (10 pixels wide) */
+ 0xFC,
+ 0x00,
+ 0xFE,
+ 0x01,
+ 0x87,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x80,
+ 0x03,
+ 0xFC,
+ 0x01,
+ 0xFE,
+ 0x00,
+ 0x07,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0xFF,
+ 0x03,
+ 0xFF,
+ 0x03,
+
+ /* @112 '3' (10 pixels wide) */
+ 0xFF,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0x80,
+ 0x03,
+ 0xC0,
+ 0x01,
+ 0xE0,
+ 0x00,
+ 0x70,
+ 0x00,
+ 0xF8,
+ 0x00,
+ 0xFC,
+ 0x01,
+ 0x80,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xFE,
+ 0x01,
+ 0xFC,
+ 0x00,
+
+ /* @140 '4' (10 pixels wide) */
+ 0xE0,
+ 0x00,
+ 0xF0,
+ 0x00,
+ 0xF8,
+ 0x00,
+ 0xDC,
+ 0x00,
+ 0xCE,
+ 0x00,
+ 0xC7,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0xC3,
+ 0x00,
+ 0xFF,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0xC0,
+ 0x00,
+ 0xC0,
+ 0x00,
+ 0xC0,
+ 0x00,
+ 0xC0,
+ 0x00,
+
+ /* @168 '5' (10 pixels wide) */
+ 0xFF,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0xFF,
+ 0x00,
+ 0xFF,
+ 0x01,
+ 0x80,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xFE,
+ 0x01,
+ 0xFC,
+ 0x00,
+
+ /* @196 '6' (10 pixels wide) */
+ 0xF0,
+ 0x00,
+ 0xFC,
+ 0x00,
+ 0x0E,
+ 0x00,
+ 0x06,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0xFF,
+ 0x00,
+ 0xFF,
+ 0x01,
+ 0x83,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xFE,
+ 0x01,
+ 0xFC,
+ 0x00,
+
+ /* @224 '7' (10 pixels wide) */
+ 0xFF,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x80,
+ 0x01,
+ 0xC0,
+ 0x01,
+ 0xE0,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x18,
+ 0x00,
+ 0x1C,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x0C,
+ 0x00,
+ 0x0C,
+ 0x00,
+
+ /* @252 '8' (10 pixels wide) */
+ 0xFC,
+ 0x00,
+ 0xFE,
+ 0x01,
+ 0x87,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xFE,
+ 0x01,
+ 0xFE,
+ 0x01,
+ 0x87,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xFE,
+ 0x01,
+ 0xFC,
+ 0x00,
+
+ /* @280 '9' (10 pixels wide) */
+ 0xFC,
+ 0x00,
+ 0xFE,
+ 0x01,
+ 0x87,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x07,
+ 0x03,
+ 0xFE,
+ 0x03,
+ 0xFC,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x80,
+ 0x01,
+ 0xC0,
+ 0x01,
+ 0xFC,
+ 0x00,
+ 0x3C,
+ 0x00,
+
+ /* @308 'B' (10 pixels wide) */
+ 0xFF,
+ 0x00,
+ 0xFF,
+ 0x01,
+ 0x83,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x83,
+ 0x03,
+ 0xFF,
+ 0x01,
+ 0xFF,
+ 0x01,
+ 0x83,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x83,
+ 0x03,
+ 0xFF,
+ 0x01,
+ 0xFF,
+ 0x00,
+
+ /* @336 'C' (10 pixels wide) */
+ 0xFC,
+ 0x00,
+ 0xFE,
+ 0x01,
+ 0x87,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xFE,
+ 0x01,
+ 0xFC,
+ 0x00,
+
+ /* @364 'D' (10 pixels wide) */
+ 0xFF,
+ 0x00,
+ 0xFF,
+ 0x01,
+ 0x83,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x83,
+ 0x03,
+ 0xFF,
+ 0x01,
+ 0xFF,
+ 0x00,
+
+ /* @392 'F' (10 pixels wide) */
+ 0xFF,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0xFF,
+ 0x00,
+ 0xFF,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+
+ /* @420 'G' (10 pixels wide) */
+ 0xFC,
+ 0x00,
+ 0xFE,
+ 0x01,
+ 0x87,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0xC3,
+ 0x03,
+ 0xC3,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x07,
+ 0x03,
+ 0xFE,
+ 0x03,
+ 0xFC,
+ 0x03,
+
+ /* @448 'H' (10 pixels wide) */
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+
+ /* @476 'J' (10 pixels wide) */
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xFE,
+ 0x01,
+ 0xFC,
+ 0x00,
+
+ /* @504 'K' (10 pixels wide) */
+ 0x83,
+ 0x03,
+ 0xC3,
+ 0x01,
+ 0xE3,
+ 0x00,
+ 0x73,
+ 0x00,
+ 0x3B,
+ 0x00,
+ 0x1F,
+ 0x00,
+ 0x0F,
+ 0x00,
+ 0x0F,
+ 0x00,
+ 0x1F,
+ 0x00,
+ 0x3B,
+ 0x00,
+ 0x73,
+ 0x00,
+ 0xE3,
+ 0x00,
+ 0xC3,
+ 0x01,
+ 0x83,
+ 0x03,
+
+ /* @532 'M' (10 pixels wide) */
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xCF,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0x7B,
+ 0x03,
+ 0x33,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+
+ /* @560 'N' (10 pixels wide) */
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x07,
+ 0x03,
+ 0x0F,
+ 0x03,
+ 0x1F,
+ 0x03,
+ 0x3B,
+ 0x03,
+ 0x73,
+ 0x03,
+ 0xE3,
+ 0x03,
+ 0xC3,
+ 0x03,
+ 0x83,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+
+ /* @588 'P' (10 pixels wide) */
+ 0xFF,
+ 0x00,
+ 0xFF,
+ 0x01,
+ 0x83,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x83,
+ 0x03,
+ 0xFF,
+ 0x01,
+ 0xFF,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+ 0x03,
+ 0x00,
+
+ /* @616 'Q' (10 pixels wide) */
+ 0xFC,
+ 0x00,
+ 0xFE,
+ 0x01,
+ 0x87,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x33,
+ 0x03,
+ 0x73,
+ 0x03,
+ 0xE7,
+ 0x03,
+ 0xFE,
+ 0x01,
+ 0xFC,
+ 0x03,
+
+ /* @644 'R' (10 pixels wide) */
+ 0xFF,
+ 0x00,
+ 0xFF,
+ 0x01,
+ 0x83,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x83,
+ 0x03,
+ 0xFF,
+ 0x01,
+ 0xFF,
+ 0x00,
+ 0x1F,
+ 0x00,
+ 0x3B,
+ 0x00,
+ 0x73,
+ 0x00,
+ 0xE3,
+ 0x00,
+ 0xC3,
+ 0x01,
+ 0x83,
+ 0x03,
+
+ /* @672 'T' (10 pixels wide) */
+ 0xFF,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+
+ /* @700 'V' (10 pixels wide) */
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x86,
+ 0x01,
+ 0x86,
+ 0x01,
+ 0xCC,
+ 0x00,
+ 0xCC,
+ 0x00,
+ 0x78,
+ 0x00,
+ 0x78,
+ 0x00,
+ 0x30,
+ 0x00,
+
+ /* @728 'W' (10 pixels wide) */
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x33,
+ 0x03,
+ 0x33,
+ 0x03,
+ 0x33,
+ 0x03,
+ 0x33,
+ 0x03,
+ 0x33,
+ 0x03,
+ 0x33,
+ 0x03,
+ 0xFF,
+ 0x03,
+ 0xFE,
+ 0x01,
+
+ /* @756 'X' (10 pixels wide) */
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xCE,
+ 0x01,
+ 0xFC,
+ 0x00,
+ 0xFC,
+ 0x00,
+ 0xCE,
+ 0x01,
+ 0x87,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+
+ /* @784 'Y' (10 pixels wide) */
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x03,
+ 0x87,
+ 0x03,
+ 0xCE,
+ 0x01,
+ 0xFC,
+ 0x00,
+ 0x78,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+ 0x30,
+ 0x00,
+};
+
+/* Character descriptors for ModeNine 15pt */
+/* { [Char width in bits], [Offset into modeNine_15ptCharBitmaps in bytes] } */
+const FONT_CHAR_INFO modeNine_15ptDescriptors[] = {
+ {10, 0}, /* - */
+ {0, 0}, /* . */
+ {0, 0}, /* / */
+ {10, 28}, /* 0 */
+ {10, 56}, /* 1 */
+ {10, 84}, /* 2 */
+ {10, 112}, /* 3 */
+ {10, 140}, /* 4 */
+ {10, 168}, /* 5 */
+ {10, 196}, /* 6 */
+ {10, 224}, /* 7 */
+ {10, 252}, /* 8 */
+ {10, 280}, /* 9 */
+ {0, 0}, /* : */
+ {0, 0}, /* ; */
+ {0, 0}, /* < */
+ {0, 0}, /* = */
+ {0, 0}, /* > */
+ {0, 0}, /* ? */
+ {0, 0}, /* @ */
+ {0, 0}, /* A */
+ {10, 308}, /* B */
+ {10, 336}, /* C */
+ {10, 364}, /* D */
+ {0, 0}, /* E */
+ {10, 392}, /* F */
+ {10, 420}, /* G */
+ {10, 448}, /* H */
+ {0, 0}, /* I */
+ {10, 476}, /* J */
+ {10, 504}, /* K */
+ {0, 0}, /* L */
+ {10, 532}, /* M */
+ {10, 560}, /* N */
+ {0, 0}, /* O */
+ {10, 588}, /* P */
+ {10, 616}, /* Q */
+ {10, 644}, /* R */
+ {0, 0}, /* S */
+ {10, 672}, /* T */
+ {0, 0}, /* U */
+ {10, 700}, /* V */
+ {10, 728}, /* W */
+ {10, 756}, /* X */
+ {10, 784}, /* Y */
+};
+
+/* Font information for ModeNine 15pt */
+const FONT_INFO modeNine_15ptFontInfo = {
+ 14, /* Character height */
+ '-', /* Start character */
+ 'Y', /* End character */
+ 2, /* Width, in pixels, of space character */
+ modeNine_15ptDescriptors, /* Character descriptor array */
+ modeNine_15ptBitmaps, /* Character bitmap array */
+};
diff --git a/applications/external/totp/ui/fonts/mode_nine/mode_nine.h b/applications/external/totp/ui/fonts/mode_nine/mode_nine.h
new file mode 100644
index 000000000..516e261f8
--- /dev/null
+++ b/applications/external/totp/ui/fonts/mode_nine/mode_nine.h
@@ -0,0 +1,8 @@
+#pragma once
+
+/* GENERATED BY https://github.com/pavius/the-dot-factory */
+
+#include "../font_info.h"
+
+/* Font data for ModeNine 15pt */
+extern const FONT_INFO modeNine_15ptFontInfo;
diff --git a/applications/external/totp/ui/scene_director.c b/applications/external/totp/ui/scene_director.c
index c77e88ab4..c6f709006 100644
--- a/applications/external/totp/ui/scene_director.c
+++ b/applications/external/totp/ui/scene_director.c
@@ -62,14 +62,6 @@ void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state
}
}
-void totp_scene_director_init_scenes(PluginState* const plugin_state) {
- totp_scene_authenticate_init(plugin_state);
- totp_scene_generate_token_init(plugin_state);
- totp_scene_add_new_token_init(plugin_state);
- totp_scene_token_menu_init(plugin_state);
- totp_scene_app_settings_init(plugin_state);
-}
-
void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state) {
switch(plugin_state->current_scene) {
case TotpSceneGenerateToken:
@@ -94,14 +86,6 @@ void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_
}
}
-void totp_scene_director_dispose(const PluginState* const plugin_state) {
- totp_scene_generate_token_free(plugin_state);
- totp_scene_authenticate_free(plugin_state);
- totp_scene_add_new_token_free(plugin_state);
- totp_scene_token_menu_free(plugin_state);
- totp_scene_app_settings_free(plugin_state);
-}
-
bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state) {
bool processing = true;
switch(plugin_state->current_scene) {
diff --git a/applications/external/totp/ui/scene_director.h b/applications/external/totp/ui/scene_director.h
index 541a63f1c..71709978f 100644
--- a/applications/external/totp/ui/scene_director.h
+++ b/applications/external/totp/ui/scene_director.h
@@ -22,12 +22,6 @@ void totp_scene_director_activate_scene(
*/
void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state);
-/**
- * @brief Initializes all the available scenes
- * @param plugin_state application state
- */
-void totp_scene_director_init_scenes(PluginState* const plugin_state);
-
/**
* @brief Renders current scene
* @param canvas canvas to render at
@@ -35,12 +29,6 @@ void totp_scene_director_init_scenes(PluginState* const plugin_state);
*/
void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state);
-/**
- * @brief Disposes all the available scenes
- * @param plugin_state application state
- */
-void totp_scene_director_dispose(const PluginState* const plugin_state);
-
/**
* @brief Handles application event for the current scene
* @param event event to be handled
diff --git a/applications/external/totp/ui/scenes/add_new_token/totp_input_text.h b/applications/external/totp/ui/scenes/add_new_token/totp_input_text.h
index 145e8904d..ffbfde692 100644
--- a/applications/external/totp/ui/scenes/add_new_token/totp_input_text.h
+++ b/applications/external/totp/ui/scenes/add_new_token/totp_input_text.h
@@ -6,6 +6,8 @@
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
+#define INPUT_BUFFER_SIZE (255)
+
typedef struct {
char* user_input;
size_t user_input_length;
@@ -20,8 +22,6 @@ typedef struct {
void* callback_data;
} InputTextSceneContext;
-#define INPUT_BUFFER_SIZE 255
-
typedef struct {
TextInput* text_input;
View* text_input_view;
diff --git a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c
index f35f0238b..3f8e4fd93 100644
--- a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c
+++ b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.c
@@ -4,17 +4,17 @@
#include "../../scene_director.h"
#include "totp_input_text.h"
#include "../../../types/token_info.h"
-#include "../../../lib/list/list.h"
+#include
#include "../../../services/config/config.h"
#include "../../ui_controls.h"
#include "../../common_dialogs.h"
-#include "../../../lib/roll_value/roll_value.h"
+#include
#include "../../../types/nullable.h"
#include "../generate_token/totp_scene_generate_token.h"
-char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512"};
-char* TOKEN_DIGITS_TEXT_LIST[] = {"6 digits", "8 digits"};
-TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = {TOTP_6_DIGITS, TOTP_8_DIGITS};
+char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512", "Steam"};
+char* TOKEN_DIGITS_TEXT_LIST[] = {"5 digits", "6 digits", "8 digits"};
+TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = {TOTP_5_DIGITS, TOTP_6_DIGITS, TOTP_8_DIGITS};
typedef enum {
TokenNameTextBox,
@@ -44,10 +44,6 @@ typedef struct {
FuriString* duration_text;
} SceneState;
-void totp_scene_add_new_token_init(const PluginState* plugin_state) {
- UNUSED(plugin_state);
-}
-
static void on_token_name_user_comitted(InputTextSceneCallbackResult* result) {
SceneState* scene_state = result->callback_data;
free(scene_state->token_name);
@@ -95,6 +91,8 @@ void totp_scene_add_new_token_activate(
scene_state->screen_y_offset = 0;
+ scene_state->digits_count_index = 1;
+
scene_state->input_state = NULL;
scene_state->duration = TOTP_TOKEN_DURATION_DEFAULT;
scene_state->duration_text = furi_string_alloc();
@@ -216,10 +214,10 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
break;
case InputKeyRight:
if(scene_state->selected_control == TokenAlgoSelect) {
- totp_roll_value_uint8_t(&scene_state->algo, 1, SHA1, SHA512, RollOverflowBehaviorRoll);
+ totp_roll_value_uint8_t(&scene_state->algo, 1, SHA1, STEAM, RollOverflowBehaviorRoll);
} else if(scene_state->selected_control == TokenLengthSelect) {
totp_roll_value_uint8_t(
- &scene_state->digits_count_index, 1, 0, 1, RollOverflowBehaviorRoll);
+ &scene_state->digits_count_index, 1, 0, 2, RollOverflowBehaviorRoll);
} else if(scene_state->selected_control == TokenDurationSelect) {
totp_roll_value_uint8_t(&scene_state->duration, 15, 15, 255, RollOverflowBehaviorStop);
update_duration_text(scene_state);
@@ -227,11 +225,10 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
break;
case InputKeyLeft:
if(scene_state->selected_control == TokenAlgoSelect) {
- totp_roll_value_uint8_t(
- &scene_state->algo, -1, SHA1, SHA512, RollOverflowBehaviorRoll);
+ totp_roll_value_uint8_t(&scene_state->algo, -1, SHA1, STEAM, RollOverflowBehaviorRoll);
} else if(scene_state->selected_control == TokenLengthSelect) {
totp_roll_value_uint8_t(
- &scene_state->digits_count_index, -1, 0, 1, RollOverflowBehaviorRoll);
+ &scene_state->digits_count_index, -1, 0, 2, RollOverflowBehaviorRoll);
} else if(scene_state->selected_control == TokenDurationSelect) {
totp_roll_value_uint8_t(
&scene_state->duration, -15, 15, 255, RollOverflowBehaviorStop);
@@ -268,6 +265,7 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
tokenInfo,
scene_state->token_secret,
scene_state->token_secret_length,
+ PLAIN_TOKEN_ENCODING_BASE32,
&plugin_state->iv[0]);
if(token_secret_set) {
@@ -352,7 +350,3 @@ void totp_scene_add_new_token_deactivate(PluginState* plugin_state) {
free(plugin_state->current_scene_state);
plugin_state->current_scene_state = NULL;
}
-
-void totp_scene_add_new_token_free(const PluginState* plugin_state) {
- UNUSED(plugin_state);
-}
diff --git a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h
index c412e5f0f..07098111a 100644
--- a/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h
+++ b/applications/external/totp/ui/scenes/add_new_token/totp_scene_add_new_token.h
@@ -10,11 +10,9 @@ typedef struct {
uint16_t current_token_index;
} TokenAddEditSceneContext;
-void totp_scene_add_new_token_init(const PluginState* plugin_state);
void totp_scene_add_new_token_activate(
PluginState* plugin_state,
const TokenAddEditSceneContext* context);
void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state);
bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState* plugin_state);
void totp_scene_add_new_token_deactivate(PluginState* plugin_state);
-void totp_scene_add_new_token_free(const PluginState* plugin_state);
diff --git a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c
index 1671542b8..d2cf629d2 100644
--- a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c
+++ b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.c
@@ -8,7 +8,7 @@
#include "../../constants.h"
#include "../../../services/config/config.h"
#include "../../../services/convert/convert.h"
-#include "../../../lib/roll_value/roll_value.h"
+#include
#include "../../../types/nullable.h"
#include "../../../features_config.h"
#ifdef TOTP_BADBT_TYPE_ENABLED
@@ -44,10 +44,6 @@ typedef struct {
Control selected_control;
} SceneState;
-void totp_scene_app_settings_init(const PluginState* plugin_state) {
- UNUSED(plugin_state);
-}
-
void totp_scene_app_settings_activate(
PluginState* plugin_state,
const AppSettingsSceneContext* context) {
@@ -332,7 +328,3 @@ void totp_scene_app_settings_deactivate(PluginState* plugin_state) {
free(plugin_state->current_scene_state);
plugin_state->current_scene_state = NULL;
}
-
-void totp_scene_app_settings_free(const PluginState* plugin_state) {
- UNUSED(plugin_state);
-}
\ No newline at end of file
diff --git a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.h b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.h
index 1721186ed..a0e408b00 100644
--- a/applications/external/totp/ui/scenes/app_settings/totp_app_settings.h
+++ b/applications/external/totp/ui/scenes/app_settings/totp_app_settings.h
@@ -8,7 +8,6 @@ typedef struct {
uint16_t current_token_index;
} AppSettingsSceneContext;
-void totp_scene_app_settings_init(const PluginState* plugin_state);
void totp_scene_app_settings_activate(
PluginState* plugin_state,
const AppSettingsSceneContext* context);
@@ -16,5 +15,4 @@ void totp_scene_app_settings_render(Canvas* const canvas, const PluginState* plu
bool totp_scene_app_settings_handle_event(
const PluginEvent* const event,
PluginState* plugin_state);
-void totp_scene_app_settings_deactivate(PluginState* plugin_state);
-void totp_scene_app_settings_free(const PluginState* plugin_state);
\ No newline at end of file
+void totp_scene_app_settings_deactivate(PluginState* plugin_state);
\ No newline at end of file
diff --git a/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.c b/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.c
index 17beb64c6..c0a0b5744 100644
--- a/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.c
+++ b/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.c
@@ -18,10 +18,6 @@ typedef struct {
uint8_t code_length;
} SceneState;
-void totp_scene_authenticate_init(PluginState* plugin_state) {
- memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
-}
-
void totp_scene_authenticate_activate(PluginState* plugin_state) {
SceneState* scene_state = malloc(sizeof(SceneState));
furi_check(scene_state != NULL);
@@ -162,7 +158,3 @@ void totp_scene_authenticate_deactivate(PluginState* plugin_state) {
free(plugin_state->current_scene_state);
plugin_state->current_scene_state = NULL;
}
-
-void totp_scene_authenticate_free(const PluginState* plugin_state) {
- UNUSED(plugin_state);
-}
diff --git a/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.h b/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.h
index b8fe174ae..5ddd44a4a 100644
--- a/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.h
+++ b/applications/external/totp/ui/scenes/authenticate/totp_scene_authenticate.h
@@ -4,11 +4,9 @@
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
-void totp_scene_authenticate_init(PluginState* plugin_state);
void totp_scene_authenticate_activate(PluginState* plugin_state);
void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state);
bool totp_scene_authenticate_handle_event(
const PluginEvent* const event,
PluginState* plugin_state);
void totp_scene_authenticate_deactivate(PluginState* plugin_state);
-void totp_scene_authenticate_free(const PluginState* plugin_state);
diff --git a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c
index a8e93bbff..22e6ebc33 100644
--- a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c
+++ b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.c
@@ -19,9 +19,11 @@
#ifdef TOTP_BADBT_TYPE_ENABLED
#include "../../../workers/bt_type_code/bt_type_code.h"
#endif
+#include "../../fonts/mode_nine/mode_nine.h"
-static const uint8_t PROGRESS_BAR_MARGIN = 3;
-static const uint8_t PROGRESS_BAR_HEIGHT = 4;
+#define PROGRESS_BAR_MARGIN (3)
+#define PROGRESS_BAR_HEIGHT (4)
+static const char* STEAM_ALGO_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY";
typedef struct {
uint16_t current_token_index;
@@ -121,13 +123,21 @@ static const NotificationSequence*
return (NotificationSequence*)scene_state->notification_sequence_badusb;
}
-static void int_token_to_str(uint32_t i_token_code, char* str, TokenDigitsCount len) {
+static void
+ int_token_to_str(uint64_t i_token_code, char* str, TokenDigitsCount len, TokenHashAlgo algo) {
if(i_token_code == OTP_ERROR) {
memset(&str[0], '-', len);
} else {
- for(int i = len - 1; i >= 0; i--) {
- str[i] = CONVERT_DIGIT_TO_CHAR(i_token_code % 10);
- i_token_code = i_token_code / 10;
+ if(algo == STEAM) {
+ for(uint8_t i = 0; i < len; i++) {
+ str[i] = STEAM_ALGO_ALPHABET[i_token_code % 26];
+ i_token_code = i_token_code / 26;
+ }
+ } else {
+ for(int8_t i = len - 1; i >= 0; i--) {
+ str[i] = CONVERT_DIGIT_TO_CHAR(i_token_code % 10);
+ i_token_code = i_token_code / 10;
+ }
}
}
@@ -137,6 +147,7 @@ static void int_token_to_str(uint32_t i_token_code, char* str, TokenDigitsCount
static TOTP_ALGO get_totp_algo_impl(TokenHashAlgo algo) {
switch(algo) {
case SHA1:
+ case STEAM:
return TOTP_ALGO_SHA1;
case SHA256:
return TOTP_ALGO_SHA256;
@@ -161,8 +172,26 @@ static void update_totp_params(PluginState* const plugin_state) {
}
}
-void totp_scene_generate_token_init(const PluginState* plugin_state) {
- UNUSED(plugin_state);
+static void draw_totp_code(Canvas* const canvas, const SceneState* const scene_state) {
+ uint8_t code_length = scene_state->current_token->digits;
+ uint8_t char_width = modeNine_15ptFontInfo.charInfo[0].width;
+ uint8_t total_length = code_length * (char_width + modeNine_15ptFontInfo.spacePixels);
+ uint8_t offset_x = (SCREEN_WIDTH - total_length) >> 1;
+ uint8_t offset_x_inc = char_width + modeNine_15ptFontInfo.spacePixels;
+ uint8_t offset_y = SCREEN_HEIGHT_CENTER - (modeNine_15ptFontInfo.height >> 1);
+ for(uint8_t i = 0; i < code_length; i++) {
+ char ch = scene_state->last_code[i];
+ uint8_t char_index = ch - modeNine_15ptFontInfo.startChar;
+ canvas_draw_xbm(
+ canvas,
+ offset_x,
+ offset_y,
+ char_width,
+ modeNine_15ptFontInfo.height,
+ &modeNine_15ptFontInfo.data[modeNine_15ptFontInfo.charInfo[char_index].offset]);
+
+ offset_x += offset_x_inc;
+ }
}
void totp_scene_generate_token_activate(
@@ -274,19 +303,19 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
int_token_to_str(
totp_at(
get_totp_algo_impl(tokenInfo->algo),
- tokenInfo->digits,
key,
key_length,
curr_ts,
plugin_state->timezone_offset,
tokenInfo->duration),
scene_state->last_code,
- tokenInfo->digits);
+ tokenInfo->digits,
+ tokenInfo->algo);
memset_s(key, key_length, 0, key_length);
free(key);
} else {
furi_mutex_acquire(scene_state->last_code_update_sync, FuriWaitForever);
- int_token_to_str(0, scene_state->last_code, tokenInfo->digits);
+ int_token_to_str(0, scene_state->last_code, tokenInfo->digits, tokenInfo->algo);
}
furi_mutex_release(scene_state->last_code_update_sync);
@@ -322,14 +351,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
canvas_set_color(canvas, ColorBlack);
}
- canvas_set_font(canvas, FontBigNumbers);
- canvas_draw_str_aligned(
- canvas,
- SCREEN_WIDTH_CENTER,
- SCREEN_HEIGHT_CENTER,
- AlignCenter,
- AlignCenter,
- scene_state->last_code);
+ draw_totp_code(canvas, scene_state);
const uint8_t TOKEN_LIFETIME = scene_state->current_token->duration;
float percentDone = (float)(TOKEN_LIFETIME - curr_ts % TOKEN_LIFETIME) / (float)TOKEN_LIFETIME;
@@ -350,14 +372,34 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
canvas, SCREEN_WIDTH - 9, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9);
}
-#if defined(TOTP_BADBT_TYPE_ENABLED) && defined(TOTP_BADBT_TYPE_ICON_ENABLED)
+#ifdef TOTP_AUTOMATION_ICONS_ENABLED
+ if(plugin_state->automation_method & AutomationMethodBadUsb) {
+ canvas_draw_icon(
+ canvas,
+#ifdef TOTP_BADBT_TYPE_ENABLED
+ SCREEN_WIDTH_CENTER -
+ (plugin_state->automation_method & AutomationMethodBadBt ? 33 : 15),
+#else
+ SCREEN_WIDTH_CENTER - 15,
+#endif
+
+ SCREEN_HEIGHT_CENTER + 12,
+ &I_hid_usb_31x9);
+ }
+
+#ifdef TOTP_BADBT_TYPE_ENABLED
if(plugin_state->automation_method & AutomationMethodBadBt &&
plugin_state->bt_type_code_worker_context != NULL &&
plugin_state->bt_type_code_worker_context->is_advertising) {
canvas_draw_icon(
- canvas, SCREEN_WIDTH_CENTER - 5, SCREEN_HEIGHT_CENTER + 13, &I_hid_ble_10x7);
+ canvas,
+ SCREEN_WIDTH_CENTER +
+ (plugin_state->automation_method & AutomationMethodBadUsb ? 2 : -15),
+ SCREEN_HEIGHT_CENTER + 12,
+ &I_hid_ble_31x9);
}
#endif
+#endif
}
bool totp_scene_generate_token_handle_event(
@@ -377,7 +419,9 @@ bool totp_scene_generate_token_handle_event(
plugin_state->automation_method & AutomationMethodBadUsb) {
scene_state = (SceneState*)plugin_state->current_scene_state;
totp_usb_type_code_worker_notify(
- scene_state->usb_type_code_worker_context, TotpUsbTypeCodeWorkerEventType);
+ scene_state->usb_type_code_worker_context,
+ TotpUsbTypeCodeWorkerEventType,
+ scene_state->current_token->automation_features);
notification_message(
plugin_state->notification_app,
get_notification_sequence_automation(plugin_state, scene_state));
@@ -389,7 +433,9 @@ bool totp_scene_generate_token_handle_event(
plugin_state->automation_method & AutomationMethodBadBt) {
scene_state = (SceneState*)plugin_state->current_scene_state;
totp_bt_type_code_worker_notify(
- plugin_state->bt_type_code_worker_context, TotpBtTypeCodeWorkerEventType);
+ plugin_state->bt_type_code_worker_context,
+ TotpBtTypeCodeWorkerEventType,
+ scene_state->current_token->automation_features);
notification_message(
plugin_state->notification_app,
get_notification_sequence_automation(plugin_state, scene_state));
@@ -469,7 +515,3 @@ void totp_scene_generate_token_deactivate(PluginState* plugin_state) {
free(scene_state);
plugin_state->current_scene_state = NULL;
}
-
-void totp_scene_generate_token_free(const PluginState* plugin_state) {
- UNUSED(plugin_state);
-}
diff --git a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.h b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.h
index 44a3b1c0f..e183f53d2 100644
--- a/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.h
+++ b/applications/external/totp/ui/scenes/generate_token/totp_scene_generate_token.h
@@ -8,7 +8,6 @@ typedef struct {
uint16_t current_token_index;
} GenerateTokenSceneContext;
-void totp_scene_generate_token_init(const PluginState* plugin_state);
void totp_scene_generate_token_activate(
PluginState* plugin_state,
const GenerateTokenSceneContext* context);
@@ -17,4 +16,3 @@ bool totp_scene_generate_token_handle_event(
const PluginEvent* const event,
PluginState* plugin_state);
void totp_scene_generate_token_deactivate(PluginState* plugin_state);
-void totp_scene_generate_token_free(const PluginState* plugin_state);
diff --git a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c
index 167762602..7b00f0a1b 100644
--- a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c
+++ b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.c
@@ -6,13 +6,13 @@
#include "../../constants.h"
#include "../../scene_director.h"
#include "../../../services/config/config.h"
-#include "../../../lib/list/list.h"
+#include
#include "../../../types/token_info.h"
#include "../generate_token/totp_scene_generate_token.h"
#include "../add_new_token/totp_scene_add_new_token.h"
#include "../app_settings/totp_app_settings.h"
#include "../../../types/nullable.h"
-#include "../../../lib/roll_value/roll_value.h"
+#include
#define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3)
#define SCREEN_HEIGHT_THIRD_CENTER (SCREEN_HEIGHT_THIRD >> 1)
@@ -24,10 +24,6 @@ typedef struct {
TotpNullable_uint16_t current_token_index;
} SceneState;
-void totp_scene_token_menu_init(const PluginState* plugin_state) {
- UNUSED(plugin_state);
-}
-
void totp_scene_token_menu_activate(
PluginState* plugin_state,
const TokenMenuSceneContext* context) {
@@ -204,7 +200,3 @@ void totp_scene_token_menu_deactivate(PluginState* plugin_state) {
free(plugin_state->current_scene_state);
plugin_state->current_scene_state = NULL;
}
-
-void totp_scene_token_menu_free(const PluginState* plugin_state) {
- UNUSED(plugin_state);
-}
diff --git a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.h b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.h
index 059b8e571..f9d4b4cbf 100644
--- a/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.h
+++ b/applications/external/totp/ui/scenes/token_menu/totp_scene_token_menu.h
@@ -8,11 +8,9 @@ typedef struct {
uint16_t current_token_index;
} TokenMenuSceneContext;
-void totp_scene_token_menu_init(const PluginState* plugin_state);
void totp_scene_token_menu_activate(
PluginState* plugin_state,
const TokenMenuSceneContext* context);
void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state);
bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginState* plugin_state);
void totp_scene_token_menu_deactivate(PluginState* plugin_state);
-void totp_scene_token_menu_free(const PluginState* plugin_state);
diff --git a/applications/external/totp/ui/ui_controls.c b/applications/external/totp/ui/ui_controls.c
index af029dd9f..d5e86aa58 100644
--- a/applications/external/totp/ui/ui_controls.c
+++ b/applications/external/totp/ui/ui_controls.c
@@ -2,8 +2,8 @@
#include
#include "constants.h"
-#define TEXT_BOX_HEIGHT 13
-#define TEXT_BOX_MARGIN 4
+#define TEXT_BOX_HEIGHT (13)
+#define TEXT_BOX_MARGIN (4)
void ui_control_text_box_render(
Canvas* const canvas,
diff --git a/applications/external/totp/workers/bt_type_code/bt_type_code.c b/applications/external/totp/workers/bt_type_code/bt_type_code.c
index 8b9cf6548..ec4c1619d 100644
--- a/applications/external/totp/workers/bt_type_code/bt_type_code.c
+++ b/applications/external/totp/workers/bt_type_code/bt_type_code.c
@@ -1,9 +1,10 @@
#include "bt_type_code.h"
#include
+#include
#include
#include "../../types/common.h"
-#include "../../services/convert/convert.h"
-#include "../constants.h"
+#include "../../types/token_info.h"
+#include "../common.h"
#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("authenticator/.bt_hid.keys")
@@ -11,32 +12,46 @@ static inline bool totp_type_code_worker_stop_requested() {
return furi_thread_flags_get() & TotpBtTypeCodeWorkerEventStop;
}
+#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
+static void totp_type_code_worker_bt_set_app_mac(uint8_t* mac) {
+ uint8_t max_i;
+ size_t uid_size = furi_hal_version_uid_size();
+ if(uid_size < 6) {
+ max_i = uid_size;
+ } else {
+ max_i = 6;
+ }
+
+ const uint8_t* uid = furi_hal_version_uid();
+ memcpy(mac, uid, max_i);
+ for(uint8_t i = max_i; i < 6; i++) {
+ mac[i] = 0;
+ }
+
+ mac[0] = 0b10;
+}
+#endif
+
static void totp_type_code_worker_type_code(TotpBtTypeCodeWorkerContext* context) {
uint8_t i = 0;
do {
furi_delay_ms(500);
i++;
- } while(!furi_hal_bt_is_active() && i < 100 && !totp_type_code_worker_stop_requested());
-
- if(furi_hal_bt_is_active() && furi_mutex_acquire(context->string_sync, 500) == FuriStatusOk) {
- furi_delay_ms(500);
- i = 0;
- while(i < context->string_length && context->string[i] != 0) {
- uint8_t digit = CONVERT_CHAR_TO_DIGIT(context->string[i]);
- if(digit > 9) break;
- uint8_t hid_kb_key = hid_number_keys[digit];
- furi_hal_bt_hid_kb_press(hid_kb_key);
- furi_delay_ms(30);
- furi_hal_bt_hid_kb_release(hid_kb_key);
- i++;
- }
+ } while(!context->is_connected && i < 100 && !totp_type_code_worker_stop_requested());
+ if(context->is_connected && furi_mutex_acquire(context->string_sync, 500) == FuriStatusOk) {
+ totp_type_code_worker_execute_automation(
+ &furi_hal_bt_hid_kb_press,
+ &furi_hal_bt_hid_kb_release,
+ context->string,
+ context->string_length,
+ context->flags);
furi_mutex_release(context->string_sync);
}
}
static int32_t totp_type_code_worker_callback(void* context) {
- furi_assert(context);
+ furi_check(context);
FuriMutex* context_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(context_mutex == NULL) {
return 251;
@@ -44,9 +59,6 @@ static int32_t totp_type_code_worker_callback(void* context) {
TotpBtTypeCodeWorkerContext* bt_context = context;
- furi_hal_bt_start_advertising();
- bt_context->is_advertising = true;
-
while(true) {
uint32_t flags = furi_thread_flags_wait(
TotpBtTypeCodeWorkerEventStop | TotpBtTypeCodeWorkerEventType,
@@ -64,21 +76,26 @@ static int32_t totp_type_code_worker_callback(void* context) {
}
}
- furi_hal_bt_stop_advertising();
-
- bt_context->is_advertising = false;
-
furi_mutex_free(context_mutex);
return 0;
}
+static void connection_status_changed_callback(BtStatus status, void* context) {
+ TotpBtTypeCodeWorkerContext* bt_context = context;
+ if(status == BtStatusConnected) {
+ bt_context->is_connected = true;
+ } else if(status < BtStatusConnected) {
+ bt_context->is_connected = false;
+ }
+}
+
void totp_bt_type_code_worker_start(
TotpBtTypeCodeWorkerContext* context,
char* code_buf,
uint8_t code_buf_length,
FuriMutex* code_buf_update_sync) {
- furi_assert(context != NULL);
+ furi_check(context != NULL);
context->string = code_buf;
context->string_length = code_buf_length;
context->string_sync = code_buf_update_sync;
@@ -91,7 +108,7 @@ void totp_bt_type_code_worker_start(
}
void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context) {
- furi_assert(context != NULL);
+ furi_check(context != NULL);
furi_thread_flags_set(furi_thread_get_id(context->thread), TotpBtTypeCodeWorkerEventStop);
furi_thread_join(context->thread);
furi_thread_free(context->thread);
@@ -100,8 +117,10 @@ void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context) {
void totp_bt_type_code_worker_notify(
TotpBtTypeCodeWorkerContext* context,
- TotpBtTypeCodeWorkerEvent event) {
- furi_assert(context != NULL);
+ TotpBtTypeCodeWorkerEvent event,
+ uint8_t flags) {
+ furi_check(context != NULL);
+ context->flags = flags;
furi_thread_flags_set(furi_thread_get_id(context->thread), event);
}
@@ -111,26 +130,66 @@ TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init() {
context->bt = furi_record_open(RECORD_BT);
context->is_advertising = false;
+ context->is_connected = false;
bt_disconnect(context->bt);
+ furi_hal_bt_reinit();
furi_delay_ms(200);
bt_keys_storage_set_storage_path(context->bt, HID_BT_KEYS_STORAGE_PATH);
+
+#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
+ totp_type_code_worker_bt_set_app_mac(&context->bt_mac[0]);
+ memcpy(
+ &context->previous_bt_name[0],
+ furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard),
+ TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN);
+ memcpy(
+ &context->previous_bt_mac[0],
+ furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard),
+ TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN);
+ char new_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN];
+ snprintf(new_name, sizeof(new_name), "%s TOTP Auth", furi_hal_version_get_name_ptr());
+ furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, new_name);
+ furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, context->bt_mac);
+#endif
+
if(!bt_set_profile(context->bt, BtProfileHidKeyboard)) {
FURI_LOG_E(LOGGING_TAG, "Failed to switch BT to keyboard HID profile");
}
+ furi_hal_bt_start_advertising();
+
+#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
+ bt_enable_peer_key_update(context->bt);
+#endif
+
+ context->is_advertising = true;
+ bt_set_status_changed_callback(context->bt, connection_status_changed_callback, context);
+
return context;
}
void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context) {
- furi_assert(context != NULL);
+ furi_check(context != NULL);
if(context->thread != NULL) {
totp_bt_type_code_worker_stop(context);
}
+ bt_set_status_changed_callback(context->bt, NULL, NULL);
+
+ furi_hal_bt_stop_advertising();
+ context->is_advertising = false;
+ context->is_connected = false;
+
bt_disconnect(context->bt);
+ furi_delay_ms(200);
bt_keys_storage_set_default_path(context->bt);
+#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
+ furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, context->previous_bt_name);
+ furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, context->previous_bt_mac);
+#endif
+
if(!bt_set_profile(context->bt, BtProfileSerial)) {
FURI_LOG_E(LOGGING_TAG, "Failed to switch BT to Serial profile");
}
diff --git a/applications/external/totp/workers/bt_type_code/bt_type_code.h b/applications/external/totp/workers/bt_type_code/bt_type_code.h
index 475b66db4..1c59ea3e9 100644
--- a/applications/external/totp/workers/bt_type_code/bt_type_code.h
+++ b/applications/external/totp/workers/bt_type_code/bt_type_code.h
@@ -4,22 +4,35 @@
#include
#include
#include
+#include "../../features_config.h"
+
+#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
+#define TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH
+#define TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE
+#endif
typedef uint8_t TotpBtTypeCodeWorkerEvent;
typedef struct {
char* string;
uint8_t string_length;
+ uint8_t flags;
FuriThread* thread;
FuriMutex* string_sync;
Bt* bt;
bool is_advertising;
+ bool is_connected;
+#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
+ uint8_t bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN];
+ char previous_bt_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN];
+ uint8_t previous_bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN];
+#endif
} TotpBtTypeCodeWorkerContext;
enum TotpBtTypeCodeWorkerEvents {
- TotpBtTypeCodeWorkerEventReserved = (1 << 0),
- TotpBtTypeCodeWorkerEventStop = (1 << 1),
- TotpBtTypeCodeWorkerEventType = (1 << 2)
+ TotpBtTypeCodeWorkerEventReserved = 0b0000,
+ TotpBtTypeCodeWorkerEventStop = 0b0100,
+ TotpBtTypeCodeWorkerEventType = 0b1000
};
TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init();
@@ -32,4 +45,5 @@ void totp_bt_type_code_worker_start(
void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context);
void totp_bt_type_code_worker_notify(
TotpBtTypeCodeWorkerContext* context,
- TotpBtTypeCodeWorkerEvent event);
\ No newline at end of file
+ TotpBtTypeCodeWorkerEvent event,
+ uint8_t flags);
diff --git a/applications/external/totp/workers/common.c b/applications/external/totp/workers/common.c
new file mode 100644
index 000000000..8ad0c2b46
--- /dev/null
+++ b/applications/external/totp/workers/common.c
@@ -0,0 +1,80 @@
+#include "common.h"
+#include
+#include
+#include "../../services/convert/convert.h"
+
+static const uint8_t hid_number_keys[] = {
+ HID_KEYBOARD_0, HID_KEYBOARD_1, HID_KEYBOARD_2, HID_KEYBOARD_3, HID_KEYBOARD_4,
+ HID_KEYBOARD_5, HID_KEYBOARD_6, HID_KEYBOARD_7, HID_KEYBOARD_8, HID_KEYBOARD_9,
+ HID_KEYBOARD_A, HID_KEYBOARD_B, HID_KEYBOARD_C, HID_KEYBOARD_D, HID_KEYBOARD_E,
+ HID_KEYBOARD_F, HID_KEYBOARD_G, HID_KEYBOARD_H, HID_KEYBOARD_I, HID_KEYBOARD_J,
+ HID_KEYBOARD_K, HID_KEYBOARD_L, HID_KEYBOARD_M, HID_KEYBOARD_N, HID_KEYBOARD_O,
+ HID_KEYBOARD_P, HID_KEYBOARD_Q, HID_KEYBOARD_R, HID_KEYBOARD_S, HID_KEYBOARD_T,
+ HID_KEYBOARD_U, HID_KEYBOARD_V, HID_KEYBOARD_W, HID_KEYBOARD_X, HID_KEYBOARD_Y,
+ HID_KEYBOARD_Z};
+
+static uint32_t get_keystroke_delay(TokenAutomationFeature features) {
+ if(features & TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER) {
+ return 100;
+ }
+
+ return 30;
+}
+
+static uint32_t get_keypress_delay(TokenAutomationFeature features) {
+ if(features & TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER) {
+ return 60;
+ }
+
+ return 30;
+}
+
+static void totp_type_code_worker_press_key(
+ uint8_t key,
+ TOTP_AUTOMATION_KEY_HANDLER key_press_fn,
+ TOTP_AUTOMATION_KEY_HANDLER key_release_fn,
+ TokenAutomationFeature features) {
+ (*key_press_fn)(key);
+ furi_delay_ms(get_keypress_delay(features));
+ (*key_release_fn)(key);
+}
+
+void totp_type_code_worker_execute_automation(
+ TOTP_AUTOMATION_KEY_HANDLER key_press_fn,
+ TOTP_AUTOMATION_KEY_HANDLER key_release_fn,
+ const char* string,
+ uint8_t string_length,
+ TokenAutomationFeature features) {
+ furi_delay_ms(500);
+ uint8_t i = 0;
+ totp_type_code_worker_press_key(
+ HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features);
+
+ while(i < string_length && string[i] != 0) {
+ uint8_t char_index = CONVERT_CHAR_TO_DIGIT(string[i]);
+ if(char_index > 9) {
+ char_index = string[i] - 0x41 + 10;
+ }
+
+ if(char_index > 35) break;
+
+ uint8_t hid_kb_key = hid_number_keys[char_index];
+ totp_type_code_worker_press_key(hid_kb_key, key_press_fn, key_release_fn, features);
+ furi_delay_ms(get_keystroke_delay(features));
+ i++;
+ }
+
+ if(features & TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END) {
+ furi_delay_ms(get_keystroke_delay(features));
+ totp_type_code_worker_press_key(
+ HID_KEYBOARD_RETURN, key_press_fn, key_release_fn, features);
+ }
+
+ if(features & TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END) {
+ furi_delay_ms(get_keystroke_delay(features));
+ totp_type_code_worker_press_key(HID_KEYBOARD_TAB, key_press_fn, key_release_fn, features);
+ }
+
+ totp_type_code_worker_press_key(
+ HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features);
+}
\ No newline at end of file
diff --git a/applications/external/totp/workers/common.h b/applications/external/totp/workers/common.h
new file mode 100644
index 000000000..5e3a2006e
--- /dev/null
+++ b/applications/external/totp/workers/common.h
@@ -0,0 +1,12 @@
+#pragma once
+#include
+#include "../types/token_info.h"
+
+typedef bool (*TOTP_AUTOMATION_KEY_HANDLER)(uint16_t key);
+
+void totp_type_code_worker_execute_automation(
+ TOTP_AUTOMATION_KEY_HANDLER key_press_fn,
+ TOTP_AUTOMATION_KEY_HANDLER key_release_fn,
+ const char* string,
+ uint8_t string_length,
+ TokenAutomationFeature features);
\ No newline at end of file
diff --git a/applications/external/totp/workers/constants.c b/applications/external/totp/workers/constants.c
deleted file mode 100644
index f3c103578..000000000
--- a/applications/external/totp/workers/constants.c
+++ /dev/null
@@ -1,14 +0,0 @@
-#include "constants.h"
-#include
-
-const uint8_t hid_number_keys[10] = {
- HID_KEYBOARD_0,
- HID_KEYBOARD_1,
- HID_KEYBOARD_2,
- HID_KEYBOARD_3,
- HID_KEYBOARD_4,
- HID_KEYBOARD_5,
- HID_KEYBOARD_6,
- HID_KEYBOARD_7,
- HID_KEYBOARD_8,
- HID_KEYBOARD_9};
\ No newline at end of file
diff --git a/applications/external/totp/workers/constants.h b/applications/external/totp/workers/constants.h
deleted file mode 100644
index c314b6c16..000000000
--- a/applications/external/totp/workers/constants.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#pragma once
-#include
-
-extern const uint8_t hid_number_keys[10];
\ No newline at end of file
diff --git a/applications/external/totp/workers/usb_type_code/usb_type_code.c b/applications/external/totp/workers/usb_type_code/usb_type_code.c
index 3132e2317..5f7ccddf8 100644
--- a/applications/external/totp/workers/usb_type_code/usb_type_code.c
+++ b/applications/external/totp/workers/usb_type_code/usb_type_code.c
@@ -1,6 +1,7 @@
#include "usb_type_code.h"
#include "../../services/convert/convert.h"
-#include "../constants.h"
+#include "../../types/token_info.h"
+#include "../common.h"
static void totp_type_code_worker_restore_usb_mode(TotpUsbTypeCodeWorkerContext* context) {
if(context->usb_mode_prev != NULL) {
@@ -25,18 +26,12 @@ static void totp_type_code_worker_type_code(TotpUsbTypeCodeWorkerContext* contex
if(furi_hal_hid_is_connected() &&
furi_mutex_acquire(context->string_sync, 500) == FuriStatusOk) {
- furi_delay_ms(500);
- i = 0;
- while(i < context->string_length && context->string[i] != 0) {
- uint8_t digit = CONVERT_CHAR_TO_DIGIT(context->string[i]);
- if(digit > 9) break;
- uint8_t hid_kb_key = hid_number_keys[digit];
- furi_hal_hid_kb_press(hid_kb_key);
- furi_delay_ms(30);
- furi_hal_hid_kb_release(hid_kb_key);
- i++;
- }
-
+ totp_type_code_worker_execute_automation(
+ &furi_hal_hid_kb_press,
+ &furi_hal_hid_kb_release,
+ context->string,
+ context->string_length,
+ context->flags);
furi_mutex_release(context->string_sync);
furi_delay_ms(100);
@@ -46,7 +41,7 @@ static void totp_type_code_worker_type_code(TotpUsbTypeCodeWorkerContext* contex
}
static int32_t totp_type_code_worker_callback(void* context) {
- furi_assert(context);
+ furi_check(context);
FuriMutex* context_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(context_mutex == NULL) {
return 251;
@@ -94,7 +89,7 @@ TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start(
}
void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context) {
- furi_assert(context != NULL);
+ furi_check(context != NULL);
furi_thread_flags_set(furi_thread_get_id(context->thread), TotpUsbTypeCodeWorkerEventStop);
furi_thread_join(context->thread);
furi_thread_free(context->thread);
@@ -104,7 +99,9 @@ void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context) {
void totp_usb_type_code_worker_notify(
TotpUsbTypeCodeWorkerContext* context,
- TotpUsbTypeCodeWorkerEvent event) {
- furi_assert(context != NULL);
+ TotpUsbTypeCodeWorkerEvent event,
+ uint8_t flags) {
+ furi_check(context != NULL);
+ context->flags = flags;
furi_thread_flags_set(furi_thread_get_id(context->thread), event);
}
\ No newline at end of file
diff --git a/applications/external/totp/workers/usb_type_code/usb_type_code.h b/applications/external/totp/workers/usb_type_code/usb_type_code.h
index 94fddcc59..d0ea600ce 100644
--- a/applications/external/totp/workers/usb_type_code/usb_type_code.h
+++ b/applications/external/totp/workers/usb_type_code/usb_type_code.h
@@ -9,15 +9,16 @@ typedef uint8_t TotpUsbTypeCodeWorkerEvent;
typedef struct {
char* string;
uint8_t string_length;
+ uint8_t flags;
FuriThread* thread;
FuriMutex* string_sync;
FuriHalUsbInterface* usb_mode_prev;
} TotpUsbTypeCodeWorkerContext;
enum TotpUsbTypeCodeWorkerEvents {
- TotpUsbTypeCodeWorkerEventReserved = (1 << 0),
- TotpUsbTypeCodeWorkerEventStop = (1 << 1),
- TotpUsbTypeCodeWorkerEventType = (1 << 2)
+ TotpUsbTypeCodeWorkerEventReserved = 0b00,
+ TotpUsbTypeCodeWorkerEventStop = 0b01,
+ TotpUsbTypeCodeWorkerEventType = 0b10
};
TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start(
@@ -27,4 +28,5 @@ TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start(
void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context);
void totp_usb_type_code_worker_notify(
TotpUsbTypeCodeWorkerContext* context,
- TotpUsbTypeCodeWorkerEvent event);
\ No newline at end of file
+ TotpUsbTypeCodeWorkerEvent event,
+ uint8_t flags);
\ No newline at end of file
diff --git a/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c b/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c
index a9f998124..38a5a20e4 100644
--- a/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c
+++ b/applications/external/uart_terminal/scenes/uart_terminal_scene_console_output.c
@@ -111,11 +111,21 @@ void uart_terminal_scene_console_output_on_enter(void* context) {
uart_terminal_uart_set_handle_rx_data_cb(
app->uart, uart_terminal_console_output_handle_rx_data_cb); // setup callback for rx thread
- // Send command with newline '\n'
+ // Send command with CR+LF or newline '\n'
if(app->is_command && app->selected_tx_string) {
- uart_terminal_uart_tx(
- (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
- uart_terminal_uart_tx((uint8_t*)("\n"), 1);
+ if(app->TERMINAL_MODE == 1) {
+ // char buffer[240];
+ // snprintf(buffer, 240, "%s\r\n", (app->selected_tx_string));
+ // uart_terminal_uart_tx((unsigned char *)buffer, strlen(buffer));
+ uart_terminal_uart_tx(
+ (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
+ uart_terminal_uart_tx((uint8_t*)("\r"), 1);
+ uart_terminal_uart_tx((uint8_t*)("\n"), 1);
+ } else {
+ uart_terminal_uart_tx(
+ (uint8_t*)(app->selected_tx_string), strlen(app->selected_tx_string));
+ uart_terminal_uart_tx((uint8_t*)("\n"), 1);
+ }
}
}
@@ -144,4 +154,4 @@ void uart_terminal_scene_console_output_on_exit(void* context) {
//if(app->is_command) {
// uart_terminal_uart_tx((uint8_t*)("exit\n"), strlen("exit\n"));
//}
-}
+}
\ No newline at end of file
diff --git a/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c b/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c
index db783e9b2..d4de748b6 100644
--- a/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c
+++ b/applications/external/uart_terminal/scenes/uart_terminal_scene_start.c
@@ -31,6 +31,7 @@ const UART_TerminalItem items[NUM_MENU_ITEMS] = {
FOCUS_CONSOLE_TOGGLE,
NO_TIP},
{"Send command", {""}, 1, {""}, INPUT_ARGS, FOCUS_CONSOLE_END, NO_TIP},
+ {"Send AT command", {""}, 1, {"AT"}, INPUT_ARGS, FOCUS_CONSOLE_END, NO_TIP},
{"Fast cmd",
{"help", "uptime", "date", "df -h", "ps", "dmesg", "reboot", "poweroff"},
8,
diff --git a/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c b/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c
index 62c0792d4..e200ba266 100644
--- a/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c
+++ b/applications/external/uart_terminal/scenes/uart_terminal_scene_text_input.c
@@ -25,7 +25,13 @@ void uart_terminal_scene_text_input_on_enter(void* context) {
// Setup view
UART_TextInput* text_input = app->text_input;
// Add help message to header
- uart_text_input_set_header_text(text_input, "Send command to UART");
+ if(0 == strncmp("AT", app->selected_tx_string, strlen("AT"))) {
+ app->TERMINAL_MODE = 1;
+ uart_text_input_set_header_text(text_input, "Send AT command to UART");
+ } else {
+ app->TERMINAL_MODE = 0;
+ uart_text_input_set_header_text(text_input, "Send command to UART");
+ }
uart_text_input_set_result_callback(
text_input,
uart_terminal_scene_text_input_callback,
diff --git a/applications/external/uart_terminal/uart_terminal_app_i.h b/applications/external/uart_terminal/uart_terminal_app_i.h
index 6b1996eb5..a0c4bb81f 100644
--- a/applications/external/uart_terminal/uart_terminal_app_i.h
+++ b/applications/external/uart_terminal/uart_terminal_app_i.h
@@ -12,7 +12,7 @@
#include
#include "uart_text_input.h"
-#define NUM_MENU_ITEMS (4)
+#define NUM_MENU_ITEMS (5)
#define UART_TERMINAL_TEXT_BOX_STORE_SIZE (4096)
#define UART_TERMINAL_TEXT_INPUT_STORE_SIZE (512)
@@ -40,6 +40,7 @@ struct UART_TerminalApp {
bool focus_console_start;
bool show_stopscan_tip;
int BAUDRATE;
+ int TERMINAL_MODE; //1=AT mode, 0=other mode
};
typedef enum {
diff --git a/applications/external/uart_terminal/uart_text_input.c b/applications/external/uart_terminal/uart_text_input.c
index 4a571b127..7d400b81e 100644
--- a/applications/external/uart_terminal/uart_text_input.c
+++ b/applications/external/uart_terminal/uart_text_input.c
@@ -1,6 +1,7 @@
#include "uart_text_input.h"
#include
#include "uart_terminal_icons.h"
+#include "uart_terminal_app_i.h"
#include
struct UART_TextInput {
@@ -36,6 +37,8 @@ static const uint8_t keyboard_origin_x = 1;
static const uint8_t keyboard_origin_y = 29;
static const uint8_t keyboard_row_count = 4;
+#define mode_AT "Send AT command to UART"
+
#define ENTER_KEY '\r'
#define BACKSPACE_KEY '\b'
@@ -163,6 +166,47 @@ static bool char_is_lowercase(char letter) {
return (letter >= 0x61 && letter <= 0x7A);
}
+static bool char_is_uppercase(char letter) {
+ return (letter >= 0x41 && letter <= 0x5A);
+}
+
+static char char_to_lowercase(const char letter) {
+ switch(letter) {
+ case ' ':
+ return 0x5f;
+ break;
+ case ')':
+ return 0x28;
+ break;
+ case '}':
+ return 0x7b;
+ break;
+ case ']':
+ return 0x5b;
+ break;
+ case '\\':
+ return 0x2f;
+ break;
+ case ':':
+ return 0x3b;
+ break;
+ case ',':
+ return 0x2e;
+ break;
+ case '?':
+ return 0x21;
+ break;
+ case '>':
+ return 0x3c;
+ break;
+ }
+ if(char_is_uppercase(letter)) {
+ return (letter + 0x20);
+ } else {
+ return letter;
+ }
+}
+
static char char_to_uppercase(const char letter) {
switch(letter) {
case '_':
@@ -193,7 +237,7 @@ static char char_to_uppercase(const char letter) {
return 0x3e;
break;
}
- if(isalpha(letter)) {
+ if(char_is_lowercase(letter)) {
return (letter - 0x20);
} else {
return letter;
@@ -209,7 +253,7 @@ static void uart_text_input_backspace_cb(UART_TextInputModel* model) {
static void uart_text_input_view_draw_callback(Canvas* canvas, void* _model) {
UART_TextInputModel* model = _model;
- uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
+ //uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
uint8_t needed_string_width = canvas_width(canvas) - 8;
uint8_t start_pos = 4;
@@ -291,15 +335,12 @@ static void uart_text_input_view_draw_callback(Canvas* canvas, void* _model) {
} else {
canvas_set_color(canvas, ColorBlack);
}
-
- if(model->clear_default_text ||
- (text_length == 0 && char_is_lowercase(keys[column].text))) {
+ if(0 == strcmp(model->header, mode_AT)) {
canvas_draw_glyph(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
- //char_to_uppercase(keys[column].text));
- keys[column].text);
+ char_to_uppercase(keys[column].text));
} else {
canvas_draw_glyph(
canvas,
@@ -372,10 +413,18 @@ static void uart_text_input_handle_ok(
char selected = get_selected_char(model);
uint8_t text_length = strlen(model->text_buffer);
- if(shift) {
+ if(0 == strcmp(model->header, mode_AT)) {
selected = char_to_uppercase(selected);
}
+ if(shift) {
+ if(0 == strcmp(model->header, mode_AT)) {
+ selected = char_to_lowercase(selected);
+ } else {
+ selected = char_to_uppercase(selected);
+ }
+ }
+
if(selected == ENTER_KEY) {
if(model->validator_callback &&
(!model->validator_callback(
@@ -392,9 +441,6 @@ static void uart_text_input_handle_ok(
text_length = 0;
}
if(text_length < (model->text_buffer_size - 1)) {
- if(text_length == 0 && char_is_lowercase(selected)) {
- //selected = char_to_uppercase(selected);
- }
model->text_buffer[text_length] = selected;
model->text_buffer[text_length + 1] = 0;
}
diff --git a/applications/external/unitemp/Sensors.c b/applications/external/unitemp/Sensors.c
index 30419a929..666438bfa 100644
--- a/applications/external/unitemp/Sensors.c
+++ b/applications/external/unitemp/Sensors.c
@@ -45,7 +45,7 @@ static const GPIO GPIOList[] = {
{14, "14 (RX)", &RX_14},
{15, "15 (C1)", &gpio_ext_pc1},
{16, "16 (C0)", &gpio_ext_pc0},
- {17, "17 (1W)", &ibutton_gpio}};
+ {17, "17 (1W)", &gpio_ibutton}};
//Список интерфейсов, которые прикреплены к GPIO (определяется индексом)
//NULL - порт свободен, указатель на интерфейс - порт занят этим интерфейсом
diff --git a/applications/external/weather_station/images/Fishing_123x52.png b/applications/external/weather_station/images/Fishing_123x52.png
new file mode 100644
index 000000000..1e365de8f
Binary files /dev/null and b/applications/external/weather_station/images/Fishing_123x52.png differ
diff --git a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c
index 5d007b12f..f2fddd40c 100644
--- a/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c
+++ b/applications/external/weather_station/protocols/lacrosse_tx141thbv2.c
@@ -2,11 +2,15 @@
#define TAG "WSProtocolLaCrosse_TX141THBv2"
+#define LACROSSE_TX141TH_BV2_BIT_COUNT 41
+
/*
* Help
* https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse_tx141x.c
*
- * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u
+ * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u - 41 bit
+ * or
+ * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | -40 bit
* - i: identification; changes on battery switch
* - c: lfsr_digest8_reflect;
* - u: unknown;
@@ -17,10 +21,10 @@
*/
static const SubGhzBlockConst ws_protocol_lacrosse_tx141thbv2_const = {
- .te_short = 250,
- .te_long = 500,
+ .te_short = 208,
+ .te_long = 417,
.te_delta = 120,
- .min_count_bit_for_found = 41,
+ .min_count_bit_for_found = 40,
};
struct WSProtocolDecoderLaCrosse_TX141THBv2 {
@@ -102,14 +106,14 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context) {
static bool
ws_protocol_lacrosse_tx141thbv2_check_crc(WSProtocolDecoderLaCrosse_TX141THBv2* instance) {
if(!instance->decoder.decode_data) return false;
- uint8_t msg[] = {
- instance->decoder.decode_data >> 33,
- instance->decoder.decode_data >> 25,
- instance->decoder.decode_data >> 17,
- instance->decoder.decode_data >> 9};
+ uint64_t data = instance->decoder.decode_data;
+ if(instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) {
+ data >>= 1;
+ }
+ uint8_t msg[] = {data >> 32, data >> 24, data >> 16, data >> 8};
uint8_t crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4);
- return (crc == ((instance->decoder.decode_data >> 1) & 0xFF));
+ return (crc == (data & 0xFF));
}
/**
@@ -117,14 +121,43 @@ static bool
* @param instance Pointer to a WSBlockGeneric* instance
*/
static void ws_protocol_lacrosse_tx141thbv2_remote_controller(WSBlockGeneric* instance) {
- instance->id = instance->data >> 33;
- instance->battery_low = (instance->data >> 32) & 1;
- instance->btn = (instance->data >> 31) & 1;
- instance->channel = ((instance->data >> 29) & 0x03) + 1;
- instance->temp = ((float)((instance->data >> 17) & 0x0FFF) - 500.0f) / 10.0f;
- instance->humidity = (instance->data >> 9) & 0xFF;
+ uint64_t data = instance->data;
+ if(instance->data_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) {
+ data >>= 1;
+ }
+ instance->id = data >> 32;
+ instance->battery_low = (data >> 31) & 1;
+ instance->btn = (data >> 30) & 1;
+ instance->channel = ((data >> 28) & 0x03) + 1;
+ instance->temp = ((float)((data >> 16) & 0x0FFF) - 500.0f) / 10.0f;
+ instance->humidity = (data >> 8) & 0xFF;
}
+/**
+ * Analysis of received data
+ * @param instance Pointer to a WSBlockGeneric* instance
+ */
+static bool ws_protocol_decoder_lacrosse_tx141thbv2_add_bit(
+ WSProtocolDecoderLaCrosse_TX141THBv2* instance,
+ uint32_t te_last,
+ uint32_t te_current) {
+ furi_assert(instance);
+ bool ret = false;
+ if(DURATION_DIFF(
+ te_last + te_current,
+ ws_protocol_lacrosse_tx141thbv2_const.te_short +
+ ws_protocol_lacrosse_tx141thbv2_const.te_long) <
+ ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) {
+ if(te_last > te_current) {
+ subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+ } else {
+ subghz_protocol_blocks_add_bit(&instance->decoder, 0);
+ }
+ ret = true;
+ }
+
+ return ret;
+}
void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
WSProtocolDecoderLaCrosse_TX141THBv2* instance = context;
@@ -132,7 +165,7 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin
switch(instance->decoder.parser_step) {
case LaCrosse_TX141THBv2DecoderStepReset:
if((level) &&
- (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
+ (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) <
ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) {
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule;
instance->decoder.te_last = duration;
@@ -146,33 +179,17 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin
} else {
if((DURATION_DIFF(
instance->decoder.te_last,
- ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
+ ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) <
ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) &&
- (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
+ (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) <
ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) {
//Found preambule
instance->header_count++;
} else if(instance->header_count == 4) {
- if((DURATION_DIFF(
- instance->decoder.te_last,
- ws_protocol_lacrosse_tx141thbv2_const.te_short) <
- ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
- (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) <
- ws_protocol_lacrosse_tx141thbv2_const.te_delta)) {
- instance->decoder.decode_data = 0;
- instance->decoder.decode_count_bit = 0;
- subghz_protocol_blocks_add_bit(&instance->decoder, 0);
- instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
- } else if(
- (DURATION_DIFF(
- instance->decoder.te_last,
- ws_protocol_lacrosse_tx141thbv2_const.te_long) <
- ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
- (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) <
- ws_protocol_lacrosse_tx141thbv2_const.te_delta)) {
- instance->decoder.decode_data = 0;
- instance->decoder.decode_count_bit = 0;
- subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+ if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit(
+ instance, instance->decoder.te_last, duration)) {
+ instance->decoder.decode_data = instance->decoder.decode_data & 1;
+ instance->decoder.decode_count_bit = 1;
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
} else {
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset;
@@ -198,37 +215,26 @@ void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uin
instance->decoder.te_last,
ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) &&
- (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
+ (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) <
ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) {
if((instance->decoder.decode_count_bit ==
- ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) &&
- ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) {
- instance->generic.data = instance->decoder.decode_data;
- instance->generic.data_count_bit = instance->decoder.decode_count_bit;
- ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic);
- if(instance->base.callback)
- instance->base.callback(&instance->base, instance->base.context);
+ ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) ||
+ (instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT)) {
+ if(ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) {
+ instance->generic.data = instance->decoder.decode_data;
+ instance->generic.data_count_bit = instance->decoder.decode_count_bit;
+ ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic);
+ if(instance->base.callback)
+ instance->base.callback(&instance->base, instance->base.context);
+ }
+ instance->decoder.decode_data = 0;
+ instance->decoder.decode_count_bit = 0;
+ instance->header_count = 1;
+ instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule;
+ break;
}
- instance->decoder.decode_data = 0;
- instance->decoder.decode_count_bit = 0;
- instance->header_count = 1;
- instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule;
- break;
- } else if(
- (DURATION_DIFF(
- instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short) <
- ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
- (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) <
- ws_protocol_lacrosse_tx141thbv2_const.te_delta)) {
- subghz_protocol_blocks_add_bit(&instance->decoder, 0);
- instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
- } else if(
- (DURATION_DIFF(
- instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_long) <
- ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
- (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) <
- ws_protocol_lacrosse_tx141thbv2_const.te_delta)) {
- subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+ } else if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit(
+ instance, instance->decoder.te_last, duration)) {
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
} else {
instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset;
diff --git a/applications/external/weather_station/views/weather_station_receiver.c b/applications/external/weather_station/views/weather_station_receiver.c
index f8e2e3288..62f1adad0 100644
--- a/applications/external/weather_station/views/weather_station_receiver.c
+++ b/applications/external/weather_station/views/weather_station_receiver.c
@@ -202,6 +202,8 @@ void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) {
FuriString* str_buff;
str_buff = furi_string_alloc();
+ bool ext_module = furi_hal_subghz_get_radio_type();
+
WSReceiverMenuItem* item_menu;
for(size_t i = 0; i < MIN(model->history_item, MENU_ITEMS); ++i) {
@@ -226,10 +228,11 @@ void ws_view_receiver_draw(Canvas* canvas, WSReceiverModel* model) {
canvas_set_color(canvas, ColorBlack);
if(model->history_item == 0) {
- canvas_draw_icon(canvas, 0, 0, &I_Scanning_123x52);
+ canvas_draw_icon(canvas, 0, 0, ext_module ? &I_Fishing_123x52 : &I_Scanning_123x52);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 63, 46, "Scanning...");
canvas_set_font(canvas, FontSecondary);
+ canvas_draw_str(canvas, 44, 10, ext_module ? "Ext" : "Int");
}
// Draw RSSI
diff --git a/applications/main/bad_kb/bad_kb_app.c b/applications/main/bad_kb/bad_kb_app.c
index 251181554..d1215d628 100644
--- a/applications/main/bad_kb/bad_kb_app.c
+++ b/applications/main/bad_kb/bad_kb_app.c
@@ -5,12 +5,19 @@
#include
#include
#include
+#include
#include
#include
#define BAD_KB_SETTINGS_PATH BAD_KB_APP_BASE_FOLDER "/" BAD_KB_SETTINGS_FILE_NAME
+// this is the MAC address used when we do not forget paired device (BOUND STATE)
+const uint8_t BAD_KB_BOUND_MAC_ADDRESS[BAD_KB_MAC_ADDRESS_LEN] =
+ {0x41, 0x4a, 0xef, 0xb6, 0xa9, 0xd4};
+const uint8_t BAD_KB_EMPTY_MAC_ADDRESS[BAD_KB_MAC_ADDRESS_LEN] =
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
static bool bad_kb_app_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
BadKbApp* app = context;
@@ -30,25 +37,41 @@ static void bad_kb_app_tick_event_callback(void* context) {
}
static void bad_kb_load_settings(BadKbApp* app) {
- File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
- if(storage_file_open(settings_file, BAD_KB_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)) {
- furi_string_push_back(app->keyboard_layout, chr);
+ furi_string_reset(app->keyboard_layout);
+ strcpy(app->config.bt_name, "");
+ memcpy(
+ app->config.bt_mac,
+ furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard),
+ BAD_KB_MAC_ADDRESS_LEN);
+
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ FlipperFormat* file = flipper_format_file_alloc(storage);
+ if(flipper_format_file_open_existing(file, BAD_KB_SETTINGS_PATH)) {
+ FuriString* tmp_str = furi_string_alloc();
+ if(!flipper_format_read_string(file, "Keyboard_Layout", app->keyboard_layout)) {
+ furi_string_reset(app->keyboard_layout);
}
- } else {
- furi_string_reset(app->keyboard_layout);
+ if(flipper_format_read_string(file, "Bt_Name", tmp_str) && !furi_string_empty(tmp_str)) {
+ strcpy(app->config.bt_name, furi_string_get_cstr(tmp_str));
+ } else {
+ strcpy(app->config.bt_name, "");
+ }
+ if(!flipper_format_read_hex(
+ file, "Bt_Mac", (uint8_t*)&app->config.bt_mac, BAD_KB_MAC_ADDRESS_LEN)) {
+ memcpy(
+ app->config.bt_mac,
+ furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard),
+ BAD_KB_MAC_ADDRESS_LEN);
+ }
+ furi_string_free(tmp_str);
+ flipper_format_file_close(file);
}
- storage_file_close(settings_file);
- storage_file_free(settings_file);
+ flipper_format_free(file);
if(!furi_string_empty(app->keyboard_layout)) {
- Storage* fs_api = furi_record_open(RECORD_STORAGE);
FileInfo layout_file_info;
FS_Error file_check_err = storage_common_stat(
- fs_api, furi_string_get_cstr(app->keyboard_layout), &layout_file_info);
- furi_record_close(RECORD_STORAGE);
+ storage, furi_string_get_cstr(app->keyboard_layout), &layout_file_info);
if(file_check_err != FSE_OK) {
furi_string_reset(app->keyboard_layout);
return;
@@ -57,19 +80,22 @@ static void bad_kb_load_settings(BadKbApp* app) {
furi_string_reset(app->keyboard_layout);
}
}
+
+ furi_record_close(RECORD_STORAGE);
}
static void bad_kb_save_settings(BadKbApp* app) {
- File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
- if(storage_file_open(settings_file, BAD_KB_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
- storage_file_write(
- settings_file,
- furi_string_get_cstr(app->keyboard_layout),
- furi_string_size(app->keyboard_layout));
- storage_file_write(settings_file, "\n", 1);
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ FlipperFormat* file = flipper_format_file_alloc(storage);
+ if(flipper_format_file_open_always(file, BAD_KB_SETTINGS_PATH)) {
+ flipper_format_write_string(file, "Keyboard_Layout", app->keyboard_layout);
+ flipper_format_write_string_cstr(file, "Bt_Name", app->config.bt_name);
+ flipper_format_write_hex(
+ file, "Bt_Mac", (uint8_t*)&app->config.bt_mac, BAD_KB_MAC_ADDRESS_LEN);
+ flipper_format_file_close(file);
}
- storage_file_close(settings_file);
- storage_file_free(settings_file);
+ flipper_format_free(file);
+ furi_record_close(RECORD_STORAGE);
}
void bad_kb_reload_worker(BadKbApp* app) {
@@ -91,49 +117,66 @@ void bad_kb_config_switch_mode(BadKbApp* app) {
void bad_kb_config_switch_remember_mode(BadKbApp* app) {
if(app->bt_remember) {
- // set bouding mac
- uint8_t mac[6] = BAD_KB_BOUND_MAC_ADDRESS;
furi_hal_bt_set_profile_pairing_method(
FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo);
- bt_set_profile_mac_address(app->bt, mac); // this also restart bt
- // enable keys storage
+ bt_set_profile_mac_address(app->bt, (uint8_t*)&BAD_KB_BOUND_MAC_ADDRESS);
bt_enable_peer_key_update(app->bt);
} else {
- // set back user defined mac address
furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, GapPairingNone);
- bt_set_profile_mac_address(app->bt, app->mac);
- // disable key storage
+ bt_set_profile_mac_address(app->bt, app->config.bt_mac);
bt_disable_peer_key_update(app->bt);
}
bad_kb_reload_worker(app);
}
int32_t bad_kb_connection_init(BadKbApp* app) {
- app->usb_prev_mode = furi_hal_usb_get_config();
+ app->prev_config.usb_mode = furi_hal_usb_get_config();
furi_hal_usb_set_config(NULL, NULL);
+ strcpy(
+ app->prev_config.bt_name, furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard));
+ memcpy(
+ app->prev_config.bt_mac,
+ furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard),
+ BAD_KB_MAC_ADDRESS_LEN);
+ app->prev_config.bt_mode = furi_hal_bt_get_profile_pairing_method(FuriHalBtProfileHidKeyboard);
+
bt_timeout = bt_hid_delays[LevelRssi39_0];
bt_disconnect(app->bt);
- // furi_delay_ms(200);
bt_keys_storage_set_storage_path(app->bt, BAD_KB_APP_PATH_BOUND_KEYS_FILE);
- app->bt_prev_mode = furi_hal_bt_get_profile_pairing_method(FuriHalBtProfileHidKeyboard);
+ if(strcmp(app->config.bt_name, "") != 0) {
+ furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->config.bt_name);
+ }
if(app->bt_remember) {
- uint8_t mac[6] = BAD_KB_BOUND_MAC_ADDRESS;
- furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, mac);
- // using GapPairingNone breaks bounding between devices
+ furi_hal_bt_set_profile_mac_addr(
+ FuriHalBtProfileHidKeyboard, (uint8_t*)&BAD_KB_BOUND_MAC_ADDRESS);
furi_hal_bt_set_profile_pairing_method(
FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo);
} else {
+ if(memcmp(
+ app->config.bt_mac, (uint8_t*)&BAD_KB_EMPTY_MAC_ADDRESS, BAD_KB_MAC_ADDRESS_LEN) !=
+ 0) {
+ furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->config.bt_mac);
+ }
furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, GapPairingNone);
}
-
bt_set_profile(app->bt, BtProfileHidKeyboard);
+ if(strcmp(app->config.bt_name, "") == 0) {
+ strcpy(app->config.bt_name, furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard));
+ }
+ if(memcmp(app->config.bt_mac, (uint8_t*)&BAD_KB_EMPTY_MAC_ADDRESS, BAD_KB_MAC_ADDRESS_LEN) ==
+ 0) {
+ memcpy(
+ app->config.bt_mac,
+ furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard),
+ BAD_KB_MAC_ADDRESS_LEN);
+ }
if(app->is_bt) {
furi_hal_bt_start_advertising();
if(app->bt_remember) {
bt_enable_peer_key_update(app->bt);
} else {
- bt_disable_peer_key_update(app->bt); // disable peer key adding to bt SRAM storage
+ bt_disable_peer_key_update(app->bt);
}
} else {
furi_hal_bt_stop_advertising();
@@ -143,21 +186,15 @@ int32_t bad_kb_connection_init(BadKbApp* app) {
}
void bad_kb_connection_deinit(BadKbApp* app) {
- furi_hal_usb_set_config(app->usb_prev_mode, NULL);
+ furi_hal_usb_set_config(app->prev_config.usb_mode, NULL);
- // bt_hid_hold_while_keyboard_buffer_full(6, 3000); // release all keys
- bt_disconnect(app->bt); // stop ble
- // furi_delay_ms(200); // Wait 2nd core to update nvm storage
+ bt_disconnect(app->bt);
bt_keys_storage_set_default_path(app->bt);
- if(app->bt_remember) {
- // hal primitives doesn't restarts ble, that's what we want cuz we are shutting down
- furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->mac);
- }
- bt_enable_peer_key_update(app->bt); // starts saving peer keys (bounded devices)
- // fails if ble radio stack isn't ready when switching profile
- // if it happens, maybe we should increase the delay after bt_disconnect
+ furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->prev_config.bt_name);
+ furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->prev_config.bt_mac);
+ furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, app->prev_config.bt_mode);
bt_set_profile(app->bt, BtProfileSerial);
- furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, app->bt_prev_mode);
+ bt_enable_peer_key_update(app->bt);
}
BadKbApp* bad_kb_app_alloc(char* arg) {
@@ -199,14 +236,6 @@ BadKbApp* bad_kb_app_alloc(char* arg) {
app->bt->suppress_pin_screen = true;
app->is_bt = XTREME_SETTINGS()->bad_bt;
app->bt_remember = XTREME_SETTINGS()->bad_bt_remember;
- const char* adv_name = furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard);
- memcpy(app->name, adv_name, BAD_KB_ADV_NAME_MAX_LEN);
- memcpy(app->bt_old_config.name, adv_name, BAD_KB_ADV_NAME_MAX_LEN);
-
- // need to be done before bt init (where mac address get modified if bounding is activated)
- const uint8_t* mac_addr = furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard);
- memcpy(app->mac, mac_addr, BAD_KB_MAC_ADDRESS_LEN);
- memcpy(app->bt_old_config.mac, mac_addr, BAD_KB_MAC_ADDRESS_LEN);
// Custom Widget
app->widget = widget_alloc();
@@ -285,16 +314,7 @@ void bad_kb_app_free(BadKbApp* app) {
scene_manager_free(app->scene_manager);
// Restore bt config
- // BtProfile has already been switched to the previous one
- // So we directly modify the right profile
- if(strcmp(app->bt_old_config.name, app->name) != 0) {
- furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->bt_old_config.name);
- }
- if(memcmp(app->bt_old_config.mac, app->mac, BAD_KB_MAC_ADDRESS_LEN) != 0) {
- furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->bt_old_config.mac);
- }
app->bt->suppress_pin_screen = false;
-
if(app->conn_init_thread) {
furi_thread_join(app->conn_init_thread);
furi_thread_free(app->conn_init_thread);
diff --git a/applications/main/bad_kb/bad_kb_app.h b/applications/main/bad_kb/bad_kb_app.h
index 6aa133963..2da40a21f 100644
--- a/applications/main/bad_kb/bad_kb_app.h
+++ b/applications/main/bad_kb/bad_kb_app.h
@@ -21,11 +21,8 @@
#define BAD_KB_APP_SCRIPT_EXTENSION ".txt"
#define BAD_KB_APP_LAYOUT_EXTENSION ".kl"
-#define BAD_KB_MAC_ADDRESS_LEN 6 // need replace with MAC size maccro
-#define BAD_KB_ADV_NAME_MAX_LEN 18
-
-// this is the MAC address used when we do not forget paired device (BOUND STATE)
-#define BAD_KB_BOUND_MAC_ADDRESS {0x41, 0x4a, 0xef, 0xb6, 0xa9, 0xd4};
+#define BAD_KB_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH
+#define BAD_KB_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE
typedef enum {
BadKbAppErrorNoFiles,
@@ -39,10 +36,11 @@ typedef enum BadKbCustomEvent {
} BadKbCustomEvent;
typedef struct {
- //uint8_t bounded_mac[BAD_KB_MAC_ADDRESS_LEN];
- uint8_t mac[BAD_KB_MAC_ADDRESS_LEN];
- char name[BAD_KB_ADV_NAME_MAX_LEN + 1];
-} BadKbBtConfig;
+ char bt_name[BAD_KB_ADV_NAME_MAX_LEN + 1];
+ uint8_t bt_mac[BAD_KB_MAC_ADDRESS_LEN];
+ FuriHalUsbInterface* usb_mode;
+ GapPairing bt_mode;
+} BadKbConfig;
typedef struct {
Gui* gui;
@@ -52,14 +50,8 @@ typedef struct {
DialogsApp* dialogs;
Widget* widget;
VariableItemList* var_item_list;
-
- Bt* bt;
TextInput* text_input;
ByteInput* byte_input;
- uint8_t mac[BAD_KB_MAC_ADDRESS_LEN];
- char name[BAD_KB_ADV_NAME_MAX_LEN + 1];
- bool bt_remember; // weither we remember paired devices or not
- BadKbBtConfig bt_old_config;
BadKbAppError error;
FuriString* file_path;
@@ -67,11 +59,11 @@ typedef struct {
BadKb* bad_kb_view;
BadKbScript* bad_kb_script;
+ Bt* bt;
bool is_bt;
-
- FuriHalUsbInterface* usb_prev_mode;
- GapPairing bt_prev_mode;
-
+ bool bt_remember;
+ BadKbConfig config;
+ BadKbConfig prev_config;
FuriThread* conn_init_thread;
} BadKbApp;
diff --git a/applications/main/bad_kb/scenes/bad_kb_scene_config_mac.c b/applications/main/bad_kb/scenes/bad_kb_scene_config_mac.c
index c44935fb1..2b30c90ac 100644
--- a/applications/main/bad_kb/scenes/bad_kb_scene_config_mac.c
+++ b/applications/main/bad_kb/scenes/bad_kb_scene_config_mac.c
@@ -2,16 +2,6 @@
#define TAG "BadKbConfigMac"
-static uint8_t* reverse_mac_addr(uint8_t* mac, uint8_t* out) {
- uint8_t tmp;
- for(int i = 0; i < 3; i++) {
- tmp = mac[i];
- out[i] = mac[5 - i];
- out[5 - i] = tmp;
- }
- return out;
-}
-
void bad_kb_scene_config_mac_byte_input_callback(void* context) {
BadKbApp* bad_kb = context;
@@ -29,7 +19,7 @@ void bad_kb_scene_config_mac_on_enter(void* context) {
bad_kb_scene_config_mac_byte_input_callback,
NULL,
bad_kb,
- reverse_mac_addr(bad_kb->mac, bad_kb->mac),
+ bad_kb->config.bt_mac,
GAP_MAC_ADDR_SIZE);
view_dispatcher_switch_to_view(bad_kb->view_dispatcher, BadKbAppViewConfigMac);
}
@@ -40,8 +30,7 @@ bool bad_kb_scene_config_mac_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == BadKbAppCustomEventByteInputDone) {
- uint8_t mac[GAP_MAC_ADDR_SIZE];
- bt_set_profile_mac_address(bad_kb->bt, reverse_mac_addr(bad_kb->mac, mac));
+ bt_set_profile_mac_address(bad_kb->bt, bad_kb->config.bt_mac);
scene_manager_previous_scene(bad_kb->scene_manager);
consumed = true;
}
@@ -55,7 +44,4 @@ void bad_kb_scene_config_mac_on_exit(void* context) {
// Clear view
byte_input_set_result_callback(bad_kb->byte_input, NULL, NULL, NULL, NULL, 0);
byte_input_set_header_text(bad_kb->byte_input, "");
-
- // reverse back addr (in case it didn't get modified)
- reverse_mac_addr(bad_kb->mac, bad_kb->mac);
}
diff --git a/applications/main/bad_kb/scenes/bad_kb_scene_config_name.c b/applications/main/bad_kb/scenes/bad_kb_scene_config_name.c
index 108dd9689..40bfc4be8 100644
--- a/applications/main/bad_kb/scenes/bad_kb_scene_config_name.c
+++ b/applications/main/bad_kb/scenes/bad_kb_scene_config_name.c
@@ -16,7 +16,7 @@ void bad_kb_scene_config_name_on_enter(void* context) {
text_input,
bad_kb_scene_config_name_text_input_callback,
bad_kb,
- bad_kb->name,
+ bad_kb->config.bt_name,
BAD_KB_ADV_NAME_MAX_LEN,
true);
@@ -30,7 +30,7 @@ bool bad_kb_scene_config_name_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == BadKbAppCustomEventTextEditResult) {
- bt_set_profile_adv_name(bad_kb->bt, bad_kb->name);
+ bt_set_profile_adv_name(bad_kb->bt, bad_kb->config.bt_name);
}
scene_manager_previous_scene(bad_kb->scene_manager);
}
diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c
index 6caaa9639..391efa80c 100644
--- a/applications/main/fap_loader/fap_loader_app.c
+++ b/applications/main/fap_loader/fap_loader_app.c
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#define TAG "FapLoader"
@@ -30,6 +31,18 @@ bool fap_loader_load_name_and_icon(
Storage* storage,
uint8_t** icon_ptr,
FuriString* item_name) {
+ StorageData* storage_data;
+ if(storage_get_data(storage, path, &storage_data) == FSE_OK &&
+ storage_path_already_open(path, storage_data)) {
+ size_t offset = furi_string_search_rchar(path, '/');
+ if(offset != FURI_STRING_FAILURE) {
+ furi_string_set_n(item_name, path, offset + 1, furi_string_size(path) - offset - 1);
+ } else {
+ furi_string_set(item_name, path);
+ }
+ return false;
+ }
+
FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface);
FlipperApplicationPreloadStatus preload_res =
@@ -37,7 +50,8 @@ bool fap_loader_load_name_and_icon(
bool load_success = false;
- if(preload_res == FlipperApplicationPreloadStatusSuccess) {
+ if(preload_res == FlipperApplicationPreloadStatusSuccess ||
+ preload_res == FlipperApplicationPreloadStatusApiMismatch) {
const FlipperApplicationManifest* manifest = flipper_application_get_manifest(app);
if(manifest->has_icon && icon_ptr != NULL && *icon_ptr != NULL) {
memcpy(*icon_ptr, manifest->icon, FAP_MANIFEST_MAX_ICON_SIZE);
@@ -46,6 +60,12 @@ bool fap_loader_load_name_and_icon(
load_success = true;
} else {
FURI_LOG_E(TAG, "FAP Loader failed to preload %s", furi_string_get_cstr(path));
+ size_t offset = furi_string_search_rchar(path, '/');
+ if(offset != FURI_STRING_FAILURE) {
+ furi_string_set_n(item_name, path, offset + 1, furi_string_size(path) - offset - 1);
+ } else {
+ furi_string_set(item_name, path);
+ }
load_success = false;
}
diff --git a/applications/main/gpio/views/gpio_usb_uart.c b/applications/main/gpio/views/gpio_usb_uart.c
index 837f2e3ec..3234309a2 100644
--- a/applications/main/gpio/views/gpio_usb_uart.c
+++ b/applications/main/gpio/views/gpio_usb_uart.c
@@ -80,9 +80,9 @@ static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) {
canvas_draw_icon(canvas, 48, 14, &I_ArrowUpEmpty_14x15);
if(model->rx_active)
- canvas_draw_icon(canvas, 48, 34, &I_ArrowDownFilled_14x15);
+ canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180);
else
- canvas_draw_icon(canvas, 48, 34, &I_ArrowDownEmpty_14x15);
+ canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpEmpty_14x15, IconRotation180);
}
static bool gpio_usb_uart_input_callback(InputEvent* event, void* context) {
diff --git a/applications/main/onewire/onewire_cli.c b/applications/main/onewire/onewire_cli.c
index 4c16fb389..5f6cdc670 100644
--- a/applications/main/onewire/onewire_cli.c
+++ b/applications/main/onewire/onewire_cli.c
@@ -25,7 +25,7 @@ static void onewire_cli_print_usage() {
static void onewire_cli_search(Cli* cli) {
UNUSED(cli);
- OneWireHost* onewire = onewire_host_alloc(&ibutton_gpio);
+ OneWireHost* onewire = onewire_host_alloc(&gpio_ibutton);
uint8_t address[8];
bool done = false;
diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h
index eb0ed70e7..9d2be7ee5 100644
--- a/applications/main/subghz/helpers/subghz_custom_event.h
+++ b/applications/main/subghz/helpers/subghz_custom_event.h
@@ -13,6 +13,8 @@ typedef enum {
SubmenuIndexSomfyTelis,
SubmenuIndexBeninca433,
SubmenuIndexBeninca868,
+ SubmenuIndexAllmatic433,
+ SubmenuIndexAllmatic868,
SubmenuIndexIronLogic,
SubmenuIndexElmesElectronic,
SubmenuIndexSommer_FM_434,
diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h
index 3c5982427..270fd7a21 100644
--- a/applications/main/subghz/helpers/subghz_types.h
+++ b/applications/main/subghz/helpers/subghz_types.h
@@ -35,6 +35,12 @@ typedef enum {
SubGhzSpeakerStateEnable,
} SubGhzSpeakerState;
+/** SubGhzStarLineIgnore state */
+typedef enum {
+ SubGhzStarLineIgnoreDisable,
+ SubGhzStarLineIgnoreEnable,
+} SubGhzStarLineIgnoreState;
+
/** SubGhzRxKeyState state */
typedef enum {
SubGhzRxKeyStateIDLE,
diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c
index c0112199c..08fed3aa4 100644
--- a/applications/main/subghz/scenes/subghz_scene_receiver.c
+++ b/applications/main/subghz/scenes/subghz_scene_receiver.c
@@ -147,6 +147,16 @@ void subghz_scene_receiver_on_enter(void* context) {
subghz_receiver_set_rx_callback(
subghz->txrx->receiver, subghz_scene_add_to_history_callback, subghz);
+ if(subghz->txrx->starline_state == SubGhzStarLineIgnoreEnable) {
+ SubGhzProtocolDecoderBase* protocoldecoderbase = NULL;
+ protocoldecoderbase =
+ subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, "Star Line");
+ if(protocoldecoderbase) {
+ subghz_protocol_decoder_base_set_decoder_callback(
+ protocoldecoderbase, NULL, subghz->txrx->receiver);
+ }
+ }
+
subghz->state_notifications = SubGhzNotificationStateRx;
if(subghz->txrx->txrx_state == SubGhzTxRxStateRx) {
subghz_rx_end(subghz);
diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c
index ff51e2f4d..6a6cf860b 100644
--- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c
+++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c
@@ -6,6 +6,7 @@ enum SubGhzSettingIndex {
SubGhzSettingIndexHopping,
SubGhzSettingIndexModulation,
SubGhzSettingIndexBinRAW,
+ SubGhzSettingIndexIgnoreStarline,
SubGhzSettingIndexSound,
SubGhzSettingIndexLock,
SubGhzSettingIndexRAWThresholdRSSI,
@@ -68,6 +69,15 @@ const uint32_t bin_raw_value[BIN_RAW_COUNT] = {
SubGhzProtocolFlag_Decodable,
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_BinRAW,
};
+#define STAR_LINE_COUNT 2
+const char* const star_line_text[STAR_LINE_COUNT] = {
+ "OFF",
+ "ON",
+};
+const uint32_t star_line_value[STAR_LINE_COUNT] = {
+ SubGhzStarLineIgnoreDisable,
+ SubGhzStarLineIgnoreEnable,
+};
uint8_t subghz_scene_receiver_config_next_frequency(const uint32_t value, void* context) {
furi_assert(context);
@@ -217,6 +227,14 @@ static void subghz_scene_receiver_config_set_raw_threshold_rssi(VariableItem* it
subghz->txrx->raw_threshold_rssi = raw_threshold_rssi_value[index];
}
+static void subghz_scene_receiver_config_set_starline(VariableItem* item) {
+ SubGhz* subghz = variable_item_get_context(item);
+ uint8_t index = variable_item_get_current_value_index(item);
+
+ variable_item_set_current_value_text(item, star_line_text[index]);
+ subghz->txrx->starline_state = star_line_value[index];
+}
+
static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
furi_assert(context);
SubGhz* subghz = context;
@@ -291,6 +309,21 @@ void subghz_scene_receiver_config_on_enter(void* context) {
variable_item_set_current_value_text(item, bin_raw_text[value_index]);
}
+ if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
+ SubGhzCustomEventManagerSet) {
+ item = variable_item_list_add(
+ subghz->variable_item_list,
+ "Ignore StarLine:",
+ STAR_LINE_COUNT,
+ subghz_scene_receiver_config_set_starline,
+ subghz);
+
+ value_index =
+ value_index_uint32(subghz->txrx->starline_state, star_line_value, STAR_LINE_COUNT);
+ variable_item_set_current_value_index(item, value_index);
+ variable_item_set_current_value_text(item, star_line_text[value_index]);
+ }
+
// Enable speaker, will send all incoming noises and signals to speaker so you can listen how your remote sounds like :)
item = variable_item_list_add(
subghz->variable_item_list,
diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c
index 978954181..99765ee16 100644
--- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c
+++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c
@@ -2,10 +2,8 @@
#include "../helpers/subghz_custom_event.h"
#include
#include
-#include
-#include
-#include
-#include
+
+#include
void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) {
furi_assert(context);
@@ -238,10 +236,7 @@ void subghz_scene_receiver_info_on_exit(void* context) {
keeloq_reset_mfname();
keeloq_reset_kl_type();
keeloq_reset_original_btn();
- alutech_reset_original_btn();
- nice_flors_reset_original_btn();
- somfy_telis_reset_original_btn();
- secplus2_reset_original_btn();
+ subghz_custom_btns_reset();
star_line_reset_mfname();
star_line_reset_kl_type();
}
diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c
index 3e9ac11bc..e313ab3c2 100644
--- a/applications/main/subghz/scenes/subghz_scene_rpc.c
+++ b/applications/main/subghz/scenes/subghz_scene_rpc.c
@@ -1,10 +1,8 @@
#include "../subghz_i.h"
#include
#include
-#include
-#include
-#include
-#include
+
+#include
#include "xtreme/assets.h"
@@ -116,10 +114,7 @@ void subghz_scene_rpc_on_exit(void* context) {
keeloq_reset_mfname();
keeloq_reset_kl_type();
keeloq_reset_original_btn();
- alutech_reset_original_btn();
- nice_flors_reset_original_btn();
- somfy_telis_reset_original_btn();
- secplus2_reset_original_btn();
+ subghz_custom_btns_reset();
star_line_reset_mfname();
star_line_reset_kl_type();
}
diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c
index b95eee8c9..431d5d2d7 100644
--- a/applications/main/subghz/scenes/subghz_scene_set_type.c
+++ b/applications/main/subghz/scenes/subghz_scene_set_type.c
@@ -60,6 +60,43 @@ bool subghz_scene_set_type_submenu_gen_data_protocol(
return res;
}
+bool subghz_scene_set_type_submenu_gen_data_keeloq(
+ void* context,
+ const char* preset_name,
+ uint32_t frequency,
+ uint32_t serial,
+ uint8_t btn,
+ uint16_t cnt,
+ const char* manufacture_name) {
+ SubGhz* subghz = context;
+
+ bool res = false;
+
+ subghz->txrx->transmitter =
+ subghz_transmitter_alloc_init(subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
+ subghz_preset_init(subghz, preset_name, frequency, NULL, 0);
+
+ if(subghz->txrx->transmitter &&
+ subghz_protocol_keeloq_create_data(
+ subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
+ subghz->txrx->fff_data,
+ serial,
+ btn,
+ cnt,
+ manufacture_name,
+ subghz->txrx->preset)) {
+ flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", manufacture_name);
+ res = true;
+ }
+
+ subghz_transmitter_free(subghz->txrx->transmitter);
+ if(!res) {
+ furi_string_set(subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
+ scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
+ }
+ return res;
+}
+
void subghz_scene_set_type_submenu_callback(void* context, uint32_t index) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
@@ -134,6 +171,18 @@ void subghz_scene_set_type_on_enter(void* context) {
SubmenuIndexBeninca868,
subghz_scene_set_type_submenu_callback,
subghz);
+ submenu_add_item(
+ subghz->submenu,
+ "KL: Allmatic 433MHz",
+ SubmenuIndexAllmatic433,
+ subghz_scene_set_type_submenu_callback,
+ subghz);
+ submenu_add_item(
+ subghz->submenu,
+ "KL: Allmatic 868MHz",
+ SubmenuIndexAllmatic868,
+ subghz_scene_set_type_submenu_callback,
+ subghz);
submenu_add_item(
subghz->submenu,
"KL: Sommer 434MHz",
@@ -445,310 +494,129 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
}
break;
case SubmenuIndexBeninca433:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- (key & 0x000FFF00) | 0x00800080,
- 0x1,
- 0x0005,
- "Beninca",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "Beninca");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz,
+ "AM650",
+ 433920000,
+ (key & 0x000FFF00) | 0x00800080,
+ 0x1,
+ 0x0005,
+ "Beninca")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexBeninca868:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 868350000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- (key & 0x000FFF00) | 0x00800080,
- 0x1,
- 0x0005,
- "Beninca",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "Beninca");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz,
+ "AM650",
+ 868350000,
+ (key & 0x000FFF00) | 0x00800080,
+ 0x1,
+ 0x0005,
+ "Beninca")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
}
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
+ break;
+ case SubmenuIndexAllmatic433:
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz,
+ "AM650",
+ 433920000,
+ (key & 0x00FFFF00) | 0x01000011,
+ 0xC,
+ 0x0005,
+ "Beninca")) {
+ generated_protocol = true;
+ }
+ break;
+ case SubmenuIndexAllmatic868:
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz,
+ "AM650",
+ 868350000,
+ (key & 0x00FFFF00) | 0x01000011,
+ 0xC,
+ 0x0005,
+ "Beninca")) {
+ generated_protocol = true;
}
break;
case SubmenuIndexElmesElectronic:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- (key & 0x00FFFFFF) | 0x02000000,
- 0x2,
- 0x0003,
- "Elmes_Poland",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(
- subghz->txrx->fff_data, "Manufacture", "Elmes_Poland");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz,
+ "AM650",
+ 433920000,
+ (key & 0x00FFFFFF) | 0x02000000,
+ 0x2,
+ 0x0003,
+ "Elmes_Poland")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexANMotorsAT4:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- (key & 0x00FFFFFF) | 0x04000000,
- 0x2,
- 0x0021,
- "AN-Motors",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(
- subghz->txrx->fff_data, "Manufacture", "AN-Motors");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz,
+ "AM650",
+ 433920000,
+ (key & 0x000FFFFF) | 0x04700000,
+ 0x2,
+ 0x0021,
+ "AN-Motors")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexAprimatic:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- (key & 0x000FFFFF) | 0x00600000,
- 0x4,
- 0x0003,
- "Aprimatic",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(
- subghz->txrx->fff_data, "Manufacture", "Aprimatic");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz,
+ "AM650",
+ 433920000,
+ (key & 0x000FFFFF) | 0x00600000,
+ 0x4,
+ 0x0003,
+ "Aprimatic")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexGibidi433:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- key & 0x00FFFFFF,
- 0x2,
- 0x0003,
- "Gibidi",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "Gibidi");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz, "AM650", 433920000, key & 0x00FFFFFF, 0x2, 0x0003, "Gibidi")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexGSN:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- key & 0x0FFFFFFF,
- 0x2,
- 0x0003,
- "GSN",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "GSN");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz, "AM650", 433920000, key & 0x0FFFFFFF, 0x2, 0x0003, "GSN")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexIronLogic:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- key & 0x00FFFFF0,
- 0x4,
- 0x0005,
- "IronLogic",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(
- subghz->txrx->fff_data, "Manufacture", "IronLogic");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz, "AM650", 433920000, key & 0x00FFFFF0, 0x4, 0x0005, "IronLogic")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexSommer_FM_434:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "FM476", 434420000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- key & 0x0FFFFFFF,
- 0x4,
- 0x0003,
- "Sommer(fsk476)",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(
- subghz->txrx->fff_data, "Manufacture", "Sommer(fsk476)");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz, "FM476", 434420000, key & 0x0FFFFFFF, 0x4, 0x0003, "Sommer(fsk476)")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexSommer_FM_868:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "FM476", 868800000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- key & 0x0FFFFFFF,
- 0x4,
- 0x0003,
- "Sommer(fsk476)",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(
- subghz->txrx->fff_data, "Manufacture", "Sommer(fsk476)");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz, "FM476", 868800000, key & 0x0FFFFFFF, 0x4, 0x0003, "Sommer(fsk476)")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexDTMNeo433:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- key & 0x000FFFFF,
- 0x2,
- 0x0005,
- "DTM_Neo",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "DTM_Neo");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz, "AM650", 433920000, key & 0x000FFFFF, 0x2, 0x0005, "DTM_Neo")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexCAMESpace:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- key & 0x00FFFFFF,
- 0x2,
- 0x0003,
- "Came_Space",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(
- subghz->txrx->fff_data, "Manufacture", "Came_Space");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz, "AM650", 433920000, key & 0x00FFFFFF, 0x2, 0x0003, "Came_Space")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexBFTMitto:
@@ -834,53 +702,15 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
}
break;
case SubmenuIndexDoorHan_433_92:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- key & 0x0FFFFFFF,
- 0x2,
- 0x0003,
- "DoorHan",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "DoorHan");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz, "AM650", 433920000, key & 0x0FFFFFFF, 0x2, 0x0003, "DoorHan")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexDoorHan_315_00:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 315000000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- key & 0x0FFFFFFF,
- 0x2,
- 0x0003,
- "DoorHan",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(subghz->txrx->fff_data, "Manufacture", "DoorHan");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz, "AM650", 315000000, key & 0x0FFFFFFF, 0x2, 0x0003, "DoorHan")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexNiceFlorS_433_92:
@@ -932,29 +762,9 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
}
break;
case SubmenuIndexNiceSmilo_433_92:
- subghz->txrx->transmitter = subghz_transmitter_alloc_init(
- subghz->txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
- subghz_preset_init(subghz, "AM650", 433920000, NULL, 0);
- if(subghz->txrx->transmitter) {
- subghz_protocol_keeloq_create_data(
- subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter),
- subghz->txrx->fff_data,
- key & 0x00FFFFFF,
- 0x2,
- 0x0003,
- "NICE_Smilo",
- subghz->txrx->preset);
- flipper_format_write_string_cstr(
- subghz->txrx->fff_data, "Manufacture", "NICE_Smilo");
+ if(subghz_scene_set_type_submenu_gen_data_keeloq(
+ subghz, "AM650", 433920000, key & 0x00FFFFFF, 0x2, 0x0003, "NICE_Smilo")) {
generated_protocol = true;
- } else {
- generated_protocol = false;
- }
- subghz_transmitter_free(subghz->txrx->transmitter);
- if(!generated_protocol) {
- furi_string_set(
- subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
- scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError);
}
break;
case SubmenuIndexLiftMaster_315_00:
diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c
index e32e2b7b2..7895d8bc3 100644
--- a/applications/main/subghz/scenes/subghz_scene_transmitter.c
+++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c
@@ -2,11 +2,9 @@
#include "../views/transmitter.h"
#include
#include
-#include
#include
-#include
-#include
-#include
+
+#include
void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) {
furi_assert(context);
@@ -91,12 +89,8 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
subghz_tx_stop(subghz);
subghz_sleep(subghz);
}
- if(keeloq_get_custom_btn() != 0) {
- keeloq_set_btn(0);
- alutech_set_btn(0);
- nice_flors_set_btn(0);
- somfy_telis_set_btn(0);
- secplus2_set_btn(0);
+ if(subghz_custom_btn_get() != 0) {
+ subghz_custom_btn_set(0);
uint8_t tmp_counter = furi_hal_subghz_get_rolling_counter_mult();
furi_hal_subghz_set_rolling_counter_mult(0);
// Calling restore!
@@ -138,10 +132,7 @@ void subghz_scene_transmitter_on_exit(void* context) {
keeloq_reset_mfname();
keeloq_reset_kl_type();
keeloq_reset_original_btn();
- alutech_reset_original_btn();
- nice_flors_reset_original_btn();
- somfy_telis_reset_original_btn();
- secplus2_reset_original_btn();
+ subghz_custom_btns_reset();
star_line_reset_mfname();
star_line_reset_kl_type();
}
diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c
index 579a16520..c1c17c374 100644
--- a/applications/main/subghz/subghz_i.c
+++ b/applications/main/subghz/subghz_i.c
@@ -611,7 +611,7 @@ void subghz_hopper_update(SubGhz* subghz) {
void subghz_speaker_on(SubGhz* subghz) {
if(subghz->txrx->debug_pin_state) {
- furi_hal_subghz_set_async_mirror_pin(&ibutton_gpio);
+ furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton);
}
if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) {
@@ -656,7 +656,7 @@ void subghz_speaker_mute(SubGhz* subghz) {
void subghz_speaker_unmute(SubGhz* subghz) {
if(subghz->txrx->debug_pin_state) {
- furi_hal_subghz_set_async_mirror_pin(&ibutton_gpio);
+ furi_hal_subghz_set_async_mirror_pin(&gpio_ibutton);
}
if(subghz->txrx->speaker_state == SubGhzSpeakerStateEnable) {
if(furi_hal_speaker_is_mine()) {
diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h
index 393dd667d..adabe4aad 100644
--- a/applications/main/subghz/subghz_i.h
+++ b/applications/main/subghz/subghz_i.h
@@ -73,6 +73,7 @@ struct SubGhzTxRx {
SubGhzTxRxState txrx_state;
SubGhzHopperState hopper_state;
SubGhzSpeakerState speaker_state;
+ SubGhzStarLineIgnoreState starline_state;
uint8_t hopper_timeout;
uint8_t hopper_idx_frequency;
SubGhzRxKeyState rx_key_state;
diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c
index 7de0e6495..e8c53d1ed 100644
--- a/applications/main/subghz/views/transmitter.c
+++ b/applications/main/subghz/views/transmitter.c
@@ -4,11 +4,7 @@
#include
#include
-#include
-#include
-#include
-#include
-#include
+#include
struct SubGhzViewTransmitter {
View* view;
@@ -160,30 +156,18 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) {
// Temp Buttons (UP)
if(can_be_sent && event->key == InputKeyUp && event->type == InputTypePress) {
- keeloq_set_btn(1);
- alutech_set_btn(1);
- nice_flors_set_btn(1);
- somfy_telis_set_btn(1);
- secplus2_set_btn(1);
+ subghz_custom_btn_set(1);
with_view_model(
subghz_transmitter->view,
SubGhzViewTransmitterModel * model,
{
furi_string_reset(model->temp_button_id);
- if(keeloq_get_original_btn() != 0) {
- furi_string_printf(model->temp_button_id, "%01X", keeloq_get_original_btn());
- model->draw_temp_button = true;
- } else if(alutech_get_original_btn() != 0) {
- furi_string_printf(model->temp_button_id, "%01X", alutech_get_original_btn());
- model->draw_temp_button = true;
- } else if(nice_flors_get_original_btn() != 0) {
- furi_string_printf(
- model->temp_button_id, "%01X", nice_flors_get_original_btn());
- model->draw_temp_button = true;
- } else if(somfy_telis_get_original_btn() != 0) {
- furi_string_printf(
- model->temp_button_id, "%01X", somfy_telis_get_original_btn());
- model->draw_temp_button = true;
+ if(subghz_custom_btn_get_original() != 0) {
+ if(subghz_custom_btn_get() == 1) {
+ furi_string_printf(
+ model->temp_button_id, "%01X", subghz_custom_btn_get_original());
+ model->draw_temp_button = true;
+ }
}
},
true);
@@ -197,30 +181,18 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) {
}
// Down
if(can_be_sent && event->key == InputKeyDown && event->type == InputTypePress) {
- keeloq_set_btn(2);
- alutech_set_btn(2);
- nice_flors_set_btn(2);
- somfy_telis_set_btn(2);
- secplus2_set_btn(2);
+ subghz_custom_btn_set(2);
with_view_model(
subghz_transmitter->view,
SubGhzViewTransmitterModel * model,
{
furi_string_reset(model->temp_button_id);
- if(keeloq_get_original_btn() != 0) {
- furi_string_printf(model->temp_button_id, "%01X", keeloq_get_original_btn());
- model->draw_temp_button = true;
- } else if(alutech_get_original_btn() != 0) {
- furi_string_printf(model->temp_button_id, "%01X", alutech_get_original_btn());
- model->draw_temp_button = true;
- } else if(nice_flors_get_original_btn() != 0) {
- furi_string_printf(
- model->temp_button_id, "%01X", nice_flors_get_original_btn());
- model->draw_temp_button = true;
- } else if(somfy_telis_get_original_btn() != 0) {
- furi_string_printf(
- model->temp_button_id, "%01X", somfy_telis_get_original_btn());
- model->draw_temp_button = true;
+ if(subghz_custom_btn_get_original() != 0) {
+ if(subghz_custom_btn_get() == 2) {
+ furi_string_printf(
+ model->temp_button_id, "%01X", subghz_custom_btn_get_original());
+ model->draw_temp_button = true;
+ }
}
},
true);
@@ -234,30 +206,18 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) {
}
// Left
if(can_be_sent && event->key == InputKeyLeft && event->type == InputTypePress) {
- keeloq_set_btn(3);
- alutech_set_btn(3);
- nice_flors_set_btn(3);
- somfy_telis_set_btn(3);
- secplus2_set_btn(3);
+ subghz_custom_btn_set(3);
with_view_model(
subghz_transmitter->view,
SubGhzViewTransmitterModel * model,
{
furi_string_reset(model->temp_button_id);
- if(keeloq_get_original_btn() != 0) {
- furi_string_printf(model->temp_button_id, "%01X", keeloq_get_original_btn());
- model->draw_temp_button = true;
- } else if(alutech_get_original_btn() != 0) {
- furi_string_printf(model->temp_button_id, "%01X", alutech_get_original_btn());
- model->draw_temp_button = true;
- } else if(nice_flors_get_original_btn() != 0) {
- furi_string_printf(
- model->temp_button_id, "%01X", nice_flors_get_original_btn());
- model->draw_temp_button = true;
- } else if(somfy_telis_get_original_btn() != 0) {
- furi_string_printf(
- model->temp_button_id, "%01X", somfy_telis_get_original_btn());
- model->draw_temp_button = true;
+ if(subghz_custom_btn_get_original() != 0) {
+ if(subghz_custom_btn_get() == 3) {
+ furi_string_printf(
+ model->temp_button_id, "%01X", subghz_custom_btn_get_original());
+ model->draw_temp_button = true;
+ }
}
},
true);
@@ -271,19 +231,18 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) {
}
// Right
if(can_be_sent && event->key == InputKeyRight && event->type == InputTypePress) {
- keeloq_set_btn(4);
- alutech_set_btn(4);
+ subghz_custom_btn_set(4);
with_view_model(
subghz_transmitter->view,
SubGhzViewTransmitterModel * model,
{
furi_string_reset(model->temp_button_id);
- if(keeloq_get_original_btn() != 0) {
- furi_string_printf(model->temp_button_id, "%01X", keeloq_get_original_btn());
- model->draw_temp_button = true;
- } else if(alutech_get_original_btn() != 0) {
- furi_string_printf(model->temp_button_id, "%01X", alutech_get_original_btn());
- model->draw_temp_button = true;
+ if(subghz_custom_btn_get_original() != 0) {
+ if(subghz_custom_btn_get() == 4) {
+ furi_string_printf(
+ model->temp_button_id, "%01X", subghz_custom_btn_get_original());
+ model->draw_temp_button = true;
+ }
}
},
true);
diff --git a/applications/main/xtreme_app/scenes/xtreme_app_scene_interface_mainmenu_add.c b/applications/main/xtreme_app/scenes/xtreme_app_scene_interface_mainmenu_add.c
index cc866ce00..57fa78090 100644
--- a/applications/main/xtreme_app/scenes/xtreme_app_scene_interface_mainmenu_add.c
+++ b/applications/main/xtreme_app/scenes/xtreme_app_scene_interface_mainmenu_add.c
@@ -9,8 +9,7 @@ static bool xtreme_app_scene_interface_mainmenu_add_file_browser_callback(
void* context,
uint8_t** icon_ptr,
FuriString* item_name) {
- XtremeApp* app = context;
- if(furi_string_end_with(file_path, app->fap_name)) return false;
+ UNUSED(context);
Storage* storage = furi_record_open(RECORD_STORAGE);
bool success = fap_loader_load_name_and_icon(file_path, storage, icon_ptr, item_name);
furi_record_close(RECORD_STORAGE);
diff --git a/applications/main/xtreme_app/xtreme_app.c b/applications/main/xtreme_app/xtreme_app.c
index 005d4ece1..eabd25581 100644
--- a/applications/main/xtreme_app/xtreme_app.c
+++ b/applications/main/xtreme_app/xtreme_app.c
@@ -322,7 +322,6 @@ void xtreme_app_free(XtremeApp* app) {
extern int32_t xtreme_app(void* p) {
UNUSED(p);
XtremeApp* app = xtreme_app_alloc();
- snprintf(app->fap_name, 32, "%s.fap", furi_thread_get_appid(furi_thread_get_current_id()));
scene_manager_next_scene(app->scene_manager, XtremeAppSceneStart);
view_dispatcher_run(app->view_dispatcher);
xtreme_app_free(app);
diff --git a/applications/main/xtreme_app/xtreme_app.h b/applications/main/xtreme_app/xtreme_app.h
index 759afd769..a352d86ee 100644
--- a/applications/main/xtreme_app/xtreme_app.h
+++ b/applications/main/xtreme_app/xtreme_app.h
@@ -31,7 +31,6 @@
ARRAY_DEF(CharList, char*)
typedef struct {
- char fap_name[32];
Gui* gui;
DialogsApp* dialogs;
NotificationApp* notification;
diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c
index e5f561ec6..9cb9f1b24 100644
--- a/applications/services/bt/bt_service/bt.c
+++ b/applications/services/bt/bt_service/bt.c
@@ -402,7 +402,7 @@ void bt_set_profile_adv_name(Bt* bt, const char* fmt, ...) {
furi_assert(bt);
furi_assert(fmt);
- char name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH];
+ char name[FURI_HAL_BT_ADV_NAME_LENGTH];
va_list args;
va_start(args, fmt);
vsnprintf(name, sizeof(name), fmt, args);
diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c
index dab7fefc3..a578ecfcb 100644
--- a/applications/services/desktop/desktop.c
+++ b/applications/services/desktop/desktop.c
@@ -36,7 +36,7 @@ static void desktop_loader_callback(const void* message, void* context) {
static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) {
UNUSED(context);
furi_assert(canvas);
- canvas_draw_icon(canvas, 0, 0, &I_Lock_8x8);
+ canvas_draw_icon(canvas, 0, 0, &I_Lock_7x8);
}
static bool desktop_custom_event_callback(void* context, uint32_t event) {
@@ -216,7 +216,7 @@ Desktop* desktop_alloc() {
// Lock icon
desktop->lock_icon_viewport = view_port_alloc();
- view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_8x8));
+ view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_7x8));
view_port_draw_callback_set(
desktop->lock_icon_viewport, desktop_lock_icon_draw_callback, desktop);
view_port_enabled_set(desktop->lock_icon_viewport, false);
diff --git a/applications/services/desktop/scenes/desktop_scene_lock_menu.c b/applications/services/desktop/scenes/desktop_scene_lock_menu.c
index 05c397f18..3b2309844 100644
--- a/applications/services/desktop/scenes/desktop_scene_lock_menu.c
+++ b/applications/services/desktop/scenes/desktop_scene_lock_menu.c
@@ -57,7 +57,7 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeTick) {
- bool check_pin_changed =
+ int check_pin_changed =
scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLockMenu);
if(check_pin_changed) {
DESKTOP_SETTINGS_LOAD(&desktop->settings);
@@ -66,6 +66,12 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0);
desktop_pin_lock(&desktop->settings);
desktop_lock(desktop);
+ if(check_pin_changed == 2) {
+ Power* power = furi_record_open(RECORD_POWER);
+ furi_delay_ms(500);
+ power_off(power);
+ furi_record_close(RECORD_POWER);
+ }
}
}
} else if(event.type == SceneManagerEventTypeCustom) {
@@ -97,6 +103,26 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
}
consumed = true;
break;
+ case DesktopLockMenuEventLockPinOff:
+ desktop_scene_lock_menu_save_settings(desktop);
+ if(desktop->settings.pin_code.length > 0) {
+ desktop_pin_lock(&desktop->settings);
+ desktop_lock(desktop);
+ Power* power = furi_record_open(RECORD_POWER);
+ furi_delay_ms(500);
+ power_off(power);
+ furi_record_close(RECORD_POWER);
+ } else {
+ LoaderStatus status =
+ loader_start(desktop->loader, "Desktop", DESKTOP_SETTINGS_RUN_PIN_SETUP_ARG);
+ if(status == LoaderStatusOk) {
+ scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 2);
+ } else {
+ FURI_LOG_E(TAG, "Unable to start desktop settings");
+ }
+ }
+ consumed = true;
+ break;
case DesktopLockMenuEventXtreme:
desktop_scene_lock_menu_save_settings(desktop);
loader_start(
diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c
index a34eba444..8be63bfe3 100644
--- a/applications/services/desktop/scenes/desktop_scene_main.c
+++ b/applications/services/desktop/scenes/desktop_scene_main.c
@@ -61,19 +61,6 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl
}
#endif
-static void desktop_scene_main_open_app_or_profile(Desktop* desktop, const char* path) {
- do {
- LoaderStatus status = loader_start(desktop->loader, FAP_LOADER_APP_NAME, path);
- if(status == LoaderStatusOk) break;
- FURI_LOG_E(TAG, "loader_start failed: %d", status);
-
- status = loader_start(desktop->loader, "Passport", NULL);
- if(status != LoaderStatusOk) {
- FURI_LOG_E(TAG, "loader_start failed: %d", status);
- }
- } while(false);
-}
-
void desktop_scene_main_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context;
if(desktop->in_transition) return;
@@ -205,7 +192,11 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
break;
}
case DesktopMainEventOpenClock: {
- desktop_scene_main_open_app_or_profile(desktop, EXT_PATH("/apps/Misc/Nightstand.fap"));
+ LoaderStatus status = loader_start(
+ desktop->loader, FAP_LOADER_APP_NAME, EXT_PATH("apps/Misc/Nightstand.fap"));
+ if(status != LoaderStatusOk) {
+ FURI_LOG_E(TAG, "loader_start failed: %d", status);
+ }
break;
}
case DesktopLockedEventUpdate:
diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h
index e3d4e2d7b..9e4a3bb4d 100644
--- a/applications/services/desktop/views/desktop_events.h
+++ b/applications/services/desktop/views/desktop_events.h
@@ -42,6 +42,7 @@ typedef enum {
DesktopLockMenuEventSettings,
DesktopLockMenuEventLock,
DesktopLockMenuEventLockPin,
+ DesktopLockMenuEventLockPinOff,
DesktopLockMenuEventXtreme,
DesktopAnimationEventCheckAnimation,
diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c
index 1275a48e4..0ccc4f326 100644
--- a/applications/services/desktop/views/desktop_view_lock_menu.c
+++ b/applications/services/desktop/views/desktop_view_lock_menu.c
@@ -160,10 +160,11 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) {
if(m->show_lock_menu) {
canvas_set_font(canvas, FontSecondary);
- elements_bold_rounded_frame(canvas, 24, 10, 80, 44);
- canvas_draw_str_aligned(canvas, 64, 24, AlignCenter, AlignCenter, "Keypad Lock");
- canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "PIN Code Lock");
- elements_frame(canvas, 30, m->pin_lock ? 32 : 16, 68, 15);
+ elements_bold_rounded_frame(canvas, 24, 4, 80, 56);
+ canvas_draw_str_aligned(canvas, 64, 16, AlignCenter, AlignCenter, "Keypad Lock");
+ canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, "PIN Code Lock");
+ canvas_draw_str_aligned(canvas, 64, 48, AlignCenter, AlignCenter, "PIN Lock + OFF");
+ elements_frame(canvas, 28, 8 + m->pin_lock * 16, 72, 15);
}
}
@@ -178,7 +179,7 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) {
DesktopLockMenuView* lock_menu = context;
uint8_t idx = 0;
- bool pin_lock = false;
+ int pin_lock = 0;
bool show_lock_menu = false;
bool consumed = true;
@@ -189,8 +190,16 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) {
show_lock_menu = model->show_lock_menu;
if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
if(model->show_lock_menu) {
- if(event->key == InputKeyUp || event->key == InputKeyDown) {
- model->pin_lock = !model->pin_lock;
+ if(event->key == InputKeyUp) {
+ model->pin_lock--;
+ if(model->pin_lock < 0) {
+ model->pin_lock = 2;
+ }
+ } else if(event->key == InputKeyDown) {
+ model->pin_lock++;
+ if(model->pin_lock > 2) {
+ model->pin_lock = 0;
+ }
} else if(event->key == InputKeyBack || event->key == InputKeyOk) {
model->show_lock_menu = false;
}
@@ -238,10 +247,18 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) {
DesktopEvent desktop_event = 0;
if(show_lock_menu) {
if(event->key == InputKeyOk && event->type == InputTypeShort) {
- if(pin_lock) {
- desktop_event = DesktopLockMenuEventLockPin;
- } else {
+ switch(pin_lock) {
+ case 0:
desktop_event = DesktopLockMenuEventLock;
+ break;
+ case 1:
+ desktop_event = DesktopLockMenuEventLockPin;
+ break;
+ case 2:
+ desktop_event = DesktopLockMenuEventLockPinOff;
+ break;
+ default:
+ break;
}
}
} else {
diff --git a/applications/services/desktop/views/desktop_view_lock_menu.h b/applications/services/desktop/views/desktop_view_lock_menu.h
index ece48840f..957551b36 100644
--- a/applications/services/desktop/views/desktop_view_lock_menu.h
+++ b/applications/services/desktop/views/desktop_view_lock_menu.h
@@ -25,7 +25,7 @@ struct DesktopLockMenuView {
typedef struct {
uint8_t idx;
bool pin_is_set;
- bool pin_lock;
+ int pin_lock;
bool show_lock_menu;
DesktopLockMenuView* lock_menu;
} DesktopLockMenuViewModel;
diff --git a/applications/services/desktop/views/desktop_view_pin_input.c b/applications/services/desktop/views/desktop_view_pin_input.c
index b86bf2929..d3dadd7d7 100644
--- a/applications/services/desktop/views/desktop_view_pin_input.c
+++ b/applications/services/desktop/views/desktop_view_pin_input.c
@@ -115,16 +115,18 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu
} else {
switch(model->pin.data[i]) {
case InputKeyDown:
- canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_down_7x9);
+ canvas_draw_icon_ex(
+ canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9, IconRotation180);
break;
case InputKeyUp:
- canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9);
+ canvas_draw_icon_ex(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9, IconRotation0);
break;
case InputKeyLeft:
- canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_left_9x7);
+ canvas_draw_icon_ex(
+ canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation270);
break;
case InputKeyRight:
- canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_right_9x7);
+ canvas_draw_icon_ex(canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation90);
break;
default:
furi_assert(0);
@@ -147,7 +149,8 @@ static void desktop_view_pin_input_draw(Canvas* canvas, void* context) {
desktop_view_pin_input_draw_cells(canvas, model);
if((model->pin.length > 0) && !model->locked_input) {
- canvas_draw_icon(canvas, 4, 53, &I_Pin_back_full_40x8);
+ canvas_draw_icon(canvas, 4, 53, &I_Pin_back_arrow_10x8);
+ canvas_draw_str(canvas, 16, 60, "= clear");
}
if(model->button_label && ((model->pin.length >= MIN_PIN_SIZE) || model->locked_input)) {
diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c
index daefeb73d..fc4064282 100644
--- a/applications/services/gui/canvas.c
+++ b/applications/services/gui/canvas.c
@@ -236,7 +236,7 @@ void canvas_draw_bitmap(
y += canvas->offset_y;
uint8_t* bitmap_data = NULL;
compress_icon_decode(canvas->compress_icon, compressed_bitmap_data, &bitmap_data);
- u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data);
+ canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap_data, IconRotation0);
}
void canvas_draw_icon_animation(
@@ -252,13 +252,138 @@ void canvas_draw_icon_animation(
uint8_t* icon_data = NULL;
compress_icon_decode(
canvas->compress_icon, icon_animation_get_data(icon_animation), &icon_data);
- u8g2_DrawXBM(
+ canvas_draw_u8g2_bitmap(
&canvas->fb,
x,
y,
icon_animation_get_width(icon_animation),
icon_animation_get_height(icon_animation),
- icon_data);
+ icon_data,
+ IconRotation0);
+}
+
+static void canvas_draw_u8g2_bitmap_int(
+ u8g2_t* u8g2,
+ u8g2_uint_t x,
+ u8g2_uint_t y,
+ u8g2_uint_t w,
+ u8g2_uint_t h,
+ bool mirror,
+ bool rotation,
+ const uint8_t* bitmap) {
+ u8g2_uint_t blen;
+ blen = w;
+ blen += 7;
+ blen >>= 3;
+
+ if(rotation && !mirror) {
+ x += w + 1;
+ } else if(mirror && !rotation) {
+ y += h - 1;
+ }
+
+ while(h > 0) {
+ const uint8_t* b = bitmap;
+ uint16_t len = w;
+ uint16_t x0 = x;
+ uint16_t y0 = y;
+ uint8_t mask;
+ uint8_t color = u8g2->draw_color;
+ uint8_t ncolor = (color == 0 ? 1 : 0);
+ mask = 1;
+
+ while(len > 0) {
+ if(u8x8_pgm_read(b) & mask) {
+ u8g2->draw_color = color;
+ u8g2_DrawHVLine(u8g2, x0, y0, 1, 0);
+ } else if(u8g2->bitmap_transparency == 0) {
+ u8g2->draw_color = ncolor;
+ u8g2_DrawHVLine(u8g2, x0, y0, 1, 0);
+ }
+
+ if(rotation) {
+ y0++;
+ } else {
+ x0++;
+ }
+
+ mask <<= 1;
+ if(mask == 0) {
+ mask = 1;
+ b++;
+ }
+ len--;
+ }
+
+ u8g2->draw_color = color;
+ bitmap += blen;
+
+ if(mirror) {
+ if(rotation) {
+ x++;
+ } else {
+ y--;
+ }
+ } else {
+ if(rotation) {
+ x--;
+ } else {
+ y++;
+ }
+ }
+ h--;
+ }
+}
+
+void canvas_draw_u8g2_bitmap(
+ u8g2_t* u8g2,
+ u8g2_uint_t x,
+ u8g2_uint_t y,
+ u8g2_uint_t w,
+ u8g2_uint_t h,
+ const uint8_t* bitmap,
+ IconRotation rotation) {
+ u8g2_uint_t blen;
+ blen = w;
+ blen += 7;
+ blen >>= 3;
+#ifdef U8G2_WITH_INTERSECTION
+ if(u8g2_IsIntersection(u8g2, x, y, x + w, y + h) == 0) return;
+#endif /* U8G2_WITH_INTERSECTION */
+
+ switch(rotation) {
+ case IconRotation0:
+ canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 0, 0, bitmap);
+ break;
+ case IconRotation90:
+ canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 0, 1, bitmap);
+ break;
+ case IconRotation180:
+ canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 1, 0, bitmap);
+ break;
+ case IconRotation270:
+ canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 1, 1, bitmap);
+ break;
+ default:
+ break;
+ }
+}
+
+void canvas_draw_icon_ex(
+ Canvas* canvas,
+ uint8_t x,
+ uint8_t y,
+ const Icon* icon,
+ IconRotation rotation) {
+ furi_assert(canvas);
+ furi_assert(icon);
+
+ x += canvas->offset_x;
+ y += canvas->offset_y;
+ uint8_t* icon_data = NULL;
+ compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data);
+ canvas_draw_u8g2_bitmap(
+ &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, rotation);
}
void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) {
@@ -269,7 +394,8 @@ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) {
y += canvas->offset_y;
uint8_t* icon_data = NULL;
compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data);
- u8g2_DrawXBM(&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data);
+ canvas_draw_u8g2_bitmap(
+ &canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, IconRotation0);
}
void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y) {
@@ -379,7 +505,7 @@ void canvas_draw_xbm(
furi_assert(canvas);
x += canvas->offset_x;
y += canvas->offset_y;
- u8g2_DrawXBM(&canvas->fb, x, y, w, h, bitmap);
+ canvas_draw_u8g2_bitmap(&canvas->fb, x, y, w, h, bitmap, IconRotation0);
}
void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch) {
diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h
index 79ad2bcc5..9d8cf607f 100644
--- a/applications/services/gui/canvas.h
+++ b/applications/services/gui/canvas.h
@@ -65,6 +65,22 @@ typedef struct {
uint8_t descender;
} CanvasFontParameters;
+/** Icon flip */
+typedef enum {
+ IconFlipNone,
+ IconFlipHorizontal,
+ IconFlipVertical,
+ IconFlipBoth,
+} IconFlip;
+
+/** Icon rotation */
+typedef enum {
+ IconRotation0,
+ IconRotation90,
+ IconRotation180,
+ IconRotation270,
+} IconRotation;
+
/** Canvas anonymous structure */
typedef struct Canvas Canvas;
@@ -218,6 +234,22 @@ void canvas_draw_bitmap(
uint8_t height,
const uint8_t* compressed_bitmap_data);
+/** Draw icon at position defined by x,y with rotation and flip.
+ *
+ * @param canvas Canvas instance
+ * @param x x coordinate
+ * @param y y coordinate
+ * @param icon Icon instance
+ * @param flip IconFlip
+ * @param rotation IconRotation
+ */
+void canvas_draw_icon_ex(
+ Canvas* canvas,
+ uint8_t x,
+ uint8_t y,
+ const Icon* icon,
+ IconRotation rotation);
+
/** Draw animation at position defined by x,y.
*
* @param canvas Canvas instance
diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h
index aadb5200f..f26fe8a89 100644
--- a/applications/services/gui/canvas_i.h
+++ b/applications/services/gui/canvas_i.h
@@ -83,6 +83,25 @@ void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation);
*/
CanvasOrientation canvas_get_orientation(const Canvas* canvas);
+/** Draw a u8g2 bitmap
+ *
+ * @param canvas Canvas instance
+ * @param x x coordinate
+ * @param y y coordinate
+ * @param width width
+ * @param height height
+ * @param bitmap bitmap
+ * @param rotation rotation
+ */
+void canvas_draw_u8g2_bitmap(
+ u8g2_t* u8g2,
+ uint8_t x,
+ uint8_t y,
+ uint8_t width,
+ uint8_t height,
+ const uint8_t* bitmap,
+ uint8_t rotation);
+
#ifdef __cplusplus
}
#endif
diff --git a/applications/services/gui/modules/text_input.c b/applications/services/gui/modules/text_input.c
index 619c1da11..35ab30c16 100644
--- a/applications/services/gui/modules/text_input.c
+++ b/applications/services/gui/modules/text_input.c
@@ -247,6 +247,7 @@ static char char_to_uppercase(const char letter) {
static void text_input_backspace_cb(TextInputModel* model) {
if(model->clear_default_text) {
model->text_buffer[0] = 0;
+ model->cursor_pos = 0;
} else if(model->cursor_pos > 0) {
furi_string_set_str(model->temp_str, model->text_buffer);
furi_string_replace_at(model->temp_str, model->cursor_pos - 1, 1, "");
@@ -261,6 +262,8 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) {
uint8_t needed_string_width = canvas_width(canvas) - 8;
uint8_t start_pos = 4;
+ model->cursor_pos = model->cursor_pos > text_length ? text_length : model->cursor_pos;
+
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
diff --git a/applications/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp
index 814dd82c9..52e86efc2 100644
--- a/applications/services/loader/firmware_api/firmware_api.cpp
+++ b/applications/services/loader/firmware_api/firmware_api.cpp
@@ -4,7 +4,7 @@
#include
/* Generated table */
-#include
+#include
static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!");
diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c
index ecb4b7639..2326e7d2f 100644
--- a/applications/services/rpc/rpc.c
+++ b/applications/services/rpc/rpc.c
@@ -244,7 +244,7 @@ static int32_t rpc_session_worker(void* context) {
.callback = rpc_pb_stream_read,
.state = session,
.errmsg = NULL,
- .bytes_left = RPC_MAX_MESSAGE_SIZE, /* max incoming message size */
+ .bytes_left = SIZE_MAX,
};
bool message_decode_failed = false;
diff --git a/applications/services/rpc/rpc.h b/applications/services/rpc/rpc.h
index 21836d9a4..ec290e083 100644
--- a/applications/services/rpc/rpc.h
+++ b/applications/services/rpc/rpc.h
@@ -10,7 +10,6 @@ extern "C" {
#endif
#define RPC_BUFFER_SIZE (1024)
-#define RPC_MAX_MESSAGE_SIZE (1536)
#define RECORD_RPC "rpc"
diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c
index 2216debc4..b2a00b2c2 100644
--- a/applications/services/storage/storage_processing.c
+++ b/applications/services/storage/storage_processing.c
@@ -77,7 +77,7 @@ static void storage_path_change_to_real_storage(FuriString* path, StorageType re
}
}
-static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage) {
+FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage) {
StorageType type = storage_get_type_by_path(path);
if(storage_type_is_valid(type)) {
diff --git a/applications/services/storage/storage_processing.h b/applications/services/storage/storage_processing.h
index bb779c7a8..67d16d8f8 100644
--- a/applications/services/storage/storage_processing.h
+++ b/applications/services/storage/storage_processing.h
@@ -9,6 +9,8 @@
extern "C" {
#endif
+FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage);
+
void storage_process_message(Storage* app, StorageMessage* message);
#ifdef __cplusplus
diff --git a/assets/icons/GPIO/ArrowDownEmpty_14x15.png b/assets/icons/GPIO/ArrowDownEmpty_14x15.png
deleted file mode 100644
index 8c6d54f9c..000000000
Binary files a/assets/icons/GPIO/ArrowDownEmpty_14x15.png and /dev/null differ
diff --git a/assets/icons/GPIO/ArrowDownFilled_14x15.png b/assets/icons/GPIO/ArrowDownFilled_14x15.png
deleted file mode 100644
index 6cef0f4a7..000000000
Binary files a/assets/icons/GPIO/ArrowDownFilled_14x15.png and /dev/null differ
diff --git a/assets/icons/PIN/Pin_arrow_down_7x9.png b/assets/icons/PIN/Pin_arrow_down_7x9.png
deleted file mode 100644
index 9687397af..000000000
Binary files a/assets/icons/PIN/Pin_arrow_down_7x9.png and /dev/null differ
diff --git a/assets/icons/PIN/Pin_arrow_left_9x7.png b/assets/icons/PIN/Pin_arrow_left_9x7.png
deleted file mode 100644
index fb4ded78f..000000000
Binary files a/assets/icons/PIN/Pin_arrow_left_9x7.png and /dev/null differ
diff --git a/assets/icons/PIN/Pin_arrow_right_9x7.png b/assets/icons/PIN/Pin_arrow_right_9x7.png
deleted file mode 100644
index 97648d176..000000000
Binary files a/assets/icons/PIN/Pin_arrow_right_9x7.png and /dev/null differ
diff --git a/assets/icons/PIN/Pin_back_full_40x8.png b/assets/icons/PIN/Pin_back_full_40x8.png
deleted file mode 100644
index cd1301512..000000000
Binary files a/assets/icons/PIN/Pin_back_full_40x8.png and /dev/null differ
diff --git a/assets/icons/StatusBar/Lock_8x8.png b/assets/icons/StatusBar/Lock_8x8.png
deleted file mode 100644
index 01fb0eb6b..000000000
Binary files a/assets/icons/StatusBar/Lock_8x8.png and /dev/null differ
diff --git a/assets/resources/infrared/assets/ac.ir b/assets/resources/infrared/assets/ac.ir
index 9880cf9eb..edddec899 100644
--- a/assets/resources/infrared/assets/ac.ir
+++ b/assets/resources/infrared/assets/ac.ir
@@ -1,7 +1,7 @@
Filetype: IR library file
Version: 1
# Last Updated 21st Mar, 2023
-# Last Checked 21st Mar, 2023
+# Last Checked 29th Mar, 2023
#
name: POWER
type: raw
diff --git a/assets/resources/infrared/assets/audio.ir b/assets/resources/infrared/assets/audio.ir
index 1d32591b6..0adb61efb 100644
--- a/assets/resources/infrared/assets/audio.ir
+++ b/assets/resources/infrared/assets/audio.ir
@@ -1,7 +1,7 @@
Filetype: IR library file
Version: 1
-# Last Updated 21st Mar, 2023
-# Last Checked 21st Mar, 2023
+# Last Updated 29th Mar, 2023
+# Last Checked 29th Mar, 2023
#
name: POWER
type: parsed
@@ -2122,3 +2122,15 @@ type: parsed
protocol: NEC
address: FD 00 00 00
command: B9 00 00 00
+#
+name: POWER
+type: raw
+frequency: 38000
+duty_cycle: 0.330000
+data: 2760 833 499 418 470 419 416 887 445 872 893 468 416 469 414 442 443 441 444 440 446 440 446 440 446 440 892 882 445 444 466 446 439 447 438 448 437 449 884 447 437 450 437 894 436 449 437 449 437 449 437 449 437 449 884 448 436 894 437 450 436 116126 2673 887 445 473 440 449 439 893 438 880 884 448 437 449 437 449 437 449 437 449 437 449 437 449 437 449 884 890 437 449 437 449 437 450 436 449 437 449 884 448 436 451 437 894 436 449 437 450 436 450 436 449 437 450 883 448 436 894 436 449 437 116123 2672 888 469 449 439 450 437 893 438 881 883 448 437 449 437 449 437 449 437 449 438 449 437 449 437 449 884 891 437 449 437 449 437 449 437 449 437 449 884 448 436 451 437 894 437 449 437 449 437 449 437 449 437 449 884 448 436 894 437 449 437
+#
+name: MUTE
+type: raw
+frequency: 38000
+duty_cycle: 0.330000
+data: 2701 861 496 420 445 444 444 885 446 871 892 441 444 441 444 441 471 415 471 415 470 417 467 444 440 446 883 891 436 449 437 449 437 450 436 450 436 449 883 448 437 451 436 894 436 450 436 450 436 450 436 450 436 450 882 449 436 894 883 116552 2698 862 469 448 438 449 438 894 437 880 884 448 437 449 437 448 438 449 437 449 437 449 437 449 437 449 884 890 437 449 437 449 437 449 437 449 437 449 884 448 437 450 437 893 437 449 437 449 437 449 437 449 437 449 884 448 436 894 883
diff --git a/assets/resources/infrared/assets/fans.ir b/assets/resources/infrared/assets/fans.ir
index 29a967db2..f451ba483 100644
--- a/assets/resources/infrared/assets/fans.ir
+++ b/assets/resources/infrared/assets/fans.ir
@@ -1,7 +1,7 @@
Filetype: IR library file
Version: 1
# Last Updated 15th Feb, 2023
-# Last Checked 21st Mar, 2023
+# Last Checked 29th Mar, 2023
#
name: POWER
type: raw
diff --git a/assets/resources/infrared/assets/projectors.ir b/assets/resources/infrared/assets/projectors.ir
index fe281a6f2..92f5a8eeb 100644
--- a/assets/resources/infrared/assets/projectors.ir
+++ b/assets/resources/infrared/assets/projectors.ir
@@ -1,7 +1,7 @@
Filetype: IR library file
Version: 1
# Last Updated 07th Mar, 2023
-# Last Checked 21st Mar, 2023
+# Last Checked 29th Mar, 2023
#
# ON
name: POWER
diff --git a/assets/resources/infrared/assets/tv.ir b/assets/resources/infrared/assets/tv.ir
index d67192b7b..fe2108537 100644
--- a/assets/resources/infrared/assets/tv.ir
+++ b/assets/resources/infrared/assets/tv.ir
@@ -1,7 +1,7 @@
Filetype: IR library file
Version: 1
# Last Updated 07th Mar, 2023
-# Last Checked 21st Mar, 2023
+# Last Checked 29th Mar, 2023
#
name: POWER
type: parsed
diff --git a/fbt_options.py b/fbt_options.py
index 84ec42bfc..0e87521a1 100644
--- a/fbt_options.py
+++ b/fbt_options.py
@@ -14,7 +14,7 @@ DEBUG = 0
# Suffix to add to files when building distribution
# If OS environment has DIST_SUFFIX set, it will be used instead
-DIST_SUFFIX = "XFW-0043_28032023"
+DIST_SUFFIX = "XFW-0044_09042023"
# Coprocessor firmware
COPRO_OB_DATA = "scripts/ob.data"
diff --git a/firmware.scons b/firmware.scons
index a094765af..c7fdc6392 100644
--- a/firmware.scons
+++ b/firmware.scons
@@ -68,7 +68,7 @@ env = ENV.Clone(
],
},
},
- SDK_APISYMS=None,
+ FW_API_TABLE=None,
_APP_ICONS=None,
)
@@ -241,7 +241,7 @@ Depends(
[
fwenv["FW_VERSION_JSON"],
fwenv["FW_ASSETS_HEADERS"],
- fwenv["SDK_APISYMS"],
+ fwenv["FW_API_TABLE"],
fwenv["_APP_ICONS"],
],
)
diff --git a/firmware/targets/f18/api_symbols.csv b/firmware/targets/f18/api_symbols.csv
index 42de52c03..df4401aab 100644
--- a/firmware/targets/f18/api_symbols.csv
+++ b/firmware/targets/f18/api_symbols.csv
@@ -1,5 +1,5 @@
entry,status,name,type,params
-Version,+,20.0,,
+Version,+,21.0,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
@@ -158,6 +158,7 @@ Header,+,lib/toolbox/args.h,,
Header,+,lib/toolbox/crc32_calc.h,,
Header,+,lib/toolbox/dir_walk.h,,
Header,+,lib/toolbox/float_tools.h,,
+Header,+,lib/toolbox/hex.h,,
Header,+,lib/toolbox/manchester_decoder.h,,
Header,+,lib/toolbox/manchester_encoder.h,,
Header,+,lib/toolbox/md5.h,,
@@ -539,6 +540,7 @@ Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t"
Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*"
Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*"
+Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation"
Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t"
@@ -875,12 +877,6 @@ Function,-,furi_hal_clock_resume_tick,void,
Function,-,furi_hal_clock_suspend_tick,void,
Function,-,furi_hal_clock_switch_to_hsi,void,
Function,-,furi_hal_clock_switch_to_pll,void,
-Function,-,compress_alloc,Compress*,uint16_t
-Function,-,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*"
-Function,-,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*"
-Function,-,compress_free,void,Compress*
-Function,-,compress_icon_decode,void,"const uint8_t*, uint8_t**"
-Function,-,compress_icon_init,void,
Function,+,furi_hal_console_disable,void,
Function,+,furi_hal_console_enable,void,
Function,+,furi_hal_console_init,void,
@@ -1315,6 +1311,10 @@ Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*"
Function,+,hal_sd_detect,_Bool,
Function,+,hal_sd_detect_init,void,
Function,+,hal_sd_detect_set_low,void,
+Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*"
+Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*"
+Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*"
+Function,+,hex_chars_to_uint8,_Bool,"const char*, uint8_t*"
Function,+,icon_animation_alloc,IconAnimation*,const Icon*
Function,+,icon_animation_free,void,IconAnimation*
Function,+,icon_animation_get_height,uint8_t,const IconAnimation*
@@ -1876,6 +1876,7 @@ Function,+,uECC_sign,int,"const uint8_t*, const uint8_t*, unsigned, uint8_t*, uE
Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned, const uECC_HashContext*, uint8_t*, uECC_Curve"
Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve"
Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve"
+Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int"
Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t"
Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t"
Function,-,ulTaskGetIdleRunTimeCounter,uint32_t,
@@ -2161,7 +2162,7 @@ Variable,+,gpio_usart_rx,const GpioPin,
Variable,+,gpio_usart_tx,const GpioPin,
Variable,+,gpio_usb_dm,const GpioPin,
Variable,+,gpio_usb_dp,const GpioPin,
-Variable,+,ibutton_gpio,const GpioPin,
+Variable,+,gpio_ibutton,const GpioPin,
Variable,+,input_pins,const InputPin[],
Variable,+,input_pins_count,const size_t,
Variable,+,message_blink_set_color_blue,const NotificationMessage,
@@ -2309,7 +2310,7 @@ Variable,+,message_red_255,const NotificationMessage,
Variable,+,message_sound_off,const NotificationMessage,
Variable,+,message_vibro_off,const NotificationMessage,
Variable,+,message_vibro_on,const NotificationMessage,
-Variable,+,periph_power,const GpioPin,
+Variable,+,gpio_periph_power,const GpioPin,
Variable,+,sequence_audiovisual_alert,const NotificationSequence,
Variable,+,sequence_blink_blue_10,const NotificationSequence,
Variable,+,sequence_blink_blue_100,const NotificationSequence,
@@ -2364,4 +2365,4 @@ Variable,+,usb_cdc_single,FuriHalUsbInterface,
Variable,+,usb_hid,FuriHalUsbInterface,
Variable,+,usb_hid_u2f,FuriHalUsbInterface,
Variable,+,usbd_devfs,const usbd_driver,
-Variable,+,vibro_gpio,const GpioPin,
+Variable,+,gpio_vibro,const GpioPin,
diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.c b/firmware/targets/f18/furi_hal/furi_hal_resources.c
index abb258cb1..19bc9f998 100644
--- a/firmware/targets/f18/furi_hal/furi_hal_resources.c
+++ b/firmware/targets/f18/furi_hal/furi_hal_resources.c
@@ -6,8 +6,8 @@
#define TAG "FuriHalResources"
-const GpioPin vibro_gpio = {.port = GPIOA, .pin = LL_GPIO_PIN_8};
-const GpioPin ibutton_gpio = {.port = GPIOB, .pin = LL_GPIO_PIN_14};
+const GpioPin gpio_vibro = {.port = GPIOA, .pin = LL_GPIO_PIN_8};
+const GpioPin gpio_ibutton = {.port = GPIOB, .pin = LL_GPIO_PIN_14};
const GpioPin gpio_display_cs = {.port = GPIOC, .pin = LL_GPIO_PIN_11};
const GpioPin gpio_display_rst_n = {.port = GPIOB, .pin = LL_GPIO_PIN_0};
@@ -57,7 +57,7 @@ const GpioPin gpio_i2c_power_scl = {.port = GPIOA, .pin = LL_GPIO_PIN_9};
const GpioPin gpio_speaker = {.port = GPIOB, .pin = LL_GPIO_PIN_8};
-const GpioPin periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3};
+const GpioPin gpio_periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3};
const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11};
const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12};
@@ -118,8 +118,8 @@ void furi_hal_resources_init_early() {
furi_hal_resources_init_input_pins(GpioModeInput);
// SD Card stepdown control
- furi_hal_gpio_write(&periph_power, 1);
- furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_write(&gpio_periph_power, 1);
+ furi_hal_gpio_init(&gpio_periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
// Display pins
furi_hal_gpio_write(&gpio_display_rst_n, 1);
@@ -165,6 +165,10 @@ void furi_hal_resources_init() {
// Button pins
furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall);
+ // Explicit pulls pins
+ furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
+ furi_hal_gpio_init(&gpio_vibro, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
+
// Display pins
furi_hal_gpio_init(&gpio_display_rst_n, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_display_rst_n, 0);
@@ -176,9 +180,7 @@ void furi_hal_resources_init() {
furi_hal_gpio_init(&gpio_sdcard_cd, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_sdcard_cd, 0);
- furi_hal_gpio_init(&vibro_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
-
- furi_hal_gpio_init(&ibutton_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_init(&gpio_ibutton, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
NVIC_EnableIRQ(EXTI0_IRQn);
@@ -222,7 +224,7 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) {
return 15;
else if(gpio == &gpio_ext_pc0)
return 16;
- else if(gpio == &ibutton_gpio)
+ else if(gpio == &gpio_ibutton)
return 17;
else
return -1;
diff --git a/firmware/targets/f18/furi_hal/furi_hal_resources.h b/firmware/targets/f18/furi_hal/furi_hal_resources.h
index a24afbf94..3c4708d15 100644
--- a/firmware/targets/f18/furi_hal/furi_hal_resources.h
+++ b/firmware/targets/f18/furi_hal/furi_hal_resources.h
@@ -50,8 +50,8 @@ extern const size_t input_pins_count;
extern const GpioPinRecord gpio_pins[];
extern const size_t gpio_pins_count;
-extern const GpioPin vibro_gpio;
-extern const GpioPin ibutton_gpio;
+extern const GpioPin gpio_vibro;
+extern const GpioPin gpio_ibutton;
extern const GpioPin gpio_display_cs;
extern const GpioPin gpio_display_rst_n;
@@ -100,7 +100,7 @@ extern const GpioPin gpio_i2c_power_scl;
extern const GpioPin gpio_speaker;
-extern const GpioPin periph_power;
+extern const GpioPin gpio_periph_power;
extern const GpioPin gpio_usb_dm;
extern const GpioPin gpio_usb_dp;
diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv
index 340b43a1e..66536e574 100644
--- a/firmware/targets/f7/api_symbols.csv
+++ b/firmware/targets/f7/api_symbols.csv
@@ -1,5 +1,5 @@
entry,status,name,type,params
-Version,+,20.0,,
+Version,+,21.0,,
Header,+,applications/main/fap_loader/fap_loader_app.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
@@ -200,6 +200,7 @@ Header,+,lib/toolbox/args.h,,
Header,+,lib/toolbox/crc32_calc.h,,
Header,+,lib/toolbox/dir_walk.h,,
Header,+,lib/toolbox/float_tools.h,,
+Header,+,lib/toolbox/hex.h,,
Header,+,lib/toolbox/manchester_decoder.h,,
Header,+,lib/toolbox/manchester_encoder.h,,
Header,+,lib/toolbox/md5.h,,
@@ -517,10 +518,6 @@ Function,-,acosl,long double,long double
Function,-,aligned_alloc,void*,"size_t, size_t"
Function,+,aligned_free,void,void*
Function,+,aligned_malloc,void*,"size_t, size_t"
-Function,-,alutech_get_custom_btn,uint8_t,
-Function,-,alutech_get_original_btn,uint8_t,
-Function,+,alutech_reset_original_btn,void,
-Function,-,alutech_set_btn,void,uint8_t
Function,-,arc4random,__uint32_t,
Function,-,arc4random_buf,void,"void*, size_t"
Function,-,arc4random_uniform,__uint32_t,__uint32_t
@@ -658,12 +655,14 @@ Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t"
Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*"
Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*"
Function,+,canvas_draw_icon_bitmap,void,"Canvas*, uint8_t, uint8_t, int16_t, int16_t, const Icon*"
+Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation"
Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_str,void,"Canvas*, uint8_t, uint8_t, const char*"
Function,+,canvas_draw_str_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*"
Function,+,canvas_draw_triangle,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, CanvasDirection"
+Function,+,canvas_draw_u8g2_bitmap,void,"u8g2_t*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*, uint8_t"
Function,+,canvas_draw_xbm,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, const uint8_t*"
Function,-,canvas_frame_set,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
Function,-,canvas_free,void,Canvas*
@@ -1125,7 +1124,7 @@ Function,+,furi_hal_bt_serial_start,void,
Function,+,furi_hal_bt_serial_stop,void,
Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t"
Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*"
-Function,+,furi_hal_bt_set_profile_adv_name,void,"FuriHalBtProfile, const char[( 1 + ( 8 + 1 ) ) + 9 - 1]"
+Function,+,furi_hal_bt_set_profile_adv_name,void,"FuriHalBtProfile, const char[( 18 + 1 )]"
Function,+,furi_hal_bt_set_profile_mac_addr,void,"FuriHalBtProfile, const uint8_t[( 6 )]"
Function,+,furi_hal_bt_set_profile_pairing_method,void,"FuriHalBtProfile, GapPairing"
Function,+,furi_hal_bt_start_advertising,void,
@@ -1716,6 +1715,10 @@ Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*"
Function,+,hal_sd_detect,_Bool,
Function,+,hal_sd_detect_init,void,
Function,+,hal_sd_detect_set_low,void,
+Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*"
+Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*"
+Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*"
+Function,+,hex_chars_to_uint8,_Bool,"const char*, uint8_t*"
Function,-,hypot,double,"double, double"
Function,-,hypotf,float,"float, float"
Function,-,hypotl,long double,"long double, long double"
@@ -1854,12 +1857,9 @@ Function,-,j1f,float,float
Function,-,jn,double,"int, double"
Function,-,jnf,float,"int, float"
Function,-,jrand48,long,unsigned short[3]
-Function,-,keeloq_get_custom_btn,uint8_t,
-Function,-,keeloq_get_original_btn,uint8_t,
Function,+,keeloq_reset_kl_type,void,
Function,+,keeloq_reset_mfname,void,
Function,+,keeloq_reset_original_btn,void,
-Function,-,keeloq_set_btn,void,uint8_t
Function,-,l64a,char*,long
Function,-,labs,long,long
Function,-,lcong48,void,unsigned short[7]
@@ -2181,10 +2181,6 @@ Function,-,nfcv_inventory,ReturnCode,uint8_t*
Function,-,nfcv_read_blocks,ReturnCode,"NfcVReader*, NfcVData*"
Function,-,nfcv_read_card,_Bool,"NfcVReader*, FuriHalNfcDevData*, NfcVData*"
Function,-,nfcv_read_sysinfo,ReturnCode,"FuriHalNfcDevData*, NfcVData*"
-Function,-,nice_flors_get_custom_btn,uint8_t,
-Function,-,nice_flors_get_original_btn,uint8_t,
-Function,+,nice_flors_reset_original_btn,void,
-Function,-,nice_flors_set_btn,void,uint8_t
Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*"
Function,+,notification_internal_message_block,void,"NotificationApp*, const NotificationSequence*"
Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*"
@@ -2568,10 +2564,6 @@ Function,+,scene_manager_search_and_switch_to_previous_scene_one_of,_Bool,"Scene
Function,+,scene_manager_set_scene_state,void,"SceneManager*, uint32_t, uint32_t"
Function,+,scene_manager_stop,void,SceneManager*
Function,+,sd_api_get_fs_type_text,const char*,SDFsType
-Function,-,secplus2_get_custom_btn,uint8_t,
-Function,-,secplus2_get_original_btn,uint8_t,
-Function,+,secplus2_reset_original_btn,void,
-Function,-,secplus2_set_btn,void,uint8_t
Function,-,secure_getenv,char*,const char*
Function,-,seed48,unsigned short*,unsigned short[3]
Function,-,select,int,"int, fd_set*, fd_set*, fd_set*, timeval*"
@@ -2607,10 +2599,6 @@ Function,-,siprintf,int,"char*, const char*, ..."
Function,-,siscanf,int,"const char*, const char*, ..."
Function,-,sniprintf,int,"char*, size_t, const char*, ..."
Function,+,snprintf,int,"char*, size_t, const char*, ..."
-Function,-,somfy_telis_get_custom_btn,uint8_t,
-Function,-,somfy_telis_get_original_btn,uint8_t,
-Function,+,somfy_telis_reset_original_btn,void,
-Function,-,somfy_telis_set_btn,void,uint8_t
Function,-,sprintf,int,"char*, const char*, ..."
Function,-,sqrt,double,double
Function,-,sqrtf,float,float
@@ -2767,6 +2755,12 @@ Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGen
Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t"
Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*"
Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*"
+Function,-,subghz_custom_btn_get,uint8_t,
+Function,-,subghz_custom_btn_get_original,uint8_t,
+Function,-,subghz_custom_btn_set,void,uint8_t
+Function,-,subghz_custom_btn_set_max,void,uint8_t
+Function,-,subghz_custom_btn_set_original,void,uint8_t
+Function,+,subghz_custom_btns_reset,void,
Function,+,subghz_environment_alloc,SubGhzEnvironment*,
Function,+,subghz_environment_free,void,SubGhzEnvironment*
Function,+,subghz_environment_get_alutech_at_4n_rainbow_table_file_name,const char*,SubGhzEnvironment*
@@ -4559,6 +4553,7 @@ Function,-,uECC_sign_deterministic,int,"const uint8_t*, const uint8_t*, unsigned
Function,-,uECC_valid_public_key,int,"const uint8_t*, uECC_Curve"
Function,-,uECC_verify,int,"const uint8_t*, const uint8_t*, unsigned, const uint8_t*, uECC_Curve"
Function,-,ucStreamBufferGetStreamBufferType,uint8_t,StreamBufferHandle_t
+Function,+,uint8_to_hex_chars,void,"const uint8_t*, uint8_t*, int"
Function,-,ulTaskGenericNotifyTake,uint32_t,"UBaseType_t, BaseType_t, TickType_t"
Function,-,ulTaskGenericNotifyValueClear,uint32_t,"TaskHandle_t, UBaseType_t, uint32_t"
Function,-,ulTaskGetIdleRunTimeCounter,uint32_t,
@@ -4881,7 +4876,7 @@ Variable,+,gpio_usart_rx,const GpioPin,
Variable,+,gpio_usart_tx,const GpioPin,
Variable,+,gpio_usb_dm,const GpioPin,
Variable,+,gpio_usb_dp,const GpioPin,
-Variable,+,ibutton_gpio,const GpioPin,
+Variable,+,gpio_ibutton,const GpioPin,
Variable,+,input_pins,const InputPin[],
Variable,+,input_pins_count,const size_t,
Variable,+,lfrfid_protocols,const ProtocolBase*[],
@@ -5030,7 +5025,7 @@ Variable,+,message_red_255,const NotificationMessage,
Variable,+,message_sound_off,const NotificationMessage,
Variable,+,message_vibro_off,const NotificationMessage,
Variable,+,message_vibro_on,const NotificationMessage,
-Variable,+,periph_power,const GpioPin,
+Variable,+,gpio_periph_power,const GpioPin,
Variable,+,sequence_audiovisual_alert,const NotificationSequence,
Variable,+,sequence_blink_blue_10,const NotificationSequence,
Variable,+,sequence_blink_blue_100,const NotificationSequence,
@@ -7037,4 +7032,4 @@ Variable,+,usb_cdc_single,FuriHalUsbInterface,
Variable,+,usb_hid,FuriHalUsbInterface,
Variable,+,usb_hid_u2f,FuriHalUsbInterface,
Variable,+,usbd_devfs,const usbd_driver,
-Variable,+,vibro_gpio,const GpioPin,
+Variable,+,gpio_vibro,const GpioPin,
diff --git a/firmware/targets/f7/fatfs/fatfs.c b/firmware/targets/f7/fatfs/fatfs.c
index 66ddbca06..c8581ff2e 100644
--- a/firmware/targets/f7/fatfs/fatfs.c
+++ b/firmware/targets/f7/fatfs/fatfs.c
@@ -17,6 +17,7 @@
*/
#include "fatfs.h"
+#include "furi_hal_rtc.h"
/** logical drive path */
char fatfs_path[4];
@@ -27,13 +28,16 @@ void fatfs_init(void) {
FATFS_LinkDriver(&sd_fatfs_driver, fatfs_path);
}
-/**
- * @brief Gets Time from RTC
- * @param None
- * @retval Time in DWORD
+/** Gets Time from RTC
+ *
+ * @return Time in DWORD (toasters per square washing machine)
*/
-DWORD get_fattime(void) {
- return 0;
+DWORD get_fattime() {
+ FuriHalRtcDateTime furi_time;
+ furi_hal_rtc_get_datetime(&furi_time);
+
+ return ((uint32_t)(furi_time.year - 1980) << 25) | furi_time.month << 21 |
+ furi_time.day << 16 | furi_time.hour << 11 | furi_time.minute << 5 | furi_time.second;
}
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
diff --git a/firmware/targets/f7/fatfs/ffconf.h b/firmware/targets/f7/fatfs/ffconf.h
index a44521550..8408a1ec1 100644
--- a/firmware/targets/f7/fatfs/ffconf.h
+++ b/firmware/targets/f7/fatfs/ffconf.h
@@ -219,10 +219,7 @@
/ When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1)
/ Note that enabling exFAT discards C89 compatibility. */
-#define _FS_NORTC 1
-#define _NORTC_MON 7
-#define _NORTC_MDAY 20
-#define _NORTC_YEAR 2021
+#define _FS_NORTC 0
/* The option _FS_NORTC switches timestamp functiton. If the system does not have
/ any RTC function or valid timestamp is not needed, set _FS_NORTC = 1 to disable
/ the timestamp function. All objects modified by FatFs will have a fixed timestamp
diff --git a/firmware/targets/f7/fatfs/user_diskio.c b/firmware/targets/f7/fatfs/user_diskio.c
index 8b693886f..1a9559a90 100644
--- a/firmware/targets/f7/fatfs/user_diskio.c
+++ b/firmware/targets/f7/fatfs/user_diskio.c
@@ -22,16 +22,14 @@
#include
#include "sector_cache.h"
-static volatile DSTATUS Stat = STA_NOINIT;
-
static DSTATUS driver_check_status(BYTE lun) {
UNUSED(lun);
- Stat = STA_NOINIT;
- if(sd_get_card_state() == SdSpiStatusOK) {
- Stat &= ~STA_NOINIT;
+ DSTATUS status = 0;
+ if(sd_get_card_state() != SdSpiStatusOK) {
+ status = STA_NOINIT;
}
- return Stat;
+ return status;
}
static DSTATUS driver_initialize(BYTE pdrv);
@@ -127,6 +125,16 @@ static bool sd_device_write(uint32_t* buff, uint32_t sector, uint32_t count) {
* @retval DSTATUS: Operation status
*/
static DSTATUS driver_initialize(BYTE pdrv) {
+ UNUSED(pdrv);
+ return RES_OK;
+}
+
+/**
+ * @brief Gets Disk Status
+ * @param pdrv: Physical drive number (0..)
+ * @retval DSTATUS: Operation status
+ */
+static DSTATUS driver_status(BYTE pdrv) {
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast);
furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast;
@@ -138,16 +146,6 @@ static DSTATUS driver_initialize(BYTE pdrv) {
return status;
}
-/**
- * @brief Gets Disk Status
- * @param pdrv: Physical drive number (0..)
- * @retval DSTATUS: Operation status
- */
-static DSTATUS driver_status(BYTE pdrv) {
- UNUSED(pdrv);
- return Stat;
-}
-
/**
* @brief Reads Sector(s)
* @param pdrv: Physical drive number (0..)
@@ -244,15 +242,15 @@ static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT coun
* @retval DRESULT: Operation result
*/
static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) {
- UNUSED(pdrv);
DRESULT res = RES_ERROR;
SD_CardInfo CardInfo;
- if(Stat & STA_NOINIT) return RES_NOTRDY;
-
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast);
furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast;
+ DSTATUS status = driver_check_status(pdrv);
+ if(status & STA_NOINIT) return RES_NOTRDY;
+
switch(cmd) {
/* Make sure that no pending write process */
case CTRL_SYNC:
diff --git a/firmware/targets/f7/furi_hal/furi_hal_bt.c b/firmware/targets/f7/furi_hal/furi_hal_bt.c
index 198bb4454..ce01b8871 100644
--- a/firmware/targets/f7/furi_hal/furi_hal_bt.c
+++ b/firmware/targets/f7/furi_hal/furi_hal_bt.c
@@ -206,12 +206,10 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb,
if(profile == FuriHalBtProfileSerial) {
// Set mac address
memcpy(
- profile_config[profile].config.mac_address,
- furi_hal_version_get_ble_mac(),
- sizeof(profile_config[profile].config.mac_address));
+ config->mac_address, furi_hal_version_get_ble_mac(), sizeof(config->mac_address));
// Set advertise name
strlcpy(
- profile_config[profile].config.adv_name,
+ config->adv_name,
furi_hal_version_get_ble_local_device_name_ptr(),
FURI_HAL_VERSION_DEVICE_NAME_LENGTH);
@@ -223,12 +221,14 @@ bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb,
config->mac_address[2]++;
}
// Change name Flipper -> Control
- if(strlen(&config->adv_name[1]) == 0) {
+ if(strnlen(config->adv_name, FURI_HAL_VERSION_DEVICE_NAME_LENGTH) < 2 ||
+ strnlen(config->adv_name + 1, FURI_HAL_VERSION_DEVICE_NAME_LENGTH) < 1) {
snprintf(
- &config->adv_name[1],
- strlen("Control ") + FURI_HAL_VERSION_DEVICE_NAME_LENGTH,
- "Control %s",
- furi_hal_version_get_ble_local_device_name_ptr());
+ config->adv_name,
+ FURI_HAL_VERSION_DEVICE_NAME_LENGTH,
+ "%cControl %s",
+ *furi_hal_version_get_ble_local_device_name_ptr(),
+ furi_hal_version_get_ble_local_device_name_ptr() + 1);
}
}
if(!gap_init(config, event_cb, context)) {
@@ -475,7 +475,7 @@ bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) {
void furi_hal_bt_set_profile_adv_name(
FuriHalBtProfile profile,
- const char name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH - 1]) {
+ const char name[FURI_HAL_BT_ADV_NAME_LENGTH]) {
furi_assert(profile < FuriHalBtProfileNumber);
furi_assert(name);
@@ -486,10 +486,7 @@ void furi_hal_bt_set_profile_adv_name(
strlen(&(profile_config[profile].config.adv_name[1])));
} else {
profile_config[profile].config.adv_name[0] = AD_TYPE_COMPLETE_LOCAL_NAME;
- memcpy(
- &(profile_config[profile].config.adv_name[1]),
- name,
- FURI_HAL_VERSION_DEVICE_NAME_LENGTH - 1);
+ memcpy(&(profile_config[profile].config.adv_name[1]), name, FURI_HAL_BT_ADV_NAME_LENGTH);
}
}
diff --git a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c
index f19fd0a0e..c8041c9f2 100644
--- a/firmware/targets/f7/furi_hal/furi_hal_ibutton.c
+++ b/firmware/targets/f7/furi_hal/furi_hal_ibutton.c
@@ -93,15 +93,15 @@ void furi_hal_ibutton_emulate_stop() {
}
void furi_hal_ibutton_pin_configure() {
- furi_hal_gpio_write(&ibutton_gpio, true);
- furi_hal_gpio_init(&ibutton_gpio, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_write(&gpio_ibutton, true);
+ furi_hal_gpio_init(&gpio_ibutton, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
}
void furi_hal_ibutton_pin_reset() {
- furi_hal_gpio_write(&ibutton_gpio, true);
- furi_hal_gpio_init(&ibutton_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_write(&gpio_ibutton, true);
+ furi_hal_gpio_init(&gpio_ibutton, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
void furi_hal_ibutton_pin_write(const bool state) {
- furi_hal_gpio_write(&ibutton_gpio, state);
+ furi_hal_gpio_write(&gpio_ibutton, state);
}
diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c
index dd7c34ae7..fd601ec7e 100644
--- a/firmware/targets/f7/furi_hal/furi_hal_power.c
+++ b/firmware/targets/f7/furi_hal/furi_hal_power.c
@@ -440,11 +440,11 @@ float furi_hal_power_get_usb_voltage() {
}
void furi_hal_power_enable_external_3_3v() {
- furi_hal_gpio_write(&periph_power, 1);
+ furi_hal_gpio_write(&gpio_periph_power, 1);
}
void furi_hal_power_disable_external_3_3v() {
- furi_hal_gpio_write(&periph_power, 0);
+ furi_hal_gpio_write(&gpio_periph_power, 0);
}
void furi_hal_power_suppress_charge_enter() {
diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c
index 5cf44a6a8..a6c72fd5a 100644
--- a/firmware/targets/f7/furi_hal/furi_hal_resources.c
+++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c
@@ -6,8 +6,8 @@
#define TAG "FuriHalResources"
-const GpioPin vibro_gpio = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin};
-const GpioPin ibutton_gpio = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin};
+const GpioPin gpio_vibro = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin};
+const GpioPin gpio_ibutton = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin};
const GpioPin gpio_cc1101_g0 = {.port = CC1101_G0_GPIO_Port, .pin = CC1101_G0_Pin};
const GpioPin gpio_cc1101_g0_ext = {.port = GPIOB, .pin = LL_GPIO_PIN_2};
@@ -64,7 +64,7 @@ const GpioPin gpio_i2c_power_scl = {.port = GPIOA, .pin = LL_GPIO_PIN_9};
const GpioPin gpio_speaker = {.port = GPIOB, .pin = LL_GPIO_PIN_8};
-const GpioPin periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3};
+const GpioPin gpio_periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3};
const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11};
const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12};
@@ -111,8 +111,8 @@ void furi_hal_resources_init_early() {
furi_hal_resources_init_input_pins(GpioModeInput);
// SD Card stepdown control
- furi_hal_gpio_write(&periph_power, 1);
- furi_hal_gpio_init(&periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_write(&gpio_periph_power, 1);
+ furi_hal_gpio_init(&gpio_periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
// Display pins
furi_hal_gpio_write(&gpio_display_rst_n, 1);
@@ -158,6 +158,11 @@ void furi_hal_resources_init() {
// Button pins
furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall);
+ // Explicit pulls pins
+ furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
+ furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
+ furi_hal_gpio_init(&gpio_vibro, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
+
// Display pins
furi_hal_gpio_init(&gpio_display_rst_n, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_display_rst_n, 0);
@@ -169,9 +174,7 @@ void furi_hal_resources_init() {
furi_hal_gpio_init(&gpio_sdcard_cd, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_sdcard_cd, 0);
- furi_hal_gpio_init(&vibro_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
-
- furi_hal_gpio_init(&ibutton_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_init(&gpio_ibutton, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeInterruptRise, GpioPullNo, GpioSpeedLow);
@@ -218,7 +221,7 @@ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) {
return 15;
else if(gpio == &gpio_ext_pc0)
return 16;
- else if(gpio == &ibutton_gpio)
+ else if(gpio == &gpio_ibutton)
return 17;
else
return -1;
diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.h b/firmware/targets/f7/furi_hal/furi_hal_resources.h
index 33e2a3289..c0e32c313 100644
--- a/firmware/targets/f7/furi_hal/furi_hal_resources.h
+++ b/firmware/targets/f7/furi_hal/furi_hal_resources.h
@@ -50,8 +50,8 @@ extern const size_t input_pins_count;
extern const GpioPinRecord gpio_pins[];
extern const size_t gpio_pins_count;
-extern const GpioPin vibro_gpio;
-extern const GpioPin ibutton_gpio;
+extern const GpioPin gpio_vibro;
+extern const GpioPin gpio_ibutton;
extern const GpioPin gpio_cc1101_g0;
extern const GpioPin gpio_cc1101_g0_ext;
@@ -107,7 +107,7 @@ extern const GpioPin gpio_i2c_power_scl;
extern const GpioPin gpio_speaker;
-extern const GpioPin periph_power;
+extern const GpioPin gpio_periph_power;
extern const GpioPin gpio_usb_dm;
extern const GpioPin gpio_usb_dp;
diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c
index e011406ad..84e7fe395 100644
--- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c
+++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c
@@ -281,8 +281,10 @@ FuriHalRtcLocaleDateFormat furi_hal_rtc_get_locale_dateformat() {
}
void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime) {
+ furi_check(!FURI_IS_IRQ_MODE());
furi_assert(datetime);
+ FURI_CRITICAL_ENTER();
/* Disable write protection */
LL_RTC_DisableWriteProtection(RTC);
@@ -319,13 +321,17 @@ void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime) {
/* Enable write protection */
LL_RTC_EnableWriteProtection(RTC);
+ FURI_CRITICAL_EXIT();
}
void furi_hal_rtc_get_datetime(FuriHalRtcDateTime* datetime) {
+ furi_check(!FURI_IS_IRQ_MODE());
furi_assert(datetime);
+ FURI_CRITICAL_ENTER();
uint32_t time = LL_RTC_TIME_Get(RTC); // 0x00HHMMSS
uint32_t date = LL_RTC_DATE_Get(RTC); // 0xWWDDMMYY
+ FURI_CRITICAL_EXIT();
datetime->second = __LL_RTC_CONVERT_BCD2BIN((time >> 0) & 0xFF);
datetime->minute = __LL_RTC_CONVERT_BCD2BIN((time >> 8) & 0xFF);
diff --git a/firmware/targets/f7/furi_hal/furi_hal_speaker.c b/firmware/targets/f7/furi_hal/furi_hal_speaker.c
index c4a0bdd1e..5421509cc 100644
--- a/firmware/targets/f7/furi_hal/furi_hal_speaker.c
+++ b/firmware/targets/f7/furi_hal/furi_hal_speaker.c
@@ -52,7 +52,7 @@ void furi_hal_speaker_release() {
furi_check(furi_hal_speaker_is_mine());
furi_hal_speaker_stop();
- furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
furi_hal_power_insomnia_exit();
furi_check(furi_mutex_release(furi_hal_speaker_mutex) == FuriStatusOk);
diff --git a/firmware/targets/f7/furi_hal/furi_hal_vibro.c b/firmware/targets/f7/furi_hal/furi_hal_vibro.c
index 4315ea637..f46784677 100644
--- a/firmware/targets/f7/furi_hal/furi_hal_vibro.c
+++ b/firmware/targets/f7/furi_hal/furi_hal_vibro.c
@@ -4,11 +4,11 @@
#define TAG "FuriHalVibro"
void furi_hal_vibro_init() {
- furi_hal_gpio_init(&vibro_gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
- furi_hal_gpio_write(&vibro_gpio, false);
+ furi_hal_gpio_init(&gpio_vibro, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
+ furi_hal_gpio_write(&gpio_vibro, false);
FURI_LOG_I(TAG, "Init OK");
}
void furi_hal_vibro_on(bool value) {
- furi_hal_gpio_write(&vibro_gpio, value);
+ furi_hal_gpio_write(&gpio_vibro, value);
}
diff --git a/firmware/targets/f7/src/dfu.c b/firmware/targets/f7/src/dfu.c
index b060bc8d2..7e094b4c4 100644
--- a/firmware/targets/f7/src/dfu.c
+++ b/firmware/targets/f7/src/dfu.c
@@ -2,29 +2,24 @@
#include
#include
#include
-#include
#include
#include
+#include
+#include
void flipper_boot_dfu_show_splash() {
// Initialize
- CompressIcon* compress_icon = compress_icon_alloc();
+ Canvas* canvas = canvas_init();
- u8g2_t* fb = malloc(sizeof(u8g2_t));
- memset(fb, 0, sizeof(u8g2_t));
- u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
- u8g2_InitDisplay(fb);
- u8g2_SetDrawColor(fb, 0x01);
- uint8_t* splash_data = NULL;
- compress_icon_decode(compress_icon, icon_get_data(&I_DFU_128x50), &splash_data);
- u8g2_DrawXBM(fb, 0, 64 - 50, 128, 50, splash_data);
- u8g2_SetFont(fb, u8g2_font_helvB08_tr);
- u8g2_DrawStr(fb, 2, 8, "Update & Recovery Mode");
- u8g2_DrawStr(fb, 2, 21, "DFU Started");
- u8g2_SetPowerSave(fb, 0);
- u8g2_SendBuffer(fb);
+ canvas_set_color(canvas, ColorBlack);
+ canvas_set_font(canvas, FontPrimary);
- compress_icon_free(compress_icon);
+ canvas_draw_icon(canvas, 0, 64 - 50, &I_DFU_128x50);
+ canvas_draw_str(canvas, 2, 8, "Update & Recovery Mode");
+ canvas_draw_str(canvas, 2, 21, "DFU Started");
+ canvas_commit(canvas);
+
+ canvas_free(canvas);
}
void flipper_boot_dfu_exec() {
diff --git a/firmware/targets/f7/src/recovery.c b/firmware/targets/f7/src/recovery.c
index d037e8118..49d780d47 100644
--- a/firmware/targets/f7/src/recovery.c
+++ b/firmware/targets/f7/src/recovery.c
@@ -2,44 +2,43 @@
#include
#include
#include
-#include
#include
#include
+#include
+#include
#define COUNTER_VALUE (136U)
-static void flipper_boot_recovery_draw_splash(u8g2_t* fb, size_t progress) {
+static void flipper_boot_recovery_draw_progress(Canvas* canvas, size_t progress) {
if(progress < COUNTER_VALUE) {
// Fill the progress bar while the progress is going down
- u8g2_SetDrawColor(fb, 0x01);
- u8g2_DrawRFrame(fb, 59, 41, 69, 8, 2);
+ canvas_draw_rframe(canvas, 59, 41, 69, 8, 2);
size_t width = (COUNTER_VALUE - progress) * 68 / COUNTER_VALUE;
- u8g2_DrawBox(fb, 60, 42, width, 6);
+ canvas_draw_box(canvas, 60, 42, width, 6);
} else {
- u8g2_SetDrawColor(fb, 0x00);
- u8g2_DrawRBox(fb, 59, 41, 69, 8, 2);
+ canvas_draw_rframe(canvas, 59, 41, 69, 8, 2);
+ canvas_set_color(canvas, ColorWhite);
+ canvas_draw_box(canvas, 60, 42, 67, 6);
+ canvas_set_color(canvas, ColorBlack);
}
- u8g2_SendBuffer(fb);
+ canvas_commit(canvas);
+}
+
+void flipper_boot_recovery_draw_splash(Canvas* canvas) {
+ canvas_set_color(canvas, ColorBlack);
+ canvas_set_font(canvas, FontPrimary);
+
+ canvas_draw_icon(canvas, 0, 0, &I_Erase_pin_128x64);
+
+ canvas_commit(canvas);
}
void flipper_boot_recovery_exec() {
- u8g2_t* fb = malloc(sizeof(u8g2_t));
- u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
- u8g2_InitDisplay(fb);
+ Canvas* canvas = canvas_init();
- CompressIcon* compress_icon = compress_icon_alloc();
- uint8_t* splash_data = NULL;
- compress_icon_decode(compress_icon, icon_get_data(&I_Erase_pin_128x64), &splash_data);
-
- u8g2_ClearBuffer(fb);
- u8g2_SetDrawColor(fb, 0x01);
-
- // Draw the recovery picture
- u8g2_DrawXBM(fb, 0, 0, 128, 64, splash_data);
- u8g2_SendBuffer(fb);
- u8g2_SetPowerSave(fb, 0);
- compress_icon_free(compress_icon);
+ // Show recovery splashscreen
+ flipper_boot_recovery_draw_splash(canvas);
size_t counter = COUNTER_VALUE;
while(counter) {
@@ -53,7 +52,7 @@ void flipper_boot_recovery_exec() {
counter = COUNTER_VALUE;
}
- flipper_boot_recovery_draw_splash(fb, counter);
+ flipper_boot_recovery_draw_progress(canvas, counter);
}
if(!counter) {
diff --git a/firmware/targets/furi_hal_include/furi_hal_bt.h b/firmware/targets/furi_hal_include/furi_hal_bt.h
index d717d346d..24817b552 100644
--- a/firmware/targets/furi_hal_include/furi_hal_bt.h
+++ b/firmware/targets/furi_hal_include/furi_hal_bt.h
@@ -230,7 +230,7 @@ bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode);
*/
void furi_hal_bt_set_profile_adv_name(
FuriHalBtProfile profile,
- const char name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH - 1]);
+ const char name[FURI_HAL_BT_ADV_NAME_LENGTH]);
const char* furi_hal_bt_get_profile_adv_name(FuriHalBtProfile profile);
diff --git a/firmware/targets/furi_hal_include/furi_hal_version.h b/firmware/targets/furi_hal_include/furi_hal_version.h
index b81602029..2ff6730f6 100644
--- a/firmware/targets/furi_hal_include/furi_hal_version.h
+++ b/firmware/targets/furi_hal_include/furi_hal_version.h
@@ -16,9 +16,9 @@ extern "C" {
#define FURI_HAL_VERSION_NAME_LENGTH 8
#define FURI_HAL_VERSION_ARRAY_NAME_LENGTH (FURI_HAL_VERSION_NAME_LENGTH + 1)
-/** BLE symbol + name */
+#define FURI_HAL_BT_ADV_NAME_LENGTH (18 + 1) // 18 characters + null terminator
#define FURI_HAL_VERSION_DEVICE_NAME_LENGTH \
- (1 + FURI_HAL_VERSION_ARRAY_NAME_LENGTH) + 9 // for bad kb custom name
+ (1 + FURI_HAL_BT_ADV_NAME_LENGTH) // Used for custom BT name, BLE symbol + name
/** OTP Versions enum */
typedef enum {
diff --git a/lib/fatfs/diskio.h b/lib/fatfs/diskio.h
index 5b61e570b..0ccc9d461 100644
--- a/lib/fatfs/diskio.h
+++ b/lib/fatfs/diskio.h
@@ -37,7 +37,6 @@ DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
-DWORD get_fattime (void);
/* Disk Status Bits (DSTATUS) */
diff --git a/lib/ibutton/protocols/dallas/protocol_group_dallas.c b/lib/ibutton/protocols/dallas/protocol_group_dallas.c
index d0ffa511b..63ec97855 100644
--- a/lib/ibutton/protocols/dallas/protocol_group_dallas.c
+++ b/lib/ibutton/protocols/dallas/protocol_group_dallas.c
@@ -14,8 +14,8 @@ typedef struct {
static iButtonProtocolGroupDallas* ibutton_protocol_group_dallas_alloc() {
iButtonProtocolGroupDallas* group = malloc(sizeof(iButtonProtocolGroupDallas));
- group->host = onewire_host_alloc(&ibutton_gpio);
- group->bus = onewire_slave_alloc(&ibutton_gpio);
+ group->host = onewire_host_alloc(&gpio_ibutton);
+ group->bus = onewire_slave_alloc(&gpio_ibutton);
return group;
}
diff --git a/lib/nfc/parsers/troika_4k_parser.c b/lib/nfc/parsers/troika_4k_parser.c
index aca0ee2c0..af4b4b3d1 100644
--- a/lib/nfc/parsers/troika_4k_parser.c
+++ b/lib/nfc/parsers/troika_4k_parser.c
@@ -90,13 +90,14 @@ bool troika_4k_parser_parse(NfcDeviceData* dev_data) {
uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5];
uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
- temp_ptr = &data->block[8 * 4].value[3];
+ temp_ptr = &data->block[8 * 4].value[2];
uint32_t number = 0;
- for(size_t i = 0; i < 4; i++) {
+ for(size_t i = 1; i < 5; i++) {
number <<= 8;
number |= temp_ptr[i];
}
number >>= 4;
+ number |= (temp_ptr[0] & 0xf) << 28;
furi_string_printf(
dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance);
diff --git a/lib/nfc/parsers/troika_parser.c b/lib/nfc/parsers/troika_parser.c
index fe699a765..17de0d8a5 100644
--- a/lib/nfc/parsers/troika_parser.c
+++ b/lib/nfc/parsers/troika_parser.c
@@ -70,13 +70,14 @@ bool troika_parser_parse(NfcDeviceData* dev_data) {
// Parse data
uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5];
uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
- temp_ptr = &data->block[8 * 4].value[3];
+ temp_ptr = &data->block[8 * 4].value[2];
uint32_t number = 0;
- for(size_t i = 0; i < 4; i++) {
+ for(size_t i = 1; i < 5; i++) {
number <<= 8;
number |= temp_ptr[i];
}
number >>= 4;
+ number |= (temp_ptr[0] & 0xf) << 28;
furi_string_printf(
dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance);
diff --git a/lib/subghz/blocks/custom_btn.c b/lib/subghz/blocks/custom_btn.c
new file mode 100644
index 000000000..dd1436798
--- /dev/null
+++ b/lib/subghz/blocks/custom_btn.c
@@ -0,0 +1,34 @@
+#include "custom_btn.h"
+
+static uint8_t custom_btn_id;
+static uint8_t custom_btn_original;
+static uint8_t custom_btn_max_btns = 0;
+
+void subghz_custom_btn_set(uint8_t b) {
+ if(b > custom_btn_max_btns) {
+ custom_btn_id = 0;
+ } else {
+ custom_btn_id = b;
+ }
+}
+
+uint8_t subghz_custom_btn_get() {
+ return custom_btn_id;
+}
+
+void subghz_custom_btn_set_original(uint8_t b) {
+ custom_btn_original = b;
+}
+
+uint8_t subghz_custom_btn_get_original() {
+ return custom_btn_original;
+}
+
+void subghz_custom_btn_set_max(uint8_t b) {
+ custom_btn_max_btns = b;
+}
+
+void subghz_custom_btns_reset() {
+ custom_btn_original = 0;
+ custom_btn_max_btns = 0;
+}
\ No newline at end of file
diff --git a/lib/subghz/blocks/custom_btn.h b/lib/subghz/blocks/custom_btn.h
new file mode 100644
index 000000000..bd3e56b8a
--- /dev/null
+++ b/lib/subghz/blocks/custom_btn.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void subghz_custom_btn_set(uint8_t b);
+
+uint8_t subghz_custom_btn_get();
+
+void subghz_custom_btn_set_original(uint8_t b);
+
+uint8_t subghz_custom_btn_get_original();
+
+void subghz_custom_btn_set_max(uint8_t b);
+
+void subghz_custom_btns_reset();
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c
index 5c09e8bb8..04e10adf5 100644
--- a/lib/subghz/protocols/alutech_at_4n.c
+++ b/lib/subghz/protocols/alutech_at_4n.c
@@ -5,6 +5,8 @@
#include "../blocks/generic.h"
#include "../blocks/math.h"
+#include "../blocks/custom_btn.h"
+
#define TAG "SubGhzProtocoAlutech_at_4n"
#define SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE 0xFFFFFFFF
@@ -77,25 +79,6 @@ const SubGhzProtocol subghz_protocol_alutech_at_4n = {
.encoder = &subghz_protocol_alutech_at_4n_encoder,
};
-static uint8_t al_btn_temp_id;
-static uint8_t al_btn_temp_id_original;
-
-void alutech_set_btn(uint8_t b) {
- al_btn_temp_id = b;
-}
-
-uint8_t alutech_get_original_btn() {
- return al_btn_temp_id_original;
-}
-
-uint8_t alutech_get_custom_btn() {
- return al_btn_temp_id;
-}
-
-void alutech_reset_original_btn() {
- al_btn_temp_id_original = 0;
-}
-
void* subghz_protocol_encoder_alutech_at_4n_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderAlutech_at_4n* instance =
@@ -341,13 +324,16 @@ static bool subghz_protocol_encoder_alutech_at_4n_get_upload(
furi_assert(instance);
// Save original button for later use
- if(al_btn_temp_id_original == 0) {
- al_btn_temp_id_original = btn;
+ if(subghz_custom_btn_get_original() == 0) {
+ subghz_custom_btn_set_original(btn);
}
+ uint8_t custom_btn_id = subghz_custom_btn_get();
+ uint8_t original_btn_num = subghz_custom_btn_get_original();
+
// Set custom button
- if(al_btn_temp_id == 1) {
- switch(al_btn_temp_id_original) {
+ if(custom_btn_id == 1) {
+ switch(original_btn_num) {
case 0x11:
btn = 0x22;
break;
@@ -368,8 +354,8 @@ static bool subghz_protocol_encoder_alutech_at_4n_get_upload(
break;
}
}
- if(al_btn_temp_id == 2) {
- switch(al_btn_temp_id_original) {
+ if(custom_btn_id == 2) {
+ switch(original_btn_num) {
case 0x11:
btn = 0x44;
break;
@@ -390,8 +376,8 @@ static bool subghz_protocol_encoder_alutech_at_4n_get_upload(
break;
}
}
- if(al_btn_temp_id == 3) {
- switch(al_btn_temp_id_original) {
+ if(custom_btn_id == 3) {
+ switch(original_btn_num) {
case 0x11:
btn = 0x33;
break;
@@ -412,8 +398,8 @@ static bool subghz_protocol_encoder_alutech_at_4n_get_upload(
break;
}
}
- if(al_btn_temp_id == 4) {
- switch(al_btn_temp_id_original) {
+ if(custom_btn_id == 4) {
+ switch(original_btn_num) {
case 0x11:
btn = 0xFF;
break;
@@ -435,8 +421,8 @@ static bool subghz_protocol_encoder_alutech_at_4n_get_upload(
}
}
- if((al_btn_temp_id == 0) && (al_btn_temp_id_original != 0)) {
- btn = al_btn_temp_id_original;
+ if((custom_btn_id == 0) && (original_btn_num != 0)) {
+ btn = original_btn_num;
}
//gen new key
if(subghz_protocol_alutech_at_4n_gen_data(instance, btn)) {
@@ -735,9 +721,10 @@ static void subghz_protocol_alutech_at_4n_remote_controller(
}
// Save original button for later use
- if(al_btn_temp_id_original == 0) {
- al_btn_temp_id_original = instance->btn;
+ if(subghz_custom_btn_get_original() == 0) {
+ subghz_custom_btn_set_original(instance->btn);
}
+ subghz_custom_btn_set_max(4);
}
uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context) {
diff --git a/lib/subghz/protocols/alutech_at_4n.h b/lib/subghz/protocols/alutech_at_4n.h
index 023fca591..68d6580ea 100644
--- a/lib/subghz/protocols/alutech_at_4n.h
+++ b/lib/subghz/protocols/alutech_at_4n.h
@@ -14,14 +14,6 @@ extern const SubGhzProtocolDecoder subghz_protocol_alutech_at_4n_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_alutech_at_4n_encoder;
extern const SubGhzProtocol subghz_protocol_alutech_at_4n;
-// Custom buttons
-void alutech_set_btn(uint8_t b);
-
-uint8_t alutech_get_original_btn();
-uint8_t alutech_get_custom_btn();
-
-void alutech_reset_original_btn();
-
/**
* Allocate SubGhzProtocolEncoderAlutech_at_4n.
* @param environment Pointer to a SubGhzEnvironment instance
diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c
index ab021151d..6698f54fe 100644
--- a/lib/subghz/protocols/keeloq.c
+++ b/lib/subghz/protocols/keeloq.c
@@ -10,6 +10,8 @@
#include "../blocks/generic.h"
#include "../blocks/math.h"
+#include "../blocks/custom_btn.h"
+
#define TAG "SubGhzProtocolKeeloq"
static const SubGhzBlockConst subghz_protocol_keeloq_const = {
@@ -86,25 +88,12 @@ const SubGhzProtocol subghz_protocol_keeloq = {
static const char* mfname;
static uint8_t kl_type;
-static uint8_t btn_temp_id;
-static uint8_t btn_temp_id_original;
static uint8_t klq_prog_mode;
static uint16_t temp_counter;
-void keeloq_set_btn(uint8_t b) {
- btn_temp_id = b;
-}
-
-uint8_t keeloq_get_original_btn() {
- return btn_temp_id_original;
-}
-
-uint8_t keeloq_get_custom_btn() {
- return btn_temp_id;
-}
-
void keeloq_reset_original_btn() {
- btn_temp_id_original = 0;
+ subghz_custom_btn_set_original(0);
+ subghz_custom_btn_set_max(0);
temp_counter = 0;
klq_prog_mode = 0;
}
@@ -243,9 +232,9 @@ static bool subghz_protocol_keeloq_gen_data(
decrypt = btn << 28 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt;
}
- // Beninca -> 4bit serial - simple XOR
+ // Beninca / Allmatic -> no serial - simple XOR
if(strcmp(instance->manufacture_name, "Beninca") == 0) {
- decrypt = btn << 28 | (instance->generic.serial & 0xF) << 16 | instance->generic.cnt;
+ decrypt = btn << 28 | (0x000) << 16 | instance->generic.cnt;
}
if(strcmp(instance->manufacture_name, "Unknown") == 0) {
@@ -384,8 +373,8 @@ static bool
furi_assert(instance);
// Save original button
- if(btn_temp_id_original == 0) {
- btn_temp_id_original = btn;
+ if(subghz_custom_btn_get_original() == 0) {
+ subghz_custom_btn_set_original(btn);
}
if(instance->manufacture_name == 0x0) {
@@ -402,9 +391,12 @@ static bool
klq_last_custom_btn = 0xF;
}
+ uint8_t custom_btn_id = subghz_custom_btn_get();
+ uint8_t original_btn_num = subghz_custom_btn_get_original();
+
// Set custom button
- if(btn_temp_id == 1) {
- switch(btn_temp_id_original) {
+ if(custom_btn_id == 1) {
+ switch(original_btn_num) {
case 0x1:
btn = 0x2;
break;
@@ -429,8 +421,8 @@ static bool
break;
}
}
- if(btn_temp_id == 2) {
- switch(btn_temp_id_original) {
+ if(custom_btn_id == 2) {
+ switch(original_btn_num) {
case 0x1:
btn = 0x4;
break;
@@ -455,8 +447,8 @@ static bool
break;
}
}
- if(btn_temp_id == 3) {
- switch(btn_temp_id_original) {
+ if(custom_btn_id == 3) {
+ switch(original_btn_num) {
case 0x1:
btn = 0x8;
break;
@@ -481,8 +473,8 @@ static bool
break;
}
}
- if(btn_temp_id == 4) {
- switch(btn_temp_id_original) {
+ if(custom_btn_id == 4) {
+ switch(original_btn_num) {
case 0x1:
btn = klq_last_custom_btn;
break;
@@ -508,8 +500,8 @@ static bool
}
}
- if((btn_temp_id == 0) && (btn_temp_id_original != 0)) {
- btn = btn_temp_id_original;
+ if((custom_btn_id == 0) && (original_btn_num != 0)) {
+ btn = original_btn_num;
}
// Generate new key
@@ -1213,9 +1205,10 @@ static void subghz_protocol_keeloq_check_remote_controller(
instance->btn = key_fix >> 28;
// Save original button for later use
- if(btn_temp_id_original == 0) {
- btn_temp_id_original = instance->btn;
+ if(subghz_custom_btn_get_original() == 0) {
+ subghz_custom_btn_set_original(instance->btn);
}
+ subghz_custom_btn_set_max(4);
}
uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context) {
diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h
index a1d5b26b4..31fdb2198 100644
--- a/lib/subghz/protocols/keeloq.h
+++ b/lib/subghz/protocols/keeloq.h
@@ -2,6 +2,8 @@
#include "base.h"
+#include "../blocks/custom_btn.h"
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -19,11 +21,6 @@ void keeloq_reset_mfname();
void keeloq_reset_kl_type();
-void keeloq_set_btn(uint8_t b);
-
-uint8_t keeloq_get_original_btn();
-uint8_t keeloq_get_custom_btn();
-
void keeloq_reset_original_btn();
/**
diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c
index dbdb4c8b5..5d8e7f587 100644
--- a/lib/subghz/protocols/nice_flor_s.c
+++ b/lib/subghz/protocols/nice_flor_s.c
@@ -6,6 +6,8 @@
#include "../blocks/generic.h"
#include "../blocks/math.h"
+#include "../blocks/custom_btn.h"
+
/*
* https://phreakerclub.com/1615
* https://phreakerclub.com/forum/showthread.php?t=2360
@@ -84,25 +86,6 @@ const SubGhzProtocol subghz_protocol_nice_flor_s = {
.encoder = &subghz_protocol_nice_flor_s_encoder,
};
-static uint8_t n_btn_temp_id;
-static uint8_t n_btn_temp_id_original;
-
-void nice_flors_set_btn(uint8_t b) {
- n_btn_temp_id = b;
-}
-
-uint8_t nice_flors_get_original_btn() {
- return n_btn_temp_id_original;
-}
-
-uint8_t nice_flors_get_custom_btn() {
- return n_btn_temp_id;
-}
-
-void nice_flors_reset_original_btn() {
- n_btn_temp_id_original = 0;
-}
-
static void subghz_protocol_nice_flor_s_remote_controller(
SubGhzBlockGeneric* instance,
const char* file_name);
@@ -148,13 +131,16 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload(
btn = instance->generic.btn;
// Save original button for later use
- if(n_btn_temp_id_original == 0) {
- n_btn_temp_id_original = btn;
+ if(subghz_custom_btn_get_original() == 0) {
+ subghz_custom_btn_set_original(btn);
}
+ uint8_t custom_btn_id = subghz_custom_btn_get();
+ uint8_t original_btn_num = subghz_custom_btn_get_original();
+
// Set custom button
- if(n_btn_temp_id == 1) {
- switch(n_btn_temp_id_original) {
+ if(custom_btn_id == 1) {
+ switch(original_btn_num) {
case 0x1:
btn = 0x2;
break;
@@ -172,8 +158,8 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload(
break;
}
}
- if(n_btn_temp_id == 2) {
- switch(n_btn_temp_id_original) {
+ if(custom_btn_id == 2) {
+ switch(original_btn_num) {
case 0x1:
btn = 0x4;
break;
@@ -191,8 +177,8 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload(
break;
}
}
- if(n_btn_temp_id == 3) {
- switch(n_btn_temp_id_original) {
+ if(custom_btn_id == 3) {
+ switch(original_btn_num) {
case 0x1:
btn = 0x8;
break;
@@ -211,8 +197,8 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload(
}
}
- if((n_btn_temp_id == 0) && (n_btn_temp_id_original != 0)) {
- btn = n_btn_temp_id_original;
+ if((custom_btn_id == 0) && (original_btn_num != 0)) {
+ btn = original_btn_num;
}
size_t size_upload = ((instance->generic.data_count_bit * 2) + ((37 + 2 + 2) * 2) * 16);
@@ -756,9 +742,10 @@ static void subghz_protocol_nice_flor_s_remote_controller(
}
// Save original button for later use
- if(n_btn_temp_id_original == 0) {
- n_btn_temp_id_original = instance->btn;
+ if(subghz_custom_btn_get_original() == 0) {
+ subghz_custom_btn_set_original(instance->btn);
}
+ subghz_custom_btn_set_max(3);
}
uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context) {
diff --git a/lib/subghz/protocols/nice_flor_s.h b/lib/subghz/protocols/nice_flor_s.h
index 333f826e1..56ef15aa3 100644
--- a/lib/subghz/protocols/nice_flor_s.h
+++ b/lib/subghz/protocols/nice_flor_s.h
@@ -15,14 +15,6 @@ extern const SubGhzProtocolDecoder subghz_protocol_nice_flor_s_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_nice_flor_s_encoder;
extern const SubGhzProtocol subghz_protocol_nice_flor_s;
-// Custom buttons
-void nice_flors_set_btn(uint8_t b);
-
-uint8_t nice_flors_get_original_btn();
-uint8_t nice_flors_get_custom_btn();
-
-void nice_flors_reset_original_btn();
-
/**
* Allocate SubGhzProtocolEncoderNiceFlorS.
* @param environment Pointer to a SubGhzEnvironment instance
diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c
index b68e3c869..c2fa77b2f 100644
--- a/lib/subghz/protocols/scher_khan.c
+++ b/lib/subghz/protocols/scher_khan.c
@@ -220,18 +220,48 @@ static void subghz_protocol_scher_khan_check_remote_controller(
*/
switch(instance->data_count_bit) {
- // case 35: //MAGIC CODE, Static
- // instance->protocol_name = "MAGIC CODE, Static";
- // break;
+ case 35: //MAGIC CODE, Static
+ *protocol_name = "MAGIC CODE, Static";
+ instance->serial = 0;
+ instance->btn = 0;
+ instance->cnt = 0;
+ break;
case 51: //MAGIC CODE, Dynamic
*protocol_name = "MAGIC CODE, Dynamic";
instance->serial = ((instance->data >> 24) & 0xFFFFFF0) | ((instance->data >> 20) & 0x0F);
instance->btn = (instance->data >> 24) & 0x0F;
instance->cnt = instance->data & 0xFFFF;
break;
- // case 57: //MAGIC CODE PRO / PRO2
- // instance->protocol_name = "MAGIC CODE PRO / PRO2";
- // break;
+ case 57: //MAGIC CODE PRO / PRO2
+ *protocol_name = "MAGIC CODE PRO/PRO2";
+ instance->serial = 0;
+ instance->btn = 0;
+ instance->cnt = 0;
+ break;
+ case 63: //MAGIC CODE, Dynamic Response
+ *protocol_name = "MAGIC CODE, Response";
+ instance->serial = 0;
+ instance->btn = 0;
+ instance->cnt = 0;
+ break;
+ case 64: //MAGICAR, Response ???
+ *protocol_name = "MAGICAR, Response";
+ instance->serial = 0;
+ instance->btn = 0;
+ instance->cnt = 0;
+ break;
+ case 81: //MAGIC CODE PRO / PRO2 Response ???
+ *protocol_name = "MAGIC CODE PRO,\n Response";
+ instance->serial = 0;
+ instance->btn = 0;
+ instance->cnt = 0;
+ break;
+ case 82: //MAGIC CODE PRO / PRO2 Response ???
+ *protocol_name = "MAGIC CODE PRO,\n Response";
+ instance->serial = 0;
+ instance->btn = 0;
+ instance->cnt = 0;
+ break;
default:
*protocol_name = "Unknown";
diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c
index a0bc2b9f4..5d0ecb0dd 100644
--- a/lib/subghz/protocols/secplus_v2.c
+++ b/lib/subghz/protocols/secplus_v2.c
@@ -7,6 +7,8 @@
#include "../blocks/generic.h"
#include "../blocks/math.h"
+#include "../blocks/custom_btn.h"
+
/*
* Help
* https://github.com/argilo/secplus
@@ -83,25 +85,6 @@ const SubGhzProtocol subghz_protocol_secplus_v2 = {
.encoder = &subghz_protocol_secplus_v2_encoder,
};
-static uint8_t sc_btn_temp_id;
-static uint8_t sc_btn_temp_id_original;
-
-void secplus2_set_btn(uint8_t b) {
- sc_btn_temp_id = b;
-}
-
-uint8_t secplus2_get_original_btn() {
- return sc_btn_temp_id_original;
-}
-
-uint8_t secplus2_get_custom_btn() {
- return sc_btn_temp_id;
-}
-
-void secplus2_reset_original_btn() {
- sc_btn_temp_id_original = 0;
-}
-
void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolEncoderSecPlus_v2));
@@ -359,9 +342,10 @@ static void
}
// Save original button for later use
- if(sc_btn_temp_id_original == 0) {
- sc_btn_temp_id_original = instance->btn;
+ if(subghz_custom_btn_get_original() == 0) {
+ subghz_custom_btn_set_original(instance->btn);
}
+ subghz_custom_btn_set_max(3);
}
/**
@@ -398,13 +382,16 @@ static uint64_t subghz_protocol_secplus_v2_encode_half(uint8_t roll_array[], uin
static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* instance) {
// Save original button for later use
- if(sc_btn_temp_id_original == 0) {
- sc_btn_temp_id_original = instance->generic.btn;
+ if(subghz_custom_btn_get_original() == 0) {
+ subghz_custom_btn_set_original(instance->generic.btn);
}
+ uint8_t custom_btn_id = subghz_custom_btn_get();
+ uint8_t original_btn_num = subghz_custom_btn_get_original();
+
// Set custom button
- if(sc_btn_temp_id == 1) {
- switch(sc_btn_temp_id_original) {
+ if(custom_btn_id == 1) {
+ switch(original_btn_num) {
case 0x68:
instance->generic.btn = 0x80;
break;
@@ -422,8 +409,8 @@ static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* i
break;
}
}
- if(sc_btn_temp_id == 2) {
- switch(sc_btn_temp_id_original) {
+ if(custom_btn_id == 2) {
+ switch(original_btn_num) {
case 0x68:
instance->generic.btn = 0x81;
break;
@@ -441,8 +428,8 @@ static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* i
break;
}
}
- if(sc_btn_temp_id == 3) {
- switch(sc_btn_temp_id_original) {
+ if(custom_btn_id == 3) {
+ switch(original_btn_num) {
case 0x68:
instance->generic.btn = 0xE2;
break;
@@ -460,8 +447,8 @@ static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* i
break;
}
}
- if((sc_btn_temp_id == 0) && (sc_btn_temp_id_original != 0)) {
- instance->generic.btn = sc_btn_temp_id_original;
+ if((custom_btn_id == 0) && (original_btn_num != 0)) {
+ instance->generic.btn = original_btn_num;
}
uint32_t fixed_1[1] = {instance->generic.btn << 12 | instance->generic.serial >> 20};
uint32_t fixed_2[1] = {instance->generic.serial & 0xFFFFF};
diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h
index 4d9d5df00..c71038bcb 100644
--- a/lib/subghz/protocols/secplus_v2.h
+++ b/lib/subghz/protocols/secplus_v2.h
@@ -14,14 +14,6 @@ extern const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder;
extern const SubGhzProtocol subghz_protocol_secplus_v2;
-// Custom buttons
-void secplus2_set_btn(uint8_t b);
-
-uint8_t secplus2_get_original_btn();
-uint8_t secplus2_get_custom_btn();
-
-void secplus2_reset_original_btn();
-
/**
* Allocate SubGhzProtocolEncoderSecPlus_v2.
* @param environment Pointer to a SubGhzEnvironment instance
diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c
index 0294a1922..047efb688 100644
--- a/lib/subghz/protocols/somfy_telis.c
+++ b/lib/subghz/protocols/somfy_telis.c
@@ -7,6 +7,8 @@
#include "../blocks/generic.h"
#include "../blocks/math.h"
+#include "../blocks/custom_btn.h"
+
#define TAG "SubGhzProtocolSomfyTelis"
static const SubGhzBlockConst subghz_protocol_somfy_telis_const = {
@@ -73,25 +75,6 @@ const SubGhzProtocol subghz_protocol_somfy_telis = {
.encoder = &subghz_protocol_somfy_telis_encoder,
};
-static uint8_t st_btn_temp_id;
-static uint8_t st_btn_temp_id_original;
-
-void somfy_telis_set_btn(uint8_t b) {
- st_btn_temp_id = b;
-}
-
-uint8_t somfy_telis_get_original_btn() {
- return st_btn_temp_id_original;
-}
-
-uint8_t somfy_telis_get_custom_btn() {
- return st_btn_temp_id;
-}
-
-void somfy_telis_reset_original_btn() {
- st_btn_temp_id_original = 0;
-}
-
void* subghz_protocol_encoder_somfy_telis_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderSomfyTelis* instance = malloc(sizeof(SubGhzProtocolEncoderSomfyTelis));
@@ -128,13 +111,16 @@ static bool subghz_protocol_somfy_telis_gen_data(
}
// Save original button for later use
- if(st_btn_temp_id_original == 0) {
- st_btn_temp_id_original = btn;
+ if(subghz_custom_btn_get_original() == 0) {
+ subghz_custom_btn_set_original(btn);
}
+ uint8_t custom_btn_id = subghz_custom_btn_get();
+ uint8_t original_btn_num = subghz_custom_btn_get_original();
+
// Set custom button
- if(st_btn_temp_id == 1) {
- switch(st_btn_temp_id_original) {
+ if(custom_btn_id == 1) {
+ switch(original_btn_num) {
case 0x1:
btn = 0x2;
break;
@@ -152,8 +138,8 @@ static bool subghz_protocol_somfy_telis_gen_data(
break;
}
}
- if(st_btn_temp_id == 2) {
- switch(st_btn_temp_id_original) {
+ if(custom_btn_id == 2) {
+ switch(original_btn_num) {
case 0x1:
btn = 0x4;
break;
@@ -171,8 +157,8 @@ static bool subghz_protocol_somfy_telis_gen_data(
break;
}
}
- if(st_btn_temp_id == 3) {
- switch(st_btn_temp_id_original) {
+ if(custom_btn_id == 3) {
+ switch(original_btn_num) {
case 0x1:
btn = 0x8;
break;
@@ -191,8 +177,8 @@ static bool subghz_protocol_somfy_telis_gen_data(
}
}
- if((st_btn_temp_id == 0) && (st_btn_temp_id_original != 0)) {
- btn = st_btn_temp_id_original;
+ if((custom_btn_id == 0) && (original_btn_num != 0)) {
+ btn = original_btn_num;
}
if(instance->generic.cnt < 0xFFFF) {
@@ -684,9 +670,10 @@ static void subghz_protocol_somfy_telis_check_remote_controller(SubGhzBlockGener
instance->serial = data & 0xFFFFFF; // address
// Save original button for later use
- if(st_btn_temp_id_original == 0) {
- st_btn_temp_id_original = instance->btn;
+ if(subghz_custom_btn_get_original() == 0) {
+ subghz_custom_btn_set_original(instance->btn);
}
+ subghz_custom_btn_set_max(3);
}
/**
diff --git a/lib/subghz/protocols/somfy_telis.h b/lib/subghz/protocols/somfy_telis.h
index dc1ec2b77..053940304 100644
--- a/lib/subghz/protocols/somfy_telis.h
+++ b/lib/subghz/protocols/somfy_telis.h
@@ -15,14 +15,6 @@ extern const SubGhzProtocolDecoder subghz_protocol_somfy_telis_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_somfy_telis_encoder;
extern const SubGhzProtocol subghz_protocol_somfy_telis;
-// Custom buttons
-void somfy_telis_set_btn(uint8_t b);
-
-uint8_t somfy_telis_get_original_btn();
-uint8_t somfy_telis_get_custom_btn();
-
-void somfy_telis_reset_original_btn();
-
/**
* Allocate SubGhzProtocolEncoderSomfyTelis.
* @param environment Pointer to a SubGhzEnvironment instance
diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c
index 5f96d3fcb..181dee643 100644
--- a/lib/subghz/protocols/star_line.c
+++ b/lib/subghz/protocols/star_line.c
@@ -419,11 +419,14 @@ void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t
if(duration >= (subghz_protocol_star_line_const.te_long +
subghz_protocol_star_line_const.te_delta)) {
instance->decoder.parser_step = StarLineDecoderStepReset;
- if(instance->decoder.decode_count_bit >=
- subghz_protocol_star_line_const.min_count_bit_for_found) {
+ if((instance->decoder.decode_count_bit >=
+ subghz_protocol_star_line_const.min_count_bit_for_found) &&
+ (instance->decoder.decode_count_bit <=
+ subghz_protocol_star_line_const.min_count_bit_for_found + 2)) {
if(instance->generic.data != instance->decoder.decode_data) {
instance->generic.data = instance->decoder.decode_data;
- instance->generic.data_count_bit = instance->decoder.decode_count_bit;
+ instance->generic.data_count_bit =
+ subghz_protocol_star_line_const.min_count_bit_for_found;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
@@ -447,14 +450,24 @@ void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t
subghz_protocol_star_line_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_star_line_const.te_short) <
subghz_protocol_star_line_const.te_delta)) {
- subghz_protocol_blocks_add_bit(&instance->decoder, 0);
+ if(instance->decoder.decode_count_bit <
+ subghz_protocol_star_line_const.min_count_bit_for_found) {
+ subghz_protocol_blocks_add_bit(&instance->decoder, 0);
+ } else {
+ instance->decoder.decode_count_bit++;
+ }
instance->decoder.parser_step = StarLineDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_star_line_const.te_long) <
subghz_protocol_star_line_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long) <
subghz_protocol_star_line_const.te_delta)) {
- subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+ if(instance->decoder.decode_count_bit <
+ subghz_protocol_star_line_const.min_count_bit_for_found) {
+ subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+ } else {
+ instance->decoder.decode_count_bit++;
+ }
instance->decoder.parser_step = StarLineDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = StarLineDecoderStepReset;
diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript
index caff7bd0a..9dce9d0f4 100644
--- a/lib/toolbox/SConscript
+++ b/lib/toolbox/SConscript
@@ -28,6 +28,7 @@ env.Append(
File("stream/buffered_file_stream.h"),
File("protocols/protocol_dict.h"),
File("pretty_format.h"),
+ File("hex.h"),
],
)
diff --git a/scripts/fbt_tools/fbt_assets.py b/scripts/fbt_tools/fbt_assets.py
index d2a58f3fb..e4c567993 100644
--- a/scripts/fbt_tools/fbt_assets.py
+++ b/scripts/fbt_tools/fbt_assets.py
@@ -1,6 +1,6 @@
from SCons.Builder import Builder
from SCons.Action import Action
-from SCons.Errors import SConsEnvironmentError
+from SCons.Errors import StopError
import os
import subprocess
@@ -90,7 +90,7 @@ def proto_ver_generator(target, source, env):
source_dir=src_dir,
)
except (subprocess.CalledProcessError, EnvironmentError) as e:
- raise SConsEnvironmentError("Git: describe failed")
+ raise StopError("Git: describe failed")
git_major, git_minor = git_describe.split(".")
version_file_data = (
diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py
index ef0c2d301..4ac1c6873 100644
--- a/scripts/fbt_tools/fbt_extapps.py
+++ b/scripts/fbt_tools/fbt_extapps.py
@@ -21,6 +21,10 @@ from fbt.sdk.cache import SdkCache
from fbt.util import extract_abs_dir_path
+_FAP_META_SECTION = ".fapmeta"
+_FAP_FILEASSETS_SECTION = ".fapassets"
+
+
@dataclass
class FlipperExternalAppInfo:
app: FlipperApplication
@@ -234,6 +238,8 @@ def BuildAppElf(env, app):
def prepare_app_metadata(target, source, env):
+ metadata_node = next(filter(lambda t: t.name.endswith(_FAP_META_SECTION), target))
+
sdk_cache = SdkCache(env["SDK_DEFINITION"].path, load_version_only=True)
if not sdk_cache.is_buildable():
@@ -242,8 +248,7 @@ def prepare_app_metadata(target, source, env):
)
app = env["APP"]
- meta_file_name = source[0].path + ".meta"
- with open(meta_file_name, "wb") as f:
+ with open(metadata_node.abspath, "wb") as f:
f.write(
assemble_manifest_data(
app_manifest=app,
@@ -337,24 +342,26 @@ def embed_app_metadata_emitter(target, source, env):
if app.apptype == FlipperAppType.PLUGIN:
target[0].name = target[0].name.replace(".fap", ".fal")
- meta_file_name = source[0].path + ".meta"
- target.append("#" + meta_file_name)
+ target.append(env.File(source[0].abspath + _FAP_META_SECTION))
if app.fap_file_assets:
- files_section = source[0].path + ".files.section"
- target.append("#" + files_section)
+ target.append(env.File(source[0].abspath + _FAP_FILEASSETS_SECTION))
return (target, source)
def prepare_app_files(target, source, env):
+ files_section_node = next(
+ filter(lambda t: t.name.endswith(_FAP_FILEASSETS_SECTION), target)
+ )
+
app = env["APP"]
- directory = app._appdir.Dir(app.fap_file_assets)
+ directory = env.Dir(app._apppath).Dir(app.fap_file_assets)
if not directory.exists():
raise UserError(f"File asset directory {directory} does not exist")
bundler = FileBundler(directory.abspath)
- bundler.export(source[0].path + ".files.section")
+ bundler.export(files_section_node.abspath)
def generate_embed_app_metadata_actions(source, target, env, for_signature):
@@ -367,15 +374,15 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature):
objcopy_str = (
"${OBJCOPY} "
"--remove-section .ARM.attributes "
- "--add-section .fapmeta=${SOURCE}.meta "
+ "--add-section ${_FAP_META_SECTION}=${SOURCE}${_FAP_META_SECTION} "
)
if app.fap_file_assets:
actions.append(Action(prepare_app_files, "$APPFILE_COMSTR"))
- objcopy_str += "--add-section .fapassets=${SOURCE}.files.section "
+ objcopy_str += "--add-section ${_FAP_FILEASSETS_SECTION}=${SOURCE}${_FAP_FILEASSETS_SECTION} "
objcopy_str += (
- "--set-section-flags .fapmeta=contents,noload,readonly,data "
+ "--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data "
"--strip-debug --strip-unneeded "
"--add-gnu-debuglink=${SOURCE} "
"${SOURCES} ${TARGET}"
@@ -391,6 +398,51 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature):
return Action(actions)
+def AddAppLaunchTarget(env, appname, launch_target_name):
+ deploy_sources, flipp_dist_paths, validators = [], [], []
+ run_script_extra_ars = ""
+
+ def _add_dist_targets(app_artifacts):
+ validators.append(app_artifacts.validator)
+ for _, ext_path in app_artifacts.dist_entries:
+ deploy_sources.append(app_artifacts.compact)
+ flipp_dist_paths.append(f"/ext/{ext_path}")
+ return app_artifacts
+
+ def _add_host_app_to_targets(host_app):
+ artifacts_app_to_run = env["EXT_APPS"].get(host_app.appid, None)
+ _add_dist_targets(artifacts_app_to_run)
+ for plugin in host_app._plugins:
+ _add_dist_targets(env["EXT_APPS"].get(plugin.appid, None))
+
+ artifacts_app_to_run = env.GetExtAppByIdOrPath(appname)
+ if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN:
+ # We deploy host app instead
+ host_app = env["APPMGR"].get(artifacts_app_to_run.app.requires[0])
+
+ if host_app:
+ if host_app.apptype == FlipperAppType.EXTERNAL:
+ _add_host_app_to_targets(host_app)
+ else:
+ # host app is a built-in app
+ run_script_extra_ars = f"-a {host_app.name}"
+ _add_dist_targets(artifacts_app_to_run)
+ else:
+ raise UserError("Host app is unknown")
+ else:
+ _add_host_app_to_targets(artifacts_app_to_run.app)
+
+ # print(deploy_sources, flipp_dist_paths)
+ env.PhonyTarget(
+ launch_target_name,
+ '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}',
+ source=deploy_sources,
+ FLIPPER_FILE_TARGETS=flipp_dist_paths,
+ EXTRA_ARGS=run_script_extra_ars,
+ )
+ env.Alias(launch_target_name, validators)
+
+
def generate(env, **kw):
env.SetDefault(
EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}",
@@ -410,10 +462,14 @@ def generate(env, **kw):
EXT_APPS={}, # appid -> FlipperExternalAppInfo
EXT_LIBS={},
_APP_ICONS=[],
+ _FAP_META_SECTION=_FAP_META_SECTION,
+ _FAP_FILEASSETS_SECTION=_FAP_FILEASSETS_SECTION,
)
env.AddMethod(BuildAppElf)
env.AddMethod(GetExtAppByIdOrPath)
+ env.AddMethod(AddAppLaunchTarget)
+
env.Append(
BUILDERS={
"FapDist": Builder(
diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py
index 324819818..90d0831eb 100644
--- a/scripts/fbt_tools/fbt_sdk.py
+++ b/scripts/fbt_tools/fbt_sdk.py
@@ -38,13 +38,13 @@ def ProcessSdkDepends(env, filename):
return depends
-def prebuild_sdk_emitter(target, source, env):
+def api_amalgam_emitter(target, source, env):
target.append(env.ChangeFileExtension(target[0], ".d"))
target.append(env.ChangeFileExtension(target[0], ".i.c"))
return target, source
-def prebuild_sdk_create_origin_file(target, source, env):
+def api_amalgam_gen_origin_header(target, source, env):
mega_file = env.subst("${TARGET}.c", target=target[0])
with open(mega_file, "wt") as sdk_c:
sdk_c.write(
@@ -87,6 +87,7 @@ class SdkMeta:
class SdkTreeBuilder:
SDK_DIR_SUBST = "SDK_ROOT_DIR"
SDK_APP_EP_SUBST = "SDK_APP_EP_SUBST"
+ HEADER_EXTENSIONS = [".h", ".hpp"]
def __init__(self, env, target, source) -> None:
self.env = env
@@ -111,7 +112,10 @@ class SdkTreeBuilder:
lines = LogicalLines(deps_f).readlines()
_, depends = lines[0].split(":", 1)
self.header_depends = list(
- filter(lambda fname: fname.endswith(".h"), depends.split()),
+ filter(
+ lambda fname: any(map(fname.endswith, self.HEADER_EXTENSIONS)),
+ depends.split(),
+ ),
)
self.header_depends.append(self.sdk_env.subst("${LINKER_SCRIPT_PATH}"))
self.header_depends.append(self.sdk_env.subst("${SDK_DEFINITION}"))
@@ -180,12 +184,12 @@ class SdkTreeBuilder:
self._generate_sdk_meta()
-def deploy_sdk_tree_action(target, source, env):
+def deploy_sdk_header_tree_action(target, source, env):
sdk_tree = SdkTreeBuilder(env, target, source)
return sdk_tree.deploy_action()
-def deploy_sdk_tree_emitter(target, source, env):
+def deploy_sdk_header_tree_emitter(target, source, env):
sdk_tree = SdkTreeBuilder(env, target, source)
return sdk_tree.emitter(target, source, env)
@@ -224,7 +228,7 @@ def _check_sdk_is_up2date(sdk_cache: SdkCache):
)
-def validate_sdk_cache(source, target, env):
+def validate_api_cache(source, target, env):
# print(f"Generating SDK for {source[0]} to {target[0]}")
current_sdk = SdkCollector()
current_sdk.process_source_file_for_sdk(source[0].path)
@@ -237,7 +241,7 @@ def validate_sdk_cache(source, target, env):
_check_sdk_is_up2date(sdk_cache)
-def generate_sdk_symbols(source, target, env):
+def generate_api_table(source, target, env):
sdk_cache = SdkCache(source[0].path)
_check_sdk_is_up2date(sdk_cache)
@@ -249,11 +253,11 @@ def generate_sdk_symbols(source, target, env):
def generate(env, **kw):
if not env["VERBOSE"]:
env.SetDefault(
- SDK_PREGEN_COMSTR="\tPREGEN\t${TARGET}",
- SDK_COMSTR="\tSDKSRC\t${TARGET}",
+ SDK_AMALGAMATE_HEADER_COMSTR="\tAPIPREP\t${TARGET}",
+ SDK_AMALGAMATE_PP_COMSTR="\tAPIPP\t${TARGET}",
SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}",
- SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}",
- SDKDEPLOY_COMSTR="\tSDKTREE\t${TARGET}",
+ APITABLE_GENERATOR_COMSTR="\tAPITBL\t${TARGET}",
+ SDKTREE_COMSTR="\tSDKTREE\t${TARGET}",
)
# Filtering out things cxxheaderparser cannot handle
@@ -274,40 +278,40 @@ def generate(env, **kw):
env.AddMethod(ProcessSdkDepends)
env.Append(
BUILDERS={
- "SDKPrebuilder": Builder(
- emitter=prebuild_sdk_emitter,
+ "ApiAmalgamator": Builder(
+ emitter=api_amalgam_emitter,
action=[
Action(
- prebuild_sdk_create_origin_file,
- "$SDK_PREGEN_COMSTR",
+ api_amalgam_gen_origin_header,
+ "$SDK_AMALGAMATE_HEADER_COMSTR",
),
Action(
"$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c",
- "$SDK_COMSTR",
+ "$SDK_AMALGAMATE_PP_COMSTR",
),
],
suffix=".i",
),
- "SDKTree": Builder(
+ "SDKHeaderTreeExtractor": Builder(
action=Action(
- deploy_sdk_tree_action,
- "$SDKDEPLOY_COMSTR",
+ deploy_sdk_header_tree_action,
+ "$SDKTREE_COMSTR",
),
- emitter=deploy_sdk_tree_emitter,
+ emitter=deploy_sdk_header_tree_emitter,
src_suffix=".d",
),
- "SDKSymUpdater": Builder(
+ "ApiTableValidator": Builder(
action=Action(
- validate_sdk_cache,
+ validate_api_cache,
"$SDKSYM_UPDATER_COMSTR",
),
suffix=".csv",
src_suffix=".i",
),
- "SDKSymGenerator": Builder(
+ "ApiSymbolTable": Builder(
action=Action(
- generate_sdk_symbols,
- "$SDKSYM_GENERATOR_COMSTR",
+ generate_api_table,
+ "$APITABLE_GENERATOR_COMSTR",
),
suffix=".h",
src_suffix=".csv",
diff --git a/scripts/fbt_tools/fbt_tweaks.py b/scripts/fbt_tools/fbt_tweaks.py
index a903d4033..700f66d23 100644
--- a/scripts/fbt_tools/fbt_tweaks.py
+++ b/scripts/fbt_tools/fbt_tweaks.py
@@ -1,4 +1,6 @@
import SCons.Warnings as Warnings
+from SCons.Errors import UserError
+
# from SCons.Script.Main import find_deepest_user_frame
@@ -36,6 +38,11 @@ def fbt_warning(e):
def generate(env):
+ if env.get("UFBT_WORK_DIR"):
+ raise UserError(
+ "You're trying to use a new format SDK on a legacy ufbt version. "
+ "Please update ufbt to a version from PyPI: https://pypi.org/project/ufbt/"
+ )
Warnings._warningOut = fbt_warning
diff --git a/scripts/flipper/storage.py b/scripts/flipper/storage.py
index 47e11236d..7b56ee0d0 100644
--- a/scripts/flipper/storage.py
+++ b/scripts/flipper/storage.py
@@ -56,11 +56,11 @@ class StorageErrorCode(enum.Enum):
class FlipperStorageException(Exception):
- def __init__(self, message):
- super().__init__(f"Storage error: {message}")
-
- def __init__(self, path: str, error_code: StorageErrorCode):
- super().__init__(f"Storage error: path '{path}': {error_code.value}")
+ @staticmethod
+ def from_error_code(path: str, error_code: StorageErrorCode):
+ return FlipperStorageException(
+ f"Storage error: path '{path}': {error_code.value}"
+ )
class BufferedRead:
@@ -247,7 +247,9 @@ class FlipperStorage:
if self.has_error(answer):
last_error = self.get_error(answer)
self.read.until(self.CLI_PROMPT)
- raise FlipperStorageException(filename_to, last_error)
+ raise FlipperStorageException.from_error_code(
+ filename_to, last_error
+ )
self.port.write(filedata)
self.read.until(self.CLI_PROMPT)
@@ -319,7 +321,7 @@ class FlipperStorage:
StorageErrorCode.INVALID_NAME,
):
return False
- raise FlipperStorageException(path, error_code)
+ raise FlipperStorageException.from_error_code(path, error_code)
return True
@@ -333,7 +335,7 @@ class FlipperStorage:
def _check_no_error(self, response, path=None):
if self.has_error(response):
- raise FlipperStorageException(self.get_error(response))
+ raise FlipperStorageException.from_error_code(self.get_error(response))
def size(self, path: str):
"""file size on Flipper"""
diff --git a/scripts/get_env.py b/scripts/get_env.py
index e2da6eda5..92f9243c2 100644
--- a/scripts/get_env.py
+++ b/scripts/get_env.py
@@ -31,9 +31,10 @@ def parse_args():
def get_commit_json(event):
context = ssl._create_unverified_context()
- with urllib.request.urlopen(
- event["pull_request"]["_links"]["commits"]["href"], context=context
- ) as commit_file:
+ commit_url = event["pull_request"]["base"]["repo"]["commits_url"].replace(
+ "{/sha}", f"/{event['pull_request']['head']['sha']}"
+ )
+ with urllib.request.urlopen(commit_url, context=context) as commit_file:
commit_json = json.loads(commit_file.read().decode("utf-8"))
return commit_json
@@ -43,8 +44,8 @@ def get_details(event, args):
current_time = datetime.datetime.utcnow().date()
if args.type == "pull":
commit_json = get_commit_json(event)
- data["commit_comment"] = shlex.quote(commit_json[-1]["commit"]["message"])
- data["commit_hash"] = commit_json[-1]["sha"]
+ data["commit_comment"] = shlex.quote(commit_json["commit"]["message"])
+ data["commit_hash"] = commit_json["sha"]
ref = event["pull_request"]["head"]["ref"]
data["pull_id"] = event["pull_request"]["number"]
data["pull_name"] = shlex.quote(event["pull_request"]["title"])
diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py
index ce261954c..01e9794f4 100644
--- a/scripts/sconsdist.py
+++ b/scripts/sconsdist.py
@@ -1,13 +1,15 @@
#!/usr/bin/env python3
-from flipper.app import App
-from os.path import join, exists, relpath
-from os import makedirs, walk, environ
-from update import Main as UpdateMain
+import json
import shutil
-import zipfile
import tarfile
+import zipfile
+from os import makedirs, walk, environ
+from os.path import exists, join, relpath, basename, split
+
from ansi.color import fg
+from flipper.app import App
+from update import Main as UpdateMain
class ProjectDir:
@@ -54,12 +56,19 @@ class Main(App):
if project_name == "firmware" and filetype != "elf":
project_name = "full"
- return self.get_dist_file_name(project_name, filetype)
+ dist_target_path = self.get_dist_file_name(project_name, filetype)
+ self.note_dist_component(
+ project_name, filetype, self.get_dist_path(dist_target_path)
+ )
+ return dist_target_path
+
+ def note_dist_component(self, component: str, extension: str, srcpath: str) -> None:
+ self._dist_components[f"{component}.{extension}"] = srcpath
def get_dist_file_name(self, dist_artifact_type: str, filetype: str) -> str:
return f"{self.DIST_FILE_PREFIX}{self.target}-{dist_artifact_type}-{self.args.suffix}.{filetype}"
- def get_dist_file_path(self, filename: str) -> str:
+ def get_dist_path(self, filename: str) -> str:
return join(self.output_dir_path, filename)
def copy_single_project(self, project: ProjectDir) -> None:
@@ -69,32 +78,26 @@ class Main(App):
if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")):
shutil.copyfile(
src_file,
- self.get_dist_file_path(
- self.get_project_file_name(project, filetype)
- ),
+ self.get_dist_path(self.get_project_file_name(project, filetype)),
)
- for foldertype in ("sdk", "lib"):
+ for foldertype in ("sdk_headers", "lib"):
if exists(sdk_folder := join(obj_directory, foldertype)):
- self.package_zip(foldertype, sdk_folder)
+ self.note_dist_component(foldertype, "dir", sdk_folder)
- def package_zip(self, foldertype, sdk_folder):
+ # TODO: remove this after everyone migrates to new uFBT
+ self.create_zip_stub("lib")
+
+ def create_zip_stub(self, foldertype):
with zipfile.ZipFile(
- self.get_dist_file_path(self.get_dist_file_name(foldertype, "zip")),
+ self.get_dist_path(self.get_dist_file_name(foldertype, "zip")),
"w",
zipfile.ZIP_DEFLATED,
- ) as zf:
- for root, _, files in walk(sdk_folder):
- for file in files:
- zf.write(
- join(root, file),
- relpath(
- join(root, file),
- sdk_folder,
- ),
- )
+ ) as _:
+ pass
def copy(self) -> int:
- self.projects = dict(
+ self._dist_components: dict[str, str] = dict()
+ self.projects: dict[str, ProjectDir] = dict(
map(
lambda pd: (pd.project, pd),
map(ProjectDir, self.args.project),
@@ -122,12 +125,18 @@ class Main(App):
try:
shutil.rmtree(self.output_dir_path)
except Exception as ex:
- pass
+ self.logger.warn(f"Failed to clean output directory: {ex}")
if not exists(self.output_dir_path):
+ self.logger.debug(f"Creating output directory {self.output_dir_path}")
makedirs(self.output_dir_path)
+ for folder in ("debug", "scripts"):
+ if exists(folder):
+ self.note_dist_component(folder, "dir", folder)
+
for project in self.projects.values():
+ self.logger.debug(f"Copying {project.project} for {project.target}")
self.copy_single_project(project)
self.logger.info(
@@ -137,69 +146,144 @@ class Main(App):
)
if self.args.version:
- bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[
- : self.DIST_FOLDER_MAX_NAME_LENGTH
- ]
- bundle_dir = join(self.output_dir_path, bundle_dir_name)
- bundle_args = [
- "generate",
- "-d",
- bundle_dir,
- "-v",
- self.args.version,
- "-t",
- self.target,
- "--dfu",
- self.get_dist_file_path(
- self.get_project_file_name(self.projects["firmware"], "dfu")
- ),
- "--stage",
- self.get_dist_file_path(
- self.get_project_file_name(self.projects["updater"], "bin")
- ),
- ]
- if self.args.resources:
- bundle_args.extend(
- (
- "-r",
- self.args.resources,
- )
- )
- bundle_args.extend(self.other_args)
- log_custom_fz_name = environ.get("CUSTOM_FLIPPER_NAME", None) or ""
- if (
- (log_custom_fz_name != "")
- and (len(log_custom_fz_name) <= 8)
- and (log_custom_fz_name.isalnum())
- and (log_custom_fz_name.isascii())
- ):
- self.logger.info(
- f"Flipper Custom Name is set:\n\tName: {log_custom_fz_name} : length - {len(log_custom_fz_name)} chars"
- )
+ if bundle_result := self.bundle_update_package():
+ return bundle_result
- if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0:
- self.logger.info(
- fg.boldgreen(
- f"Use this directory to self-update your Flipper:\n\t{bundle_dir}"
- )
- )
-
- # Create tgz archive
- with tarfile.open(
- join(
- self.output_dir_path,
- f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz",
- ),
- "w:gz",
- compresslevel=9,
- format=tarfile.USTAR_FORMAT,
- ) as tar:
- tar.add(bundle_dir, arcname=bundle_dir_name)
-
- return bundle_result
+ required_components = ("firmware.elf", "full.bin", "update.dir")
+ if all(
+ map(
+ lambda c: c in self._dist_components,
+ required_components,
+ )
+ ):
+ self.bundle_sdk()
return 0
+ def bundle_sdk(self):
+ self.logger.info("Bundling SDK")
+ components_paths = dict()
+
+ sdk_components_keys = (
+ "full.bin",
+ "firmware.elf",
+ "update.dir",
+ "sdk_headers.dir",
+ "lib.dir",
+ "debug.dir",
+ "scripts.dir",
+ )
+
+ with zipfile.ZipFile(
+ self.get_dist_path(self.get_dist_file_name("sdk", "zip")),
+ "w",
+ zipfile.ZIP_DEFLATED,
+ ) as zf:
+ for component_key in sdk_components_keys:
+ component_path = self._dist_components.get(component_key)
+ components_paths[component_key] = basename(component_path)
+
+ if component_key.endswith(".dir"):
+ for root, dirnames, files in walk(component_path):
+ if "__pycache__" in dirnames:
+ dirnames.remove("__pycache__")
+ for file in files:
+ zf.write(
+ join(root, file),
+ join(
+ components_paths[component_key],
+ relpath(
+ join(root, file),
+ component_path,
+ ),
+ ),
+ )
+ else:
+ zf.write(component_path, basename(component_path))
+
+ zf.writestr(
+ "components.json",
+ json.dumps(
+ {
+ "meta": {
+ "hw_target": self.target,
+ "flavor": self.flavor,
+ "version": self.args.version,
+ },
+ "components": components_paths,
+ }
+ ),
+ )
+
+ def bundle_update_package(self):
+ self.logger.debug(
+ f"Generating update bundle with version {self.args.version} for {self.target}"
+ )
+ bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[
+ : self.DIST_FOLDER_MAX_NAME_LENGTH
+ ]
+ bundle_dir = self.get_dist_path(bundle_dir_name)
+ bundle_args = [
+ "generate",
+ "-d",
+ bundle_dir,
+ "-v",
+ self.args.version,
+ "-t",
+ self.target,
+ "--dfu",
+ self.get_dist_path(
+ self.get_project_file_name(self.projects["firmware"], "dfu")
+ ),
+ "--stage",
+ self.get_dist_path(
+ self.get_project_file_name(self.projects["updater"], "bin")
+ ),
+ ]
+ if self.args.resources:
+ bundle_args.extend(
+ (
+ "-r",
+ self.args.resources,
+ )
+ )
+ bundle_args.extend(self.other_args)
+
+ log_custom_fz_name = environ.get("CUSTOM_FLIPPER_NAME", None) or ""
+ if (
+ (log_custom_fz_name != "")
+ and (len(log_custom_fz_name) <= 8)
+ and (log_custom_fz_name.isalnum())
+ and (log_custom_fz_name.isascii())
+ ):
+ self.logger.info(
+ f"Flipper Custom Name is set:\n\tName: {log_custom_fz_name} : length - {len(log_custom_fz_name)} chars"
+ )
+
+ if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0:
+ self.note_dist_component("update", "dir", bundle_dir)
+ self.logger.info(
+ fg.boldgreen(
+ f"Use this directory to self-update your Flipper:\n\t{bundle_dir}"
+ )
+ )
+
+ # Create tgz archive
+ with tarfile.open(
+ join(
+ self.output_dir_path,
+ bundle_tgz := f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz",
+ ),
+ "w:gz",
+ compresslevel=9,
+ format=tarfile.USTAR_FORMAT,
+ ) as tar:
+ self.note_dist_component(
+ "update", "tgz", self.get_dist_path(bundle_tgz)
+ )
+ tar.add(bundle_dir, arcname=bundle_dir_name)
+ return bundle_result
+
if __name__ == "__main__":
Main()()
diff --git a/scripts/serial_cli.py b/scripts/serial_cli.py
index 441bc7cc8..390b1f263 100644
--- a/scripts/serial_cli.py
+++ b/scripts/serial_cli.py
@@ -8,7 +8,7 @@ import sys
def main():
logger = logging.getLogger()
if not (port := resolve_port(logger, "auto")):
- logger.error("Is Flipper connected over USB and isn't in DFU mode?")
+ logger.error("Is Flipper connected over USB and is it not in DFU mode?")
return 1
subprocess.call(
[
diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd
index 8587f6d0e..9d45b7e9d 100644
--- a/scripts/toolchain/fbtenv.cmd
+++ b/scripts/toolchain/fbtenv.cmd
@@ -15,10 +15,12 @@ if not ["%FBT_NOENV%"] == [""] (
set "FLIPPER_TOOLCHAIN_VERSION=21"
-if ["%FBT_TOOLCHAIN_ROOT%"] == [""] (
- set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows"
+if ["%FBT_TOOLCHAIN_PATH%"] == [""] (
+ set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%"
)
+set "FBT_TOOLCHAIN_ROOT=%FBT_TOOLCHAIN_PATH%\toolchain\x86_64-windows"
+
set "FBT_TOOLCHAIN_VERSION_FILE=%FBT_TOOLCHAIN_ROOT%\VERSION"
if not exist "%FBT_TOOLCHAIN_ROOT%" (
diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh
index cefa55d7e..be4e405c4 100755
--- a/scripts/toolchain/fbtenv.sh
+++ b/scripts/toolchain/fbtenv.sh
@@ -4,9 +4,15 @@
# public variables
DEFAULT_SCRIPT_PATH="$(pwd -P)";
-SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}";
FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"21"}";
-FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}";
+
+if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then
+ FBT_TOOLCHAIN_PATH_WAS_SET=0;
+else
+ FBT_TOOLCHAIN_PATH_WAS_SET=1;
+fi
+
+FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$DEFAULT_SCRIPT_PATH}";
FBT_VERBOSE="${FBT_VERBOSE:-""}";
fbtenv_show_usage()
@@ -60,7 +66,6 @@ fbtenv_restore_env()
unset SAVED_PYTHONPATH;
unset SAVED_PYTHONHOME;
- unset SCRIPT_PATH;
unset FBT_TOOLCHAIN_VERSION;
unset FBT_TOOLCHAIN_PATH;
}
@@ -104,13 +109,14 @@ fbtenv_set_shell_prompt()
return 0; # all other shells
}
-fbtenv_check_script_path()
+fbtenv_check_env_vars()
{
- if [ ! -x "$SCRIPT_PATH/fbt" ] && [ ! -x "$SCRIPT_PATH/ufbt" ] ; then
- echo "Please source this script from [u]fbt root directory, or specify 'SCRIPT_PATH' variable manually";
+ # Return error if FBT_TOOLCHAIN_PATH is not set before script is sourced or if fbt executable is not in DEFAULT_SCRIPT_PATH
+ if [ "$FBT_TOOLCHAIN_PATH_WAS_SET" -eq 0 ] && [ ! -x "$DEFAULT_SCRIPT_PATH/fbt" ] && [ ! -x "$DEFAULT_SCRIPT_PATH/ufbt" ] ; then
+ echo "Please source this script from [u]fbt root directory, or specify 'FBT_TOOLCHAIN_PATH' variable manually";
echo "Example:";
- printf "\tSCRIPT_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n";
- echo "If current directory is right, type 'unset SCRIPT_PATH' and try again"
+ printf "\tFBT_TOOLCHAIN_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n";
+ echo "If current directory is right, type 'unset FBT_TOOLCHAIN_PATH' and try again"
return 1;
fi
return 0;
@@ -217,7 +223,7 @@ fbtenv_show_unpack_percentage()
fbtenv_unpack_toolchain()
{
- echo "Unpacking toolchain:";
+ echo "Unpacking toolchain to '$FBT_TOOLCHAIN_PATH/toolchain':";
tar -xvf "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" -C "$FBT_TOOLCHAIN_PATH/toolchain" 2>&1 | fbtenv_show_unpack_percentage;
mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1;
mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1;
@@ -225,7 +231,7 @@ fbtenv_unpack_toolchain()
return 0;
}
-fbtenv_clearing()
+fbtenv_cleanup()
{
printf "Cleaning up..";
if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then
@@ -280,14 +286,14 @@ fbtenv_download_toolchain()
fbtenv_check_tar || return 1;
TOOLCHAIN_TAR="$(basename "$TOOLCHAIN_URL")";
TOOLCHAIN_DIR="$(echo "$TOOLCHAIN_TAR" | sed "s/-$FBT_TOOLCHAIN_VERSION.tar.gz//g")";
- trap fbtenv_clearing 2; # trap will be restored in fbtenv_clearing
+ trap fbtenv_cleanup 2; # trap will be restored in fbtenv_cleanup
if ! fbtenv_check_downloaded_toolchain; then
fbtenv_curl_wget_check || return 1;
fbtenv_download_toolchain_tar || return 1;
fi
fbtenv_remove_old_tooclhain;
fbtenv_unpack_toolchain || return 1;
- fbtenv_clearing;
+ fbtenv_cleanup;
return 0;
}
@@ -306,8 +312,8 @@ fbtenv_main()
fbtenv_restore_env;
return 0;
fi
- fbtenv_check_if_sourced_multiple_times; # many source it's just a warning
- fbtenv_check_script_path || return 1;
+ fbtenv_check_if_sourced_multiple_times;
+ fbtenv_check_env_vars || return 1;
fbtenv_check_download_toolchain || return 1;
fbtenv_set_shell_prompt;
fbtenv_print_version;
diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct
new file mode 100644
index 000000000..a82189c14
--- /dev/null
+++ b/scripts/ufbt/SConstruct
@@ -0,0 +1,393 @@
+from SCons.Platform import TempFileMunge
+from SCons.Node import FS
+from SCons.Errors import UserError
+
+import os
+import multiprocessing
+import pathlib
+
+SetOption("num_jobs", multiprocessing.cpu_count())
+SetOption("max_drift", 1)
+# SetOption("silent", False)
+
+ufbt_state_dir = Dir(os.environ.get("UFBT_STATE_DIR", "#.ufbt"))
+ufbt_script_dir = Dir(os.environ.get("UFBT_SCRIPT_DIR"))
+
+ufbt_current_sdk_dir = ufbt_state_dir.Dir("current")
+
+SConsignFile(ufbt_state_dir.File(".sconsign.dblite").abspath)
+
+ufbt_variables = SConscript("commandline.scons")
+
+forward_os_env = {
+ # Import PATH from OS env - scons doesn't do that by default
+ "PATH": os.environ["PATH"],
+}
+
+# Proxying environment to child processes & scripts
+variables_to_forward = [
+ # CI/CD variables
+ "WORKFLOW_BRANCH_OR_TAG",
+ "DIST_SUFFIX",
+ # Python & other tools
+ "HOME",
+ "APPDATA",
+ "PYTHONHOME",
+ "PYTHONNOUSERSITE",
+ "TMP",
+ "TEMP",
+ # Colors for tools
+ "TERM",
+]
+
+if proxy_env := GetOption("proxy_env"):
+ variables_to_forward.extend(proxy_env.split(","))
+
+for env_value_name in variables_to_forward:
+ if environ_value := os.environ.get(env_value_name, None):
+ forward_os_env[env_value_name] = environ_value
+
+# Core environment init - loads SDK state, sets up paths, etc.
+core_env = Environment(
+ variables=ufbt_variables,
+ ENV=forward_os_env,
+ UFBT_STATE_DIR=ufbt_state_dir,
+ UFBT_CURRENT_SDK_DIR=ufbt_current_sdk_dir,
+ UFBT_SCRIPT_DIR=ufbt_script_dir,
+ toolpath=[ufbt_current_sdk_dir.Dir("scripts/ufbt/site_tools")],
+ tools=[
+ "ufbt_state",
+ ("ufbt_help", {"vars": ufbt_variables}),
+ ],
+)
+
+if "update" in BUILD_TARGETS:
+ SConscript(
+ "update.scons",
+ exports={"core_env": core_env},
+ )
+
+if "purge" in BUILD_TARGETS:
+ core_env.Execute(Delete(ufbt_state_dir))
+ print("uFBT state purged")
+ Exit(0)
+
+# Now we can import stuff bundled with SDK - it was added to sys.path by ufbt_state
+
+from fbt.util import (
+ tempfile_arg_esc_func,
+ single_quote,
+ extract_abs_dir,
+ extract_abs_dir_path,
+ wrap_tempfile,
+ path_as_posix,
+)
+from fbt.appmanifest import FlipperAppType
+from fbt.sdk.cache import SdkCache
+
+# Base environment with all tools loaded from SDK
+env = core_env.Clone(
+ toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")],
+ tools=[
+ "fbt_tweaks",
+ (
+ "crosscc",
+ {
+ "toolchain_prefix": "arm-none-eabi-",
+ "versions": (" 10.3",),
+ },
+ ),
+ "fwbin",
+ "python3",
+ "sconsrecursiveglob",
+ "sconsmodular",
+ "ccache",
+ "fbt_apps",
+ "fbt_extapps",
+ "fbt_assets",
+ ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}),
+ ],
+ FBT_FAP_DEBUG_ELF_ROOT=ufbt_state_dir.Dir("build"),
+ TEMPFILE=TempFileMunge,
+ MAXLINELENGTH=2048,
+ PROGSUFFIX=".elf",
+ TEMPFILEARGESCFUNC=tempfile_arg_esc_func,
+ SINGLEQUOTEFUNC=single_quote,
+ ABSPATHGETTERFUNC=extract_abs_dir_path,
+ APPS=[],
+ UFBT_API_VERSION=SdkCache(
+ core_env.subst("$SDK_DEFINITION"), load_version_only=True
+ ).version,
+ APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}\n\t\tTarget: ${TARGET_HW}, API: ${UFBT_API_VERSION}",
+)
+
+wrap_tempfile(env, "LINKCOM")
+wrap_tempfile(env, "ARCOM")
+
+# print(env.Dump())
+
+# Dist env
+
+dist_env = env.Clone(
+ tools=[
+ "fbt_dist",
+ "fbt_debugopts",
+ "openocd",
+ "blackmagic",
+ "jflash",
+ "textfile",
+ ],
+ ENV=os.environ,
+ OPENOCD_OPTS=[
+ "-f",
+ "interface/stlink.cfg",
+ "-c",
+ "transport select hla_swd",
+ "-f",
+ "${FBT_DEBUG_DIR}/stm32wbx.cfg",
+ "-c",
+ "stm32wbx.cpu configure -rtos auto",
+ ],
+)
+
+openocd_target = dist_env.OpenOCDFlash(
+ dist_env["UFBT_STATE_DIR"].File("flash"),
+ dist_env["FW_BIN"],
+ OPENOCD_COMMAND=[
+ "-c",
+ "program ${SOURCE.posix} reset exit 0x08000000",
+ ],
+)
+dist_env.Alias("firmware_flash", openocd_target)
+dist_env.Alias("flash", openocd_target)
+if env["FORCE"]:
+ env.AlwaysBuild(openocd_target)
+
+firmware_debug = dist_env.PhonyTarget(
+ "debug",
+ "${GDBPYCOM}",
+ source=dist_env["FW_ELF"],
+ GDBOPTS="${GDBOPTS_BASE}",
+ GDBREMOTE="${OPENOCD_GDB_PIPE}",
+ FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")),
+)
+
+dist_env.PhonyTarget(
+ "blackmagic",
+ "${GDBPYCOM}",
+ source=dist_env["FW_ELF"],
+ GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
+ GDBREMOTE="${BLACKMAGIC_ADDR}",
+ FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")),
+)
+
+dist_env.PhonyTarget(
+ "flash_blackmagic",
+ "$GDB $GDBOPTS $SOURCES $GDBFLASH",
+ source=dist_env["FW_ELF"],
+ GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
+ GDBREMOTE="${BLACKMAGIC_ADDR}",
+ GDBFLASH=[
+ "-ex",
+ "load",
+ "-ex",
+ "quit",
+ ],
+)
+
+flash_usb_full = dist_env.UsbInstall(
+ dist_env["UFBT_STATE_DIR"].File("usbinstall"),
+ [],
+)
+dist_env.AlwaysBuild(flash_usb_full)
+dist_env.Alias("flash_usb", flash_usb_full)
+dist_env.Alias("flash_usb_full", flash_usb_full)
+
+# App build environment
+
+appenv = env.Clone(
+ CCCOM=env["CCCOM"].replace("$CFLAGS", "$CFLAGS_APP $CFLAGS"),
+ CXXCOM=env["CXXCOM"].replace("$CXXFLAGS", "$CXXFLAGS_APP $CXXFLAGS"),
+ LINKCOM=env["LINKCOM"].replace("$LINKFLAGS", "$LINKFLAGS_APP $LINKFLAGS"),
+ COMPILATIONDB_USE_ABSPATH=True,
+)
+
+
+original_app_dir = Dir(appenv.subst("$UFBT_APP_DIR"))
+app_mount_point = Dir("#/app/")
+app_mount_point.addRepository(original_app_dir)
+
+appenv.LoadAppManifest(app_mount_point)
+appenv.PrepareApplicationsBuild()
+
+#######################
+
+apps_artifacts = appenv["EXT_APPS"]
+
+apps_to_build_as_faps = [
+ FlipperAppType.PLUGIN,
+ FlipperAppType.EXTERNAL,
+]
+
+known_extapps = [
+ app
+ for apptype in apps_to_build_as_faps
+ for app in appenv["APPBUILD"].get_apps_of_type(apptype, True)
+]
+for app in known_extapps:
+ app_artifacts = appenv.BuildAppElf(app)
+ app_src_dir = extract_abs_dir(app_artifacts.app._appdir)
+ app_artifacts.installer = [
+ appenv.Install(app_src_dir.Dir("dist"), app_artifacts.compact),
+ appenv.Install(app_src_dir.Dir("dist").Dir("debug"), app_artifacts.debug),
+ ]
+
+if appenv["FORCE"]:
+ appenv.AlwaysBuild([extapp.compact for extapp in apps_artifacts.values()])
+
+# Final steps - target aliases
+
+install_and_check = [
+ (extapp.installer, extapp.validator) for extapp in apps_artifacts.values()
+]
+Alias(
+ "faps",
+ install_and_check,
+)
+Default(install_and_check)
+
+# Compilation database
+
+fwcdb = appenv.CompilationDatabase(
+ original_app_dir.Dir(".vscode").File("compile_commands.json")
+)
+
+AlwaysBuild(fwcdb)
+Precious(fwcdb)
+NoClean(fwcdb)
+if len(apps_artifacts):
+ Default(fwcdb)
+
+
+# launch handler
+runnable_apps = appenv["APPBUILD"].get_apps_of_type(FlipperAppType.EXTERNAL, True)
+
+app_to_launch = None
+if len(runnable_apps) == 1:
+ app_to_launch = runnable_apps[0].appid
+elif len(runnable_apps) > 1:
+ # more than 1 app - try to find one with matching id
+ app_to_launch = appenv.subst("$APPID")
+
+
+def ambiguous_app_call(**kw):
+ raise UserError(
+ f"More than one app is runnable: {', '.join(app.appid for app in runnable_apps)}. Please specify an app with APPID=..."
+ )
+
+
+if app_to_launch:
+ appenv.AddAppLaunchTarget(app_to_launch, "launch")
+else:
+ dist_env.PhonyTarget("launch", Action(ambiguous_app_call, None))
+
+# cli handler
+
+appenv.PhonyTarget(
+ "cli",
+ '${PYTHON3} "${FBT_SCRIPT_DIR}/serial_cli.py"',
+)
+
+# Linter
+
+dist_env.PhonyTarget(
+ "lint",
+ "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}",
+ source=original_app_dir.File(".clang-format"),
+ LINT_SOURCES=[original_app_dir],
+)
+
+dist_env.PhonyTarget(
+ "format",
+ "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py format ${LINT_SOURCES}",
+ source=original_app_dir.File(".clang-format"),
+ LINT_SOURCES=[original_app_dir],
+)
+
+
+# Prepare vscode environment
+def _path_as_posix(path):
+ return pathlib.Path(path).as_posix()
+
+
+vscode_dist = []
+project_template_dir = dist_env["UFBT_SCRIPT_ROOT"].Dir("project_template")
+for template_file in project_template_dir.Dir(".vscode").glob("*"):
+ vscode_dist.append(
+ dist_env.Substfile(
+ original_app_dir.Dir(".vscode").File(template_file.name),
+ template_file,
+ SUBST_DICT={
+ "@UFBT_VSCODE_PATH_SEP@": os.path.pathsep,
+ "@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@": pathlib.Path(
+ dist_env.WhereIs("arm-none-eabi-gcc")
+ ).parent.as_posix(),
+ "@UFBT_TOOLCHAIN_GCC@": _path_as_posix(
+ dist_env.WhereIs("arm-none-eabi-gcc")
+ ),
+ "@UFBT_TOOLCHAIN_GDB_PY@": _path_as_posix(
+ dist_env.WhereIs("arm-none-eabi-gdb-py")
+ ),
+ "@UFBT_TOOLCHAIN_OPENOCD@": _path_as_posix(dist_env.WhereIs("openocd")),
+ "@UFBT_APP_DIR@": _path_as_posix(original_app_dir.abspath),
+ "@UFBT_ROOT_DIR@": _path_as_posix(Dir("#").abspath),
+ "@UFBT_DEBUG_DIR@": dist_env["FBT_DEBUG_DIR"],
+ "@UFBT_DEBUG_ELF_DIR@": _path_as_posix(
+ dist_env["FBT_FAP_DEBUG_ELF_ROOT"].abspath
+ ),
+ "@UFBT_FIRMWARE_ELF@": _path_as_posix(dist_env["FW_ELF"].abspath),
+ },
+ )
+ )
+
+for config_file in project_template_dir.glob(".*"):
+ if isinstance(config_file, FS.Dir):
+ continue
+ vscode_dist.append(dist_env.Install(original_app_dir, config_file))
+
+dist_env.Precious(vscode_dist)
+dist_env.NoClean(vscode_dist)
+dist_env.Alias("vscode_dist", vscode_dist)
+
+
+# Creating app from base template
+
+dist_env.SetDefault(FBT_APPID=appenv.subst("$APPID") or "template")
+app_template_dist = []
+for template_file in project_template_dir.Dir("app_template").glob("*"):
+ dist_file_name = dist_env.subst(template_file.name)
+ if template_file.name.endswith(".png"):
+ app_template_dist.append(
+ dist_env.InstallAs(original_app_dir.File(dist_file_name), template_file)
+ )
+ else:
+ app_template_dist.append(
+ dist_env.Substfile(
+ original_app_dir.File(dist_file_name),
+ template_file,
+ SUBST_DICT={
+ "@FBT_APPID@": dist_env.subst("$FBT_APPID"),
+ },
+ )
+ )
+
+AddPostAction(
+ app_template_dist[-1],
+ [
+ Mkdir(original_app_dir.Dir("images")),
+ Touch(original_app_dir.Dir("images").File(".gitkeep")),
+ ],
+)
+dist_env.Precious(app_template_dist)
+dist_env.NoClean(app_template_dist)
+dist_env.Alias("create", app_template_dist)
diff --git a/scripts/ufbt/commandline.scons b/scripts/ufbt/commandline.scons
new file mode 100644
index 000000000..9af5e9bce
--- /dev/null
+++ b/scripts/ufbt/commandline.scons
@@ -0,0 +1,90 @@
+AddOption(
+ "--proxy-env",
+ action="store",
+ dest="proxy_env",
+ default="",
+ help="Comma-separated list of additional environment variables to pass to child SCons processes",
+)
+
+AddOption(
+ "--channel",
+ action="store",
+ dest="sdk_channel",
+ choices=["dev", "rc", "release"],
+ default="",
+ help="Release channel to use for SDK",
+)
+
+AddOption(
+ "--branch",
+ action="store",
+ dest="sdk_branch",
+ help="Custom main repo branch to use for SDK",
+)
+
+AddOption(
+ "--hw-target",
+ action="store",
+ dest="sdk_target",
+ help="SDK Hardware target",
+)
+
+vars = Variables("ufbt_options.py", ARGUMENTS)
+
+vars.AddVariables(
+ BoolVariable(
+ "VERBOSE",
+ help="Print full commands",
+ default=False,
+ ),
+ BoolVariable(
+ "FORCE",
+ help="Force target action (for supported targets)",
+ default=False,
+ ),
+ # These 2 are inherited from SDK
+ # BoolVariable(
+ # "DEBUG",
+ # help="Enable debug build",
+ # default=True,
+ # ),
+ # BoolVariable(
+ # "COMPACT",
+ # help="Optimize for size",
+ # default=False,
+ # ),
+ PathVariable(
+ "OTHER_ELF",
+ help="Path to prebuilt ELF file to debug",
+ validator=PathVariable.PathAccept,
+ default="",
+ ),
+ (
+ "OPENOCD_OPTS",
+ "Options to pass to OpenOCD",
+ "",
+ ),
+ (
+ "BLACKMAGIC",
+ "Blackmagic probe location",
+ "auto",
+ ),
+ (
+ "OPENOCD_ADAPTER_SERIAL",
+ "OpenOCD adapter serial number",
+ "auto",
+ ),
+ (
+ "APPID",
+ "Application id",
+ "",
+ ),
+ PathVariable(
+ "UFBT_APP_DIR",
+ help="Application dir to work with",
+ validator=PathVariable.PathIsDir,
+ default="",
+ ),
+)
+
+Return("vars")
diff --git a/scripts/ufbt/project_template/.clang-format b/scripts/ufbt/project_template/.clang-format
new file mode 100644
index 000000000..4b76f7fa4
--- /dev/null
+++ b/scripts/ufbt/project_template/.clang-format
@@ -0,0 +1,191 @@
+---
+Language: Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: AlwaysBreak
+AlignArrayOfStructures: None
+AlignConsecutiveMacros: None
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: None
+AlignEscapedNewlines: Left
+AlignOperands: Align
+AlignTrailingComments: false
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+ - __capability
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: Never
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: true
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit: 99
+CommentPragmas: '^ IWYU pragma:'
+QualifierAlignment: Leave
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat: false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: BinPack
+BasedOnStyle: ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: false
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IfMacros:
+ - KJ_IF_MAYBE
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '.*'
+ Priority: 1
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '^(<|"(gtest|gmock|isl|json)/)'
+ Priority: 3
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '.*'
+ Priority: 1
+ SortPriority: 0
+ CaseSensitive: false
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: false
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentExternBlock: AfterExternBlock
+IndentRequires: false
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 10
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Left
+PPIndentWidth: -1
+ReferenceAlignment: Pointer
+ReflowComments: false
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes: Never
+SortJavaStaticImport: Before
+SortUsingDeclarations: false
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: Never
+SpaceBeforeParensOptions:
+ AfterControlStatements: false
+ AfterForeachMacros: false
+ AfterFunctionDefinitionName: false
+ AfterFunctionDeclarationName: false
+ AfterIfMacros: false
+ AfterOverloadedOperator: false
+ BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+ Minimum: 1
+ Maximum: -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard: c++03
+StatementAttributeLikeMacros:
+ - Q_EMIT
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 4
+UseCRLF: false
+UseTab: Never
+WhitespaceSensitiveMacros:
+ - STRINGIZE
+ - PP_STRINGIZE
+ - BOOST_PP_STRINGIZE
+ - NS_SWIFT_NAME
+ - CF_SWIFT_NAME
+...
+
diff --git a/scripts/ufbt/project_template/.editorconfig b/scripts/ufbt/project_template/.editorconfig
new file mode 100644
index 000000000..a31ef8e75
--- /dev/null
+++ b/scripts/ufbt/project_template/.editorconfig
@@ -0,0 +1,13 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+
+[*.{cpp,h,c,py,sh}]
+indent_style = space
+indent_size = 4
+
+[{Makefile,*.mk}]
+indent_size = tab
diff --git a/scripts/ufbt/project_template/.gitignore b/scripts/ufbt/project_template/.gitignore
new file mode 100644
index 000000000..e2a15a10a
--- /dev/null
+++ b/scripts/ufbt/project_template/.gitignore
@@ -0,0 +1,4 @@
+dist/*
+.vscode
+.clang-format
+.editorconfig
\ No newline at end of file
diff --git a/scripts/ufbt/project_template/.vscode/c_cpp_properties.json b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json
new file mode 100644
index 000000000..922a9091b
--- /dev/null
+++ b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json
@@ -0,0 +1,14 @@
+{
+ "configurations": [
+ {
+ "name": "main",
+ "compilerPath": "@UFBT_TOOLCHAIN_GCC@",
+ "intelliSenseMode": "gcc-arm",
+ "compileCommands": "${workspaceFolder}/.vscode/compile_commands.json",
+ "configurationProvider": "ms-vscode.cpptools",
+ "cStandard": "gnu17",
+ "cppStandard": "c++17"
+ },
+ ],
+ "version": 4
+}
\ No newline at end of file
diff --git a/scripts/ufbt/project_template/.vscode/extensions.json b/scripts/ufbt/project_template/.vscode/extensions.json
new file mode 100644
index 000000000..35f90700a
--- /dev/null
+++ b/scripts/ufbt/project_template/.vscode/extensions.json
@@ -0,0 +1,18 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+ // List of extensions which should be recommended for users of this workspace.
+ "recommendations": [
+ "ms-python.black-formatter",
+ "ms-vscode.cpptools",
+ "amiralizadeh9480.cpp-helper",
+ "marus25.cortex-debug",
+ "zxh404.vscode-proto3",
+ "augustocdias.tasks-shell-input"
+ ],
+ // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+ "unwantedRecommendations": [
+ "twxs.cmake",
+ "ms-vscode.cmake-tools"
+ ]
+}
\ No newline at end of file
diff --git a/scripts/ufbt/project_template/.vscode/launch.json b/scripts/ufbt/project_template/.vscode/launch.json
new file mode 100644
index 000000000..d9c98dcc1
--- /dev/null
+++ b/scripts/ufbt/project_template/.vscode/launch.json
@@ -0,0 +1,98 @@
+{
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "inputs": [
+ // {
+ // "id": "BLACKMAGIC",
+ // "type": "command",
+ // "command": "shellCommand.execute",
+ // "args": {
+ // "useSingleResult": true,
+ // "env": {
+ // "PATH": "${workspaceFolder};${env:PATH}"
+ // },
+ // "command": "./fbt get_blackmagic",
+ // "description": "Get Blackmagic device",
+ // }
+ // },
+ ],
+ "configurations": [
+ {
+ "name": "Attach FW (ST-Link)",
+ "cwd": "${workspaceFolder}",
+ "executable": "@UFBT_FIRMWARE_ELF@",
+ "request": "attach",
+ "type": "cortex-debug",
+ "servertype": "openocd",
+ "device": "stlink",
+ "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd",
+ "rtos": "FreeRTOS",
+ "configFiles": [
+ "interface/stlink.cfg",
+ "@UFBT_DEBUG_DIR@/stm32wbx.cfg"
+ ],
+ "postAttachCommands": [
+ "source @UFBT_DEBUG_DIR@/flipperapps.py",
+ "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@"
+ ],
+ // "showDevDebugOutput": "raw",
+ },
+ {
+ "name": "Attach FW (DAP)",
+ "cwd": "${workspaceFolder}",
+ "executable": "@UFBT_FIRMWARE_ELF@",
+ "request": "attach",
+ "type": "cortex-debug",
+ "servertype": "openocd",
+ "device": "cmsis-dap",
+ "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd",
+ "rtos": "FreeRTOS",
+ "configFiles": [
+ "interface/cmsis-dap.cfg",
+ "@UFBT_DEBUG_DIR@/stm32wbx.cfg"
+ ],
+ "postAttachCommands": [
+ "source @UFBT_DEBUG_DIR@/flipperapps.py",
+ "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@"
+ ],
+ // "showDevDebugOutput": "raw",
+ },
+ // {
+ // "name": "Attach FW (blackmagic)",
+ // "cwd": "${workspaceFolder}",
+ // "executable": "@UFBT_FIRMWARE_ELF@",
+ // "request": "attach",
+ // "type": "cortex-debug",
+ // "servertype": "external",
+ // "gdbTarget": "${input:BLACKMAGIC}",
+ // "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd",
+ // "rtos": "FreeRTOS",
+ // "postAttachCommands": [
+ // "monitor swdp_scan",
+ // "attach 1",
+ // "set confirm off",
+ // "set mem inaccessible-by-default off",
+ // "source @UFBT_DEBUG_DIR@/flipperapps.py",
+ // "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@"
+ // ]
+ // // "showDevDebugOutput": "raw",
+ // },
+ {
+ "name": "Attach FW (JLink)",
+ "cwd": "${workspaceFolder}",
+ "executable": "@UFBT_FIRMWARE_ELF@",
+ "request": "attach",
+ "type": "cortex-debug",
+ "servertype": "jlink",
+ "interface": "swd",
+ "device": "STM32WB55RG",
+ "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd",
+ "rtos": "FreeRTOS",
+ "postAttachCommands": [
+ "source @UFBT_DEBUG_DIR@/flipperapps.py",
+ "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@"
+ ]
+ // "showDevDebugOutput": "raw",
+ },
+ ]
+}
\ No newline at end of file
diff --git a/scripts/ufbt/project_template/.vscode/settings.json b/scripts/ufbt/project_template/.vscode/settings.json
new file mode 100644
index 000000000..33cd3f035
--- /dev/null
+++ b/scripts/ufbt/project_template/.vscode/settings.json
@@ -0,0 +1,20 @@
+{
+ "cortex-debug.enableTelemetry": false,
+ "cortex-debug.variableUseNaturalFormat": false,
+ "cortex-debug.showRTOS": true,
+ "cortex-debug.armToolchainPath": "@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@",
+ "cortex-debug.openocdPath": "@UFBT_TOOLCHAIN_OPENOCD@",
+ "cortex-debug.gdbPath": "@UFBT_TOOLCHAIN_GDB_PY@",
+ "editor.formatOnSave": true,
+ "files.associations": {
+ "*.scons": "python",
+ "SConscript": "python",
+ "SConstruct": "python",
+ "*.fam": "python"
+ },
+ "cortex-debug.registerUseNaturalFormat": false,
+ "python.analysis.typeCheckingMode": "off",
+ "[python]": {
+ "editor.defaultFormatter": "ms-python.black-formatter"
+ }
+}
\ No newline at end of file
diff --git a/scripts/ufbt/project_template/.vscode/tasks.json b/scripts/ufbt/project_template/.vscode/tasks.json
new file mode 100644
index 000000000..6343bba7b
--- /dev/null
+++ b/scripts/ufbt/project_template/.vscode/tasks.json
@@ -0,0 +1,54 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "2.0.0",
+ "options": {
+ "env": {
+ "PATH": "${workspaceFolder}@UFBT_VSCODE_PATH_SEP@${env:PATH}@UFBT_VSCODE_PATH_SEP@@UFBT_ROOT_DIR@"
+ }
+ },
+ "tasks": [
+ {
+ "label": "Launch App on Flipper",
+ "group": "build",
+ "type": "shell",
+ "command": "ufbt launch"
+ },
+ {
+ "label": "Build",
+ "group": "build",
+ "type": "shell",
+ "command": "ufbt"
+ },
+ {
+ "label": "Flash FW (ST-Link)",
+ "group": "build",
+ "type": "shell",
+ "command": "ufbt FORCE=1 flash"
+ },
+ // {
+ // "label": "[NOTIMPL] Flash FW (blackmagic)",
+ // "group": "build",
+ // "type": "shell",
+ // "command": "ufbt flash_blackmagic"
+ // },
+ // {
+ // "label": "[NOTIMPL] Flash FW (JLink)",
+ // "group": "build",
+ // "type": "shell",
+ // "command": "ufbt FORCE=1 jflash"
+ // },
+ {
+ "label": "Flash FW (USB, with resources)",
+ "group": "build",
+ "type": "shell",
+ "command": "ufbt FORCE=1 flash_usb"
+ },
+ {
+ "label": "Update uFBT SDK",
+ "group": "build",
+ "type": "shell",
+ "command": "ufbt update"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/scripts/ufbt/project_template/app_template/${FBT_APPID}.c b/scripts/ufbt/project_template/app_template/${FBT_APPID}.c
new file mode 100644
index 000000000..9b8113cb5
--- /dev/null
+++ b/scripts/ufbt/project_template/app_template/${FBT_APPID}.c
@@ -0,0 +1,12 @@
+#include
+
+/* generated by fbt from .png files in images folder */
+#include <@FBT_APPID@_icons.h>
+
+int32_t @FBT_APPID@_app(void* p) {
+ UNUSED(p);
+ FURI_LOG_I("TEST", "Hello world");
+ FURI_LOG_I("TEST", "I'm @FBT_APPID@!");
+
+ return 0;
+}
diff --git a/scripts/ufbt/project_template/app_template/${FBT_APPID}.png b/scripts/ufbt/project_template/app_template/${FBT_APPID}.png
new file mode 100644
index 000000000..59e6c185f
Binary files /dev/null and b/scripts/ufbt/project_template/app_template/${FBT_APPID}.png differ
diff --git a/scripts/ufbt/project_template/app_template/application.fam b/scripts/ufbt/project_template/app_template/application.fam
new file mode 100644
index 000000000..31fadb207
--- /dev/null
+++ b/scripts/ufbt/project_template/app_template/application.fam
@@ -0,0 +1,17 @@
+# For details & more options, see documentation/AppManifests.md in firmware repo
+
+App(
+ appid="@FBT_APPID@", # Must be unique
+ name="App @FBT_APPID@", # Displayed in menus
+ apptype=FlipperAppType.EXTERNAL,
+ entry_point="@FBT_APPID@_app",
+ stack_size=2 * 1024,
+ fap_category="Misc",
+ # Optional values
+ # fap_version=(0, 1), # (major, minor)
+ fap_icon="@FBT_APPID@.png", # 10x10 1-bit PNG
+ # fap_description="A simple app",
+ # fap_author="J. Doe",
+ # fap_weburl="https://github.com/user/@FBT_APPID@",
+ fap_icon_assets="images", # Image assets to compile for this application
+)
diff --git a/scripts/ufbt/site_init.py b/scripts/ufbt/site_init.py
new file mode 100644
index 000000000..557085ede
--- /dev/null
+++ b/scripts/ufbt/site_init.py
@@ -0,0 +1,36 @@
+from SCons.Script import GetBuildFailures
+import SCons.Errors
+
+import atexit
+from ansi.color import fg, fx
+
+
+def bf_to_str(bf):
+ """Convert an element of GetBuildFailures() to a string
+ in a useful way."""
+
+ if bf is None: # unknown targets product None in list
+ return "(unknown tgt)"
+ elif isinstance(bf, SCons.Errors.StopError):
+ return fg.yellow(str(bf))
+ elif bf.node:
+ return fg.yellow(str(bf.node)) + ": " + bf.errstr
+ elif bf.filename:
+ return fg.yellow(bf.filename) + ": " + bf.errstr
+ return fg.yellow("unknown failure: ") + bf.errstr
+
+
+def display_build_status():
+ """Display the build status. Called by atexit.
+ Here you could do all kinds of complicated things."""
+ bf = GetBuildFailures()
+ if bf:
+ # bf is normally a list of build failures; if an element is None,
+ # it's because of a target that scons doesn't know anything about.
+ failures_message = "\n".join([bf_to_str(x) for x in bf if x is not None])
+ print()
+ print(fg.brightred(fx.bold("*" * 10 + " FBT ERRORS " + "*" * 10)))
+ print(failures_message)
+
+
+atexit.register(display_build_status)
diff --git a/scripts/ufbt/site_tools/ufbt_help.py b/scripts/ufbt/site_tools/ufbt_help.py
new file mode 100644
index 000000000..da6ff6e51
--- /dev/null
+++ b/scripts/ufbt/site_tools/ufbt_help.py
@@ -0,0 +1,53 @@
+targets_help = """Configuration variables:
+"""
+
+tail_help = """
+
+TASKS:
+ (* - not supported yet)
+
+ launch:
+ Upload and start application over USB
+ vscode_dist:
+ Configure application in current directory for development in VSCode.
+ create:
+ Copy application template to current directory. Set APPID=myapp to create an app with id 'myapp'.
+
+Building:
+ faps:
+ Build all FAP apps
+ fap_{APPID}, launch APPSRC={APPID}:
+ Build FAP app with appid={APPID}; upload & start it over USB
+
+Flashing & debugging:
+ flash, flash_blackmagic, *jflash:
+ Flash firmware to target using debug probe
+ flash_usb, flash_usb_full:
+ Install firmware using self-update package
+ debug, debug_other, blackmagic:
+ Start GDB
+
+Other:
+ cli:
+ Open a Flipper CLI session over USB
+ lint:
+ run linter for C code
+ format:
+ reformat C code
+
+How to create a new application:
+ 1. Create a new directory for your application and cd into it.
+ 2. Run `ufbt vscode_dist create APPID=myapp`
+ 3. In VSCode, open the folder and start editing.
+ 4. Run `ufbt launch` to build and upload your application.
+"""
+
+
+def generate(env, **kw):
+ vars = kw["vars"]
+ basic_help = vars.GenerateHelpText(env)
+ env.Help(targets_help + basic_help + tail_help)
+
+
+def exists(env):
+ return True
diff --git a/scripts/ufbt/site_tools/ufbt_state.py b/scripts/ufbt/site_tools/ufbt_state.py
new file mode 100644
index 000000000..6ba8c6962
--- /dev/null
+++ b/scripts/ufbt/site_tools/ufbt_state.py
@@ -0,0 +1,117 @@
+from SCons.Errors import StopError
+from SCons.Warnings import warn, WarningOnByDefault
+
+import json
+import os
+import sys
+import pathlib
+from functools import reduce
+
+
+def _load_sdk_data(sdk_root):
+ split_vars = {
+ "cc_args",
+ "cpp_args",
+ "linker_args",
+ "linker_libs",
+ }
+ subst_vars = split_vars | {
+ "sdk_symbols",
+ }
+ sdk_data = {}
+ with open(os.path.join(sdk_root, "sdk.opts")) as f:
+ sdk_json_data = json.load(f)
+ replacements = {
+ sdk_json_data["app_ep_subst"]: "${APP_ENTRY}",
+ sdk_json_data["sdk_path_subst"]: sdk_root.replace("\\", "/"),
+ sdk_json_data["map_file_subst"]: "${TARGET}",
+ }
+
+ def do_value_substs(src_value):
+ if isinstance(src_value, str):
+ return reduce(
+ lambda acc, kv: acc.replace(*kv), replacements.items(), src_value
+ )
+ elif isinstance(src_value, list):
+ return [do_value_substs(v) for v in src_value]
+ else:
+ return src_value
+
+ for key, value in sdk_json_data.items():
+ if key in split_vars:
+ value = value.split()
+ if key in subst_vars:
+ value = do_value_substs(value)
+ sdk_data[key] = value
+
+ return sdk_data
+
+
+def _load_state_file(state_dir_node, filename: str) -> dict:
+ state_path = os.path.join(state_dir_node.abspath, filename)
+ if not os.path.exists(state_path):
+ raise StopError(f"State file {state_path} not found")
+
+ with open(state_path, "r") as f:
+ return json.load(f)
+
+
+def generate(env, **kw):
+ sdk_current_sdk_dir_node = env["UFBT_CURRENT_SDK_DIR"]
+
+ sdk_components_filename = kw.get("SDK_COMPONENTS", "components.json")
+ ufbt_state_filename = kw.get("UFBT_STATE", "ufbt_state.json")
+
+ sdk_state = _load_state_file(sdk_current_sdk_dir_node, sdk_components_filename)
+ ufbt_state = _load_state_file(sdk_current_sdk_dir_node, ufbt_state_filename)
+
+ if not (sdk_components := sdk_state.get("components", {})):
+ raise StopError("SDK state file doesn't contain components data")
+
+ sdk_data = _load_sdk_data(
+ sdk_current_sdk_dir_node.Dir(sdk_components["sdk_headers.dir"]).abspath
+ )
+
+ if not sdk_state["meta"]["hw_target"].endswith(sdk_data["hardware"]):
+ raise StopError("SDK state file doesn't match hardware target")
+
+ if sdk_state["meta"]["version"] != ufbt_state["version"]:
+ warn(
+ WarningOnByDefault,
+ f"Version mismatch: SDK state vs uFBT: {sdk_state['meta']['version']} vs {ufbt_state['version']}",
+ )
+
+ scripts_dir = sdk_current_sdk_dir_node.Dir(sdk_components["scripts.dir"])
+ env.SetDefault(
+ # Paths
+ SDK_DEFINITION=env.File(sdk_data["sdk_symbols"]),
+ FBT_DEBUG_DIR=pathlib.Path(
+ sdk_current_sdk_dir_node.Dir(sdk_components["debug.dir"]).abspath
+ ).as_posix(),
+ FBT_SCRIPT_DIR=scripts_dir,
+ LIBPATH=sdk_current_sdk_dir_node.Dir(sdk_components["lib.dir"]),
+ FW_ELF=sdk_current_sdk_dir_node.File(sdk_components["firmware.elf"]),
+ FW_BIN=sdk_current_sdk_dir_node.File(sdk_components["full.bin"]),
+ UPDATE_BUNDLE_DIR=sdk_current_sdk_dir_node.Dir(sdk_components["update.dir"]),
+ SVD_FILE="${FBT_DEBUG_DIR}/STM32WB55_CM4.svd",
+ # Build variables
+ ROOT_DIR=env.Dir("#"),
+ FIRMWARE_BUILD_CFG="firmware",
+ TARGET_HW=int(sdk_data["hardware"]),
+ CFLAGS_APP=sdk_data["cc_args"],
+ CXXFLAGS_APP=sdk_data["cpp_args"],
+ LINKFLAGS_APP=sdk_data["linker_args"],
+ LIBS=sdk_data["linker_libs"],
+ # ufbt state
+ # UFBT_STATE_DIR=ufbt_state_dir_node,
+ # UFBT_CURRENT_SDK_DIR=sdk_current_sdk_dir_node,
+ UFBT_STATE=ufbt_state,
+ UFBT_BOOTSTRAP_SCRIPT="${UFBT_SCRIPT_DIR}/bootstrap.py",
+ UFBT_SCRIPT_ROOT=scripts_dir.Dir("ufbt"),
+ )
+
+ sys.path.insert(0, env["FBT_SCRIPT_DIR"].abspath)
+
+
+def exists(env):
+ return True
diff --git a/scripts/ufbt/update.scons b/scripts/ufbt/update.scons
new file mode 100644
index 000000000..9658e0bb2
--- /dev/null
+++ b/scripts/ufbt/update.scons
@@ -0,0 +1,37 @@
+from SCons.Errors import StopError
+
+Import("core_env")
+
+update_env = core_env.Clone(
+ toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")],
+ tools=["python3"],
+)
+print("Updating SDK...")
+ufbt_state = update_env["UFBT_STATE"]
+
+update_args = [
+ "--ufbt-dir",
+ f'"{update_env["UFBT_STATE_DIR"]}"',
+]
+
+if branch_name := GetOption("sdk_branch"):
+ update_args.extend(["--branch", branch_name])
+elif channel_name := GetOption("sdk_channel"):
+ update_args.extend(["--channel", channel_name])
+elif branch_name := ufbt_state.get("branch", None):
+ update_args.extend(["--branch", branch_name])
+elif channel_name := ufbt_state.get("channel", None):
+ update_args.extend(["--channel", channel_name])
+else:
+ raise StopError("No branch or channel specified for SDK update")
+
+if hw_target := GetOption("sdk_target"):
+ update_args.extend(["--hw-target", hw_target])
+else:
+ update_args.extend(["--hw-target", ufbt_state["hw_target"]])
+
+update_env.Replace(UPDATE_ARGS=update_args)
+result = update_env.Execute(
+ update_env.subst('$PYTHON3 "$UFBT_BOOTSTRAP_SCRIPT" $UPDATE_ARGS'),
+)
+Exit(result)
diff --git a/scripts/version.py b/scripts/version.py
index 6351c72a1..2527105d1 100644
--- a/scripts/version.py
+++ b/scripts/version.py
@@ -1,5 +1,5 @@
#!/usb/bin/env python3
-VERSION = "XFW-0043"
+VERSION = "XFW-0044"
from flipper.app import App
diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons
index 4af966007..7789f95b1 100644
--- a/site_scons/extapps.scons
+++ b/site_scons/extapps.scons
@@ -113,86 +113,47 @@ Alias(
extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], [])
+
if appsrc := appenv.subst("$APPSRC"):
- deploy_sources, flipp_dist_paths, validators = [], [], []
- run_script_extra_ars = ""
+ appenv.AddAppLaunchTarget(appsrc, "launch_app")
- def _add_dist_targets(app_artifacts):
- validators.append(app_artifacts.validator)
- for _, ext_path in app_artifacts.dist_entries:
- deploy_sources.append(app_artifacts.compact)
- flipp_dist_paths.append(f"/ext/{ext_path}")
- return app_artifacts
-
- def _add_host_app_to_targets(host_app):
- artifacts_app_to_run = appenv["EXT_APPS"].get(host_app.appid, None)
- _add_dist_targets(artifacts_app_to_run)
- for plugin in host_app._plugins:
- _add_dist_targets(appenv["EXT_APPS"].get(plugin.appid, None))
-
- artifacts_app_to_run = appenv.GetExtAppByIdOrPath(appsrc)
- if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN:
- # We deploy host app instead
- host_app = appenv["APPMGR"].get(artifacts_app_to_run.app.requires[0])
-
- if host_app:
- if host_app.apptype == FlipperAppType.EXTERNAL:
- _add_host_app_to_targets(host_app)
- else:
- # host app is a built-in app
- run_script_extra_ars = f"-a {host_app.name}"
- _add_dist_targets(artifacts_app_to_run)
- else:
- raise UserError("Host app is unknown")
- else:
- _add_host_app_to_targets(artifacts_app_to_run.app)
-
- # print(deploy_sources, flipp_dist_paths)
- appenv.PhonyTarget(
- "launch_app",
- '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}',
- source=deploy_sources,
- FLIPPER_FILE_TARGETS=flipp_dist_paths,
- EXTRA_ARGS=run_script_extra_ars,
- )
- appenv.Alias("launch_app", validators)
# SDK management
-sdk_origin_path = "${BUILD_DIR}/sdk_origin"
-sdk_source = appenv.SDKPrebuilder(
- sdk_origin_path,
+amalgamated_api = "${BUILD_DIR}/sdk_origin"
+sdk_source = appenv.ApiAmalgamator(
+ amalgamated_api,
# Deps on root SDK headers and generated files
(appenv["SDK_HEADERS"], appenv["FW_ASSETS_HEADERS"]),
)
# Extra deps on headers included in deeper levels
# Available on second and subsequent builds
-Depends(sdk_source, appenv.ProcessSdkDepends(f"{sdk_origin_path}.d"))
+Depends(sdk_source, appenv.ProcessSdkDepends(f"{amalgamated_api}.d"))
-appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk")
-sdk_tree = appenv.SDKTree(appenv["SDK_DIR"], sdk_origin_path)
+appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk_headers")
+sdk_header_tree = appenv.SDKHeaderTreeExtractor(appenv["SDK_DIR"], amalgamated_api)
# AlwaysBuild(sdk_tree)
-Alias("sdk_tree", sdk_tree)
-extapps.sdk_tree = sdk_tree
+Alias("sdk_tree", sdk_header_tree)
+extapps.sdk_tree = sdk_header_tree
-sdk_apicheck = appenv.SDKSymUpdater(appenv["SDK_DEFINITION"], sdk_origin_path)
-Precious(sdk_apicheck)
-NoClean(sdk_apicheck)
-AlwaysBuild(sdk_apicheck)
-Alias("sdk_check", sdk_apicheck)
+api_check = appenv.ApiTableValidator(appenv["SDK_DEFINITION"], amalgamated_api)
+Precious(api_check)
+NoClean(api_check)
+AlwaysBuild(api_check)
+Alias("api_check", api_check)
-sdk_apisyms = appenv.SDKSymGenerator(
- "${BUILD_DIR}/assets/compiled/symbols.h", appenv["SDK_DEFINITION"]
+firmware_apitable = appenv.ApiSymbolTable(
+ "${BUILD_DIR}/assets/compiled/firmware_api_table.h", appenv["SDK_DEFINITION"]
)
-Alias("api_syms", sdk_apisyms)
+Alias("api_table", firmware_apitable)
ENV.Replace(
- SDK_APISYMS=sdk_apisyms,
+ FW_API_TABLE=firmware_apitable,
_APP_ICONS=appenv["_APP_ICONS"],
)
if appenv["FORCE"]:
- appenv.AlwaysBuild(sdk_source, sdk_tree, sdk_apicheck, sdk_apisyms)
+ appenv.AlwaysBuild(sdk_source, sdk_header_tree, api_check, firmware_apitable)
Return("extapps")