ens160_aq/
lib.rs

1#![no_std]
2#![allow(dead_code)]
3#![allow(unused_variables)]
4
5pub mod error;
6
7use crate::error::Error;
8
9pub mod data;
10use crate::data::ENS160Command;
11use crate::data::OperationMode;
12
13use data::Measurements;
14use data::{AirQualityIndex, Status, ECO2};
15
16pub mod constants;
17
18use crate::constants::DeviceAddress::{Primary, Secondary};
19
20#[allow(unused_imports)]
21use crate::constants::{
22    ENS160_COMMAND, ENS160_CONFIG, ENS160_DATA_AQI, ENS160_DATA_ECO2, ENS160_DATA_ETOH,
23    ENS160_DATA_MISR, ENS160_DATA_RH, ENS160_DATA_T, ENS160_DATA_TVOC, ENS160_DEVICE_STATUS,
24    ENS160_GPR_READ, ENS160_GPR_WRITE, ENS160_GRP_READ6, ENS160_OPMODE, ENS160_PART_ID,
25    ENS160_RH_IN, ENS160_TEMP_IN,
26};
27
28#[cfg(not(feature = "async"))]
29use embedded_hal::{i2c::I2c, delay::DelayNs};
30#[cfg(feature = "async")]
31use embedded_hal_async::{i2c::I2c as AsyncI2c, delay::DelayNs as AsyncDelayNs};
32//  use embedded_hal::delay::DelayNs;
33//  use embedded_hal::i2c::{I2c, SevenBitAddress};
34
35use libm::{powf, truncf};
36use log::{debug, info};
37
38/// Default I²C address, ADDR pin low
39/// which is default depends on actual ENS160 board
40/// the IC itself requires the ADDR to NOT be left open
41//const DEFAULT_ADDRESS: u8 = 0x52;
42/// the sensor's secondary address ['SECONDARY_ADDRESS']), ADDR pin high
43//const SECONDARY_ADDRESS: u8 = 0x53;
44
45/// the ENS160 device
46pub struct Ens160<I2C, D> {
47    /// I²C interface
48    i2c: I2C,
49
50    /// I²C device address
51    address: u8,
52    delayer: D,
53}
54
55#[cfg(not(feature = "async"))]
56impl<I2C, D, E> Ens160<I2C, D>
57where
58    I2C: I2c<Error = E>,
59    D: DelayNs,
60{
61    /// create new ENS160 driver with default I2C address: ADDR pin low
62    pub fn new(i2c: I2C, delayer: D) -> Self {
63        debug!("new called");
64        Self {
65            i2c,
66            address: Primary.into(),
67            delayer,
68        }
69    }
70    
71    /// create new ENS160 driver with secondary I2C address: ADDR pin high
72    pub fn new_secondary_address(i2c: I2C, delayer: D) -> Self {
73        Self {
74            i2c,
75            address: Secondary.into(),
76            delayer,
77        }
78    }
79    
80    /// give back the I2C interface
81    pub fn release(self) -> I2C {
82        self.i2c
83    }
84}
85
86#[cfg(feature = "async")]
87impl<I2C, D, E> Ens160<I2C, D>
88where
89    I2C: AsyncI2c<Error = E>,
90    D: AsyncDelayNs,
91{
92    /// create new ENS160 driver with default I2C address: ADDR pin low
93    pub fn new(i2c: I2C, delayer: D) -> Self {
94        debug!("new called");
95        Self {
96            i2c,
97            address: Primary.into(),
98            delayer,
99        }
100    }
101    
102    /// create new ENS160 driver with secondary I2C address: ADDR pin high
103    pub fn new_secondary_address(i2c: I2C, delayer: D) -> Self {
104        Self {
105            i2c,
106            address: Secondary.into(),
107            delayer,
108        }
109    }
110    
111    /// give back the I2C interface
112    pub fn release(self) -> I2C {
113        self.i2c
114    }
115}
116
117#[maybe_async_cfg::maybe(
118    sync(
119        cfg(not(feature = "async")),
120        self = "Ens160",
121        idents(AsyncI2c(sync = "I2c"), AsyncDelayNs(sync = "DelayNs"))
122    ),
123    async(feature = "async", keep_self)
124)]
125
126
127impl<I2C, D, E> Ens160<I2C, D>
128where
129    I2C: AsyncI2c<Error = E>,
130    D: AsyncDelayNs,
131{
132
133    // command_buf is an u8 array that starts with command byte followed by command data byte(s)
134    async fn write_command<const N: usize>(
135        &mut self,
136        command_buf: [u8; N],
137    ) -> Result<(), Error<E>> {
138        // debug!("write_command : {:#?}", command_buf);
139        self.i2c
140            .write(self.address, &command_buf).await
141            .map_err(Error::I2c)
142    }
143
144    async fn read_register(
145        &mut self,
146        register_address: u8,
147        buffer: &mut [u8],
148    ) -> Result<(), Error<E>> {
149        let mut command_buffer = [0u8; 1];
150        command_buffer[0] = register_address;
151        // let mut result_buffer = [0u8; N];
152        self.i2c
153            .write_read(self.address, &command_buffer, buffer).await
154            .map_err(Error::I2c)?;
155        Ok(())
156    }
157
158    /// set operating mode:  deep sleep, idle, normal operation or reset
159    /// reset puts the ENS160 into initial start mode for an hour and it still will persist
160    /// until 24 hours of continuous power on.  
161    pub async fn set_operation_mode(
162        &mut self,
163        mode: OperationMode,
164    ) -> Result<OperationMode, Error<E>> {
165        debug!("setting ens160 operation mode to {:#?}", mode);
166        self.write_command([ENS160_OPMODE, mode as u8]).await?;
167        self.delayer.delay_ms(50).await;
168        let mut result_buf: [u8; 1] = [0; 1];
169        self.read_register(ENS160_OPMODE, &mut result_buf).await?;
170        return Ok(OperationMode::from(result_buf[0]));
171    }
172
173    /// Returns ENS160 part ID, expect 0x0160
174    pub async fn get_part_id(&mut self) -> Result<u16, Error<E>> {
175        let mut result_buf = [0; 2];
176        self.read_register(ENS160_PART_ID, &mut result_buf[0..2]).await?;
177        //   .map(u16::from_le_bytes) // ENS160 returns little endian data
178
179        Ok(u16::from_le_bytes(result_buf))
180    }
181
182    /// Gets ENS160 firmware version (this library was tested with 5.4.6)
183    pub async fn get_firmware_version(&mut self) -> Result<(u8, u8, u8), Error<E>> {
184        self.write_command([ENS160_COMMAND, ENS160Command::GetAppVersion as u8]).await?;
185        let mut result_buf: [u8; 8] = [0; 8];
186        self.read_register(ENS160_GPR_READ, &mut result_buf).await?;
187        Ok((result_buf[4], result_buf[5], result_buf[6]))
188    }
189
190    /// Clears group data registers
191    pub async fn clear_command(&mut self) -> Result<(), Error<E>> {
192        self.write_command([ENS160_COMMAND, ENS160Command::Nop as u8]).await?;
193        self.write_command([ENS160_COMMAND, ENS160Command::ClearGPR as u8]).await?;
194        Ok(())
195    }
196
197    /// Gets Equivalent Carbon Dioxide  measurement from the sensor in ppm, returns ECO2 enum.
198    pub async fn get_eco2(&mut self) -> Result<ECO2, Error<E>> {
199        let mut result_buf = [0; 2];
200        self.read_register(ENS160_DATA_ECO2, &mut result_buf).await?;
201        // debug!("eco2 u16 = {:#?}", result_buf);
202        let eco2 = u16::from_le_bytes(result_buf);
203        // debug("eco2 u16 = {:#04x}", eco2);
204        Ok(ECO2::from(eco2))
205    }
206
207    /// Get Total Volitaile organic compounds in ppb.  No range for indexing given in data sheet
208    pub async fn get_tvoc(&mut self) -> Result<u16, Error<E>> {
209        let mut result_buf = [0; 2];
210        self.read_register(ENS160_DATA_TVOC, &mut result_buf).await?;
211        Ok(u16::from_le_bytes(result_buf))
212        //.map(u16::from_le_bytes)
213    }
214
215    /// Gets Air Quality Index value from sensor.
216    /// The air quality index value is matched to the AirQualityIndex enum (resultant)
217    pub async fn get_airquality_index(&mut self) -> Result<AirQualityIndex, Error<E>> {
218        let mut result_buf = [0; 1];
219        self.read_register(ENS160_DATA_AQI, &mut result_buf).await?;
220        debug!(" read ENS160_DATA_AQI result is {}", result_buf[0]);
221        Ok(AirQualityIndex::from(result_buf[0]))
222    }
223
224    /// get ethanol concentration in ppb
225    pub async fn get_etoh(&mut self) -> Result<u16, Error<E>> {
226        let mut result_buf: [u8; 2] = [0; 2];
227        self.read_register(ENS160_DATA_ETOH, &mut result_buf).await?;
228        Ok(u16::from_le_bytes(result_buf))
229    }
230
231    /// get raw resistance value which can be used for custom calulations, in ohms
232    pub async fn get_raw_resistance(&mut self) -> Result<f32, Error<E>> {
233        let mut result_buf: [u8; 2] = [0; 2];
234        self.read_register(ENS160_GRP_READ6, &mut result_buf).await?;
235        // convert to ohm, see datasheet section 7
236        let exponent: f32 = u16::from_le_bytes(result_buf) as f32;
237        //debug!("raw resistance before conversion {}", exponent);
238        let resistance = powf(2.0, exponent / 2048.0);
239        Ok(resistance)
240    }
241
242    /// get ENS160 status flags
243    pub async fn get_status(&mut self) -> Result<Status, Error<E>> {
244        let mut result_buf = [0; 1];
245        self.read_register(ENS160_DEVICE_STATUS, &mut result_buf).await?;
246        //debug!(" raw ens160 status byte is {:#04x}", result_buf[0]);
247        Ok(Status(result_buf[0]))
248    }
249
250    /// read ENS160 group data
251    pub async fn get_group_data(&mut self) -> Result<[u8; 8], Error<E>> {
252        let mut result_buf: [u8; 8] = [0; 8];
253        self.read_register(ENS160_GPR_READ, &mut result_buf).await?;
254        // debug!(" group register read results are {:#?}", result_buf);
255        Ok(result_buf)
256    }
257    /// set the temperature in degrees C and relative humdity in percent for compensation calculation
258    pub async fn set_temp_rh_comp(
259        &mut self,
260        temp_c: f32,
261        rh_percent: u16,
262    ) -> Result<(), Error<E>> {
263        let mut buffer: [u8; 2];
264        let temp_val: u16 = truncf((temp_c + 273.15) * 64.0) as u16; // to Kelvin and scale it
265                                                                     //info!("setting temp comp to {:#04x}", temp_val.to_le());
266        buffer = temp_val.to_le_bytes(); // ???? or is it be
267        self.write_command([ENS160_TEMP_IN, buffer[0], buffer[1]]).await?;
268
269        buffer = rh_percent.to_le_bytes();
270        //debug!("setting rh comp to {:#04x} {:#04x}", buffer[0], buffer[1]);
271        self.write_command([ENS160_RH_IN, buffer[0], buffer[1]]).await?;
272
273        Ok(())
274    }
275
276    pub async fn get_temp_rh_comp(&mut self) -> Result<(f32, u16), Error<E>> {
277        let mut result_buf: [u8; 2] = [0; 2];
278        self.read_register(ENS160_DATA_T, &mut result_buf).await?;
279        let value: u16 = u16::from_le_bytes(result_buf);
280        let temp_comp_c = ((value as f32) / 64.0) - 273.15;
281        //debug!("temp c compensation is {}", temp_comp_c);
282
283        self.read_register(ENS160_DATA_RH, &mut result_buf).await?;
284        let rh: u16 = u16::from_le_bytes(result_buf);
285        //debug!("read rh back as {}", rh);
286        Ok((temp_comp_c, rh))
287    }
288
289    /// configure the interrupt pin of ENS160.  See data sheet for config:u8 parameter
290    /// or use the handy InterruptPinConfig::builder() and its function to generate the
291    /// config:u8 parameter for you.
292    /// returns the ENS160 config register read back (should equal value written)
293    pub async fn config_interrupt_pin(&mut self, config: u8) -> Result<u8, Error<E>> {
294        self.write_command([ENS160_CONFIG, config]).await?;
295        let mut result_buf: [u8; 1] = [0; 1];
296        self.read_register(ENS160_CONFIG, &mut result_buf).await?;
297        Ok(result_buf[0])
298    }
299
300    /// initialize the ENS160 device
301    pub async fn initialize(&mut self) -> Result<bool, Error<E>> {
302        //self.reset()?;  NO, this will put ENS160 back to factory defaults including InitialStartUp 24 hours
303        // self.set_operation_mode(OperationMode::Reset)?;
304        //self.delayer.delay_ms(250);
305        self.set_operation_mode(OperationMode::Idle).await?;
306        //self.idle_mode()?;
307        let the_status = self.get_status().await?;
308        debug!(
309            " command to idle, ENS160 status is {:#?}", the_status );
310        if let Ok(part_id) = self.get_part_id().await {
311            if part_id != 0x0160 {
312                Err(Error::UnexpectedChipId(part_id as u16))
313            } else {
314                info!("ENS160 part id is good {}", part_id);
315                self.delayer.delay_ms(50).await;
316                self.clear_command().await?;
317                let the_status = self.get_status().await?;
318                debug!(" command to clear grp data, ENS160 status is {:#?}", the_status );
319                self.delayer.delay_ms(50).await;
320                let (fw_major, fw_minor, fw_build) = self.get_firmware_version().await?;
321                info!("firmware version {}.{}.{}", fw_major, fw_minor, fw_build);
322                self.delayer.delay_ms(10).await;
323                // self.standard_mode()?;
324                let new_mode = self.set_operation_mode(OperationMode::Standard).await?;
325                if new_mode != OperationMode::Standard {
326                    return Err(Error::OpModeNotCorrect(new_mode as u8));
327                }
328                self.delayer.delay_ms(150).await;
329                let the_status = self.get_status().await?;
330                debug!(" command to std mode, ENS160 status is {:#?}", the_status );
331                // read opmode register
332                let mut result_buf: [u8; 1] = [0; 1];
333                self.read_register(ENS160_OPMODE, &mut result_buf).await?;
334                debug!("opmode read is {:#04x}", result_buf[0]);
335                Ok(true)
336            }
337        } else {
338            Ok(false)
339        }
340    }
341
342    /// get all measurements from sensor
343    pub async fn get_measurements(&mut self) -> Result<Measurements, Error<E>> {
344        let eco2 = self.get_eco2().await?;
345        let tvoc = self.get_tvoc().await?;
346        let aqi = self.get_airquality_index().await?;
347        let etoh = self.get_etoh().await?;
348        let raw_resistance = self.get_raw_resistance().await?;
349        let measurements: Measurements = Measurements {
350            co2eq_ppm: eco2,
351            tvoc_ppb: tvoc,
352            air_quality_index: aqi,
353            etoh,
354            raw_resistance,
355        };
356        Ok(measurements)
357    }
358
359    // Interrupt pin configuration
360}