PROJECT STORY

Title: Lichess Keypad Bridge

ABOUT THE PROJECT

I built a physical controller for online chess: a 4x4 membrane keypad on an Arduino Uno R3 that lets me enter moves, plus four LEDs for game feedback (capture, check, win, loss). A Python script bridges the Arduino to Lichess using the Board API, validates and submits moves, and renders a simple Unicode chessboard in the terminal.

My goal was to keep the loop tight and tactile: press keys, see the board update, and get instant LED feedback without touching the mouse.

INSPIRATION

I wanted the feel of "punching in" a move while still playing online. A $5 keypad is hackathon-friendly, and LEDs give at-a-glance status so I don’t need to watch the screen for every event.

HOW I BUILT IT

Hardware

  • Arduino Uno R3
  • 4x4 membrane keypad (HX-543)
  • 4 LEDs with 220–1k ohm series resistors (PWM pins for brightness)
  • Breadboard + jumpers

Firmware (Arduino)

  • Keypad reading with the Keypad library.
  • Move entry: I press four digits 1..8 for from/to, then '#'. Example: e2e4 -> 5 2 5 4 # '*' clears the buffer. A/B/C/D are hotkeys to demo LEDs (win/lose/cap/check).
  • Serial protocol (line-based): Outgoing: UCI move like "e2e4" or "e7e8q". Incoming: "CAP", "CHECK_ON", "CHECK_OFF", "WIN", "LOSE".
  • Non-blocking LED driver: timers for 5–10 s pulses; steady ON for check.
  • LEDs on PWM pins (3,5,6,9) so I can tune brightness.

Software (Python)

  • Serial bridge: auto-detects the Arduino COM port; strict UCI parser so only real moves pass through.
  • Lichess client: berserk for the Board API, python-chess to mirror the position and confirm legality before sending.
  • Terminal UI: a fixed-width Unicode board (consistent with Cascadia Mono / Consolas); highlights last move.
  • LED events: capture detection, check state tracking, and game results sent back as text commands over serial.

WHAT I LEARNED

  • Design of a tiny, human-readable serial protocol and why strict validation matters.
  • Practical Lichess API usage: token scopes (board:play), safe env vars, and the correct client calls.
  • Terminal rendering details on Windows (fonts and code page) to keep Unicode aligned.
  • Quick electronics math and measurement habits: R ~= (VCC - Vf) / I and I ~= (VCC - Vf) / R Keeping LED current around 5–10 mA is plenty, PWM handles the rest.
  • Windows dev ergonomics: COM port contention, ssh-agent on Windows, and Git line ending normalization.

CHALLENGES I FACED (AND HOW I SOLVED THEM)

1) COM port "Access is denied." Cause: Arduino Serial Monitor had the port open. Fix: Close the monitor before running Python; add clearer error text.

2) Missing Arduino libraries / includes. Fix: Install the Keypad library via Library Manager and re-verify.

3) "READY"/"SENT" getting misread as moves. Fix: Add strict UCI validation: only [a-h][1-8][a-h]1-8 is accepted.

4) Wrong Lichess API call. Symptom: "module 'berserk' has no attribute 'Board'". Fix: Use client.board.make_move(game_id, uci).

5) Check LED not lighting as expected. Cause: I only lit yellow when my side to move was in check. Fix: Add a tiny state machine (update_check_led) and a toggle to choose "you only" vs "either side".

6) Unicode board misalignment. Fix: Use fixed 3-char cells, run chcp 65001, and pick a monospace font (Cascadia Mono or Consolas).

7) Git line endings broke scripts. Fix: Add .gitattributes to keep LF for code, set core.autocrlf=input, and renormalize.

8) SSH key issues on Windows. Fix: Start ssh-agent in an elevated terminal, add the ed25519 key, and paste the OpenSSH .pub into GitHub.

RESULTS

  • I can play full Lichess games from the keypad.
  • LEDs give immediate, glanceable feedback (capture, check state, win/loss).
  • The terminal board mirrors the live game and doubles as a great debug view.

WHAT'S NEXT

  • Promotions UX: map A/B/C/D to q/r/b/n after a 5-digit entry.
  • LED polish: fade curves and non-blocking animations; optional NeoPixel variant.
  • QoL: premove/draw/resign hotkeys; optional sound cues.
  • Enclosure: a small printed faceplate or case to make it more portable.

QUICK BUILD NOTES

  • Run order matters: flash the Arduino first, then start the Python bridge (with the Serial Monitor closed).
  • Keep LED current modest; PWM handles brightness balancing.
  • If something looks off, watch for logs like: [SEND] -> CHECK_ON and Serial: RX: CHECK_ON That confirms the end-to-end signal path is alive.

Built With

Share this project:

Updates