1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code, missing_docs)]
3#![no_std]
4
5use crc::{Crc, CRC_8_NRSC_5};
6use embedded_hal::i2c::{Operation, SevenBitAddress};
7#[allow(unused_imports)]
8use micromath::F32Ext;
9
10pub const I2C_ADDRESS: SevenBitAddress = 0x58;
12
13const GET_BASELINE_COMMAND: &[u8] = &[0x20, 0x15];
14const GET_FEATURE_SET_VERSION_COMMAND: &[u8] = &[0x20, 0x2f];
15const GET_SERIAL_ID_COMMAND: &[u8] = &[0x36, 0x82];
16const INIT_AIR_QUALITY_COMMAND: &[u8] = &[0x20, 0x03];
17const MEASURE_AIR_QUALITY_COMMAND: &[u8] = &[0x20, 0x08];
18const MEASURE_RAW_SIGNALS_COMMAND: &[u8] = &[0x20, 0x50];
19const RESET_COMMAND: &[u8] = &[0x00, 0x06];
20const SET_BASELINE_COMMAND: &[u8] = &[0x20, 0x1e];
21const SET_HUMIDITY_COMMAND: &[u8] = &[0x20, 0x61];
22
23#[derive(Debug)]
25pub enum Error<I2cE>
26where
27 I2cE: embedded_hal::i2c::Error,
28{
29 I2c(I2cE),
31 ChipNotDetected,
33 InvalidProduct,
36 FeatureNotSupported,
39 BadCrc,
41}
42
43impl<I2cE> From<I2cE> for Error<I2cE>
44where
45 I2cE: embedded_hal::i2c::Error,
46{
47 fn from(value: I2cE) -> Self {
48 Error::I2c(value)
49 }
50}
51
52#[derive(Clone, Copy, Debug, Default)]
54pub struct AirQuality {
55 pub co2: u16,
58 pub tvoc: u16,
60}
61
62#[derive(Clone, Copy, Debug, Default)]
64pub struct RawSignals {
65 pub ethanol: u16,
67 pub h2: u16,
69}
70
71#[derive(Debug)]
73pub struct Sgp30<I2C, D> {
74 address: SevenBitAddress,
75 delay: D,
76 i2c: I2C,
77 product_version: u8,
78}
79
80impl<I2C, D> Sgp30<I2C, D>
81where
82 I2C: embedded_hal::i2c::I2c,
83 D: embedded_hal::delay::DelayNs,
84{
85 pub fn get_baseline(&mut self) -> Result<AirQuality, Error<I2C::Error>> {
94 self.get_air_quality(GET_BASELINE_COMMAND, 10)
95 }
96
97 pub fn initialize_air_quality_measure(&mut self) -> Result<(), Error<I2C::Error>> {
106 self.i2c.write(self.address, INIT_AIR_QUALITY_COMMAND)?;
107 self.delay.delay_ms(10);
108 Ok(())
109 }
110
111 pub fn measure_air_quality(&mut self) -> Result<AirQuality, Error<I2C::Error>> {
122 self.get_air_quality(MEASURE_AIR_QUALITY_COMMAND, 12)
123 }
124
125 pub fn measure_raw_signals(&mut self) -> Result<RawSignals, Error<I2C::Error>> {
132 self.i2c.write(self.address, MEASURE_RAW_SIGNALS_COMMAND)?;
133 self.delay.delay_ms(25);
134 let mut h2 = [0u8; 2];
135 let mut h2_crc = [0u8; 1];
136 let mut ethanol = [0u8; 2];
137 let mut ethanol_crc = [0u8; 1];
138 let mut operations = [
139 Operation::Read(&mut h2),
140 Operation::Read(&mut h2_crc),
141 Operation::Read(&mut ethanol),
142 Operation::Read(&mut ethanol_crc),
143 ];
144 self.i2c.transaction(self.address, &mut operations)?;
145 Self::check_crc(&h2, h2_crc[0])?;
146 Self::check_crc(ðanol, ethanol_crc[0])?;
147 Ok(RawSignals {
148 h2: Self::get_u16_value(&h2),
149 ethanol: Self::get_u16_value(ðanol),
150 })
151 }
152
153 pub fn new(i2c: I2C, address: SevenBitAddress, delay: D) -> Result<Self, Error<I2C::Error>> {
155 let mut device = Self {
156 address,
157 delay,
158 i2c,
159 product_version: 0,
160 };
161
162 if device.get_serial_id().is_err() {
164 return Err(Error::ChipNotDetected);
165 }
166
167 let feature_set_version = device.get_feature_set_version()?;
169 let product_type = (feature_set_version & 0xf000) >> 12;
170 let product_version = (feature_set_version & 0x00ff) as u8;
171 if product_type != 0 || product_version == 0 {
172 return Err(Error::InvalidProduct);
173 }
174 device.product_version = product_version;
175
176 Ok(device)
177 }
178
179 pub fn reset(&mut self) -> Result<(), Error<I2C::Error>> {
181 self.i2c.write(self.address, RESET_COMMAND)?;
182 self.delay.delay_us(600); Ok(())
184 }
185
186 pub fn set_baseline(&mut self, baseline: AirQuality) -> Result<(), Error<I2C::Error>> {
196 let co2 = Self::get_u8_array_value(baseline.co2);
197 let co2_crc = [Self::calc_crc(&co2)];
198 let tvoc = Self::get_u8_array_value(baseline.tvoc);
199 let tvoc_crc = [Self::calc_crc(&tvoc)];
200 let mut operations = [
201 Operation::Write(SET_BASELINE_COMMAND),
202 Operation::Write(&tvoc),
203 Operation::Write(&tvoc_crc),
204 Operation::Write(&co2),
205 Operation::Write(&co2_crc),
206 ];
207 self.i2c.transaction(self.address, &mut operations)?;
208 self.delay.delay_ms(10);
209 Ok(())
210 }
211
212 pub fn set_humidity(&mut self, humidity: f32) -> Result<(), Error<I2C::Error>> {
221 if self.product_version < 0x20 {
222 return Err(Error::FeatureNotSupported);
223 }
224 let humidity = [
225 humidity.trunc() as u8,
226 (humidity.fract() * 256.0).trunc() as u8,
227 ];
228 let humidity_crc = [Self::calc_crc(&humidity)];
229 let mut operations = [
230 Operation::Write(SET_HUMIDITY_COMMAND),
231 Operation::Write(&humidity),
232 Operation::Write(&humidity_crc),
233 ];
234 self.i2c.transaction(self.address, &mut operations)?;
235 self.delay.delay_ms(10);
236 Ok(())
237 }
238
239 fn get_air_quality(
240 &mut self,
241 command: &[u8],
242 wait: u32,
243 ) -> Result<AirQuality, Error<I2C::Error>> {
244 self.i2c.write(self.address, command)?;
245 self.delay.delay_ms(wait);
246 let mut co2 = [0u8; 2];
247 let mut co2_crc = [0u8; 1];
248 let mut tvoc = [0u8; 2];
249 let mut tvoc_crc = [0u8; 1];
250 let mut operations = [
251 Operation::Read(&mut co2),
252 Operation::Read(&mut co2_crc),
253 Operation::Read(&mut tvoc),
254 Operation::Read(&mut tvoc_crc),
255 ];
256 self.i2c.transaction(self.address, &mut operations)?;
257 Self::check_crc(&co2, co2_crc[0])?;
258 Self::check_crc(&tvoc, tvoc_crc[0])?;
259 Ok(AirQuality {
260 co2: Self::get_u16_value(&co2),
261 tvoc: Self::get_u16_value(&tvoc),
262 })
263 }
264
265 fn get_feature_set_version(&mut self) -> Result<u16, Error<I2C::Error>> {
266 let mut feature_set_version = [0u8; 2];
267 let mut feature_set_version_crc = [0u8; 1];
268 let mut operations = [
269 Operation::Write(GET_FEATURE_SET_VERSION_COMMAND),
270 Operation::Read(&mut feature_set_version),
271 Operation::Read(&mut feature_set_version_crc),
272 ];
273 self.i2c.transaction(self.address, &mut operations)?;
274 Self::check_crc(&feature_set_version, feature_set_version_crc[0])?;
275 Ok(Self::get_u16_value(&feature_set_version))
276 }
277
278 fn get_serial_id(&mut self) -> Result<u64, Error<I2C::Error>> {
279 let mut id1 = [0u8; 2];
280 let mut id2 = [0u8; 2];
281 let mut id3 = [0u8; 2];
282 let mut id1_crc = [0u8; 1];
283 let mut id2_crc = [0u8; 1];
284 let mut id3_crc = [0u8; 1];
285 self.i2c.write(self.address, GET_SERIAL_ID_COMMAND)?;
286 self.delay.delay_us(500);
287 let mut operations = [
288 Operation::Read(&mut id1),
289 Operation::Read(&mut id1_crc),
290 Operation::Read(&mut id2),
291 Operation::Read(&mut id2_crc),
292 Operation::Read(&mut id3),
293 Operation::Read(&mut id3_crc),
294 ];
295 self.i2c.transaction(self.address, &mut operations)?;
296 Self::check_crc(&id1, id1_crc[0])?;
297 Self::check_crc(&id2, id2_crc[0])?;
298 Self::check_crc(&id3, id3_crc[0])?;
299 Ok((Self::get_u16_value(&id1) as u64) << 32
300 | (Self::get_u16_value(&id2) as u64) << 16
301 | (Self::get_u16_value(&id3) as u64))
302 }
303
304 fn calc_crc(data: &[u8; 2]) -> u8 {
305 let crc = Crc::<u8>::new(&CRC_8_NRSC_5);
306 let mut digest = crc.digest();
307 digest.update(data);
308 digest.finalize()
309 }
310
311 fn check_crc(data: &[u8; 2], expected_crc: u8) -> Result<(), Error<I2C::Error>> {
312 if Self::calc_crc(data) != expected_crc {
313 Err(Error::BadCrc)
314 } else {
315 Ok(())
316 }
317 }
318
319 #[inline]
320 fn get_u8_array_value(data: u16) -> [u8; 2] {
321 [(data >> 8) as u8, (data & 0xff) as u8]
322 }
323
324 #[inline]
325 fn get_u16_value(data: &[u8; 2]) -> u16 {
326 (data[0] as u16) << 8 | (data[1] as u16)
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use crate::*;
333 use embedded_hal_mock::eh1::delay::StdSleep as Delay;
334 use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
335
336 fn create_device() -> Sgp30<I2cMock, Delay> {
337 let expectations = [
338 I2cTransaction::write(I2C_ADDRESS, GET_SERIAL_ID_COMMAND.to_vec()),
339 I2cTransaction::transaction_start(I2C_ADDRESS),
340 I2cTransaction::read(I2C_ADDRESS, [0x01, 0x02].to_vec()),
341 I2cTransaction::read(I2C_ADDRESS, [0x17].to_vec()),
342 I2cTransaction::read(I2C_ADDRESS, [0x03, 0x04].to_vec()),
343 I2cTransaction::read(I2C_ADDRESS, [0x68].to_vec()),
344 I2cTransaction::read(I2C_ADDRESS, [0x05, 0x06].to_vec()),
345 I2cTransaction::read(I2C_ADDRESS, [0x50].to_vec()),
346 I2cTransaction::transaction_end(I2C_ADDRESS),
347 I2cTransaction::transaction_start(I2C_ADDRESS),
348 I2cTransaction::write(I2C_ADDRESS, GET_FEATURE_SET_VERSION_COMMAND.to_vec()),
349 I2cTransaction::read(I2C_ADDRESS, [0x00, 0x20].to_vec()),
350 I2cTransaction::read(I2C_ADDRESS, [0x07].to_vec()),
351 I2cTransaction::transaction_end(I2C_ADDRESS),
352 ];
353 let i2c = I2cMock::new(&expectations);
354 let mut device = Sgp30::new(i2c, I2C_ADDRESS, Delay {}).unwrap();
355 device.i2c.done();
356 device
357 }
358
359 #[test]
360 fn get_baseline() {
361 let expectations = [
362 I2cTransaction::write(I2C_ADDRESS, GET_BASELINE_COMMAND.to_vec()),
363 I2cTransaction::transaction_start(I2C_ADDRESS),
364 I2cTransaction::read(I2C_ADDRESS, [0x02, 0x76].to_vec()),
365 I2cTransaction::read(I2C_ADDRESS, [0x06].to_vec()),
366 I2cTransaction::read(I2C_ADDRESS, [0x02, 0xdd].to_vec()),
367 I2cTransaction::read(I2C_ADDRESS, [0x10].to_vec()),
368 I2cTransaction::transaction_end(I2C_ADDRESS),
369 ];
370 let mut device = create_device();
371 device.i2c.update_expectations(&expectations);
372 device.get_baseline().unwrap();
373 device.i2c.done();
374 }
375
376 #[test]
377 fn initialize_air_quality_measure() {
378 let expectations = [I2cTransaction::write(
379 I2C_ADDRESS,
380 INIT_AIR_QUALITY_COMMAND.to_vec(),
381 )];
382 let mut device = create_device();
383 device.i2c.update_expectations(&expectations);
384 device.initialize_air_quality_measure().unwrap();
385 device.i2c.done();
386 }
387
388 #[test]
389 fn measure_air_quality() {
390 let expectations = [
391 I2cTransaction::write(I2C_ADDRESS, MEASURE_AIR_QUALITY_COMMAND.to_vec()),
392 I2cTransaction::transaction_start(I2C_ADDRESS),
393 I2cTransaction::read(I2C_ADDRESS, [0x02, 0x76].to_vec()),
394 I2cTransaction::read(I2C_ADDRESS, [0x06].to_vec()),
395 I2cTransaction::read(I2C_ADDRESS, [0x02, 0xdd].to_vec()),
396 I2cTransaction::read(I2C_ADDRESS, [0x10].to_vec()),
397 I2cTransaction::transaction_end(I2C_ADDRESS),
398 ];
399 let mut device = create_device();
400 device.i2c.update_expectations(&expectations);
401 device.measure_air_quality().unwrap();
402 device.i2c.done();
403 }
404
405 #[test]
406 fn measure_raw_signals() {
407 let expectations = [
408 I2cTransaction::write(I2C_ADDRESS, MEASURE_RAW_SIGNALS_COMMAND.to_vec()),
409 I2cTransaction::transaction_start(I2C_ADDRESS),
410 I2cTransaction::read(I2C_ADDRESS, [0x00, 0x24].to_vec()),
411 I2cTransaction::read(I2C_ADDRESS, [0xc3].to_vec()),
412 I2cTransaction::read(I2C_ADDRESS, [0x01, 0x51].to_vec()),
413 I2cTransaction::read(I2C_ADDRESS, [0x3a].to_vec()),
414 I2cTransaction::transaction_end(I2C_ADDRESS),
415 ];
416 let mut device = create_device();
417 device.i2c.update_expectations(&expectations);
418 device.measure_raw_signals().unwrap();
419 device.i2c.done();
420 }
421
422 #[test]
423 fn reset() {
424 let expectations = [I2cTransaction::write(I2C_ADDRESS, RESET_COMMAND.to_vec())];
425 let mut device = create_device();
426 device.i2c.update_expectations(&expectations);
427 device.reset().unwrap();
428 device.i2c.done();
429 }
430
431 #[test]
432 fn set_baseline() {
433 let air_quality = AirQuality {
434 co2: 630,
435 tvoc: 733,
436 };
437 let expectations = [
438 I2cTransaction::transaction_start(I2C_ADDRESS),
439 I2cTransaction::write(I2C_ADDRESS, SET_BASELINE_COMMAND.to_vec()),
440 I2cTransaction::write(I2C_ADDRESS, [0x02, 0xdd].to_vec()),
441 I2cTransaction::write(I2C_ADDRESS, [0x10].to_vec()),
442 I2cTransaction::write(I2C_ADDRESS, [0x02, 0x76].to_vec()),
443 I2cTransaction::write(I2C_ADDRESS, [0x06].to_vec()),
444 I2cTransaction::transaction_end(I2C_ADDRESS),
445 ];
446 let mut device = create_device();
447 device.i2c.update_expectations(&expectations);
448 device.set_baseline(air_quality).unwrap();
449 device.i2c.done();
450 }
451
452 #[test]
453 fn set_humidity() {
454 let expectations = [
455 I2cTransaction::transaction_start(I2C_ADDRESS),
456 I2cTransaction::write(I2C_ADDRESS, SET_HUMIDITY_COMMAND.to_vec()),
457 I2cTransaction::write(I2C_ADDRESS, [0x09, 0x35].to_vec()),
458 I2cTransaction::write(I2C_ADDRESS, [0x72].to_vec()),
459 I2cTransaction::transaction_end(I2C_ADDRESS),
460 ];
461 let mut device = create_device();
462 device.i2c.update_expectations(&expectations);
463 device.set_humidity(9.21).unwrap();
464 device.i2c.done();
465 }
466}