A ROS2-based autonomous chess-playing robot using the Hiwonder AiNex Biped Humanoid Robot and a magnetic vertical chess board.
# Linux
apt install git-lfs
git lfs install
# MacOS
brew install git-lfs
git lfs installIf LFS wasn’t installed or enabled when you cloned the repository, run git lfs pull inside the repository to download large files (e.g. images, videos, PDFs).
git clone --recursive git@github.com:2025W-HumanoidRoboticSystems/VerticalChess.git
Recursive cloning is needed to download the submodules, e.g., Stockfish.
Run following commands inside the repository:
git submodule init
git submodule update
⚠️ If you get aERROR: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Head "http://%2Fvar%2Frun%2Fdocker.sock/_ping": dial unix /var/run/docker.sock: connect: permission deniederror, then you must execute the following:
# Contains shortcut commands
source .aliases
# Build image - takes a while
build-vertical-chess-docker-image
# Create container (only once or after a `docker rm`)
create-vertical-chess-docker-container
# Stop container
docker stop vertical-chess
# docker rm vertical-chess THIS DELETES THE CONTAINER AND ITS FILES!!!
# Resume container
docker start vertical-chess
docker attach vertical-chessWeird "chess not found" error bug. Solution:
python3 -m venv .venv
source .venv/bin/activate
pip install rospkg
pip install -U colcon-common-extensions
pip install chess numpy lark
# Build
python3 -m colcon build --symlink-installAfter a fresh clone of the repository, run setup.sh. You only need to do this once!
# Source the .aliases file
source .aliases
# Build
build
# Cleanup build files
clean# Launch all components with defaults
./launch_vertical_chess.sh
# With custom options
./launch_vertical_chess.sh --skill-level 5 --time-limit 1000
# Disable specific components
./launch_vertical_chess.sh --no-occupancy --no-manipulation
# See all options
./launch_vertical_chess.sh --helpros2 launch vertical_chess vertical_chess.launch.py
# With parameters
ros2 launch vertical_chess vertical_chess.launch.py \
enable_occupancy_detection:=true \
stockfish_skill_level:=5 \
enable_visualization:=trueAvailable launch parameters:
| Parameter | Default | Description |
|---|---|---|
enable_camera |
true | System camera node |
enable_board_detection |
true | ChArUco board detection |
enable_occupancy_detection |
true | Color-based occupancy |
enable_chess_engine |
true | Chess logic + Stockfish |
enable_manipulation |
true | Pick-and-place FSM |
enable_visualization |
true | Debug windows |
stockfish_skill_level |
5 | AI strength (0-20) |
stockfish_depth |
5 | Search depth |
stockfish_time_limit_ms |
1000 | Time per move |
flowchart TB
subgraph Hardware
CAM[📷 Phone Camera<br/>/dev/video0]
ROBOT[🤖 AiNex Robot<br/>24 Servos + Grippers]
BOARD[♟️ Magnetic Vertical Board<br/>8x8 ChArUco]
end
subgraph Perception["Perception Pipeline"]
SYS_CAM[system_camera_node]
UNDIST[undistort_image_node]
BOARD_DET[board_detection_node]
OCC_DET[occupancy_detection_node]
end
subgraph Chess["Chess Engine"]
ENGINE[chess_engine_node]
STOCK[(Stockfish)]
end
subgraph Manipulation["Manipulation Pipeline"]
MARKER[marker_locator_node]
PICK[pick_and_place_node]
end
CAM --> SYS_CAM
SYS_CAM --> UNDIST
SYS_CAM --> BOARD_DET
UNDIST --> OCC_DET
BOARD_DET --> OCC_DET
OCC_DET --> ENGINE
ENGINE <--> STOCK
ENGINE --> PICK
MARKER --> PICK
PICK --> ROBOT
ROBOT --> BOARD
flowchart LR
subgraph Topics["Published Topics"]
T1[/"camera/system/compressed"/]
T2[/"camera/system/camera_info"/]
T3[/"/camera_image/undistorted"/]
T4[/"/chessboard_homography"/]
T5[/"/chessboard_pose_base"/]
T6[/"~/occupancy_state"/]
T7[/"~/board_state"/]
T8[/"~/execute_move"/]
T9[/"~/game_status"/]
end
SYS[system_camera_node] --> T1
SYS --> T2
UND[undistort_image_node] --> T3
BD[board_detection_node] --> T4
BD --> T5
OCC[occupancy_detection_node] --> T6
CHE[chess_engine_node] --> T7
CHE --> T8
CHE --> T9
T1 --> BD
T1 --> OCC
T2 --> BD
T3 --> OCC
T4 --> OCC
T6 --> CHE
T8 --> PP[pick_and_place_node]
flowchart TB
subgraph chess_engine_node
S1([get_board_state])
S2([make_move])
S3([get_best_move])
S4([get_legal_moves])
S5([validate_move])
S6([reset_board])
end
subgraph marker_locator_node
S7([get_square_position])
end
subgraph occupancy_detection_node
S8([set_enabled])
end
CLI[External Clients] -.-> S1
CLI -.-> S2
CLI -.-> S3
CLI -.-> S6
PP[pick_and_place_node] -.->|"Get 3D position"| S7
PP -.->|"Pause during manipulation"| S8
stateDiagram-v2
[*] --> WAITING_FOR_MOVE
WAITING_FOR_MOVE --> IDLE: ExecuteChessMove received
IDLE --> ADJUST_KNEE_PICK: Start pick sequence
state "Pick Sequence" as pick {
ADJUST_KNEE_PICK --> OPEN_GRIPPER
OPEN_GRIPPER --> MOVE_TO_PICK
MOVE_TO_PICK --> GRASP
GRASP --> RETRACT_FROM_PICK
RETRACT_FROM_PICK --> RETRACT_SHOULDER_1
RETRACT_SHOULDER_1 --> MOVE_TO_HOME_1
}
MOVE_TO_HOME_1 --> ADJUST_KNEE_PLACE: Start place sequence
state "Place Sequence" as place {
ADJUST_KNEE_PLACE --> MOVE_TO_PLACE
MOVE_TO_PLACE --> RELEASE
RELEASE --> RETRACT_FROM_PLACE
RETRACT_FROM_PLACE --> RETRACT_SHOULDER_2
RETRACT_SHOULDER_2 --> MOVE_TO_HOME_2
}
MOVE_TO_HOME_2 --> CROUCH
CROUCH --> DONE
DONE --> WAITING_FOR_MOVE: Ready for next move
flowchart TB
BF[base_footprint<br/>Ground origin 0,0,0]
BL[base_link<br/>z = 0.245m]
CAM[system_camera_optical_link<br/>Camera frame]
CB[chessboard_frame<br/>Origin at a1]
SQ[square_a1 ... square_h8<br/>64 virtual links]
BF --> BL
BF --> CAM
CAM -.->|"Detected pose"| CB
CB --> SQ
style CB stroke-dasharray: 5 5
sequenceDiagram
participant Cam as Camera
participant Per as Perception
participant Occ as Occupancy
participant Eng as Chess Engine
participant Stock as Stockfish
participant Man as Manipulation
participant Robot as Robot
Note over Cam,Robot: Human makes a move on the board
Cam->>Per: Compressed image
Per->>Occ: Homography + undistorted image
Occ->>Eng: Occupancy state (64 bools)
Note over Eng: Infer move from occupancy change
Eng->>Eng: Apply human's move
Eng->>Stock: Get best response
Stock->>Eng: Best move (UCI)
Eng->>Man: ExecuteChessMove
Man->>Man: Get square 3D position
Man->>Robot: Joint commands
Note over Robot: Robot executes pick-and-place
| Node | Package | Purpose |
|---|---|---|
system_camera_node |
vertical_chess | Capture from external camera (1280x720, 30fps) |
undistort_image_node |
vertical_chess | Fisheye lens distortion correction |
board_detection_node |
vertical_chess | ChArUco detection, homography computation |
occupancy_detection_node |
vertical_chess | Color-based piece occupancy tracking |
chess_engine_node |
vertical_chess | Chess logic, move inference, Stockfish interface |
pick_and_place_node |
vertical_chess | 17-state FSM for piece manipulation |
marker_locator_node |
vertical_chess | 3D square position lookup via URDF geometry |
| Topic | Type | Description |
|---|---|---|
camera/system/compressed |
CompressedImage | Raw camera feed (BEST_EFFORT QoS) |
camera/system/camera_info |
CameraInfo | Camera calibration parameters |
/camera_image/undistorted |
CompressedImage | Fisheye-corrected image (PNG) |
/chessboard_homography |
Float64MultiArray | 3x3 image-to-board transform |
/chessboard_pose_base |
PoseStamped | Board pose in robot base frame |
/occupancy_detection_node/occupancy_state |
OccupancyState | 64-bool occupancy array |
/chess_engine_node/board_state |
BoardState | FEN, turn, game status |
/chess_engine_node/execute_move |
ExecuteChessMove | Move command for robot |
/chess_engine_node/game_status |
GameStatus | Check/checkmate/stalemate events |
| Service | Type | Description |
|---|---|---|
/chess_engine_node/get_board_state |
GetBoardState | Current FEN and legal moves |
/chess_engine_node/make_move |
MakeMove | Apply a move to the board |
/chess_engine_node/get_best_move |
GetBestMove | Query Stockfish for best move |
/chess_engine_node/get_legal_moves |
GetLegalMoves | All legal moves from position |
/chess_engine_node/validate_move |
ValidateMove | Check if move is legal |
/chess_engine_node/reset_board |
ResetBoard | Reset to start or custom FEN |
/marker_locator_node/get_square_position |
GetSquarePosition | 3D position of any square |
/occupancy_detection_node/set_enabled |
SetBool | Pause occupancy during manipulation |
# Make a move
ros2 service call /chess_engine_node/make_move \
vertical_chess_interfaces/srv/MakeMove "{move: 'e4', validate: true}"
# Get best move
ros2 service call /chess_engine_node/get_best_move \
vertical_chess_interfaces/srv/GetBestMove "{fen: '', skill_level: -1, depth: -1, time_limit_ms: -1}"
# Reset board
ros2 service call /chess_engine_node/reset_board \
vertical_chess_interfaces/srv/ResetBoard "{fen: ''}"# Board state
ros2 topic echo /chess_engine_node/board_state
# Occupancy
ros2 topic echo /occupancy_detection_node/occupancy_state
# Game events
ros2 topic echo /chess_engine_node/game_status# Get 3D position of any square
ros2 service call /marker_locator_node/get_square_position \
vertical_chess_interfaces/srv/GetSquarePosition "{square: 'e4'}"# List all parameters
ros2 param list
# Get/set Stockfish skill
ros2 param get /chess_engine_node stockfish_skill_level
ros2 param set /chess_engine_node stockfish_skill_level 10# Launch RViz with robot model
ros2 launch ainex_description display.launch.pyConfigure RViz:
- Set Fixed Frame to
base_link - Add MarkerArray display for
/marker_locator_node/square_position_viz - Add TF display for coordinate frames
Publish test occupancy states:
# Starting position
ros2 topic pub --once /occupancy_detection_node/occupancy_state \
vertical_chess_interfaces/OccupancyState \
'{occupancy: [true,true,true,true,true,true,true,true,
true,true,true,true,true,true,true,true,
false,false,false,false,false,false,false,false,
false,false,false,false,false,false,false,false,
false,false,false,false,false,false,false,false,
false,false,false,false,false,false,false,false,
true,true,true,true,true,true,true,true,
true,true,true,true,true,true,true,true], changed: false}'- Robot: AiNex biped, fixed position 22cm from board
- Board: Vertical magnetic, 32cm x 32cm, 40mm squares
- Camera: Phone on tripod, 53cm from board, behind robot
- Pieces: Magnetic, white at top, black at bottom
CLAUDE.md- AI assistant context and architecture detailsdocs/COMMANDS.md- ROS2 command referencedocs/INTERFACES.md- Topic/service specifications