use std::{
collections::BTreeMap as Map,
ffi::CStr,
thread::sleep,
time::{Duration, Instant},
};
use hidapi::HidDevice as Device;
use crate::{Color, Error, Piece, Position, Response, Result, Role, Square};
pub const VID: u16 = 0x2d80;
pub const USAGE_PAGE: u16 = 0xFF00;
pub fn pids() -> Map<u16, &'static str> {
pub const PIDS: &[u16] = &[0x8000, 0x8100, 0x8200, 0x8300, 0x8400, 0x8500, 0x8600];
PIDS.iter().copied().map(|pid| (pid, "unknown")).collect()
}
#[derive(Debug)]
pub struct Connection {
pub(crate) device: Device,
earliest_write: Option<Instant>,
read_timeout: Duration,
}
fn parse_nibble(nibble: u8) -> Result<Option<Piece>> {
use {Color::*, Role::*};
if nibble == 0 {
return Ok(None);
}
let (color, role) = match nibble {
0x1 => (Black, Queen),
0x2 => (Black, King),
0x3 => (Black, Bishop),
0x4 => (Black, Pawn),
0x5 => (Black, Knight),
0x6 => (White, Rook),
0x7 => (White, Pawn),
0x8 => (Black, Rook),
0x9 => (White, Bishop),
0xA => (White, Knight),
0xB => (White, Queen),
0xC => (White, King),
_ => {
error!("problem parsing {nibble} as piece");
return Err(Error::InvalidReport("nibble not a piece"));
}
};
Ok(Some(Piece { color, role }))
}
impl TryFrom<(u8, Vec<u8>)> for Response {
type Error = Error;
fn try_from((indicator, report): (u8, Vec<u8>)) -> Result<Response> {
match indicator {
0x01 => {
if report.len() < 32 {
return Err(Error::InvalidReport("short position report"));
}
let mut square = 64u8;
let mut position = Position::empty();
for byte in report.iter().take(32) {
square -= 1;
if let Some(piece) = parse_nibble(byte & 0xF)? {
let square = Square::try_from(square).unwrap();
position.set_piece_at(square, piece);
}
square -= 1;
if let Some(piece) = parse_nibble((byte & 0xF0) >> 4)? {
let square = Square::try_from(square).unwrap();
position.set_piece_at(square, piece);
}
}
Ok(Response::Position(position))
}
0x28 => {
let (kind, content) = report
.split_first()
.ok_or(Error::InvalidReport("version report missing sub-indicator"))?;
let content = CStr::from_bytes_until_nul(content)
.map_err(|_| Error::InvalidReport("version report invalid C string"))?
.to_string_lossy()
.into_owned();
match kind {
0x0 => Ok(Response::BleVersion(content)),
0x1 => Ok(Response::McuVersion(content)),
_ => Err(Error::InvalidReport("version report invalid version kind")),
}
}
_ => Err(Error::UnknownReport(indicator)),
}
}
}
impl Connection {
const READ_TIMEOUT: Duration = Duration::from_millis(100);
const WRITE_INTERVAL: Duration = Duration::from_millis(200);
pub fn new() -> Result<Self> {
info!(":: HID connect");
let mut api = hidapi::HidApi::new_without_enumerate().map_err(Error::Connect)?;
api.add_devices(VID, 0).map_err(Error::Connect)?;
let pids = pids();
let mut iter = api.device_list().filter(|info| {
pids.contains_key(&(info.product_id() & 0xFF00)) && info.usage_page() == USAGE_PAGE
});
let info = iter.next().ok_or(Error::NoBoardFound)?;
if iter.next().is_some() {
}
let device = info.open_device(&api).map_err(Error::Connect)?;
let info = device.get_device_info().map_err(Error::Connect)?;
info!("...ok {:x}:{:x}", info.vendor_id(), info.product_id());
Ok(Self { device, earliest_write: None, read_timeout: Self::READ_TIMEOUT })
}
pub fn report(&mut self) -> Result<(u8, Vec<u8>)> {
const MAX_SHORT_HID_REPORTS: usize = 5;
let mut hid_report = vec![0u8; 256];
let mut short_hid_reports = 0;
loop {
let size = self
.device
.read_timeout(&mut hid_report, self.read_timeout.as_millis() as _)
.map_err(Error::Read)?;
hid_report.resize(size, 0);
debug!("<- {}", hex::encode(&hid_report));
if hid_report.len() < 2 {
warn!("too short HID report: {}", hex::encode(&hid_report));
short_hid_reports += 1;
if short_hid_reports >= MAX_SHORT_HID_REPORTS {
error!("{MAX_SHORT_HID_REPORTS} consecutive short HID reports");
return Err(Error::BoardNotOn);
}
continue;
}
let mut report = hid_report.split_off(2);
let indicator = hid_report[0];
let len = hid_report[1] as usize;
if report.len() < len {
warn!(
"too short report (indicator {}, len {}): {}",
indicator,
len,
hex::encode(&report)
);
continue;
}
report.resize(len, 0);
return Ok((indicator, report));
}
}
pub fn execute(&mut self, command: &[u8]) -> Result<()> {
debug!("-> {}", hex::encode(command));
self.earliest_write.into_iter().for_each(sleep_until);
let count = self.device.write(command).map_err(Error::Write)?;
assert_eq!(count, command.len());
self.earliest_write = Some(Instant::now() + Self::WRITE_INTERVAL);
Ok(())
}
pub fn set_mode<M: crate::Mode>(mut self) -> Result<Self> {
use crate::Command;
info!(":: mode {}", M::MODE);
self.execute(&Command::switch_mode::<M>())?;
debug!("...ok");
Ok(self)
}
}
fn sleep_until(deadline: Instant) {
let now = Instant::now();
if let Some(delay) = deadline.checked_duration_since(now) {
sleep(delay);
}
}