signalk/
lib.rs

1//! # Signal K
2//!
3//! `signalk` is a collections of types to serialize and deserialize the
4//! signal-k protocol.
5
6use serde::{Deserialize, Serialize};
7
8pub use definitions::{
9    V1Attr, V1CommonValueFields, V1DefSource, V1Meta, V1MetaZone, V1NumberValue,
10};
11pub use delta::{V1DeltaFormat, V1UpdateMeta, V1UpdateType, V1UpdateValue, V1UpdateValueType};
12pub use discovery::{V1Discovery, V1DiscoveryEndpoint, V1DiscoveryServer};
13pub use electrical::{V1ACBus, V1Electrical, V1ElectricalACQualities, V1ElectricalIdentity};
14pub use environment::{
15    V1Environment, V1EnvironmentCurrent, V1EnvironmentCurrentValue, V1EnvironmentDepth,
16    V1EnvironmentInside, V1EnvironmentTime,
17};
18pub use full::V1FullFormat;
19pub use hello::V1Hello;
20pub use navigation::{V1Navigation, V1PositionType, V1PositionValue};
21pub use notification::{V1Notification, V1NotificationValue};
22pub use propulsion::V1Propulsion;
23pub use put::{V1Put, V1PutValue};
24pub use sources::{V1Source, V1SourceProperty, V1Sources};
25pub use subscribe::{V1Subscribe, V1Subscription};
26pub use unsubscribe::{V1Unsubscribe, V1Unsubscription};
27pub use vessel::V1Vessel;
28
29pub mod communication;
30pub mod definitions;
31pub mod delta;
32mod design;
33pub mod discovery;
34pub mod electrical;
35pub mod environment;
36pub mod full;
37pub mod hello;
38mod helper_functions;
39pub mod navigation;
40mod navigation_course;
41mod navigation_gnss;
42pub mod notification;
43mod performance;
44pub mod propulsion;
45pub mod put;
46pub mod sources;
47mod steering;
48pub mod subscribe;
49pub mod unsubscribe;
50pub mod vessel;
51
52/// Type for messages that can be received over the signal-k stream
53#[derive(Serialize, Deserialize, PartialEq, Debug, Default)]
54#[serde(untagged)]
55pub enum SignalKStreamMessage {
56    Hello(V1Hello),
57    Full(V1FullFormat),
58    Delta(V1DeltaFormat),
59    #[default]
60    BadData,
61}
62
63/// Possible Errors gen getting data from SignalK storage
64#[derive(Debug, PartialEq)]
65pub enum SignalKGetError {
66    NoSuchPath,
67    WrongDataType,
68    ValueNotSet,
69    TBD,
70}
71
72/// Keep data from Signal-K
73///
74/// This struct keeps data for a signalk environment, it can be reviced by a full
75/// message and update with a delta message.
76///
77/// # Examples
78///
79/// ```
80/// use signalk::{Storage, V1FullFormat};
81/// let j = r#"
82///         { "self":"vessels.urn:mrn:imo:mmsi:366982330",
83///           "vessels": {
84///              "urn:mrn:imo:mmsi:366982330": {
85///                "mmsi": "366982330"
86///              }
87///           },
88///           "version": "1.0.0"
89///         }"#;
90/// let full_data: V1FullFormat = serde_json::from_str(j).unwrap();
91/// let storage = Storage::new(full_data);
92/// assert_eq!(
93///    storage.data().get_self().unwrap().mmsi.clone().unwrap(),
94///    "366982330".to_string())
95/// ```
96///
97#[derive(Debug, Default)]
98pub struct Storage {
99    data: V1FullFormat,
100}
101
102impl Storage {
103    /// Set the id of the self vessel.
104    pub fn set_self(&mut self, value: &str) {
105        self.data.self_ = value.to_string();
106    }
107
108    /// Apply a Delta change to the structure
109    ///
110    /// # Examples
111    /// ```
112    /// use signalk::{Storage, V1DeltaFormat, V1FullFormat};
113    /// let mut storage = Storage::new(V1FullFormat::default());
114    /// let j = r#"{"updates": [
115    ///                {"values": [{"path": "navigation.courseOverGroundTrue", "value": 123.45 }]}
116    ///             ],
117    ///             "context": "vessels.urn:mrn:signalk:uuid:7980c650-6871-45d5-be0f-205d2efadacc"
118    /// }"#;
119    /// let delta_message: V1DeltaFormat = serde_json::from_str(j).unwrap();
120    /// storage.update(&delta_message);
121    /// println!("{:?}", storage);
122    /// assert_eq!(
123    ///   storage.get_f64_for_path(
124    ///     "vessels.urn:mrn:signalk:uuid:7980c650-6871-45d5-be0f-205d2efadacc.navigation.courseOverGroundTrue".to_string())
125    ///     .unwrap(),
126    ///   123.45)
127    /// ```
128    pub fn update(&mut self, delta: &V1DeltaFormat) {
129        self.data.apply_delta(delta);
130    }
131
132    /// Get a clone of the stored data
133    ///
134    /// # Examples
135    /// ```
136    /// use signalk::{Storage, V1FullFormat};
137    /// let data = V1FullFormat::default();
138    /// let storage = Storage::new(data.clone());
139    ///
140    /// assert_eq!(data, storage.get());
141    /// ```
142    pub fn get(&self) -> V1FullFormat {
143        (self.data).clone()
144    }
145
146    /// Get a reference to the stored data
147    ///
148    /// # Examples
149    /// ```
150    /// use signalk::{Storage, V1FullFormat};
151    /// let data = V1FullFormat::default();
152    /// let storage = Storage::new(data.clone());
153    ///
154    /// assert_eq!(&data, storage.data());
155    /// ```
156    pub fn data(&self) -> &V1FullFormat {
157        &self.data
158    }
159
160    /// Create a new structure with data.
161    ///
162    /// # Examples
163    /// ```
164    /// use signalk::{Storage, V1FullFormat};
165    /// let data = V1FullFormat::default();
166    /// let storage = Storage::new(data.clone());
167    ///
168    /// assert_eq!(data, storage.get());
169    /// ```
170    pub fn new(data: V1FullFormat) -> Self {
171        Self { data }
172    }
173
174    /// Return the f64 value stored for a SignalK path
175    ///
176    /// # Examples
177    /// ```
178    /// use signalk::{Storage, V1FullFormat};
179    /// let j = r#"
180    ///         { "self":"vessels.urn:mrn:imo:mmsi:366982330",
181    ///           "vessels": {
182    ///              "urn:mrn:imo:mmsi:366982330": {
183    ///                "navigation": {
184    ///                  "courseOverGroundTrue": {
185    ///                    "value": 245.69,
186    ///                    "timestamp": "2015-01-25T12:01:01Z",
187    ///                    "$source": "a.suitable.path"
188    ///                  }
189    ///                }
190    ///              }
191    ///           },
192    ///           "version": "1.0.0"
193    ///         }"#;
194    /// let storage = Storage::new(serde_json::from_str(j).unwrap());
195    ///
196    /// assert_eq!(
197    ///   storage.get_f64_for_path("self.navigation.courseOverGroundTrue".to_string())
198    ///     .unwrap(),
199    ///   245.69)
200    /// ```
201    pub fn get_f64_for_path(&self, path: String) -> Result<f64, SignalKGetError> {
202        self.data.get_f64_for_path(path)
203    }
204}
205
206#[cfg(test)]
207mod storage_tests {
208    use serde_json::{Number, Value};
209
210    use crate::{
211        Storage, V1DeltaFormat, V1FullFormat, V1Navigation, V1NumberValue, V1UpdateType,
212        V1UpdateValue, V1Vessel,
213    };
214
215    #[test]
216    fn get_gives_default() {
217        let storage = Storage::default();
218        let expected = V1FullFormat::default();
219        assert_eq!(expected, storage.get())
220    }
221
222    #[test]
223    fn apply_delta_for_sog_5_6() {
224        let mut storage = Storage::default();
225        let expected = V1FullFormat::builder()
226            .add_vessel(
227                "urn:mrn:imo:mmsi:366982330".into(),
228                V1Vessel::builder()
229                    .mmsi("366982330".into())
230                    .navigation(
231                        V1Navigation::builder()
232                            .speed_over_ground(V1NumberValue::builder().value(5.6).build())
233                            .build(),
234                    )
235                    .build(),
236            )
237            .build();
238        let delta = V1DeltaFormat::builder()
239            .context("vessels.urn:mrn:imo:mmsi:366982330".into())
240            .add_update(
241                V1UpdateType::builder()
242                    .add_update(V1UpdateValue::new(
243                        "navigation.speedOverGround".into(),
244                        Value::Number(Number::from_f64(5.6).unwrap()),
245                    ))
246                    .build(),
247            )
248            .build();
249        storage.update(&delta);
250        assert_eq!(expected, storage.get())
251    }
252
253    #[test]
254    fn apply_delta_for_sog_15_8() {
255        let mut storage = Storage::default();
256        let expected = V1FullFormat::builder()
257            .add_vessel(
258                "urn:mrn:imo:mmsi:366982330".into(),
259                V1Vessel::builder()
260                    .mmsi("366982330".into())
261                    .navigation(
262                        V1Navigation::builder()
263                            .speed_over_ground(V1NumberValue::builder().value(15.8).build())
264                            .build(),
265                    )
266                    .build(),
267            )
268            .build();
269        let delta = V1DeltaFormat::builder()
270            .context("vessels.urn:mrn:imo:mmsi:366982330".into())
271            .add_update(
272                V1UpdateType::builder()
273                    .add_update(V1UpdateValue::new(
274                        "navigation.speedOverGround".into(),
275                        Value::Number(Number::from_f64(15.8).unwrap()),
276                    ))
277                    .build(),
278            )
279            .build();
280        storage.update(&delta);
281        assert_eq!(expected, storage.get())
282    }
283}