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}