Add Gitea OTA firmware update, dual-slot partition layout, and Gitea Actions release workflow

This commit is contained in:
2026-09-12 10:49:39 +02:00
parent 8ac465229c
commit 1d954287a2
82 changed files with 7611 additions and 395 deletions

342
README.md
View File

@@ -1,14 +1,28 @@
# MTG RFID Companion
# 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).
A dedicated tabletop companion for **Magic: The Gathering** players. Slip micro RFID/NFC stickers into your card sleeves and tap them to the device for instant card lookups, official full-color art, life tracking, dice rolling, and complete deck management.
---
## 🌟 Feature Tour & Modes
## 🃏 Part 1: Player's Guide (Non-Technical Overview)
The companion boots directly into an interactive, rotary-controlled interface designed specifically for tabletop MTG gameplay:
### What is the MTG Companion?
The MTG Companion is a handheld gadget designed to sit on your playmat during Commander (EDH), Modern, or casual kitchen-table games.
Instead of fumbling for your phone to check complex Oracle card text, searching for spindown dice, or asking *"Wait, how much commander tax am I at?"*, the Companion handles everything with a turn of a knob and a tap of a card:
* **⚡ Tap-to-Identify:** Tap any sleeved card with an NFC sticker to instantly display its official illustration, mana cost, updated Oracle text, type line, power/toughness, and current market price.
* **👑 Legendary Fanfares:** An 8-bit stinger plays whenever you cast a Mythic Rare or Legendary spell!
* **❤️ Life & Multi-Counter:** Tracks starting life (40 for Commander, 20 for Standard/Modern), **Poison Counters** (tracks to 10 lethal), and **Commander Combat Damage** (tracks to 21 lethal). Sounds an 8-bit defeat tone if a player dies.
* **🎲 Spindown D20, D6 & Fair Coin:** Roll animated dice on screen with ticking sound effects and special fanfares for Natural 20s and Natural 1s.
* **🪙 Token & Counter Spawner:** Digital counters for **Treasures, Foods, Clues, 1/1 Soldiers, 1/1 Goblins, 2/2 Zombies, 3/3 Beasts**, and **+1/+1 Counters**.
* **👑 Commander Tax & Turn Tracker:** Automatically calculates the extra {2} mana tax every time your commander returns to the command zone, alongside a table turn counter.
* **📱 Wireless Phone Pairing:** Connect your phone or laptop over your home Wi-Fi (or the device's hotspot) to easily assign card names to tags with live Scryfall search autocomplete.
* **🔄 Over-The-Air (OTA) Updates:** Update to new software versions wirelessly over Wi-Fi right from the on-device menu.
---
### How to Use at the Table
```
┌──────────────────────────────┐
@@ -20,160 +34,115 @@ The companion boots directly into an interactive, rotary-controlled interface de
│ [4] Commander Tax │
│ [5] Token & Counters │
│ [6] Deck Storage │
│ [7] Device Info │
│ [7] Device & WiFi Info │
│ [8] Check Firmware OTA │
└──────────────────────────────┘
```
### 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.
#### Controls
* **Turn the knob:** Scroll through menus, adjust life totals, change token quantities, or pick dice modes.
* **Click the knob:** Select an option, roll dice, or toggle between Life, Poison, and Commander Damage modes.
* **Long Press (Hold knob for 0.75s):** Sounds a chirp and instantly takes you back to the **Main Menu** from any screen.
* **Tap a Card Anytime:** Tapping a card sleeve immediately pulls up that card's details, regardless of which screen you are on.
---
### 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.
### Tabletop Modes
#### 1. Card Scanner & Art Viewer
Tap your card against the scanner area. The device checks its internal memory and displays:
* Full-color official art crop streamed from Scryfall.
* Mana cost, card type, power / toughness, and loyalty.
* Updated Oracle card text with rules text formatting.
* Market price in USD.
#### 2. Life & Metric Tracker
* Starts at **40 life** (Commander) or **20 life** (1v1).
* Rotate clockwise to gain life; counter-clockwise to take damage.
* **Click the knob** to cycle between:
1. **Life Total** (Standard life score).
2. **Poison Counters** (Counts 0 to 10; triggers a defeat tone at 10).
3. **Commander Combat Damage** (Counts 0 to 21; triggers lethal banner at 21).
#### 3. Spindown D20 / D6 / Fair Coin Flip
* Select **D20 Spindown**, **D6**, or **Coin Flip** and click to roll.
* An animated roll plays with sound effects.
* **Natural 20:** Plays a triumphant 8-bit Victory Fanfare!
* **Natural 1:** Sounds a critical failure buzz.
* **Coin Flip:** Clean 50/50 toss landing on **HEADS** or **TAILS**.
#### 4. Built-in Token & Counter Spawner
Never dig through your token box again for missing tokens:
* Pre-loaded with MTG staples: **Treasure, Food, Clue, Soldier (1/1), Zombie (2/2), Goblin (1/1), Beast (3/3)**, and **+1/+1 Counters**.
* Rotate to select a token, click to edit, and rotate to change quantity (persists throughout your game).
#### 5. Commander Tax & Turn Tracker
* Rotate the knob each time your commander is recast from the command zone; the screen calculates the extra mana required (e.g. `Cast 3 = +4 Mana`).
* Click to switch to the table turn counter.
#### 6. Pairing Cards from your Phone (No App Needed!)
When you scan a new card tag that hasn't been programmed yet:
1. The screen displays:
```
PAIR VIA WI-FI:
http://192.168.0.57
(or Hotspot: MTG-Companion @ 192.168.4.1)
```
2. Open that address in your smartphone or laptop browser.
3. Tap the card sleeve to the device—its unique ID pops up in the web form automatically.
4. Start typing the card name (e.g. `"Sol Ring"`). Real-time Scryfall suggestions appear.
5. Click **Pair Card**. The device downloads the high-res card art and text and saves it to its permanent memory.
6. **Deck Overview:** The web page also lets you view your entire scanned deck with card art thumbnails and unbind old cards with one click.
#### 7. Over-The-Air (OTA) Updates
* Select **8. CHECK FIRMWARE OTA** from the menu (or let it auto-check on boot when connected to Wi-Fi).
* If a new version is detected, the screen shows the new version number and asks you to click to install or turn the knob to dismiss.
* While updating, a live progress bar shows the download. When finished, the device verifies the update and restarts automatically.
---
### 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.
## 🛠️ Part 2: Technical Specifications & Build Guide
### Hardware Architecture
| Component | Part / Model | Purpose |
| :--- | :--- | :--- |
| **MCU** | ESP32-WROOM-32 (4MB Flash, no external PSRAM) | Dual-core 240 MHz processor |
| **Display** | 2.0" ST7789 IPS TFT LCD (240x320 resolution) | 16-bit RGB565 high-contrast screen |
| **RFID Reader** | MFRC522 (13.56 MHz, SPI Interface) | Reads ISO/IEC 14443A tags & stickers |
| **User Input** | EC11 Rotary Encoder with push button | Tabletop navigation & life adjustment |
| **Audio** | Passive Piezo Buzzer (GPIO 14) | LEDC PWM multi-frequency tones |
| **Tags** | NTAG213 / NTAG215 micro FPC stickers | Thin adhesive tags embedded inside sleeves |
| **Capacitor** | **2200 µF Electrolytic Capacitor** | **CRITICAL**: Placed across MFRC522 VCC/GND to absorb RF transmit current spikes |
---
### 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)**.
### GPIO Wiring & Pinout
---
### 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:
The display and RFID reader run on **two separate SPI host controllers** to prevent SPI bus contention and clock frequency mismatches:
- **ST7789 Display:** `SPI2_HOST` (VSPI) at 20 MHz.
- **MFRC522 Reader:** `SPI3_HOST` (HSPI) at 1 MHz.
| Peripheral | Function | ESP32 GPIO | Notes |
| Peripheral | Functional Pin | 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 |
| **ST7789 Display** (SPI2) | SCL (Clock) | **GPIO 18** | SPI2_HOST (VSPI) Clock |
| | SDA (MOSI) | **GPIO 23** | SPI2_HOST MOSI |
| | CS | **GPIO 21** | Dedicated Chip Select (non-strapping) |
| | DC | **GPIO 19** | Data / Command Select (non-strapping) |
| | RST | **GPIO 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 |
| **MFRC522 RFID** (SPI3) | SCK | **GPIO 16** | SPI3_HOST (HSPI) Clock |
| | MOSI | **GPIO 17** | SPI3_HOST MOSI |
| | MISO | **GPIO 13** | SPI3_HOST MISO |
| | SDA (CS) | **GPIO 26** | Dedicated Chip Select |
| | RST | **GPIO 22** | Reader Reset |
| | 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 |
| **EC11 Rotary Encoder** | CLK (A) | **GPIO 32** | Quadrature A (interrupt-driven) |
| | DT (B) | **GPIO 33** | Quadrature B |
| | SW (Button) | **GPIO 25** | Push button (active LOW) |
| **Audio** | Buzzer SIG | **GPIO 14** | LEDC PWM audio output |
### Wiring Diagram
#### Wiring Diagram
```
ESP32-WROOM-32
┌────────────────────────────┐
@@ -182,13 +151,12 @@ The display and RFID reader use **two separate SPI host controllers** to prevent
│ 13 ── MISO ────┼──┘ │
│ 26 ── SDA(CS) ─┘ │
│ 22 ── RST ─┐ │
│ 21 ── CS ──┼───────┤ ST7789 (7-pin, SPI2_HOST)
│ 21 ── CS ──┼───────┤ ST7789 Display (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)
└────────────────────────────┘
│ │
@@ -200,57 +168,93 @@ The display and RFID reader use **two separate SPI host controllers** to prevent
└────────────────────────────┘
```
> **Note on Flashing:** GPIO21 (CS) and GPIO19 (DC) are deliberate non-strapping pins. The ST7789 display can remain fully connected during `idf.py flash`.
---
### Flash Partition Layout (4MB Dual-OTA)
Configured in [`partitions.csv`](file:///c:/Users/rsben/Desktop/Rasmus/Programmering/espMTG/partitions.csv):
| Partition | Type | SubType | Offset | Size | Purpose |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `nvs` | data | nvs | `0x9000` | 16 KB | Wi-Fi credentials & system settings |
| `otadata` | data | ota | `0xd000` | 8 KB | Active OTA slot pointer & rollback state |
| `phy_init` | data | phy | `0xf000` | 4 KB | RF calibration data |
| `ota_0` | app | ota_0 | `0x10000` | 1,600 KB | Primary firmware slot |
| `ota_1` | app | ota_1 | `0x1a0000` | 1,600 KB | Secondary firmware slot |
| `storage` | data | littlefs | `0x330000` | 832 KB | LittleFS flash database (`/spiffs`) |
* **Rollback Safety:** Uses `CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y`. On first boot of a new firmware version, the app must call `esp_ota_mark_app_valid_cancel_rollback()`. If an update is corrupt or crashes on boot, the bootloader automatically reverts to the previous working slot.
---
## 💻 Software Setup & Build
### FreeRTOS Dual-Core Architecture
### 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
```
* **Core 0 (Networking, Storage & Hardware I/O):**
* `rc522_polling_task`: Continuously polls the MFRC522 reader.
* `net_task`: Manages Wi-Fi auto-reconnection and Scryfall HTTPS API downloads (`esp_http_client`).
* `httpd`: Runs the embedded web server (serves the pairing SPA and REST APIs).
* `ota_check` / `ota_update`: Queries Gitea release API and streams firmware updates into the inactive OTA bank.
* **Core 1 (User Interface & Rendering):**
* `ui_task`: Drives the LVGL display subsystem, rotary encoder state machine, and screen animations at 40 FPS without I/O stutter.
### 2. Configure Wi-Fi
Configure home Wi-Fi credentials for automatic Scryfall fetching:
---
### Software Setup & Build Commands
#### 1. Prerequisites
Install [ESP-IDF v5.x](https://docs.espressif.com/projects/esp-idf/en/v5.5/esp32/get-started/):
```powershell
. C:\Espressif\frameworks\esp-idf-v5.5\export.ps1
idf.py --version
```
#### 2. Configuration
Configure Wi-Fi credentials and Gitea instance details:
```powershell
idf.py menuconfig
# Navigate to: MTG RFID Companion -> Wi-Fi SSID & Password
# Navigate to: "MTG RFID Companion"
# - Wi-Fi SSID & Password
# - Gitea instance base URL (e.g. https://git.example.com)
# - Gitea repository owner & repo name
```
*(Or edit `sdkconfig.defaults.esp32` directly).*
### 3. Build & Flash
#### 3. Build & Flash (Initial USB Provisioning)
```powershell
idf.py set-target esp32
idf.py build
idf.py -p COM4 flash monitor
idf.py -p COM4 erase-flash flash monitor
```
---
## 📂 LittleFS Storage Structure
### Automated CI / Gitea Actions Workflow
The repository includes a Gitea Actions workflow in [`.gitea/workflows/releases.yml`](file:///c:/Users/rsben/Desktop/Rasmus/Programmering/espMTG/.gitea/workflows/releases.yml):
* **Trigger:** Pushing any version tag (e.g. `git tag v1.0.1 && git push origin v1.0.1`).
* **Environment:** Official `docker://espressif/idf:v5.5` container.
* **Artifacts:** Compiles the firmware and automatically attaches `mtg-rfid-companion.bin` (for wireless OTA updates) and `full-flash-bundle-*.tar.gz` (for USB cable flashing) directly to the Gitea release.
---
### LittleFS Storage Structure
All local card details and artwork are stored in LittleFS mounted at `/spiffs`:
```
/spiffs/
├── mappings.json UID -> Card Name JSON map
├── mappings.json # Scanned UID -> Card Name mapping table
└── cards/
├── <UID>.json Scryfall attributes (Mana, Type, Oracle, P/T)
└── <UID>.jpg Cached 200x110 Baseline JPEG art crop
├── <UID>.json # Cached Scryfall card data (Mana, Oracle, Type, 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
### ❓ 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. |
| **Device reboots when scanning a card** | Power rail sag caused by the RF transmitter. **Solder 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`. |
| **Card displays "Could not fetch card"** | Device is unable to reach Scryfall. Verify your home Wi-Fi credentials in `menuconfig` or verify card spelling on the web pairing page. |
| **Router displays device name as "espressif"** | Hostname is set to `mtg-companion`. Clear DHCP lease on your router or reboot the ESP32 after connecting. |
| **Flash fails: `Failed to connect to ESP32`** | Ensure no serial monitor is holding COM4 open (`Ctrl + ]` to exit monitor). Hold the device BOOT button while plugging into USB. |