Files
MTGcompanion/README.md
Rasmus 794ddc9b8f
All checks were successful
ESP32 Build & Release / build (push) Successful in 1m14s
fix(art): preserve uncropped card art aspect ratio using fit=inside
2026-09-12 18:57:22 +02:00

18 KiB
Raw Blame History

MTG RFID Companion 🧙‍♂️✨

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.


🃏 Part 1: Player's Guide (Non-Technical Overview)

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

                  ┌──────────────────────────────┐
                  │      ★ MTG COMPANION ★       │
                  ├──────────────────────────────┤
                  │  > [1] Card Scanner       <  │
                  │    [2] Life & Counters       │
                  │    [3] Dice & Coin           │
                  │    [4] Commander Tax         │
                  │    [5] Token & Counters      │
                  │    [6] Deck Storage          │
                  │    [7] Device & WiFi Info    │
                  │    [8] Check Firmware OTA    │
                  └──────────────────────────────┘

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.

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 & On-Device Wi-Fi Setup (No App Needed!)

The device runs a built-in web portal that works on any smartphone, tablet, or laptop:

  • Connecting to the Portal:

    • Option A (Instant Captive Portal): Connect your phone to the Wi-Fi network named MTG-Companion (no password). Your phone will automatically pop up the setup screen! (Or open http://192.168.4.1 in your browser).
    • Option B (Home Wi-Fi): If connected to your home network, simply browse to the device's IP shown on screen (e.g. http://192.168.0.57).
  • 🎴 Card Pairing Tab:

    1. Tap an unmapped card sleeve to the reader—its unique tag ID appears instantly in the form.
    2. Type the card name (e.g. "Sol Ring"); Scryfall auto-complete displays live suggestions.
    3. Click Pair Card. The device downloads high-resolution illustration art and official oracle text into permanent flash memory.
    4. Browse and manage all paired cards in your deck with illustration thumbnails and 1-click unbind.
  • 📶 Wi-Fi Settings Tab:

    1. Click Scan Networks to see all nearby Wi-Fi networks with signal strength and security indicators.
    2. Select your network (or mobile hotspot) from the dropdown, enter the password, and click Connect & Save.
    3. Multi-Network Memory: The device remembers up to 5 Wi-Fi profiles in permanent flash. When you turn the companion on, it automatically scans and connects to whichever saved network is in range (e.g. seamlessly switching between Home Wi-Fi and your Phone's Mobile Hotspot)!
    4. Forget unwanted networks anytime with one click.

7. Over-The-Air (OTA) Updates

The companion automatically fetches firmware releases directly from:
https://git.rasmusbendtsen.dk/rasmus/MTGcompanion/releases

  • Automatic Hash Verification: Whenever you push code to main, Gitea Actions builds the new firmware and computes its cryptographic SHA-256 hash.
  • Instant Detection: Select 8. CHECK FIRMWARE OTA from the menu (or let it auto-check on boot when connected to Wi-Fi). The device compares its currently running image hash against the latest build on your server:
    • If a new build is available, the screen shows the new target hash and invites you to click the knob to install.
    • If the hashes match, it confirms the firmware is up to date and skips unnecessary flashing.
  • Safe Dual-Slot Flashing: While updating, a live progress bar streams the binary over HTTPS into the inactive partition slot. The device validates the new image and reboots automatically with rollback protection.

💡 Switching Wi-Fi Networks on the Go (Home Wi-Fi ↔ Phone Hotspot)

When heading to your Local Game Store (LGS), a friend's house, or a tournament, switching between your home Wi-Fi and phone hotspot is completely effortless:

  1. One-Time Setup via Phone Hotspot:
    • Turn on your phone's personal hotspot.
    • Connect your phone to the MTG-Companion hotspot.
    • In the pop-up portal under 📶 Wi-Fi Settings, click Scan Networks, select your phone hotspot, enter the password, and click Connect & Save.
  2. Automatic Hand-off:
    • The device stores both your Home Wi-Fi and Phone Hotspot.
    • At home: it automatically joins Home Wi-Fi.
    • At the game store: turn on your phone's hotspot and turn on the companion—it locks onto your phone automatically!

🛟 Offline Play Always Works:
All previously scanned cards and art are stored in local flash memory, and the device's built-in MTG-Companion pairing portal works offline with zero internet required.


🔋 Battery Life & Power Optimization (850 mAh LiPo)

The MTG Companion is engineered for long tabletop sessions powered by a standard 850 mAh 3.7V LiPo cell (~3.15 Wh).

Expected Runtime

Play Style Average Current Expected Battery Life Notes
Typical Commander / FNM Session ~60 – 70 mA 10 – 12 hours Screen ON, tracking life, token spawning, occasional card scans, Wi-Fi modem sleep active
Offline Play (No Wi-Fi) ~50 – 60 mA 12 – 14 hours Wi-Fi idle/disconnected, all card art and Oracle data streamed from local LittleFS flash
Heavy Wi-Fi Setup / Card Pairing ~130 – 160 mA 4 – 5.5 hours Continuous SoftAP web portal active, bulk Scryfall art downloads, OTA updates

Firmware Power-Saving Features

  • 💤 Automatic Wi-Fi Modem Sleep (WIFI_PS_MIN_MODEM):
    When connected to Wi-Fi but not actively transferring data (e.g. while tracking life or waiting for card taps), the Wi-Fi radio enters modem sleep between beacon intervals. This cuts ESP32 power consumption by ~30–40 mA. The radio automatically boosts to full power (WIFI_PS_NONE) during active Scryfall downloads, OTA flashing, or SoftAP pairing mode.
  • 📡 125 ms RFID Carrier Duty Cycling:
    The MFRC522 polling loop runs an RF carrier burst sweep every 125 ms instead of continuously pumping 13.56 MHz RF power. This reduces average RFID reader draw from 20 mA down to ~5 mA without any perceptible tag detection latency.

⚠️ Hardware Tip: LDO Voltage Regulator Selection

To extract the full capacity of a single-cell LiPo battery (discharging from 4.2V down to ~3.4V):

  • Recommended LDOs: Use ultra-low-dropout regulators such as ME6211, AP2112K, or XC6206 (dropout voltage ~100–250 mV). These maintain a solid 3.3V rail down to ~3.45V battery level, utilizing >90% of the battery's energy.
  • Avoid AMS1117-3.3: The common AMS1117 has a large 1.1V–1.3V dropout. When powered from a 3.7V LiPo, it browns out before utilizing even 20% of the battery capacity.

🛠️ 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

GPIO Wiring & Pinout

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 Functional Pin ESP32 GPIO Notes
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 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) 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

        ESP32-WROOM-32
  ┌────────────────────────────┐
  │ 3V3  16 ── SCK ────────┐   │
  │ GND  17 ── MOSI ────┐  │   │
  │       13 ── MISO ────┼──┘   │
  │       26 ── SDA(CS) ─┘      │
  │       22 ── RST ─┐          │
  │       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)
  │       14 ── SIG ────────┤ Piezo Buzzer (GPIO 14)
  └────────────────────────────┘
        │   │
      [2200 µF Cap]
        │   │
  ┌─────┴───┴──────────────────┐
  │ 3V3  GND                   │
  │ MFRC522 RFID Module (SPI3) │
  └────────────────────────────┘

