vexide_devices/smart/
link.rs

1//! VEXlink
2//!
3//! This module provides support for VEXlink, a point-to-point wireless communications protocol between
4//! two VEXnet radios.
5//!
6//! # Hardware Overview
7//!
8//! There are two types of radios in a VEXnink connection: "manager" and "worker". A "manager" radio can transmit data at up to 1040 bytes/s
9//! while a "worker" radio can transmit data at up to 520 bytes/s.
10//! A connection should only ever have both types of radios.
11//!
12//! In order to connect to a radio, VEXos hashes a given link name and uses it as an ID to verify the connection.
13//! For this reason, you should try to create a unique name for each radio link
14//! to avoid accidentally interfering, or being interfered with by, an unrelated VEXlink connection.
15//! Ideally, you want a name that will never be used by another team.
16//!
17//! The lights on the radio can be used as a status indicator:
18//! - Blinking red: The radio is waiting for a connection to be established.
19//! - Alternating red and green quickly: The radio is connected to another radio and is the "manager" radio.
20//! - Alternating red and green slowly: The radio is connected to another radio and is the "worker" radio.
21//!
22//! For further information, see <https://www.vexforum.com/t/vexlink-documentaton/84538>
23
24use alloc::ffi::CString;
25use core::time::Duration;
26
27use no_std_io::io;
28use snafu::Snafu;
29use vex_sdk::{
30    vexDeviceGenericRadioConnection, vexDeviceGenericRadioLinkStatus, vexDeviceGenericRadioReceive,
31    vexDeviceGenericRadioReceiveAvail, vexDeviceGenericRadioTransmit,
32    vexDeviceGenericRadioWriteFree, V5_DeviceT,
33};
34
35use super::{SmartDevice, SmartDeviceType, SmartPort};
36
37/// VEXLink Wireless Radio Link
38///
39/// VEXLink is a point-to-point wireless communications protocol between
40/// two VEXNet radios. For further information, see <https://www.vexforum.com/t/vexlink-documentaton/84538>
41#[derive(Debug, Eq, PartialEq)]
42pub struct RadioLink {
43    port: SmartPort,
44    device: V5_DeviceT,
45}
46
47// SAFETY: Required because we store a raw pointer to the device handle to avoid it getting from the
48// SDK each device function. Simply sharing a raw pointer across threads is not inherently unsafe.
49unsafe impl Send for RadioLink {}
50unsafe impl Sync for RadioLink {}
51
52impl RadioLink {
53    /// The length of the link's FIFO input and output buffers.
54    pub const INTERNAL_BUFFER_SIZE: usize = 512;
55
56    /// Opens a radio link from a VEXNet radio plugged into a Smart Port. Once
57    /// opened, other VEXNet functionality such as controller tethering on this
58    /// specific radio will be disabled.
59    /// Other radios connected to the Brain can take over this functionality.
60    ///
61    /// # Panics
62    ///
63    /// - Panics if a NUL (0x00) character was found anywhere in the specified `id`.
64    ///
65    /// # Examples
66    ///
67    /// ```
68    /// use vexide::prelude::*;
69    ///
70    /// #[vexide::main]
71    /// async fn main(peripherals: Peripherals) {
72    ///     let link = RadioLink::open(port_1, "643A", LinkType::Manager);
73    /// }
74    /// ```
75    #[must_use]
76    pub fn open(port: SmartPort, id: &str, link_type: LinkType) -> Self {
77        let id = CString::new(id)
78            .expect("CString::new encountered NUL (U+0000) byte in non-terminating position.");
79
80        unsafe {
81            vexDeviceGenericRadioConnection(
82                port.device_handle(),
83                id.as_ptr().cast_mut(),
84                match link_type {
85                    LinkType::Worker => 0,
86                    LinkType::Manager => 1,
87                },
88                true,
89            );
90        }
91
92        Self {
93            device: unsafe { port.device_handle() },
94            port,
95        }
96    }
97
98    /// Returns the number of bytes that are waiting to be read from the radio's input buffer.
99    ///
100    /// # Errors
101    ///
102    /// - A [`LinkError::ReadFailed`] error is returned if the input buffer could not be accessed.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use vexide::prelude::*;
108    ///
109    /// #[vexide::main]
110    /// async fn main(peripherals: Peripherals) {
111    ///     let mut link = RadioLink::open(port_1, "643A", LinkType::Manager);
112    ///
113    ///     let mut buffer = vec![0; 2048];
114    ///
115    ///     // Read into `buffer` if there are unread bytes.
116    ///     if link.unread_bytes().is_ok_and(|bytes| bytes > 0) {
117    ///         _ = link.read(&mut buffer);
118    ///     }
119    /// }
120    /// ```
121    pub fn unread_bytes(&self) -> Result<usize, LinkError> {
122        match unsafe { vexDeviceGenericRadioReceiveAvail(self.device) } {
123            -1 => Err(LinkError::ReadFailed),
124            unread => Ok(unread as usize),
125        }
126    }
127
128    /// Returns the number of bytes free in the radio's output buffer.
129    ///
130    /// # Errors
131    ///
132    /// - A [`LinkError::ReadFailed`] error is returned if the output buffer could not be accessed.
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use vexide::prelude::*;
138    ///
139    /// #[vexide::main]
140    /// async fn main(peripherals: Peripherals) {
141    ///     let mut link = RadioLink::open(port_1, "643A", LinkType::Manager);
142    ///
143    ///     // Write a byte if there's free space in the buffer.
144    ///     if link.available_write_bytes().is_ok_and(|available| available > 0) {
145    ///         _ = link.write(0x80);
146    ///     }
147    /// }
148    /// ```
149    pub fn available_write_bytes(&self) -> Result<usize, LinkError> {
150        match unsafe { vexDeviceGenericRadioWriteFree(self.device) } {
151            -1 => Err(LinkError::ReadFailed),
152            available => Ok(available as usize),
153        }
154    }
155
156    /// Returns `true` if there is a link established with another radio.
157    ///
158    /// # Examples
159    ///
160    /// ```
161    /// use vexide::prelude::*;
162    ///
163    /// #[vexide::main]
164    /// async fn main(peripherals: Peripherals) {
165    ///     let mut link = RadioLink::open(port_1, "643A", LinkType::Manager);
166    ///
167    ///     // Write a byte if we are connected to another radio.
168    ///     if link.is_linked() == Ok(true) {
169    ///         _ = link.write(0x80);
170    ///     }
171    /// }
172    /// ```
173    #[must_use]
174    pub fn is_linked(&self) -> bool {
175        unsafe { vexDeviceGenericRadioLinkStatus(self.device) }
176    }
177}
178
179const RADIO_NOT_LINKED: &str = "The radio has not established a link with another radio.";
180
181impl io::Read for RadioLink {
182    /// Read some bytes sent to the radio into the specified buffer, returning how many bytes were read.
183    ///
184    /// # Errors
185    ///
186    /// - An error with the kind [`io::ErrorKind::AddrNotAvailable`] is returned if there is no device connected.
187    /// - An error with the kind [`io::ErrorKind::AddrInUse`] is returned a device other than a radio is connected.
188    /// - An error with the kind [`io::ErrorKind::NotConnected`] is returned if a connection with another radio has not been
189    ///   established. Use [`RadioLink::is_linked`] to check this if needed.
190    /// - An error with the kind [`io::ErrorKind::Other`] is returned if an unexpected internal read error occurred.
191    ///
192    /// # Examples
193    ///
194    /// ```
195    /// use vexide::prelude::*;
196    ///
197    /// #[vexide::main]
198    /// async fn main(peripherals: Peripherals) {
199    ///     let mut link = RadioLink::open(port_1, "643A", LinkType::Manager);
200    ///
201    ///     let mut buffer = vec![0; 2048];
202    ///
203    ///     loop {
204    ///         _ = link.read(&mut buffer);
205    ///         sleep(core::time::Duration::from_millis(10)).await;
206    ///     }
207    /// }
208    /// ```
209    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
210        if !self.is_linked() {
211            return Err(io::Error::new(
212                io::ErrorKind::NotConnected,
213                RADIO_NOT_LINKED,
214            ));
215        }
216
217        match unsafe {
218            vexDeviceGenericRadioReceive(self.device, buf.as_mut_ptr(), buf.len() as u16)
219        } {
220            -1 => Err(io::Error::new(
221                io::ErrorKind::InvalidData,
222                "Internal read error occurred.",
223            )),
224            received => Ok(received as usize),
225        }
226    }
227}
228
229impl io::Write for RadioLink {
230    /// Write a buffer into the radio's output buffer, returning how many bytes were written.
231    ///
232    /// # Errors
233    ///
234    /// - An error with the kind [`io::ErrorKind::NotConnected`] is returned if a connection with another radio has not been
235    ///   established. Use [`RadioLink::is_linked`] to check this if needed.
236    /// - An error with the kind [`io::ErrorKind::Other`] is returned if an unexpected internal write error occurred.
237    ///
238    /// # Examples
239    ///
240    /// ```
241    /// use vexide::prelude::*;
242    ///
243    /// #[vexide::main]
244    /// async fn main(peripherals: Peripherals) {
245    ///     let mut link = RadioLink::open(port_1, "643A", LinkType::Manager);
246    ///
247    ///     _ = link.write(b"yo");
248    /// }
249    /// ```
250    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
251        if !self.is_linked() {
252            return Err(io::Error::new(
253                io::ErrorKind::NotConnected,
254                RADIO_NOT_LINKED,
255            ));
256        }
257
258        match unsafe { vexDeviceGenericRadioTransmit(self.device, buf.as_ptr(), buf.len() as u16) }
259        {
260            -1 => Err(io::Error::new(
261                io::ErrorKind::Other,
262                "Internal write error occurred.",
263            )),
264            written => Ok(written as usize),
265        }
266    }
267
268    /// This function does nothing.
269    ///
270    /// VEXLink immediately sends and clears data sent into the write buffer.
271    ///
272    /// # Errors
273    ///
274    /// - An error with the kind [`io::ErrorKind::NotConnected`] is returned if a connection with another radio has not been
275    ///   established. Use [`RadioLink::is_linked`] to check this if needed.
276    fn flush(&mut self) -> io::Result<()> {
277        if !self.is_linked() {
278            return Err(io::Error::new(
279                io::ErrorKind::NotConnected,
280                RADIO_NOT_LINKED,
281            ));
282        }
283
284        Ok(())
285    }
286}
287
288impl SmartDevice for RadioLink {
289    const UPDATE_INTERVAL: Duration = Duration::from_millis(25);
290
291    fn port_number(&self) -> u8 {
292        self.port.number()
293    }
294
295    fn device_type(&self) -> SmartDeviceType {
296        SmartDeviceType::GenericSerial
297    }
298}
299impl From<RadioLink> for SmartPort {
300    fn from(device: RadioLink) -> Self {
301        device.port
302    }
303}
304
305/// The type of radio link being established.
306///
307/// VEXLink is a point-to-point connection, with one "manager" robot and
308/// one "worker" robot.
309#[derive(Debug, Clone, Copy, Eq, PartialEq)]
310pub enum LinkType {
311    /// Manager Radio
312    ///
313    /// This end of the link has a 1040-bytes/sec data rate when
314    /// communicating with a worker radio.
315    Manager,
316
317    /// Worker Radio
318    ///
319    /// This end of the link has a 520-bytes/sec data rate when
320    /// communicating with a manager radio.
321    Worker,
322}
323
324/// Errors that can occur when interacting with a [`RadioLink`].
325#[derive(Debug, Snafu)]
326pub enum LinkError {
327    /// Not linked with another radio.
328    NotLinked,
329
330    /// Internal write error occurred.
331    WriteFailed,
332
333    /// Internal read error occurred.
334    ReadFailed,
335}