use core::time::Duration;
use std::collections::BTreeSet as Set;
#[macro_use(debug, error, info, warn)]
extern crate tracing;
pub use shakmaty::{Color, Piece, Role, Square, board::Board as Position};
use thiserror::Error;
mod hid;
pub mod util;
pub type Result<T, E = Error> = core::result::Result<T, E>;
pub fn init_tracing() {
use tracing_subscriber::{
EnvFilter, fmt, layer::SubscriberExt as _, util::SubscriberInitExt as _,
};
tracing_subscriber::registry().with(fmt::layer()).with(EnvFilter::from_env("LOG_LEVEL")).init();
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error("board connect error: {0:?}")]
Connect(hidapi::HidError),
#[error("board read error: {0:?}")]
Read(hidapi::HidError),
#[error("board write error: {0:?}")]
Write(hidapi::HidError),
#[error("no Chessnut board found")]
NoBoardFound,
#[error("Chessnut board not turned on")]
BoardNotOn,
#[error("invalid report: {0}")]
InvalidReport(&'static str),
#[error("unknown report: {0}")]
UnknownReport(u8),
}
#[derive(Debug)]
pub struct Board<M: Mode> {
connection: hid::Connection,
lit: Set<Square>,
#[allow(dead_code)]
mode: M,
}
#[derive(Debug)]
pub enum Response {
BatteryLevel(u8),
BleVersion(String),
McuVersion(String),
Position(Position),
}
struct Command;
impl Command {
const BATTERY_LEVEL: &[u8] = &[0x29, 0x01, 0x00];
const MCU_VERSION: &[u8] = &[0x27, 0x01, 0x01];
const BLE_VERSION: &[u8] = &[0x27, 0x01, 0x00];
fn beep(beep: Beep) -> Vec<u8> {
let Beep { duration, frequency } = beep;
let duration = duration.as_millis() as u16;
let mut command: Vec<_> = [0x0b, 0x04].into();
command.extend(frequency.to_be_bytes());
command.extend(duration.to_be_bytes());
command
}
fn lighten(squares: &Set<Square>) -> Vec<u8> {
let mut encoding = [0u8; 8];
for square in squares.iter() {
let (file, rank) = square.coords();
encoding[(7 - rank as u8) as usize] |= 1 << (7 - file as u8);
}
let mut command: Vec<_> = [0x0a, 0x08].into();
command.extend(encoding);
command
}
fn switch_mode<M: Mode>() -> Vec<u8> {
let mut command: Vec<_> = [0x21, 0x01].into();
command.push(M::MODE_BYTE);
command
}
}
impl Board<Realtime> {
pub fn realtime() -> Result<Self> {
let connection = hid::Connection::new()?.set_mode::<Realtime>()?;
let mut board = Board { connection, lit: Default::default(), mode: Realtime };
board.darken()?;
board.position()?;
Ok(board)
}
}
impl Board<Upload> {
pub fn upload() -> Result<Board<Upload>> {
let connection = hid::Connection::new()?.set_mode::<Upload>()?;
let mut board = Board { connection, lit: Default::default(), mode: Upload };
board.darken()?;
Ok(board)
}
}
impl<M: Mode> Board<M> {
fn execute(&mut self, command: &[u8]) -> Result<()> {
debug!("-> {}", hex::encode(command));
self.connection.execute(command)
}
pub fn battery_level(&mut self) -> Result<u8> {
info!(":: battery-level");
self.execute(Command::BATTERY_LEVEL)?;
loop {
let (indicator, report) = self.connection.report()?;
if indicator == 0x2a {
assert_eq!(report.len(), 2);
let battery_level = report[0];
info!("...ok {battery_level}");
return Ok(battery_level);
}
}
}
pub fn beep(&mut self, beep: Beep) -> Result<()> {
info!(":: beep {beep:?}");
self.execute(&Command::beep(beep))?;
info!("...ok");
Ok(())
}
pub fn beep_default(&mut self) -> Result<()> {
self.beep(Beep::default())
}
pub fn ble_version(&mut self) -> Result<String> {
info!(":: ble-version");
let mut i = 0;
loop {
if i % 10 == 0 {
self.execute(Command::BLE_VERSION)?;
}
i += 1;
if let Ok(Response::BleVersion(version)) = self.connection.report()?.try_into() {
info!("...ok {version}");
return Ok(version);
}
}
}
pub fn mcu_version(&mut self) -> Result<String> {
info!(":: mcu-version");
let mut i = 0;
loop {
if i % 3 == 0 {
self.execute(Command::MCU_VERSION)?;
}
i += 1;
if let Ok(Response::McuVersion(version)) = self.connection.report()?.try_into() {
info!("...ok {version}");
return Ok(version);
}
}
}
pub fn set_led(&mut self, square: Square, on: bool) -> Result<()> {
let mut squares = self.lit.clone();
if on {
squares.insert(square);
} else {
squares.remove(&square);
}
self.lighten(squares)
}
pub fn led_on(&mut self, square: Square) -> Result<()> {
self.set_led(square, true)
}
pub fn led_off(&mut self, square: Square) -> Result<()> {
self.set_led(square, false)
}
pub fn lighten(&mut self, squares: Set<Square>) -> Result<()> {
info!(":: lighten {squares:?}");
self.execute(&Command::lighten(&squares))?;
self.lit = squares;
info!("...ok");
Ok(())
}
pub fn darken(&mut self) -> Result<()> {
self.lighten(Set::default())
}
pub fn disco(&mut self, count: usize) -> Result<()> {
use crate::util::Squares;
for _ in 0..count {
self.lighten(Squares::light())?;
self.lighten(Squares::dark())?;
}
Ok(())
}
pub fn position(&mut self) -> Result<Position> {
info!(":: position");
loop {
if let Ok(Response::Position(position)) = self.connection.report()?.try_into() {
info!("...ok {position}");
return Ok(position);
}
}
}
}
impl Board<Upload> {
pub fn games(&mut self, _delete: bool) -> Result<Vec<String>> {
todo!();
}
}
impl Board<Realtime> {
pub fn to_upload(self) -> Result<Board<Upload>> {
let Board { connection, lit, .. } = self;
let connection = connection.set_mode::<Upload>()?;
Ok(Board { connection, lit, mode: Upload })
}
}
impl Board<Upload> {
pub fn to_realtime(self) -> Result<Board<Realtime>> {
let Board { connection, lit, .. } = self;
let connection = connection.set_mode::<Realtime>()?;
Ok(Board { connection, lit, mode: Realtime })
}
}
pub trait Mode: private::Mode {}
impl<M: private::Mode> Mode for M {}
mod private {
pub trait Mode {
const MODE: &str;
const MODE_BYTE: u8;
}
impl Mode for crate::Realtime {
const MODE: &str = "realtime";
const MODE_BYTE: u8 = 0x00;
}
impl Mode for crate::Upload {
const MODE: &str = "upload";
const MODE_BYTE: u8 = 0x01;
}
}
#[derive(Copy, Clone, Debug)]
pub struct Realtime;
#[derive(Copy, Clone, Debug)]
pub struct Upload;
#[derive(Copy, Clone, Debug)]
pub struct Beep {
duration: Duration,
frequency: u16,
}
impl Beep {
pub const DEFAULT_DURATION: Duration = Duration::from_millis(200);
pub const DEFAULT_FREQUENCY: u16 = 1000;
pub fn new() -> Self {
Self { duration: Self::DEFAULT_DURATION, frequency: Self::DEFAULT_FREQUENCY }
}
pub fn set_duration(mut self, duration: Duration) -> Self {
self.duration = duration;
self
}
pub fn set_frequency(mut self, frequency: u16) -> Self {
self.frequency = frequency;
self
}
}
impl Default for Beep {
fn default() -> Self {
Self::new()
}
}