Skip to content

shyhirt/chess-pro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

♟️ Chess Pro — Smart Hardware Chess Board

A physical chess board with Hall effect sensors, dual displays, LED lighting, and full Lichess integration — built on Raspberry Pi / Orange Pi Zero 2W, written in Go.

Go Platform DietPi License


📸 Overview

Chess Pro is an open-source smart chess board that detects piece positions using Hall effect sensors embedded under each square. It connects to Lichess for online play, runs Stockfish locally for engine analysis, and provides real-time visual feedback via WS2812B LED strips and two displays. Demo

⚠️ Migrated to DietPi! See the DietPi section for details on the optimized system image with power-loss protection.


✨ Features

  • 64 Hall effect sensors (A3144) — one per square, detect piece presence via neodymium magnets embedded in piece bases
  • Dual display system:
    • SSD1681 e-paper display (200×200 px) — shows current board position with pixel-art pieces, fast partial refresh on each move
    • SH1106 OLED display (128×64 px) — chess clock, status, engine info, game events
  • WS2812B RGB LED strip — highlights valid moves, last move, check, premoves
  • Stockfish engine — local analysis, engine-vs-player mode
  • Lichess Board API — play online games; opponent moves appear on the physical board
  • Premove support — queue a move during the opponent's turn
  • Puzzle mode — solve Lichess puzzles on the physical board
  • WiFi provisioning via BLE (Android companion app)
  • Game history — PGN storage with Stockfish analysis web UI
  • Power-loss protection — read-only root overlay + journaled /data partition
  • Graceful shutdown / restart via hardware button

🔧 Hardware

Main Board

Component Model Qty
Single-board computer Raspberry Pi Zero 2W 1
Hall effect sensor A3144 (digital, TO-92, 5 V) 64
Neodymium magnet 10×3 mm 32 (one per piece)
I2C GPIO expander PCF8575 16-bit 4
LED strip WS2812B 60 LED/m, 5 V ~1.5 m
Pull-up resistor 10 kΩ, 0.25 W 64
I2C pull-up resistor 4.7 kΩ 2
Bulk decoupling capacitor 1000 µF 10 V electrolytic 4
Hall bypass capacitor 100 nF ceramic 64
Power MOSFET AOD4184 (N-channel, 40 V / 50 A) 1
Current sensor INA219 breakout 1
Microcontroller Arduino Nano 1
Gate resistor 430 Ω, 0.25 W 1

Displays

Component Model Interface
E-paper display SSD1681 1.54″ 200×200 B/W SPI
OLED display SH1106 1.3″ 128×64 I2C

Controls & Audio

Component Notes
Tactile button 6×6×5 mm 3× — menu, confirm, back
Passive buzzer 5 V Move confirmation, alerts

Power

  • 5 V / 2.5 A micro-USB power supply, or 10 000 mAh powerbank
  • MikroTik hAP ac2 USB port (5 V / 1 A) is sufficient for light load

Soft-start / overcurrent protection

WS2812B LEDs draw up to 60 mA each at full white; 64 LEDs fully lit would exceed 3.8 A and trip a powerbank's protection. A hardware safety circuit prevents this:

5 V supply
    │
    ├──► INA219 (current sensor, I²C to Arduino Nano)
    │         │
    │    Arduino Nano
    │         │ PWM soft-start + overcurrent cutoff
    │         ▼
    │    AOD4184 MOSFET gate (via 430 Ω)
    │         │
    └── MOSFET drain/source ──► LED 5 V rail

Arduino Nano behaviour:

  • On power-up: ramps the MOSFET gate PWM from 0 % to 100 % over ~200 ms (soft-start), avoiding the inrush current spike that triggers powerbank protection
  • Continuous monitoring: if INA219 reads > 2 A, MOSFET is switched off immediately
  • Pi and sensors are powered directly from the 5 V rail, not through the MOSFET — they remain on even if LEDs are cut

Why not software-only? A firmware bug or misconfiguration could light all 64 LEDs simultaneously. The Arduino + INA219 cutoff is independent of the Pi and cannot be bypassed by bad application code.

