1313//! "pressure": 220
1414//! }"#;
1515//! let entity = CloudEntityMetadata::new("foo".into(), EntityMetadata::main_device(None));
16- //! let output = from_thin_edge_json(single_value_thin_edge_json, &entity,"");
16+ //! let output = from_thin_edge_json(single_value_thin_edge_json, &entity,"",None );
1717//! ```
1818
1919use crate :: entity_cache:: CloudEntityMetadata ;
2020use crate :: serializer;
2121use clock:: Clock ;
2222use clock:: WallClock ;
23+ use std:: collections:: HashMap ;
2324use tedge_api:: measurement:: * ;
2425use time:: OffsetDateTime ;
2526use time:: { self } ;
@@ -38,9 +39,10 @@ pub fn from_thin_edge_json(
3839 input : & str ,
3940 entity : & CloudEntityMetadata ,
4041 m_type : & str ,
42+ units : Option < & Units > ,
4143) -> Result < String , CumulocityJsonError > {
4244 let timestamp = WallClock . now ( ) ;
43- let c8y_vec = from_thin_edge_json_with_timestamp ( input, timestamp, entity, m_type) ?;
45+ let c8y_vec = from_thin_edge_json_with_timestamp ( input, timestamp, entity, m_type, units ) ?;
4446 Ok ( c8y_vec)
4547}
4648
@@ -49,12 +51,89 @@ fn from_thin_edge_json_with_timestamp(
4951 timestamp : OffsetDateTime ,
5052 entity : & CloudEntityMetadata ,
5153 m_type : & str ,
54+ units : Option < & Units > ,
5255) -> Result < String , CumulocityJsonError > {
53- let mut serializer = serializer:: C8yJsonSerializer :: new ( timestamp, entity, m_type) ;
56+ let mut serializer = serializer:: C8yJsonSerializer :: new ( timestamp, entity, m_type, units ) ;
5457 parse_str ( input, & mut serializer) ?;
5558 Ok ( serializer. into_string ( ) ?)
5659}
5760
61+ /// Units used for measurements of a given type
62+ #[ derive( Default ) ]
63+ pub struct Units {
64+ units : HashMap < String , String > ,
65+ group_units : HashMap < String , Units > ,
66+ }
67+
68+ impl Units {
69+ /// An empty set of measurement units
70+ ///
71+ /// This is the default when no measurement metadata is published for a measurement topic
72+ pub fn new ( ) -> Units {
73+ Units {
74+ units : HashMap :: new ( ) ,
75+ group_units : HashMap :: new ( ) ,
76+ }
77+ }
78+
79+ /// True if no units are actually defined
80+ pub fn is_empty ( & self ) -> bool {
81+ self . units . is_empty ( ) && self . group_units . is_empty ( )
82+ }
83+
84+ /// Measurement units as defined by metadata published on a measurement topic
85+ pub fn from_metadata ( meta : serde_json:: Value ) -> Self {
86+ let mut units = Units :: new ( ) ;
87+ if let serde_json:: Value :: Object ( map) = meta {
88+ for ( k, v) in map {
89+ units. set_unit ( k, v) ;
90+ }
91+ }
92+ units
93+ }
94+
95+ pub fn set_unit ( & mut self , measurement : String , meta : serde_json:: Value ) {
96+ if let Some ( serde_json:: Value :: String ( unit) ) = meta. get ( "unit" ) {
97+ match measurement. split_once ( '.' ) {
98+ None => {
99+ // "Temperature": {"unit": "°C"},
100+ self . units . insert ( measurement, unit. to_owned ( ) ) ;
101+ }
102+ Some ( ( group, measurement) ) => {
103+ // "Climate.Temperature": {"unit": "°C"},
104+ self . group_units
105+ . entry ( group. to_owned ( ) )
106+ . or_default ( )
107+ . set_unit ( measurement. to_owned ( ) , meta) ;
108+ }
109+ }
110+ }
111+ }
112+
113+ pub fn set_group_units ( & mut self , group : String , meta : serde_json:: Value ) {
114+ let units = Units :: from_metadata ( meta) ;
115+ if units. is_empty ( ) {
116+ self . group_units . remove ( & group) ;
117+ } else {
118+ self . group_units . insert ( group, units) ;
119+ }
120+ }
121+
122+ pub fn unset_group_units ( & mut self , group : String ) {
123+ self . group_units . remove ( & group) ;
124+ }
125+
126+ /// Retrieve the unit to be used for a measurement, if any
127+ pub fn get_unit ( & self , measurement : & str ) -> Option < & str > {
128+ self . units . get ( measurement) . map ( |x| x. as_str ( ) )
129+ }
130+
131+ /// Retrieve the units to be used for a measurement group, if any
132+ pub fn get_group_units ( & self , group : & str ) -> Option < & Units > {
133+ self . group_units . get ( group)
134+ }
135+ }
136+
58137#[ cfg( test) ]
59138mod tests {
60139 use super :: * ;
@@ -77,8 +156,13 @@ mod tests {
77156 let timestamp = datetime ! ( 2021 -04 -08 0 : 00 : 0 +05 : 00 ) ;
78157
79158 let entity = CloudEntityMetadata :: new ( "foo" . into ( ) , EntityMetadata :: main_device ( None ) ) ;
80- let output =
81- from_thin_edge_json_with_timestamp ( single_value_thin_edge_json, timestamp, & entity, "" ) ;
159+ let output = from_thin_edge_json_with_timestamp (
160+ single_value_thin_edge_json,
161+ timestamp,
162+ & entity,
163+ "" ,
164+ None ,
165+ ) ;
82166
83167 let expected_output = json ! ( {
84168 "time" : timestamp
@@ -114,8 +198,13 @@ mod tests {
114198 let timestamp = datetime ! ( 2021 -04 -08 0 : 00 : 0 +05 : 00 ) ;
115199
116200 let entity = CloudEntityMetadata :: new ( "foo" . into ( ) , EntityMetadata :: main_device ( None ) ) ;
117- let output =
118- from_thin_edge_json_with_timestamp ( single_value_thin_edge_json, timestamp, & entity, "" ) ;
201+ let output = from_thin_edge_json_with_timestamp (
202+ single_value_thin_edge_json,
203+ timestamp,
204+ & entity,
205+ "" ,
206+ None ,
207+ ) ;
119208
120209 let expected_output = json ! ( {
121210 "time" : timestamp
@@ -160,7 +249,7 @@ mod tests {
160249 }"# ;
161250
162251 let entity = CloudEntityMetadata :: new ( "foo" . into ( ) , EntityMetadata :: main_device ( None ) ) ;
163- let output = from_thin_edge_json ( single_value_thin_edge_json, & entity, "" ) ;
252+ let output = from_thin_edge_json ( single_value_thin_edge_json, & entity, "" , None ) ;
164253
165254 assert_eq ! (
166255 expected_output. split_whitespace( ) . collect:: <String >( ) ,
@@ -183,8 +272,13 @@ mod tests {
183272 let timestamp = datetime ! ( 2021 -04 -08 0 : 00 : 0 +05 : 00 ) ;
184273
185274 let entity = CloudEntityMetadata :: new ( "foo" . into ( ) , EntityMetadata :: main_device ( None ) ) ;
186- let output =
187- from_thin_edge_json_with_timestamp ( multi_value_thin_edge_json, timestamp, & entity, "" ) ;
275+ let output = from_thin_edge_json_with_timestamp (
276+ multi_value_thin_edge_json,
277+ timestamp,
278+ & entity,
279+ "" ,
280+ None ,
281+ ) ;
188282
189283 let expected_output = json ! ( {
190284 "time" : timestamp
@@ -221,6 +315,56 @@ mod tests {
221315 ) ;
222316 }
223317
318+ #[ test]
319+ fn using_metadata_to_define_measurement_units ( ) {
320+ let input = r#"
321+ {
322+ "time": "2013-06-22T17:03:14.123+02:00",
323+ "Climate":{
324+ "Temperature":23.4,
325+ "Humidity":95.0
326+ },
327+ "Acceleration":{
328+ "X-Axis":0.002,
329+ "Y-Axis":0.015,
330+ "Z-Axis":5.0
331+ }
332+ }"# ;
333+
334+ let units = r#"
335+ {
336+ "Climate.Temperature":{"unit": "°C"},
337+ "Climate.Humidity": {"unit": "%RH"},
338+ "Acceleration.X-Axis": {"unit": "m/s²"},
339+ "Acceleration.Y-Axis": {"unit": "m/s²"},
340+ "Acceleration.Z-Axis": {"unit": "m/s²"}
341+ }"# ;
342+
343+ let expected_output = r#"
344+ {
345+ "time": "2013-06-22T17:03:14.123+02:00",
346+ "Climate": {
347+ "Temperature": {"value":23.4,"unit":"°C"},
348+ "Humidity":{"value":95.0,"unit":"%RH"}
349+ },
350+ "Acceleration": {
351+ "X-Axis": {"value":0.002,"unit":"m/s²"},
352+ "Y-Axis": {"value":0.015,"unit":"m/s²"},
353+ "Z-Axis": {"value":5.0,"unit":"m/s²"}
354+ },
355+ "type": "ThinEdgeMeasurement"
356+ }"# ;
357+
358+ let entity = CloudEntityMetadata :: new ( "foo" . into ( ) , EntityMetadata :: main_device ( None ) ) ;
359+ let units = Units :: from_metadata ( serde_json:: from_str ( units) . unwrap ( ) ) ;
360+ let output = from_thin_edge_json ( input, & entity, "" , Some ( & units) ) ;
361+
362+ assert_eq ! (
363+ expected_output. split_whitespace( ) . collect:: <String >( ) ,
364+ output. unwrap( ) . split_whitespace( ) . collect:: <String >( )
365+ ) ;
366+ }
367+
224368 #[ test]
225369 fn thin_edge_json_round_tiny_number ( ) {
226370 let input = r#"{
@@ -239,7 +383,7 @@ mod tests {
239383 }"# ;
240384
241385 let entity = CloudEntityMetadata :: new ( "foo" . into ( ) , EntityMetadata :: main_device ( None ) ) ;
242- let output = from_thin_edge_json ( input, & entity, "" ) ;
386+ let output = from_thin_edge_json ( input, & entity, "" , None ) ;
243387
244388 let actual_output = output. unwrap ( ) . split_whitespace ( ) . collect :: < String > ( ) ;
245389
@@ -272,7 +416,7 @@ mod tests {
272416 }}"# , time, measurement, measurement) ;
273417
274418 let entity = CloudEntityMetadata :: new( "foo" . into( ) , EntityMetadata :: main_device( None ) ) ;
275- let output = from_thin_edge_json( input. as_str( ) , & entity, "" ) . unwrap( ) ;
419+ let output = from_thin_edge_json( input. as_str( ) , & entity, "" , None ) . unwrap( ) ;
276420 assert_eq!(
277421 expected_output. split_whitespace( ) . collect:: <String >( ) ,
278422 output
@@ -323,7 +467,8 @@ mod tests {
323467 child_id. into ( ) ,
324468 EntityMetadata :: child_device ( child_id. to_string ( ) ) . unwrap ( ) ,
325469 ) ;
326- let output = from_thin_edge_json_with_timestamp ( thin_edge_json, timestamp, & entity, "" ) ;
470+ let output =
471+ from_thin_edge_json_with_timestamp ( thin_edge_json, timestamp, & entity, "" , None ) ;
327472 assert_json_eq ! (
328473 serde_json:: from_str:: <serde_json:: Value >( output. unwrap( ) . as_str( ) ) . unwrap( ) ,
329474 expected_output
0 commit comments