Initial commit: Minecraft Orb project
ESP32-C3 firmware for interactive treasure hunt device with RFID, OLED display, LED effects, buzzer, and touch input. Includes 3D printable STL files for the enclosure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
28c36c51f6
36 changed files with 2733 additions and 0 deletions
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# PlatformIO
|
||||
firmware/.pio/
|
||||
|
||||
# Secrets
|
||||
firmware/include/secrets.h
|
||||
|
||||
# Generated gcode files
|
||||
*.gcode
|
||||
|
||||
# Claude Code
|
||||
.claude/
|
||||
tasks/
|
||||
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_1.stl
Normal file
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_1.stl
Normal file
Binary file not shown.
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_2.stl
Normal file
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_2.stl
Normal file
Binary file not shown.
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_3.stl
Normal file
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_3.stl
Normal file
Binary file not shown.
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_4.stl
Normal file
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_4.stl
Normal file
Binary file not shown.
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_5.stl
Normal file
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_5.stl
Normal file
Binary file not shown.
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_6.stl
Normal file
BIN
STLs/Fillers/Fillers_with_ridge/Minecraft Ore - filler_6.stl
Normal file
Binary file not shown.
Binary file not shown.
BIN
STLs/Fillers/Minecraft Ore - Filler_1.stl
Normal file
BIN
STLs/Fillers/Minecraft Ore - Filler_1.stl
Normal file
Binary file not shown.
BIN
STLs/Fillers/Minecraft Ore - Filler_2.stl
Normal file
BIN
STLs/Fillers/Minecraft Ore - Filler_2.stl
Normal file
Binary file not shown.
BIN
STLs/Fillers/Minecraft Ore - Filler_3.stl
Normal file
BIN
STLs/Fillers/Minecraft Ore - Filler_3.stl
Normal file
Binary file not shown.
BIN
STLs/Fillers/Minecraft Ore - Filler_4.stl
Normal file
BIN
STLs/Fillers/Minecraft Ore - Filler_4.stl
Normal file
Binary file not shown.
BIN
STLs/Fillers/Minecraft Ore - Filler_5.stl
Normal file
BIN
STLs/Fillers/Minecraft Ore - Filler_5.stl
Normal file
Binary file not shown.
BIN
STLs/Fillers/Minecraft Ore - Filler_6.stl
Normal file
BIN
STLs/Fillers/Minecraft Ore - Filler_6.stl
Normal file
Binary file not shown.
BIN
STLs/Images/Model_closed.png
Normal file
BIN
STLs/Images/Model_closed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 182 KiB |
BIN
STLs/Images/Model_exploded.png
Normal file
BIN
STLs/Images/Model_exploded.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 160 KiB |
BIN
STLs/Images/photo_1.jpg
Normal file
BIN
STLs/Images/photo_1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
STLs/Images/photo_2.jpg
Normal file
BIN
STLs/Images/photo_2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
BIN
STLs/Minecraft Ore - Plain - Minecraft Ore - Plain.3mf
Normal file
BIN
STLs/Minecraft Ore - Plain - Minecraft Ore - Plain.3mf
Normal file
Binary file not shown.
5
STLs/README.md
Normal file
5
STLs/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Minecraft Orb - STL Files
|
||||
|
||||
3D printable files for the Minecraft Orb project.
|
||||
|
||||
The model is available on Printables: https://www.printables.com/model/1663249-a-minecraft-orb
|
||||
BIN
STLs/Structure/Minecraft Ore - Mounting Frame.stl
Normal file
BIN
STLs/Structure/Minecraft Ore - Mounting Frame.stl
Normal file
Binary file not shown.
BIN
STLs/Structure/Minecraft Ore - Support for PCB.stl
Normal file
BIN
STLs/Structure/Minecraft Ore - Support for PCB.stl
Normal file
Binary file not shown.
BIN
STLs/Structure/Minecraft Ore - Top surface.stl
Normal file
BIN
STLs/Structure/Minecraft Ore - Top surface.stl
Normal file
Binary file not shown.
BIN
STLs/Structure/Minecraft Ore - Wall.stl
Normal file
BIN
STLs/Structure/Minecraft Ore - Wall.stl
Normal file
Binary file not shown.
2
firmware/.gitignore
vendored
Normal file
2
firmware/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.pio/
|
||||
include/secrets.h
|
||||
88
firmware/CLAUDE.md
Normal file
88
firmware/CLAUDE.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
ESP32-C3 SuperMini firmware for the "Minecraft Orb" — an interactive treasure hunt device with RFID card reading, OLED display, LED effects, buzzer audio, and touch input. Built with Arduino framework via PlatformIO. Quest clues are stored directly on MIFARE Classic 1K cards and read/displayed by the device.
|
||||
|
||||
## Build & Upload Commands
|
||||
|
||||
```bash
|
||||
pio run # Build firmware
|
||||
pio run --target upload # Upload to board via USB-C
|
||||
pio device monitor # Serial monitor (115200 baud)
|
||||
pio run --target upload && pio device monitor # Upload + monitor
|
||||
```
|
||||
|
||||
## Hardware Configuration
|
||||
|
||||
- **MCU**: ESP32-C3 SuperMini (RISC-V), USB CDC serial on boot
|
||||
- **Display**: SSD1306 128x64 OLED via I2C (GPIO2=SDA, GPIO3=SCL)
|
||||
- **RFID**: RC522 via SPI (GPIO7=SCK, GPIO8=MOSI, GPIO9=MISO, GPIO6=CS, GPIO10=RST)
|
||||
- **LED**: White LED on GPIO4 (PWM breathing effect)
|
||||
- **Buzzer**: Piezo on GPIO0
|
||||
- **Touch**: TTP223 sensor on GPIO5 (must be GPIO0-5 for deep sleep wake)
|
||||
|
||||
Pin definitions are in `include/pins.h`. Full wiring diagram in `pinout.svg`.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Source Files
|
||||
|
||||
- **`src/main.cpp`** (~1500 lines) — Main firmware: hardware init, event loop, display rendering, serial command handler, RFID event processing, Morse code, touch input puzzle, authentication state machine, LED breathing
|
||||
- **`src/cards.cpp`** — Card database: NVS storage, MIFARE card read/write operations (sectors 1-4, blocks 4-18)
|
||||
- **`include/cards.h`** — Card struct and database interface
|
||||
- **`include/config.h`** — WiFi credentials, NVS namespace config, power management timeouts
|
||||
- **`include/pins.h`** — GPIO pin assignments
|
||||
|
||||
### Core Event Loop
|
||||
|
||||
`setup()` initializes all hardware then enters auth mode. `loop()` calls `checkSerial()`, `checkRFID()`, `checkTouch()`, `breatheLED()`, and `checkPowerSave()` each cycle. State is tracked via globals: `authMode`, `programmingMode`, `waitingForCard`, `authCount`.
|
||||
|
||||
### Operating Modes
|
||||
|
||||
1. **Auth Mode** (startup default): Requires 5 players to scan AUTH cards in sequence (IDs 1-5 in order)
|
||||
2. **Programming Mode**: Entered via `PROG` serial command. Write/read quest data to MIFARE cards
|
||||
3. **Normal Mode**: Scan cards to display clues; handles special command cards (MORSE, INPUT, AUTH)
|
||||
|
||||
### MIFARE Card Data Layout
|
||||
|
||||
Quest data is written directly to MIFARE Classic 1K cards (not ESP32 flash):
|
||||
- Sector 1 (blocks 4-5): Card name (32 bytes)
|
||||
- Sectors 2-4 (blocks 8-18): Clue text (144 bytes)
|
||||
- Authentication uses key A = `0xFFFFFFFFFFFF`
|
||||
|
||||
### Serial Commands (Programming Mode)
|
||||
|
||||
`PROG` — enter programming mode | `EXIT` — leave | `SCAN` — read card | `ADD name|clue` — write quest to card | `HELP` — show commands
|
||||
|
||||
### Special Card Types
|
||||
|
||||
Cards with specific clue prefixes trigger special behaviors:
|
||||
- `MORSE:` — Plays clue as Morse code via LED + buzzer
|
||||
- `INPUT:` — Touch-based number input puzzle (answer 1-10)
|
||||
- `AUTH:` — Multi-player authentication card (format: `name|id|text`)
|
||||
|
||||
## Key Libraries
|
||||
|
||||
- Adafruit SSD1306 v2.5.7 + GFX v1.11.5 (display)
|
||||
- MFRC522 v1.4.10 (RFID)
|
||||
- Preferences (ESP32 NVS for persistent storage)
|
||||
|
||||
## Power Management (Battery Operation)
|
||||
|
||||
Powered via ESP32-C3 SuperMini expansion board with 3.7V LiPo battery (USB charging).
|
||||
|
||||
- **Display auto-off**: 3 min inactivity (`DISPLAY_TIMEOUT_MS` in `config.h`)
|
||||
- **Deep sleep**: 10 min inactivity (`SLEEP_TIMEOUT_MS` in `config.h`), ~µA draw
|
||||
- **Wake**: Touch sensor press → full reboot through `setup()`
|
||||
- Any interaction (card scan, touch, serial) resets inactivity timer
|
||||
- ESP32-C3 deep sleep wake only supports GPIO0-5 — touch sensor must stay on one of these pins
|
||||
|
||||
## Notes
|
||||
|
||||
- No unit tests — embedded firmware tested via serial + hardware
|
||||
- `main.cpp` is large; consider splitting display, audio, and RFID logic into separate modules for major changes
|
||||
- WiFi code exists but is currently disabled
|
||||
- See `PLANNING.md` for full architectural documentation and future plans
|
||||
182
firmware/PLANNING.md
Normal file
182
firmware/PLANNING.md
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
# Minecraft Orb - Firmware
|
||||
|
||||
## Project Overview
|
||||
|
||||
ESP32-C3 SuperMini-based RFID reader with OLED display, designed for the Minecraft Orb project (Giulio Hunt 2025).
|
||||
|
||||
## Hardware
|
||||
|
||||
### Microcontroller
|
||||
- **Board**: ESP32-C3 SuperMini
|
||||
- **Form factor**: Two rows of 8 pins, USB-C connector
|
||||
- **Framework**: Arduino (via PlatformIO)
|
||||
- **Power**: ESP32-C3 SuperMini Expansion Board with 3.7V LiPo battery (USB charging via TP4056)
|
||||
|
||||
### Connected Components
|
||||
|
||||
| Component | Interface | Description |
|
||||
|-----------|-----------|-------------|
|
||||
| SSD1306 OLED | I2C | 128x64 pixel display |
|
||||
| RC522 | SPI | RFID/NFC reader (13.56 MHz) |
|
||||
| White LED | GPIO | Status indicator (100 ohm resistor) |
|
||||
| Piezo Buzzer | GPIO/PWM | Audio feedback (passive) |
|
||||
| TTP223 | GPIO | Capacitive touch sensor |
|
||||
|
||||
### Pin Configuration
|
||||
|
||||
```
|
||||
ESP32-C3 SuperMini Pinout (Top View, USB-C facing down)
|
||||
|
||||
Left Side Right Side
|
||||
--------- ----------
|
||||
GPIO5 <- Touch 5V
|
||||
GPIO6 <- RC522 SDA GND
|
||||
GPIO7 <- RC522 SCK 3V3
|
||||
GPIO8 <- RC522 MOSI GPIO4 <- LED
|
||||
GPIO9 <- RC522 MISO GPIO3 <- LCD SCL
|
||||
GPIO10 <- RC522 RST GPIO2 <- LCD SDA
|
||||
GPIO20 (FREE) GPIO1 (FREE)
|
||||
GPIO21 (FREE) GPIO0 <- Buzzer
|
||||
```
|
||||
|
||||
### Detailed Wiring
|
||||
|
||||
| Component | Component Pin | GPIO | Notes |
|
||||
|-----------|---------------|------|-------|
|
||||
| SSD1306 | SDA | GPIO 2 | I2C Data |
|
||||
| SSD1306 | SCL | GPIO 3 | I2C Clock |
|
||||
| SSD1306 | VCC | 3V3 | 3.3V power |
|
||||
| SSD1306 | GND | GND | Ground |
|
||||
| RC522 | SDA (CS) | GPIO 6 | Chip Select |
|
||||
| RC522 | SCK | GPIO 7 | SPI Clock |
|
||||
| RC522 | MOSI | GPIO 8 | SPI Data Out |
|
||||
| RC522 | MISO | GPIO 9 | SPI Data In |
|
||||
| RC522 | RST | GPIO 10 | Reset |
|
||||
| RC522 | VCC | 3V3 | 3.3V power |
|
||||
| RC522 | GND | GND | Ground |
|
||||
| White LED | Anode (+) | GPIO 4 | Via 100 ohm resistor |
|
||||
| White LED | Cathode (-) | GND | Ground |
|
||||
| Buzzer | + (Signal) | GPIO 0 | Passive piezo buzzer |
|
||||
| Buzzer | - (GND) | GND | Ground |
|
||||
| TTP223 | I/O (Signal) | GPIO 5 | Touch output (HIGH when touched, GPIO0-5 required for deep sleep wake) |
|
||||
| TTP223 | VCC | 3V3 | 3.3V power |
|
||||
| TTP223 | GND | GND | Ground |
|
||||
|
||||
## Software Architecture
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
firmware/
|
||||
├── platformio.ini # PlatformIO configuration
|
||||
├── PLANNING.md # This file
|
||||
├── include/
|
||||
│ ├── pins.h # Pin definitions
|
||||
│ ├── config.h # WiFi credentials & constants
|
||||
│ └── cards.h # Card database structures
|
||||
└── src/
|
||||
├── main.cpp # Main firmware
|
||||
└── cards.cpp # Card database (NVS storage)
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `Adafruit SSD1306` - OLED display driver
|
||||
- `Adafruit GFX Library` - Graphics primitives
|
||||
- `MFRC522` - RC522 RFID reader driver
|
||||
- `WiFi` - ESP32 WiFi (built-in, currently disabled)
|
||||
- `Preferences` - ESP32 NVS storage (built-in)
|
||||
|
||||
### Operating Modes
|
||||
|
||||
1. **Auth Mode** (startup): 5 players must scan AUTH cards in ascending ID order
|
||||
2. **Programming Mode**: Write/read quest data to MIFARE cards via serial
|
||||
3. **Normal Mode**: Scan cards to display clues; handles MORSE, INPUT, AUTH command cards
|
||||
|
||||
### Event Loop
|
||||
|
||||
`setup()` initializes all hardware then enters auth mode. `loop()` calls:
|
||||
1. `checkSerial()` - Process serial commands
|
||||
2. `breatheLED()` - LED animation (normal mode only)
|
||||
3. `checkRFID()` - Poll for RFID cards
|
||||
4. `checkTouch()` - Poll touch sensor for activity
|
||||
5. `checkPowerSave()` - Display timeout and deep sleep
|
||||
|
||||
## Building & Flashing
|
||||
|
||||
### Prerequisites
|
||||
- PlatformIO CLI or VS Code with PlatformIO extension
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
pio run # Build firmware
|
||||
pio run --target upload # Upload to board
|
||||
pio device monitor # Monitor serial output
|
||||
```
|
||||
|
||||
## Serial Programming Mode
|
||||
|
||||
Connect via serial (115200 baud) and use these commands:
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `PROG` | Enter programming mode |
|
||||
| `EXIT` | Exit programming mode |
|
||||
| `SCAN` | Scan card and read contents |
|
||||
| `ADD <name>\|<clue>` | Write quest data to card |
|
||||
| `HELP` | Show help |
|
||||
|
||||
Quest data is written directly to MIFARE Classic 1K cards (not stored in ESP32 flash).
|
||||
|
||||
### Card Data Layout
|
||||
|
||||
| Sector | Blocks | Content |
|
||||
|--------|--------|---------|
|
||||
| 1 | 4-5 | Name (32 bytes) |
|
||||
| 2 | 8-10 | Clue part 1 (48 bytes) |
|
||||
| 3 | 12-14 | Clue part 2 (48 bytes) |
|
||||
| 4 | 16-18 | Clue part 3 (48 bytes) |
|
||||
|
||||
Total capacity: 32 byte name + 144 byte clue
|
||||
|
||||
### Command Cards
|
||||
|
||||
Special card names trigger commands instead of displaying clues:
|
||||
|
||||
| Name | Clue Format | Description |
|
||||
|------|-------------|-------------|
|
||||
| `MORSE` | `<message>` | Play clue as Morse code |
|
||||
| `INPUT` | `<answer>\|<message>` | Touch input puzzle (1-10) |
|
||||
|
||||
### Example Usage
|
||||
|
||||
```
|
||||
PROG
|
||||
ADD Quest 1|Go to the old oak tree near the lake
|
||||
<scan card>
|
||||
EXIT
|
||||
```
|
||||
|
||||
## Power Management (Battery Operation)
|
||||
|
||||
Powered via ESP32-C3 SuperMini expansion board with 3.7V LiPo battery (USB charging via TP4056).
|
||||
|
||||
| Feature | Timeout | Behavior |
|
||||
|---------|---------|----------|
|
||||
| Display auto-off | 3 min inactivity | OLED turned off, touch/card/serial wakes it |
|
||||
| Deep sleep | 10 min inactivity | All peripherals off, ~uA draw |
|
||||
| Wake from sleep | Touch sensor press | Full reboot through setup() |
|
||||
|
||||
Timeouts configured in `include/config.h` (`DISPLAY_TIMEOUT_MS`, `SLEEP_TIMEOUT_MS`).
|
||||
|
||||
Touch sensor must be on GPIO0-5 for deep sleep wake support (ESP32-C3 limitation).
|
||||
|
||||
## Future Development
|
||||
|
||||
- [x] Write quest data directly to MIFARE cards
|
||||
- [x] WiFi connectivity
|
||||
- [x] Command cards (MORSE, INPUT)
|
||||
- [x] Deep sleep for battery operation
|
||||
- [ ] Custom animations on display
|
||||
- [ ] OTA firmware updates
|
||||
149
firmware/README.md
Normal file
149
firmware/README.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# Minecraft Orb - Firmware
|
||||
|
||||
An ESP32-C3 powered interactive treasure hunt device. Players scan RFID cards to reveal clues, solve Morse code puzzles, and complete touch-based challenges. Quest data is stored directly on MIFARE Classic 1K cards.
|
||||
|
||||
Built for the **Giulio Hunt 2025** project.
|
||||
|
||||
## Hardware
|
||||
|
||||
| Component | Model | Interface | GPIO |
|
||||
|-----------|-------|-----------|------|
|
||||
| Microcontroller | ESP32-C3 SuperMini | - | - |
|
||||
| Display | SSD1306 128x64 OLED | I2C | SDA=2, SCL=3 |
|
||||
| RFID Reader | RC522 (13.56 MHz) | SPI | SCK=7, MOSI=8, MISO=9, CS=6, RST=10 |
|
||||
| LED | White (100 ohm resistor) | PWM | 4 |
|
||||
| Buzzer | Passive piezo | Tone | 0 |
|
||||
| Touch Sensor | TTP223 capacitive | Digital | 5 |
|
||||
|
||||
Power is supplied via the **ESP32-C3 SuperMini Expansion Board**, which supports 3.7V LiPo battery with USB charging.
|
||||
|
||||
```
|
||||
ESP32-C3 SuperMini Pinout (Top View, USB-C facing down)
|
||||
|
||||
Left Side Right Side
|
||||
--------- ----------
|
||||
GPIO5 <- Touch 5V
|
||||
GPIO6 <- RC522 SDA GND
|
||||
GPIO7 <- RC522 SCK 3V3
|
||||
GPIO8 <- RC522 MOSI GPIO4 <- LED
|
||||
GPIO9 <- RC522 MISO GPIO3 <- LCD SCL
|
||||
GPIO10 <- RC522 RST GPIO2 <- LCD SDA
|
||||
GPIO20 (FREE) GPIO1 (FREE)
|
||||
GPIO21 (FREE) GPIO0 <- Buzzer
|
||||
```
|
||||
|
||||
## Building & Uploading
|
||||
|
||||
Requires [PlatformIO](https://platformio.org/).
|
||||
|
||||
```bash
|
||||
pio run # Build
|
||||
pio run --target upload # Upload via USB-C
|
||||
pio device monitor # Serial monitor (115200 baud)
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Startup: Authentication
|
||||
|
||||
On power-up, the device requires **5 players to scan their AUTH cards in ascending order** (IDs 1 through 5). Each card must have higher ID than the previous one. An out-of-order scan resets the sequence.
|
||||
|
||||
AUTH card clue format: `PlayerName|ID|WelcomeMessage`
|
||||
|
||||
Once all 5 authenticate successfully, the quest begins.
|
||||
|
||||
### Quest Mode
|
||||
|
||||
Players scan RFID cards to reveal clues on the OLED display. There are three types of quest cards:
|
||||
|
||||
**Regular cards** — Display the card name and clue text on screen.
|
||||
|
||||
**MORSE cards** — The clue is played as Morse code through the buzzer and LED. Dots and dashes are shown on the display but the text itself stays hidden.
|
||||
|
||||
**INPUT cards** — A touch-based number puzzle. Players press the touch sensor N times to enter a code (1-10). The answer auto-submits after 3 seconds of no input. Correct answers reveal a message; wrong answers show an error.
|
||||
|
||||
Card clue format for INPUT: `answer|message` (e.g., `5|The treasure is under the bridge`)
|
||||
|
||||
### Programming Cards
|
||||
|
||||
Connect via serial (115200 baud) and enter programming mode:
|
||||
|
||||
```
|
||||
> PROG
|
||||
>>> Entered programming mode
|
||||
|
||||
> ADD Quest 1|Go to the old oak tree near the lake
|
||||
Scan card to add as 'Quest 1'...
|
||||
<scan card>
|
||||
OK: Card programmed as 'Quest 1'
|
||||
|
||||
> SCAN
|
||||
<scan card>
|
||||
Card UID: 12:AB:34:CD
|
||||
Name: Quest 1
|
||||
Clue: Go to the old oak tree near the lake
|
||||
|
||||
> EXIT
|
||||
```
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `PROG` | Enter programming mode |
|
||||
| `EXIT` | Leave programming mode |
|
||||
| `SCAN` | Read card UID and contents |
|
||||
| `ADD name\|clue` | Write quest data to next scanned card |
|
||||
| `HELP` | Show commands |
|
||||
|
||||
### Card Data Layout (MIFARE Classic 1K)
|
||||
|
||||
| Sector | Blocks | Content | Size |
|
||||
|--------|--------|---------|------|
|
||||
| 1 | 4-5 | Card name | 32 bytes |
|
||||
| 2 | 8-10 | Clue part 1 | 48 bytes |
|
||||
| 3 | 12-14 | Clue part 2 | 48 bytes |
|
||||
| 4 | 16-18 | Clue part 3 | 48 bytes |
|
||||
|
||||
Total capacity: 32 bytes name + 144 bytes clue. Uses default MIFARE key A (0xFFFFFFFFFFFF).
|
||||
|
||||
## Battery Operation
|
||||
|
||||
The device supports battery power via the ESP32-C3 SuperMini Expansion Board with a 3.7V LiPo cell. USB charging is handled by the board's TP4056 circuit (green LED = charging, LED off = full).
|
||||
|
||||
Power saving features:
|
||||
|
||||
| Event | Timeout | Behavior |
|
||||
|-------|---------|----------|
|
||||
| Display auto-off | 3 min inactivity | OLED turned off, any interaction wakes it |
|
||||
| Deep sleep | 10 min inactivity | All peripherals powered down (~uA draw) |
|
||||
| Wake | Touch sensor press | Full reboot through startup sequence |
|
||||
|
||||
Timeouts are configurable in `include/config.h` (`DISPLAY_TIMEOUT_MS`, `SLEEP_TIMEOUT_MS`).
|
||||
|
||||
The touch sensor must be on GPIO 0-5 for deep sleep wake to work (ESP32-C3 hardware limitation).
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
firmware/
|
||||
├── platformio.ini # Build configuration
|
||||
├── include/
|
||||
│ ├── pins.h # GPIO pin assignments
|
||||
│ ├── config.h # Timeouts, card limits, NVS keys
|
||||
│ └── cards.h # Card data structures
|
||||
└── src/
|
||||
├── main.cpp # Firmware: init, event loop, display, RFID, audio, power
|
||||
└── cards.cpp # NVS card database, MIFARE read/write operations
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| Adafruit SSD1306 | 2.5.x | OLED display driver |
|
||||
| Adafruit GFX | 1.12.x | Graphics primitives |
|
||||
| MFRC522 | 1.4.x | RC522 RFID reader |
|
||||
| Preferences | built-in | ESP32 NVS storage |
|
||||
|
||||
## License
|
||||
|
||||
Part of the Giulio Hunt 2025 project.
|
||||
118
firmware/include/cards.h
Normal file
118
firmware/include/cards.h
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* @file cards.h
|
||||
* @brief RFID Card database management for Minecraft Orb
|
||||
*
|
||||
* Provides structures and functions for storing and retrieving
|
||||
* quest card data from ESP32 NVS (Non-Volatile Storage).
|
||||
*/
|
||||
|
||||
#ifndef CARDS_H
|
||||
#define CARDS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* @brief Quest card data structure
|
||||
*
|
||||
* Stores RFID card UID along with quest metadata.
|
||||
*/
|
||||
struct QuestCard {
|
||||
byte uid[MAX_UID_LEN]; // Card UID bytes
|
||||
byte uidLength; // Actual UID length (4 or 7)
|
||||
char name[CARD_NAME_LEN]; // Quest name
|
||||
char clue[CARD_CLUE_LEN]; // Clue/description text
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize the card database
|
||||
*
|
||||
* Opens NVS namespace and loads card count.
|
||||
*/
|
||||
void initCardDatabase();
|
||||
|
||||
/**
|
||||
* @brief Get the number of stored cards
|
||||
*
|
||||
* Returns:
|
||||
* int: Number of cards in database
|
||||
*/
|
||||
int getCardCount();
|
||||
|
||||
/**
|
||||
* @brief Find a card by UID
|
||||
*
|
||||
* Args:
|
||||
* uid: Pointer to UID bytes
|
||||
* uidLength: Length of UID
|
||||
* card: Pointer to QuestCard to fill if found
|
||||
*
|
||||
* Returns:
|
||||
* int: Card index if found, -1 if not found
|
||||
*/
|
||||
int findCardByUID(byte* uid, byte uidLength, QuestCard* card);
|
||||
|
||||
/**
|
||||
* @brief Add a new card to the database
|
||||
*
|
||||
* Args:
|
||||
* card: Pointer to QuestCard to add
|
||||
*
|
||||
* Returns:
|
||||
* bool: true if added successfully
|
||||
*/
|
||||
bool addCard(QuestCard* card);
|
||||
|
||||
/**
|
||||
* @brief Delete a card by index
|
||||
*
|
||||
* Args:
|
||||
* index: Card index to delete
|
||||
*
|
||||
* Returns:
|
||||
* bool: true if deleted successfully
|
||||
*/
|
||||
bool deleteCard(int index);
|
||||
|
||||
/**
|
||||
* @brief Get a card by index
|
||||
*
|
||||
* Args:
|
||||
* index: Card index
|
||||
* card: Pointer to QuestCard to fill
|
||||
*
|
||||
* Returns:
|
||||
* bool: true if card exists at index
|
||||
*/
|
||||
bool getCard(int index, QuestCard* card);
|
||||
|
||||
/**
|
||||
* @brief Clear all cards from database
|
||||
*/
|
||||
void clearAllCards();
|
||||
|
||||
/**
|
||||
* @brief Format UID as hex string
|
||||
*
|
||||
* Args:
|
||||
* uid: Pointer to UID bytes
|
||||
* uidLength: Length of UID
|
||||
* buffer: Output buffer (must be at least uidLength*3)
|
||||
*/
|
||||
void formatUID(byte* uid, byte uidLength, char* buffer);
|
||||
|
||||
/**
|
||||
* @brief Compare two UIDs
|
||||
*
|
||||
* Args:
|
||||
* uid1: First UID
|
||||
* len1: First UID length
|
||||
* uid2: Second UID
|
||||
* len2: Second UID length
|
||||
*
|
||||
* Returns:
|
||||
* bool: true if UIDs match
|
||||
*/
|
||||
bool compareUID(byte* uid1, byte len1, byte* uid2, byte len2);
|
||||
|
||||
#endif // CARDS_H
|
||||
36
firmware/include/config.h
Normal file
36
firmware/include/config.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @file config.h
|
||||
* @brief Configuration constants for Minecraft Orb
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include "secrets.h" // WIFI_SSID, WIFI_PASSWORD
|
||||
|
||||
// =============================================================================
|
||||
// WiFi Configuration
|
||||
// =============================================================================
|
||||
#define WIFI_TIMEOUT_MS 10000 // 10 seconds connection timeout
|
||||
|
||||
// =============================================================================
|
||||
// Card Database Configuration
|
||||
// =============================================================================
|
||||
#define MAX_CARDS 15 // Maximum number of stored cards
|
||||
#define CARD_NAME_LEN 32 // Max length of quest name
|
||||
#define CARD_CLUE_LEN 128 // Max length of clue text
|
||||
#define MAX_UID_LEN 7 // Max RFID UID length (4 or 7 bytes)
|
||||
|
||||
// =============================================================================
|
||||
// NVS Storage Keys
|
||||
// =============================================================================
|
||||
#define NVS_NAMESPACE "orb_cards"
|
||||
#define NVS_CARD_COUNT "card_count"
|
||||
|
||||
// =============================================================================
|
||||
// Power Management (Battery Operation)
|
||||
// =============================================================================
|
||||
#define DISPLAY_TIMEOUT_MS 180000 // 3 minutes: turn off display
|
||||
#define SLEEP_TIMEOUT_MS 600000 // 10 minutes: enter deep sleep
|
||||
|
||||
#endif // CONFIG_H
|
||||
48
firmware/include/pins.h
Normal file
48
firmware/include/pins.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @file pins.h
|
||||
* @brief Pin definitions for Minecraft Orb - ESP32-C3 SuperMini
|
||||
*
|
||||
* Hardware Configuration:
|
||||
* - SSD1306 OLED Display (128x64) via I2C
|
||||
* - RC522 RFID Reader via SPI
|
||||
* - White LED indicator
|
||||
* - Piezo buzzer for audio feedback
|
||||
* - TTP223 capacitive touch sensor
|
||||
*/
|
||||
|
||||
#ifndef PINS_H
|
||||
#define PINS_H
|
||||
|
||||
// =============================================================================
|
||||
// I2C - SSD1306 OLED Display (Right side of board)
|
||||
// =============================================================================
|
||||
#define I2C_SDA 2 // I2C Data
|
||||
#define I2C_SCL 3 // I2C Clock
|
||||
#define OLED_ADDRESS 0x3C // Default I2C address for SSD1306
|
||||
|
||||
// =============================================================================
|
||||
// SPI - RC522 RFID Reader (Left side of board)
|
||||
// Pin order matches RC522 board: SDA, SCK, MOSI, MISO, RST
|
||||
// =============================================================================
|
||||
#define RC522_CS 6 // SDA - Chip Select for Reader
|
||||
#define SPI_SCK 7 // SCK - SPI Clock
|
||||
#define SPI_MOSI 8 // MOSI - SPI Data Out
|
||||
#define SPI_MISO 9 // MISO - SPI Data In
|
||||
#define RC522_RST 10 // RST - Reset for Reader
|
||||
|
||||
// =============================================================================
|
||||
// LED (Right side of board)
|
||||
// =============================================================================
|
||||
#define LED_WHITE 4 // White LED (via 100 ohm resistor)
|
||||
|
||||
// =============================================================================
|
||||
// Buzzer (Right side of board)
|
||||
// =============================================================================
|
||||
#define BUZZER_PIN 0 // Piezo buzzer (passive)
|
||||
|
||||
// =============================================================================
|
||||
// Touch Sensor (Left side of board)
|
||||
// =============================================================================
|
||||
#define TOUCH_PIN 5 // TTP223 capacitive touch sensor (GPIO0-5 for deep sleep wake)
|
||||
|
||||
#endif // PINS_H
|
||||
14
firmware/include/secrets.h.example
Normal file
14
firmware/include/secrets.h.example
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* @file secrets.h
|
||||
* @brief Sensitive credentials — DO NOT COMMIT
|
||||
*
|
||||
* Copy this file to secrets.h and fill in your values.
|
||||
*/
|
||||
|
||||
#ifndef SECRETS_H
|
||||
#define SECRETS_H
|
||||
|
||||
#define WIFI_SSID "your-ssid"
|
||||
#define WIFI_PASSWORD "your-password"
|
||||
|
||||
#endif // SECRETS_H
|
||||
284
firmware/pinout.svg
Normal file
284
firmware/pinout.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 348 KiB |
27
firmware/platformio.ini
Normal file
27
firmware/platformio.ini
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
; PlatformIO Project Configuration File
|
||||
; Minecraft Orb - ESP32-C3 SuperMini
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32c3]
|
||||
platform = espressif32
|
||||
board = esp32-c3-devkitm-1
|
||||
framework = arduino
|
||||
|
||||
; Serial monitor
|
||||
monitor_speed = 115200
|
||||
|
||||
; Build flags
|
||||
build_flags =
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DARDUINO_USB_MODE=1
|
||||
|
||||
; Libraries
|
||||
lib_deps =
|
||||
; OLED Display (SSD1306 via I2C)
|
||||
adafruit/Adafruit SSD1306@^2.5.7
|
||||
adafruit/Adafruit GFX Library@^1.11.5
|
||||
; RFID Reader (RC522 via SPI)
|
||||
miguelbalboa/MFRC522@^1.4.10
|
||||
|
||||
; Upload settings for ESP32-C3 SuperMini
|
||||
upload_speed = 921600
|
||||
137
firmware/src/cards.cpp
Normal file
137
firmware/src/cards.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* @file cards.cpp
|
||||
* @brief RFID Card database implementation
|
||||
*
|
||||
* Uses ESP32 Preferences library for NVS storage.
|
||||
*/
|
||||
|
||||
#include "cards.h"
|
||||
#include <Preferences.h>
|
||||
|
||||
// NVS preferences instance
|
||||
static Preferences prefs;
|
||||
static int cardCount = 0;
|
||||
|
||||
void initCardDatabase() {
|
||||
prefs.begin(NVS_NAMESPACE, false); // false = read/write mode
|
||||
cardCount = prefs.getInt(NVS_CARD_COUNT, 0);
|
||||
Serial.print("OK: Card database initialized, ");
|
||||
Serial.print(cardCount);
|
||||
Serial.println(" cards loaded");
|
||||
}
|
||||
|
||||
int getCardCount() {
|
||||
return cardCount;
|
||||
}
|
||||
|
||||
bool compareUID(byte* uid1, byte len1, byte* uid2, byte len2) {
|
||||
if (len1 != len2) return false;
|
||||
for (byte i = 0; i < len1; i++) {
|
||||
if (uid1[i] != uid2[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void formatUID(byte* uid, byte uidLength, char* buffer) {
|
||||
buffer[0] = '\0';
|
||||
for (byte i = 0; i < uidLength; i++) {
|
||||
char hex[4];
|
||||
sprintf(hex, "%02X", uid[i]);
|
||||
strcat(buffer, hex);
|
||||
if (i < uidLength - 1) strcat(buffer, ":");
|
||||
}
|
||||
}
|
||||
|
||||
int findCardByUID(byte* uid, byte uidLength, QuestCard* card) {
|
||||
QuestCard temp;
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
if (getCard(i, &temp)) {
|
||||
if (compareUID(uid, uidLength, temp.uid, temp.uidLength)) {
|
||||
if (card != nullptr) {
|
||||
memcpy(card, &temp, sizeof(QuestCard));
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool getCard(int index, QuestCard* card) {
|
||||
if (index < 0 || index >= cardCount) return false;
|
||||
|
||||
char key[16];
|
||||
sprintf(key, "card_%d", index);
|
||||
|
||||
size_t len = prefs.getBytesLength(key);
|
||||
if (len != sizeof(QuestCard)) return false;
|
||||
|
||||
prefs.getBytes(key, card, sizeof(QuestCard));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addCard(QuestCard* card) {
|
||||
if (cardCount >= MAX_CARDS) {
|
||||
Serial.println("ERROR: Card database full");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if card already exists
|
||||
if (findCardByUID(card->uid, card->uidLength, nullptr) >= 0) {
|
||||
Serial.println("ERROR: Card already exists");
|
||||
return false;
|
||||
}
|
||||
|
||||
char key[16];
|
||||
sprintf(key, "card_%d", cardCount);
|
||||
|
||||
prefs.putBytes(key, card, sizeof(QuestCard));
|
||||
cardCount++;
|
||||
prefs.putInt(NVS_CARD_COUNT, cardCount);
|
||||
|
||||
Serial.print("OK: Card added at index ");
|
||||
Serial.println(cardCount - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool deleteCard(int index) {
|
||||
if (index < 0 || index >= cardCount) {
|
||||
Serial.println("ERROR: Invalid card index");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shift all cards after this one down by one
|
||||
for (int i = index; i < cardCount - 1; i++) {
|
||||
QuestCard temp;
|
||||
char srcKey[16], dstKey[16];
|
||||
sprintf(srcKey, "card_%d", i + 1);
|
||||
sprintf(dstKey, "card_%d", i);
|
||||
|
||||
prefs.getBytes(srcKey, &temp, sizeof(QuestCard));
|
||||
prefs.putBytes(dstKey, &temp, sizeof(QuestCard));
|
||||
}
|
||||
|
||||
// Remove the last card entry
|
||||
char lastKey[16];
|
||||
sprintf(lastKey, "card_%d", cardCount - 1);
|
||||
prefs.remove(lastKey);
|
||||
|
||||
cardCount--;
|
||||
prefs.putInt(NVS_CARD_COUNT, cardCount);
|
||||
|
||||
Serial.print("OK: Card deleted, ");
|
||||
Serial.print(cardCount);
|
||||
Serial.println(" cards remaining");
|
||||
return true;
|
||||
}
|
||||
|
||||
void clearAllCards() {
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
char key[16];
|
||||
sprintf(key, "card_%d", i);
|
||||
prefs.remove(key);
|
||||
}
|
||||
cardCount = 0;
|
||||
prefs.putInt(NVS_CARD_COUNT, 0);
|
||||
Serial.println("OK: All cards cleared");
|
||||
}
|
||||
1631
firmware/src/main.cpp
Normal file
1631
firmware/src/main.cpp
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue