Welcome to the official documentation for ChessPi! This project is a complete C++ application designed to run on a Raspberry Pi, transforming a custom-wired chessboard into a smart, move-detecting device. It uses a grid of 64 Hall effect sensors to detect the presence and color of magnetic chess pieces, all processed in real-time with a robust, multithreaded architecture.
- Architectural Philosophy
- Python Frontend UI
- Hardware Setup & Wiring Logic
- Software Prerequisites
- Compilation Guide
- Running the Application
- Mandatory Calibration
- Codebase Deep Dive
- Troubleshooting
- Generating Developer Documentation
- Future Roadmap
The core of this project is built on a producer-consumer model to ensure a responsive and non-blocking system.
- The Producer (Sensor Scanning Thread): A dedicated background thread has one job: continuously scan all 64 sensors. This is a slow, I/O-bound task that takes over half a second per full board scan. By isolating it, we prevent the main application from freezing.
- The Consumer (Main Logic Thread): The main thread handles all the "smart" logic. It grabs the latest sensor data from a shared buffer, updates the state of each square (applying filters and debouncing), and detects valid move patterns (a piece lift followed by a piece land).
This is made thread-safe using a std::mutex, which acts as a gatekeeper for the shared data.
+--------------------------+ +------------------------+
| Sensor Scanning Thread | | Main Logic Thread |
| (Producer) | | (Consumer) |
|--------------------------| |------------------------|
| Loops forever: | | Loops forever: |
| 1. Read Sensor (row,col) | | 1. Lock Mutex |
| 2. Repeat for all 64 | ----> | 2. Copy data quickly |
| 3. Lock Mutex | | 3. Unlock Mutex |
| 4. Write data to buffer | | 4. Process copied data |
| 5. Unlock Mutex | | 5. Detect Moves |
+--------------------------+ +------------------------+
The project is split into two main components that communicate via standard I/O pipes:
- C++ Backend (
./bin/ChessPi): A high-performance application that handles all direct hardware communication. Its sole responsibility is to accurately detect physical piece movements and print the move in UCI format (e.g.,e2e4) tostdout. - Python Frontend (
ui/main.py): A user-facing script that receives the UCI moves from the backend. It uses thepython-chesslibrary to validate the move, maintain the game state, and display the board to the user.
This separation of concerns allows for rapid development of the UI and game logic in Python, while leveraging the speed of C++ for real-time sensor processing.
The hardware consists of a hierarchical network of I2C devices. Understanding this layout is crucial for correctly mapping the physical board to the software.
- ✅ Raspberry Pi: Model 3B+ or newer.
- ✅ Hall Effect Sensors: 64 sensors (e.g., A3144, AH49E).
- ✅ ADCs: 8 x ADS1115 16-bit I2C ADCs.
- ✅ I2C Multiplexers: 2 x TCA9548A I2C Multiplexers.
- ✅ Magnetic Chess Pieces: Pieces with magnets in their bases. Crucially, white and black pieces should have opposite magnetic polarities.
The software in SensorMatrix.cpp assumes the following logical connections. You must wire your board to match this scheme.
-
Multiplexer 1 (Address
0x70):- Channel 0: Connects to ADC 1 (Address
0x48) - Channel 1: Connects to ADC 2 (Address
0x49) - Channel 2: Connects to ADC 3 (Address
0x4A) - Channel 3: Connects to ADC 4 (Address
0x4B)
- Channel 0: Connects to ADC 1 (Address
-
Multiplexer 2 (Address
0x71):- Channel 0: Connects to ADC 5 (Address
0x48) - Channel 1: Connects to ADC 6 (Address
0x49) - Channel 2: Connects to ADC 7 (Address
0x4A) - Channel 3: Connects to ADC 8 (Address
0x4B)
- Channel 0: Connects to ADC 5 (Address
-
Sensor Mapping:
- ADC 1 handles sensors for squares a1 to h1 (Row 0).
- ADC 2 handles sensors for squares a2 to h2 (Row 1).
- ...and so on...
- ADC 8 handles sensors for squares a8 to h8 (Row 7).
Note: The ADS1115 has 4 analog inputs. The code currently assumes single-ended inputs A0, A1, A2, and A3 are used per ADC. This must be expanded if you are using more inputs.
The project requires both C++ build tools and a Python library.
Install the tools needed to compile the C++ hardware interface:
# Install compiler, cmake, and the I2C library
sudo apt-get install build-essential cmake libi2c-devInstall the python-chess library needed for the UI and move validation:
pip3 install python-chessCompiling is handled by CMake. Follow these steps precisely on the Raspberry Pi.
Navigate to the project's root directory:
cd /path/to/your/chesspiCreate and enter a build directory:
mkdir build
cd buildRun CMake to generate the build files:
# This tells CMake to look for the configuration file in the parent directory
cmake ..Compile the source code:
makeThe final executable, ChessPi, will be located in the /path/to/your/chesspi/bin/ directory.
The program requires administrative privileges to access the I2C hardware bus. To run the full application, you must pipe the output of the C++ backend into the Python frontend script. This requires administrative privileges for the C++ portion.
# Run from the project's root directory
sudo ./bin/ChessPi | python3 ui/main.pyThe terminal will display status messages from the backend (on stderr) and the game board UI from the Python script (on stdout).
The default settings in the code are placeholders. You must calibrate the system to match your specific sensors, magnets, and wiring distances for reliable detection.
The configuration file is: include/chess_move_detector/common/Constants.hpp
Run i2cdetect -y 1 on your Pi. You should see the addresses for your two multiplexers (0x70, 0x71). When you start the ChessPi program, it will select channels and you should be able to see the ADC addresses appear if you run the detect command again.
Update MUX_ADDRESS_1, MUX_ADDRESS_2, and the ADC_ADDRESSES array in Constants.hpp to match your hardware.
This is the most critical step. You need to find the raw sensor values for your setup.
Temporarily modify the sensor scanning loop in src/main.cpp to print the raw values:
// Inside the sensor_scanning_thread_func, before the lock
current_scan[r][c] = sensorMatrix.readSensorValue(r, c);
std::cout << "Sensor(" << r << "," << c << "): " << current_scan[r][c] << std::endl;Recompile and run this debug version.
- Find Empty Value: Note the range of values for an empty board. This will help you set
EMPTY_SQUARE_HYSTERESIS. - Find White Piece Value: Place a white piece on a square and record the value. It should be a large positive number. Do this for several pieces to find a reliable average for
WHITE_PIECE_THRESHOLD. - Find Black Piece Value: Do the same for black pieces. The value should be a large negative number. Use this to set
BLACK_PIECE_THRESHOLD.
Once you have your values, update Constants.hpp and remove the debug print statement.
DEBOUNCE_COUNT: If you get false positives from nudging pieces, increase this value from 3 to 4 or 5.MOVING_AVERAGE_WINDOW_SIZE: If your readings are very noisy, increase this to smooth them out.
- src/hardware/: Contains low-level classes for interacting with the hardware.
I2CController.cpp: A C++ wrapper for Linux I2C communication.SensorMatrix.cpp: The brain of the hardware layer. Translates (row, col) coordinates into the correct MUX/ADC/Channel path and returns a sensor value.
- src/processing/: Contains high-level classes for data interpretation.
Square.cpp: Manages the state of a single square. Implements the moving average filter, debouncing, and the EMPTY/OCCUPIED/LIFTED/LANDING state machine.Board.cpp: Holds an 8x8 grid of Square objects. Orchestrates updates and identifies when a LIFTED event followed by an OCCUPIED event constitutes a valid move.
- src/main.cpp: The entry point of the application. Sets up all objects, launches the producer/consumer threads, and handles the main processing loop.
- include/common/: Contains project-wide definitions.
Constants.hpp: The main configuration file.Types.hpp: Defines custom data types like BoardPosition and SquareState.
Q: I get "undefined reference to i2c_smbus..." errors when compiling.
A: This is a linker error. It almost always means you are trying to compile the project on a non-Raspberry Pi machine (like a Windows/macOS/Linux laptop). This project must be compiled on the Raspberry Pi itself to link against the correct hardware libraries.
Q: The program runs but detects nothing, or detects moves randomly.
A: This is a calibration issue. Your sensor thresholds in Constants.hpp do not match your hardware reality. Follow the calibration steps meticulously.
Q: The program crashes with "Failed to open I2C bus".
A: You forgot to run the program with sudo. It needs root privileges to access /dev/i2c-1. Also, ensure the I2C interface is enabled in raspi-config.
This project uses Doxygen to automatically generate a detailed HTML website from the source code comments, complete with class diagrams. This is highly recommended for developers who want to understand the codebase in depth.
First, you need to install doxygen and graphviz (which generates the diagrams).
sudo apt-get install doxygen graphvizNavigate to the root directory of the project (the one containing the Doxyfile) and run the following command:
doxygen DoxyfileA new directory named docs/ will be created in the project root.
Open the main page of the generated website in a web browser:
# Example using the default Raspberry Pi browser
sensible-browser docs/html/index.htmlYou can now explore all classes, files, and diagrams to get a comprehensive overview of the project's architecture.
- Integrate a Chess Engine: Use a library like Stockfish or an internal one to validate that a detected move is legal according to the rules of chess.
- Handle Special Moves: Add logic to detect castling (two pieces move) and en passant.
- GUI: Create a graphical user interface using a library like Qt or a simple web interface to display the board state.
- Online Play: Connect the board to online chess platforms like Lichess through their public APIs.