Enclosure

  • Birch plywood 8 mm — base, sides
  • Birch plywood 4 mm — top panel, cell dividers
  • Chess pieces: king height 85–95 mm; neodymium magnet pressed into a 10 mm × 5 mm hole drilled in each piece base
  • WS2812B strip mounted in a routed channel around the board perimeter

🔌 Wiring

A3144 Hall Sensor (per sensor)

Pin 1 (Vcc) → 5 V
Pin 2 (GND) → GND
Pin 3 (OUT) → 10 kΩ pull-up to 3.3 V → PCF8575 input

Copper shielding tape is not needed — Hall sensors respond to magnetic fields, not conductivity.

Place a 100 nF ceramic capacitor between Vcc and GND as close to each sensor as possible. Without it, switching transients from the WS2812B data line can cause false trigger readings on sensors sharing the same 5 V rail.

PCF8575 I2C Expander (×4, addresses 0x20–0x23)

SDA → RPi GPIO 2  (I2C1 SDA)
SCL → RPi GPIO 3  (I2C1 SCL)
VCC → 3.3 V
GND → GND

Address pins A0/A1/A2 set per board via solder pads.

Place a 1000 µF / 10 V electrolytic capacitor on the 5 V rail near each PCF8575 pair. Four capacitors total stabilise the supply against voltage dips when multiple LED channels switch simultaneously.

SSD1681 E-Paper

BUSY → GPIO 24
RST  → GPIO 25
DC   → GPIO 8
CS   → GPIO 7  (SPI CE1)
CLK  → GPIO 11 (SPI CLK)
DIN  → GPIO 10 (SPI MOSI)
VCC  → 3.3 V
GND  → GND

SH1106 OLED

SDA → GPIO 2  (I2C1, shared bus with PCF8575)
SCL → GPIO 3
VCC → 3.3 V
GND → GND

WS2812B LEDs

DIN → GPIO 18  (PWM0)
5 V → 5 V rail (separate supply; ~3 A for 60 LEDs at full brightness)
GND → Common GND

🗂️ Project Structure

chess-pro/
├── cmd/
│   └── chess-pro/            # main entry point
├── internal/
│   ├── clock/                # chess clock
│   ├── config/               # config loader
│   ├── display/              # OLED (SH1106) controller
│   ├── engines/              # Stockfish, Lichess API, puzzle engine
│   ├── epaper/               # E-paper (SSD1681) + pixel-art piece renderer
│   ├── game/                 # main game loop, premove logic, capture detection
│   ├── hardware/             # WS2812B LED controller
│   ├── logger/               # structured logger
│   ├── sensors/              # Hall sensor matrix (PCF8575 × 4)
│   ├── state/                # game state machine
│   └── storage/              # PGN save, game history, safe writes
├── ble-server/               # WiFi provisioning BLE GATT server (Go)
├── android-app/              # Companion Android app (Kotlin)
├── web/                      # Game analysis web UI (Stockfish + PGN viewer)
├── image/
│   ├── chess-pro-image.mk    # Makefile — builds protected DietPi image
│   └── configure.sh          # Interactive image configurator
├── config.yaml               # Board runtime configuration
├── Makefile                  # make build / make deploy
└── README.md

🚀 Getting Started

Prerequisites

  • Go 1.22+
  • Raspberry Pi Zero 2W or Orange Pi Zero 2W running DietPi (recommended) or Raspberry Pi OS Lite
  • Stockfish: sudo apt install stockfish
  • I2C enabled in /boot/config.txt or via sudo raspi-config

Build & Deploy

# Cross-compile for ARM
make build

# Deploy to board over SSH
make deploy

# Or build directly on the board
go build -o chess-pro ./cmd/chess-pro/

Runtime Config (config.yaml)

lichess:
  token: "your_lichess_api_token"

stockfish:
  path: "/usr/games/stockfish"
  depth: 15

display:
  epaper: true       # SSD1681
  oled:   true       # SH1106

sensors:
  i2c_bus: 1
  addresses: [0x20, 0x21, 0x22, 0x23]

leds:
  gpio_pin: 18
  count:    64
  brightness: 80

🧠 How It Works

