#![no_std]
pub mod state;
pub mod types;
mod obis;
mod reader;
pub use crate::obis::*;
pub use crate::reader::*;
#[derive(Debug)]
pub enum Error {
InvalidFormat,
InvalidChecksum,
UnknownObis,
}
pub type Result<T> = core::result::Result<T, Error>;
pub struct Readout {
pub buffer: [u8; 2048], }
impl Readout {
pub fn to_telegram(&'_ self) -> Result<Telegram<'_>> {
let buffer = core::str::from_utf8(&self.buffer).map_err(|_| Error::InvalidFormat)?;
if buffer.len() < 16 {
return Err(Error::InvalidFormat);
}
let data_end = buffer.find('!').ok_or(Error::InvalidFormat)?;
let (buffer, postfix) = buffer.split_at(data_end + 1);
let given_checksum = u16::from_str_radix(postfix.get(..4).ok_or(Error::InvalidFormat)?, 16)
.map_err(|_| Error::InvalidFormat)?;
let real_checksum = crc16::State::<crc16::ARC>::calculate(buffer.as_bytes());
if given_checksum != real_checksum {
return Err(Error::InvalidChecksum);
}
let data_start = buffer.find("\r\n\r\n").ok_or(Error::InvalidFormat)?;
let (header, data) = buffer.split_at(data_start);
let prefix = header.get(1..4).ok_or(Error::InvalidFormat)?;
let identification = header.get(5..).ok_or(Error::InvalidFormat)?;
Ok(Telegram {
checksum: given_checksum,
prefix,
identification,
object_buffer: data.get(4..data.len() - 3).ok_or(Error::InvalidFormat)?,
})
}
}
pub struct Telegram<'a> {
pub checksum: u16,
pub prefix: &'a str,
pub identification: &'a str,
object_buffer: &'a str,
}
impl<'a> Telegram<'a> {
pub fn objects(&self) -> impl core::iter::Iterator<Item = Result<OBIS<'a>>> {
self.object_buffer.lines().map(OBIS::parse)
}
}
#[cfg(test)]
#[macro_use]
extern crate std;
#[cfg(test)]
mod tests {
#[test]
fn example_isk() {
let mut buffer = [0u8; 2048];
let file = std::fs::read("test/isk.txt").unwrap();
let (left, _right) = buffer.split_at_mut(file.len());
left.copy_from_slice(file.as_slice());
let readout = crate::Readout { buffer };
let telegram = readout.to_telegram().unwrap();
assert_eq!(telegram.prefix, "ISK");
assert_eq!(telegram.identification, "\\2M550E-1012");
telegram.objects().for_each(|o| {
println!("{:?}", o); let o = o.unwrap();
use crate::OBIS::*;
use core::convert::From;
match o {
Version(v) => {
let b: std::vec::Vec<u8> = v.as_octets().map(|b| b.unwrap()).collect();
assert_eq!(b, [80]);
}
DateTime(tst) => {
assert_eq!(
tst,
crate::types::TST {
year: 19,
month: 3,
day: 20,
hour: 18,
minute: 14,
second: 3,
dst: false
}
);
}
EquipmentIdentifier(ei) => {
let b: std::vec::Vec<u8> = ei.as_octets().map(|b| b.unwrap()).collect();
assert_eq!(std::str::from_utf8(&b).unwrap(), "E0043007052870318");
}
MeterReadingTo(crate::Tariff::Tariff1, mr) => {
assert_eq!(f64::from(&mr), 576.239);
}
MeterReadingTo(crate::Tariff::Tariff2, mr) => {
assert_eq!(f64::from(&mr), 465.162);
}
TariffIndicator(ti) => {
let b: std::vec::Vec<u8> = ti.as_octets().map(|b| b.unwrap()).collect();
assert_eq!(b, [0, 2]);
}
PowerFailures(crate::types::UFixedInteger(pf)) => {
assert_eq!(pf, 9);
}
_ => (), }
});
}
#[test]
fn example_kaifa() {
let mut buffer = [0u8; 2048];
let file = std::fs::read("test/kaifa.txt").unwrap();
let (left, _right) = buffer.split_at_mut(file.len());
left.copy_from_slice(file.as_slice());
let readout = crate::Readout { buffer };
let telegram = readout.to_telegram().unwrap();
assert_eq!(telegram.prefix, "KFM");
assert_eq!(telegram.identification, "KAIFA-METER");
telegram.objects().for_each(|o| {
println!("{:?}", o); let o = o.unwrap();
use crate::OBIS::*;
use core::convert::From;
match o {
Version(v) => {
let b: std::vec::Vec<u8> = v.as_octets().map(|b| b.unwrap()).collect();
assert_eq!(b, [66]);
}
DateTime(tst) => {
assert_eq!(
tst,
crate::types::TST {
year: 22,
month: 9,
day: 1,
hour: 15,
minute: 22,
second: 1,
dst: true
}
);
}
EquipmentIdentifier(ei) => {
let b: std::vec::Vec<u8> = ei.as_octets().map(|b| b.unwrap()).collect();
assert_eq!(std::str::from_utf8(&b).unwrap(), "E0026000024153615");
}
MeterReadingTo(crate::Tariff::Tariff1, mr) => {
assert_eq!(f64::from(&mr), 6285.065);
}
MeterReadingTo(crate::Tariff::Tariff2, mr) => {
assert_eq!(f64::from(&mr), 6758.327);
}
TariffIndicator(ti) => {
let b: std::vec::Vec<u8> = ti.as_octets().map(|b| b.unwrap()).collect();
assert_eq!(b, [0, 2]);
}
PowerFailures(crate::types::UFixedInteger(pf)) => {
assert_eq!(pf, 3);
}
_ => (), }
});
}
}