modio-mqttbridge 0.6.1

Modio on-device MQTT bridge
// Author: D.S. Ljungmark <spider@skuggor.se>, Modio AB
// SPDX-License-Identifier: AGPL-3.0-or-later
use serde::Serialize;
use std::fmt;

pub type SenML = LimitedSenML;

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::Result;

    #[test]
    fn test_json_encode() -> Result<()> {
        let datum = LimitedSenML::make_one(
            "urn:dev:mac:ffffaaaa0000eeee:",
            "modio.test.test",
            "abc123",
            1_628_546_838.0,
            None,
        );
        // Serialize it to a JSON string.
        let row = serde_json::to_string(&datum)?;
        println!("Row is :{:?}", row);
        Ok(())
    }

    #[test]
    fn test_from_ipc() -> Result<()> {
        let datum = LimitedSenML::make_one(
            "urn:dev:mac:ffffaaaa0000eeee:",
            "modio.test.test",
            "abc123",
            1_628_546_838.0,
            None,
        );
        // Serialize it to a JSON string.
        let res = datum.to_string();
        println!("Row is :{:?}", &res);
        assert_eq!(
            res,
            r#"{"bn":"urn:dev:mac:ffffaaaa0000eeee:","n":"modio.test.test","vs":"abc123","t":1628546838.0}"#,
        );
        Ok(())
    }

    #[test]
    fn test_with_unit() -> Result<()> {
        let datum = LimitedSenML::make_one(
            "urn:dev:mac:ffffaaaa0000eeee:",
            "modio.test.test",
            "10.0",
            1_628_546_838.0,
            Some("cel"),
        );
        // Serialize it to a JSON string.
        let res = datum.to_string();
        assert_eq!(
            res,
            r#"{"bn":"urn:dev:mac:ffffaaaa0000eeee:","n":"modio.test.test","u":"cel","vs":"10.0","t":1628546838.0}"#,
        );
        Ok(())
    }
}

#[derive(Debug, Serialize)]
pub struct LimitedSenML {
    #[serde(skip_serializing_if = "Option::is_none")]
    bn: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    n: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    u: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    s: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    vs: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    t: Option<f64>,
}

impl LimitedSenML {
    pub fn make_one(bn: &str, n: &str, vs: &str, t: f64, u: Option<&str>) -> LimitedSenML {
        LimitedSenML {
            bn: Some(bn.to_string()),
            n: Some(n.to_string()),
            vs: Some(vs.to_string()),
            t: Some(t),
            u: u.map(String::from),
            s: None,
        }
    }

    pub fn make_topic(&self, prefix: &str) -> String {
        if let Some(name) = &self.n {
            let name = name.replace('.', "/");
            format!("{}/metric/{}", &prefix, name)
        } else {
            prefix.to_string()
        }
    }
}

impl fmt::Display for LimitedSenML {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = serde_json::to_string(&self).map_err(|_| std::fmt::Error::default())?;
        write!(f, "{}", s)
    }
}

#[cfg(test)]
mod test_prefix {
    use super::LimitedSenML;
    use std::error::Error;

    #[test]
    fn test_no_name() -> Result<(), Box<dyn Error>> {
        let prefix = "modio/modio-localhost";
        let datum = LimitedSenML {
            bn: Some("urn:dev:mac:ffffaaaa0000eeee:".to_string()),
            n: None,
            vs: None,
            t: None,
            u: None,
            s: None,
        };
        let res = datum.make_topic(prefix);
        assert_eq!(res, "modio/modio-localhost");
        Ok(())
    }
    #[test]
    fn test_name_splits() -> Result<(), Box<dyn Error>> {
        let prefix = "modio/modio-localhost";
        let datum = LimitedSenML {
            bn: Some("urn:dev:mac:ffffaaaa0000eeee:".to_string()),
            n: Some("28.000000000000".to_string()),
            vs: Some("21".to_string()),
            t: Some(1_636_722_945.0),
            u: None,
            s: None,
        };
        let res = datum.make_topic(prefix);
        assert_eq!(res, "modio/modio-localhost/metric/28/000000000000");

        Ok(())
    }
}