Files
MTGcompanion/README.md

228 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:
- 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.
---
## Features
| | |
| :--- | :--- |
| 🃏 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 |
---
## 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
---
## Wiring Guide
> **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
| Peripheral | Function | ESP32 GPIO | Notes |
| :--- | :--- | :---: | :--- |
| **Shared SPI** | SCK | **18** | SPI2_HOST (VSPI), 1 MHz modules |
| | MOSI | **23** | |
| | MISO | **19** | |
| **MFRC522** | SDA (CS) | **5** | dedicated chip select |
| | RST | **22** | hard reset |
| | 3.3V / GND | — | feed from board 3.3V rail |
| **ST7789** | CS | **15** | dedicated chip select |
| | DC (A0) | **2** | data/command |
| | RST | **4** | panel reset |
| | LED | 3.3V or 5V | optional backlight pin |
| **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
```
ESP32-WROOM-32
┌────────────────────────────┐
│ 3V3 18 ──── SCK ───────┤ MFRC522 5 ── SDA(CS) 22 ── RST
│ GND 23 ──── MOSI ──────┤ (SPI) 3V3/GND
│ 19 ──── MISO ─────┤
│ 15 ──── CS ───────┤ ST7789 2 ── DC 4 ── RST
│ 4 ─────────────────┤ (SPI)
│ 32 ── CLK ─────────┤ EC11 33 ── DT 25 ── SW/GND
│ 27 ── Din ─────────┤ WS2812B (5V + GND)
│ 14 ── SIG ─────────┤ Buzzer (GND)
└────────────────────────────┘
```
- 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.
---
## Getting Started
### 1. Toolchain
Install ESP-IDF **v5.5** (any 5.x works): https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/
Verify:
```powershell
# Windows (Espressif "ESP-IDF 5.5 PowerShell" terminal)
. C:\Espressif\Initialize-Idf.ps1 # or: . $IDF_PATH\export.ps1
idf.py --version
```
### 2. Build
```powershell
idf.py set-target esp32
idf.py build
```
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 still runs, so you can use the device
without a home network.
### 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
```
---
## 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.
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` and the card is fetched & cached.
---
## Storage Layout (`/spiffs`)
```
/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
```
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.
---
## Troubleshooting
| Symptom | Fix |
| :--- | :--- |
| 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 |
| Wi-Fi never connects | Re-run `idf.py menuconfig`, confirm you replaced the `YOUR_...` placeholders |
| Can't reach 192.168.4.1 | Ensure your phone joined `MTG-Companion`, not a cached network |
| 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.