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}