Flash Partition Layout (4MB Dual-OTA)

Configured in 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.

FreeRTOS Dual-Core Architecture

  • 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.

Software Setup & Build Commands

1. Prerequisites

Install ESP-IDF v5.x:

. C:\Espressif\frameworks\esp-idf-v5.5\export.ps1
idf.py --version

2. Configuration

Configure initial Wi-Fi credentials (or use on-device Wi-Fi setup):

idf.py menuconfig
# Navigate to: "MTG RFID Companion"
#   - Wi-Fi SSID & Password (Optional - can also be configured via captive portal)

(The firmware is pre-configured to check updates directly from https://git.rasmusbendtsen.dk/rasmus/MTGcompanion/releases)

3. Build & Flash (Initial USB Provisioning)

idf.py set-target esp32
idf.py build
idf.py -p COM4 erase-flash flash monitor

Automated CI / Gitea Actions Workflow

The repository includes a Gitea Actions workflow in .gitea/workflows/releases.yml:

  • Trigger: Pushes to the main branch.
  • Environment: Official docker://espressif/idf:v5.5 container.
  • Artifacts & Hashes: Compiles the firmware, generates SHA-256 hashes (firmware.sha256), and automatically releases mtg-rfid-companion.bin (for wireless OTA updates) to the Gitea repository at https://git.rasmusbendtsen.dk/rasmus/MTGcompanion/releases.
  • Hash-Based Verification: Embeds the Git commit SHA and binary SHA-256 hash in the release metadata so the ESP32 can verify whether an update is available without relying on manual version bumps.

LittleFS Storage Structure

All local card details and artwork are stored in LittleFS mounted at /spiffs:

/spiffs/
├── mappings.json          # Scanned UID -> Card Name mapping table
└── cards/
    ├── <UID>.json         # Cached Scryfall card data (Mana, Oracle, Type, P/T)
    └── <UID>.jpg          # Cached Baseline JPEG art crop (aspect-ratio preserved)

❓ Troubleshooting

Symptom Cause & Solution
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.