┌──────────────────────────────────────────────────────────────┐
│                          Game Loop                           │
│                                                              │
│  Hall Sensors (64×) ──► Piece State Matrix                   │
│         │                      │                             │
│         ▼                      ▼                             │
│   Diff Detection ──────► Move Validation (notnil/chess)      │
│         │                      │                             │
│         ▼                      ▼                             │
│   LED Feedback           Premove Queue                       │
│   (valid / invalid)            │                             │
│                                ▼                             │
│                      Lichess API / Stockfish                  │
│                                │                             │
│                   ┌────────────┴────────────┐                │
│                   ▼                         ▼                │
│            E-paper update             OLED update            │
│          (SSD1681 partial)         (SH1106 status)           │
└──────────────────────────────────────────────────────────────┘

Piece detection: Each square has one Hall sensor beneath it. Pieces have a neodymium magnet (10×3 mm) in their base. The sensor output goes low when a piece is present, high when lifted.

Move detection: Each scan computes a diff between the current and previous board state. A legal move manifests as one square going low (destination) and one going high (source). Captures show two going high (captured piece + source) and one going low.

Premove: If a player lifts and places a piece during the opponent's turn, the move is queued. When the opponent's move arrives from Lichess or Stockfish, the premove is auto-confirmed if legal.


🖥️ Displays

E-Paper — SSD1681 1.54″

  • Full board render on game start and position reset
  • Partial refresh on each move (~0.6 s) — only the two changed squares are redrawn to minimise refresh wear
  • Pixel-art chess pieces rendered entirely in software — generated mathematically in Go, no bitmap files required
  • Last-move highlight

OLED — SH1106 1.3″ 128×64

  • Chess clock (white / black time)
  • Current turn indicator
  • Engine name and search depth
  • Game result, check / checkmate alerts
  • WiFi and Lichess connection status

🛡️ DietPi Protected Image

The project ships a ready-to-flash DietPi image builder that provides power-loss protection — safe to unplug at any moment without corrupting the SD card.

Why DietPi?

Metric DietPi Raspberry Pi OS Lite
Image size ~400 MB ~2 GB
RAM after boot ~60 MB ~150 MB
Extra RAM for Stockfish +100 MB
I2C / SPI / GPIO configured out of the box manual setup
Logs written to RAM

Partition Layout

┌─────────────────────────────────────────┐
│  SD Card (6 GB)                         │
├─────────────────────────────────────────┤
│  /boot  128 MB  FAT32                   │
│  DietPi configs, config.txt             │
│  I2C / SPI / GPIO enabled               │
├─────────────────────────────────────────┤
│  /  (root)  2 GB  ext4 + overlay        │
│  System READ-ONLY (~400 MB)             │
│  Changes stored in RAM (tmpfs)          │
│  Clean state restored on reboot         │
│  RAM after boot: ~60 MB                 │
├─────────────────────────────────────────┤
│  /data  3.5 GB  ext4 + journaling       │
│  data=journal — writes go to log first  │
│  sync  — no write cache                 │
│  Atomic operations via SafeWrite()      │
│                                         │
│  /data/games/   PGN game files          │
│  /data/stats/   Player statistics       │
│  /data/config/  Board settings          │
│  /data/logs/    Application logs        │
└─────────────────────────────────────────┘

Peripherals configured out of the box:
✅ I2C 400 kHz  — SH1106 OLED, PCF8575
✅ SPI          — SSD1681 e-paper
✅ GPIO         — buzzer (GPIO 27), Hall sensors
✅ WS2812B LED strip support

Build the Image

1. Install dependencies

# Fedora
sudo dnf install qemu-img parted e2fsprogs dosfstools wget p7zip p7zip-plugins

# Ubuntu / Debian
sudo apt install qemu-utils parted e2fsprogs dosfstools wget p7zip-full

# Arch
sudo pacman -S qemu-img parted e2fsprogs dosfstools wget p7zip

2. Configure

# Interactive wizard (recommended)
./image/configure.sh

# Or edit manually
cp image/chess-pro.config.example image/chess-pro.config

Minimum required parameters:

WIFI_SSID="YourNetworkName"
WIFI_PASSWORD="YourPassword"
WIFI_COUNTRY="UA"
SSH_PASSWORD="chess2026"

Full parameter reference:

