This is the C/C++ SDK for Chessnut electronic chess computers, such as the Chessnut Air.
Licensed under The MIT License (MIT).
- Get the position of the pieces on the chessboard in real-time
- Control the display of the LED lights on the chessboard
- Make a beeping sound
- Query the version of the hardware
- Query the battery level
- Get game recordings from internal storage
$ just
Available recipes:
build # build for Debug
clang-details # print clang details, including environment and architecture
clangd-check filename # run a clangd check on a single file
clean # clean
configure # configure a build
coverage # generate code coverage report
default # print available targets
do # clean, compile, build for Debug
docs # generated documentation (requires Doxygen)
format # format source code (.c and .h files) with clang-format
just-vars # evaluate and print all just variables
release # build for Release
run *args # run a Debug binary
run-release *args # run a Release binary
system-info # print system information such as OS and architecture
tidy # run clang-tidy (see .clang-tidy)
tidy-checks # show configured checks of clang-tidy
tidy-config # show effective configuration of clang-tidy
tidy-verify-config # verify configuration of clang-tidy
An example application is available at src/main.c.
- Call
cl_connect()
to connect to the chessboard. - Call
cl_disconnect()
to disconnect from the chessboard.
#include <stdio.h>
#include <stdlib.h>
#include "easy_link_c.h"
int main(void) {
// Connect to the chessboard device with HID. If the device is not
// connected, it will automatically connect when the device is plugged into
// the computer.
if (cl_connect() == 1) {
printf("Successfully connected to chessboard\n");
} else {
printf("[ERROR] Failed to connect to chessboard. Exiting ...\n");
return EXIT_FAILURE;
}
// Disconnect from the board.
cl_disconnect();
return EXIT_SUCCESS;
}
- Call
cl_connect()
to connect to the chessboard. - Create a callback function whose parameter
const char *fen
will be populated with a FEN string (Forsyth-Edwards Notation) that describes the board position at the time of the callback, and the parameterint len
will represent the size of this FEN string. - Register the callback function through the function
cl_set_readtime_callback(callback)
.- You can disable/unregister the callback function by registering a NULL pointer as the callback.
- Finally, switch the chessboard mode to real-time mode with
cl_switch_real_time_mode()
. Setting the real-time mode is required, otherwise you cannot obtain the FEN of the chessboard's current state.
#include <stdio.h>
#include "easy_link_c.h"
void callback(const char *fen, int len) {
printf("Board position in FEN: %.*s\n", len, fen);
}
int main(void) {
cl_connect(); // we skip error handling here for the sake of brevity
cl_set_readtime_callback(callback);
cl_switch_real_time_mode();
for (;;) {
getchar();
}
cl_disconnect();
}
- Call
cl_connect()
to connect to the chessboard. - Create a
char **led
array of 8 strings with 8 chars each, representing the LED states of the 8x8 chessboard.- The first string represents row
8
, the last string is row1
on the board. - The first character in a string is colum
a
, the last character is columnh
. - Char
'1'
in a string means enable the LED of the associated square. Char'0'
means disable the LED.
- The first string represents row
- Set the LED state of the chessboard via
cl_led(const char *leds[8])
.
Illustration for const char *leds[8]
:
abcdefgh
8........8 leds[0]
7........7 leds[1]
6........6 leds[2]
5........5 leds[3]
4..x.....4 leds[4]
3........3 leds[5]
2........2 leds[6]
1........1 leds[7]
abcdefgh
For example, to enable the LED for square c4
only ('x' above), use:
{ "00000000",
"00000000",
"00000000",
"00000000",
"00100000", // <-- here
"00000000",
"00000000",
"00000000" };
Full example:
#include <stdio.h>
#include "easy_link_c.h"
int main(void) {
cl_connect(); // we skip error handling here for the sake of brevity
const char *leds[8] = { "11111111",
"00000000",
"11111111",
"00000000",
"11111111",
"00000000",
"11111111",
"00000000" };
if (cl_led(leds) == 0) {
printf("[ERROR] Could not enable/disable LEDs\n");
}
cl_disconnect();
}
- Call
cl_connect()
to connect to the chessboard. - Call
cl_beep(unsigned short frequencyHz, unsigned short durationMs)
. The parameters set the frequency (in Hz) and duration (in milliseconds) of the buzzer, respectively.
#include "easy_link_c.h"
int main(void) {
cl_connect(); // we skip error handling here for the sake of brevity
if (cl_beep(1000, 200) == 0) {
printf("[ERROR] failed to beep\n");
}
cl_disconnect();
}
- Call
cl_connect()
to connect to the chessboard. - Query the available versions of the hardware by providing a string (
char*
) into which the information will be stored.cl_version(char *version)
: version of the SDK library; theversion
parameter should have a length of at least 20cl_get_mcu_version(char *version)
: version of the MCU hardware; theversion
parameter should have a length of at least 100cl_get_ble_version(char *version)
: version of the BLE hardware (Bluetooth Low Energy); theversion
parameter should have a length of at least 100
- All three functions return the length of the actual data written to the
passed parameter, or
0
if the function call failed.
#include <stdio.h>
#include "easy_link_c.h"
int main(void) {
cl_connect(); // we skip error handling here for the sake of brevity
char sdk_version[20];
const size_t sdk_version_length = cl_version(sdk_version);
if (sdk_version_length > 0) {
printf("[DEBUG] SDK version: %.*s\n", (int)sdk_version_length, sdk_version);
// Example output:
// SDK version: 1.0.0
} else {
printf("[ERROR] Could not get SDK version\n");
}
char mcu_version[100_mcu_version_length];
const size_t mcu_version_length = cl_get_mcu_version(mcu_version);
if (mcu_version_length > 0) {
printf("MCU hardware version: %.*s\n", (int)mcu_version_length, mcu_version);
// Example output:
// MCU hardware version: CN_DVT9_210659
} else {
printf("[ERROR] Could not query MCU hardware version\n");
}
char ble_version[100_ble_version_length];
const size_t ble_version_length = cl_get_ble_version(ble_version);
if (ble_version_length > 0) {
printf("BLE hardware version: %.*s\n", (int)ble_version_length, ble_version);
// Example output:
// BLE hardware version: CNCA100_V201
} else {
printf("[ERROR] Could not query BLE hardware version\n");
}
cl_disconnect();
}
- Call
cl_connect()
to connect to the chess board. - Query the battery level with
cl_get_battery()
. Note that the battery level is only an estimate that is not always accurate.
#include <stdio.h>
#include "easy_link_c.h"
int main(void) {
cl_connect(); // we skip error handling here for the sake of brevity
const int battery_level = cl_get_battery();
if (battery_level >= 0) {
printf("Battery level: %d%%\n", battery_level);
// Example output:
// Battery level: 67%
} else {
printf("[ERROR] Could not query the battery level\n");
}
cl_disconnect();
}
The chess board can store replays of played matches in its internal storage.
The file format is a sequence of FEN strings, separated by ;
. It is
recommended to validate the FEN strings after retrieval, as the board will,
for example, faithfully record board positions verbatim, even illegal ones.
Example game file content:
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR;rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR;rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR
To retrieve recorded game files from internal storage:
- Call
cl_connect()
to connect to the chess board. - It is recommended to query the number of available game files first via
cl_get_file_count()
before attempting to retrieve any files. - CAUTION: Retrieve the next available game file with
cl_get_file_and_delete(char* game_data, size_t len)
and then automatically delete the game file from the chessboard's internal storage.- DANGER: If the size of
char *game_data
is too small to fully store the game file, then the game file is still irrevocably deleted from the chessboard's internal storage! - Use the safer variant
cl_get_file_and_keep()
to perform a dry-run of file retrieval first (e.g., to verify that the providedchar *game_data
is sufficiently large), and only if this succeeds do you callcl_get_file_and_delete()
. - Unfortunately, the SDK does not allow you to retrieve specific game files
or iterate through them without deletion. The only way to "advance the
cursor" and iterate through available game files is by repeatedly calling
cl_get_file_and_delete()
or its aliascl_get_file()
, both of which will automatically delete the current game file after retrieval; the next call will then operate on the next game file.
- DANGER: If the size of
- Calling this function will automatically set the board's mode to file upload mode.
#include <stdio.h>
#include "easy_link_c.h"
int main(void) {
cl_connect(); // we skip error handling here for the sake of brevity
const int file_count = cl_get_file_count();
if (file_count >= 0) {
printf("Stored game files: %d\n", file_count);
if (file_count > 0) {
// You must ensure that the retrieved game file fits into `game_content`,
// otherwise retrieval will fail and the respective game fail is lost!
char game_content[1024 * 10];
const int game_file_len = cl_get_file_and_delete(game_content, sizeof(game_content));
if (game_file_len > 0) {
printf("Game file content: %.*s\n", game_file_len, game_content);
//
// Example output:
// Game file content: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR;rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR;rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR
//
} else if (game_file_len == 0) {
printf("[WARNING] The game file is empty\n");
} else {
// When the provided `game_content` parameter was too small
printf("[ERROR] Failed to retrieve game file, which is now irrevocably lost\n");
}
}
} else {
printf("[ERROR] Could not retrieve number of stored game files");
}
cl_disconnect();
}
Supported platforms:
- Windows
- Linux
- Mac
Dependencies:
Further details are provided below.
-
Clone this project's git repository. (In "normal" Windows, not in WSL/Linux.)
-
Use Visual Studio 2022 (Community Edition) and open the project directory.
-
Visual Studio should automatically configure the project via its built-in cmake.
-
Run
Build > Build All
to compile the project, including the EasyLinkSDK DLLeasylink.dll
and the main applicationmain.exe
that depends on the EasyLinkSDK DLL. -
Once the compilation is completed, open a terminal in PowerShell (or
cmd.exe
) and run:# 1. Make easylink.ddl available to main.exe $ cp out\build\x64-Debug\sdk\easylink.dll .\out\build\x64-Debug\src # 2. Run main.exe $ out\build\x64-Debug\src\main.exe
TODO: Automate and verify the Windows build setup. In the meantime, see the notes below as well as the GitHub workflow configuration, which uses Windows.
Install dependencies (e.g., with choco):
# clang toolchain
choco install -y cmake llvm ninja
choco install -y doxygen.install # optional, for generating documentation
# https://github.com/casey/just (a command runner)
choco install -y just # optional, but convenient
Then run the same configure and compile steps as described in the Linux/macOS section below.
use with gnu/linux
In order to use EasyLink as a user in the wheel group ( group can be arbitrary ) You must give the user read and write permissions for the Chessnut air. This can be done through a udev rule.
create a 99-chessnutair.rules file: /etc/udev/rules.d/99-chessnutair.rules, with the following:
SUBSYSTEM=="usb", ATTRS{idVendor}:="0x2d80", / ATTRS{idProduct}:="0x8002", GROUP="wheel", MODE="0660"
KERNEL=="hidraw2", GROUP="wheel", MODE="0660"
======== end =========
Currently supported USB Vender ID and Product ID
Vender ID: 0x2d80 Product IDs for different models:
Air : 0x80** Pro : 0x81** Air+: 0x82** Evo: 0x83** Go: 0x85**
Install dependencies:
### Debian/Ubuntu (EXPERIMENTAL: project compiles, but can't connect to chessboard)
# clang toolchain
sudo apt-get install -y build-essential clang clang-tidy cmake lldb ninja-build
sudo apt-get install -y doxygen # optional, for generating documentation
# Dependencies for EasyLinkSDK
sudo apt install libudev-dev libusb-dev libusb-1.0-0-dev
### macOS (EXPERIMENTAL: project compiles, but can't connect to chessboard)
# clang toolchain
brew install cmake llvm ninja
brew install doxygen # optional, for generating documentation
Ensure that the build setup uses clang as defined in .env:
# .env
CC=clang
CXX=clang++
Run the build with just
(or use cmake
directly):
# Option 1: You have just (https://github.com/casey/just) installed
$ just do
# Option 2: Use cmake directly
#
# 1. Configure
$ cmake -B build/ -S . -G "Ninja Multi-Config" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
# 2. Compile (pick one of the two)
$ cmake --build build/ --config Debug --target all # for a Debug build
$ cmake --build build/ --config Release --target all # for a Release build
If compilation succeeded, you can now run the main application that will attempt to connect to your chessboard.
- Turn the chessboard on.
- Connect your computer directly to the board via a wired USB cable
- Unfortunately, a wireless connection via BLE (Bluetooth Low Energy) seems not to work yet?
- Execute
just run
(or directlybuild/src/Debug/main
) to run the main application. See example below.
If the connection to the chessboard is successful, you will see output similar to:
# Or, from the top-level project directory:
#
# $ build/src/Debug/main (if you build the Debug version)
# $ build/src/Release/main (if you build the Release version)
#
$ just run
[DEBUG] SDK version: 1.0.0
[DEBUG] Connecting to chessboard via HID...
Successfully connected to chessboard
MCU hardware version: CN_DVT9_210659
BLE hardware version: CNCA100_V201
Battery level: 100%
[DEBUG] Disconnecting from chessboard
$