1#![no_std]
4
5pub mod state;
6pub mod types;
7
8mod obis;
9mod reader;
10
11pub use crate::obis::*;
12pub use crate::reader::*;
13
14#[derive(Debug)]
15pub enum Error {
16 InvalidFormat,
17 InvalidChecksum,
18 UnknownObis,
19}
20
21pub type Result<T> = core::result::Result<T, Error>;
22
23pub struct Readout {
25 pub buffer: [u8; 2048], }
27
28impl Readout {
29 pub fn to_telegram(&'_ self) -> Result<Telegram<'_>> {
34 let buffer = core::str::from_utf8(&self.buffer).map_err(|_| Error::InvalidFormat)?;
35
36 if buffer.len() < 16 {
37 return Err(Error::InvalidFormat);
38 }
39
40 let data_end = buffer.find('!').ok_or(Error::InvalidFormat)?;
41 let (buffer, postfix) = buffer.split_at(data_end + 1);
42
43 let given_checksum = u16::from_str_radix(postfix.get(..4).ok_or(Error::InvalidFormat)?, 16)
44 .map_err(|_| Error::InvalidFormat)?;
45 let real_checksum = crc16::State::<crc16::ARC>::calculate(buffer.as_bytes());
46
47 if given_checksum != real_checksum {
48 return Err(Error::InvalidChecksum);
49 }
50
51 let data_start = buffer.find("\r\n\r\n").ok_or(Error::InvalidFormat)?;
52 let (header, data) = buffer.split_at(data_start);
53
54 let prefix = header.get(1..4).ok_or(Error::InvalidFormat)?;
55 let identification = header.get(5..).ok_or(Error::InvalidFormat)?;
56
57 Ok(Telegram {
58 checksum: given_checksum,
59 prefix,
60 identification,
61 object_buffer: data.get(4..data.len() - 3).ok_or(Error::InvalidFormat)?,
62 })
63 }
64}
65
66pub struct Telegram<'a> {
68 pub checksum: u16,
70
71 pub prefix: &'a str,
73
74 pub identification: &'a str,
76
77 object_buffer: &'a str,
79}
80
81impl<'a> Telegram<'a> {
82 pub fn objects(&self) -> impl core::iter::Iterator<Item = Result<OBIS<'a>>> {
84 self.object_buffer.lines().map(OBIS::parse)
85 }
86}
87
88#[cfg(test)]
89#[macro_use]
90extern crate std;
91
92#[cfg(test)]
93mod tests {
94 #[test]
95 fn example_isk() {
96 let mut buffer = [0u8; 2048];
97 let file = std::fs::read("test/isk.txt").unwrap();
98
99 let (left, _right) = buffer.split_at_mut(file.len());
100 left.copy_from_slice(file.as_slice());
101
102 let readout = crate::Readout { buffer };
103 let telegram = readout.to_telegram().unwrap();
104
105 assert_eq!(telegram.prefix, "ISK");
106 assert_eq!(telegram.identification, "\\2M550E-1012");
107
108 telegram.objects().for_each(|o| {
109 println!("{:?}", o); let o = o.unwrap();
111
112 use crate::OBIS::*;
113 use core::convert::From;
114 match o {
115 Version(v) => {
116 let b: std::vec::Vec<u8> = v.as_octets().map(|b| b.unwrap()).collect();
117 assert_eq!(b, [80]);
118 }
119 DateTime(tst) => {
120 assert_eq!(
121 tst,
122 crate::types::TST {
123 year: 19,
124 month: 3,
125 day: 20,
126 hour: 18,
127 minute: 14,
128 second: 3,
129 dst: false
130 }
131 );
132 }
133 EquipmentIdentifier(ei) => {
134 let b: std::vec::Vec<u8> = ei.as_octets().map(|b| b.unwrap()).collect();
135 assert_eq!(std::str::from_utf8(&b).unwrap(), "E0043007052870318");
136 }
137 MeterReadingTo(crate::Tariff::Tariff1, mr) => {
138 assert_eq!(f64::from(&mr), 576.239);
139 }
140 MeterReadingTo(crate::Tariff::Tariff2, mr) => {
141 assert_eq!(f64::from(&mr), 465.162);
142 }
143 TariffIndicator(ti) => {
144 let b: std::vec::Vec<u8> = ti.as_octets().map(|b| b.unwrap()).collect();
145 assert_eq!(b, [0, 2]);
146 }
147 PowerFailures(crate::types::UFixedInteger(pf)) => {
148 assert_eq!(pf, 9);
149 }
150 _ => (), }
152 });
153 }
154
155 #[test]
156 fn example_kaifa() {
157 let mut buffer = [0u8; 2048];
158 let file = std::fs::read("test/kaifa.txt").unwrap();
159
160 let (left, _right) = buffer.split_at_mut(file.len());
161 left.copy_from_slice(file.as_slice());
162
163 let readout = crate::Readout { buffer };
164 let telegram = readout.to_telegram().unwrap();
165
166 assert_eq!(telegram.prefix, "KFM");
167 assert_eq!(telegram.identification, "KAIFA-METER");
168
169 telegram.objects().for_each(|o| {
170 println!("{:?}", o); let o = o.unwrap();
172
173 use crate::OBIS::*;
174 use core::convert::From;
175 match o {
176 Version(v) => {
177 let b: std::vec::Vec<u8> = v.as_octets().map(|b| b.unwrap()).collect();
178 assert_eq!(b, [66]);
179 }
180 DateTime(tst) => {
181 assert_eq!(
182 tst,
183 crate::types::TST {
184 year: 22,
185 month: 9,
186 day: 1,
187 hour: 15,
188 minute: 22,
189 second: 1,
190 dst: true
191 }
192 );
193 }
194 EquipmentIdentifier(ei) => {
195 let b: std::vec::Vec<u8> = ei.as_octets().map(|b| b.unwrap()).collect();
196 assert_eq!(std::str::from_utf8(&b).unwrap(), "E0026000024153615");
197 }
198 MeterReadingTo(crate::Tariff::Tariff1, mr) => {
199 assert_eq!(f64::from(&mr), 6285.065);
200 }
201 MeterReadingTo(crate::Tariff::Tariff2, mr) => {
202 assert_eq!(f64::from(&mr), 6758.327);
203 }
204 TariffIndicator(ti) => {
205 let b: std::vec::Vec<u8> = ti.as_octets().map(|b| b.unwrap()).collect();
206 assert_eq!(b, [0, 2]);
207 }
208 PowerFailures(crate::types::UFixedInteger(pf)) => {
209 assert_eq!(pf, 3);
210 }
211 _ => (), }
213 });
214 }
215
216 #[test]
217 fn example_mcs() {
218 let mut buffer = [0u8; 2048];
219 let file = std::fs::read("test/mcs.txt").unwrap();
220
221 let (left, _right) = buffer.split_at_mut(file.len());
222 left.copy_from_slice(file.as_slice());
223
224 let readout = crate::Readout { buffer };
225 let telegram = readout.to_telegram().unwrap();
226
227 assert_eq!(telegram.prefix, "MCS");
228 assert_eq!(telegram.identification, "0000000000000");
229
230 telegram.objects().for_each(|o| {
231 let o = o.unwrap();
233
234 use crate::OBIS::*;
235 match o {
236 Version(v) => {
237 let b: std::vec::Vec<u8> = v.as_octets().map(|b| b.unwrap()).collect();
238 assert_eq!(b, [80]);
239 }
240 SlaveDeviceType(_slave, devide_type) => {
241 assert_eq!(devide_type.is_none(), true);
242 }
243 SlaveMeterReading(_slave, _tst, value) => {
244 assert_eq!(value.is_none(), true)
245 }
246 _ => (), }
248 });
249 }
250}