From ad17fe17678b623879120e8671eb2636f5b22f2b Mon Sep 17 00:00:00 2001
From: Francois <thiebolt@irit.fr>
Date: Mon, 7 Mar 2022 01:05:34 +0100
Subject: [PATCH] on way to Sensirion SDC4x i2c CO2+temp+hygro sensor

---
 .../libraries/neocampus_drivers/SCD4x.cpp     | 509 ++++++++++++++++++
 neosensor/libraries/neocampus_drivers/SCD4x.h | 163 ++++++
 .../libraries/neocampus_drivers/SHT3x.cpp     |   2 +-
 .../libraries/neocampus_modules/airquality.h  |   4 +-
 4 files changed, 675 insertions(+), 3 deletions(-)
 create mode 100644 neosensor/libraries/neocampus_drivers/SCD4x.cpp
 create mode 100644 neosensor/libraries/neocampus_drivers/SCD4x.h

diff --git a/neosensor/libraries/neocampus_drivers/SCD4x.cpp b/neosensor/libraries/neocampus_drivers/SCD4x.cpp
new file mode 100644
index 00000000..75789847
--- /dev/null
+++ b/neosensor/libraries/neocampus_drivers/SCD4x.cpp
@@ -0,0 +1,509 @@
+/**************************************************************************/
+/*! 
+    @file     SCD4x.h
+    @author   F. Thiebolt
+	  @license
+	
+    This is part of a the neOCampus drivers library.
+    SCD4x is an ultrasonic CO2 sensor that features both temperature and hygro
+    on an I2C interface
+    
+    Remember: all transfers are 16bits wide
+    
+    (c) Copyright 2022 Thiebolt.F <thiebolt@irit.fr>
+
+	@section  HISTORY
+
+    2022-March    - F.Thiebolt Initial release
+    
+*/
+/**************************************************************************/
+
+#include <Arduino.h>
+#include <limits.h>
+
+#include "neocampus.h"
+#include "neocampus_debug.h"
+#include "neocampus_i2c.h"
+
+// [may.20] debug
+#include "neocampus_utils.h"
+
+
+#include "SCD4x.h"
+
+
+
+/**************************************************************************/
+/*! 
+    @brief  Declare list of possible I2C addrs
+*/
+/**************************************************************************/
+const uint8_t SCD4x::i2c_addrs[] = { 0x69 };
+
+
+#if 0
+TO BE CONTINUED
+
+
+
+/**************************************************************************/
+/*! 
+    @brief  Test if device at 'adr' is really what we think of
+*/
+/**************************************************************************/
+boolean SHT3x::is_device( uint8_t a ) {
+  
+  // First step, parse all addresses
+  boolean found = false;
+  for( uint8_t i=0; i < sizeof(SHT3x::i2c_addrs); i++ ) {
+    if( SHT3x::i2c_addrs[i] == a) {
+      found = true;
+      break;
+    }
+  }
+  if( found == false ) return false;
+  
+  // check device identity
+  if( !_check_identity(a) ) return false;
+
+  // ... okay, we're probably what we expect ;)
+  return true;
+}
+
+
+/**************************************************************************/
+/*! 
+    @brief  Instantiates a new class
+*/
+/**************************************************************************/
+SHT3x::SHT3x( sht3xMeasureType_t kindness  ) : generic_driver() {
+  _i2caddr = -1;
+  _measureType = kindness;
+  _resolution = SHT3X_DEFL_RESOLUTION;
+}
+
+
+/* declare kind of units */
+const char *SHT3x::_t_units = "celsius";
+const char *SHT3x::_rh_units = "%r.H.";
+
+/* declare others static vars */
+unsigned long SHT3x::_lastMsRead  = 0;
+uint16_t SHT3x::_t_sensor   = (uint16_t)(-1);
+uint16_t SHT3x::_rh_sensor  = (uint16_t)(-1);
+
+
+/**************************************************************************/
+/*! 
+    @brief  send back units
+*/
+/**************************************************************************/
+const char * SHT3x::sensorUnits( uint8_t idx ) {
+  
+  switch( _measureType ) {
+    case sht3xMeasureType_t::temperature:
+      return _t_units;
+      break;
+    case sht3xMeasureType_t::humidity:
+      return _rh_units;
+      break;
+    default:
+      log_error(F("\n[SHT3x] unknown kind of measureType ?!?")); log_flush();
+      return nullptr;
+  }
+}
+
+
+/**************************************************************************/
+/*! 
+    @brief  Setups the HW
+*/
+/**************************************************************************/
+boolean SHT3x::begin( uint8_t addr=-1) {
+  // get i2caddr
+  if( (addr < (uint8_t)(I2C_ADDR_START)) or (addr > (uint8_t)(I2C_ADDR_STOP)) ) return false;
+  _i2caddr = addr;
+
+  // check device identity
+  if( !_check_identity(_i2caddr) ) return false;
+
+  /* set config:
+   * - nothing to configure
+   * - reset ?
+   */
+
+  // define defaults parameters
+  setResolution( _resolution );
+
+  /* start lastmsg time measurement.
+   * This way, we get sure to have at least a first acquisition! */
+  _lastMsRead = ULONG_MAX/2;
+
+  return true;
+}
+
+
+/*
+ * Power modes: ON or powerOFF
+ */
+void SHT3x::powerOFF( void )
+{
+  // device does not feature continuous integration so nothing to start or stop
+}
+
+void SHT3x::powerON( void )
+{
+  // device does not feature continuous integration so nothing to start or stop
+}
+
+
+/**************************************************************************/
+/*! 
+    @brief  Setups the HW: set resolution of sensor
+    @note   remember that resolution is virtual, we just set integration
+            timings, proper cmds for datat acquisition ought to get sent.
+*/
+/**************************************************************************/
+bool SHT3x::setResolution( sht3xResolution_t res ) {
+
+  // set integration timeout
+  if( res == sht3xResolution_t::high_res )
+    _integrationTime = static_cast<uint8_t>(sht3xIntegration_t::ms_integrate_high);
+  else if( res == sht3xResolution_t::medium_res )
+    _integrationTime = static_cast<uint8_t>(sht3xIntegration_t::ms_integrate_medium);
+  else if( res == sht3xResolution_t::low_res )
+    _integrationTime = static_cast<uint8_t>(sht3xIntegration_t::ms_integrate_low);
+  else return false;
+
+  // add some constant to integration time ...
+  _integrationTime+=(uint8_t)SHT3X_INTEGRATION_TIME_CTE;
+
+  // finish :)
+  return true;
+}
+
+
+/**************************************************************************/
+/*! 
+    @brief  Reads the 16-bit temperature register and returns the Centigrade
+            temperature as a float.
+
+*/
+/**************************************************************************/
+boolean SHT3x::acquire( float *pval )
+{
+  if( pval==nullptr ) return false;
+
+  // HUMIDITY
+  if( _measureType == sht3xMeasureType_t::humidity ) {
+    return getRH( pval );
+  }
+
+  // TEMPERATURE
+  if( !getTemp(pval) ) {
+    // error reading value ... too bad
+    return false;
+  }
+
+  // [Mar.18] temperature correction for last i2c sensor ... the one
+  // supposed to get tied to the main board.
+#ifdef TEMPERATURE_CORRECTION_LASTI2C
+  static uint8_t _last_i2c = (uint8_t)-1;
+  // determine last i2c addr
+  if( _last_i2c == (uint8_t)-1 ) {
+    _last_i2c=i2c_addrs[(sizeof(i2c_addrs)/sizeof(i2c_addrs[0]))-1];
+  }
+  // is last sensor ?
+  if ( _i2caddr == _last_i2c ) {
+    *pval += TEMPERATURE_CORRECTION_LASTI2C;
+    log_debug(F("\n[SHT3x] corrected temperature for i2c=0x"));log_debug(_i2caddr,HEX);log_flush();
+  }
+#endif
+
+  return true;
+}
+
+
+/*
+ * READ sensor's HUMIDITY
+ */
+boolean SHT3x::getRH( float *pval ) {
+
+  if( pval==nullptr ) return false;
+
+  bool res = false;
+  uint8_t retry = 3;
+  uint16_t val;
+  
+  while( res==false and retry-- ) {
+    res = _readSensor( &val );
+    if( res == true ) break;
+    delay(1);  // 1ms demay between commands
+  }
+
+  if( res==false ) {
+    // error ...
+    log_error(F("\n[SHT3x] unable to read value ?!?!"));log_flush();
+    return false;
+  }
+
+  uint32_t _tmp = (uint32_t)val;
+  // from Adafruit SHT31
+  // simplified (65536 instead of 65535) integer version of:
+  // humidity = (shum * 100.0f) / 65535.0f;
+  // 100.0 x _rh = val x 100 x 100 / ( 4096 x 16 )
+  _tmp = (625 * _tmp) >> 12;
+  *pval = (float)_tmp / 100.0f;
+  return true;
+}
+
+
+/*
+ * READ sensor's TEMPERATURE
+ */
+boolean SHT3x::getTemp( float *pval )
+{
+  if( pval==nullptr ) return false;
+
+  bool res = false;
+  uint8_t retry = 3;
+  uint16_t val;
+  
+  while( res==false and retry-- ) {
+    res = _readSensor( &val );
+    if( res == true ) break;
+    delay(1);  // 1ms demay between commands
+  }
+
+  if( res==false ) {
+    // error ...
+    log_error(F("\n[SHT3x] unable to read value ?!?!"));log_flush();
+    return false;
+  }
+
+  int32_t _tmp = (int32_t)val;
+  // from Adafruit SHT31
+  // simplified (65536 instead of 65535) integer version of:
+  // temp = (stemp * 175.0f) / 65535.0f - 45.0f;
+  // 100.0 x _tmp = (17500 x _tmp) / (16384 x 4) - 4500
+  _tmp = ((4375 * _tmp) >> 14) - 4500;
+  *pval = (float)_tmp / 100.0f;
+  return true;
+}
+
+
+/* ------------------------------------------------------------------------------
+ * Private methods and attributes
+ */
+
+/*
+ * Software reset through I2C command
+ */
+void SHT3x::sw_reset( uint8_t adr ) {
+  log_debug(F("\n[SHT3x] SOFT RESET action started ..."));log_flush();
+  _writeCmd( adr, static_cast<uint16_t>(sht3xCmd_t::soft_reset) );
+  delay( 50 );
+}
+
+
+/*
+ * Read 16bits sensor values registers and check CRCs
+ * Note: SHT3x sensors read both T and RH with both CRCs
+ * hence we store static values with a timestamp to avoid
+ * multiple (useless) access for same things.
+ */
+bool SHT3x::_readSensor( uint16_t *pval ) {
+
+  // do we need to acquire fresh sensors values ?
+  if( (millis() - _lastMsRead ) >= (unsigned long)(SHT3X_SENSOR_CACHE_MS) ) {
+
+    // select proper command
+    uint16_t _cmd;
+    if( _resolution == sht3xResolution_t::high_res )
+      _cmd = static_cast<uint16_t>(sht3xCmd_t::meas_highRes);
+    else if( _resolution == sht3xResolution_t::medium_res )
+      _cmd = static_cast<uint16_t>(sht3xCmd_t::meas_medRes);
+    else
+      _cmd = static_cast<uint16_t>(sht3xCmd_t::meas_lowRes);
+    
+    uint8_t _retry = 3;
+    bool status = false;
+    while( status == false and _retry-- ) {
+      // start acquisition command
+      _writeCmd( _i2caddr, _cmd );
+
+      // wait for integration
+      delay( _integrationTime );
+
+      // retrieve data:
+      //  MSB[T] + LSB[T] + CRC[T] + MSB[RH] + LSB[RH] + CRC[RH]
+      uint8_t buf[6];
+      if( readList_ll(_i2caddr, buf, sizeof(buf)) < sizeof(buf) ) {
+        log_error(F("\n[SHT2x] insufficient bytes answered"));log_flush();
+        yield();
+        sw_reset( _i2caddr );
+        continue;
+      }
+
+      // check first CRC ( TEMP )
+      if( not crc_check(buf,2,buf[2]) ) {
+        log_error(F("\n[SHT3x] invalid CRC for TEMP ...")); log_flush();
+        yield();
+        continue;
+      }
+
+      // check second CRC ( RH )
+      if( not crc_check(&buf[3],2,buf[5]) ) {
+        log_error(F("\n[SHT3x] invalid CRC for RH ...")); log_flush();
+        yield();
+        continue;
+      }
+
+      // both CRC are valid, let's grab the data
+      _t_sensor = buf[0] << 8;
+      _t_sensor |= buf[1];
+      _rh_sensor = buf[3] << 8;
+      _rh_sensor |= buf[4];
+
+      status = true;  // success
+    }
+    // check acquisition is ok
+    if( not status ) {
+      log_error(F("\n[SHT3x] unable to read sensor ...give up :("));log_flush();
+      return false;
+    }
+
+    // success ==> update last read
+    _lastMsRead = millis();
+  }
+  else {
+    log_debug(F("\n[SHT3x] using cached value for "));
+    if( _measureType == sht3xMeasureType_t::temperature ) {
+      log_debug(F("TEMP sensor!"));
+    }
+    else {
+      log_debug(F("RH sensor!"));
+    }
+    log_flush();
+  }
+
+  // send back pointer to proper value
+  if( _measureType == sht3xMeasureType_t::temperature ) *pval = _t_sensor;
+  else *pval = _rh_sensor;
+
+  return true;
+}
+
+
+/*
+ * CRC related attributes
+ */
+const uint8_t SHT3x::_crc8_polynom = 0x31;
+
+#ifdef SHT3X_CRC_LOOKUP_TABLE
+// CRC8 lookup table
+const uint8_t SHT3x::_crc8_table[256] PROGMEM = {
+0x00,0x31,0x62,0x53,0xC4,0xF5,0xA6,0x97,0xB9,0x88,0xDB,0xEA,0x7D,0x4C,0x1F,0x2E,
+0x43,0x72,0x21,0x10,0x87,0xB6,0xE5,0xD4,0xFA,0xCB,0x98,0xA9,0x3E,0x0F,0x5C,0x6D,
+0x86,0xB7,0xE4,0xD5,0x42,0x73,0x20,0x11,0x3F,0x0E,0x5D,0x6C,0xFB,0xCA,0x99,0xA8,
+0xC5,0xF4,0xA7,0x96,0x01,0x30,0x63,0x52,0x7C,0x4D,0x1E,0x2F,0xB8,0x89,0xDA,0xEB,
+0x3D,0x0C,0x5F,0x6E,0xF9,0xC8,0x9B,0xAA,0x84,0xB5,0xE6,0xD7,0x40,0x71,0x22,0x13,
+0x7E,0x4F,0x1C,0x2D,0xBA,0x8B,0xD8,0xE9,0xC7,0xF6,0xA5,0x94,0x03,0x32,0x61,0x50,
+0xBB,0x8A,0xD9,0xE8,0x7F,0x4E,0x1D,0x2C,0x02,0x33,0x60,0x51,0xC6,0xF7,0xA4,0x95,
+0xF8,0xC9,0x9A,0xAB,0x3C,0x0D,0x5E,0x6F,0x41,0x70,0x23,0x12,0x85,0xB4,0xE7,0xD6,
+0x7A,0x4B,0x18,0x29,0xBE,0x8F,0xDC,0xED,0xC3,0xF2,0xA1,0x90,0x07,0x36,0x65,0x54,
+0x39,0x08,0x5B,0x6A,0xFD,0xCC,0x9F,0xAE,0x80,0xB1,0xE2,0xD3,0x44,0x75,0x26,0x17,
+0xFC,0xCD,0x9E,0xAF,0x38,0x09,0x5A,0x6B,0x45,0x74,0x27,0x16,0x81,0xB0,0xE3,0xD2,
+0xBF,0x8E,0xDD,0xEC,0x7B,0x4A,0x19,0x28,0x06,0x37,0x64,0x55,0xC2,0xF3,0xA0,0x91,
+0x47,0x76,0x25,0x14,0x83,0xB2,0xE1,0xD0,0xFE,0xCF,0x9C,0xAD,0x3A,0x0B,0x58,0x69,
+0x04,0x35,0x66,0x57,0xC0,0xF1,0xA2,0x93,0xBD,0x8C,0xDF,0xEE,0x79,0x48,0x1B,0x2A,
+0xC1,0xF0,0xA3,0x92,0x05,0x34,0x67,0x56,0x78,0x49,0x1A,0x2B,0xBC,0x8D,0xDE,0xEF,
+0x82,0xB3,0xE0,0xD1,0x46,0x77,0x24,0x15,0x3B,0x0A,0x59,0x68,0xFF,0xCE,0x9D,0xAC
+};
+
+// crc checksum verification
+bool SHT3x::crc_check( uint8_t data[], uint8_t nb_bytes, uint8_t checksum ) {
+	uint8_t crc = 0xff;
+  while( nb_bytes ) {
+    crc = pgm_read_byte_near(_crc8_table + (*data ^ crc));
+    data++;
+    nb_bytes--;
+  }
+
+  if( (crc & 0xFF) != checksum ) return false;
+ 	else return true;
+}
+#else
+// crc checksum verification
+bool SHT3x::crc_check( uint8_t data[], uint8_t nb_bytes, uint8_t checksum ) {
+	uint8_t crc = 0xff;
+  uint8_t byteCtr;
+
+ 	//calculates 8-Bit checksum with given polynomial
+  for( byteCtr = nb_bytes; byteCtr; --byteCtr) {
+ 	  crc ^= *data++;
+ 	  for (uint8_t bit = 8; bit; --bit) {
+      crc = (crc & 0x80) ? (crc << 1) ^ _crc8_polynom : (crc << 1);
+ 	  }
+ 	}
+
+ 	if (crc != checksum) return false;
+ 	else return true;
+}
+#endif /* SHT3X_CRC_LOOKUP_TABLE */
+
+
+/*
+ * Check that device identity is what we expect!
+ */
+void SHT3x::_writeCmd( uint8_t a, uint16_t cmd ) {
+  write8( a, (uint8_t)(cmd>>8) , (uint8_t)(cmd&0xFF) );
+  delay(1); // 1ms min between i2c transactions
+}
+
+
+/*
+ * Check that device identity is what we expect!
+ */
+bool SHT3x::_check_identity( uint8_t a ) {
+
+  uint8_t _retry = 3;
+  bool status = false;
+
+  while( not status and _retry-- ) {
+    // cmd for read out status register
+    _writeCmd( a, static_cast<uint16_t>(sht3xCmd_t::get_status) );
+
+    // read answer (MSB + LSB + CRC)
+    uint8_t buf[3];
+    if( readList_ll(a, buf, sizeof(buf)) < sizeof(buf) ) {
+      log_error(F("\n[SHT3x] not enough bytes read !"));log_flush();
+      delay(1); // min time between commands
+      sw_reset( a );
+      continue;
+    }
+
+    // check answer's CRC
+    if( not crc_check(buf, 2, buf[2]) ) {
+      log_error(F("\n[SHT3x] CRC check failed !"));log_flush();
+      delay(1); // min time between commands
+      continue;
+    }
+  
+    // finally does status match our expectations
+    uint16_t status_reg = buf[0] << 8;
+    status_reg |= buf[1];
+    if( (status_reg & SHT3X_STATUS_REG_MASK) != SHT3X_STATUS_REG_DEFL ) return false;
+
+    status = true;
+  }
+  // check result
+  if( not status ) return false;
+
+  // additional tests here
+
+  return true;
+}
+
+#endif /* 0 */
diff --git a/neosensor/libraries/neocampus_drivers/SCD4x.h b/neosensor/libraries/neocampus_drivers/SCD4x.h
new file mode 100644
index 00000000..e6bfc129
--- /dev/null
+++ b/neosensor/libraries/neocampus_drivers/SCD4x.h
@@ -0,0 +1,163 @@
+/**************************************************************************/
+/*! 
+    @file     SCD4x.h
+    @author   F. Thiebolt
+	  @license
+	
+    This is part of a the neOCampus drivers library.
+    SCD4x is an ultrasonic CO2 sensor that features both temperature and hygro
+    on an I2C interface
+    
+    Remember: all transfers are 16bits wide
+    
+    (c) Copyright 2022 Thiebolt.F <thiebolt@irit.fr>
+
+	@section  HISTORY
+
+    2022-March    - F.Thiebolt Initial release
+    
+*/
+/**************************************************************************/
+
+#ifndef _SCD4X_H_
+#define _SCD4X_H_
+
+
+#include <Arduino.h>
+
+// generic sensor driver
+#include "generic_driver.h"
+
+
+#if 0
+TO BE CONTINUED
+
+
+/*
+ * Definitions
+ */
+/* SHT3x sensors send back both T and RH at the same time, but since
+ * we'll have both instances of this class ==> we implement a cache to
+ * avoid reading the sensors two times one for T then for RH */
+#define SHT3X_SENSOR_CACHE_MS       5000  // ms caches values validity
+
+// Enable CRC lookup table (regular computation otherwise)
+#ifndef SHT3X_CRC_LOOKUP_TABLE
+#define SHT3X_CRC_LOOKUP_TABLE    1
+#endif
+
+/* sht3x commands
+ * Note:
+ * - we won't make use of the 'Periodic Mode' features (i.e continuous measurement)
+ * - avoid stretch modes because measuremernt can take up to 16ms
+ * - useless heater
+ * - Repeatability is resolution
+ * - 1ms min. delay between two commands
+ * - both temperature and humidity are measured and sent in a single 6bytes frame
+ *      16bits T + CRC + 16bits RH + CRC
+ * - every 16bits frames are CRC protected
+ * - CRC8 0x31, initial=0xFF for 16bits data (first is )
+ */
+enum class sht3xCmd_t : uint16_t {
+  soft_reset                = 0X30A2,   // software reset
+  heater_enable             = 0x306D,   // on-chip heater enable
+  heater_disable            = 0x3066,   // on-chip heater disable
+  get_status                = 0xF32D,   // read Status Register
+  clear_status              = 0x3041,   // clear Status Register
+  meas_highRes_stretch      = 0x2C06,   // Measurement High Repeatability (resolution) with Clock Stretch Enabled
+  meas_medRes_stretch       = 0x2C0D,   // Measurement Medium Repeatability (resolution) with Clock Stretch Enabled
+  meas_lowRes_stretch       = 0x2C10,   // Measurement Low Repeatability (resolution) with Clock Stretch Enabled
+  meas_highRes              = 0x2400,   // Measurement High Repeatability (resolution) WITHOUT clock stretching
+  meas_medRes               = 0x240B,   // Measurement Medium Repeatability (resolution) WITHOUT clock stretching
+  meas_lowRes               = 0x2416,   // Measurement Low Repeatability (resolution) WITHOUT clock stretching
+};
+
+// status
+#define SHT3X_STATUS_REG_MASK   0xFC1F  // removed bit 9:5 because of their unspecified value
+#define SHT3X_STATUS_REG_DEFL   0x8010  // expected default value (apply MASK for value checking)
+
+// type of measure
+enum class sht3xMeasureType_t : uint8_t {
+  humidity      = 0x10,
+  temperature   = 0x20
+};
+
+// resolution
+enum class sht3xResolution_t : uint8_t {
+  high_res,
+  medium_res,
+  low_res
+};
+#define SHT3X_DEFL_RESOLUTION   sht3xResolution_t::high_res
+
+// integration time (ms)
+enum class sht3xIntegration_t : uint8_t {
+  ms_integrate_high     = 16,   // integraion time (ms) for high resolution measure
+  ms_integrate_medium   = 7,    // integraion time (ms) for medium resolution measure
+  ms_integrate_low      = 5     // integraion time (ms) for low resolution measure
+};
+
+#define SHT3X_INTEGRATION_TIME_CTE    5 // additionnal ms delay to all timings (total <= 255 ---uint8_t)
+
+
+
+/*
+ * Class
+ */
+class SHT3x : public generic_driver {
+  public:
+    SHT3x( sht3xMeasureType_t );
+  
+    boolean begin( uint8_t );       // start with an i2c address
+    bool setResolution( sht3xResolution_t );
+    void powerON( void );       // switch ON
+    void powerOFF( void );      // switch OFF
+
+    // send back sensor's value, units and I2C addr
+    boolean acquire( float* );
+    const char *sensorUnits( uint8_t=0 );
+    String subID( uint8_t=0 ) { return String(_i2caddr); };
+    
+    // read sensor's values
+    boolean getRH( float* );    // retrieve humidity
+    boolean getTemp( float* );  // retrieve temperature
+
+    // --- static methods / constants -----------------------
+    
+    // list of possibles I2C addrs
+    static const uint8_t i2c_addrs[];
+
+    // device detection
+    static boolean is_device( uint8_t );
+    
+  private:
+    // -- private methods
+    bool _readSensor( uint16_t* );                // low-level function to read value registers
+    static void sw_reset( uint8_t );              // reset sensor via software reset procedure
+    static bool crc_check( uint8_t[], uint8_t, uint8_t );  // data array, nb_bytes, checksum
+    static bool _check_identity( uint8_t );       // check device is what we expect!
+    static void _writeCmd( uint8_t, uint16_t );   // write I2C command
+
+    // --- private attributes
+    uint8_t _i2caddr;
+    sht3xResolution_t _resolution;
+    sht3xMeasureType_t _measureType;
+    static const char *_t_units;
+    static const char *_rh_units;
+    uint8_t _integrationTime;   // ms time to integrate a measure (for non continuous mode)
+                                /* both TEMP and RH are read at the same time ==> hence we store them
+                                   as shared attributes across all instances */
+    static unsigned long _lastMsRead;   // last time data have been read (elapsed ms from beginning)
+    static uint16_t _t_sensor;  // shared across all instances (for both humidity and temperature modules)
+    static uint16_t _rh_sensor; // shared across all instances (for both humidity and temperature modules)
+
+    // CRC computation
+    static const uint8_t _crc8_polynom;    // crc P(x)=x^8+x^5+x^4+1 (0x31) 1.00110001, init=0xFF
+#ifdef SHT3X_CRC_LOOKUP_TABLE
+    static const uint8_t _crc8_table[256];
+#endif /* SHT3X_CRC_LOOKUP_TABLE */
+};
+
+#endif /* 0 */
+
+#endif /* _SCD4X_H_ */
diff --git a/neosensor/libraries/neocampus_drivers/SHT3x.cpp b/neosensor/libraries/neocampus_drivers/SHT3x.cpp
index 69ae3186..471ed6ef 100644
--- a/neosensor/libraries/neocampus_drivers/SHT3x.cpp
+++ b/neosensor/libraries/neocampus_drivers/SHT3x.cpp
@@ -1,6 +1,6 @@
 /**************************************************************************/
 /*! 
-    @file     SHT3x.h
+    @file     SHT3x.cpp
     @author   F. Thiebolt
 	  @license
 	
diff --git a/neosensor/libraries/neocampus_modules/airquality.h b/neosensor/libraries/neocampus_modules/airquality.h
index 03f7fceb..8901be7e 100644
--- a/neosensor/libraries/neocampus_modules/airquality.h
+++ b/neosensor/libraries/neocampus_modules/airquality.h
@@ -24,8 +24,8 @@
 // chips & devices drivers
 #include "generic_driver.h"
 #include "lcc_sensor.h"   // LCC's air quality sensors
-#include "pm_serial.h"    // particule meters with serial link: includes PMSx003, SDS011, sensirion SCD40 ...
-
+#include "pm_serial.h"    // particule meters with serial link: includes PMSx003, SDS011, sensirion SPS30 ...
+#include "SCD4x.h"        // Sensirion SCD4X CO2 sensor including temp + hygro
 
 
 
-- 
GitLab