Skip to main content

sensor_log/
sensor_log.rs

1//! Embedded-style sensor-log example.
2//!
3//! Demonstrates a use case typical in constrained environments: parsing a
4//! stream of heterogeneous JSON records from a fixed-size input buffer,
5//! with no heap allocation at any step.
6//!
7//! Each record is one of:
8//!   {"type":"reading","sensor":"temp","value":2350}   (value in hundredths °C)
9//!   {"type":"reading","sensor":"humidity","value":6200}
10//!   {"type":"alert","sensor":"temp","code":1}
11//!   {"type":"heartbeat"}
12//!
13//! Shows both API tiers:
14//!
15//! **`std` tier**: `nanojson::stringify` / `nanojson::parse_as` for
16//! quick prototyping without buffer management.
17//!
18//! **`no_std` tier**: `nanojson::serialize::<N>` + `Parser::new` for the real
19//! embedded target — all memory on the stack.
20
21extern crate std;
22
23use nanojson::{Parser};
24
25// ---- Domain types (stack-allocated) ----
26
27#[derive(Debug)]
28enum Record {
29    Reading { sensor: SensorId, value: i64 },
30    Alert   { sensor: SensorId, code:  i64 },
31    Heartbeat,
32}
33
34#[derive(Debug, PartialEq, Copy, Clone)]
35enum SensorId {
36    Temp,
37    Humidity,
38    Unknown,
39}
40
41// ---- Parsing helpers ----
42
43/// Copy a &str into a fixed byte array.
44/// Used for string *values* decoded into a scratch buffer via `string()`.
45fn copy_str<const N: usize>(s: &str) -> ([u8; N], usize) {
46    let mut arr = [0u8; N];
47    let len = s.len().min(N);
48    arr[..len].copy_from_slice(&s.as_bytes()[..len]);
49    (arr, len)
50}
51
52fn bstr<const N: usize>(arr: &[u8; N], len: usize) -> &str {
53    core::str::from_utf8(&arr[..len]).unwrap_or("")
54}
55
56fn parse_sensor_id(s: &str) -> SensorId {
57    match s {
58        "temp"     => SensorId::Temp,
59        "humidity" => SensorId::Humidity,
60        _          => SensorId::Unknown,
61    }
62}
63
64/// Parse one record object. The opening `{` has already been consumed
65/// via `array_item()` + `object_begin()` in the caller.
66fn parse_record(json: &mut Parser) -> Record {
67    let mut type_arr  = [0u8; 16]; let mut type_len  = 0usize;
68    let mut sensor_arr= [0u8; 16]; let mut sensor_len= 0usize;
69    let mut value: Option<i64> = None;
70    let mut code:  Option<i64> = None;
71
72    while let Some(key) = json.member().unwrap() {
73        match key {
74            "type" => {
75                let s = json.string().unwrap();
76                let (a, l) = copy_str::<16>(s);
77                type_arr = a; type_len = l;
78            }
79            "sensor" => {
80                let s = json.string().unwrap();
81                let (a, l) = copy_str::<16>(s);
82                sensor_arr = a; sensor_len = l;
83            }
84            "value" => {
85                value = json.integer().ok();
86            }
87            "code" => {
88                code = json.integer().ok();
89            }
90            _ => { json.null().ok(); }
91        }
92    }
93    json.object_end().unwrap();
94
95    let type_str   = bstr(&type_arr,   type_len);
96    let sensor_str = bstr(&sensor_arr, sensor_len);
97
98    match type_str {
99        "reading"   => Record::Reading {
100            sensor: parse_sensor_id(sensor_str),
101            value:  value.unwrap_or(0),
102        },
103        "alert"     => Record::Alert {
104            sensor: parse_sensor_id(sensor_str),
105            code:   code.unwrap_or(0),
106        },
107        "heartbeat" => Record::Heartbeat,
108        other       => panic!("unknown record type: {other}"),
109    }
110}
111
112fn print_records(records: &[Option<Record>], count: usize) {
113    std::println!("Parsed {count} records:");
114    for i in 0..count {
115        if let Some(r) = &records[i] {
116            match r {
117                Record::Reading { sensor, value } => {
118                    let (int, frac) = (value / 100, value.abs() % 100);
119                    std::println!("  [{i}] Reading  {sensor:?}: {int}.{frac:02}");
120                }
121                Record::Alert { sensor, code } => {
122                    std::println!("  [{i}] Alert    {sensor:?}: code {code}");
123                }
124                Record::Heartbeat => {
125                    std::println!("  [{i}] Heartbeat");
126                }
127            }
128        }
129    }
130}
131
132fn main() {
133    // ==================================================================
134    // std tier — stringify / parse_as
135    // No buffer sizes to choose; heap grows as needed.
136    // ==================================================================
137
138    let json = nanojson::stringify_as(|json| {
139        json.array_begin()?;
140
141        json.object_begin()?;
142          json.member("type").unwrap();   json.string("reading")?;
143          json.member("sensor").unwrap(); json.string("temp")?;
144          json.member("value").unwrap();  json.integer(2350)?;
145        json.object_end()?;
146
147        json.object_begin()?;
148          json.member("type")?;   json.string("reading")?;
149          json.member("sensor")?; json.string("humidity")?;
150          json.member("value")?;  json.integer(6200)?;
151        json.object_end()?;
152
153        json.object_begin()?;
154          json.member("type")?; json.string("heartbeat")?;
155        json.object_end()?;
156
157        json.object_begin()?;
158          json.member("type")?;   json.string("alert")?;
159          json.member("sensor")?; json.string("temp")?;
160          json.member("code")?;   json.integer(1)?;
161        json.object_end()?;
162
163        json.array_end()
164    })
165    .unwrap();
166
167    std::println!("=== std tier ===");
168    std::println!("Log JSON:\n{}\n", json);
169
170    let mut records: [Option<Record>; 8] = [const { None }; 8];
171    let mut count = 0usize;
172
173    nanojson::parse_as(json.as_bytes(), |json| {
174        json.array_begin()?;
175        while json.array_item()? {
176            json.object_begin()?;
177            records[count] = Some(parse_record(json));
178            count += 1;
179        }
180        json.array_end()?;
181        Ok(())
182    })
183    .unwrap();
184
185    print_records(&records, count);
186
187    // ==================================================================
188    // no_std tier — serialize::<N> + Parser::new
189    // All memory on the stack; sizes chosen at compile time.
190    // ==================================================================
191
192    std::println!("\n=== no_std tier ===");
193
194    // Build the log into a 512-byte stack buffer.
195    let mut buf = [0; 512];
196    let log = nanojson::stringify_sized_as(&mut buf, |json| {
197        json.array_begin()?;
198
199        json.object_begin()?;
200          json.member("type")?;   json.string("reading")?;
201          json.member("sensor")?; json.string("temp")?;
202          json.member("value")?;  json.integer(2350)?;
203        json.object_end()?;
204
205        json.object_begin()?;
206          json.member("type")?;   json.string("reading")?;
207          json.member("sensor")?; json.string("humidity")?;
208          json.member("value")?;  json.integer(6200)?;
209        json.object_end()?;
210
211        json.object_begin()?;
212          json.member("type")?; json.string("heartbeat")?;
213        json.object_end()?;
214
215        json.object_begin()?;
216          json.member("type")?;   json.string("alert")?;
217          json.member("sensor")?; json.string("temp")?;
218          json.member("code")?;   json.integer(1)?;
219        json.object_end()?;
220
221        json.array_end()
222    })
223    .unwrap();
224
225    std::println!("Log JSON ({} bytes):\n{}\n", log.len(), log);
226
227    // Parse the log. str_buf = 32 bytes is enough for any single field value.
228    let mut records: [Option<Record>; 8] = [const { None }; 8];
229    let mut count = 0usize;
230
231    let mut str_buf = [0u8; 32];
232    let mut json = Parser::new(log.as_bytes(), &mut str_buf);
233
234    json.array_begin().unwrap();
235    while json.array_item().unwrap() {
236        json.object_begin().unwrap();
237        records[count] = Some(parse_record(&mut json));
238        count += 1;
239    }
240    json.array_end().unwrap();
241
242    print_records(&records, count);
243
244    // ==================================================================
245    // Size estimation — useful for choosing N on constrained targets
246    // ==================================================================
247
248    let n = nanojson::measure(|json| {
249        json.object_begin()?;
250          json.member("type")?;   json.string("reading")?;
251          json.member("sensor")?; json.string("temp")?;
252          json.member("value")?;  json.integer(2350)?;
253        json.object_end()
254    });
255    std::println!("\nA 'reading' record is {n} bytes when serialized.");
256}
257
258#[cfg(test)] #[test] fn test_main() { main() }