various bug fixes
This commit is contained in:
501
README.md
501
README.md
@@ -1,75 +1,179 @@
|
||||
# MTG RFID Companion
|
||||
|
||||
A hand-held **Magic: The Gathering** companion built on an **ESP32-WROOM-32**. Tap an RFID/NFC-tagged card to a built-in reader and the device:
|
||||
A feature-packed, handheld **Magic: The Gathering** companion built on the **ESP32-WROOM-32**. Slip an RFID/NFC tag into your card sleeves and tap them to the device for instant card lookups, official art display, life tracking, dice rolling, and complete deck management.
|
||||
|
||||
- renders the card's cover art and metadata on a **2.0" ST7789 TFT (240x320)**,
|
||||
- flashes a **WS2812B Neopixel** in the card's mana color and plays a short **buzzer** chirp,
|
||||
- keeps a **life counter** (20/40) you adjust with a rotary encoder,
|
||||
- caches cards locally on **LittleFS** and refetches from the **Scryfall API** over Wi-Fi,
|
||||
- runs a **SoftAP registration server** (`MTG-Companion`) so you can pair an unknown tag to a card name from your phone.
|
||||
|
||||
All code is **pure ESP-IDF v5.x (C) / FreeRTOS** — no Arduino.
|
||||
All firmware is written in **pure ESP-IDF v5.x (C) / FreeRTOS** — no Arduino overhead, dual-core task segregation, and strictly optimized for devices without external PSRAM (< 520 KB SRAM).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
## 🌟 Feature Tour & Modes
|
||||
|
||||
| | |
|
||||
The companion boots directly into an interactive, rotary-controlled interface designed specifically for tabletop MTG gameplay:
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ ★ MTG COMPANION ★ │
|
||||
├──────────────────────────────┤
|
||||
│ > [1] Card Scanner < │
|
||||
│ [2] Life & Counters │
|
||||
│ [3] Dice & Coin │
|
||||
│ [4] Commander Tax │
|
||||
│ [5] Token & Counters │
|
||||
│ [6] Deck Storage │
|
||||
│ [7] Device Info │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1. Interactive Boot Menu (`APP_STATE_MENU`)
|
||||
On power-up, the device greets you with the Main Menu.
|
||||
* **Rotate the knob** to highlight any of the 7 modes with an active amber cursor.
|
||||
* **Click the knob** to enter the highlighted mode.
|
||||
* **Universal Back (Hold knob $\ge$ 0.75s):** Press and hold the rotary switch from **any** screen to sound a double-chirp, flash purple on the WS2812B LED, and instantly return to the Main Menu.
|
||||
* **Universal Card Interrupt:** Tapping a card tag from **any** screen or menu immediately transitions into that card's view.
|
||||
|
||||
---
|
||||
|
||||
### 2. Universal RFID Scanner & Art Crop Display (`APP_STATE_CARD_VIEW`)
|
||||
* **Instant Tag Identification:** Supports MFRC522, ISO/IEC 14443A, NTAG213/215 stickers, and standard MIFARE 1K cards (4-byte and 7-byte UIDs).
|
||||
* **True RGB565 Scryfall Art:** Cards display official card illustrations pre-scaled to a crisp 200×110 format, decoded via hardware-assisted Baseline JPEG in ~10 ms with true color accuracy.
|
||||
* **Card Metadata:** Shows card name, mana cost, type line, oracle text, power/toughness, loyalty, and market pricing.
|
||||
* **8-Bit Legendary Stinger & Neopixel Feedback:**
|
||||
* **Legendary / Mythic Stinger:** Scanning any Legendary creature or Mythic Rare plays an upward 8-bit stinger and pulses the LED in gold!
|
||||
* **Mana Color LEDs:** WS2812B Neopixel illuminates in the card's native mana identity (White, Blue, Black, Red, Green, Gold, Colorless).
|
||||
* **Unmapped Card Support:** Tapping an unmapped tag switches to the Registration view, displaying the UID and pairing instructions.
|
||||
* **Smart Cache Validation:** When a card tag is re-paired or changed in the web UI, the firmware automatically detects the change, purges old cached assets, and downloads the fresh card details and art.
|
||||
|
||||
---
|
||||
|
||||
### 3. Life & Multi-Counter (`APP_STATE_LIFE_COUNTER`)
|
||||
A dedicated, high-contrast tournament-grade counter supporting the three most important Magic metrics:
|
||||
* **Life Total (Starts at 40 Commander / 20 Standard):**
|
||||
* Turn clockwise to gain life (green LED flash); turn counter-clockwise to lose life (red LED flash).
|
||||
* **Click to Cycle Counter Types:**
|
||||
1. **Life Total:** Standard life tracking.
|
||||
2. **Poison Counters (0–10):** In MTG, taking 10 poison counters loses the game.
|
||||
3. **Commander Combat Damage (0–21):** Dealing 21 combat damage with a single commander eliminates a player.
|
||||
* **8-Bit Dramatic Loss Tone:** Reaching 0 life or 10 poison triggers an 8-bit descending defeat stinger and a red alert banner.
|
||||
|
||||
---
|
||||
|
||||
### 4. Spindown D20 / D6 / Fair Coin Flip (`APP_STATE_DICE_ROLLER`)
|
||||
* **Rotate knob** to select between **D20 (Spindown)**, **D6**, or **Coin Flip**, then **click to roll**.
|
||||
* Plays an interactive rolling shuffle animation with rapid tick sounds.
|
||||
* **Natural 20 (Critical Hit):** Plays the 8-bit **Victory Fanfare** and flashes brilliant green.
|
||||
* **Natural 1 (Critical Fail):** Sounds a buzzer tone and flashes red.
|
||||
* **D6 Mode:** Quick six-sided die roll for ability triggers and random player selection.
|
||||
* **Coin Flip Mode:** Random 50/50 flip landing on **HEADS (Green)** or **TAILS (Amber)**.
|
||||
|
||||
---
|
||||
|
||||
### 5. Built-in Token & Counter Spawner (`APP_STATE_TOKEN_SPAWNER`)
|
||||
An interactive tabletop tool for spawning and managing tokens on the battlefield:
|
||||
* **Pre-configured Tokens:**
|
||||
* **Treasure** (Artifact Token) — `{T}, Sacrifice: Add one mana of any color.`
|
||||
* **Food** (Artifact Token) — `{2}, {T}, Sacrifice: You gain 3 life.`
|
||||
* **Clue** (Artifact Token) — `{2}, Sacrifice: Draw a card.`
|
||||
* **1/1 Soldier** (White Creature Token) — With full P/T box.
|
||||
* **2/2 Zombie** (Black Creature Token) — With full P/T box.
|
||||
* **1/1 Goblin** (Red Creature Token) — With full P/T box.
|
||||
* **3/3 Beast** (Green Creature Token) — With full P/T box.
|
||||
* **+1/+1 Counter** (Permanent Modifier) — Universal stat counter.
|
||||
* **Controls:**
|
||||
* **Pick Mode:** Rotate the knob to cycle through tokens (`< 1 OF 8 >`).
|
||||
* **Edit Mode:** Click the knob to select; rotate to increment/decrement count (`0` to `99`). Click again to save. Token quantities persist throughout the play session.
|
||||
|
||||
---
|
||||
|
||||
### 6. Commander Tax & Turn Tracker (`APP_STATE_COMMANDER_TRACKER`)
|
||||
In Commander (EDH), casting your commander costs an additional {2} generic mana for each time it has previously been cast from the command zone.
|
||||
* **Commander Casts:** Rotate the knob to adjust casts; the screen automatically calculates and displays the total tax (e.g., `Cast 3 = +4 Mana`).
|
||||
* **Turn Counter:** Click the button to switch to the table turn tracker.
|
||||
|
||||
---
|
||||
|
||||
### 7. Deck & Storage Manager (`APP_STATE_STORAGE_MANAGER`)
|
||||
View flash disk usage and manage cards stored in LittleFS:
|
||||
* **Storage Metrics:** Displays total storage space (~1.9 MB), used bytes, and counts of cached JSON cards and art crop images.
|
||||
* **Wipe All Cards (New Deck Mode):** Switching to a new deck? Rotate to highlight "Wipe All Cards", click, and confirm. This purges `/spiffs/cards/*` and resets `mappings.json`, freeing space for a completely new 100-card deck with full artwork.
|
||||
|
||||
---
|
||||
|
||||
### 8. Web Registration, SoftAP Pairing & Scanned Cards Overview (`web_server`)
|
||||
Pair tags and manage your entire scanned deck directly from your smartphone browser—no host software required:
|
||||
1. Connect your phone or laptop to the Wi-Fi network: **`MTG-Companion`** (open network, default IP `192.168.4.1`).
|
||||
2. Open `http://192.168.4.1` in your browser.
|
||||
3. **Pair New Tag:**
|
||||
* Tap an RFID tag to the reader — the UID populates automatically.
|
||||
* Type the card name (Scryfall autocomplete suggestions appear in real time).
|
||||
* Click **Pair Card**. The binding is saved to `/spiffs/mappings.json` and the firmware fetches the card details and cover art over Wi-Fi.
|
||||
4. **Scanned / Paired Cards Overview:**
|
||||
* View an alphabetized list of all registered cards in your deck with live card counter (`Paired Cards (X)`).
|
||||
* Displays the card title, tag UID, and cover art thumbnails streamed directly from LittleFS flash storage (`/api/art?uid=...`).
|
||||
* **Unbind Card:** Click "Unbind" next to any card to immediately delete its mapping and clean up its cached JSON and JPEG files from flash memory.
|
||||
* **Refresh:** Click "Refresh" to re-sync the list after scans or edits.
|
||||
|
||||
---
|
||||
|
||||
### 9. Device Info Screen (`APP_STATE_DEVICE_INFO`)
|
||||
Real-time diagnostic screen showing:
|
||||
* Wi-Fi STA connection status, SSID, and assigned IP address.
|
||||
* SoftAP network details (`MTG-Companion`, `192.168.4.1`).
|
||||
* Free internal heap SRAM (typically ~180 KB free during active rendering).
|
||||
* System uptime and LittleFS partition status.
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Quick Controls Cheat Sheet
|
||||
|
||||
| Action | Control |
|
||||
| :--- | :--- |
|
||||
| 🃏 RFID scanning | MFRC522, NTAG21x / MIFARE tags, 4- and 7-byte UIDs |
|
||||
| 🖥️ Display | ST7789 240x320 RGB565, 5x7 text, scaled digits, streaming JPEG decode |
|
||||
| 🌐 Network | Wi-Fi STA auto-connect, Scryfall HTTPS with local cache |
|
||||
| 📡 Pairing | SoftAP `MTG-Companion` (192.168.4.1) web SPA for UID ↔ card name |
|
||||
| 🔄 UX | EC11 rotary encoder, backlit life counter, mana-colored LED, audio cues |
|
||||
| 💾 Storage | LittleFS `/spiffs` (~1.9MB) seeded with card mapping + example art |
|
||||
| **Navigate / Adjust** | Rotate EC11 rotary knob clockwise / counter-clockwise |
|
||||
| **Select / Roll / Toggle** | Short press encoder button |
|
||||
| **Universal Back (To Menu)** | **Long press encoder button ($\ge$ 0.75s)** (double chirp + purple flash) |
|
||||
| **Instant Card View** | Tap any RFID card sleeve to reader from **any** screen |
|
||||
|
||||
---
|
||||
|
||||
## Hardware Requirements
|
||||
## 🛠️ Hardware Requirements
|
||||
|
||||
- **ESP32-WROOM-32** dev board (4MB flash, no PSRAM required)
|
||||
- **MFRC522** RFID reader (13.56 MHz, SPI)
|
||||
- **ST7789** SPI TFT, 240x320 (e.g. common 2.0-2.4" breakout)
|
||||
- **EC11** rotary encoder with push switch
|
||||
- **WS2812B / Neopixel** (single LED)
|
||||
- **Passive piezo buzzer**
|
||||
- Antenna-friendly **NTAG213/NTAG215** stickers or MIFARE cards, ~3.3V–5V supply
|
||||
- **ESP32-WROOM-32** development board (4MB flash, dual-core LX6, no external PSRAM needed).
|
||||
- **ST7789** SPI TFT LCD, 240x320 resolution (2.0" or 2.4" breakout).
|
||||
- **MFRC522** RFID reader module (13.56 MHz, SPI).
|
||||
- **EC11** rotary encoder with integrated push switch.
|
||||
- **WS2812B / Neopixel** (single addressable RGB LED).
|
||||
- **Passive piezo buzzer** (driven via LEDC PWM).
|
||||
- **Antenna-friendly NTAG213/NTAG215** micro FPC stickers (for card sleeves) or standard MIFARE cards.
|
||||
- **2200 µF Electrolytic Capacitor** (placed directly across MFRC522 VCC and GND) — **CRITICAL**: absorbs RF transmit spikes and prevents 3.3V rail voltage dips that trigger ESP32 brownouts or MFRC522 register resets.
|
||||
|
||||
---
|
||||
|
||||
## Wiring Guide
|
||||
## 🔌 Peripheral Wiring & Pinout
|
||||
|
||||
> **Do not change these GPIO assignments in code** unless you update both
|
||||
> `main/hw_pins.h` and this table. The ST7789 and MFRC522 **share one SPI bus** (SPI2_HOST).
|
||||
|
||||
### Pin map
|
||||
|
||||
> **Display and reader are on two separate SPI controllers:** the ST7789 on
|
||||
> **SPI2_HOST (VSPI)**, the MFRC522 on **SPI3_HOST (HSPI)** — so the panel and
|
||||
> the RFID reader never share bus timing.
|
||||
The display and RFID reader use **two separate SPI host controllers** to prevent SPI bus contention and clock mismatch:
|
||||
- **ST7789 Display:** `SPI2_HOST` (VSPI) at 20 MHz.
|
||||
- **MFRC522 Reader:** `SPI3_HOST` (HSPI) at 1 MHz.
|
||||
|
||||
| Peripheral | Function | ESP32 GPIO | Notes |
|
||||
| :--- | :--- | :---: | :--- |
|
||||
| **ST7789** (7-pin) — SPI2_HOST | SCL | **18** | SPI2 (VSPI) clock |
|
||||
| | SDA | **23** | SPI2 MOSI (data) |
|
||||
| | CS | **21** | dedicated chip select (non-strapping pin) |
|
||||
| | DC | **19** | data/command (non-strapping pin) |
|
||||
| | RST | **4** | panel reset |
|
||||
| | VCC / GND | 3.3V / GND | panel power (no separate LED pin) |
|
||||
| **MFRC522** — SPI3_HOST | SCK | **16** | SPI3 (HSPI) clock (own bus) |
|
||||
| | MOSI | **17** | SPI3 data out |
|
||||
| | MISO | **13** | SPI3 data in |
|
||||
| | SDA (CS) | **26** | dedicated chip select |
|
||||
| | RST | **22** | hard reset |
|
||||
| | 3.3V / GND | — | feed from board 3.3V rail |
|
||||
| **EC11** | CLK | **32** | interrupt input, pull-up |
|
||||
| | DT | **33** | quadrature B |
|
||||
| | SW | **25** | button, active low, pull-up |
|
||||
| **WS2812B** | DIN | **27** | RMT Tx; supply 5V/VDD, GND common |
|
||||
| **Buzzer** | SIG | **14** | LEDC PWM |
|
||||
|
||||
### Suggested breadboard layout
|
||||
| **ST7789 Display** (SPI2) | SCL (Clock) | **18** | SPI2_HOST (VSPI) clock |
|
||||
| | SDA (MOSI) | **23** | SPI2_HOST MOSI |
|
||||
| | CS | **21** | Dedicated Chip Select (non-strapping pin) |
|
||||
| | DC | **19** | Data / Command select (non-strapping pin) |
|
||||
| | RST | **4** | Display Reset |
|
||||
| | VCC / GND | 3.3V / GND | Display logic & backlight power |
|
||||
| **MFRC522 RFID** (SPI3) | SCK | **16** | SPI3_HOST (HSPI) clock |
|
||||
| | MOSI | **17** | SPI3_HOST MOSI |
|
||||
| | MISO | **13** | SPI3_HOST MISO |
|
||||
| | SDA (CS) | **26** | Dedicated Chip Select |
|
||||
| | RST | **22** | Hard reset line |
|
||||
| | 3.3V / GND | 3.3V / GND | **Attach 2200 µF capacitor across these pins!** |
|
||||
| **EC11 Rotary Encoder** | CLK (A) | **32** | Quadrature A (interrupt-driven, internal pull-up) |
|
||||
| | DT (B) | **33** | Quadrature B (internal pull-up) |
|
||||
| | SW (Button) | **25** | Push button (active LOW, internal pull-up) |
|
||||
| **Indicators** | WS2812B DIN | **27** | RMT peripheral Tx (supply 5V to VDD, common GND) |
|
||||
| **Audio** | Buzzer SIG | **14** | LEDC PWM channel |
|
||||
|
||||
### Wiring Diagram
|
||||
```
|
||||
ESP32-WROOM-32
|
||||
┌────────────────────────────┐
|
||||
@@ -78,282 +182,75 @@ All code is **pure ESP-IDF v5.x (C) / FreeRTOS** — no Arduino.
|
||||
│ 13 ── MISO ────┼──┘ │
|
||||
│ 26 ── SDA(CS) ─┘ │
|
||||
│ 22 ── RST ─┐ │
|
||||
│ 21 ── CS ──┼───────┤ ST7789 (7-pin)
|
||||
│ 19 ── DC ──┤ │ SCL = 18, SDA = 23
|
||||
│ 4 ── RST ─┘ │ VCC = 3V3, GND = GND
|
||||
│ 21 ── CS ──┼───────┤ ST7789 (7-pin, SPI2_HOST)
|
||||
│ 19 ── DC ──┤ │ SCL=18, SDA=23, CS=21, DC=19, RST=4
|
||||
│ 4 ── RST ─┘ │ VCC=3.3V, GND=GND
|
||||
│ 18 ── SCL ─────────┘
|
||||
│ 23 ── SDA ───────── (display = SPI2)
|
||||
│ 32 ── CLK ────────┤ EC11 33 ── DT 25 ── SW/GND
|
||||
│ 27 ── Din ────────┤ WS2812B (5V + GND)
|
||||
│ 14 ── SIG ────────┤ Buzzer (GND)
|
||||
│ 23 ── SDA ─────────
|
||||
│ 32 ── CLK ────────┤ EC11 Rotary Encoder (CLK=32, DT=33, SW=25)
|
||||
│ 27 ── Din ────────┤ WS2812B LED (GPIO 27, 5V rail)
|
||||
│ 14 ── SIG ────────┤ Piezo Buzzer (GPIO 14)
|
||||
└────────────────────────────┘
|
||||
│ │
|
||||
[2200 µF Cap]
|
||||
│ │
|
||||
┌─────┴───┴──────────────────┐
|
||||
│ 3V3 GND │
|
||||
│ MFRC522 RFID Module (SPI3) │
|
||||
└────────────────────────────┘
|
||||
```
|
||||
|
||||
> **MFRC522 (own SPI3/HSPI bus):** SCK→16, MOSI→17, MISO→13, SDA(CS)→26,
|
||||
> RST→22, VCC→3.3V, GND→GND. It shares **no lines** with the display.
|
||||
>
|
||||
> **ST7789 (SPI2/VSPI):** SCL→18, SDA→23, CS→21, DC→19, RST→4, VCC→3.3V, GND→GND.
|
||||
>
|
||||
> CS (GPIO21) and DC (GPIO19) are deliberately **not** strapping pins, so the
|
||||
> panel can stay connected during `idf.py flash`. (The old GPIO15/GPIO2
|
||||
> assignment made the panel hold boot-strap lines and block flashing.)
|
||||
|
||||
- Keep SPI signal runs short (< 10 cm) for reliable 20 MHz display transfers.
|
||||
- NTAG213/215 FPC stickers need to sit flat and close to the MFRC522 antenna.
|
||||
- The **WS2812B** draws up to ~60 mA; power it from the 5V rail, never from a GPIO.
|
||||
> **Note on Flashing:** GPIO21 (CS) and GPIO19 (DC) are deliberate non-strapping pins. The ST7789 display can remain fully connected during `idf.py flash`.
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
## 💻 Software Setup & Build
|
||||
|
||||
### 1. Toolchain
|
||||
|
||||
Install ESP-IDF **v5.5** (any 5.x works): https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/
|
||||
|
||||
Verify:
|
||||
### 1. Requirements
|
||||
- **ESP-IDF v5.x** (v5.0 through v5.5 supported):
|
||||
```powershell
|
||||
. C:\Espressif\frameworks\esp-idf-v5.5\export.ps1
|
||||
idf.py --version
|
||||
```
|
||||
|
||||
### 2. Configure Wi-Fi
|
||||
Configure home Wi-Fi credentials for automatic Scryfall fetching:
|
||||
```powershell
|
||||
# Windows (Espressif "ESP-IDF 5.5 PowerShell" terminal)
|
||||
. C:\Espressif\Initialize-Idf.ps1 # or: . $IDF_PATH\export.ps1
|
||||
idf.py --version
|
||||
idf.py menuconfig
|
||||
# Navigate to: MTG RFID Companion -> Wi-Fi SSID & Password
|
||||
```
|
||||
*(Or edit `sdkconfig.defaults.esp32` directly).*
|
||||
|
||||
> **Never used Espressif tools before? Skip ahead to
|
||||
> "[Changing the Wi-Fi without the toolchain](#changing-the-wifi-without-the-toolchain)";**
|
||||
> the section below is optional.
|
||||
|
||||
### 2. Build
|
||||
|
||||
### 3. Build & Flash
|
||||
```powershell
|
||||
idf.py set-target esp32
|
||||
idf.py build
|
||||
idf.py -p COM4 flash monitor
|
||||
```
|
||||
|
||||
The custom partition table (`partitions.csv`) reserves:
|
||||
|
||||
| Offset | Size | Subtype | Purpose |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 0x10000 | 2 MB | app/factory | firmware |
|
||||
| 0x210000 | ~1.9 MB | data/littlefs | `/spiffs` cache (seeded from `storage/`) |
|
||||
|
||||
### 3. Configure Wi-Fi
|
||||
|
||||
Two equivalent ways:
|
||||
|
||||
```powershell
|
||||
# a) interactive menu (recommended)
|
||||
idf.py menuconfig # -> "MTG RFID Companion" -> SSID + password
|
||||
|
||||
# b) edit the preset file directly
|
||||
# sdkconfig.defaults.esp32
|
||||
```
|
||||
|
||||
Leaving the SSID empty (or keeping the `YOUR_...` placeholders) **disables
|
||||
station mode**. The SoftAP pairing server runs regardless, so the device stays
|
||||
usable as a fully standalone scanner — no home network required.
|
||||
|
||||
#### What "station mode disabled" actually means
|
||||
|
||||
The Wi-Fi radio still turns on, but only as an access point:
|
||||
|
||||
1. `wifi_manager_start()` sees the blank/placeholder SSID and returns early —
|
||||
it never calls `esp_wifi_connect()`, so the device won't try to join any
|
||||
router (and won't pretend to be online).
|
||||
2. `web_server_start()` starts unconditionally: SoftAP **`MTG-Companion`** at
|
||||
`192.168.4.1` (own DHCP) plus the `/api/last_uid` and `/api/bind` endpoints.
|
||||
3. `net_task` skips the 60-second connect wait **and** the boot-time prefetch.
|
||||
Card lookups only run when you explicitly trigger one, and those fail fast
|
||||
(no internet route) with a `scryfall` log line — they never block the UI.
|
||||
|
||||
#### What works and what doesn't
|
||||
|
||||
| Feature | In AP-only mode | Notes |
|
||||
| :--- | :--- | :--- |
|
||||
| RFID scanning, life counter, LED/buzzer | ✔ | fully local |
|
||||
| Showing **already-cached** cards (art + stats) | ✔ | served from `/spiffs` |
|
||||
| Binding a UID to a card name | ✔ | written to `/spiffs/mappings.json` immediately |
|
||||
| **Scryfall lookup + art download** | ✘ | the device has no internet here; the fetch fails and is logged. It completes automatically on a later boot **with Wi-Fi configured** — the boot-time prefetch walks `mappings.json` and downloads anything missing |
|
||||
| Scryfall autocomplete in the pairing page | ⚠️ | the AP doesn't pass internet through (no NAT). A phone with **cellular data keeps its mobile connection**, so autocomplete works there; on a device with no other internet path, type the exact card name manually |
|
||||
|
||||
> **Use case:** AP-only mode is the normal "deck on the table" flow — mid-game
|
||||
> you almost never need live Scryfall lookups. When you do (new card, missing
|
||||
> art), configure Wi-Fi once, boot briefly, and the binding + art cache update
|
||||
> themselves via prefetch.
|
||||
|
||||
---
|
||||
|
||||
## Changing the Wi-Fi without the toolchain
|
||||
|
||||
> **If you're new to this and haven't installed any Espressif software yet,
|
||||
> start here.**
|
||||
|
||||
The Wi-Fi network the device connects to is **baked into the firmware at
|
||||
compile time** — there is no on-device settings menu yet. To change it you
|
||||
must edit one text file and produce a new firmware image. Here is the shortest
|
||||
ESPRESSIF-FREE path to do that, with zero drivers or toolchains.
|
||||
|
||||
### The one file you edit
|
||||
|
||||
Open **`sdkconfig.defaults.esp32`** in any text editor (Notepad is fine) and
|
||||
find these two lines:
|
||||
|
||||
```
|
||||
CONFIG_MTG_WIFI_SSID="YOUR_SSID_HERE"
|
||||
CONFIG_MTG_WIFI_PASS="YOUR_PASSWORD_HERE"
|
||||
```
|
||||
|
||||
Replace the placeholders with **your** network name and password, keeping the
|
||||
quotes:
|
||||
|
||||
```
|
||||
CONFIG_MTG_WIFI_SSID="MyHomeWiFi"
|
||||
CONFIG_MTG_WIFI_PASS="correct horse battery staple"
|
||||
```
|
||||
|
||||
- Keep the file **UTF-8 without BOM**, and don't add spaces inside the quotes.
|
||||
- If your password has a `"` or `\` in it, this file format can't represent it
|
||||
(rare) — pick that one character out of the password or use the toolchain
|
||||
menu instead.
|
||||
- Want to go back to offline-only later? Restore the `YOUR_...` placeholders
|
||||
and rebuild.
|
||||
|
||||
### Getting a new firmware image from this file
|
||||
|
||||
The repo cannot build firmware without the toolchain. Your options, easiest
|
||||
first:
|
||||
|
||||
| Path | What's needed | How |
|
||||
| :--- | :--- | :--- |
|
||||
| **Ask whoever gave you the device** | a minute of their time | Send them the edited file; they run `idf.py build` and hand you a new `.bin` |
|
||||
| **One-time toolchain install (~45 min, guided)** | the Espressif installer (free, Windows/macOS/Linux) | Follow the official guide: <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/> then run the 5 commands in [Build](#2-build) |
|
||||
| **CI build** (GitHub Actions etc.) | a GitHub account | The repo can build on `ubuntu` in CI; push the edited file and download the built `mtg-rfid-companion.bin` artifact |
|
||||
|
||||
### Flashing the new firmware (no toolchain needed)
|
||||
|
||||
Flash using **Espressif Flash Download Tool** (graphical, no toolchain):
|
||||
<https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/linux-setup-scratch.html#esp32-flash-download-tool>
|
||||
or `esptool.py` if you already have Python:
|
||||
|
||||
```
|
||||
pip install esptool
|
||||
esptool.py -p COM3 --chip esp32 --before default_reset write_flash --flash_mode dio --flash_size 4MB 0x1000 bootloader.bin 0x8000 partition-table.bin 0x10000 mtg-rfid-companion.bin
|
||||
```
|
||||
|
||||
### TL;DR
|
||||
|
||||
1. Edit `sdkconfig.defaults.esp32` → 2. rebuild (yours or someone's toolchain) →
|
||||
3. flash the `.bin` → 4. done. If you don't need your home Wi-Fi, **skip the
|
||||
whole thing**: the device works standalone and has its own AP.
|
||||
|
||||
---
|
||||
|
||||
### 4. Flash & watch
|
||||
|
||||
```powershell
|
||||
idf.py -p COMx flash monitor # COMx = your serial port
|
||||
```
|
||||
|
||||
Expected boot log if everything is wired right:
|
||||
|
||||
```
|
||||
I storage: LittleFS mounted at /spiffs: <size> bytes total ...
|
||||
I storage: boot_count read back = 1 (persistence across reboots OK)
|
||||
I rfid: RC522 polling task pinned to Core 0
|
||||
I display: ST7789 240x320 ready ...
|
||||
I web: SoftAP 'MTG-Companion' at 192.168.4.1, web server ready
|
||||
I ui: UI task on core 1
|
||||
```
|
||||
|
||||
> **RFID reader missing / not wired? That's OK.** A reader self-test failure no
|
||||
> longer aborts boot — the device logs a warning and continues into the display,
|
||||
> SoftAP and web-pairing screens without card scanning:
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Scan a card
|
||||
Place a tagged card on the reader:
|
||||
1. **Mapped UID** → chirp, mana-colored LED flash, then the card view (cover art + name/type/P·T/price/mana).
|
||||
2. **Unknown UID** → registration screen with the pairing address.
|
||||
|
||||
### Life counter
|
||||
Press the encoder button to toggle **SCANNER ↔ LIFE_COUNTER**. Rotate to adjust
|
||||
(20/40 preset or whatever you last set; press again to reset to the preset).
|
||||
|
||||
### Web pairing (registration mode)
|
||||
1. Connect your phone to the `MTG-Companion` access point. A phone with
|
||||
cellular data keeps its mobile internet, so Scryfall autocomplete works;
|
||||
otherwise type the name manually.
|
||||
2. Open `http://192.168.4.1`.
|
||||
3. Scan an unmapped card — the UID appears in the page.
|
||||
4. Type / pick a Scryfall-autocomplete name → **Pair UID with card**.
|
||||
5. The mapping persists to `/spiffs/mappings.json`. The card data/art fetch
|
||||
runs immediately if the device has Wi-Fi, or is picked up automatically by
|
||||
the boot-time prefetch the next time you boot with Wi-Fi configured.
|
||||
|
||||
---
|
||||
|
||||
## Storage Layout (`/spiffs`)
|
||||
## 📂 LittleFS Storage Structure
|
||||
|
||||
```
|
||||
/spiffs/
|
||||
├── mappings.json UID -> Card Name map
|
||||
├── cards/
|
||||
│ ├── sample.json example metadata
|
||||
│ ├── sample.jpg example cover art (baked into flash image)
|
||||
│ ├── <UID>.json Scryfall metadata cache
|
||||
│ └── <UID>.jpg cached cover art
|
||||
├── mappings.json UID -> Card Name JSON map
|
||||
└── cards/
|
||||
├── <UID>.json Scryfall attributes (Mana, Type, Oracle, P/T)
|
||||
└── <UID>.jpg Cached 200x110 Baseline JPEG art crop
|
||||
```
|
||||
|
||||
The `storage/` folder is compiled into the LittleFS partition image at build
|
||||
time (`littlefs_create_partition_image`), so a fresh flash already contains
|
||||
`mappings.json` and the example art.
|
||||
The `storage/` repository directory is compiled directly into the LittleFS partition image at build time (`littlefs_create_partition_image`).
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
## ❓ Troubleshooting
|
||||
|
||||
| Symptom | Fix |
|
||||
| Symptom | Cause & Solution |
|
||||
| :--- | :--- |
|
||||
| Display = black / garbage | Check CS/DC/RST wiring; try toggling `esp_lcd_panel_invert_color` and `LCD_RGB_ELEMENT_ORDER_*` in `display_manager.c` |
|
||||
| Colors inverted | Same toggle as above — panel clones differ |
|
||||
| No UID printed on scan | Verify MFRC522 wiring, tag type (ISO14443A), antenna positioning; confirm `rfid: RC522 polling task pinned to Core 0` in the log |
|
||||
| Boot log: `rc522: FIFO length missmatch` / `RTOS: RFID unavailable` | Reader self-test failed. Check the MFRC522 has 3.3V power (many clones have a separate power pin), CS=GPIO26 / RST=GPIO22, SCK/MOSI/MISO=16/17/13 on its own SPI3 bus. Try lowering `RC522_SPI_CLK_HZ` in `rfid_manager.c` (e.g. `1000000`); the skip-self-test option (`CONFIG_MTG_RFID_SKIP_SELFTEST`) lets scanning start anyway |
|
||||
| Wi-Fi never connects | Re-run `idf.py menuconfig`, confirm you replaced the `YOUR_...` placeholders |
|
||||
| Scryfall autocomplete shows nothing | The AP has no internet passthrough — the phone needs its own mobile data connection, or type the card name manually |
|
||||
| Can't reach 192.168.4.1 | Ensure your phone joined `MTG-Companion`, not a cached network |
|
||||
| Flash fails: `Serial data stream stopped` | If the ST7789 is wired with CS=GPIO15 / DC=GPIO2 (the old strap-pin assignment), disconnect/power-down the display during `idf.py flash` — the panel holds the boot-strap lines at reset and corrupts the serial link. With the current recommended wiring (CS=GPIO21, DC=GPIO19, both non-strapping) the display can stay connected. |
|
||||
| RFID step of boot log: `Buffers content missmatch`, buffer2 = `13 33 37` | The reader answers (SPI OK) but the PCD self-test is flaky on clones. RST can stay unconnected (`-1`), the clock is already 1 MHz; confirm the MFRC522's dedicated power header gets a clean 3.3V and that MISO=GPIO13 is actually connected |
|
||||
| Card renders but no art | Card not cached yet — check `scryfall` log lines; art downloads are best-effort |
|
||||
| App grows near 2MB | mbedTLS + HTTP pulls are large; if needed, enable `CONFIG_MBEDTLS_CERTIFICATE_BUNDLE` (smaller) |
|
||||
|
||||
---
|
||||
|
||||
## Project Layout
|
||||
|
||||
```
|
||||
├── CMakeLists.txt top-level; bakes LittleFS storage image
|
||||
├── partitions.csv custom partition table
|
||||
├── sdkconfig.defaults shared ESP32 defaults
|
||||
├── sdkconfig.defaults.esp32 Wi-Fi credential preset (edit me)
|
||||
├── storage/ files pre-seeded into /spiffs
|
||||
├── tools/ host-side helpers (sample JPEG generator)
|
||||
└── main/
|
||||
├── hw_pins.h GPIO assignments (authoritative)
|
||||
├── spi_bus_manager.c shared SPI2_HOST bus
|
||||
├── storage_manager.c LittleFS mount + JSON helpers
|
||||
├── rfid_manager.c MFRC522 / Core 0 polling
|
||||
├── display_manager.c ST7789 + fonts + streaming JPEG
|
||||
├── ui_task.c state machine + screens (Core 1)
|
||||
├── wifi_manager.c STA connect/reconnect
|
||||
├── scryfall_client.c Scryfall HTTPS + cache (net_task)
|
||||
├── web_server.c SoftAP pairing server
|
||||
├── encoder.c EC11 ISR decode
|
||||
├── led_manager.c WS2812B RMT
|
||||
├── buzzer.c LEDC PWM tones
|
||||
└── app_state.c shared app/life-counter state
|
||||
```
|
||||
|
||||
See `AGENTS.md` for the detailed architecture, core split, and coding
|
||||
conventions used across this repository.
|
||||
| **ESP32 reboots when scanning card** | 3.3V rail voltage sag caused by the MFRC522 RF transmitter. **Add a 2200 µF capacitor** directly across the MFRC522 VCC and GND pins. |
|
||||
| **Display colors inverted (blues look red)** | Check `flags.swap_color_bytes = 1` in JPEG config and `esp_lcd_panel_invert_color()` in `display_manager.c`. Panel clones differ between RGB and BGR. |
|
||||
| **Card shows "PARSE FAILED"** | Corrupt card JSON in cache. The firmware auto-purges corrupt entries on reboot, or use **Deck Storage -> Wipe** to reset. |
|
||||
| **Scanned card shows previously paired card** | Re-pairing a card now automatically purges the old cache. Ensure firmware is up to date, or clear via **Deck Storage -> Wipe**. |
|
||||
| **Art crop doesn't show** | Ensure device has Wi-Fi connectivity to download from Scryfall. Images are cached locally on flash once downloaded. |
|
||||
| **Flash fails: `Failed to connect to ESP32`** | Make sure no other terminal is monitoring COM4 (`Ctrl + ]` to exit monitor). If using older GPIO15/2 wiring, hold the BOOT button during connect. |
|
||||
Reference in New Issue
Block a user