# WiFi
WIFI_SSID="MyNetwork"
WIFI_PASSWORD="password123"
WIFI_COUNTRY="UA"               # UA / US / GB / DE ...
WIFI_SSID_2=""                  # Optional backup network
WIFI_PASSWORD_2=""
DISABLE_WIFI_POWERSAVE="yes"

# Network
HOSTNAME="chess-pro"
STATIC_IP=""                    # Empty = DHCP, or "192.168.1.100/24"
GATEWAY=""                      # Required only for static IP
DNS_SERVERS="8.8.8.8 8.8.4.4"

# SSH
ENABLE_SSH="yes"
SSH_PASSWORD="chess2026"
SSH_PUBLIC_KEY=""               # Optional — paste public key for passwordless login

# Locale
TIMEZONE="Europe/Kiev"
LOCALE="en_US.UTF-8"
KEYBOARD_LAYOUT="us"

# Chess Pro
CHESS_BOARD_NAME="Chess Pro Board #1"
CHESS_LED_BRIGHTNESS="80"
CHESS_SOUND_ENABLED="true"
CHESS_LICHESS_TOKEN=""

# System
DISABLE_BLUETOOTH="no"
ENABLE_WATCHDOG="yes"
NTP_SERVERS="0.ua.pool.ntp.org"

3. Build

# Full build in one step
make -f image/chess-pro-image.mk all

# Step by step
make -f image/chess-pro-image.mk download          # Download base DietPi image
make -f image/chess-pro-image.mk prepare-image     # Partition the image
make -f image/chess-pro-image.mk setup-overlay     # Configure root overlay
make -f image/chess-pro-image.mk configure-system  # WiFi, SSH, locale
make -f image/chess-pro-image.mk create-data       # Prepare /data partition
make -f image/chess-pro-image.mk install-app       # Install firmware binary

# Preview configuration before building
make -f image/chess-pro-image.mk info

# Utility targets
make -f image/chess-pro-image.mk test-mount        # Mount image for inspection
make -f image/chess-pro-image.mk test-umount       # Unmount
make -f image/chess-pro-image.mk clean             # Remove temp files
make -f image/chess-pro-image.mk deep-clean        # Remove output image

4. Flash

lsblk                                              # Identify SD card device
make -f image/chess-pro-image.mk flash DEVICE=/dev/sdX

# Or manually
sudo dd if=chess-pro-protected.img of=/dev/sdX bs=4M status=progress conv=fsync && sync

First Boot

  1. Insert the SD card and power on the board
  2. First boot takes 3–4 minutes — DietPi auto-installs packages
    • Green LED blinking fast → installation in progress
    • Green LED steady → system ready
  3. The board connects to WiFi automatically
  4. SSH in: ssh dietpi@chess-pro.local (default password: chess2026)

Finding the IP address:

ping chess-pro.local
nmap -sn 192.168.88.0/24 | grep chess-pro
# Or check the DHCP lease list in your router's web interface

Verify peripherals after boot:

ssh dietpi@chess-pro.local

i2cdetect -y 1        # Expect: 0x3c (SH1106), 0x20–0x23 (PCF8575)
ls /dev/spidev*       # Expect: /dev/spidev0.0  /dev/spidev0.1
gpiodetect
lsmod | grep -E "i2c|spi"

DietPi Management

sudo dietpi-launcher        # Main menu
sudo dietpi-config          # Network, display, peripherals
sudo dietpi-software        # Install / remove packages
sudo dietpi-backup          # System backup

# Temporarily disable overlay to make persistent changes
sudo dietpi-ramdisk 0
# ... make changes ...
sudo dietpi-ramdisk 1
sudo reboot                 # Returns to read-only mode

Updating the Firmware

# On dev machine — cross-compile
make build-arm64

# Copy binary to board
scp bin/chess-pro dietpi@chess-pro.local:/tmp/

# On board — temporarily disable overlay, replace binary, re-enable
ssh dietpi@chess-pro.local
sudo dietpi-ramdisk 0
sudo cp /tmp/chess-pro /opt/chess-pro/
sudo systemctl restart chess-pro
sudo dietpi-ramdisk 1
sudo reboot

Power-Loss Protection Details

