1use std::collections::HashMap;
15use std::io::{self, Read, Write};
16use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
17use std::sync::{mpsc, Arc, Mutex};
18use std::thread;
19use std::time::{Duration, Instant};
20
21use irbt_proto::{Command, Decoded, Decoder, Frame, Reply, Request};
22
23pub use irbt_proto as proto;
24pub use irbt_proto::{
25 Battery, BuildInfo, Component, DeviceInfo, Event, HidKey, IrCode, Log, LogLevel, Mapping,
26 Status, PROTOCOL_VERSION,
27};
28
29pub const USB_VID: u16 = 0x303A;
31pub const USB_PID: u16 = 0x1001;
32
33pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(2);
35
36const READ_TIMEOUT: Duration = Duration::from_millis(50);
39
40const NOTIFICATION_BACKLOG: usize = 1024;
42
43#[derive(Debug, thiserror::Error)]
44pub enum Error {
45 #[error("I/O error: {0}")]
46 Io(#[from] io::Error),
47 #[error("serial port error: {0}")]
48 Serial(#[from] serialport::Error),
49 #[error("failed to encode request: {0}")]
50 Encode(irbt_proto::EncodeError),
51 #[error("no ir-bt-bridge found (looking for USB {USB_VID:04x}:{USB_PID:04x}); pass a port explicitly")]
52 NoDevice,
53 #[error("several candidate devices found ({}); pass a port explicitly", .0.join(", "))]
54 MultipleDevices(Vec<String>),
55 #[error("device speaks protocol v{device}, this tool speaks v{host}; update the firmware or the tool")]
56 ProtocolMismatch { device: u16, host: u16 },
57 #[error("timed out waiting for the device")]
58 Timeout,
59 #[error("device disconnected")]
60 Disconnected,
61 #[error("device error: {0}")]
62 Device(String),
63 #[error("unexpected reply from device: {0:?}")]
64 UnexpectedReply(Reply),
65}
66
67pub type Result<T, E = Error> = std::result::Result<T, E>;
68
69#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum Notification {
72 Event(Event),
73 Log(Log),
74 Raw(String),
77}
78
79type Pending = Arc<Mutex<HashMap<u32, mpsc::Sender<Result<Reply, String>>>>>;
80
81pub struct Device {
83 writer: Mutex<Box<dyn Write + Send>>,
84 pending: Pending,
85 notifications: Mutex<mpsc::Receiver<Notification>>,
86 alive: Arc<AtomicBool>,
87 next_id: AtomicU32,
88 timeout: Duration,
89}
90
91pub fn find_ports() -> Result<Vec<String>> {
97 Ok(serialport::available_ports()?
98 .into_iter()
99 .filter(|port| {
100 matches!(&port.port_type, serialport::SerialPortType::UsbPort(usb)
101 if usb.vid == USB_VID && usb.pid == USB_PID)
102 })
103 .map(|port| port.port_name)
104 .collect())
105}
106
107fn find_single_port() -> Result<String, Error> {
108 let mut ports = find_ports()?;
109 if ports.len() > 1 {
110 return Err(Error::MultipleDevices(ports));
111 }
112 ports.pop().ok_or(Error::NoDevice)
113}
114
115impl Device {
116 pub fn open(port: Option<&str>) -> Result<Self> {
127 let path = match port {
128 Some(path) => path.to_owned(),
129 None => find_single_port()?,
130 };
131
132 let mut port = serialport::new(&path, 115_200)
133 .timeout(READ_TIMEOUT)
134 .open()?;
135 port.write_request_to_send(false)?;
139 port.write_data_terminal_ready(false)?;
140
141 let reader = port.try_clone()?;
142 let device = Self::from_io(reader, port);
143 device.handshake()?;
144 Ok(device)
145 }
146
147 pub fn from_io(
158 reader: impl Read + Send + 'static,
159 writer: impl Write + Send + 'static,
160 ) -> Self {
161 let pending = Pending::default();
162 let alive = Arc::new(AtomicBool::new(true));
163 let (tx, rx) = mpsc::sync_channel(NOTIFICATION_BACKLOG);
164
165 {
166 let pending = pending.clone();
167 let alive = alive.clone();
168 thread::spawn(move || {
169 read_loop(reader, &pending, &tx);
170 alive.store(false, Ordering::SeqCst);
171 pending.lock().unwrap().clear();
173 });
174 }
175
176 Self {
177 writer: Mutex::new(Box::new(writer)),
178 pending,
179 notifications: Mutex::new(rx),
180 alive,
181 next_id: AtomicU32::new(1),
182 timeout: DEFAULT_TIMEOUT,
183 }
184 }
185
186 pub fn set_timeout(&mut self, timeout: Duration) {
187 self.timeout = timeout;
188 }
189
190 fn handshake(&self) -> Result<DeviceInfo> {
191 let info = self.info()?;
192 if info.protocol_version != PROTOCOL_VERSION {
193 return Err(Error::ProtocolMismatch {
194 device: info.protocol_version,
195 host: PROTOCOL_VERSION,
196 });
197 }
198 Ok(info)
199 }
200
201 pub fn call(&self, command: Command) -> Result<Reply> {
214 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
215 let wire =
216 irbt_proto::encode(&Frame::Request(Request { id, command })).map_err(Error::Encode)?;
217
218 let (tx, rx) = mpsc::channel();
219 self.pending.lock().unwrap().insert(id, tx);
220 let forget = || {
221 self.pending.lock().unwrap().remove(&id);
222 };
223
224 if !self.alive.load(Ordering::SeqCst) {
225 forget();
226 return Err(Error::Disconnected);
227 }
228 let written = {
229 let mut writer = self.writer.lock().unwrap();
230 writer.write_all(&wire).and_then(|()| writer.flush())
231 };
232 if let Err(e) = written {
233 forget();
234 return Err(e.into());
235 }
236
237 match rx.recv_timeout(self.timeout) {
238 Ok(Ok(reply)) => Ok(reply),
239 Ok(Err(message)) => Err(Error::Device(message)),
240 Err(mpsc::RecvTimeoutError::Timeout) => {
241 forget();
242 Err(Error::Timeout)
243 }
244 Err(mpsc::RecvTimeoutError::Disconnected) => Err(Error::Disconnected),
245 }
246 }
247
248 fn call_ok(&self, command: Command) -> Result<()> {
249 match self.call(command)? {
250 Reply::Ok => Ok(()),
251 other => Err(Error::UnexpectedReply(other)),
252 }
253 }
254
255 pub fn ping(&self) -> Result<()> {
262 match self.call(Command::Ping)? {
263 Reply::Pong => Ok(()),
264 other => Err(Error::UnexpectedReply(other)),
265 }
266 }
267
268 pub fn info(&self) -> Result<DeviceInfo> {
275 match self.call(Command::GetInfo)? {
276 Reply::Info(info) => Ok(info),
277 other => Err(Error::UnexpectedReply(other)),
278 }
279 }
280
281 pub fn build_info(&self) -> Result<BuildInfo> {
290 match self.call(Command::GetBuildInfo)? {
291 Reply::BuildInfo(info) => Ok(info),
292 other => Err(Error::UnexpectedReply(other)),
293 }
294 }
295
296 pub fn status(&self) -> Result<Status> {
303 match self.call(Command::GetStatus)? {
304 Reply::Status(status) => Ok(status),
305 other => Err(Error::UnexpectedReply(other)),
306 }
307 }
308
309 pub fn keymap(&self) -> Result<Vec<Mapping>> {
316 let mut mappings = Vec::new();
317 loop {
318 let offset = u16::try_from(mappings.len()).unwrap_or(u16::MAX);
319 let page = match self.call(Command::ListKeymap { offset })? {
320 Reply::Keymap(page) => page,
321 other => return Err(Error::UnexpectedReply(other)),
322 };
323 let done = page.entries.is_empty()
324 || usize::from(page.offset) + page.entries.len() >= usize::from(page.total);
325 mappings.extend(page.entries);
326 if done {
327 return Ok(mappings);
328 }
329 }
330 }
331
332 pub fn set_mapping(&self, ir: IrCode, key: HidKey) -> Result<()> {
338 self.call_ok(Command::SetMapping(Mapping { ir, key }))
339 }
340
341 pub fn remove_mapping(&self, ir: IrCode) -> Result<()> {
347 self.call_ok(Command::RemoveMapping { ir })
348 }
349
350 pub fn clear_keymap(&self) -> Result<()> {
357 self.call_ok(Command::ClearKeymap)
358 }
359
360 pub fn reset_keymap(&self) -> Result<()> {
367 self.call_ok(Command::ResetKeymap)
368 }
369
370 pub fn learn(&self, key: HidKey, timeout: Duration) -> Result<Option<Mapping>> {
381 let timeout_ms = u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX);
382 self.call_ok(Command::Learn { key, timeout_ms })?;
383
384 let deadline = Instant::now() + timeout + self.timeout;
386 loop {
387 let left = deadline.saturating_duration_since(Instant::now());
388 match self.next_notification(left)? {
389 Some(Notification::Event(Event::Learned(mapping))) => return Ok(Some(mapping)),
390 Some(Notification::Event(Event::LearnTimedOut)) => return Ok(None),
391 Some(Notification::Event(Event::LearnFailed { reason })) => {
392 return Err(Error::Device(reason))
393 }
394 Some(_) => {}
395 None => return Err(Error::Timeout),
396 }
397 }
398 }
399
400 pub fn cancel_learn(&self) -> Result<()> {
407 self.call_ok(Command::CancelLearn)
408 }
409
410 pub fn send_hid(&self, key: HidKey) -> Result<()> {
417 self.call_ok(Command::SendHid { key })
418 }
419
420 pub fn send_ir(&self, code: IrCode) -> Result<()> {
427 self.call_ok(Command::SendIr { code })
428 }
429
430 pub fn set_monitor(&self, enabled: bool) -> Result<()> {
437 self.call_ok(Command::SetMonitor { enabled })
438 }
439
440 pub fn next_notification(&self, timeout: Duration) -> Result<Option<Notification>> {
452 match self.notifications.lock().unwrap().recv_timeout(timeout) {
453 Ok(notification) => Ok(Some(notification)),
454 Err(mpsc::RecvTimeoutError::Timeout) => Ok(None),
455 Err(mpsc::RecvTimeoutError::Disconnected) => Err(Error::Disconnected),
456 }
457 }
458}
459
460fn read_loop(mut reader: impl Read, pending: &Pending, notify: &mpsc::SyncSender<Notification>) {
461 let mut decoder = Decoder::new();
462 let mut buf = [0u8; 512];
463
464 let mut dispatch = |decoded: Decoded| {
465 let notification = match decoded {
466 Decoded::Frame(Frame::Response(response)) => {
467 if let Some(tx) = pending.lock().unwrap().remove(&response.id) {
469 let _ = tx.send(response.result);
470 }
471 return;
472 }
473 Decoded::Frame(Frame::Event(event)) => Notification::Event(event),
474 Decoded::Frame(Frame::Log(log)) => Notification::Log(log),
475 Decoded::Frame(Frame::Request(_)) => return,
476 Decoded::Raw(bytes) => Notification::Raw(String::from_utf8_lossy(&bytes).into_owned()),
477 };
478 let _ = notify.try_send(notification);
480 };
481
482 loop {
483 match reader.read(&mut buf) {
484 Ok(0) => break,
485 Ok(n) => buf[..n]
486 .iter()
487 .filter_map(|&byte| decoder.push(byte))
488 .for_each(&mut dispatch),
489 Err(e)
490 if matches!(
491 e.kind(),
492 io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock
493 ) =>
494 {
495 decoder.flush().into_iter().for_each(&mut dispatch);
496 }
497 Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
498 Err(_) => break,
499 }
500 }
501 decoder.flush().into_iter().for_each(dispatch);
502}
503
504#[cfg(test)]
505mod tests;