256 lines
14 KiB
Markdown
256 lines
14 KiB
Markdown
# MTG RFID Companion
|
||
|
||
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.
|
||
|
||
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).
|
||
|
||
---
|
||
|
||
## 🌟 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 |
|
||
| :--- | :--- |
|
||
| **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
|
||
|
||
- **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.
|
||
|
||
---
|
||
|
||
## 🔌 Peripheral Wiring & Pinout
|
||
|
||
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 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
|
||
┌────────────────────────────┐
|
||
│ 3V3 16 ── SCK ────────┐ │
|
||
│ GND 17 ── MOSI ────┐ │ │
|
||
│ 13 ── MISO ────┼──┘ │
|
||
│ 26 ── SDA(CS) ─┘ │
|
||
│ 22 ── RST ─┐ │
|
||
│ 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 ─────────
|
||
│ 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) │
|
||
└────────────────────────────┘
|
||
```
|
||
|
||
> **Note on Flashing:** GPIO21 (CS) and GPIO19 (DC) are deliberate non-strapping pins. The ST7789 display can remain fully connected during `idf.py flash`.
|
||
|
||
---
|
||
|
||
## 💻 Software Setup & Build
|
||
|
||
### 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
|
||
idf.py menuconfig
|
||
# Navigate to: MTG RFID Companion -> Wi-Fi SSID & Password
|
||
```
|
||
*(Or edit `sdkconfig.defaults.esp32` directly).*
|
||
|
||
### 3. Build & Flash
|
||
```powershell
|
||
idf.py set-target esp32
|
||
idf.py build
|
||
idf.py -p COM4 flash monitor
|
||
```
|
||
|
||
---
|
||
|
||
## 📂 LittleFS Storage Structure
|
||
|
||
```
|
||
/spiffs/
|
||
├── 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/` repository directory is compiled directly into the LittleFS partition image at build time (`littlefs_create_partition_image`).
|
||
|
||
---
|
||
|
||
## ❓ Troubleshooting
|
||
|
||
| Symptom | Cause & Solution |
|
||
| :--- | :--- |
|
||
| **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. | |