Skip to main content

sen6x/
commands.rs

1#[cfg(any(feature = "embedded-hal", feature = "embedded-hal-async"))]
2use crate::types::*;
3use sen6x_macros::SenCmd;
4
5#[derive(Debug, PartialEq, SenCmd)]
6pub(crate) enum CommandId {
7    /// Starts a continuous measurement. After starting the measurement, it takes some time (~1.1s) until
8    /// the first measurement results are available. You could poll with the command Get Data Ready to check when
9    /// the results are ready to be read.
10    ///
11    /// *Note:* For SEN63C and SEN69C only: SEN63C and SEN69C are conditioning the CO2 sensor during the initial
12    /// 24 seconds after starting a measurement. As this process cannot be interrupted, the following limitations apply
13    /// during this period:
14    /// - You may stop the measurement if needed, but do not start it again until at least 24 seconds have
15    ///   passed to avoid a CO2-1 – CO2 Sensor Error.
16    /// - Do not stop the sensor and use the commands Perform Forced CO2 Recalibration, Set CO2 Sensor
17    ///   Automatic Self Calibration or Perform CO2 Sensor Factory Reset.
18    #[execution_time(50)]
19    #[send(allowed_in = [Idle], new_state = Measurement)]
20    StartContinuousMeasurement = 0x0021,
21    ///  Stops the measurement and returns to idle mode.
22    #[execution_time(1400)]
23    #[send(allowed_in = [Measurement], new_state = Idle)]
24    StopMeasurement = 0x0104,
25    /// This command can be used to check if new measurement results are ready to read. The data_ready flag is automatically reset after reading the measurement values.
26    #[execution_time(20)]
27    #[read(allowed_in = [Measurement], rx = DataReady)]
28    DataReady = 0x0202,
29    ///  Returns the measured values. The command data_ready can be used to check if new data is available since the last read operation. If no new data is available, the previous values will be returned. If no
30    /// data is available at all (e.g. measurement not running for at least one second), all values will be None
31    #[execution_time(20)]
32    #[read(allowed_in = [Measurement], rx = MeasuredValuesSen62)]
33    #[target(Sen62)]
34    #[alias(MeasuredValues)]
35    MeasuredValuesSen62 = 0x04A3,
36    ///  Returns the measured values. The command data_ready can be used to check if new data is available since the last read operation. If no new data is available, the previous values will be returned. If no
37    /// data is available at all (e.g. measurement not running for at least one second), all values will be None
38    #[execution_time(20)]
39    #[read(allowed_in = [Measurement], rx = MeasuredValuesSen63c)]
40    #[target(Sen63c)]
41    #[alias(MeasuredValues)]
42    MeasuredValuesSen63c = 0x0471,
43    ///  Returns the measured values. The command data_ready can be used to check if new data is available since the last read operation. If no new data is available, the previous values will be returned. If no
44    /// data is available at all (e.g. measurement not running for at least one second), all values will be None
45    #[execution_time(20)]
46    #[read(allowed_in = [Measurement], rx = MeasuredValuesSen65)]
47    #[target(Sen65)]
48    #[alias(MeasuredValues)]
49    MeasuredValuesSen65 = 0x0446,
50    ///  Returns the measured values. The command data_ready can be used to check if new data is available since the last read operation. If no new data is available, the previous values will be returned. If no
51    /// data is available at all (e.g. measurement not running for at least one second), all values will be None
52    #[execution_time(20)]
53    #[read(allowed_in = [Measurement], rx = MeasuredValuesSen66)]
54    #[target(Sen66)]
55    #[alias(MeasuredValues)]
56    MeasuredValuesSen66 = 0x0300,
57    ///  Returns the measured values. The command data_ready can be used to check if new data is available since the last read operation. If no new data is available, the previous values will be returned. If no
58    /// data is available at all (e.g. measurement not running for at least one second), all values will be None
59    #[execution_time(20)]
60    #[read(allowed_in = [Measurement], rx = MeasuredValuesSen68)]
61    #[target(Sen68)]
62    #[alias(MeasuredValues)]
63    MeasuredValuesSen68 = 0x0467,
64    ///  Returns the measured values. The command data_ready can be used to check if new data is available since the last read operation. If no new data is available, the previous values will be returned. If no
65    /// data is available at all (e.g. measurement not running for at least one second), all values will be None
66    #[execution_time(20)]
67    #[read(allowed_in = [Measurement], rx = MeasuredValuesSen69c)]
68    #[target(Sen69c)]
69    #[alias(MeasuredValues)]
70    MeasuredValuesSen69c = 0x04B5,
71    /// Returns the measured raw values. The command data_ready can be used to check if new
72    /// data is available since the last read operation. If no new data is available, the previous values will be returned.
73    /// If no data is available at all (e.g. measurement not running for at least one second), all values will be None
74    #[execution_time(20)]
75    #[read(allowed_in = [Measurement], rx = RawValuesSen62Sen63c)]
76    #[target(Sen62)]
77    #[target(Sen63c)]
78    #[alias(RawValues)]
79    RawValuesSen62Sen63c = 0x0492,
80    /// Returns the measured raw values. The command data_ready can be used to check if new
81    /// data is available since the last read operation. If no new data is available, the previous values will be returned.
82    /// If no data is available at all (e.g. measurement not running for at least one second), all values will be None
83    #[execution_time(20)]
84    #[read(allowed_in = [Measurement], rx = RawValuesSen65Sen68Sen69c)]
85    #[target(Sen65)]
86    #[target(Sen68)]
87    #[target(Sen69c)]
88    #[alias(RawValues)]
89    RawValuesSen65Sen68Sen69c = 0x0455,
90    /// Returns the measured raw values. The command data_ready can be used to check if new
91    /// data is available since the last read operation. If no new data is available, the previous values will be returned.
92    /// If no data is available at all (e.g. measurement not running for at least one second), all values will be None
93    #[execution_time(20)]
94    #[read(allowed_in = [Measurement], rx = RawValuesSen66)]
95    #[target(Sen66)]
96    #[alias(RawValues)]
97    RawValuesSen66 = 0x0405,
98    /// Returns the measured number concentration values. The command Get Data Ready can be used
99    /// to check if new data is available since the last read operation. If no new data is available, the previous values
100    /// will be returned. If no data is available at all (e.g. measurement not running for at least one second), all values
101    /// will be None
102    #[execution_time(20)]
103    #[read(allowed_in = [Measurement], rx = NumberConcentrationValues)]
104    NumberConcentrationValues = 0x0316,
105    /// This command allows to compensate temperature effects of the design-in at customer side by
106    /// applying custom temperature offsets to the ambient temperature.
107    #[execution_time(20)]
108    #[write(allowed_in = [Idle, Measurement], tx = TemperatureOffsetParameters)]
109    TemperatureOffsetParameters = 0x60B2,
110    /// This command allows to set custom temperature acceleration parameters of the RH/T engine. It
111    /// overwrites the default temperature acceleration parameters of the RH/T engine with custom values.
112    #[execution_time(20)]
113    #[write(allowed_in = [Idle], tx = TemperatureAccelerationParameters)]
114    TemperatureAccelerationParameters = 0x6100,
115    /// Gets the product name from the device
116    #[execution_time(20)]
117    #[read(allowed_in = [Measurement], rx = ProductName)]
118    ProductName = 0xD014,
119    ///  Gets the serial number from the device.
120    #[execution_time(20)]
121    #[read(allowed_in = [Idle, Measurement], rx = SerialNumber)]
122    SerialNumber = 0xD033,
123
124    /// Reads the current device status.
125    ///
126    /// Use this command to get detailed information about the device status. The device status is encoded in flags.
127    /// Each device status flag represents a single bit in a 32-bit integer value. If more than one error is present, the
128    /// device status register value is the sum of the corresponding flag values. For details about the available flags,
129    /// refer to the Device Status Register documentation.
130    ///
131    /// *Note*: The status flags of type "Error" are sticky, i.e. they are not cleared automatically even if the error condition
132    /// no longer exists. So, they can only be cleared manually with read_and_clear_device_status or through a reset,
133    /// either by calling Device Reset or through a power cycle. All other flags are not sticky, i.e. they are cleared
134    /// automatically if the trigger condition disappears
135    #[execution_time(20)]
136    #[read(allowed_in = [Idle, Measurement], rx = DeviceStatus)]
137    DeviceStatus = 0xD206,
138    /// Reads the current device status (like command Read device_status) and afterwards clears all flags
139    #[execution_time(20)]
140    #[read(allowed_in = [Idle, Measurement], rx = DeviceStatus)]
141    ReadAndClearDeviceStatus = 0xD210,
142    ///  Gets the version information for the firmware
143    #[execution_time(20)]
144    #[read(allowed_in = [Idle, Measurement], rx = Version)]
145    Version = 0xD100,
146    /// Executes a reset on the device. This has the same effect as a power cycle
147    #[execution_time(1200)]
148    #[send(allowed_in = [Idle])]
149    DeviceReset = 0xD304,
150    ///  This command triggers fan cleaning. The fan is set to the maximum speed for 10 seconds and
151    /// then automatically stopped. Wait at least 10s after this command before starting a measurement.
152    #[execution_time(20)]
153    #[send(allowed_in = [Idle])]
154    StartFanCleaning = 0x5607,
155    /// This command allows you to use the inbuilt heater in SHT sensor to reverse creep at high humidity.
156    /// This command activates the SHT sensor heater with 200mW for 1s. The heater is then automatically deactivated
157    /// again. For firmware versions with an Execution Time of 20ms in the table below, the Get SHT Heater
158    /// Measurements command can be polled to check whether the heating is finished to trigger another cycle to
159    /// maximize the duty cycle. Older firmware version do not yet support Get SHT Heater Measurements.
160    /// Wait at least 20s after this command before starting a measurement to get coherent temperature values
161    /// (heating consequence to disappear).
162    #[execution_time(20)] // FIXME: execution time should depend on Version, this is value for new sensors
163    #[send(allowed_in = [Idle])]
164    ActivateShtHeater = 0x6765,
165    /// Get the measurement values when the SHT sensor heating is finished.
166    /// *Note*: This command is only available from the Firmware Version specified in the table below. It must be used
167    /// after the Activate SHT Heater command. The command can be queried every 50ms to check if the heating
168    /// cycle is finished and measurements are available.
169    #[execution_time(20)] // FIXME: availability depend on Version
170    #[read(allowed_in = [Idle], rx = ShtHeaterMeasurements)]
171    ShtHeaterMeasurements = 0x6790,
172
173    /// Gets the parameters to customize the VOC algorithm. For more information on what the
174    /// parameters below do, refer to Sensirion’s
175    /// [VOC Index for Indoor Air Applications](https://sensirion.com/media/documents/02232963/6294E043/Info_Note_VOC_Index.pdf).
176    #[read(allowed_in = [Idle], rx = VocAlgorithmTuningParameters)]
177    ///  Sets the parameters to customize the VOC algorithm. It has no effect if at least one parameter is
178    /// outside the specified range. For more information on what the parameters below do, refer to Sensirion’s
179    /// [VOC Index for Indoor Air Applications](https://sensirion.com/media/documents/02232963/6294E043/Info_Note_VOC_Index.pdf).
180    #[write(allowed_in = [Idle], tx = VocAlgorithmTuningParameters)]
181    #[execution_time(20)]
182    #[target(Sen65)]
183    #[target(Sen66)]
184    #[target(Sen68)]
185    #[target(Sen69c)]
186    VocAlgorithmTuningParameters = 0x60D0,
187
188    /// Allows backup of the VOC algorithm state to resume operation after a power cycle or device reset,
189    /// skipping initial learning phase. By default, the VOC Engine is reset, and the algorithm state is retained if a
190    /// measurement is stopped and started again. If the VOC algorithm state shall be reset, a device reset, or a power
191    /// cycle can be executed.
192    #[read(allowed_in = [Idle, Measurement], rx = VocAlgorithmState)]
193    /// Allows restoration of the VOC algorithm state to resume operation after a power cycle or device
194    /// reset, skipping initial learning phase. By default, the VOC Engine is reset, and the algorithm state is retained if
195    /// a measurement is stopped and started again. If the VOC algorithm state shall be reset, a device reset, or a
196    /// power cycle can be executed.
197    /// Sets the VOC algorithm state previously received with the Get VOC Algorithm State command. This command
198    /// is only available in idle mode and the state will be applied only once when starting the next measurement. In
199    /// measurement mode, this command has no effect.
200    #[write(allowed_in = [Idle], tx = VocAlgorithmState)]
201    #[execution_time(20)]
202    #[target(Sen65)]
203    #[target(Sen66)]
204    #[target(Sen68)]
205    #[target(Sen69c)]
206    VocAlgorithmState = 0x6181,
207
208    /// Gets the parameters to customize the NOx algorithm. For more information on what the
209    /// parameters below do, refer to Sensirion’s
210    /// [NOx Index for Indoor Air Applications](https://sensirion.com/media/documents/9F289B95/6294DFFC/Info_Note_NOx_Index.pdf).
211    #[read(allowed_in = [Idle], rx = NoxAlgorithmTuningParameters)]
212    /// Sets the parameters to customize the NOx algorithm. It has no effect if at least one parameter is
213    /// outside the specified range. To check whether the parameters have been set successfully, use the Get NOx
214    /// Algorithm Tuning Parameters command. For more information on what the parameters below do, refer to
215    /// Sensirion’s [NOx Index for Indoor Air Applications](https://sensirion.com/media/documents/9F289B95/6294DFFC/Info_Note_NOx_Index.pdf).
216    #[write(allowed_in = [Idle], tx = NoxAlgorithmTuningParameters)]
217    #[execution_time(20)]
218    #[target(Sen65)]
219    #[target(Sen66)]
220    #[target(Sen68)]
221    #[target(Sen69c)]
222    NoxAlgorithmTuningParameters = 0x60E1,
223
224    //TODO: implement calibration isntead of exposing raw method
225    /// Execute the forced recalibration (FRC) of the CO2 signal. To successfully conduct an accurate FRC,
226    /// the following steps need to be taken:
227    /// 1. Start a measurement with the command Start Continuous Measurement and operate the sensor for
228    ///    at least 3 minutes in an environment with homogenous and constant CO2 concentration. If applicable,
229    ///    the reference value for altitude or pressure compensation must be provided to the sensor beforehand
230    ///    with the command Set Sensor Altitude or Set Ambient Pressure respectively.
231    /// 2. Stop the measurement with the command Stop Measurement and wait at least 1400ms.
232    /// 3. Issue the Perform Forced CO2 Recalibration command with the reference CO2 concentration that the
233    ///    sensor should be set to. The recalibration procedure will take about 500 ms to complete, during which
234    ///    time no other functions can be executed. A return value of 0xFFFF indicates that the FRC has failed
235    #[execution_time(500)]
236    #[fetch(allowed_in = [Idle], rx = Co2Correction, tx = PpmU16 )]
237    #[target(Sen63c)]
238    #[target(Sen66)]
239    #[target(Sen69c)]
240    PerformForcedCo2Recalibration = 0x6707,
241
242    // FIXME: availability depend on Version
243    /// This command resets all CO2 sensor configuration settings stored in the EEPROM and erases the
244    /// forced recalibration (FRC) and automatic self-calibration (ASC) algorithm history of the CO2 sensor, restarting
245    /// the bypass phase. Refer to the [STCC4 datasheet](https://sensirion.com/resource/datasheet/STCC4) for more information.
246    #[execution_time(1400)]
247    #[send(allowed_in = [Idle])]
248    #[target(Sen63c)]
249    #[target(Sen66)]
250    #[target(Sen69c)]
251    PerformCo2SensorFactoryReset = 0x6754,
252
253    /// Gets the status of the CO2 sensor automatic self-calibration (ASC). The CO2 sensor supports
254    /// automatic self-calibration (ASC) for long-term stability of the CO2 output. This feature can be enabled or
255    /// disabled. By default, it is enabled.
256    #[read(allowed_in = [Idle], rx = bool)]
257    /// Sets the status of the CO2 sensor automatic self-calibration (ASC). The CO2 sensor supports
258    /// automatic self-calibration (ASC) for long-term stability of the CO2 output. This feature can be enabled or
259    /// disabled. By default, it is enabled.
260    /// The automatic self-calibration can be disabled for testing under lab conditions where concentrations below
261    /// 400ppm are expected, to avoid an alteration of the baseline. In the field, ASC must be enabled and exposure
262    /// to fresh air (i.e. CO2 concentration at 400 ppm) at least once per week is required to reach datasheet
263    /// specifications
264    #[write(allowed_in = [Idle], tx = bool)]
265    #[execution_time(20)]
266    #[target(Sen63c)]
267    #[target(Sen66)]
268    #[target(Sen69c)]
269    Co2SensorAutomaticSelfCalibration = 0x6711,
270
271    /// Gets the ambient pressure value that was set with Set Ambient Pressure. The ambient pressure
272    /// can be used for pressure compensation in the CO2 sensor
273    #[read(allowed_in = [Idle, Measurement], rx = Hpa)]
274    /// Sets the ambient pressure value. The ambient pressure can be used for pressure compensation in
275    /// the CO2 sensor. Setting an ambient pressure overrides any pressure compensation based on a previously set
276    /// sensor altitude. Use of this command is recommended for applications experiencing significant ambient
277    /// pressure changes to ensure CO2 sensor accuracy. Valid input values are between 700 to 1’200 hPa. The default
278    /// value is 1013 hPa.
279    #[write(allowed_in = [Idle, Measurement], tx = Hpa)]
280    #[execution_time(20)]
281    #[target(Sen63c)]
282    #[target(Sen66)]
283    #[target(Sen69c)]
284    AmbientPressure = 0x6720,
285
286    /// Gets the current sensor altitude. The sensor altitude can be used for pressure compensation in
287    /// the CO2 sensor.
288    #[read(allowed_in = [Idle], rx = Meters)]
289    /// Sets the current sensor altitude. The sensor altitude can be used for pressure compensation in the
290    /// CO2 sensor. The default sensor altitude value is set to 0 meters above sea level. Valid input values are between
291    /// 0 and 3000m.
292    #[write(allowed_in = [Idle], tx = Meters)]
293    #[execution_time(20)]
294    #[target(Sen63c)]
295    #[target(Sen66)]
296    #[target(Sen69c)]
297    SensorAltitude = 0x6736,
298}
299
300#[cfg(test)]
301mod tests {
302    use crate::errors::Error;
303    use crate::io::{FromBytes, ToBytes};
304    use crate::types::DataReady;
305    use crate::types::SerialNumber;
306
307    //TODO: generate only needed traits!
308    #[test]
309    fn parse_data_ready() {
310        assert_eq!(
311            DataReady::from_bytes_with_crc::<u32>(&[0x00, 0x02, 0x00]),
312            Err(Error::Crc)
313        );
314        assert_eq!(
315            DataReady::from_bytes_with_crc::<u32>(&[
316                0x00,
317                0x00,
318                sensirion_i2c::crc8::calculate(&[0x00, 0x00])
319            ]),
320            Ok(DataReady { data_ready: false })
321        );
322        assert_eq!(
323            DataReady::from_bytes_with_crc::<u32>(&[
324                0x00,
325                0x01,
326                sensirion_i2c::crc8::calculate(&[0x00, 0x01])
327            ]),
328            Ok(DataReady { data_ready: true })
329        );
330        assert_eq!(
331            DataReady::from_bytes_with_crc::<u32>(&[
332                0x00,
333                0x02,
334                sensirion_i2c::crc8::calculate(&[0x00, 0x02])
335            ]),
336            Err(Error::InvalidValue)
337        );
338    }
339
340    #[test]
341    fn parse_serial() {
342        let raw_serial: [u8; 32] = [
343            0x34, 0x32, 0x34, 0x35, 0x39, 0x39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
344            0, 0, 0, 0, 0, 0, 0, 0, 0,
345        ];
346        let mut raw_data = [0u8; 48];
347        for i in 0..raw_serial.len() / 2 {
348            raw_data[i * 3] = raw_serial[i * 2];
349            raw_data[i * 3 + 1] = raw_serial[i * 2 + 1];
350            raw_data[i * 3 + 2] = sensirion_i2c::crc8::calculate(&raw_data[i * 3..i * 3 + 2]);
351        }
352
353        assert_eq!(
354            SerialNumber::from_bytes_with_crc::<u32>(&raw_data),
355            Ok(SerialNumber::new("424599"))
356        );
357    }
358
359    #[test]
360    fn emit_data_ready() {
361        assert_eq!(
362            DataReady { data_ready: false }.to_bytes(),
363            [0x00, 0x00, sensirion_i2c::crc8::calculate(&[0x00, 0x00])]
364        );
365        assert_eq!(
366            DataReady { data_ready: true }.to_bytes(),
367            [0x00, 0x01, sensirion_i2c::crc8::calculate(&[0x00, 0x01])]
368        );
369    }
370}