resol_vbus/
specification.rs

1//! This module provides the `Specification` and its associated types to allow interpretation
2//! of the fields contained within the `frame_data` payload of `Packet` values.
3use std::{cell::RefCell, clone::Clone, fmt, rc::Rc};
4
5use chrono::{DateTime, TimeZone, Utc};
6
7use crate::{
8    data::Data,
9    error::Result,
10    packet::{PacketFieldId, PacketId},
11    specification_file::{
12        Language, PacketTemplateFieldPart, SpecificationFile, Type, Unit, UnitFamily, UnitId,
13    },
14};
15
16/// Contains information about a VBus device.
17///
18/// # Examples
19///
20/// ```rust
21/// use resol_vbus::{SpecificationFile, Specification, Language};
22///
23/// let spec = Specification::from_file(SpecificationFile::new_default(), Language::De);
24///
25/// let device_spec = spec.get_device_spec(0x00, 0x7E11, 0x0010);
26/// assert_eq!("00_7E11", device_spec.device_id);
27/// assert_eq!(0, device_spec.channel);
28/// assert_eq!(0x7E11, device_spec.self_address);
29/// assert_eq!(None, device_spec.peer_address);
30/// assert_eq!("DeltaSol MX [Regler]", device_spec.name);
31/// ```
32#[derive(Debug)]
33pub struct DeviceSpec {
34    /// A device identifier.
35    pub device_id: String,
36
37    /// The VBus channel the device is attached to.
38    pub channel: u8,
39
40    /// The VBus address of the device itself.
41    pub self_address: u16,
42
43    /// Optionally the VBus address of the device's peer.
44    pub peer_address: Option<u16>,
45
46    /// The name of the device.
47    pub name: String,
48}
49
50/// Contains information about a VBus packet and its fields.
51///
52/// # Examples
53///
54/// ```rust
55/// use resol_vbus::{SpecificationFile, Specification, Language};
56///
57/// let spec = Specification::from_file(SpecificationFile::new_default(), Language::De);
58///
59/// let packet_spec = spec.get_packet_spec(0x00, 0x0010, 0x7E11, 0x0100);
60/// assert_eq!("00_0010_7E11_10_0100", packet_spec.packet_id);
61/// assert_eq!(0, packet_spec.channel);
62/// assert_eq!(0x0010, packet_spec.destination_address);
63/// assert_eq!(0x7E11, packet_spec.source_address);
64/// assert_eq!(0x0100, packet_spec.command);
65/// assert_eq!("DFA", packet_spec.destination_device.name);
66/// assert_eq!("DeltaSol MX [Regler]", packet_spec.source_device.name);
67/// assert_eq!("DeltaSol MX [Regler]", packet_spec.name);
68/// ```
69#[derive(Debug)]
70pub struct PacketSpec {
71    /// A packet identifier.
72    pub packet_id: String,
73
74    /// The VBus channel to packet was sent to.
75    pub channel: u8,
76
77    /// The destination VBus address the packet was sent to.
78    pub destination_address: u16,
79
80    /// The source VBus address to packet was send from.
81    pub source_address: u16,
82
83    /// The VBus command of the packet.
84    pub command: u16,
85
86    /// The `DeviceSpec` containing information about the destination VBus device.
87    pub destination_device: Rc<DeviceSpec>,
88
89    /// The `DeviceSpec` containing information about the source VBus device.
90    pub source_device: Rc<DeviceSpec>,
91
92    /// The name of the packet, containing channel, source and optionally destination names.
93    pub name: String,
94
95    /// The fields contained in the frame payload of the VBus packet.
96    pub fields: Vec<PacketFieldSpec>,
97}
98
99/// Contains information about a VBus packet field.
100///
101/// # Examples
102///
103/// ```rust
104/// use resol_vbus::{SpecificationFile, Specification, Language};
105/// use resol_vbus::specification_file::{UnitFamily, Type};
106///
107/// let spec = Specification::from_file(SpecificationFile::new_default(), Language::De);
108///
109/// let packet_spec = spec.get_packet_spec(0x00, 0x0010, 0x7E11, 0x0100);
110/// let packet_field_spec = &packet_spec.fields [0];
111///
112/// assert_eq!("000_2_0", packet_field_spec.field_id);
113/// assert_eq!("00_0010_7E11_10_0100_000_2_0", packet_field_spec.packet_field_id);
114/// assert_eq!("Temperatur Sensor 1", packet_field_spec.name);
115/// assert_eq!(62, packet_field_spec.unit_id.0);
116/// assert_eq!(UnitFamily::Temperature, packet_field_spec.unit_family);
117/// assert_eq!("DegreesCelsius", packet_field_spec.unit_code);
118/// assert_eq!(" °C", packet_field_spec.unit_text);
119/// assert_eq!(1, packet_field_spec.precision);
120/// assert_eq!(Type::Number, packet_field_spec.typ);
121/// ```
122#[derive(Debug, PartialEq)]
123pub struct PacketFieldSpec {
124    /// A field identifier.
125    pub field_id: String,
126
127    /// A packet-field identifier.
128    pub packet_field_id: String,
129
130    /// The name of the field.
131    pub name: String,
132
133    /// The `UnitId` of the field.
134    pub unit_id: UnitId,
135
136    /// The `UnitFamily` of the field.
137    pub unit_family: UnitFamily,
138
139    /// The unit code of the field.
140    pub unit_code: String,
141
142    /// The unit text of the field.
143    pub unit_text: String,
144
145    /// The precision of the field.
146    pub precision: i32,
147
148    /// The `Type` of the field.
149    pub typ: Type,
150
151    /// The parts the field consists of.
152    pub parts: Vec<PacketTemplateFieldPart>,
153
154    /// The language used for the specification.
155    pub language: Language,
156}
157
158/// A helper type for formatting raw values.
159#[derive(Debug)]
160pub struct RawValueFormatter<'a> {
161    language: Language,
162    typ: Type,
163    precision: i32,
164    raw_value: i64,
165    unit_text: &'a str,
166}
167
168/// A helper type for formatting raw values.
169#[derive(Debug)]
170pub struct PacketFieldFormatter<'a> {
171    language: Language,
172    typ: Type,
173    precision: i32,
174    raw_value: Option<i64>,
175    unit_text: &'a str,
176}
177
178/// The `Specification` type contains information about known devices and packets.
179///
180/// # Examples
181///
182/// ```rust
183/// use resol_vbus::{SpecificationFile, Specification, Language};
184///
185/// let spec = Specification::from_file(SpecificationFile::new_default(), Language::De);
186///
187/// let device_spec = spec.get_device_spec(0x00, 0x7E11, 0x0010);
188/// assert_eq!("00_7E11", device_spec.device_id);
189/// assert_eq!(0, device_spec.channel);
190/// assert_eq!(0x7E11, device_spec.self_address);
191/// assert_eq!(None, device_spec.peer_address);
192/// assert_eq!("DeltaSol MX [Regler]", device_spec.name);
193/// ```
194#[derive(Debug)]
195pub struct Specification {
196    file: SpecificationFile,
197    language: Language,
198    devices: RefCell<Vec<Rc<DeviceSpec>>>,
199    packets: RefCell<Vec<Rc<PacketSpec>>>,
200}
201
202/// An iterator over the fields of the `Packet` instances in a `DataSet`.
203///
204/// The function `Specification::fields_in_data_set` returns this iterator.
205///
206/// # Examples
207///
208/// ```rust
209/// use resol_vbus::{Specification, DataSet};
210///
211/// # #[allow(dead_code)]
212/// fn print_fields(spec: &Specification, data_set: &DataSet) {
213///     let mut last_data_index = None;
214///     for field in spec.fields_in_data_set(data_set) {
215///         let current_data_index = Some(field.data_index());
216///         if last_data_index != current_data_index {
217///             last_data_index = current_data_index;
218///             println!("- {}: {}", field.packet_spec().packet_id, field.packet_spec().name);
219///         }
220///         println!("    - {}: {}", field.field_spec().field_id, field.field_spec().name);
221///     }
222/// }
223/// ```
224#[derive(Debug)]
225pub struct DataSetPacketFieldIterator<'a, T: AsRef<[Data]>> {
226    spec: &'a Specification,
227    data_set: &'a T,
228    data_index: usize,
229    field_index: usize,
230}
231
232/// An item returned from the `DataSetPacketFieldIterator` for each field.
233#[derive(Debug)]
234pub struct DataSetPacketField<'a, T: AsRef<[Data]>> {
235    data_set: &'a T,
236    data_index: usize,
237    packet_spec: Rc<PacketSpec>,
238    field_index: usize,
239    raw_value: Option<i64>,
240}
241
242fn get_cached_device_spec(
243    devices: &[Rc<DeviceSpec>],
244    channel: u8,
245    self_address: u16,
246    peer_address: u16,
247) -> Option<Rc<DeviceSpec>> {
248    let result = devices.iter().find(|&device| {
249        if device.channel != channel {
250            false
251        } else if device.self_address != self_address {
252            false
253        } else if device.peer_address.is_some() && device.peer_address.unwrap() != peer_address {
254            false
255        } else {
256            true
257        }
258    });
259
260    result.cloned()
261}
262
263fn get_or_create_cached_device_spec(
264    devices: &mut Vec<Rc<DeviceSpec>>,
265    channel: u8,
266    self_address: u16,
267    peer_address: u16,
268    file: &SpecificationFile,
269    language: Language,
270) -> Rc<DeviceSpec> {
271    if let Some(device) = get_cached_device_spec(devices, channel, self_address, peer_address) {
272        return device;
273    }
274
275    let device_template = file.find_device_template(self_address, peer_address);
276
277    let peer_address_option = match device_template {
278        None => None,
279        Some(device_template) => {
280            if device_template.peer_mask == 0 {
281                None
282            } else {
283                Some(peer_address)
284            }
285        }
286    };
287
288    let device_id = match peer_address_option {
289        None => format!("{:02X}_{:04X}", channel, self_address),
290        Some(peer_address) => format!("{:02X}_{:04X}_{:04X}", channel, self_address, peer_address),
291    };
292
293    let name = match device_template {
294        None => {
295            match language {
296                Language::En => format!("Unknown device 0x{:04X}", self_address),
297                Language::De => format!("Unbekanntes Gerät 0x{:04X}", self_address),
298                Language::Fr => format!("Unknown device 0x{:04X}", self_address), // FIXME(daniel): missing translation
299            }
300        }
301        Some(device_template) => file
302            .localized_text_by_index(&device_template.name_localized_text_index, language)
303            .to_owned(),
304    };
305
306    let name = match channel {
307        0 => name,
308        _ => format!("VBus {}: {}", channel, name),
309    };
310
311    let device = DeviceSpec {
312        device_id,
313        channel,
314        self_address,
315        peer_address: peer_address_option,
316        name,
317    };
318
319    devices.push(Rc::new(device));
320
321    get_cached_device_spec(devices, channel, self_address, peer_address).unwrap()
322}
323
324fn get_cached_packet_spec(
325    packets: &[Rc<PacketSpec>],
326    packet_id: PacketId,
327) -> Option<Rc<PacketSpec>> {
328    let PacketId(channel, destination_address, source_address, command) = packet_id;
329
330    let result = packets.iter().find(|&packet| {
331        if packet.channel != channel {
332            false
333        } else if packet.destination_address != destination_address {
334            false
335        } else if packet.source_address != source_address {
336            false
337        } else if packet.command != command {
338            false
339        } else {
340            true
341        }
342    });
343
344    result.cloned()
345}
346
347fn get_or_create_cached_packet_spec(
348    packets: &mut Vec<Rc<PacketSpec>>,
349    packet_id: PacketId,
350    devices: &mut Vec<Rc<DeviceSpec>>,
351    file: &SpecificationFile,
352    language: Language,
353) -> Rc<PacketSpec> {
354    let PacketId(channel, destination_address, source_address, command) = packet_id;
355
356    if let Some(packet) = get_cached_packet_spec(packets, packet_id) {
357        return packet;
358    }
359
360    let destination_device = get_or_create_cached_device_spec(
361        devices,
362        channel,
363        destination_address,
364        source_address,
365        file,
366        language,
367    );
368    let source_device = get_or_create_cached_device_spec(
369        devices,
370        channel,
371        source_address,
372        destination_address,
373        file,
374        language,
375    );
376
377    let packet_id_string = packet_id.packet_id_string();
378
379    let packet_name = match destination_address {
380        0x0010 => source_device.name.clone(),
381        _ => format!("{} => {}", source_device.name, destination_device.name),
382    };
383
384    let fields = match file.find_packet_template(destination_address, source_address, command) {
385        None => Vec::new(),
386        Some(packet_template) => packet_template
387            .fields
388            .iter()
389            .map(|field| {
390                let field_id = file.text_by_index(&field.id_text_index).to_string();
391
392                let packet_field_id = format!("{}_{}", packet_id_string, field_id);
393
394                let field_name = file
395                    .localized_text_by_index(&field.name_localized_text_index, language)
396                    .to_string();
397
398                let unit = file.unit_by_id(&field.unit_id);
399
400                let unit_family = file.unit_family_by_id(&unit.unit_family_id);
401                let unit_code = file.text_by_index(&unit.unit_code_text_index).to_string();
402                let unit_text = file.text_by_index(&unit.unit_text_text_index).to_string();
403
404                let typ = file.type_by_id(&field.type_id);
405
406                PacketFieldSpec {
407                    field_id,
408                    packet_field_id,
409                    name: field_name,
410                    unit_id: field.unit_id,
411                    unit_family,
412                    unit_code,
413                    unit_text,
414                    precision: field.precision,
415                    typ,
416                    parts: field.parts.clone(),
417                    language,
418                }
419            })
420            .collect(),
421    };
422
423    let packet = PacketSpec {
424        packet_id: packet_id_string,
425        channel,
426        destination_address,
427        source_address,
428        command,
429        destination_device,
430        source_device,
431        name: packet_name,
432        fields,
433    };
434
435    packets.push(Rc::new(packet));
436
437    get_cached_packet_spec(packets, packet_id).unwrap()
438}
439
440/// Get the "power of 10" `i64` value for common "n"s and calculate it otherwise.
441pub fn power_of_ten_i64(n: u32) -> i64 {
442    match n {
443        0 => 1,
444        1 => 10,
445        2 => 100,
446        3 => 1_000,
447        4 => 10_000,
448        5 => 100_000,
449        6 => 1_000_000,
450        7 => 10_000_000,
451        8 => 100_000_000,
452        9 => 1_000_000_000,
453        _ => 10i64.pow(n),
454    }
455}
456
457/// Get the "power of 10" `f64` value for common "n"s and calculate it otherwise.
458pub fn power_of_ten_f64(n: i32) -> f64 {
459    match n {
460        -9 => 0.000_000_001,
461        -8 => 0.000_000_01,
462        -7 => 0.000_000_1,
463        -6 => 0.000_001,
464        -5 => 0.000_01,
465        -4 => 0.000_1,
466        -3 => 0.001,
467        -2 => 0.01,
468        -1 => 0.1,
469        0 => 1.0,
470        1 => 10.0,
471        2 => 100.0,
472        3 => 1_000.0,
473        4 => 10_000.0,
474        5 => 100_000.0,
475        6 => 1_000_000.0,
476        7 => 10_000_000.0,
477        8 => 100_000_000.0,
478        9 => 1_000_000_000.0,
479        _ => 10.0f64.powf(f64::from(n)),
480    }
481}
482
483impl Specification {
484    /// Construct a `Specification` from a `SpecificationFile` and a `Language`.
485    ///
486    /// # Examples
487    ///
488    /// ```rust
489    /// use resol_vbus::{SpecificationFile, Specification, Language};
490    ///
491    /// let spec = Specification::from_file(SpecificationFile::new_default(), Language::De);
492    ///
493    /// // work with the spec...
494    /// # drop(spec);
495    /// ```
496    pub fn from_file(file: SpecificationFile, language: Language) -> Specification {
497        let devices = RefCell::new(Vec::new());
498        let packets = RefCell::new(Vec::new());
499
500        Specification {
501            file,
502            language,
503            devices,
504            packets,
505        }
506    }
507
508    /// Get the `SpecificationFile` that was used to construct this `Specification`.
509    pub fn specification_file(&self) -> &SpecificationFile {
510        &self.file
511    }
512
513    /// Get the `Language` that was used to construct this `Specification`.
514    pub fn language(&self) -> Language {
515        self.language
516    }
517
518    /// Get a `DeviceSpec`.
519    ///
520    /// # Examples
521    ///
522    /// ```rust
523    /// use resol_vbus::{SpecificationFile, Specification, Language};
524    ///
525    /// let spec = Specification::from_file(SpecificationFile::new_default(), Language::De);
526    ///
527    /// let device_spec = spec.get_device_spec(0x00, 0x7E11, 0x0010);
528    /// assert_eq!("00_7E11", device_spec.device_id);
529    /// assert_eq!(0, device_spec.channel);
530    /// assert_eq!(0x7E11, device_spec.self_address);
531    /// assert_eq!(None, device_spec.peer_address);
532    /// assert_eq!("DeltaSol MX [Regler]", device_spec.name);
533    /// ```
534    pub fn get_device_spec(
535        &self,
536        channel: u8,
537        self_address: u16,
538        peer_address: u16,
539    ) -> Rc<DeviceSpec> {
540        let mut devices = self.devices.borrow_mut();
541        get_or_create_cached_device_spec(
542            &mut devices,
543            channel,
544            self_address,
545            peer_address,
546            &self.file,
547            self.language,
548        )
549    }
550
551    /// Get a `PacketSpec`.
552    ///
553    /// # Examples
554    ///
555    /// ```rust
556    /// use resol_vbus::{SpecificationFile, Specification, Language};
557    ///
558    /// let spec = Specification::from_file(SpecificationFile::new_default(), Language::De);
559    ///
560    /// let packet_spec = spec.get_packet_spec(0x00, 0x0010, 0x7E11, 0x0100);
561    /// assert_eq!("00_0010_7E11_10_0100", packet_spec.packet_id);
562    /// assert_eq!(0, packet_spec.channel);
563    /// assert_eq!(0x0010, packet_spec.destination_address);
564    /// assert_eq!(0x7E11, packet_spec.source_address);
565    /// assert_eq!(0x0100, packet_spec.command);
566    /// assert_eq!("DFA", packet_spec.destination_device.name);
567    /// assert_eq!("DeltaSol MX [Regler]", packet_spec.source_device.name);
568    /// assert_eq!("DeltaSol MX [Regler]", packet_spec.name);
569    /// ```
570    pub fn get_packet_spec(
571        &self,
572        channel: u8,
573        destination_address: u16,
574        source_address: u16,
575        command: u16,
576    ) -> Rc<PacketSpec> {
577        let mut devices = self.devices.borrow_mut();
578        let mut packets = self.packets.borrow_mut();
579        let packet_id = PacketId(channel, destination_address, source_address, command);
580        get_or_create_cached_packet_spec(
581            &mut packets,
582            packet_id,
583            &mut devices,
584            &self.file,
585            self.language,
586        )
587    }
588
589    /// Get a `PacketSpec`.
590    ///
591    /// # Examples
592    ///
593    /// ```rust
594    /// use resol_vbus::{SpecificationFile, Specification, Language, PacketId};
595    ///
596    /// let spec = Specification::from_file(SpecificationFile::new_default(), Language::De);
597    ///
598    /// let packet_spec = spec.get_packet_spec_by_id(PacketId(0x00, 0x0010, 0x7E11, 0x0100));
599    /// assert_eq!("00_0010_7E11_10_0100", packet_spec.packet_id);
600    /// assert_eq!(0, packet_spec.channel);
601    /// assert_eq!(0x0010, packet_spec.destination_address);
602    /// assert_eq!(0x7E11, packet_spec.source_address);
603    /// assert_eq!(0x0100, packet_spec.command);
604    /// assert_eq!("DFA", packet_spec.destination_device.name);
605    /// assert_eq!("DeltaSol MX [Regler]", packet_spec.source_device.name);
606    /// assert_eq!("DeltaSol MX [Regler]", packet_spec.name);
607    /// ```
608    pub fn get_packet_spec_by_id(&self, packet_id: PacketId) -> Rc<PacketSpec> {
609        self.get_packet_spec(packet_id.0, packet_id.1, packet_id.2, packet_id.3)
610    }
611
612    /// Returns an iterator that iterates over all known packet fields in the data set.
613    ///
614    /// # Examples
615    ///
616    /// ```rust
617    /// use resol_vbus::{Specification, DataSet};
618    ///
619    /// # #[allow(dead_code)]
620    /// fn print_fields(spec: &Specification, data_set: &DataSet) {
621    ///     let mut last_data_index = None;
622    ///     for field in spec.fields_in_data_set(data_set) {
623    ///         let current_data_index = Some(field.data_index());
624    ///         if last_data_index != current_data_index {
625    ///             last_data_index = current_data_index;
626    ///             println!("- {}: {}", field.packet_spec().packet_id, field.packet_spec().name);
627    ///         }
628    ///         println!("    - {}: {}", field.field_spec().field_id, field.field_spec().name);
629    ///     }
630    /// }
631    /// ```
632    pub fn fields_in_data_set<'a, T: AsRef<[Data]> + 'a>(
633        &'a self,
634        data_set: &'a T,
635    ) -> DataSetPacketFieldIterator<'a, T> {
636        DataSetPacketFieldIterator {
637            spec: self,
638            data_set,
639            data_index: 0,
640            field_index: 0,
641        }
642    }
643
644    /// Format a timestamp.
645    ///
646    /// # Examples
647    ///
648    /// ```rust
649    /// use resol_vbus::{SpecificationFile, Specification, Language};
650    /// use resol_vbus::utils::utc_timestamp;
651    ///
652    /// let fmt_localized_timestamp = |language| {
653    ///     let spec = Specification::from_file(SpecificationFile::new_default(), language);
654    ///
655    ///     format!("{}", spec.fmt_timestamp(&utc_timestamp(1485688933)))
656    /// };
657    ///
658    /// assert_eq!("29/01/2017 11:22:13", fmt_localized_timestamp(Language::En));
659    /// assert_eq!("29.01.2017 11:22:13", fmt_localized_timestamp(Language::De));
660    /// assert_eq!("29/01/2017 11:22:13", fmt_localized_timestamp(Language::Fr));
661    /// ```
662    pub fn fmt_timestamp<Tz: TimeZone>(&self, timestamp: &DateTime<Tz>) -> RawValueFormatter<'_> {
663        RawValueFormatter {
664            language: self.language,
665            typ: Type::DateTime,
666            precision: 0,
667            raw_value: timestamp.naive_local().timestamp() - 978_307_200,
668            unit_text: "",
669        }
670    }
671
672    /// Get `Unit` by its unit code.
673    ///
674    /// # Examples
675    ///
676    /// ```rust
677    /// use resol_vbus::{SpecificationFile, Specification, Language};
678    /// use resol_vbus::specification_file::UnitId;
679    ///
680    /// let spec = Specification::from_file(SpecificationFile::new_default(), Language::En);
681    ///
682    /// assert_eq!(UnitId(62), spec.unit_by_unit_code("DegreesCelsius").unwrap().unit_id);
683    /// assert!(spec.unit_by_unit_code("SomeUnknownUnitCode").is_none());
684    /// ```
685    pub fn unit_by_unit_code(&self, unit_code: &str) -> Option<&Unit> {
686        self.file.unit_by_unit_code(unit_code)
687    }
688
689    /// Convert a value from one `Unit` to another.
690    ///
691    /// # Examples
692    ///
693    /// ```rust
694    /// use resol_vbus::{SpecificationFile, Specification, Language};
695    ///
696    /// let spec = Specification::from_file(SpecificationFile::new_default(), Language::En);
697    ///
698    /// let src_unit = spec.unit_by_unit_code("DegreesCelsius").unwrap();
699    /// let dst_unit = spec.unit_by_unit_code("DegreesFahrenheit").unwrap();
700    /// assert_eq!(Ok(32.0), spec.convert_value(0.0, src_unit, dst_unit));
701    /// ```
702    pub fn convert_value(&self, value: f64, src_unit: &Unit, dst_unit: &Unit) -> Result<f64> {
703        self.file.convert_value(value, src_unit, dst_unit)
704    }
705}
706
707impl PacketSpec {
708    /// Get the position of a `PacketFieldSpec` by its field ID.
709    pub fn get_field_spec_position(&self, id: &str) -> Option<usize> {
710        self.fields
711            .iter()
712            .position(|field_spec| field_spec.field_id == id)
713    }
714
715    /// Get a `PacketFieldSpec` by its position.
716    pub fn get_field_spec_by_position(&self, pos: usize) -> &PacketFieldSpec {
717        &self.fields[pos]
718    }
719
720    /// Get a `PacketFieldSpec` by its field ID.
721    pub fn get_field_spec(&self, id: &str) -> Option<&PacketFieldSpec> {
722        self.fields
723            .iter()
724            .find(|field_spec| field_spec.field_id == id)
725    }
726}
727
728impl PacketFieldSpec {
729    /// Construct an `i64` raw value from a slice of bytes.
730    pub fn raw_value_i64(&self, buf: &[u8]) -> Option<i64> {
731        let length = buf.len();
732
733        let mut valid = false;
734        let mut raw_value = 0;
735
736        for part in &self.parts {
737            let offset = part.offset as usize;
738
739            if offset < length {
740                let mut part_value = if part.is_signed {
741                    i64::from(buf[offset] as i8)
742                } else {
743                    i64::from(buf[offset])
744                };
745                if part.mask != 0xFF {
746                    part_value &= i64::from(part.mask);
747                }
748                if part.bit_pos > 0 {
749                    part_value >>= part.bit_pos;
750                }
751                raw_value += part_value * part.factor;
752                valid = true;
753            }
754        }
755
756        if valid {
757            Some(raw_value)
758        } else {
759            None
760        }
761    }
762
763    /// Construct a `f64` raw value from a slice of bytes.
764    pub fn raw_value_f64(&self, buf: &[u8]) -> Option<f64> {
765        self.raw_value_i64(buf)
766            .map(|raw_value| raw_value as f64 * power_of_ten_f64(-self.precision))
767    }
768
769    /// Format a raw value into its textual representation.
770    pub fn fmt_raw_value(
771        &self,
772        raw_value: Option<i64>,
773        append_unit: bool,
774    ) -> PacketFieldFormatter<'_> {
775        let unit_text = if append_unit { &self.unit_text } else { "" };
776        PacketFieldFormatter {
777            language: self.language,
778            typ: self.typ,
779            precision: self.precision,
780            raw_value,
781            unit_text,
782        }
783    }
784}
785
786const WEEKDAYS_EN: [&str; 7] = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
787
788const WEEKDAYS_DE: [&str; 7] = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
789
790const WEEKDAYS_FR: [&str; 7] = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
791
792impl<'a> RawValueFormatter<'a> {
793    /// Construct a `RawValueFormatter` to help format a raw value into its textual representation.
794    pub fn new(
795        language: Language,
796        typ: Type,
797        precision: i32,
798        raw_value: i64,
799        unit_text: &'a str,
800    ) -> RawValueFormatter<'a> {
801        RawValueFormatter {
802            language,
803            typ,
804            precision,
805            raw_value,
806            unit_text,
807        }
808    }
809}
810
811impl<'a> fmt::Display for RawValueFormatter<'a> {
812    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
813        match self.typ {
814            Type::Number => {
815                if self.precision > 0 {
816                    let sign = if self.raw_value < 0 { "-" } else { "" };
817                    let raw_value = self.raw_value.abs();
818                    let factor = power_of_ten_i64(self.precision as u32);
819                    let left_part = raw_value / factor;
820                    let right_part = raw_value % factor;
821                    let separator = match self.language {
822                        Language::En => ".",
823                        Language::De | Language::Fr => ",",
824                    };
825
826                    write!(f, "{}{}{}", sign, left_part, separator)?;
827                    match self.precision {
828                        1 => write!(f, "{:01}", right_part)?,
829                        2 => write!(f, "{:02}", right_part)?,
830                        3 => write!(f, "{:03}", right_part)?,
831                        4 => write!(f, "{:04}", right_part)?,
832                        5 => write!(f, "{:05}", right_part)?,
833                        6 => write!(f, "{:06}", right_part)?,
834                        7 => write!(f, "{:07}", right_part)?,
835                        8 => write!(f, "{:08}", right_part)?,
836                        9 => write!(f, "{:09}", right_part)?,
837                        _ => {
838                            let s = format!("{}", right_part + factor);
839                            write!(f, "{}", &s[1..])?;
840                        }
841                    };
842                    write!(f, "{}", self.unit_text)
843                } else {
844                    write!(f, "{}{}", self.raw_value, self.unit_text)
845                }
846            }
847            Type::Time => {
848                let hours = self.raw_value / 60;
849                let minutes = self.raw_value % 60;
850                write!(f, "{:02}:{:02}", hours, minutes)
851            }
852            Type::WeekTime => {
853                let weekday_idx = ((self.raw_value / 1440) % 7) as usize;
854                let hours = (self.raw_value / 60) % 24;
855                let minutes = self.raw_value % 60;
856                match self.language {
857                    Language::En => write!(
858                        f,
859                        "{},{:02}:{:02}",
860                        WEEKDAYS_EN[weekday_idx], hours, minutes
861                    ),
862                    Language::De => write!(
863                        f,
864                        "{},{:02}:{:02}",
865                        WEEKDAYS_DE[weekday_idx], hours, minutes
866                    ),
867                    Language::Fr => write!(
868                        f,
869                        "{},{:02}:{:02}",
870                        WEEKDAYS_FR[weekday_idx], hours, minutes
871                    ),
872                }
873            }
874            Type::DateTime => {
875                let timestamp = Utc.timestamp(self.raw_value + 978_307_200, 0);
876                match self.language {
877                    Language::En | Language::Fr => {
878                        write!(f, "{}", timestamp.format("%d/%m/%Y %H:%M:%S"))
879                    }
880                    Language::De => write!(f, "{}", timestamp.format("%d.%m.%Y %H:%M:%S")),
881                }
882            }
883        }
884    }
885}
886
887impl<'a> fmt::Display for PacketFieldFormatter<'a> {
888    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
889        if let Some(raw_value) = self.raw_value {
890            let formatter = RawValueFormatter::new(
891                self.language,
892                self.typ,
893                self.precision,
894                raw_value,
895                self.unit_text,
896            );
897            formatter.fmt(f)
898        } else {
899            Ok(())
900        }
901    }
902}
903
904impl<'a, T: AsRef<[Data]> + 'a> Iterator for DataSetPacketFieldIterator<'a, T> {
905    type Item = DataSetPacketField<'a, T>;
906
907    fn next(&mut self) -> Option<Self::Item> {
908        let data_slice = self.data_set.as_ref();
909        let data_slice_len = data_slice.len();
910
911        while self.data_index < data_slice_len {
912            let data = &data_slice[self.data_index];
913            if let Data::Packet(ref packet) = *data {
914                let packet_spec = self.spec.get_packet_spec(
915                    packet.header.channel,
916                    packet.header.destination_address,
917                    packet.header.source_address,
918                    packet.command,
919                );
920                if self.field_index < packet_spec.fields.len() {
921                    let field_index = self.field_index;
922                    self.field_index += 1;
923
924                    let frame_data = &packet.frame_data[0..packet.frame_count as usize * 4];
925
926                    let field_spec = &packet_spec.fields[field_index];
927                    let raw_value = field_spec.raw_value_i64(frame_data);
928
929                    return Some(DataSetPacketField {
930                        data_set: self.data_set,
931                        data_index: self.data_index,
932                        packet_spec: packet_spec.clone(),
933                        field_index,
934                        raw_value,
935                    });
936                }
937            }
938
939            self.data_index += 1;
940            self.field_index = 0;
941        }
942
943        None
944    }
945}
946
947impl<'a, T: AsRef<[Data]>> DataSetPacketField<'a, T> {
948    /// Construct new `DataSetPacketField` value.
949    pub fn new(
950        data_set: &'a T,
951        data_index: usize,
952        packet_spec: Rc<PacketSpec>,
953        field_index: usize,
954        raw_value: Option<i64>,
955    ) -> DataSetPacketField<'a, T> {
956        DataSetPacketField {
957            data_set,
958            data_index,
959            packet_spec,
960            field_index,
961            raw_value,
962        }
963    }
964
965    /// Return the `DataSet` associated with this field.
966    pub fn data_set(&self) -> &[Data] {
967        self.data_set.as_ref()
968    }
969
970    /// Return the index of the `Data` associated with this field.
971    pub fn data_index(&self) -> usize {
972        self.data_index
973    }
974
975    /// Return the `Data` associated with this field.
976    pub fn data(&self) -> &Data {
977        &self.data_set.as_ref()[self.data_index]
978    }
979
980    /// Return the `PacketSpec` associated with this field.
981    pub fn packet_spec(&self) -> &PacketSpec {
982        self.packet_spec.as_ref()
983    }
984
985    /// Return the index of the `PacketFieldSpec` associated with this field.
986    pub fn field_index(&self) -> usize {
987        self.field_index
988    }
989
990    /// Return the `PacketFieldSpec` associated with this field.
991    pub fn field_spec(&self) -> &PacketFieldSpec {
992        &self.packet_spec.fields[self.field_index]
993    }
994
995    /// Return the `PacketId` associated with this field.
996    pub fn packet_id(&self) -> PacketId {
997        self.data().as_packet().packet_id()
998    }
999
1000    /// Return the field ID associated with this field.
1001    pub fn field_id(&self) -> &str {
1002        &self.field_spec().field_id
1003    }
1004
1005    /// Return the `PacketFieldId` associated with this field.
1006    pub fn packet_field_id(&self) -> PacketFieldId<'_> {
1007        PacketFieldId(
1008            self.data().as_packet().packet_id(),
1009            &self.field_spec().field_id,
1010        )
1011    }
1012
1013    /// Return the raw value associated with this field.
1014    pub fn raw_value_i64(&self) -> &Option<i64> {
1015        &self.raw_value
1016    }
1017
1018    /// Return the raw value associated with this field.
1019    pub fn raw_value_f64(&self) -> Option<f64> {
1020        self.raw_value
1021            .map(|v| v as f64 * power_of_ten_f64(-self.field_spec().precision))
1022    }
1023
1024    /// Format the raw value associated with this field.
1025    pub fn fmt_raw_value(&self, append_unit: bool) -> PacketFieldFormatter<'_> {
1026        self.field_spec().fmt_raw_value(self.raw_value, append_unit)
1027    }
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032    use super::*;
1033
1034    use crate::{
1035        recording_reader::RecordingReader,
1036        test_data::{RECORDING_2, SPEC_FILE_1},
1037    };
1038
1039    #[test]
1040    fn test_power_of_ten_i64() {
1041        for n in 0..19 {
1042            assert_eq!(10i64.pow(n), power_of_ten_i64(n));
1043        }
1044    }
1045
1046    #[test]
1047    fn test_power_of_ten_f64() {
1048        for n in -20..20 {
1049            assert_eq!(10.0f64.powf(n as f64), power_of_ten_f64(n));
1050        }
1051    }
1052
1053    #[test]
1054    fn test_raw_value_formatter() {
1055        use crate::specification_file::{Language::*, Type::*};
1056
1057        let fmt_to_string = |language, typ, prec, value, unit| {
1058            let formatter = RawValueFormatter::new(language, typ, prec, value, unit);
1059            format!("{}", formatter)
1060        };
1061
1062        assert_eq!("12346", fmt_to_string(En, Number, 0, 12346, ""));
1063        assert_eq!("12346 unit", fmt_to_string(En, Number, 0, 12346, " unit"));
1064        assert_eq!("12345.7", fmt_to_string(En, Number, 1, 123457, ""));
1065        assert_eq!("12345.68", fmt_to_string(En, Number, 2, 1234568, ""));
1066        assert_eq!("12345.679", fmt_to_string(En, Number, 3, 12345679, ""));
1067        assert_eq!("12345.6789", fmt_to_string(En, Number, 4, 123456789, ""));
1068        assert_eq!(
1069            "1.2345678900",
1070            fmt_to_string(En, Number, 10, 12345678900, "")
1071        );
1072        assert_eq!(
1073            "1,2345678900",
1074            fmt_to_string(De, Number, 10, 12345678900, "")
1075        );
1076        assert_eq!(
1077            "1,2345678900",
1078            fmt_to_string(Fr, Number, 10, 12345678900, "")
1079        );
1080
1081        assert_eq!(
1082            "12:01",
1083            fmt_to_string(En, Time, 10, 721, " ignore this unit")
1084        );
1085        assert_eq!(
1086            "12:01",
1087            fmt_to_string(De, Time, 10, 721, " ignore this unit")
1088        );
1089        assert_eq!(
1090            "12:01",
1091            fmt_to_string(Fr, Time, 10, 721, " ignore this unit")
1092        );
1093
1094        assert_eq!(
1095            "Th,12:01",
1096            fmt_to_string(En, WeekTime, 10, 3 * 1440 + 721, " ignore this unit")
1097        );
1098        assert_eq!(
1099            "Do,12:01",
1100            fmt_to_string(De, WeekTime, 10, 3 * 1440 + 721, " ignore this unit")
1101        );
1102        assert_eq!(
1103            "Je,12:01",
1104            fmt_to_string(Fr, WeekTime, 10, 3 * 1440 + 721, " ignore this unit")
1105        );
1106
1107        assert_eq!(
1108            "22/12/2013 15:17:42",
1109            fmt_to_string(En, DateTime, 10, 409418262, " ignore this unit")
1110        );
1111        assert_eq!(
1112            "22.12.2013 15:17:42",
1113            fmt_to_string(De, DateTime, 10, 409418262, " ignore this unit")
1114        );
1115        assert_eq!(
1116            "22/12/2013 15:17:42",
1117            fmt_to_string(Fr, DateTime, 10, 409418262, " ignore this unit")
1118        );
1119    }
1120
1121    #[test]
1122    fn test_from_file() {
1123        let spec_file = SpecificationFile::from_bytes(SPEC_FILE_1).unwrap();
1124
1125        let spec = Specification::from_file(spec_file, Language::En);
1126
1127        assert_eq!(0, spec.devices.borrow().len());
1128        assert_eq!(0, spec.packets.borrow().len());
1129    }
1130
1131    #[test]
1132    fn test_get_device_spec() {
1133        let spec_file = SpecificationFile::from_bytes(SPEC_FILE_1).unwrap();
1134
1135        let spec = Specification::from_file(spec_file, Language::En);
1136
1137        assert_eq!(0, spec.devices.borrow().len());
1138
1139        let device_spec = spec.get_device_spec(0x01, 0x7E31, 0x0010);
1140
1141        assert_eq!(1, spec.devices.borrow().len());
1142        assert_eq!("01_7E31", device_spec.device_id);
1143        assert_eq!(0x01, device_spec.channel);
1144        assert_eq!(0x7E31, device_spec.self_address);
1145        assert_eq!(None, device_spec.peer_address);
1146        assert_eq!("VBus 1: DeltaSol MX [WMZ #1]", device_spec.name);
1147
1148        let device_spec = spec.get_device_spec(0x01, 0x7E31, 0x0010);
1149
1150        assert_eq!(1, spec.devices.borrow().len());
1151        assert_eq!("01_7E31", device_spec.device_id);
1152
1153        let device_spec = spec.get_device_spec(0x00, 0x7E31, 0x0010);
1154
1155        assert_eq!(2, spec.devices.borrow().len());
1156        assert_eq!("00_7E31", device_spec.device_id);
1157        assert_eq!(0x00, device_spec.channel);
1158        assert_eq!(0x7E31, device_spec.self_address);
1159        assert_eq!(None, device_spec.peer_address);
1160        assert_eq!("DeltaSol MX [WMZ #1]", device_spec.name);
1161
1162        let device_spec = spec.get_device_spec(0x00, 0x7E11, 0x0010);
1163
1164        assert_eq!(3, spec.devices.borrow().len());
1165        assert_eq!("00_7E11", device_spec.device_id);
1166        assert_eq!(0x00, device_spec.channel);
1167        assert_eq!(0x7E11, device_spec.self_address);
1168        assert_eq!(None, device_spec.peer_address);
1169        assert_eq!("Unknown device 0x7E11", device_spec.name);
1170    }
1171
1172    #[test]
1173    fn test_get_packet_spec() {
1174        let spec_file = SpecificationFile::from_bytes(SPEC_FILE_1).unwrap();
1175
1176        let spec = Specification::from_file(spec_file, Language::En);
1177
1178        assert_eq!(0, spec.packets.borrow().len());
1179
1180        let packet_spec = spec.get_packet_spec(0x01, 0x0010, 0x7E31, 0x0100);
1181
1182        assert_eq!(1, spec.packets.borrow().len());
1183        assert_eq!("01_0010_7E31_10_0100", packet_spec.packet_id);
1184        assert_eq!(0x01, packet_spec.channel);
1185        assert_eq!(0x0010, packet_spec.destination_address);
1186        assert_eq!(0x7E31, packet_spec.source_address);
1187        assert_eq!(0x0100, packet_spec.command);
1188        assert_eq!("VBus 1: DFA", packet_spec.destination_device.name);
1189        assert_eq!(
1190            "VBus 1: DeltaSol MX [WMZ #1]",
1191            packet_spec.source_device.name
1192        );
1193        assert_eq!("VBus 1: DeltaSol MX [WMZ #1]", packet_spec.name);
1194        assert_eq!(8, packet_spec.fields.len());
1195
1196        let field_spec = &packet_spec.fields[0];
1197        assert_eq!("000_4_0", field_spec.field_id);
1198        assert_eq!("01_0010_7E31_10_0100_000_4_0", field_spec.packet_field_id);
1199        assert_eq!("Heat quantity", field_spec.name);
1200        assert_eq!(18, field_spec.unit_id.0);
1201        assert_eq!(UnitFamily::Energy, field_spec.unit_family);
1202        assert_eq!("WattHours", field_spec.unit_code);
1203        assert_eq!(" Wh", field_spec.unit_text);
1204        assert_eq!(0, field_spec.precision);
1205        assert_eq!(Type::Number, field_spec.typ);
1206        assert_eq!(8, field_spec.parts.len());
1207
1208        let packet_spec = spec.get_packet_spec(0x01, 0x0010, 0x7E31, 0x0100);
1209
1210        assert_eq!(1, spec.packets.borrow().len());
1211        assert_eq!("01_0010_7E31_10_0100", packet_spec.packet_id);
1212
1213        let packet_spec = spec.get_packet_spec(0x00, 0x0010, 0x7E31, 0x0100);
1214
1215        assert_eq!(2, spec.packets.borrow().len());
1216        assert_eq!("00_0010_7E31_10_0100", packet_spec.packet_id);
1217        assert_eq!(0x00, packet_spec.channel);
1218        assert_eq!(0x0010, packet_spec.destination_address);
1219        assert_eq!(0x7E31, packet_spec.source_address);
1220        assert_eq!(0x0100, packet_spec.command);
1221        assert_eq!("DFA", packet_spec.destination_device.name);
1222        assert_eq!("DeltaSol MX [WMZ #1]", packet_spec.source_device.name);
1223        assert_eq!("DeltaSol MX [WMZ #1]", packet_spec.name);
1224        assert_eq!(8, packet_spec.fields.len());
1225
1226        let packet_spec = spec.get_packet_spec(0x00, 0x0010, 0x7E11, 0x0100);
1227
1228        assert_eq!(3, spec.packets.borrow().len());
1229        assert_eq!("00_0010_7E11_10_0100", packet_spec.packet_id);
1230        assert_eq!(0x00, packet_spec.channel);
1231        assert_eq!(0x0010, packet_spec.destination_address);
1232        assert_eq!(0x7E11, packet_spec.source_address);
1233        assert_eq!(0x0100, packet_spec.command);
1234        assert_eq!("DFA", packet_spec.destination_device.name);
1235        assert_eq!("Unknown device 0x7E11", packet_spec.source_device.name);
1236        assert_eq!("Unknown device 0x7E11", packet_spec.name);
1237        assert_eq!(0, packet_spec.fields.len());
1238    }
1239
1240    #[test]
1241    fn test_get_field_spec() {
1242        let spec_file = SpecificationFile::from_bytes(SPEC_FILE_1).unwrap();
1243
1244        let spec = Specification::from_file(spec_file, Language::En);
1245
1246        assert_eq!(0, spec.packets.borrow().len());
1247
1248        let packet_spec = spec.get_packet_spec(0x01, 0x0010, 0x7E31, 0x0100);
1249
1250        let field_spec = packet_spec.get_field_spec("000_4_0").unwrap();
1251        assert_eq!("000_4_0", field_spec.field_id);
1252        assert_eq!("01_0010_7E31_10_0100_000_4_0", field_spec.packet_field_id);
1253        assert_eq!("Heat quantity", field_spec.name);
1254        assert_eq!(18, field_spec.unit_id.0);
1255        assert_eq!(UnitFamily::Energy, field_spec.unit_family);
1256        assert_eq!("WattHours", field_spec.unit_code);
1257        assert_eq!(" Wh", field_spec.unit_text);
1258        assert_eq!(0, field_spec.precision);
1259        assert_eq!(Type::Number, field_spec.typ);
1260        assert_eq!(8, field_spec.parts.len());
1261
1262        assert_eq!(None, packet_spec.get_field_spec("000_2_0"));
1263    }
1264
1265    #[test]
1266    fn test_raw_value_i64() {
1267        let spec_file = SpecificationFile::from_bytes(SPEC_FILE_1).unwrap();
1268
1269        let spec = Specification::from_file(spec_file, Language::En);
1270
1271        assert_eq!(0, spec.packets.borrow().len());
1272
1273        let packet_spec = spec.get_packet_spec(0x01, 0x0010, 0x7F61, 0x0100);
1274
1275        let buf = &[
1276            0x78, 0x56, 0x34, 0x12, 0xB8, 0x22, 0x00, 0x00, 0x48, 0xDD, 0xFF, 0xFF,
1277        ];
1278
1279        assert_eq!(
1280            Some(0x12345678),
1281            packet_spec
1282                .get_field_spec("000_4_0")
1283                .unwrap()
1284                .raw_value_i64(buf)
1285        );
1286        assert_eq!(
1287            Some(8888),
1288            packet_spec
1289                .get_field_spec("004_4_0")
1290                .unwrap()
1291                .raw_value_i64(buf)
1292        );
1293        assert_eq!(
1294            Some(-8888),
1295            packet_spec
1296                .get_field_spec("008_4_0")
1297                .unwrap()
1298                .raw_value_i64(buf)
1299        );
1300        assert_eq!(
1301            Some(0x345678),
1302            packet_spec
1303                .get_field_spec("000_4_0")
1304                .unwrap()
1305                .raw_value_i64(&buf[0..3])
1306        );
1307        assert_eq!(
1308            Some(0x5678),
1309            packet_spec
1310                .get_field_spec("000_4_0")
1311                .unwrap()
1312                .raw_value_i64(&buf[0..2])
1313        );
1314        assert_eq!(
1315            Some(0x78),
1316            packet_spec
1317                .get_field_spec("000_4_0")
1318                .unwrap()
1319                .raw_value_i64(&buf[0..1])
1320        );
1321        assert_eq!(
1322            None,
1323            packet_spec
1324                .get_field_spec("000_4_0")
1325                .unwrap()
1326                .raw_value_i64(&buf[0..0])
1327        );
1328    }
1329
1330    #[test]
1331    fn test_raw_value_f64() {
1332        let spec_file = SpecificationFile::from_bytes(SPEC_FILE_1).unwrap();
1333
1334        let spec = Specification::from_file(spec_file, Language::En);
1335
1336        assert_eq!(0, spec.packets.borrow().len());
1337
1338        let packet_spec = spec.get_packet_spec(0x01, 0x0010, 0x7F61, 0x0100);
1339
1340        let buf = &[
1341            0x78, 0x56, 0x34, 0x12, 0xB8, 0x22, 0x00, 0x00, 0x48, 0xDD, 0xFF, 0xFF,
1342        ];
1343
1344        assert_eq!(
1345            Some(0x12345678 as f64),
1346            packet_spec
1347                .get_field_spec("000_4_0")
1348                .unwrap()
1349                .raw_value_f64(buf)
1350        );
1351        assert_eq!(
1352            Some(888.8000000000001),
1353            packet_spec
1354                .get_field_spec("004_4_0")
1355                .unwrap()
1356                .raw_value_f64(buf)
1357        );
1358        assert_eq!(
1359            Some(-888.8000000000001),
1360            packet_spec
1361                .get_field_spec("008_4_0")
1362                .unwrap()
1363                .raw_value_f64(buf)
1364        );
1365        assert_eq!(
1366            Some(0x345678 as f64),
1367            packet_spec
1368                .get_field_spec("000_4_0")
1369                .unwrap()
1370                .raw_value_f64(&buf[0..3])
1371        );
1372        assert_eq!(
1373            Some(0x5678 as f64),
1374            packet_spec
1375                .get_field_spec("000_4_0")
1376                .unwrap()
1377                .raw_value_f64(&buf[0..2])
1378        );
1379        assert_eq!(
1380            Some(0x78 as f64),
1381            packet_spec
1382                .get_field_spec("000_4_0")
1383                .unwrap()
1384                .raw_value_f64(&buf[0..1])
1385        );
1386        assert_eq!(
1387            None,
1388            packet_spec
1389                .get_field_spec("000_4_0")
1390                .unwrap()
1391                .raw_value_f64(&buf[0..0])
1392        );
1393    }
1394
1395    #[test]
1396    fn test_fmt_raw_value() {
1397        let fake_field_spec = |precision, typ, unit_text: &str| PacketFieldSpec {
1398            field_id: "".to_string(),
1399            packet_field_id: "".to_string(),
1400            name: "".to_string(),
1401            unit_id: UnitId(0),
1402            unit_family: UnitFamily::None,
1403            unit_code: "unit code".to_string(),
1404            unit_text: unit_text.to_string(),
1405            precision,
1406            typ,
1407            parts: Vec::new(),
1408            language: Language::En,
1409        };
1410
1411        let fmt_raw_value = |field_spec: &PacketFieldSpec, raw_value, append_unit| {
1412            let test_value = field_spec.fmt_raw_value(Some(raw_value), append_unit);
1413            format!("{}", test_value)
1414        };
1415
1416        let field_spec = fake_field_spec(0, Type::Number, "don't append unit");
1417        assert_eq!("12346", fmt_raw_value(&field_spec, 12346, false));
1418
1419        let field_spec = fake_field_spec(0, Type::Number, " unit");
1420        assert_eq!("12346 unit", fmt_raw_value(&field_spec, 12346, true));
1421
1422        let field_spec = fake_field_spec(1, Type::Number, "don't append unit");
1423        assert_eq!("12345.7", fmt_raw_value(&field_spec, 123457, false));
1424
1425        let field_spec = fake_field_spec(2, Type::Number, "don't append unit");
1426        assert_eq!("12345.68", fmt_raw_value(&field_spec, 1234568, false));
1427
1428        let field_spec = fake_field_spec(3, Type::Number, "don't append unit");
1429        assert_eq!("12345.679", fmt_raw_value(&field_spec, 12345679, false));
1430
1431        let field_spec = fake_field_spec(4, Type::Number, "don't append unit");
1432        assert_eq!("12345.6789", fmt_raw_value(&field_spec, 123456789, false));
1433
1434        let field_spec = fake_field_spec(4, Type::Number, "don't append unit");
1435        assert_eq!("12345.0009", fmt_raw_value(&field_spec, 123450009, false));
1436
1437        let field_spec = fake_field_spec(10, Type::Number, "don't append unit");
1438        assert_eq!(
1439            "1.2345678900",
1440            fmt_raw_value(&field_spec, 12345678900, false)
1441        );
1442
1443        let field_spec = fake_field_spec(10, Type::Time, "don't append unit");
1444        assert_eq!("12:01", fmt_raw_value(&field_spec, 721, true));
1445
1446        let field_spec = fake_field_spec(10, Type::WeekTime, "don't append unit");
1447        assert_eq!("Th,12:01", fmt_raw_value(&field_spec, 3 * 1440 + 721, true));
1448
1449        let field_spec = fake_field_spec(10, Type::DateTime, "don't append unit");
1450        assert_eq!(
1451            "22/12/2013 15:17:42",
1452            fmt_raw_value(&field_spec, 409418262, true)
1453        );
1454    }
1455
1456    #[test]
1457    fn test_fields_in_data_set() {
1458        let mut rr = RecordingReader::new(RECORDING_2);
1459
1460        let data_set = rr.read_data_set().unwrap().unwrap();
1461
1462        let spec_file = SpecificationFile::from_bytes(SPEC_FILE_1).unwrap();
1463
1464        let spec = Specification::from_file(spec_file, Language::En);
1465
1466        let fields = spec.fields_in_data_set(&data_set).collect::<Vec<_>>();
1467
1468        assert_eq!(8, fields.len());
1469
1470        let field = &fields[0];
1471        assert_eq!(1, field.data_index());
1472        assert_eq!(&data_set.as_data_slice()[1], field.data());
1473        assert_eq!("00_0010_7E31_10_0100", field.packet_spec().packet_id);
1474        assert_eq!(0, field.field_index());
1475        assert_eq!("000_4_0", field.field_spec().field_id);
1476        assert_eq!(Some(0f64), field.raw_value_f64());
1477        assert_eq!("0", format!("{}", field.fmt_raw_value(false)));
1478        assert_eq!("0 Wh", format!("{}", field.fmt_raw_value(true)));
1479
1480        let field = &fields[1];
1481        assert_eq!(1, field.data_index());
1482        assert_eq!(&data_set.as_data_slice()[1], field.data());
1483        assert_eq!("00_0010_7E31_10_0100", field.packet_spec().packet_id);
1484        assert_eq!(1, field.field_index());
1485        assert_eq!("008_4_0", field.field_spec().field_id);
1486        assert_eq!(Some(0f64), field.raw_value_f64());
1487        assert_eq!("0", format!("{}", field.fmt_raw_value(false)));
1488        assert_eq!("0 Wh", format!("{}", field.fmt_raw_value(true)));
1489
1490        let field = &fields[2];
1491        assert_eq!(1, field.data_index());
1492        assert_eq!(&data_set.as_data_slice()[1], field.data());
1493        assert_eq!("00_0010_7E31_10_0100", field.packet_spec().packet_id);
1494        assert_eq!(2, field.field_index());
1495        assert_eq!("012_4_0", field.field_spec().field_id);
1496        assert_eq!(Some(0f64), field.raw_value_f64());
1497        assert_eq!("0", format!("{}", field.fmt_raw_value(false)));
1498        assert_eq!("0 Wh", format!("{}", field.fmt_raw_value(true)));
1499
1500        let field = &fields[3];
1501        assert_eq!(1, field.data_index());
1502        assert_eq!(&data_set.as_data_slice()[1], field.data());
1503        assert_eq!("00_0010_7E31_10_0100", field.packet_spec().packet_id);
1504        assert_eq!(3, field.field_index());
1505        assert_eq!("020_4_0", field.field_spec().field_id);
1506        assert_eq!(Some(0f64), field.raw_value_f64());
1507        assert_eq!("0", format!("{}", field.fmt_raw_value(false)));
1508        assert_eq!("0 Wh", format!("{}", field.fmt_raw_value(true)));
1509
1510        let field = &fields[4];
1511        assert_eq!(1, field.data_index());
1512        assert_eq!(&data_set.as_data_slice()[1], field.data());
1513        assert_eq!("00_0010_7E31_10_0100", field.packet_spec().packet_id);
1514        assert_eq!(4, field.field_index());
1515        assert_eq!("016_4_0", field.field_spec().field_id);
1516        assert_eq!(Some(0f64), field.raw_value_f64());
1517        assert_eq!("0", format!("{}", field.fmt_raw_value(false)));
1518        assert_eq!("0 l", format!("{}", field.fmt_raw_value(true)));
1519
1520        let field = &fields[5];
1521        assert_eq!(1, field.data_index());
1522        assert_eq!(&data_set.as_data_slice()[1], field.data());
1523        assert_eq!("00_0010_7E31_10_0100", field.packet_spec().packet_id);
1524        assert_eq!(5, field.field_index());
1525        assert_eq!("024_4_0", field.field_spec().field_id);
1526        assert_eq!(Some(0f64), field.raw_value_f64());
1527        assert_eq!("0", format!("{}", field.fmt_raw_value(false)));
1528        assert_eq!("0 l", format!("{}", field.fmt_raw_value(true)));
1529
1530        let field = &fields[6];
1531        assert_eq!(1, field.data_index());
1532        assert_eq!(&data_set.as_data_slice()[1], field.data());
1533        assert_eq!("00_0010_7E31_10_0100", field.packet_spec().packet_id);
1534        assert_eq!(6, field.field_index());
1535        assert_eq!("028_4_0", field.field_spec().field_id);
1536        assert_eq!(Some(0f64), field.raw_value_f64());
1537        assert_eq!("0", format!("{}", field.fmt_raw_value(false)));
1538        assert_eq!("0 l", format!("{}", field.fmt_raw_value(true)));
1539    }
1540}