sen66_interface/configuration/
tuning.rs

1use crate::{
2    error::DataError,
3    util::{check_deserialization, check_range},
4};
5
6/// Configuration for the VOC Index algorithm.
7#[derive(Debug, PartialEq)]
8pub struct VocTuning(Tuning);
9
10impl VocTuning {
11    /// Creates a new [`VocTuning`](VocTuning) Index configuration:
12    /// - `index_offset`: VOC Index representing typical conditions. Range: 1 - 250, Default: 100.
13    /// - `learning_time_offset`: Time constant to estimate the offset from the history in hours.
14    ///   After twice the learning time events are forgotten. Range: 1 - 1,000h, Default: 12h
15    /// - `learning_time_gain`: Time constant to estimate the gain from the history in hours.
16    ///   After twice the learning time events are forgotten. Range 1 - 1,000h, Default: 12h
17    /// - `gating_max_durations`: Maximum duration the estimator freezes on a high VOC index
18    ///   signal. Zero disables the gating. Range 0 - 3,000min, Default 180min.
19    /// - `initial_standard_deviation`: Initial estimate for the standard deviation. Range 10 -
20    ///   5,000, Default: 50.
21    /// - `gain_factor`: Factor to amplify/attunate the VOC index output. Range 1 - 1,000, Default:
22    ///   230.
23    ///
24    /// # Errors
25    ///
26    /// - [`ValueOutOfRange`](crate::error::DataError::ValueOutOfRange)`: If the values with scaling
27    ///   are not in range.
28    pub fn new(
29        index_offset: i16,
30        learning_time_offset: i16,
31        learning_time_gain: i16,
32        gating_max_durations: i16,
33        initial_standard_deviation: i16,
34        gain_factor: i16,
35    ) -> Result<Self, DataError> {
36        Ok(Self(Tuning::new(
37            index_offset,
38            learning_time_offset,
39            learning_time_gain,
40            gating_max_durations,
41            initial_standard_deviation,
42            gain_factor,
43        )?))
44    }
45}
46
47impl From<VocTuning> for [u16; 6] {
48    fn from(value: VocTuning) -> Self {
49        <[u16; 6]>::from(value.0)
50    }
51}
52
53impl TryFrom<&[u8]> for VocTuning {
54    type Error = DataError;
55
56    /// Parse the VOC tuning parameters from the received data.
57    ///
58    /// # Errors
59    ///
60    /// - [`CrcFailed`](crate::error::DataError::CrcFailed): If the received data CRC indicates
61    ///   corruption.
62    /// - [`ReceivedBufferWrongSize`](crate::error::DataError::ReceivedBufferWrongSize): If the
63    ///   received data buffer is not the expected size.
64    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
65        Ok(VocTuning(Tuning::try_from(data)?))
66    }
67}
68
69impl Default for VocTuning {
70    /// Creates a default [`VocTuning`](VocTuning) with:
71    /// - `index_offset`: 100
72    /// - `learning_time_offset`: 12h
73    /// - `learning_time_gain`: 12h
74    /// - `gating_max_durations`: 180min
75    /// - `initial_standard_deviation`: 50
76    /// - `gain_factor`: 230
77    fn default() -> Self {
78        Self(Tuning {
79            index_offset: 100,
80            learning_time_offset: 12,
81            learning_time_gain: 12,
82            gating_max_durations: 180,
83            initial_standard_deviation: 50,
84            gain_factor: 230,
85        })
86    }
87}
88
89/// Configuration for the NOx Index algorithm.
90#[derive(Debug, PartialEq)]
91pub struct NoxTuning(Tuning);
92
93impl NoxTuning {
94    /// Creates a new [`NoxTuning`](NoxTuning) Index configuration:
95    /// - `index_offset`: NOx Index representing typical conditions. Range 1 - 250, Default: 1.
96    /// - `learning_time_offset`: Time constant to estimate the offset from the history in hours.
97    ///   After twice the learning time events are forgotten. Range 1 - 1,000h, Default 12h.
98    /// - `learning_time_gain`: Time constant to estimate the gain from the history in hours.
99    ///   After twice the learning time events are forgotten. Range 1 - 1,000h, Default 12h.
100    /// - `gating_max_durations`: Maximum duration the estimator freezes on a high NOx index
101    ///   signal. Zero disables the gating. Range 0 - 3,000min, Default: 720min.
102    /// - `gain_factor`: Factor to amplify/attunate the NOx index output. Range 1 - 1,000, Default:
103    ///   230.
104    ///
105    /// # Errors
106    ///
107    /// - [`ValueOutOfRange`](crate::error::DataError::ValueOutOfRange)`: If the values with scaling
108    ///   are not in range.
109    pub fn new(
110        index_offset: i16,
111        learning_time_offset: i16,
112        learning_time_gain: i16,
113        gating_max_durations: i16,
114        gain_factor: i16,
115    ) -> Result<Self, DataError> {
116        Ok(Self(Tuning::new(
117            index_offset,
118            learning_time_offset,
119            learning_time_gain,
120            gating_max_durations,
121            50,
122            gain_factor,
123        )?))
124    }
125}
126
127impl From<NoxTuning> for [u16; 6] {
128    fn from(value: NoxTuning) -> Self {
129        <[u16; 6]>::from(value.0)
130    }
131}
132
133impl TryFrom<&[u8]> for NoxTuning {
134    type Error = DataError;
135
136    /// Parse the NOx tuning parameters from the received data.
137    ///
138    /// # Errors
139    ///
140    /// - [`CrcFailed`](crate::error::DataError::CrcFailed): If the received data CRC indicates
141    ///   corruption.
142    /// - [`ReceivedBufferWrongSize`](crate::error::DataError::ReceivedBufferWrongSize): If the
143    ///   received data buffer is not the expected size.
144    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
145        Ok(NoxTuning(Tuning::try_from(data)?))
146    }
147}
148
149impl Default for NoxTuning {
150    /// Creates a default [`NoxTuning`](NoxTuning) with:
151    /// - `index_offset`: 1
152    /// - `learning_time_offset`: 12h
153    /// - `learning_time_gain`: 12h
154    /// - `gating_max_durations`: 720min
155    /// - `initial_standard_deviation`: 50
156    /// - `gain_factor`: 230
157    fn default() -> Self {
158        Self(Tuning {
159            index_offset: 100,
160            learning_time_offset: 12,
161            learning_time_gain: 12,
162            gating_max_durations: 180,
163            initial_standard_deviation: 50,
164            gain_factor: 230,
165        })
166    }
167}
168
169#[derive(Debug, PartialEq)]
170struct Tuning {
171    index_offset: i16,
172    learning_time_offset: i16,
173    learning_time_gain: i16,
174    gating_max_durations: i16,
175    initial_standard_deviation: i16,
176    gain_factor: i16,
177}
178
179impl Tuning {
180    fn new(
181        index_offset: i16,
182        learning_time_offset: i16,
183        learning_time_gain: i16,
184        gating_max_durations: i16,
185        initial_standard_deviation: i16,
186        gain_factor: i16,
187    ) -> Result<Self, DataError> {
188        Ok(Self {
189            index_offset: check_range(index_offset, 1, 250, "VOC Index Offset", "")?,
190            learning_time_offset: check_range(
191                learning_time_offset,
192                1,
193                1_000,
194                "VOC Learning Time Offset",
195                "h",
196            )?,
197            learning_time_gain: check_range(
198                learning_time_gain,
199                1,
200                1_000,
201                "VOC Learning Time Gain",
202                "h",
203            )?,
204            gating_max_durations: check_range(
205                gating_max_durations,
206                0,
207                3_000,
208                "VOC Gating Max Duration",
209                "min",
210            )?,
211            initial_standard_deviation: check_range(
212                initial_standard_deviation,
213                10,
214                5_000,
215                "VOC Initial Standard Deviation",
216                "",
217            )?,
218            gain_factor: check_range(gain_factor, 1, 1_000, "VOC Gain Factor", "")?,
219        })
220    }
221}
222
223impl From<Tuning> for [u16; 6] {
224    fn from(value: Tuning) -> Self {
225        [
226            value.index_offset as u16,
227            value.learning_time_offset as u16,
228            value.learning_time_gain as u16,
229            value.gating_max_durations as u16,
230            value.initial_standard_deviation as u16,
231            value.gain_factor as u16,
232        ]
233    }
234}
235
236impl TryFrom<&[u8]> for Tuning {
237    type Error = DataError;
238
239    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
240        check_deserialization(data, 18)?;
241        Tuning::new(
242            i16::from_be_bytes([data[0], data[1]]),
243            i16::from_be_bytes([data[3], data[4]]),
244            i16::from_be_bytes([data[6], data[7]]),
245            i16::from_be_bytes([data[9], data[10]]),
246            i16::from_be_bytes([data[12], data[13]]),
247            i16::from_be_bytes([data[15], data[16]]),
248        )
249    }
250}