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:
- Life Total (Standard life score).
- Poison Counters (Counts 0 to 10; triggers a defeat tone at 10).
- 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:
- The screen displays:
PAIR VIA WI-FI: http://192.168.0.57 (or Hotspot: MTG-Companion @ 192.168.4.1) - Open that address in your smartphone or laptop browser.
- Tap the card sleeve to the device—its unique ID pops up in the web form automatically.
- Start typing the card name (e.g.
"Sol Ring"). Real-time Scryfall suggestions appear. - Click Pair Card. The device downloads the high-res card art and text and saves it to its permanent memory.
- 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.
💡 Pro-Tip: Changing 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, you may want your companion connected to your phone's mobile hotspot so it can fetch fresh card data from Scryfall on the fly.
Because your Companion supports Over-The-Air updates through Gitea, you never need a USB cable or computer to change Wi-Fi credentials!
Switching from Home Wi-Fi to Phone Hotspot (Before heading out):
- Update Wi-Fi in Gitea: Open your Gitea repository in your browser (even on your phone) and edit
sdkconfig.defaults:CONFIG_MTG_WIFI_SSID="MyPhoneHotspot" CONFIG_MTG_WIFI_PASS="MyHotspotPassword" - Commit to
main: Gitea Actions will automatically compile your new firmware in ~1–2 minutes. - Trigger OTA Update: While still on your home Wi-Fi, select 8. CHECK FIRMWARE OTA on your device and click to install.
- Ready for the Game Store! The device reboots with your hotspot credentials saved. When you turn on your phone's personal hotspot at the store, the companion connects automatically.
Switching Back to Home Wi-Fi:
Simply revert or update sdkconfig.defaults in Gitea back to your home Wi-Fi name, trigger the OTA update over your phone hotspot, and you're back on your home network.
🛟 Forgot to update before leaving home?
No problem:
- The "Mirror" Hotspot Trick: Set your phone's personal hotspot name and password to match your home Wi-Fi network. The companion will connect to your phone immediately, thinking it's at home!
- Offline Play Always Works: All previously scanned cards and art are stored safely in local flash memory, and the device's built-in
MTG-Companionpairing page works offline with zero internet required.
🛠️ 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 callesp_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 Wi-Fi credentials and Gitea instance details:
idf.py menuconfig
# Navigate to: "MTG RFID Companion"
# - Wi-Fi SSID & Password
# - Gitea instance base URL (e.g. https://git.example.com)
# - Gitea repository owner & repo name
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: Pushing any version tag (e.g.
git tag v1.0.1 && git push origin v1.0.1). - Environment: Official
docker://espressif/idf:v5.5container. - Artifacts: Compiles the firmware and automatically attaches
mtg-rfid-companion.bin(for wireless OTA updates) andfull-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 # Scanned UID -> Card Name mapping table
└── cards/
├── <UID>.json # Cached Scryfall card data (Mana, Oracle, Type, P/T)
└── <UID>.jpg # Cached 200x110 Baseline JPEG art crop
❓ 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. |