1use ::std::io::{self, Read};
20
21const STX: char = '\u{0002}';
22const ETX: char = '\u{0003}';
23const EOT: char = '\u{0004}';
24
25const LF: char = '\u{000A}';
26const CR: char = '\u{000D}';
27
28const SP: char = '\u{0020}';
29const SEPARATOR: char = SP;
32
33#[derive(Debug)]
35pub enum OptionTarifaire {
36 Base,
37 HC,
38 EJP,
39 UNKNOWN(String)
40}
41
42#[derive(Debug)]
44pub enum PeriodeTarifaire {
45 TH,
47 HC,
49 HP,
51 UNKNOWN(String)
53}
54
55#[derive(Debug)]
57pub enum Tag {
58 ADCO(String),
60 OPTARIF(OptionTarifaire),
62 ISOUSC(i32),
64 BASE(i32),
66 HCHC(i32),
69 HCHP(i32),
71 PTEC(PeriodeTarifaire),
73 IINST(i32),
75 ADPS(i32),
77 IMAX(i32),
79 PAPP(i32),
81 HHPHC(char),
83 MOTDETAT(String),
85 UNKNOWN(String, String)
87}
88
89#[derive(Debug)]
91pub struct Frame {
92 pub tags: Vec<Tag>
93}
94
95impl Frame {
96 fn new() -> Frame {
97 Frame {
98 tags: Vec::new()
99 }
100 }
101
102 pub fn next_frame<T: Read>(mut input: &mut T) -> Result<Frame, TeleinfoError> {
103
104 skip_to(&mut input, STX)?;
105
106 return read_frame(&mut input);
107 }
108}
109
110#[derive(Debug)]
112pub enum TeleinfoError {
113 EndOfFile,
115 IoError(io::Error),
117 EndOfTransmission,
120 FrameError(String),
122 ChecksumError
124}
125
126impl From<io::Error> for TeleinfoError {
127
128 fn from(err: io::Error) -> TeleinfoError {
129 TeleinfoError::IoError(err)
130 }
131}
132
133fn read_frame<T: Read>(mut input: &mut T) -> Result<Frame, TeleinfoError> {
134
135 let mut frame = Frame::new();
136
137 loop {
138 let c = read_char(&mut input)?;
139
140 if c == ETX {
141 return Ok(frame);
142 }
143
144 if c != LF {
145 return Err(TeleinfoError::FrameError(format!("Expected LF but found {}", c)));
146 }
147
148 let lbl = read_to_sep(&mut input)?;
149 let val = read_to_sep(&mut input)?;
150 let c = read_char(&mut input)?;
151
152 if c != checksum(&lbl, &val) {
153 return Err(TeleinfoError::ChecksumError);
154 }
155
156 let tag = parse_tag(&lbl, &val)?;
157
158 frame.tags.push(tag);
159
160 expect_char(&mut input, CR)?;
161 }
162}
163
164fn checksum(lbl: &str, val: &str) -> char {
165
166 let mut sum = 0u8;
167
168 for c in lbl.chars() {
169 sum = sum.wrapping_add(c as u8);
170 }
171
172 sum = sum.wrapping_add(SEPARATOR as u8);
173
174 for c in val.chars() {
175 sum = sum.wrapping_add(c as u8);
176 }
177
178 ((sum & 0x3F) + 0x20) as char
179}
180
181fn parse_tag(lbl: &str, val: &str) -> Result<Tag, TeleinfoError> {
182
183 let tag = match lbl {
184
185 "ADCO" => Tag::ADCO(val.to_string()),
186
187 "OPTARIF" => {
188 Tag::OPTARIF(match val {
189 "Base" => OptionTarifaire::Base,
190 "HC.." => OptionTarifaire::HC,
191 "EJP." => OptionTarifaire::EJP,
192 _ => OptionTarifaire::UNKNOWN(val.to_string())
193 })
194 },
195
196 "ISOUSC" => {
197 let p = val.parse::<i32>()
198 .map_err(|_| TeleinfoError::FrameError(format!("Number parse error on {}", val)))?;
199 Tag::ISOUSC(p)
200 },
201
202 "BASE" => {
203 let v = val.parse::<i32>()
204 .map_err(|_| TeleinfoError::FrameError(format!("Number parse error on {}", val)))?;
205 Tag::BASE(v)
206 },
207
208 "HCHC" => {
209 let v = val.parse::<i32>()
210 .map_err(|_| TeleinfoError::FrameError(format!("Number parse error on {}", val)))?;
211 Tag::HCHC(v)
212 },
213
214 "HCHP" => {
215 let v = val.parse::<i32>()
216 .map_err(|_| TeleinfoError::FrameError(format!("Number parse error on {}", val)))?;
217 Tag::HCHP(v)
218 },
219
220 "PTEC" => {
221 Tag::PTEC(match val {
222 "TH.." => PeriodeTarifaire::TH,
223 "HC.." => PeriodeTarifaire::HC,
224 "HP.." => PeriodeTarifaire::HP,
225 _ => PeriodeTarifaire::UNKNOWN(val.to_string())
226 })
227 },
228
229 "IINST" => {
230 let v = val.parse::<i32>()
231 .map_err(|_| TeleinfoError::FrameError(format!("Number parse error on {}", val)))?;
232 Tag::IINST(v)
233 },
234
235 "IMAX" => {
236 let v = val.parse::<i32>()
237 .map_err(|_| TeleinfoError::FrameError(format!("Number parse error on {}", val)))?;
238 Tag::IMAX(v)
239 },
240
241 "ADPS" => {
242 let v = val.parse::<i32>()
243 .map_err(|_| TeleinfoError::FrameError(format!("Number parse error on {}", val)))?;
244 Tag::ADPS(v)
245 },
246
247 "PAPP" => {
248 let v = val.parse::<i32>()
249 .map_err(|_| TeleinfoError::FrameError(format!("Number parse error on {}", val)))?;
250 Tag::PAPP(v)
251 },
252
253 "HHPHC" => {
254 let c = match val.chars().next() {
255 Some(c) => c,
256 None => return Err(TeleinfoError::FrameError("HHPHC should be one char long".to_string()))
257 };
258 Tag::HHPHC(c)
259 },
260
261 "MOTDETAT" => Tag::MOTDETAT(val.to_string()),
262
263 _ => Tag::UNKNOWN(lbl.to_string(), val.to_string())
264 };
265
266 Ok(tag)
267}
268
269fn skip_to<T: Read>(mut input: &mut T, stop_char: char) -> Result<(), TeleinfoError> {
270
271 loop {
272 let c = read_char(&mut input)?;
273
274 if c == stop_char {
275 break;
276 }
277 }
278
279 Ok(())
280}
281
282fn read_to_sep<T: Read>(mut input: &mut T) -> Result<String, TeleinfoError> {
283
284 let mut result: String = String::new();
285
286 loop {
287 let c = read_char(&mut input)?;
288
289 if c == SEPARATOR {
290 break;
291 }
292
293 result.push(c);
294 }
295
296 Ok(result)
297}
298
299fn expect_char<T: Read>(mut input: &mut T, expected: char) -> Result<(), TeleinfoError> {
300
301 let c = read_char(&mut input)?;
302
303 if c != expected {
304 return Err(TeleinfoError::FrameError(format!("Expected {} but found {}", expected, c)));
305 }
306
307 Ok(())
308}
309
310fn read_char<T: Read>(input: &mut T) -> Result<char, TeleinfoError> {
311
312 let mut buf = [0u8; 1];
313 let count = input.read(&mut buf)?;
314
315 if count == 0 {
316 return Err(TeleinfoError::EndOfFile);
317 }
318
319 let c = buf[0] as char;
320
321 if c == EOT {
322 return Err(TeleinfoError::EndOfTransmission);
323 }
324
325 Ok(c)
326}
327
328#[cfg(test)]
329mod tests {
330
331 use super::*;
332 use std::path::PathBuf;
333 use std::fs::File;
334
335 #[test]
336 fn test_read_char() {
337
338 let mut v = &[b'x', 4] as &[u8];
339
340 let c = read_char(&mut v);
341 assert_matches!(c, Ok('x'));
342
343 let c = read_char(&mut v);
344 assert_matches!(c, Err(TeleinfoError::EndOfTransmission));
345
346 let c = read_char(&mut v);
347 assert_matches!(c, Err(TeleinfoError::EndOfFile));
348 }
349
350 #[test]
351 fn test_expect_char() {
352
353 let mut v = &[b'a', b'b'] as &[u8];
354
355 let r = expect_char(&mut v, 'a');
356 assert_matches!(r, Ok(()));
357
358 let r = expect_char(&mut v, 'a');
359 assert_matches!(r, Err(TeleinfoError::FrameError(_)));
360 }
361
362 #[test]
363 fn test_read_to_sep() {
364 let mut v = &[b'a', b'b', b'c', SEPARATOR as u8, b'd'] as &[u8];
365
366 let r = read_to_sep(&mut v);
367 assert_eq!(r.unwrap(), "abc");
368
369 let r = read_to_sep(&mut v);
370 assert_matches!(r, Err(TeleinfoError::EndOfFile));
371 }
372
373 #[test]
374 fn test_skip_to() {
375 let mut v = &[b'a', b'b', b'c'] as &[u8];
376
377 let r = skip_to(&mut v, 'b');
378 assert_matches!(r, Ok(()));
379
380 let c = read_char(&mut v);
381 assert_matches!(c, Ok('c'));
382 }
383
384 #[test]
385 fn test_parse_tag() {
386
387 let t = parse_tag("BASE", "99").unwrap();
388 assert_matches!(t, Tag::BASE(99));
389 }
390
391 #[test]
392 fn test_checksum() {
393
394 let s = checksum("PAPP", "00380");
395 assert_eq!(s, ',');
396 }
397
398 #[test]
399 fn test_read_frame() {
400
401 let mut file = get_test_file_reader("badchecksum.txt").unwrap();
402 let frame = Frame::next_frame(&mut file);
403
404 assert_matches!(frame, Err(TeleinfoError::ChecksumError));
405
406 let mut file = get_test_file_reader("frames.txt").unwrap();
407 let frame = Frame::next_frame(&mut file).unwrap();
408
409 assert_eq!(frame.tags.len(), 8);
410
411 for tag in frame.tags {
412 match tag {
413 Tag::PAPP(v) => {
414 assert_eq!(v, 370);
415 },
416 _ => ()
417 };
418 }
419
420
421 }
422
423 fn get_test_file_reader(file_name: &str) -> std::io::Result<File> {
424
425 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
426 d.push("resources/test");
427 d.push(file_name);
428
429 return File::open(d);
430 }
431}