18 KiB
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 & 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 openhttp://192.168.4.1in 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).
- Option A (Instant Captive Portal): Connect your phone to the Wi-Fi network named
-
🎴 Card Pairing Tab:
- Tap an unmapped card sleeve to the reader—its unique tag ID appears instantly in the form.
- Type the card name (e.g.
"Sol Ring"); Scryfall auto-complete displays live suggestions. - Click Pair Card. The device downloads high-resolution illustration art and official oracle text into permanent flash memory.
- Browse and manage all paired cards in your deck with illustration thumbnails and 1-click unbind.
-
📶 Wi-Fi Settings Tab:
- Click Scan Networks to see all nearby Wi-Fi networks with signal strength and security indicators.
- Select your network (or mobile hotspot) from the dropdown, enter the password, and click Connect & Save.
- 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)!
- 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:
- One-Time Setup via Phone Hotspot:
- Turn on your phone's personal hotspot.
- Connect your phone to the
MTG-Companionhotspot. - In the pop-up portal under 📶 Wi-Fi Settings, click Scan Networks, select your phone hotspot, enter the password, and click Connect & Save.
- 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-inMTG-Companionpairing 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 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 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
mainbranch. - Environment: Official
docker://espressif/idf:v5.5container. - Artifacts & Hashes: Compiles the firmware, generates SHA-256 hashes (
firmware.sha256), and automatically releasesmtg-rfid-companion.bin(for wireless OTA updates) to the Gitea repository athttps://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. |