Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.sh text eol=lf
18 changes: 17 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
# worker - Cloudflare Worker (cloud-plugins/) typecheck + wrangler dry-run bundle
# esp32 - ESP32-S2 WiFi devboard firmware via the ESP-IDF v5.2.8 docker image
# web - syntax check of the inline JavaScript in web-image-prep/index.html
# lint - `ufbt lint` (clang-format), ADVISORY ONLY - never fails the run
# tests - host unit tests (`make test`) with ASan + UBSan
# lint - `ufbt lint` (clang-format), ADVISORY ONLY - never fails the run
#
# There are deliberately NO `paths:` filters: esp32-wifi-fw/shared/tt_wifi_proto.h
# is compiled into BOTH the FAP (via shared/tt_wifi_proto_fap.h) and the ESP
Expand Down Expand Up @@ -261,6 +262,21 @@ jobs:
process.exit(failed ? 1 : 0);
JS

# ---------------------------------------------------------------------------
# Host unit tests - plain C on the runner, no Flipper SDK needed.
# Sanitizers catch out-of-bounds writes in the DMA table builder.
# ---------------------------------------------------------------------------
tests:
name: Host unit tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Run tests (ASan + UBSan)
run: make test CC="gcc -fsanitize=address,undefined -fno-sanitize-recover=all"

# ---------------------------------------------------------------------------
# clang-format lint - ADVISORY ONLY
# ---------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Thanks for wanting to help out! Here's how to get involved.
1. Fork the repo
2. Create a feature branch: `git checkout -b my-feature`
3. Make your changes
4. Test on hardware if possible (or at minimum, make sure `make build-fap` (plain `ufbt`) passes). CI builds the Flipper FAP, the cloud worker and the ESP32 firmware on every PR, so keep all three compiling.
4. Test on hardware if possible (or at minimum, make sure `make build-fap` (plain `ufbt`) and `make test` pass). CI builds the Flipper FAP, the cloud worker and the ESP32 firmware and runs the host unit tests on every PR, so keep all three compiling and the tests green.
5. Commit with a clear message
6. Open a pull request

Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ IDF_ENV = idf_log=$$(mktemp) \
&& rm -f "$$idf_log"

.PHONY: help setup build-fap build-worker build-esp build-all launch flash-esp \
serve-web lint check-worker clean distclean check-esp-env check-root worker-deps
serve-web lint check-worker test clean distclean check-esp-env check-root worker-deps

# --- Help -------------------------------------------------------------------
help: ## Show this help
Expand Down Expand Up @@ -161,6 +161,12 @@ lint: ## ADVISORY: run ufbt lint (clang-format). Fails on main by design; not a
check-worker: worker-deps ## Type-check the worker only (npx tsc --noEmit)
cd $(WORKER_DIR) && $(NPX) tsc --noEmit

test: ## Run host unit tests (plain C, no Flipper needed) -> dist/tests/
@mkdir -p dist/tests
$(CC) -std=c99 -Wall -Wextra -Werror -O1 tests/test_ir_seq.c ir/tagtinker_ir_seq.c -lm \
-o dist/tests/test_ir_seq
./dist/tests/test_ir_seq

# --- Cleanup ----------------------------------------------------------------
clean: check-root ## Remove build outputs only (dist/, worker dist + .wrangler, ESP build/, generated .clang-format)
rm -rf dist $(WORKER_DIR)/dist $(WORKER_DIR)/.wrangler $(ESP_DIR)/build
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ Only test tags you own or are allowed to test.

## Features

