vex_v5_serial/
serial.rs

1//! Implements discovering, opening, and interacting with vex devices connected over USB. This module does not have async support.
2
3use log::{debug, error, trace, warn};
4use serialport::{SerialPortInfo, SerialPortType};
5use std::time::Duration;
6use thiserror::Error;
7use tokio::{
8    io::{AsyncReadExt, AsyncWriteExt, BufReader},
9    select,
10    time::sleep,
11};
12use tokio_serial::SerialStream;
13use vex_cdc::{
14    Decode, DecodeError, DecodeErrorKind, Encode, FixedString, FixedStringSizeError, REPLY_HEADER,
15    VarU16,
16    cdc2::{
17        Cdc2Ack,
18        controller::{UserDataPacket, UserDataPayload, UserDataReplyPacket},
19    },
20};
21
22use crate::{CheckHeader, Connection, ConnectionType, RawPacket, trim_packets};
23
24/// The USB venddor ID for VEX devices
25pub const VEX_USB_VID: u16 = 0x2888;
26
27/// The USB PID of the V5 Brain
28pub const V5_BRAIN_USB_PID: u16 = 0x0501;
29
30/// The USB PID of the EXP Brain
31pub const EXP_BRAIN_USB_PID: u16 = 0x600;
32
33/// The USB PID of the V5 Controller
34pub const V5_CONTROLLER_USB_PID: u16 = 0x0503;
35
36pub const V5_SERIAL_BAUDRATE: u32 = 115200;
37
38/// The information of a generic vex serial port
39#[derive(Clone, Debug)]
40pub struct VexSerialPort {
41    pub port_info: tokio_serial::SerialPortInfo,
42    pub port_type: VexSerialPortType,
43}
44
45#[derive(Debug, Clone, Copy, Eq, PartialEq)]
46pub enum VexSerialPortType {
47    User,
48    System,
49    Controller,
50}
51
52/// Assigns port types by port location.
53/// This does not appear to work on windows due to its shitty serial device drivers from 2006.
54fn types_by_location(ports: &[SerialPortInfo]) -> Option<Vec<VexSerialPort>> {
55    debug!("Attempting to infer serial port types by port location.");
56    let mut vex_ports = Vec::new();
57
58    for port in ports {
59        // Get the info about the usb connection
60        // This is always going to succeed because of earlier code.
61        let SerialPortType::UsbPort(info) = port.clone().port_type else {
62            continue;
63        };
64
65        if cfg!(target_os = "macos") && port.port_name.starts_with("/dev/tty.") {
66            // https://pbxbook.com/other/mac-tty.html
67            debug!(
68                "Ignoring port named {:?} because it is a call-in device",
69                port.port_name
70            );
71            continue;
72        }
73
74        match info.pid {
75            V5_CONTROLLER_USB_PID => vex_ports.push(VexSerialPort {
76                port_info: port.clone(),
77                port_type: VexSerialPortType::Controller,
78            }),
79            V5_BRAIN_USB_PID | EXP_BRAIN_USB_PID => {
80                // Check the product name for identifying information
81                // This will not work on windows
82                if let Some(mut location) = info.interface {
83                    if cfg!(target_os = "macos") {
84                        location -= 1; // macOS is 1-indexed
85                    }
86
87                    match location {
88                        0 => {
89                            debug!("Found a 'system' serial port over a Brain connection.");
90                            vex_ports.push(VexSerialPort {
91                                port_info: port.clone(),
92                                port_type: VexSerialPortType::System,
93                            })
94                        }
95                        1 => warn!(
96                            "Found a controller serial port over a Brain connection! Things are most likely broken."
97                        ),
98                        2 => {
99                            debug!("Found a 'user' serial port over a Brain connection.");
100                            vex_ports.push(VexSerialPort {
101                                port_info: port.clone(),
102                                port_type: VexSerialPortType::User,
103                            })
104                        }
105                        _ => warn!("Unknown location for V5 device: {}", location),
106                    }
107                }
108            }
109            // Unknown product
110            _ => {}
111        }
112    }
113
114    Some(vex_ports)
115}
116
117/// Assign port types based on the last character of the port name.
118/// This is the fallback option for macOS.
119/// This is a band-aid solution and will become obsolete once serialport correctly gets the interface number.
120fn types_by_name_darwin(ports: &[SerialPortInfo]) -> Option<Vec<VexSerialPort>> {
121    assert!(cfg!(target_os = "macos"));
122
123    debug!("Attempting to infer serial port types by name. (Darwin fallback)");
124    let mut vex_ports = Vec::new();
125
126    for port in ports {
127        if cfg!(target_os = "macos") && port.port_name.starts_with("/dev/tty.") {
128            // https://pbxbook.com/other/mac-tty.html
129            debug!(
130                "Ignoring port named {:?} because it is a call-in device",
131                port.port_name
132            );
133            continue;
134        }
135
136        let Some(interface) = port.port_name.chars().last() else {
137            continue;
138        };
139        match interface {
140            '1' => {
141                debug!("Found a 'system' serial port over a Brain connection.");
142                vex_ports.push(VexSerialPort {
143                    port_info: port.clone(),
144                    port_type: VexSerialPortType::System,
145                });
146            }
147            '2' => {
148                debug!("Found a controller serial port.");
149                vex_ports.push(VexSerialPort {
150                    port_info: port.clone(),
151                    port_type: VexSerialPortType::Controller,
152                });
153            }
154            '3' => {
155                debug!("Found a 'user' serial port over a Brain connection.");
156                vex_ports.push(VexSerialPort {
157                    port_info: port.clone(),
158                    port_type: VexSerialPortType::User,
159                });
160            }
161            _ => {
162                warn!("Unknown location for V5 device: {}", interface);
163            }
164        }
165    }
166
167    Some(vex_ports)
168}
169
170/// Infers port type by numerically sorting port product names.
171/// This is the fallback option for windows.
172/// The lower number port name is usually the user port according to pros-cli comments:
173/// [https://github.com/purduesigbots/pros-cli/blob/develop/pros/serial/devices/vex/v5_device.py#L75]
174fn types_by_name_order(ports: &[SerialPortInfo]) -> Option<Vec<VexSerialPort>> {
175    debug!("Attempting to infer serial port types by order. (Windows fallback)");
176    if ports.len() != 2 {
177        return None;
178    }
179
180    let mut vex_ports = Vec::new();
181
182    let mut sorted_ports = ports.to_vec();
183    // Sort by product name
184    sorted_ports.sort_by_key(|info| info.port_name.clone());
185    sorted_ports.reverse();
186
187    // Higher Port
188    vex_ports.push(VexSerialPort {
189        port_info: sorted_ports.pop().unwrap(),
190        port_type: VexSerialPortType::System,
191    });
192    // Lower port
193    vex_ports.push(VexSerialPort {
194        port_info: sorted_ports.pop().unwrap(),
195        port_type: VexSerialPortType::User,
196    });
197
198    // If we could not infer the type of all connections, fail
199    if vex_ports.len() != ports.len() {
200        return None;
201    }
202
203    Some(vex_ports)
204}
205
206/// Finds all available VEX serial ports that can be connected to.
207fn find_ports() -> Result<Vec<VexSerialPort>, SerialError> {
208    // Get all available serial ports
209    let ports = tokio_serial::available_ports()?;
210
211    // Create a vector that will contain all vex ports
212    let mut filtered_ports = Vec::new();
213
214    // Iterate over all available ports
215    for port in ports {
216        // Get the serial port's info as long as it is a usb port.
217        // If it is not a USB port, ignore it.
218        let SerialPortType::UsbPort(port_info) = port.clone().port_type else {
219            continue;
220        };
221
222        // If the Vendor ID does not match the VEX Vendor ID, then skip it
223        if port_info.vid != VEX_USB_VID {
224            continue;
225        }
226
227        filtered_ports.push(port);
228    }
229
230    let vex_ports = types_by_location(&filtered_ports)
231        .or_else(|| {
232            if cfg!(target_os = "macos") {
233                types_by_name_darwin(&filtered_ports)
234            } else {
235                types_by_name_order(&filtered_ports)
236            }
237        })
238        .ok_or(SerialError::CouldntInferTypes)?;
239
240    Ok(vex_ports)
241}
242
243/// Finds all connected V5 devices.
244pub fn find_devices() -> Result<Vec<SerialDevice>, SerialError> {
245    // Find all vex ports, iterate using peekable.
246    let mut ports = find_ports()?.into_iter().peekable();
247
248    // Create a vector of all vex devices
249    let mut devices = Vec::<SerialDevice>::new();
250
251    // Manually use a while loop to iterate, so that we can peek and pop ahead
252    while let Some(port) = ports.next() {
253        // Find out what type it is so we can assign devices
254        match port.port_type {
255            VexSerialPortType::System => {
256                let port_name = port.port_info.port_name.clone();
257
258                // Peek the next port. If it is a user port, add it to a brain device. If not, add it to an unknown device
259                if match ports.peek() {
260                    Some(p) => p.port_type == VexSerialPortType::User,
261                    _ => false,
262                } {
263                    devices.push(SerialDevice::Brain {
264                        system_port: port_name,
265                        user_port: ports.next().unwrap().port_info.port_name.clone(),
266                    });
267                } else {
268                    // If there is only a system device, add a unknown V5 device
269                    devices.push(SerialDevice::Unknown {
270                        system_port: port_name,
271                    });
272                }
273            }
274            VexSerialPortType::User => {
275                // If it is a user port, do the same thing we do with a system port. Except ignore it if there is no other port.
276                if match ports.peek() {
277                    Some(p) => p.port_type == VexSerialPortType::System,
278                    _ => false,
279                } {
280                    devices.push(SerialDevice::Brain {
281                        system_port: ports.next().unwrap().port_info.port_name.clone(),
282                        user_port: port.port_info.port_name.clone(),
283                    });
284                }
285            }
286            VexSerialPortType::Controller => devices.push(SerialDevice::Controller {
287                system_port: port.port_info.port_name.clone(),
288            }),
289        }
290    }
291
292    // Return the devices
293    Ok(devices)
294}
295
296/// Represents a V5 device that can be connected to over serial.
297#[derive(Clone, Debug)]
298pub enum SerialDevice {
299    /// V5 Brain
300    ///
301    /// Has both a system and user port.
302    Brain {
303        user_port: String,
304        system_port: String,
305    },
306
307    /// V5 Controller
308    ///
309    /// Has a system port, but no user port.
310    Controller { system_port: String },
311
312    /// Unknown V5 Peripheral.
313    ///
314    /// A secret, more sinsiter, third thing.
315    /// *Probably doesn't even exist. How'd you even get this to happen?*
316    ///
317    /// Has a system port and no user port but __is not a controller__.
318    Unknown { system_port: String },
319}
320
321impl SerialDevice {
322    pub fn connect(&self, timeout: Duration) -> Result<SerialConnection, SerialError> {
323        SerialConnection::open(self.clone(), timeout)
324    }
325
326    pub fn system_port(&self) -> String {
327        match &self {
328            Self::Brain {
329                system_port,
330                user_port: _,
331            }
332            | Self::Controller { system_port }
333            | Self::Unknown { system_port } => system_port.clone(),
334        }
335    }
336
337    pub fn user_port(&self) -> Option<String> {
338        match &self {
339            Self::Brain {
340                system_port: _,
341                user_port,
342            } => Some(user_port.clone()),
343            _ => None,
344        }
345    }
346}
347
348/// Decodes a [`HostBoundPacket`]'s header sequence.
349fn validate_header(mut data: &[u8]) -> Result<[u8; 2], DecodeError> {
350    let header = Decode::decode(&mut data)?;
351    if header != REPLY_HEADER {
352        return Err(DecodeError::new::<[u8; 2]>(DecodeErrorKind::InvalidHeader));
353    }
354    Ok(header)
355}
356
357/// An open serial connection to a V5 device.
358#[derive(Debug)]
359pub struct SerialConnection {
360    system_port: SerialStream,
361    user_port: Option<BufReader<SerialStream>>,
362    incoming_packets: Vec<RawPacket>,
363}
364
365impl SerialConnection {
366    /// Opens a new serial connection to a V5 Brain.
367    pub fn open(device: SerialDevice, timeout: Duration) -> Result<Self, SerialError> {
368        // Open the system port
369        let system_port = match tokio_serial::SerialStream::open(
370            &tokio_serial::new(device.system_port(), 115200)
371                .parity(tokio_serial::Parity::None)
372                .timeout(timeout)
373                .stop_bits(tokio_serial::StopBits::One),
374        ) {
375            Ok(v) => Ok(v),
376            Err(e) => Err(SerialError::SerialportError(e)),
377        }?;
378
379        // Open the user port (if it exists)
380        let user_port = if let Some(port) = &device.user_port() {
381            Some(match tokio_serial::SerialStream::open(
382                &tokio_serial::new(port, V5_SERIAL_BAUDRATE)
383                    .parity(tokio_serial::Parity::None)
384                    .timeout(timeout)
385                    .stop_bits(tokio_serial::StopBits::One),
386            ) {
387                Ok(v) => Ok(BufReader::new(v)),
388                Err(e) => Err(SerialError::SerialportError(e)),
389            }?)
390        } else {
391            None
392        };
393
394        Ok(Self {
395            system_port,
396            user_port,
397            incoming_packets: Default::default(),
398        })
399    }
400
401    /// Receives a single packet from the serial port and adds it to the queue of incoming packets.
402    async fn receive_one_packet(&mut self) -> Result<(), SerialError> {
403        // Read the header into an array
404        let mut header = [0u8; 2];
405        self.system_port.read_exact(&mut header).await?;
406
407        // Verify that the header is valid
408        if let Err(e) = validate_header(&header) {
409            warn!(
410                "Skipping packet with invalid header: {:x?}. Error: {}",
411                header, e
412            );
413            return Ok(());
414        }
415
416        // Create a buffer to store the entire packet
417        let mut packet = Vec::from(header);
418
419        // Push the command's ID
420        packet.push(self.system_port.read_u8().await?);
421
422        // Get the size of the packet
423        // We do some extra logic to make sure we only read the necessary amount of bytes
424        let first_size_byte = self.system_port.read_u8().await?;
425        let size = if VarU16::check_wide(first_size_byte) {
426            let second_size_byte = self.system_port.read_u8().await?;
427            packet.extend([first_size_byte, second_size_byte]);
428
429            // Decode the size of the packet
430            VarU16::decode(&mut [first_size_byte, second_size_byte].as_slice())?
431        } else {
432            packet.push(first_size_byte);
433
434            // Decode the size of the packet
435            VarU16::decode(&mut [first_size_byte].as_slice())?
436        }
437        .into_inner() as usize;
438
439        // Read the rest of the packet
440        let mut payload = vec![0; size];
441        self.system_port.read_exact(&mut payload).await?;
442
443        // Completely fill the packet
444        packet.extend(payload);
445
446        trace!("received packet: {:x?}", packet);
447
448        // Push the packet to the incoming packets buffer
449        self.incoming_packets.push(RawPacket::new(packet));
450
451        Ok(())
452    }
453}
454
455impl Connection for SerialConnection {
456    type Error = SerialError;
457
458    fn connection_type(&self) -> ConnectionType {
459        if self.user_port.is_some() {
460            ConnectionType::Wired
461        } else {
462            ConnectionType::Controller
463        }
464    }
465
466    async fn send(&mut self, packet: impl Encode) -> Result<(), SerialError> {
467        // Encode the packet
468        let mut encoded = vec![0; packet.size()];
469        packet.encode(&mut encoded);
470
471        trace!("sent packet: {:x?}", encoded);
472
473        // Write the packet to the serial port
474        match self.system_port.write_all(&encoded).await {
475            Ok(_) => (),
476            Err(e) => return Err(SerialError::IoError(e)),
477        };
478
479        match self.system_port.flush().await {
480            Ok(_) => (),
481            Err(e) => return Err(SerialError::IoError(e)),
482        };
483
484        Ok(())
485    }
486
487    async fn recv<P: Decode + CheckHeader>(&mut self, timeout: Duration) -> Result<P, SerialError> {
488        // Return an error if the right packet is not received within the timeout
489        select! {
490            result = async {
491                loop {
492                    for packet in self.incoming_packets.iter_mut() {
493                        if packet.check_header::<P>() {
494                            match packet.decode_and_use::<P>() {
495                                Ok(decoded) => {
496                                    trim_packets(&mut self.incoming_packets);
497                                    return Ok(decoded);
498                                }
499                                Err(e) => {
500                                    error!("Failed to decode packet with valid header: {}", e);
501                                    packet.used = true;
502                                    return Err(SerialError::DecodeError(e));
503                                }
504                            }
505                        }
506                    }
507                    trim_packets(&mut self.incoming_packets);
508                    self.receive_one_packet().await?;
509                }
510            } => result,
511            _ = sleep(timeout) => Err(SerialError::Timeout)
512        }
513    }
514
515    async fn read_user(&mut self, buf: &mut [u8]) -> Result<usize, SerialError> {
516        if let Some(user_port) = &mut self.user_port {
517            Ok(user_port.read(buf).await?)
518        } else {
519            let mut data = Vec::new();
520            loop {
521                let fifo = self
522                    .handshake::<UserDataReplyPacket>(
523                        Duration::from_millis(100),
524                        1,
525                        UserDataPacket::new(UserDataPayload {
526                            channel: 1, // stdio channel
527                            write: None,
528                        }),
529                    )
530                    .await?
531                    .payload?;
532                if let Some(read) = fifo.data {
533                    data.extend(read.as_bytes());
534                    break;
535                }
536            }
537
538            let len = data.len().min(buf.len());
539            buf[..len].copy_from_slice(&data[..len]);
540
541            Ok(len)
542        }
543    }
544
545    async fn write_user(&mut self, mut buf: &[u8]) -> Result<usize, SerialError> {
546        if let Some(user_port) = &mut self.user_port {
547            Ok(user_port.write(buf).await?)
548        } else {
549            let buf_len = buf.len();
550            while !buf.is_empty() {
551                let (chunk, rest) = buf.split_at(std::cmp::min(224, buf.len()));
552                _ = self
553                    .handshake::<UserDataReplyPacket>(
554                        Duration::from_millis(100),
555                        1,
556                        UserDataPacket::new(UserDataPayload {
557                            channel: 2, // stdio channel
558                            write: Some(
559                                FixedString::new(String::from_utf8(chunk.to_vec()).unwrap())
560                                    .unwrap(),
561                            ),
562                        }),
563                    )
564                    .await?
565                    .payload?;
566                buf = rest;
567            }
568
569            Ok(buf_len)
570        }
571    }
572}
573
574#[derive(Error, Debug)]
575pub enum SerialError {
576    #[error("IO Error: {0}")]
577    IoError(#[from] std::io::Error),
578
579    #[error("Packet decoding error: {0}")]
580    DecodeError(#[from] DecodeError),
581
582    #[error("Packet timeout")]
583    Timeout,
584
585    #[error("NACK received: {0:?}")]
586    Nack(#[from] Cdc2Ack),
587
588    #[error("Serialport Error")]
589    SerialportError(#[from] tokio_serial::Error),
590
591    #[error("Could not infer serial port types")]
592    CouldntInferTypes,
593
594    #[error(transparent)]
595    FixedStringSizeError(#[from] FixedStringSizeError),
596}