teleinfo 1.0.0

Read & parse teleinfo trame
Documentation
#![warn(warnings)]

pub type Result<T = ()> = std::result::Result<T, Error>;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),
    #[error("UTF-8 error: {0}")]
    Utf8(#[from] std::string::FromUtf8Error),
    #[error("Invalid field: {0}")]
    InvalidField(String),
    #[error("Invalid number: {0}")]
    ParseIntError(#[from] std::num::ParseIntError),
}

#[derive(Debug, Default, Eq, PartialEq, serde::Serialize)]
pub struct Data {
    adco: String,
    optarif: String,
    isousc: u8,
    hchc: u32,
    hchp: u32,
    ptec: String,
    iinst: u32,
    imax: u32,
    papp: u32,
    hhphc: String,
    motdetat: String,
}

impl Data {
    pub fn new() -> Data {
        Self::default()
    }
}

#[derive(Clone, Default)]
pub struct Parser {}

impl Parser {
    pub fn new() -> Parser {
        Self::default()
    }

    pub fn read_frame<P: AsRef<std::path::Path>>(&self, path: P) -> Result<String> {
        use std::io::BufRead;

        let file = std::fs::File::open(&path)?;

        let mut buffer = std::io::BufReader::new(&file);
        let mut line: Vec<u8> = Vec::new();

        buffer.read_until(0x2, &mut line)?;

        buffer.consume(1);
        line = Vec::new();

        buffer.read_until(0x3, &mut line)?;

        String::from_utf8(line)
            .map(|s| s.trim_end_matches('\u{3}').replace('\r', ""))
            .map_err(Error::from)
    }

    pub fn parse(self, frame: String) -> Result<Data> {
        let mut data = Data::new();

        for line in frame.lines() {
            let mut tokens = line.split_whitespace();

            let key = match tokens.next() {
                Some(key) => key.to_lowercase(),
                None => continue,
            };

            let value = match tokens.next() {
                Some(value) => value,
                None => continue,
            };

            match key.as_str() {
                "adco" => data.adco = value.to_string(),
                "optarif" => data.optarif = value.to_string(),
                "isousc" => data.isousc = value.parse()?,
                "hchc" => data.hchc = value.parse()?,
                "hchp" => data.hchp = value.parse()?,
                "ptec" => data.ptec = value.to_string(),
                "iinst" => data.iinst = value.parse()?,
                "imax" => data.imax = value.parse()?,
                "papp" => data.papp = value.parse()?,
                "hhphc" => data.hhphc = value.to_string(),
                "motdetat" => data.motdetat = value.to_string(),
                _ => return Err(Error::InvalidField(key)),
            };
        }

        Ok(data)
    }
}

#[cfg(test)]
mod test {
    #[test]
    fn read_frame() -> super::Result {
        let parser = super::Parser::new();

        let frame = parser.read_frame(String::from("./teleinfo.txt"))?;

        assert_eq!(
            frame,
            "ADCO 130622778433 D
OPTARIF HC.. <
ISOUSC 45 ?
HCHC 041478078 -
HCHP 068619587 E
PTEC HP..\u{20}\u{20}
IINST 002 Y
IMAX 039 K
PAPP 00440 )
HHPHC D /
MOTDETAT 000000 B"
        );

        Ok(())
    }

    #[test]
    fn parse() -> super::Result {
        let parser = super::Parser::new();

        let frame = String::from(
            "ADCO 130622778433 D
OPTARIF HC.. <
ISOUSC 45 ?
HCHC 041478078 -
HCHP 068619587 E
PTEC HP..
IINST 002 Y
IMAX 039 K
PAPP 00440 )
HHPHC D /
MOTDETAT 000000 B",
        );

        let data = parser.parse(frame)?;

        assert_eq!(
            data,
            super::Data {
                adco: String::from("130622778433"),
                optarif: String::from("HC.."),
                isousc: 45,
                hchc: 41478078,
                hchp: 68619587,
                ptec: String::from("HP.."),
                iinst: 2,
                imax: 39,
                papp: 440,
                hhphc: String::from("D"),
                motdetat: String::from("000000"),
            }
        );

        Ok(())
    }
}