rustylink_chess/
lib.rs

1mod chessboard_device;
2
3use crate::chessboard_device::ChessboardDevice;
4use std::error::Error;
5use std::fmt::{Display, Formatter, Write};
6use std::sync::{Arc, Mutex};
7use std::thread;
8use std::thread::JoinHandle;
9
10const CHESS_PIECES: [char; 13] = [
11    '0', 'q', 'k', 'b', 'p', 'n', 'R', 'P', 'r', 'B', 'N', 'Q', 'K',
12];
13
14const BUFFER_SIZE: usize = 64;
15
16#[derive(Debug)]
17pub struct RlnError {
18    details: String,
19}
20
21impl Display for RlnError {
22    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
23        f.write_str(&self.details)
24    }
25}
26
27impl Error for RlnError {}
28
29#[derive(Debug, PartialEq, Eq)]
30pub enum Mode {
31    Default,
32    RealTime,
33    FileTransfer,
34}
35
36#[derive(Debug)]
37pub struct RustyLink {
38    device: Arc<Mutex<ChessboardDevice>>,
39    device_mode: Arc<Mutex<Mode>>,
40    rt_thread_handle: Option<JoinHandle<()>>,
41    fen: Arc<Mutex<String>>,
42    battery_state: Arc<Mutex<u8>>,
43    led_state: [u8; 8],
44}
45
46/// A connection to a chessboard device
47/// supports enabling/disabling LEDs, beeping and fetching FEN data from the board
48impl RustyLink {
49    pub fn connect(vendor_id: u16, product_id: u16) -> Result<RustyLink, RlnError> {
50        let c = ChessboardDevice::new(vendor_id, product_id)?;
51        Ok(RustyLink {
52            device: Arc::new(Mutex::new(c)),
53            device_mode: Arc::new(Mutex::new(Mode::Default)),
54            fen: Arc::new(Mutex::new(String::new())),
55            led_state: [0; 8],
56            rt_thread_handle: None,
57            battery_state: Arc::new(Mutex::new(0)),
58        })
59    }
60
61    /// Sets the board in default mode, which is the mode the board starts in.
62    /// In this mode, the FEN is not read, but you can still interact with the board
63    pub fn default_mode(&mut self) -> Result<(), RlnError> {
64        *self.device_mode.lock().unwrap() = Mode::Default;
65        if self.rt_thread_handle.is_some() {
66            let r = self.rt_thread_handle.take().unwrap().join();
67            if r.is_err() {
68                Err(RlnError {
69                    details: "Failed to join RT thread after ending RT mode".to_string(),
70                })
71            } else {
72                Ok(())
73            }
74        } else {
75            Err(RlnError {
76                details: "No thread handle to join".to_string(),
77            })
78        }
79    }
80
81    /// Sets the board to real-time mode.
82    /// In this mode, the FEN is continuously read.
83    /// Please note that this does not implement debouncing, therefore sliding a piece on the board
84    /// will successively register all positions the piece passes through.
85    pub fn real_time_mode(&mut self) -> Result<(), RlnError> {
86        let device = self.device.clone();
87        let mut device_l = self.device.lock().unwrap();
88        let r = device_l.set_real_time_mode();
89        if r.is_err() {
90            return Err(RlnError {
91                details: format!("Failed to set real time mode: {}", r.unwrap_err()),
92            });
93        }
94        *self.device_mode.lock().unwrap() = Mode::RealTime;
95        let device_mode = self.device_mode.clone();
96        let battery_state = self.battery_state.clone();
97        let fen = self.fen.clone();
98        //spawn read thread
99        let handle = thread::spawn(move || {
100            while *device_mode.lock().unwrap() == Mode::RealTime {
101                let mut data: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
102                let _ = device.lock().unwrap().read(&mut data).unwrap();
103                if data[0] == 0x2a {
104                    //battery status
105                    *battery_state.lock().unwrap() = data[2];
106                } else if data[0] == 0x01 {
107                    let mut fen_data: [u8; 34] = [0; 34];
108                    fen_data.copy_from_slice(&data[0..34]);
109                    let fen_str = to_fen(&fen_data);
110                    let mut f = fen.lock().unwrap();
111                    f.clear();
112                    f.push_str(&fen_str);
113                }
114            }
115        });
116        self.rt_thread_handle = Some(handle);
117        Ok(())
118    }
119
120    /// Returns the last FEN string registered by the board, if any. 
121    /// An empty string will be returned if the board was never put in real-time mode before.
122    pub fn fen(&self) -> String {
123        self.fen.lock().unwrap().clone()
124    }
125    
126    /// Returns the battery state, in percentage.
127    pub fn battery_state(&self) -> u8 {
128        //if device in real time mode, just grab the data, else ask
129        if *self.device_mode.lock().unwrap() == Mode::RealTime {
130            *self.battery_state.lock().unwrap()
131        } else {
132            let mut data: [u8; 3] = [0x29, 0x01, 0x00];
133            let _ = self.device.lock().unwrap().write(&data);
134            self.device.lock().unwrap().read(&mut data).unwrap();
135            data[2]
136        }
137    }
138
139    /// Update LEDs state, with each u8 representing a row and beginning with the H row.
140    pub fn update_led(&mut self, led_state: [u8; 8]) -> Result<(), RlnError> {
141        self.led_state = led_state;
142        let mut led_data: [u8; 10] = [0x0a, 0x08, 0, 0, 0, 0, 0, 0, 0, 0];
143        led_data[2..].copy_from_slice(&self.led_state);
144        let _ = self.device.lock().unwrap().write(&led_data)?;
145        Ok(())
146    }
147
148    ///Returns the LEDs state.
149    pub fn led(&self) -> [u8; 8] {
150        self.led_state
151    }
152
153    /// beeps with a specified frequency (in Hz) and duration (in ms)
154    /// Can be used to make music !?
155    pub fn beep(&mut self, frequency: u16, duration: u16) -> Result<(), RlnError> {
156        let data = [
157            0x0b,
158            0x04,
159            (frequency >> 8) as u8,
160            (frequency & 0xFF) as u8,
161            (duration >> 8) as u8,
162            (duration & 0xFF) as u8,
163        ];
164        let _ = self.device.lock().unwrap().write(&data)?;
165        Ok(())
166    }
167}
168
169fn to_fen(data: &[u8]) -> String {
170    let mut fen = String::new();
171    let mut empty = 0;
172    for i in 0..8 {
173        for j in (0..8).rev() {
174            let b = j % 2 == 0;
175            let c = match b {
176                true => {
177                    let index = data[(i * 8 + j) / 2 + 2] & 0x0f;
178                    CHESS_PIECES[index as usize]
179                }
180                false => {
181                    let index = data[(i * 8 + j) / 2 + 2] >> 4;
182                    CHESS_PIECES[index as usize]
183                }
184            };
185            if c == '0' {
186                empty += 1;
187            } else if empty > 0 {
188                fen.write_str(empty.to_string().as_str()).unwrap();
189                fen.write_str(c.to_string().as_str()).unwrap();
190                empty = 0;
191            } else {
192                fen.write_str(c.to_string().as_str()).unwrap();
193            }
194        }
195        if empty > 0 {
196            fen.write_str(empty.to_string().as_str()).unwrap();
197        }
198        if i < 7 {
199            fen.write_str("/").unwrap();
200        }
201        empty = 0;
202    }
203    fen
204}