arduino_plotter/
protocol.rs

1use std::{
2    collections::HashMap,
3    ops::{Deref, DerefMut},
4};
5
6use serde::{Deserialize, Serialize};
7
8use parse_display::{Display, FromStr};
9
10/// The generic Command structure defined by the Arduino serial plotter README.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Command<T> {
13    pub command: CommandName,
14    pub data: T,
15}
16
17/// Data lines message that can be send to the Arduino serial plotter
18///
19/// <https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-serial-plotter>
20///
21/// ```
22/// use arduino_plotter::protocol::Data;
23///
24/// // data with no line ending
25/// let data = Data(vec![
26///     "L1:1,L2:2,L3:3,L4:4".to_string(),
27///     "Label_1:99,Label_2:98,Label_3:97,Label_4:96".to_string(),
28/// ]);
29/// let data_json = serde_json::json!([
30///     "L1:1,L2:2,L3:3,L4:4",
31///     "Label_1:99,Label_2:98,Label_3:97,Label_4:96"
32/// ]);
33/// let from_json = serde_json::from_value::<Data<String>>(data_json).expect("should be valid");
34///
35/// assert_eq!(data, from_json);
36/// ```
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(transparent)]
39pub struct Data<T: core::fmt::Display>(pub Vec<T>);
40
41/// All the available Command names for both Client ([`ClientCommand`]) and Middleware ([`MiddlewareCommand`]).
42#[derive(Debug, Clone, Serialize, Deserialize, Display, FromStr)]
43#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
44#[display(style = "SNAKE_CASE")]
45pub enum CommandName {
46    /// Middleware Command (from WebSocket to Arduino Serial Plotter UI)
47    OnSettingsDidChange,
48    // Client Command (from Arduino Serial Plotter UI to WebSocket)
49    SendMessage,
50    // Client Command (from Arduino Serial Plotter UI to WebSocket)
51    ChangeSettings,
52}
53
54/// Middleware Command (from WebSocket to Arduino Serial Plotter UI)
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(
57    into = "Command<MonitorSettings>",
58    try_from = "Command<MonitorSettings>"
59)]
60pub struct MiddlewareCommand(pub MonitorSettings);
61impl From<MiddlewareCommand> for Command<MonitorSettings> {
62    fn from(value: MiddlewareCommand) -> Self {
63        Self {
64            command: CommandName::OnSettingsDidChange,
65            data: value.0,
66        }
67    }
68}
69
70impl TryFrom<Command<MonitorSettings>> for MiddlewareCommand {
71    type Error = serde_json::Error;
72
73    fn try_from(middleware_command: Command<MonitorSettings>) -> Result<Self, Self::Error> {
74        match middleware_command.command {
75            CommandName::OnSettingsDidChange => Ok(MiddlewareCommand(middleware_command.data)),
76            command_name => Err(serde::de::Error::custom(format!(
77                "ON_SETTING_DID_CHANGE command expected, got {command_name}"
78            ))),
79        }
80    }
81}
82
83/// Client Commands from Arduino Serial Plotter UI to WebSocket)
84#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(tag = "command", content = "data", rename_all = "SCREAMING_SNAKE_CASE")]
86pub enum ClientCommand {
87    SendMessage(String),
88    ChangeSettings(MonitorSettings),
89}
90
91impl From<ClientCommand> for Command<serde_json::Value> {
92    fn from(value: ClientCommand) -> Self {
93        match value {
94            ClientCommand::SendMessage(send_message) => Self {
95                command: CommandName::SendMessage,
96                data: serde_json::to_value(send_message).unwrap(),
97            },
98            ClientCommand::ChangeSettings(monitor_settings) => Self {
99                command: CommandName::ChangeSettings,
100                data: serde_json::to_value(monitor_settings).unwrap(),
101            },
102        }
103    }
104}
105
106/// A single Pluggable monitor setting
107/// ```json
108/// {
109///     "id": "baudrate",
110///     "label": "Baudrate",
111///     "type": "enum",
112///     "values": ["300","9600", "115200"],
113///     "selectedValue": "9600",
114///   }
115/// ```
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
117#[serde(rename_all = "camelCase")]
118pub struct PluggableMonitorSetting {
119    /// The setting identifier, e.g. `"baudrate"`
120    pub id: Option<String>,
121    /// A human-readable label of the setting (to be displayed on the GUI), e.g. `"Baudrate"`
122    pub label: Option<String>,
123    /// The setting type (at the moment only "enum" is available)
124    pub r#type: Option<LabelType>,
125    /// The values allowed on "enum" types, e.g. `vec!["300".to_string(), "9600".into(), "115200".into()]`
126    #[serde(default)]
127    pub values: Vec<String>,
128    /// The selected value, e.g. `"9600"`
129    pub selected_value: String,
130}
131
132/// The Pluggable Monitor setting type.
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
134#[serde(rename_all = "camelCase")]
135pub enum LabelType {
136    Enum,
137}
138
139/// All the Pluggable Monitor settings, i.e. a connected serial device,
140/// that can be changed from the Arduino serial plotter UI.
141///
142/// ```
143/// use arduino_plotter::protocol::PluggableMonitorSettings;
144///
145/// let json = serde_json::json!({
146///   "baudrate": {
147///     "id": "baudrate",
148///     "label": "Baudrate",
149///     "type": "enum",
150///     "values": ["300","9600", "115200"],
151///     "selectedValue": "9600",
152///   },
153///   "otherSetting": {
154///     "id": "otherSetting",
155///     "label": "Other Setting",
156///     "type": "enum",
157///     "values": ["A","B", "C"],
158///     "selectedValue": "B",
159///   }
160/// });
161///
162/// let settings = serde_json::from_value::<PluggableMonitorSettings>(json).expect("Valid PluggableMonitorSettings");
163///
164/// assert_eq!(2, settings.len());
165/// assert!(settings.contains_key("baudrate"));
166/// assert!(settings.contains_key("otherSetting"));
167/// ```
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
169#[serde(transparent)]
170pub struct PluggableMonitorSettings(pub HashMap<String, PluggableMonitorSetting>);
171
172impl Deref for PluggableMonitorSettings {
173    type Target = HashMap<String, PluggableMonitorSetting>;
174
175    fn deref(&self) -> &Self::Target {
176        &self.0
177    }
178}
179
180impl DerefMut for PluggableMonitorSettings {
181    fn deref_mut(&mut self) -> &mut Self::Target {
182        &mut self.0
183    }
184}
185
186/// All possible End Of Line values accepted by the Arduino Serial Plotter UI
187///
188/// # Examples
189///
190/// ```
191/// use arduino_plotter::protocol::EndOfLine;
192///
193/// let no_line_ending = EndOfLine::NoLineEnding;
194/// assert_eq!(
195///     "",
196///     serde_json::to_value(&no_line_ending)
197///         .unwrap()
198///         .as_str()
199///         .unwrap()
200/// );
201/// assert_eq!("", &no_line_ending.to_string());
202///
203/// let new_line = EndOfLine::NewLine;
204/// assert_eq!(
205///     "\n",
206///     serde_json::to_value(&new_line).unwrap().as_str().unwrap()
207/// );
208/// assert_eq!("\n", &new_line.to_string());
209///
210/// let carriage_return = EndOfLine::CarriageReturn;
211/// assert_eq!(
212///     "\r",
213///     serde_json::to_value(&carriage_return)
214///         .unwrap()
215///         .as_str()
216///         .unwrap()
217/// );
218/// assert_eq!("\r", &carriage_return.to_string());
219///
220/// let carriage_return_new_line = EndOfLine::CarriageReturnNewLine;
221/// assert_eq!(
222///     "\r\n",
223///     serde_json::to_value(&carriage_return_new_line)
224///         .unwrap()
225///         .as_str()
226///         .unwrap()
227/// );
228/// assert_eq!("\r\n", &carriage_return_new_line.to_string());
229/// ```
230#[derive(Debug, Clone, Copy, Serialize, Deserialize, Display, FromStr)]
231pub enum EndOfLine {
232    #[display("")]
233    #[serde(rename = "")]
234    NoLineEnding,
235    #[display("\n")]
236    #[serde(rename = "\n")]
237    NewLine,
238    #[display("\r")]
239    #[serde(rename = "\r")]
240    CarriageReturn,
241    #[display("\r\n")]
242    #[serde(rename = "\r\n")]
243    CarriageReturnNewLine,
244}
245
246impl EndOfLine {
247    /// A list of all the EndOfLine values as strings.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use arduino_plotter::protocol::EndOfLine;
253    ///
254    /// let all = &[
255    ///     EndOfLine::NoLineEnding.to_string(),
256    ///     EndOfLine::NewLine.to_string(),
257    ///     EndOfLine::CarriageReturn.to_string(),
258    ///     EndOfLine::CarriageReturnNewLine.to_string(),
259    /// ];
260    ///
261    /// assert_eq!(EndOfLine::EOL, all);
262    /// ```
263    pub const EOL: &'static [&'static str] = &["", "\n", "\r", "\r\n"];
264
265    /// Whether a string contains any of the EndOfLine values inside of it.
266    pub fn contains_eol(string: String) -> bool {
267        Self::EOL.iter().any(|eol| string.contains(eol))
268    }
269}
270
271/// All the UI Monitor settings that can be changed in the Arduino serial
272/// plotter application.
273#[derive(Default, Debug, Clone, Serialize, Deserialize)]
274#[serde(rename_all = "camelCase")]
275pub struct MonitorModelState {
276    #[serde(skip_serializing_if = "Option::is_none")]
277    /// Used by the serial monitors to stick at the bottom of the window.
278    pub autoscroll: Option<bool>,
279    /// Enable timestamp next to the actual data used by the serial monitors.
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub timestamp: Option<bool>,
282    /// Clients store the information about the last EOL used when sending a message to the board.
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub line_ending: Option<EndOfLine>,
285    /// Enables interpolation of the chart in the Serial Plotter App.
286    #[serde(default, skip_serializing_if = "Option::is_none")]
287    pub interpolate: Option<bool>,
288    // Whether to enable Dark theme or stick to the Light theme.
289    #[serde(default, skip_serializing_if = "Option::is_none")]
290    pub dark_theme: Option<bool>,
291    /// the current websocket port where the communication happens.
292    #[serde(default, skip_serializing_if = "Option::is_none")]
293    pub ws_port: Option<u16>,
294    /// The port at which the pluggable monitor in the middleware is connected to,
295    /// e.g. `/dev/ttyACM0` (linux), `/dev/ttyUSB0` (linux), etc.
296    #[serde(default, skip_serializing_if = "Option::is_none")]
297    pub serial_port: Option<String>,
298    #[serde(default, skip_serializing_if = "Option::is_none")]
299    /// The connection status of the pluggable monitor to the actual board.
300    pub connected: Option<bool>,
301    /// Enable mocked data generation.
302    #[serde(default)]
303    pub generate: bool,
304}
305
306/// The [`MiddlewareCommand`] Monitor settings that are sent to the
307/// Arduino serial plotter UI.
308/// This contains both [`PluggableMonitorSettings`] and [`MonitorModelState`].
309#[derive(Debug, Default, Clone, Serialize, Deserialize)]
310#[serde(rename_all = "camelCase")]
311pub struct MonitorSettings {
312    #[serde(default, skip_serializing_if = "Option::is_none")]
313    pub pluggable_monitor_settings: Option<PluggableMonitorSettings>,
314    #[serde(
315        default,
316        rename = "monitorUISettings",
317        skip_serializing_if = "Option::is_none"
318    )]
319    pub monitor_ui_settings: Option<MonitorModelState>,
320}