Layer Mechanism Effect
Root partition overlayfs (read-only + RAM tmpfs) System files survive any power cut; state resets cleanly on reboot
Data partition data=journal + sync mount options Every write is journaled before committing; no silent data corruption
SQLite WAL mode + PRAGMA synchronous=FULL Database always consistent; incomplete transactions auto-roll back
File writes SafeWrite() — write .tmpfsyncrename Atomic replacement; old file intact until new one is fully flushed

Testing protection:

# Start a game, then pull the power cord mid-game
# After reboot, verify:
sqlite3 /data/games/index.db "PRAGMA integrity_check;"
sqlite3 /data/games/index.db "SELECT COUNT(*) FROM games;"
ls -la /data/games/

The database will always be either intact or at the last complete transaction — never a corrupt partial write.

Write Performance

Sync mode reduces raw write throughput to ~5–10 MB/s vs ~50 MB/s without it. For this project that is irrelevant: a full PGN file is 2–5 KB and a single SQLite game record is ~1 KB — a complete game save takes under 100 ms.

Recommendations: batch move records before writing; run SQLite WAL checkpoint every 10 games; avoid logging on every move (log important events only).


💾 Storage API (Go)

// Open game database
db, err := storage.OpenGameDB()
defer db.Close()

// Save a completed game
pgnPath, _ := storage.SavePGN(pgnString)
db.SaveGame(&storage.GameRecord{
    Date:       time.Now(),
    Result:     "1-0",
    PGNPath:    pgnPath,
    MovesCount: 42,
})

// Load recent games
games, _ := db.GetRecentGames(10)

// Atomic config write — safe under power loss
storage.SafeWriteJSON("/data/config/board.json", configMap)

// Update player statistics
stats, _ := storage.LoadStats()
stats.GamesPlayed++
stats.Wins++
storage.SaveStats(stats)

📱 WiFi Provisioning (BLE)

On startup the board advertises a BLE GATT service. Use the companion Android app to send WiFi credentials — no keyboard or screen needed.

Service UUID:    12345678-90AB-CDEF-1234-567890ABCD01
Write char:      12345678-90AB-CDEF-1234-567890ABCD02  ← {"ssid":"...","password":"..."}
Status char:     12345678-90AB-CDEF-1234-567890ABCD03  ← "connected" / "failed"

🔮 Roadmap

  • PCB design (replace perfboard wiring)
  • iOS companion app (WiFi provisioning)
  • Opening book display on e-paper
  • Bluetooth game broadcast (spectate on phone)
  • Over-the-air firmware update
  • 3D-printed enclosure option

🛠️ Troubleshooting

Image does not boot
sudo parted chess-pro-protected.img print
sudo losetup -f --show -P chess-pro-protected.img
sudo e2fsck -f /dev/loop0p2
sudo e2fsck -f /dev/loop0p3
sudo losetup -d /dev/loop0
Overlay not working
cat /etc/overlayroot.conf
mount | grep overlay
sudo overlayroot-chroot    # Enter writable chroot for debugging
SQLite errors on /data
sqlite3 /data/games/index.db "PRAGMA integrity_check;"
sqlite3 /data/games/index.db "REINDEX;"
sqlite3 /data/games/index.db "PRAGMA wal_checkpoint(TRUNCATE);"
I2C devices not found
cat /boot/config.txt | grep i2c    # Should show: dtparam=i2c_arm=on
sudo modprobe i2c-dev
i2cdetect -y 1
Game data backup
# Local backup
rsync -av /data/ /mnt/backup/

# Over the network
rsync -av /data/ user@backup-server:/backups/chess-pro/

# Remove games older than one year
find /data/games -name "*.pgn" -mtime +365 -delete
sqlite3 /data/games/index.db \
  "DELETE FROM games WHERE date < strftime('%s','now','-1 year'); VACUUM;"

🤝 Contributing

PRs are welcome. Please open an issue first for major changes.


📄 License

Part License
Firmware (Go) GPL-3.0
PCB schematics CERN OHL v2
Documentation CC BY 4.0

🙏 Credits

About

Smart chess board with Hall effect sensors, WS2812B LEDs, e-paper + OLED displays and Lichess integration — built on Raspberry Pi Zero 2W in Go

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors