docs: add README hardware guide/wiring, sdkconfig.defaults.esp32 Wi-Fi preset, guard placeholder SSID from STA retry loop

This commit is contained in:
2026-09-09 19:19:11 +02:00
parent 01c24d17b6
commit 7599f0e6b2
4 changed files with 245 additions and 3 deletions

228
README.md Normal file
View File

@@ -0,0 +1,228 @@
# 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.

View File

@@ -85,7 +85,9 @@ esp_err_t wifi_manager_start(void)
const char *ssid = CONFIG_MTG_WIFI_SSID; const char *ssid = CONFIG_MTG_WIFI_SSID;
const char *pass = CONFIG_MTG_WIFI_PASS; const char *pass = CONFIG_MTG_WIFI_PASS;
if (strlen(ssid) == 0) { /* Empty SSID, or an untouched sdkconfig.defaults.esp32 placeholder:
* keep station mode disabled so we don't hammer a bogus AP at boot. */
if (strlen(ssid) == 0 || strstr(ssid, "YOUR_") != NULL) {
ESP_LOGW(TAG, "no SSID configured - Wi-Fi station skipped (set CONFIG_MTG_WIFI_SSID)"); ESP_LOGW(TAG, "no SSID configured - Wi-Fi station skipped (set CONFIG_MTG_WIFI_SSID)");
return ESP_OK; return ESP_OK;
} }

View File

@@ -434,8 +434,8 @@ CONFIG_PARTITION_TABLE_MD5=y
# #
# MTG RFID Companion # MTG RFID Companion
# #
CONFIG_MTG_WIFI_SSID="" CONFIG_MTG_WIFI_SSID="YOUR_SSID_HERE"
CONFIG_MTG_WIFI_PASS="" CONFIG_MTG_WIFI_PASS="YOUR_PASSWORD_HERE"
CONFIG_MTG_SCAN_PREFETCH=y CONFIG_MTG_SCAN_PREFETCH=y
# end of MTG RFID Companion # end of MTG RFID Companion

12
sdkconfig.defaults.esp32 Normal file
View File

@@ -0,0 +1,12 @@
# Target-specific preset for ESP32: station-mode Wi-Fi credentials.
#
# Fill in your network below and the device will auto-connect on boot.
# Leave the placeholder values untouched and station mode stays disabled
# (the SoftAP web-pairing server always runs either way).
#
# Precedence note: values already present in the generated `sdkconfig` win
# over these defaults. To apply changes here on an existing build, either run
# `idf.py menuconfig` ( -> "MTG RFID Companion") or remove `sdkconfig` and
# rebuild.
CONFIG_MTG_WIFI_SSID="YOUR_SSID_HERE"
CONFIG_MTG_WIFI_PASS="YOUR_PASSWORD_HERE"