- **TagTinker Flipper App:** High-performance RLE streaming IR engine.
- **TagTinker Flipper App:** High-performance RLE streaming IR engine. Frames are timed by hardware (TIM1 + DMA), so USB, Bluetooth and the screen keep running while a tag is updated.
- **Progress bar:** image and text sends show how far they are, in percent; Back cancels at any time.
- **TagTinker Image Prep (web):** Single-file, dependency-free HTML page that lists every supported tag profile, runs a full image pipeline (tone, contrast, detail, sharpen, dither, photo-grade Oklab 3-colour quantisation) and exports a Flipper-ready BMP. Hosted at **[i12bp8.github.io/TagTinker](https://i12bp8.github.io/TagTinker/)** (source: `web-image-prep/`).
- **Drop-folder image flow:** Drop a prepared BMP into `apps_data/tagtinker/dropped/` on the Flipper SD card, then open `Targeted Payloads → <tag> → Set Image` and pick it. The Flipper rescales the 1-bit and two-plane BMPs that the image preparer exports, so one file can be sent to graphics tags of other sizes and to any page. Type 1626 (SmartTAG Color 2.6) targets only accept files up to 24 KB.
- **External IR module:** `Settings → IR Output` sends through a module on GPIO pin 2 (A7) instead of the built-in LED, like the stock Infrared app (`Internal`, `GPIO A7` or `Auto` to detect the module). `5V on GPIO` powers modules that take 5V from pin 1. A stronger emitter gives more range and more reliable image updates.
- **NFC Tag Scan:** Add a target by scanning the tag's NFC chip instead of typing its barcode. This works when the tag's NFC data carries an ID TagTinker can decode; otherwise use `+ Type Barcode`.
- **WiFi Plugins (optional):** Plug a Flipper WiFi Dev Board (ESP32-S2) into the GPIO header to unlock live, network-rendered tag designs — crypto price cards, weather tiles, identicons, and more — auto-discovered by the FAP. New plugins live entirely on the cloud worker; the Flipper firmware never has to be re-flashed to add one. The dev board firmware talks to the worker named by `CONFIG_TT_CLOUD_URL`, so you can point it at your own deployment of `cloud-plugins/` instead.
<img alt="image" src="https://raw.githubusercontent.com/i12bp8/TagTinker/refs/heads/main/PXL_20260427_092219442.jpg" />
Expand Down Expand Up @@ -95,10 +97,15 @@ The repo holds three build products plus the static web tool. A root `Makefile`
| `make build-esp` | Build the ESP32-S2 firmware → `esp32-wifi-fw/build/tagtinker_wifi.bin` |
| `make flash-esp ESP_PORT=/dev/cu.usbserial-XXXX` | Build and flash the devboard (omit `ESP_PORT` to auto-detect) |
| `make serve-web` | Serve the Image Prep tool at `http://localhost:8000/` |
| `make test` | Run the host unit tests (plain C, no Flipper or SDK needed) |
| `make lint` | Advisory `ufbt lint`; it currently fails on deliberately column-aligned code and is not a CI gate |
| `make clean` | Remove build outputs only (keeps `.venv/`, `node_modules/` and `sdkconfig`) |

CI ([`build.yml`](.github/workflows/build.yml)) builds all three components — FAP, worker and ESP32 firmware — on every pull request and every push to `main`; the web tool deploys to GitHub Pages from `main`.
**Windows without `make`:** double-click `build_and_flash.bat` (or run it from a terminal) with the Flipper plugged in over USB and qFlipper closed. It runs `ufbt update`, builds the FAP and installs it with `ufbt launch`.

**Linux / macOS without `make`:** run `./build_and_flash.sh` the same way. On Linux your user needs access to the Flipper's serial port (`/dev/ttyACM0`): add it to the `dialout` group (`uucp` on Arch), then log out and back in.

CI ([`build.yml`](.github/workflows/build.yml)) builds all three components — FAP, worker and ESP32 firmware — and runs the host unit tests on every pull request and every push to `main`; the web tool deploys to GitHub Pages from `main`.

## FAQ

Expand Down
3 changes: 2 additions & 1 deletion application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ App(
name="TagTinker",
apptype=FlipperAppType.EXTERNAL,
entry_point="tagtinker_app_main",
requires=["gui", "notification", "dialogs", "storage", "bt", "nfc", "expansion"],
requires=["gui", "notification", "dialogs", "storage", "bt", "nfc", "expansion", "power"],
stack_size=12 * 1024,
fap_icon="tagtinker_10px.png",
fap_category="Infrared",
Expand All @@ -13,6 +13,7 @@ App(
sources=[
"tagtinker_app.c",
"ir/tagtinker_ir.c",
"ir/tagtinker_ir_seq.c",
"protocol/tagtinker_proto.c",
"scenes/tagtinker_scene.c",
"scenes/tagtinker_scene_about.c",
Expand Down
20 changes: 20 additions & 0 deletions build_and_flash.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@echo off
REM Windows helper: build the FAP with ufbt and install it on a USB-connected Flipper (close qFlipper first)
cd /d "%~dp0"
where ufbt >nul 2>nul && (set "UFBT=ufbt") || (set "UFBT=py -m ufbt")
echo === ufbt update (SDK release) ===
%UFBT% update
echo === build ===
%UFBT%
if errorlevel 1 goto fail
echo === launch on the Flipper ===
%UFBT% launch
if errorlevel 1 goto fail
echo.
echo OK: app installed and launched.
goto end
:fail
echo.
echo FAILED - see the messages above.
:end
pause
30 changes: 30 additions & 0 deletions build_and_flash.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Linux/macOS helper: build the FAP with ufbt and install it on a USB-connected Flipper (close qFlipper first)
set -euo pipefail
cd "$(dirname "$0")"

if [ -x .venv/bin/ufbt ]; then
UFBT=(.venv/bin/ufbt)
elif command -v ufbt >/dev/null 2>&1; then
UFBT=(ufbt)
elif python3 -c 'import ufbt' >/dev/null 2>&1; then
UFBT=(python3 -m ufbt)
else
echo "ufbt not found. Install it with: python3 -m pip install --upgrade ufbt (or run: make setup)" >&2
exit 1
fi

fail() {
echo
echo "FAILED - see the messages above." >&2
exit 1
}

echo "=== ufbt update (SDK release) ==="
"${UFBT[@]}" update || echo "(update failed, building with the SDK already installed)"
echo "=== build ==="
"${UFBT[@]}" || fail
echo "=== launch on the Flipper ==="
"${UFBT[@]}" launch || fail
echo
echo "OK: app installed and launched."
Loading