blues_notecard/
card.rs

1//! https://dev.blues.io/reference/notecard-api/card-requests/
2
3#[allow(unused_imports)]
4use defmt::{debug, error, info, trace, warn};
5use embedded_hal::blocking::delay::DelayMs;
6use embedded_hal::blocking::i2c::{Read, SevenBitAddress, Write};
7use serde::{Deserialize, Serialize};
8
9use super::{str_string, FutureResponse, NoteError, Notecard};
10
11pub struct Card<'a, IOM: Write<SevenBitAddress> + Read<SevenBitAddress>, const BS: usize> {
12    note: &'a mut Notecard<IOM, BS>,
13}
14
15/// https://dev.blues.io/api-reference/notecard-api/card-requests/latest/#card-transport
16pub enum Transport {
17    Reset,
18    WifiCell,
19    Wifi,
20    Cell,
21    NTN,
22    WifiNTN,
23    CellNTN,
24    WifiCellNTN,
25}
26
27impl Transport {
28    pub fn str(&self) -> &'static str {
29        use Transport::*;
30
31        match self {
32            Reset => "-",
33            WifiCell => "wifi-cell",
34            Wifi => "wifi",
35            Cell => "cell",
36            NTN => "ntn",
37            WifiNTN => "wifi-ntn",
38            CellNTN => "cell-ntn",
39            WifiCellNTN => "wifi-cell-ntn",
40        }
41    }
42}
43
44/// See https://dev.blues.io/api-reference/notecard-api/card-requests/latest/#card-aux for
45/// details.
46pub enum GpioMode {
47    Off,
48    Low,
49    High,
50    Input,
51}
52
53impl GpioMode {
54    pub fn str(&self) -> &'static str {
55        use GpioMode::*;
56
57        match self {
58            Off => "off",
59            Low => "low",
60            High => "high",
61            Input => "input",
62        }
63    }
64}
65
66impl<'a, IOM: Write<SevenBitAddress> + Read<SevenBitAddress>, const BS: usize> Card<'a, IOM, BS> {
67    pub fn from(note: &mut Notecard<IOM, BS>) -> Card<'_, IOM, BS> {
68        Card { note }
69    }
70
71    /// Retrieves current date and time information. Upon power-up, the Notecard must complete a
72    /// sync to Notehub in order to obtain time and location data. Before the time is obtained,
73    /// this request will return `{"zone":"UTC,Unknown"}`.
74    pub fn time(
75        self,
76        delay: &mut impl DelayMs<u16>,
77    ) -> Result<FutureResponse<'a, res::Time, IOM, BS>, NoteError> {
78        self.note.request_raw(delay, b"{\"req\":\"card.time\"}\n")?;
79        Ok(FutureResponse::from(self.note))
80    }
81
82    /// Returns general information about the Notecard's operating status.
83    pub fn status(
84        self,
85        delay: &mut impl DelayMs<u16>,
86    ) -> Result<FutureResponse<'a, res::Status, IOM, BS>, NoteError> {
87        self.note
88            .request_raw(delay, b"{\"req\":\"card.status\"}\n")?;
89        Ok(FutureResponse::from(self.note))
90    }
91
92    /// Performs a firmware restart of the Notecard.
93    pub fn restart(
94        self,
95        delay: &mut impl DelayMs<u16>,
96    ) -> Result<FutureResponse<'a, res::Empty, IOM, BS>, NoteError> {
97        self.note
98            .request_raw(delay, b"{\"req\":\"card.restart\"}\n")?;
99        Ok(FutureResponse::from(self.note))
100    }
101
102    /// Retrieves the current location of the Notecard.
103    pub fn location(
104        self,
105        delay: &mut impl DelayMs<u16>,
106    ) -> Result<FutureResponse<'a, res::Location, IOM, BS>, NoteError> {
107        self.note
108            .request_raw(delay, b"{\"req\":\"card.location\"}\n")?;
109        Ok(FutureResponse::from(self.note))
110    }
111
112    /// Sets location-related configuration settings. Retrieves the current location mode when passed with no argument.
113    pub fn location_mode(
114        self,
115        delay: &mut impl DelayMs<u16>,
116        mode: Option<&str>,
117        seconds: Option<u32>,
118        vseconds: Option<&str>,
119        delete: Option<bool>,
120        max: Option<u32>,
121        lat: Option<f32>,
122        lon: Option<f32>,
123        minutes: Option<u32>,
124    ) -> Result<FutureResponse<'a, res::LocationMode, IOM, BS>, NoteError> {
125        self.note.request(
126            delay,
127            req::LocationMode {
128                req: "card.location.mode",
129                mode: str_string(mode)?,
130                seconds,
131                vseconds: str_string(vseconds)?,
132                delete,
133                max,
134                lat,
135                lon,
136                minutes,
137            },
138        )?;
139        Ok(FutureResponse::from(self.note))
140    }
141
142    /// Store location data in a Notefile at the `periodic` interval, or using specified `heartbeat`.
143    /// Only available when `card.location.mode` has been set to `periodic`.
144    pub fn location_track(
145        self,
146        delay: &mut impl DelayMs<u16>,
147        start: bool,
148        heartbeat: bool,
149        sync: bool,
150        hours: Option<i32>,
151        file: Option<&str>,
152    ) -> Result<FutureResponse<'a, res::LocationTrack, IOM, BS>, NoteError> {
153        self.note.request(
154            delay,
155            req::LocationTrack {
156                req: "card.location.track",
157                start: start.then_some(true),
158                stop: (!start).then_some(true),
159                heartbeat: heartbeat.then_some(true),
160                sync: sync.then_some(true),
161                hours,
162                file: str_string(file)?,
163            },
164        )?;
165
166        Ok(FutureResponse::from(self.note))
167    }
168
169    pub fn wireless(
170        self,
171        delay: &mut impl DelayMs<u16>,
172        mode: Option<&str>,
173        apn: Option<&str>,
174        method: Option<&str>,
175        hours: Option<u32>,
176    ) -> Result<FutureResponse<'a, res::Wireless, IOM, BS>, NoteError> {
177        self.note.request(
178            delay,
179            req::Wireless {
180                req: "card.wireless",
181                mode: str_string(mode)?,
182                method: str_string(method)?,
183                apn: str_string(apn)?,
184                hours,
185            },
186        )?;
187
188        Ok(FutureResponse::from(self.note))
189    }
190
191    /// Returns firmware version information for the Notecard.
192    pub fn version(
193        self,
194        delay: &mut impl DelayMs<u16>,
195    ) -> Result<FutureResponse<'a, res::Version, IOM, BS>, NoteError> {
196        self.note
197            .request_raw(delay, b"{\"req\":\"card.version\"}\n")?;
198        Ok(FutureResponse::from(self.note))
199    }
200
201    /// Configure Notecard Outboard Firmware Update feature
202    /// Added in v3.5.1 Notecard Firmware.
203    pub fn dfu(
204        self,
205        delay: &mut impl DelayMs<u16>,
206        name: Option<req::DFUName>,
207        on: Option<bool>,
208        stop: Option<bool>,
209    ) -> Result<FutureResponse<'a, res::DFU, IOM, BS>, NoteError> {
210        self.note.request(delay, req::DFU::new(name, on, stop))?;
211        Ok(FutureResponse::from(self.note))
212    }
213
214    pub fn transport(
215        self,
216        delay: &mut impl DelayMs<u16>,
217        method: Transport,
218        allow: Option<bool>,
219        umin: Option<bool>,
220        seconds: Option<u32>,
221    ) -> Result<FutureResponse<'a, res::Transport, IOM, BS>, NoteError> {
222        self.note.request(
223            delay,
224            req::Transport {
225                req: "card.transport",
226                method: method.str(),
227                allow,
228                umin,
229                seconds,
230            },
231        )?;
232        Ok(FutureResponse::from(self.note))
233    }
234
235    /// Turn AUX pins off.
236    ///
237    /// https://dev.blues.io/api-reference/notecard-api/card-requests/latest/#card-aux
238    pub fn aux_off(
239        self,
240        delay: &mut impl DelayMs<u16>,
241    ) -> Result<FutureResponse<'a, res::Aux, IOM, BS>, NoteError> {
242        self.note.request_raw(delay, b"{\"req\":\"card.aux\", \"mode\":\"off\"}\n")?;
243        Ok(FutureResponse::from(self.note))
244    }
245
246    /// Configure AUX ports to act as GPIOs.
247    ///
248    /// https://dev.blues.io/api-reference/notecard-api/card-requests/latest/#card-aux
249    pub fn aux_gpio(
250        self,
251        delay: &mut impl DelayMs<u16>,
252        aux1: GpioMode,
253        aux2: GpioMode,
254        aux3: GpioMode,
255        aux4: GpioMode,
256    ) -> Result<FutureResponse<'a, res::Aux, IOM, BS>, NoteError> {
257        self.note.request(
258            delay,
259            req::Aux {
260                req: "card.aux",
261                mode: "gpio",
262                usage: [aux1.str(), aux2.str(), aux3.str(), aux4.str()],
263            },
264        )?;
265        Ok(FutureResponse::from(self.note))
266    }
267}
268
269pub mod req {
270
271    use super::*;
272
273    #[derive(Deserialize, Serialize, Debug, defmt::Format, Default)]
274    pub struct Aux {
275        pub req: &'static str,
276        pub mode: &'static str,
277        pub usage: [&'static str; 4],
278    }
279
280    #[derive(Deserialize, Serialize, Debug, defmt::Format, Default)]
281    pub struct Transport {
282        pub req: &'static str,
283
284        pub method: &'static str,
285
286        #[serde(skip_serializing_if = "Option::is_none")]
287        pub allow: Option<bool>,
288
289        #[serde(skip_serializing_if = "Option::is_none")]
290        pub umin: Option<bool>,
291
292        /// Fallback time to cellular (default 3600 seconds, 60 minutes).
293        #[serde(skip_serializing_if = "Option::is_none")]
294        pub seconds: Option<u32>,
295    }
296
297    #[derive(Deserialize, Serialize, Debug, defmt::Format, Default)]
298    pub struct Wireless {
299        pub req: &'static str,
300
301        #[serde(skip_serializing_if = "Option::is_none")]
302        pub mode: Option<heapless::String<20>>,
303
304        #[serde(skip_serializing_if = "Option::is_none")]
305        pub apn: Option<heapless::String<120>>,
306
307        #[serde(skip_serializing_if = "Option::is_none")]
308        pub method: Option<heapless::String<120>>,
309
310        #[serde(skip_serializing_if = "Option::is_none")]
311        pub hours: Option<u32>,
312    }
313
314    #[derive(Deserialize, Serialize, Debug, defmt::Format, Default)]
315    pub struct LocationTrack {
316        pub req: &'static str,
317
318        #[serde(skip_serializing_if = "Option::is_none")]
319        pub start: Option<bool>,
320
321        #[serde(skip_serializing_if = "Option::is_none")]
322        pub heartbeat: Option<bool>,
323
324        #[serde(skip_serializing_if = "Option::is_none")]
325        pub sync: Option<bool>,
326
327        #[serde(skip_serializing_if = "Option::is_none")]
328        pub stop: Option<bool>,
329
330        #[serde(skip_serializing_if = "Option::is_none")]
331        pub hours: Option<i32>,
332
333        #[serde(skip_serializing_if = "Option::is_none")]
334        pub file: Option<heapless::String<20>>,
335    }
336
337    #[derive(Deserialize, Serialize, Debug, defmt::Format, Default)]
338    pub struct LocationMode {
339        pub req: &'static str,
340
341        #[serde(skip_serializing_if = "Option::is_none")]
342        pub mode: Option<heapless::String<20>>,
343
344        #[serde(skip_serializing_if = "Option::is_none")]
345        pub seconds: Option<u32>,
346
347        #[serde(skip_serializing_if = "Option::is_none")]
348        pub vseconds: Option<heapless::String<20>>,
349
350        #[serde(skip_serializing_if = "Option::is_none")]
351        pub delete: Option<bool>,
352
353        #[serde(skip_serializing_if = "Option::is_none")]
354        pub max: Option<u32>,
355
356        #[serde(skip_serializing_if = "Option::is_none")]
357        pub lat: Option<f32>,
358
359        #[serde(skip_serializing_if = "Option::is_none")]
360        pub lon: Option<f32>,
361
362        #[serde(skip_serializing_if = "Option::is_none")]
363        pub minutes: Option<u32>,
364    }
365
366    #[derive(Deserialize, Serialize, Debug, defmt::Format, PartialEq)]
367    #[serde(rename_all = "lowercase")]
368    pub enum DFUName {
369        Esp32,
370        Stm32,
371        #[serde(rename = "stm32-bi")]
372        Stm32Bi,
373        McuBoot,
374        #[serde(rename = "-")]
375        Reset,
376    }
377
378    #[derive(Deserialize, Serialize, Debug, defmt::Format)]
379    pub struct DFU {
380        pub req: &'static str,
381
382        #[serde(skip_serializing_if = "Option::is_none")]
383        pub name: Option<req::DFUName>,
384
385        #[serde(skip_serializing_if = "Option::is_none")]
386        pub on: Option<bool>,
387
388        #[serde(skip_serializing_if = "Option::is_none")]
389        pub off: Option<bool>,
390
391        #[serde(skip_serializing_if = "Option::is_none")]
392        pub stop: Option<bool>,
393
394        #[serde(skip_serializing_if = "Option::is_none")]
395        pub start: Option<bool>,
396    }
397
398    impl DFU {
399        pub fn new(name: Option<req::DFUName>, on: Option<bool>, stop: Option<bool>) -> Self {
400            // The `on`/`off` and `stop`/`start` parameters are exclusive
401            // When on is `true` we set `on` to `Some(True)` and `off` to `None`.
402            // When on is `false` we set `on` to `None` and `off` to `Some(True)`.
403            // This way we are not sending the `on` and `off` parameters together.
404            // Same thing applies to the `stop`/`start` parameter.
405            Self {
406                req: "card.dfu",
407                name,
408                on: on.and_then(|v| if v { Some(true) } else { None }),
409                off: on.and_then(|v| if v { None } else { Some(true) }),
410                stop: stop.and_then(|v| if v { Some(true) } else { None }),
411                start: stop.and_then(|v| if v { None } else { Some(true) }),
412            }
413        }
414    }
415}
416
417pub mod res {
418    use super::*;
419
420    #[derive(Deserialize, Debug, defmt::Format)]
421    pub struct Empty {}
422
423    #[derive(Deserialize, Debug, defmt::Format)]
424    pub struct LocationTrack {
425        pub start: Option<bool>,
426        pub stop: Option<bool>,
427        pub heartbeat: Option<bool>,
428        pub seconds: Option<u32>,
429        pub hours: Option<i32>,
430        pub file: Option<heapless::String<20>>,
431    }
432
433    #[derive(Deserialize, Debug, defmt::Format)]
434    pub struct Aux {
435        pub mode: Option<heapless::String<20>>,
436        pub power: Option<bool>,
437        pub seconds: Option<u32>,
438        pub time: Option<u32>,
439        pub state: Option<[GpioState; 4]>,
440    }
441
442    #[derive(Deserialize, Debug, defmt::Format)]
443    pub struct GpioState {
444        pub low: Option<bool>,
445        pub high: Option<bool>,
446        pub input: Option<bool>,
447        pub count: Option<heapless::Vec<u32, 128>>,
448    }
449
450    #[derive(Deserialize, Debug, defmt::Format)]
451    pub struct LocationMode {
452        pub mode: heapless::String<60>,
453        pub seconds: Option<u32>,
454        pub vseconds: Option<heapless::String<40>>,
455        pub max: Option<u32>,
456        pub lat: Option<f64>,
457        pub lon: Option<f64>,
458        pub minutes: Option<u32>,
459    }
460
461    #[derive(Deserialize, Debug, defmt::Format)]
462    pub struct Location {
463        pub status: heapless::String<120>,
464        pub mode: heapless::String<120>,
465        pub lat: Option<f64>,
466        pub lon: Option<f64>,
467        pub time: Option<u32>,
468        pub max: Option<u32>,
469    }
470
471    #[derive(Deserialize, Debug, defmt::Format)]
472    pub struct Time {
473        pub time: Option<u32>,
474        pub area: Option<heapless::String<120>>,
475        pub zone: Option<heapless::String<120>>,
476        pub minutes: Option<i32>,
477        pub lat: Option<f64>,
478        pub lon: Option<f64>,
479        pub country: Option<heapless::String<120>>,
480    }
481
482    #[derive(Deserialize, Debug, defmt::Format)]
483    pub struct Status {
484        pub status: heapless::String<40>,
485        #[serde(default)]
486        pub usb: bool,
487        pub storage: usize,
488        pub time: Option<u64>,
489        #[serde(default)]
490        pub connected: bool,
491    }
492
493    #[derive(Deserialize, Debug, defmt::Format)]
494    pub struct WirelessNet {
495        iccid: Option<heapless::String<24>>,
496        imsi: Option<heapless::String<24>>,
497        imei: Option<heapless::String<24>>,
498        modem: Option<heapless::String<35>>,
499        band: Option<heapless::String<24>>,
500        rat: Option<heapless::String<24>>,
501        ratr: Option<heapless::String<24>>,
502        internal: Option<bool>,
503        rssir: Option<i32>,
504        rssi: Option<i32>,
505        rsrp: Option<i32>,
506        sinr: Option<i32>,
507        rsrq: Option<i32>,
508        bars: Option<i32>,
509        mcc: Option<i32>,
510        mnc: Option<i32>,
511        lac: Option<i32>,
512        cid: Option<i32>,
513        modem_temp: Option<i32>,
514        updated: Option<u32>,
515    }
516
517    #[derive(Deserialize, Debug, defmt::Format)]
518    pub struct Wireless {
519        pub status: Option<heapless::String<24>>,
520        pub mode: Option<heapless::String<24>>,
521        pub count: Option<u8>,
522        pub net: Option<WirelessNet>,
523    }
524
525    #[derive(Deserialize, Debug, defmt::Format)]
526    pub struct VersionInner {
527        pub org: heapless::String<24>,
528        pub product: heapless::String<24>,
529        pub version: heapless::String<24>,
530        pub ver_major: u8,
531        pub ver_minor: u8,
532        pub ver_patch: u8,
533        pub ver_build: u32,
534        pub built: heapless::String<24>,
535        pub target: Option<heapless::String<5>>,
536    }
537
538    #[derive(Deserialize, Debug, defmt::Format)]
539    pub struct Version {
540        pub body: VersionInner,
541        pub version: heapless::String<24>,
542        pub device: heapless::String<24>,
543        pub name: heapless::String<30>,
544        pub board: heapless::String<24>,
545        pub sku: heapless::String<24>,
546        pub api: Option<u16>,
547        pub wifi: Option<bool>,
548        pub cell: Option<bool>,
549        pub gps: Option<bool>,
550        pub ordering_code: Option<heapless::String<50>>,
551    }
552
553    #[derive(Deserialize, Debug, defmt::Format)]
554    pub struct DFU {
555        pub name: req::DFUName,
556    }
557
558    #[derive(Deserialize, Debug, defmt::Format)]
559    pub struct Transport {
560        pub method: heapless::String<120>,
561    }
562}
563
564#[cfg(test)]
565mod tests {
566    use super::*;
567    use crate::NotecardError;
568
569    #[test]
570    fn test_version() {
571        let r = br##"{
572  "body": {
573    "org":       "Blues Wireless",
574    "product":   "Notecard",
575    "version":   "notecard-1.5.0",
576    "ver_major": 1,
577    "ver_minor": 5,
578    "ver_patch": 0,
579    "ver_build": 11236,
580    "built":     "Sep 2 2020 08:45:10"
581  },
582  "version": "notecard-1.5.0.11236",
583  "device":  "dev:000000000000000",
584  "name":    "Blues Wireless Notecard",
585  "board":   "1.11",
586  "sku":     "NOTE-WBNA500",
587  "api":     1
588}"##;
589        let d = &mut serde_json::Deserializer::from_slice(r);
590        serde_path_to_error::deserialize::<_, res::Version>(d).unwrap();
591    }
592
593    #[test]
594    fn test_version_411() {
595        let r = br##"{"version":"notecard-4.1.1.4015681","device":"dev:000000000000000","name":"Blues Wireless Notecard","sku":"NOTE-WBEX-500","board":"1.11","api":4,"body":{"org":"Blues Wireless","product":"Notecard","version":"notecard-4.1.1","ver_major":4,"ver_minor":1,"ver_patch":1,"ver_build":4015681,"built":"Dec  5 2022 12:54:58"}}"##;
596        let d = &mut serde_json::Deserializer::from_slice(r);
597        serde_path_to_error::deserialize::<_, res::Version>(d).unwrap();
598    }
599
600    #[test]
601    fn test_version_813() {
602        let r = br##"{"version":"notecard-8.1.3.17044","device":"dev:000000000000000","name":"Blues Wireless Notecard","sku":"NOTE-WBNAN","ordering_code":"EA0WT1N0AXBB","board":"5.13","cell":true,"gps":true,"body":{"org":"Blues Wireless","product":"Notecard","target":"u5","version":"notecard-u5-8.1.3","ver_major":8,"ver_minor":1,"ver_patch":3,"ver_build":17044,"built":"Dec 20 2024 08:45:13"}}"##;
603        let d = &mut serde_json::Deserializer::from_slice(r);
604        serde_path_to_error::deserialize::<_, res::Version>(d).unwrap();
605    }
606
607    #[test]
608    fn test_version_752() {
609        let r = br##"{"version":"notecard-7.5.2.17004","device":"dev:861059067974133","name":"Blues Wireless Notecard","sku":"NOTE-NBGLN","ordering_code":"EB0WT1N0AXBA","board":"5.13","cell":true,"gps":true,"body":{"org":"Blues Wireless","product":"Notecard","target":"u5","version":"notecard-u5-7.5.2","ver_major":7,"ver_minor":5,"ver_patch":2,"ver_build":17004,"built":"Nov 26 2024 14:01:26"}}"##;
610        let d = &mut serde_json::Deserializer::from_slice(r);
611        serde_path_to_error::deserialize::<_, res::Version>(d).unwrap();
612    }
613
614    #[test]
615    fn test_card_wireless() {
616        let r = br##"{"status":"{modem-on}","count":3,"net":{"iccid":"89011703278520607527","imsi":"310170852060752","imei":"864475044204278","modem":"BG95M3LAR02A03_01.006.01.006","band":"GSM 900","rat":"gsm","rssir":-77,"rssi":-77,"bars":3,"mcc":242,"mnc":1,"lac":11001,"cid":12313,"updated":1643923524}}"##;
617        let d = &mut serde_json::Deserializer::from_slice(r);
618        serde_path_to_error::deserialize::<_, res::Wireless>(d).unwrap();
619
620        let r = br##"{"status":"{cell-registration-wait}","net":{"iccid":"89011703278520606586","imsi":"310170852060658","imei":"864475044197092","modem":"BG95M3LAR02A03_01.006.01.006"}}"##;
621        let d = &mut serde_json::Deserializer::from_slice(r);
622        serde_path_to_error::deserialize::<_, res::Wireless>(d).unwrap();
623
624        let r = br##"{"status":"{modem-off}","net":{}}"##;
625        let d = &mut serde_json::Deserializer::from_slice(r);
626        serde_path_to_error::deserialize::<_, res::Wireless>(d).unwrap();
627
628        let r = br##"{"status":"{network-up}","mode":"auto","count":3,"net":{"iccid":"89011703278520578660","imsi":"310170852057866","imei":"867730051260788","modem":"BG95M3LAR02A03_01.006.01.006","band":"GSM 900","rat":"gsm","rssir":-77,"rssi":-78,"bars":3,"mcc":242,"mnc":1,"lac":11,"cid":12286,"updated":1646227929}}"##;
629        let d = &mut serde_json::Deserializer::from_slice(r);
630        serde_path_to_error::deserialize::<_, res::Wireless>(d).unwrap();
631
632        // NTN
633        let r = br##"{"mode":"auto","count":2,"net":{"iccid":"89011704278930030582","imsi":"310170893003058","imei":"860264054655247","modem":"EG91EXGAR08A05M1G_01.001.01.001","band":"LTE BAND 20","rat":"lte","ratr":"\"LTE\"","internal":true,"rssir":-59,"rssi":-60,"rsrp":-92,"sinr":15,"rsrq":-9,"bars":2,"mcc":242,"mnc":2,"lac":2501,"cid":35398693,"modem_temp":34,"updated":1746004605}}"##;
634        let d = &mut serde_json::Deserializer::from_slice(r);
635        serde_path_to_error::deserialize::<_, res::Wireless>(d).unwrap();
636    }
637
638    #[test]
639    fn test_card_time_ok() {
640        let r = br##"
641        {
642          "time": 1599769214,
643          "area": "Beverly, MA",
644          "zone": "CDT,America/New York",
645          "minutes": -300,
646          "lat": 42.5776,
647          "lon": -70.87134,
648          "country": "US"
649        }
650        "##;
651
652        let d = &mut serde_json::Deserializer::from_slice(r);
653        serde_path_to_error::deserialize::<_, res::Time>(d).unwrap();
654    }
655
656    #[test]
657    fn test_card_time_sa() {
658        let r = br##"
659        {
660          "time": 1599769214,
661          "area": "Kommetjie Western Cape",
662          "zone": "Africa/Johannesburg",
663          "minutes": -300,
664          "lat": 42.5776,
665          "lon": -70.87134,
666          "country": "ZA"
667        }
668        "##;
669
670        let d = &mut serde_json::Deserializer::from_slice(r);
671        serde_path_to_error::deserialize::<_, res::Time>(d).unwrap();
672    }
673
674    #[test]
675    fn test_card_time_err() {
676        let r = br##"{"err":"time is not yet set","zone":"UTC,Unknown"}"##;
677        let d = &mut serde_json::Deserializer::from_slice(r);
678        serde_path_to_error::deserialize::<_, NotecardError>(d).unwrap();
679    }
680
681    #[test]
682    pub fn test_status_ok() {
683        let d = &mut serde_json::Deserializer::from_str(
684            r#"
685          {
686            "status":    "{normal}",
687            "usb":       true,
688            "storage":   8,
689            "time":      1599684765,
690            "connected": true
691          }"#,
692        );
693        serde_path_to_error::deserialize::<_, res::Status>(d).unwrap();
694    }
695
696    #[test]
697    pub fn test_status_mising() {
698        let d = &mut serde_json::Deserializer::from_str(
699            r#"
700          {
701            "status":    "{normal}",
702            "usb":       true,
703            "storage":   8
704          }"#,
705        );
706
707        serde_path_to_error::deserialize::<_, res::Status>(d).unwrap();
708    }
709
710    #[test]
711    fn test_partial_location_mode() {
712        let d = &mut serde_json::Deserializer::from_str(r#"{"seconds":60,"mode":"periodic"}"#);
713
714        serde_path_to_error::deserialize::<_, res::LocationMode>(d).unwrap();
715    }
716
717    #[test]
718    fn test_parse_exceed_string_size() {
719        let d = &mut serde_json::Deserializer::from_str(r#"{"seconds":60,"mode":"periodicperiodicperiodicperiodicperiodicperiodicperiodic"}"#);
720        serde_path_to_error::deserialize::<_, res::LocationMode>(d)
721        .ok();
722    }
723
724    #[test]
725    fn test_location_searching() {
726        let d = &mut serde_json::Deserializer::from_str(
727            r#"{"status":"GPS search (111 sec, 32/33 dB SNR, 0/1 sats) {gps-active} {gps-signal} {gps-sats}","mode":"continuous"}"#);
728        serde_path_to_error::deserialize::<_, res::Location>(d).unwrap();
729    }
730
731    #[test]
732    fn test_location_mode_err() {
733        let r = br##"{"err":"seconds: field seconds: unmarshal: expected a int32 {io}"}"##;
734        let d = &mut serde_json::Deserializer::from_slice(r);
735        serde_path_to_error::deserialize::<_, NotecardError>(d).unwrap();
736    }
737
738    #[test]
739    fn test_dfu_name() {
740        let (res, _) = serde_json_core::from_str::<req::DFUName>(r#""esp32""#).unwrap();
741        assert_eq!(res, req::DFUName::Esp32);
742        let (res, _) = serde_json_core::from_str::<req::DFUName>(r#""stm32""#).unwrap();
743        assert_eq!(res, req::DFUName::Stm32);
744        let (res, _) = serde_json_core::from_str::<req::DFUName>(r#""stm32-bi""#).unwrap();
745        assert_eq!(res, req::DFUName::Stm32Bi);
746        let (res, _) = serde_json_core::from_str::<req::DFUName>(r#""mcuboot""#).unwrap();
747        assert_eq!(res, req::DFUName::McuBoot);
748        let (res, _) = serde_json_core::from_str::<req::DFUName>(r#""-""#).unwrap();
749        assert_eq!(res, req::DFUName::Reset);
750    }
751
752    #[test]
753    fn test_dfu_req() {
754        // Test basic request
755        let req = req::DFU::new(None, None, None);
756        let res: heapless::String<1024> = serde_json_core::to_string(&req).unwrap();
757        assert_eq!(res, r#"{"req":"card.dfu"}"#);
758
759        // Test name & on request
760        let req = req::DFU::new(Some(req::DFUName::Esp32), Some(true), None);
761        let res: heapless::String<256> = serde_json_core::to_string(&req).unwrap();
762        assert_eq!(res, r#"{"req":"card.dfu","name":"esp32","on":true}"#);
763
764        // Test off request
765        let req = req::DFU::new(None, Some(false), None);
766        let res: heapless::String<256> = serde_json_core::to_string(&req).unwrap();
767        assert_eq!(res, r#"{"req":"card.dfu","off":true}"#);
768
769        // Test stop request
770        let req = req::DFU::new(None, None, Some(true));
771        let res: heapless::String<256> = serde_json_core::to_string(&req).unwrap();
772        assert_eq!(res, r#"{"req":"card.dfu","stop":true}"#);
773
774        // Test start request
775        let req = req::DFU::new(None, None, Some(false));
776        let res: heapless::String<256> = serde_json_core::to_string(&req).unwrap();
777        assert_eq!(res, r#"{"req":"card.dfu","start":true}"#);
778    }
779
780    #[test]
781    fn test_dfu_res() {
782        let d = &mut serde_json::Deserializer::from_str(r#"{"name": "stm32"}"#);
783        serde_path_to_error::deserialize::<_, res::DFU>(d).unwrap();
784    }
785
786    #[test]
787    fn test_parse_aux_gpio_state() {
788        let d = &mut serde_json::Deserializer::from_str(r#"{
789  "mode": "gpio",
790  "state": [
791    {},
792    {
793      "low": true
794    },
795    {
796      "high": true
797    },
798    {
799      "count": [
800        3
801      ]
802    }
803  ],
804  "time": 1592587637,
805  "seconds": 2
806}"#);
807        serde_path_to_error::deserialize::<_, res::Aux>(d).unwrap();
808    }
809}