Skip to main content

vexide_simulator_protocol/
lib.rs

1//! The Vexide Simulator Protocol enables communication between VEX robot simulators and user-facing frontends using a JSON-based protocol.
2//!
3//! The code executor and frontend communicate over a stream in [newline-delimited JSON format](https://jsonlines.org/).
4//!
5//! The backend sends [`Event`]s which represent a change in simulator state.
6//! These are used by the frontend to correctly display the state of the simulated program.
7//!
8//! The frontend sends [`Command`]s to the code executor to control the robot code environment, simulating changes in robot hardware (like controller input and LCD touch events) or competition phase.
9//!
10//! The full protocol is documented at <https://internals.vexide.dev/simulators/protocol>.
11#![deny(rust_2018_compatibility, rust_2018_idioms, unsafe_code)]
12
13use base64::{prelude::*, DecodeError};
14use mint::Point2;
15use rgb::RGB8;
16use serde::{Deserialize, Serialize};
17use std::{num::NonZeroU16, path::PathBuf};
18
19/// A message sent from the simulator to the frontend.
20#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
21pub enum Event {
22    Handshake {
23        version: i32,
24        extensions: Vec<String>,
25    },
26    ScreenDraw {
27        command: DrawCommand,
28        color: Color,
29        /// The region of the screen that may be mutated.
30        clip_region: Rect,
31    },
32    ScreenScroll {
33        location: ScrollLocation,
34        lines: i32,
35        background: Color,
36        /// The region of the screen that may be mutated.
37        clip_region: Rect,
38    },
39    ScreenClear {
40        color: Color,
41        /// The region of the screen that may be mutated.
42        clip_region: Rect,
43    },
44    ScreenDoubleBufferMode {
45        enable: bool,
46    },
47    ScreenRender,
48    VCodeSig(VCodeSig),
49    Ready,
50    Exited,
51    Serial(SerialData),
52    DeviceUpdate {
53        status: DeviceStatus,
54        port: Port,
55    },
56    Battery(Battery),
57    RobotPose {
58        x: f64,
59        y: f64,
60    },
61    RobotState(RobotState),
62    Log {
63        level: LogLevel,
64        message: String,
65    },
66    VEXLinkConnect {
67        port: SmartPort,
68        id: String,
69        mode: LinkMode,
70        r#override: bool,
71    },
72    VEXLinkDisconnect {
73        port: SmartPort,
74    },
75    TextMetricsRequest {
76        text: V5Text,
77    },
78}
79
80/// A message sent from the frontend to the simulator.
81#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
82pub enum Command {
83    Handshake {
84        version: i32,
85        extensions: Vec<String>,
86    },
87    Touch {
88        pos: Point2<i32>,
89        event: TouchEvent,
90    },
91    ControllerUpdate(Option<ControllerUpdate>, Option<ControllerUpdate>),
92    USD {
93        root: Option<PathBuf>,
94    },
95    VEXLinkOpened {
96        port: SmartPort,
97        mode: LinkMode,
98    },
99    VEXLinkClosed {
100        port: SmartPort,
101    },
102    CompetitionMode(CompetitionMode),
103    ConfigureDevice {
104        port: Port,
105        device: Device,
106    },
107    AdiInput {
108        port: AdiPort,
109        voltage: f64,
110    },
111    StartExecution,
112    SetBatteryCapacity {
113        capacity: f64,
114    },
115    SetTextMetrics {
116        text: V5Text,
117        metrics: TextMetrics,
118    },
119    Serial(SerialData),
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
123pub struct SerialData {
124    pub channel: u32,
125    pub data: String,
126}
127
128impl SerialData {
129    pub fn new(channel: u32, bytes: &[u8]) -> Self {
130        Self {
131            channel,
132            data: BASE64_STANDARD.encode(bytes),
133        }
134    }
135
136    pub fn to_bytes(&self) -> Result<Vec<u8>, DecodeError> {
137        BASE64_STANDARD.decode(&self.data)
138    }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
142pub struct CompetitionMode {
143    pub enabled: bool,
144    pub connected: bool,
145    pub mode: CompMode,
146    pub is_competition: bool,
147}
148
149impl Default for CompetitionMode {
150    fn default() -> Self {
151        Self {
152            enabled: true,
153            connected: false,
154            mode: CompMode::Driver,
155            is_competition: false,
156        }
157    }
158}
159
160/// Base64-encoded program metadata.
161#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
162pub struct VCodeSig(pub String);
163
164impl VCodeSig {
165    pub fn new(bytes: &[u8]) -> Self {
166        Self(BASE64_STANDARD.encode(bytes))
167    }
168
169    pub fn to_bytes(&self) -> Result<Vec<u8>, DecodeError> {
170        BASE64_STANDARD.decode(&self.0)
171    }
172}
173
174/// The configuration of a V5 peripheral.
175#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
176#[non_exhaustive]
177pub enum Device {
178    Motor {
179        physical_gearset: MotorGearset,
180        moment_of_inertia: f64,
181    },
182}
183
184/// The current state of the robot as a whole.
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
186#[non_exhaustive]
187pub struct RobotState;
188
189/// An instruction for drawing to the robot LCD screen.
190#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
191pub enum DrawCommand {
192    Fill {
193        shape: Shape,
194    },
195    Stroke {
196        shape: Shape,
197    },
198    CopyBuffer {
199        top_left: Point2<i32>,
200        bottom_right: Point2<i32>,
201        stride: NonZeroU16,
202        /// Base64 string
203        buffer: String,
204    },
205    Write {
206        text: V5Text,
207        location: TextLocation,
208        opaque: bool,
209        background: Color,
210    },
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
214pub enum TextLocation {
215    Coordinates { point: Point2<i32> },
216    Line { line: i32 },
217}
218
219impl Default for TextLocation {
220    fn default() -> Self {
221        Self::Coordinates {
222            point: Point2 { x: 0, y: 0 },
223        }
224    }
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
228pub enum ScrollLocation {
229    Line {
230        line: i32,
231    },
232    Rectangle {
233        top_left: Point2<i32>,
234        bottom_right: Point2<i32>,
235    },
236}
237
238#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
239pub struct V5Text {
240    pub data: String,
241    pub font_family: V5FontFamily,
242    pub font_size: V5FontSize,
243}
244
245#[derive(
246    Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord,
247)]
248pub enum V5FontFamily {
249    #[default]
250    UserMono,
251    TimerMono,
252}
253
254#[derive(
255    Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord,
256)]
257pub enum V5FontSize {
258    Small,
259    #[default]
260    Normal,
261    Large,
262}
263
264#[derive(
265    Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord,
266)]
267pub struct TextMetrics {
268    pub width: usize,
269    pub height: usize,
270}
271
272/// A shape that can be drawn to the robot LCD screen.
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
274pub enum Shape {
275    Rectangle {
276        top_left: Point2<i32>,
277        bottom_right: Point2<i32>,
278    },
279    Circle {
280        center: Point2<i32>,
281        radius: u16,
282    },
283    Pixel {
284        pos: Point2<i32>,
285    },
286    Line {
287        start: Point2<i32>,
288        end: Point2<i32>,
289    },
290}
291
292/// The current state of a V5 peripheral.
293#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
294#[non_exhaustive]
295pub enum DeviceStatus {
296    Motor {
297        velocity: f64,
298        reversed: bool,
299        power_draw: f64,
300        torque_output: f64,
301        flags: i32,
302        position: f64,
303        target_position: f64,
304        voltage: f64,
305        gearset: MotorGearset,
306        brake_mode: MotorBrakeMode,
307    },
308}
309
310/// The gearset of a VEX V5 motor.
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
312pub enum MotorGearset {
313    Red,
314    Green,
315    Blue,
316}
317
318/// The brake mode of a VEX V5 motor.
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
320pub enum MotorBrakeMode {
321    Coast,
322    Brake,
323    Hold,
324}
325
326/// The mode of a [VEXlink](https://drive.google.com/file/d/13mTA6BT7CPskJzh4YgsfAfoH9OgK75Hn/view)-configured radio.
327#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
328pub enum LinkMode {
329    Manager,
330    Worker,
331}
332
333/// The gearset of a VEX V5 motor.
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
335pub enum TouchEvent {
336    Released,
337    Pressed,
338    Held,
339}
340
341/// An arbitrary port on the VEX V5.
342#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
343pub enum Port {
344    Smart(SmartPort),
345    Adi(AdiPort),
346}
347
348/// An RJ9 4p4c "Smart" port on the VEX V5.
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
350pub struct SmartPort(pub u8);
351
352/// A 3-wire "ADI" port for analog devices.
353#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
354pub struct AdiPort(pub u8);
355
356/// The current stage of a competition.
357#[derive(
358    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Default,
359)]
360pub enum CompMode {
361    Auto,
362    #[default]
363    Driver,
364}
365
366/// The importance level of a log message.
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
368pub enum LogLevel {
369    Trace,
370    Info,
371    Warn,
372    Error,
373}
374
375/// Battery status and statistics.
376#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
377pub struct Battery {
378    pub voltage: f64,
379    pub current: f64,
380    pub capacity: f64,
381}
382
383/// A method of retrieving a controller's current state.
384#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
385pub enum ControllerUpdate {
386    /// Implementors can send raw controller state to the simulator,
387    /// allowing for keyboard-and-mouse-based control.
388    Raw(ControllerState),
389    /// Implementors can can send the UUID of a physical controller (more efficient and allows for SDL2 mappings).
390    UUID(String),
391}
392
393/// The raw state of a VEX V5 controller.
394#[derive(
395    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Default,
396)]
397pub struct ControllerState {
398    pub axis1: i32,
399    pub axis2: i32,
400    pub axis3: i32,
401    pub axis4: i32,
402    pub button_l1: bool,
403    pub button_l2: bool,
404    pub button_r1: bool,
405    pub button_r2: bool,
406    pub button_up: bool,
407    pub button_down: bool,
408    pub button_left: bool,
409    pub button_right: bool,
410    pub button_x: bool,
411    pub button_b: bool,
412    pub button_y: bool,
413    pub button_a: bool,
414    pub button_sel: bool,
415    pub battery_level: i32,
416    pub button_all: bool,
417    pub flags: i32,
418    pub battery_capacity: i32,
419}
420
421#[derive(
422    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Default,
423)]
424pub struct Color(pub u32);
425
426impl From<RGB8> for Color {
427    fn from(rgb: RGB8) -> Self {
428        Self(u32::from_be_bytes([0, rgb.r, rgb.g, rgb.b]))
429    }
430}
431
432impl From<Color> for RGB8 {
433    fn from(color: Color) -> Self {
434        let [_, r, g, b] = color.0.to_be_bytes();
435        RGB8 { r, g, b }
436    }
437}
438
439#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
440pub struct Rect {
441    pub top_left: Point2<i32>,
442    pub bottom_right: Point2<i32>,
443}