#[allow(unused_imports)]
use defmt::{debug, error, info, trace, warn};
use embedded_hal::blocking::delay::DelayMs;
use embedded_hal::blocking::i2c::{Read, SevenBitAddress, Write};
use serde::{Deserialize, Serialize};
use super::{str_string, FutureResponse, NoteError, Notecard};
pub struct Card<'a, IOM: Write<SevenBitAddress> + Read<SevenBitAddress>, const BS: usize> {
note: &'a mut Notecard<IOM, BS>,
}
pub enum Transport {
Reset,
WifiCell,
Wifi,
Cell,
NTN,
WifiNTN,
CellNTN,
WifiCellNTN,
}
impl Transport {
pub fn str(&self) -> &'static str {
use Transport::*;
match self {
Reset => "-",
WifiCell => "wifi-cell",
Wifi => "wifi",
Cell => "cell",
NTN => "ntn",
WifiNTN => "wifi-ntn",
CellNTN => "cell-ntn",
WifiCellNTN => "wifi-cell-ntn",
}
}
}
pub enum GpioMode {
Off,
Low,
High,
Input,
}
impl GpioMode {
pub fn str(&self) -> &'static str {
use GpioMode::*;
match self {
Off => "off",
Low => "low",
High => "high",
Input => "input",
}
}
}
impl<'a, IOM: Write<SevenBitAddress> + Read<SevenBitAddress>, const BS: usize> Card<'a, IOM, BS> {
pub fn from(note: &mut Notecard<IOM, BS>) -> Card<'_, IOM, BS> {
Card { note }
}
pub fn time(
self,
delay: &mut impl DelayMs<u16>,
) -> Result<FutureResponse<'a, res::Time, IOM, BS>, NoteError> {
self.note.request_raw(delay, b"{\"req\":\"card.time\"}\n")?;
Ok(FutureResponse::from(self.note))
}
pub fn status(
self,
delay: &mut impl DelayMs<u16>,
) -> Result<FutureResponse<'a, res::Status, IOM, BS>, NoteError> {
self.note
.request_raw(delay, b"{\"req\":\"card.status\"}\n")?;
Ok(FutureResponse::from(self.note))
}
pub fn restart(
self,
delay: &mut impl DelayMs<u16>,
) -> Result<FutureResponse<'a, res::Empty, IOM, BS>, NoteError> {
self.note
.request_raw(delay, b"{\"req\":\"card.restart\"}\n")?;
Ok(FutureResponse::from(self.note))
}
pub fn location(
self,
delay: &mut impl DelayMs<u16>,
) -> Result<FutureResponse<'a, res::Location, IOM, BS>, NoteError> {
self.note
.request_raw(delay, b"{\"req\":\"card.location\"}\n")?;
Ok(FutureResponse::from(self.note))
}
pub fn location_mode(
self,
delay: &mut impl DelayMs<u16>,
mode: Option<&str>,
seconds: Option<u32>,
vseconds: Option<&str>,
delete: Option<bool>,
max: Option<u32>,
lat: Option<f32>,
lon: Option<f32>,
minutes: Option<u32>,
) -> Result<FutureResponse<'a, res::LocationMode, IOM, BS>, NoteError> {
self.note.request(
delay,
req::LocationMode {
req: "card.location.mode",
mode: str_string(mode)?,
seconds,
vseconds: str_string(vseconds)?,
delete,
max,
lat,
lon,
minutes,
},
)?;
Ok(FutureResponse::from(self.note))
}
pub fn location_track(
self,
delay: &mut impl DelayMs<u16>,
start: bool,
heartbeat: bool,
sync: bool,
hours: Option<i32>,
file: Option<&str>,
) -> Result<FutureResponse<'a, res::LocationTrack, IOM, BS>, NoteError> {
self.note.request(
delay,
req::LocationTrack {
req: "card.location.track",
start: start.then_some(true),
stop: (!start).then_some(true),
heartbeat: heartbeat.then_some(true),
sync: sync.then_some(true),
hours,
file: str_string(file)?,
},
)?;
Ok(FutureResponse::from(self.note))
}
pub fn wireless(
self,
delay: &mut impl DelayMs<u16>,
mode: Option<&str>,
apn: Option<&str>,
method: Option<&str>,
hours: Option<u32>,
) -> Result<FutureResponse<'a, res::Wireless, IOM, BS>, NoteError> {
self.note.request(
delay,
req::Wireless {
req: "card.wireless",
mode: str_string(mode)?,
method: str_string(method)?,
apn: str_string(apn)?,
hours,
},
)?;
Ok(FutureResponse::from(self.note))
}
pub fn version(
self,
delay: &mut impl DelayMs<u16>,
) -> Result<FutureResponse<'a, res::Version, IOM, BS>, NoteError> {
self.note
.request_raw(delay, b"{\"req\":\"card.version\"}\n")?;
Ok(FutureResponse::from(self.note))
}
pub fn dfu(
self,
delay: &mut impl DelayMs<u16>,
name: Option<req::DFUName>,
on: Option<bool>,
stop: Option<bool>,
) -> Result<FutureResponse<'a, res::DFU, IOM, BS>, NoteError> {
self.note.request(delay, req::DFU::new(name, on, stop))?;
Ok(FutureResponse::from(self.note))
}
pub fn transport(
self,
delay: &mut impl DelayMs<u16>,
method: Transport,
allow: Option<bool>,
umin: Option<bool>,
seconds: Option<u32>,
) -> Result<FutureResponse<'a, res::Transport, IOM, BS>, NoteError> {
self.note.request(
delay,
req::Transport {
req: "card.transport",
method: method.str(),
allow,
umin,
seconds,
},
)?;
Ok(FutureResponse::from(self.note))
}
pub fn aux_off(
self,
delay: &mut impl DelayMs<u16>,
) -> Result<FutureResponse<'a, res::Aux, IOM, BS>, NoteError> {
self.note.request_raw(delay, b"{\"req\":\"card.aux\", \"mode\":\"off\"}\n")?;
Ok(FutureResponse::from(self.note))
}
pub fn aux_gpio(
self,
delay: &mut impl DelayMs<u16>,
aux1: GpioMode,
aux2: GpioMode,
aux3: GpioMode,
aux4: GpioMode,
) -> Result<FutureResponse<'a, res::Aux, IOM, BS>, NoteError> {
self.note.request(
delay,
req::Aux {
req: "card.aux",
mode: "gpio",
usage: [aux1.str(), aux2.str(), aux3.str(), aux4.str()],
},
)?;
Ok(FutureResponse::from(self.note))
}
}
pub mod req {
use super::*;
#[derive(Deserialize, Serialize, Debug, defmt::Format, Default)]
pub struct Aux {
pub req: &'static str,
pub mode: &'static str,
pub usage: [&'static str; 4],
}
#[derive(Deserialize, Serialize, Debug, defmt::Format, Default)]
pub struct Transport {
pub req: &'static str,
pub method: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub allow: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub umin: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seconds: Option<u32>,
}
#[derive(Deserialize, Serialize, Debug, defmt::Format, Default)]
pub struct Wireless {
pub req: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<heapless::String<20>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub apn: Option<heapless::String<120>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<heapless::String<120>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hours: Option<u32>,
}
#[derive(Deserialize, Serialize, Debug, defmt::Format, Default)]
pub struct LocationTrack {
pub req: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub heartbeat: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hours: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file: Option<heapless::String<20>>,
}
#[derive(Deserialize, Serialize, Debug, defmt::Format, Default)]
pub struct LocationMode {
pub req: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<heapless::String<20>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seconds: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vseconds: Option<heapless::String<20>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delete: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lat: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lon: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minutes: Option<u32>,
}
#[derive(Deserialize, Serialize, Debug, defmt::Format, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum DFUName {
Esp32,
Stm32,
#[serde(rename = "stm32-bi")]
Stm32Bi,
McuBoot,
#[serde(rename = "-")]
Reset,
}
#[derive(Deserialize, Serialize, Debug, defmt::Format)]
pub struct DFU {
pub req: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<req::DFUName>,
#[serde(skip_serializing_if = "Option::is_none")]
pub on: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub off: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<bool>,
}
impl DFU {
pub fn new(name: Option<req::DFUName>, on: Option<bool>, stop: Option<bool>) -> Self {
Self {
req: "card.dfu",
name,
on: on.and_then(|v| if v { Some(true) } else { None }),
off: on.and_then(|v| if v { None } else { Some(true) }),
stop: stop.and_then(|v| if v { Some(true) } else { None }),
start: stop.and_then(|v| if v { None } else { Some(true) }),
}
}
}
}
pub mod res {
use super::*;
#[derive(Deserialize, Debug, defmt::Format)]
pub struct Empty {}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct LocationTrack {
pub start: Option<bool>,
pub stop: Option<bool>,
pub heartbeat: Option<bool>,
pub seconds: Option<u32>,
pub hours: Option<i32>,
pub file: Option<heapless::String<20>>,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct Aux {
pub mode: Option<heapless::String<20>>,
pub power: Option<bool>,
pub seconds: Option<u32>,
pub time: Option<u32>,
pub state: Option<[GpioState; 4]>,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct GpioState {
pub low: Option<bool>,
pub high: Option<bool>,
pub input: Option<bool>,
pub count: Option<heapless::Vec<u32, 128>>,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct LocationMode {
pub mode: heapless::String<60>,
pub seconds: Option<u32>,
pub vseconds: Option<heapless::String<40>>,
pub max: Option<u32>,
pub lat: Option<f64>,
pub lon: Option<f64>,
pub minutes: Option<u32>,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct Location {
pub status: heapless::String<120>,
pub mode: heapless::String<120>,
pub lat: Option<f64>,
pub lon: Option<f64>,
pub time: Option<u32>,
pub max: Option<u32>,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct Time {
pub time: Option<u32>,
pub area: Option<heapless::String<120>>,
pub zone: Option<heapless::String<120>>,
pub minutes: Option<i32>,
pub lat: Option<f64>,
pub lon: Option<f64>,
pub country: Option<heapless::String<120>>,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct Status {
pub status: heapless::String<40>,
#[serde(default)]
pub usb: bool,
pub storage: usize,
pub time: Option<u64>,
#[serde(default)]
pub connected: bool,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct WirelessNet {
iccid: Option<heapless::String<24>>,
imsi: Option<heapless::String<24>>,
imei: Option<heapless::String<24>>,
modem: Option<heapless::String<35>>,
band: Option<heapless::String<24>>,
rat: Option<heapless::String<24>>,
ratr: Option<heapless::String<24>>,
internal: Option<bool>,
rssir: Option<i32>,
rssi: Option<i32>,
rsrp: Option<i32>,
sinr: Option<i32>,
rsrq: Option<i32>,
bars: Option<i32>,
mcc: Option<i32>,
mnc: Option<i32>,
lac: Option<i32>,
cid: Option<i32>,
modem_temp: Option<i32>,
updated: Option<u32>,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct Wireless {
pub status: Option<heapless::String<24>>,
pub mode: Option<heapless::String<24>>,
pub count: Option<u8>,
pub net: Option<WirelessNet>,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct VersionInner {
pub org: heapless::String<24>,
pub product: heapless::String<24>,
pub version: heapless::String<24>,
pub ver_major: u8,
pub ver_minor: u8,
pub ver_patch: u8,
pub ver_build: u32,
pub built: heapless::String<24>,
pub target: Option<heapless::String<5>>,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct Version {
pub body: VersionInner,
pub version: heapless::String<24>,
pub device: heapless::String<24>,
pub name: heapless::String<30>,
pub board: heapless::String<24>,
pub sku: heapless::String<24>,
pub api: Option<u16>,
pub wifi: Option<bool>,
pub cell: Option<bool>,
pub gps: Option<bool>,
pub ordering_code: Option<heapless::String<50>>,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct DFU {
pub name: req::DFUName,
}
#[derive(Deserialize, Debug, defmt::Format)]
pub struct Transport {
pub method: heapless::String<120>,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::NotecardError;
#[test]
fn test_version() {
let r = br##"{
"body": {
"org": "Blues Wireless",
"product": "Notecard",
"version": "notecard-1.5.0",
"ver_major": 1,
"ver_minor": 5,
"ver_patch": 0,
"ver_build": 11236,
"built": "Sep 2 2020 08:45:10"
},
"version": "notecard-1.5.0.11236",
"device": "dev:000000000000000",
"name": "Blues Wireless Notecard",
"board": "1.11",
"sku": "NOTE-WBNA500",
"api": 1
}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Version>(d).unwrap();
}
#[test]
fn test_version_411() {
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"}}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Version>(d).unwrap();
}
#[test]
fn test_version_813() {
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"}}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Version>(d).unwrap();
}
#[test]
fn test_version_752() {
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"}}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Version>(d).unwrap();
}
#[test]
fn test_card_wireless() {
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}}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Wireless>(d).unwrap();
let r = br##"{"status":"{cell-registration-wait}","net":{"iccid":"89011703278520606586","imsi":"310170852060658","imei":"864475044197092","modem":"BG95M3LAR02A03_01.006.01.006"}}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Wireless>(d).unwrap();
let r = br##"{"status":"{modem-off}","net":{}}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Wireless>(d).unwrap();
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}}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Wireless>(d).unwrap();
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}}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Wireless>(d).unwrap();
}
#[test]
fn test_card_time_ok() {
let r = br##"
{
"time": 1599769214,
"area": "Beverly, MA",
"zone": "CDT,America/New York",
"minutes": -300,
"lat": 42.5776,
"lon": -70.87134,
"country": "US"
}
"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Time>(d).unwrap();
}
#[test]
fn test_card_time_sa() {
let r = br##"
{
"time": 1599769214,
"area": "Kommetjie Western Cape",
"zone": "Africa/Johannesburg",
"minutes": -300,
"lat": 42.5776,
"lon": -70.87134,
"country": "ZA"
}
"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, res::Time>(d).unwrap();
}
#[test]
fn test_card_time_err() {
let r = br##"{"err":"time is not yet set","zone":"UTC,Unknown"}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, NotecardError>(d).unwrap();
}
#[test]
pub fn test_status_ok() {
let d = &mut serde_json::Deserializer::from_str(
r#"
{
"status": "{normal}",
"usb": true,
"storage": 8,
"time": 1599684765,
"connected": true
}"#,
);
serde_path_to_error::deserialize::<_, res::Status>(d).unwrap();
}
#[test]
pub fn test_status_mising() {
let d = &mut serde_json::Deserializer::from_str(
r#"
{
"status": "{normal}",
"usb": true,
"storage": 8
}"#,
);
serde_path_to_error::deserialize::<_, res::Status>(d).unwrap();
}
#[test]
fn test_partial_location_mode() {
let d = &mut serde_json::Deserializer::from_str(r#"{"seconds":60,"mode":"periodic"}"#);
serde_path_to_error::deserialize::<_, res::LocationMode>(d).unwrap();
}
#[test]
fn test_parse_exceed_string_size() {
let d = &mut serde_json::Deserializer::from_str(r#"{"seconds":60,"mode":"periodicperiodicperiodicperiodicperiodicperiodicperiodic"}"#);
serde_path_to_error::deserialize::<_, res::LocationMode>(d)
.ok();
}
#[test]
fn test_location_searching() {
let d = &mut serde_json::Deserializer::from_str(
r#"{"status":"GPS search (111 sec, 32/33 dB SNR, 0/1 sats) {gps-active} {gps-signal} {gps-sats}","mode":"continuous"}"#);
serde_path_to_error::deserialize::<_, res::Location>(d).unwrap();
}
#[test]
fn test_location_mode_err() {
let r = br##"{"err":"seconds: field seconds: unmarshal: expected a int32 {io}"}"##;
let d = &mut serde_json::Deserializer::from_slice(r);
serde_path_to_error::deserialize::<_, NotecardError>(d).unwrap();
}
#[test]
fn test_dfu_name() {
let (res, _) = serde_json_core::from_str::<req::DFUName>(r#""esp32""#).unwrap();
assert_eq!(res, req::DFUName::Esp32);
let (res, _) = serde_json_core::from_str::<req::DFUName>(r#""stm32""#).unwrap();
assert_eq!(res, req::DFUName::Stm32);
let (res, _) = serde_json_core::from_str::<req::DFUName>(r#""stm32-bi""#).unwrap();
assert_eq!(res, req::DFUName::Stm32Bi);
let (res, _) = serde_json_core::from_str::<req::DFUName>(r#""mcuboot""#).unwrap();
assert_eq!(res, req::DFUName::McuBoot);
let (res, _) = serde_json_core::from_str::<req::DFUName>(r#""-""#).unwrap();
assert_eq!(res, req::DFUName::Reset);
}
#[test]
fn test_dfu_req() {
let req = req::DFU::new(None, None, None);
let res: heapless::String<1024> = serde_json_core::to_string(&req).unwrap();
assert_eq!(res, r#"{"req":"card.dfu"}"#);
let req = req::DFU::new(Some(req::DFUName::Esp32), Some(true), None);
let res: heapless::String<256> = serde_json_core::to_string(&req).unwrap();
assert_eq!(res, r#"{"req":"card.dfu","name":"esp32","on":true}"#);
let req = req::DFU::new(None, Some(false), None);
let res: heapless::String<256> = serde_json_core::to_string(&req).unwrap();
assert_eq!(res, r#"{"req":"card.dfu","off":true}"#);
let req = req::DFU::new(None, None, Some(true));
let res: heapless::String<256> = serde_json_core::to_string(&req).unwrap();
assert_eq!(res, r#"{"req":"card.dfu","stop":true}"#);
let req = req::DFU::new(None, None, Some(false));
let res: heapless::String<256> = serde_json_core::to_string(&req).unwrap();
assert_eq!(res, r#"{"req":"card.dfu","start":true}"#);
}
#[test]
fn test_dfu_res() {
let d = &mut serde_json::Deserializer::from_str(r#"{"name": "stm32"}"#);
serde_path_to_error::deserialize::<_, res::DFU>(d).unwrap();
}
#[test]
fn test_parse_aux_gpio_state() {
let d = &mut serde_json::Deserializer::from_str(r#"{
"mode": "gpio",
"state": [
{},
{
"low": true
},
{
"high": true
},
{
"count": [
3
]
}
],
"time": 1592587637,
"seconds": 2
}"#);
serde_path_to_error::deserialize::<_, res::Aux>(d).unwrap();
}
}