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}