diff --git a/README.md b/README.md new file mode 100644 index 0000000..731b7d5 --- /dev/null +++ b/README.md @@ -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: 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) +โ”‚ โ”œโ”€โ”€ .json Scryfall metadata cache +โ”‚ โ””โ”€โ”€ .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. \ No newline at end of file diff --git a/main/wifi_manager.c b/main/wifi_manager.c index 71090c1..bf326f7 100644 --- a/main/wifi_manager.c +++ b/main/wifi_manager.c @@ -85,7 +85,9 @@ esp_err_t wifi_manager_start(void) const char *ssid = CONFIG_MTG_WIFI_SSID; 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)"); return ESP_OK; } diff --git a/sdkconfig b/sdkconfig index 38b8b15..263a4ab 100644 --- a/sdkconfig +++ b/sdkconfig @@ -434,8 +434,8 @@ CONFIG_PARTITION_TABLE_MD5=y # # MTG RFID Companion # -CONFIG_MTG_WIFI_SSID="" -CONFIG_MTG_WIFI_PASS="" +CONFIG_MTG_WIFI_SSID="YOUR_SSID_HERE" +CONFIG_MTG_WIFI_PASS="YOUR_PASSWORD_HERE" CONFIG_MTG_SCAN_PREFETCH=y # end of MTG RFID Companion diff --git a/sdkconfig.defaults.esp32 b/sdkconfig.defaults.esp32 new file mode 100644 index 0000000..7fd107f --- /dev/null +++ b/sdkconfig.defaults.esp32 @@ -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" \ No newline at end of file