From e4bb9e39ccd79200d605c6345ff67b20fcebdb92 Mon Sep 17 00:00:00 2001
From: Pim van Pelt <pim@ipng.nl>
Date: Tue, 10 Apr 2018 21:36:10 +0200
Subject: [PATCH] Add CCS811 driver

---
 include/mgos_ccs811.h      | 102 ++++++++++++++++
 src/main.c                 | 121 ++++++++++++-------
 src/mgos_ccs811.c          | 238 +++++++++++++++++++++++++++++++++++++
 src/mgos_ccs811_internal.h |  74 ++++++++++++
 4 files changed, 490 insertions(+), 45 deletions(-)
 create mode 100644 include/mgos_ccs811.h
 create mode 100644 src/mgos_ccs811.c
 create mode 100644 src/mgos_ccs811_internal.h

diff --git a/include/mgos_ccs811.h b/include/mgos_ccs811.h
new file mode 100644
index 0000000..092bf8a
--- /dev/null
+++ b/include/mgos_ccs811.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "mgos.h"
+#include "mgos_i2c.h"
+
+#define MGOS_CCS811_READ_DELAY    (2)
+
+#ifdef __cplusplus
+extern "C" {
+  #endif
+
+struct mgos_ccs811;
+struct mgos_ccs811_stats {
+  double   last_read_time;       // value of mg_time() upon last call to _read()
+  uint32_t read;                 // calls to _read()
+  uint32_t read_success;         // successful _read()
+  uint32_t read_success_cached;  // calls to _read() which were cached
+  // Note: read_errors := read - read_success - read_success_cached
+  double   read_success_usecs;   // time spent in successful uncached _read()
+};
+
+// Drive modes
+enum mgos_ccs811_drive_mode_t {
+  CCS811_DRIVE_MODE_IDLE  = 0x00,
+  CCS811_DRIVE_MODE_1SEC  = 0x01,
+  CCS811_DRIVE_MODE_10SEC = 0x02,
+  CCS811_DRIVE_MODE_60SEC = 0x03,
+  CCS811_DRIVE_MODE_250MS = 0x04,
+};
+
+/*
+ * Initialize a CCS811 on the I2C bus `i2c` at address specified in `i2caddr`
+ * parameter (default CCS811 is on address 0x5A). The sensor will be polled for
+ * validity, upon success a new `struct mgos_ccs811` is allocated and
+ * returned. If the device could not be found, NULL is returned.
+ */
+struct mgos_ccs811 *mgos_ccs811_create(struct mgos_i2c *i2c, uint8_t i2caddr);
+
+/*
+ * Destroy the data structure associated with a CCS811 device. The reference
+ * to the pointer of the `struct mgos_ccs811` has to be provided, and upon
+ * successful destruction, its associated memory will be freed and the pointer
+ * set to NULL.
+ */
+void mgos_ccs811_destroy(struct mgos_ccs811 **sensor);
+
+/*
+ * The sensor will be polled for its temperature and humidity data. If the poll
+ * has occured in the last `MGOS_CCS811_READ_DELAY` seconds, the cached data is
+ * used (so as not to repeatedly poll the bus upon subsequent calls).
+ */
+bool mgos_ccs811_read(struct mgos_ccs811 *sensor);
+
+/*
+ * Set the drive mode of the CCS811 sensor based on the `mode` argument
+ * Returns true on success, false otherwise.
+ */
+bool mgos_ccs811_setDriveMode(struct mgos_ccs811 *sensor, enum mgos_ccs811_drive_mode_t mode);
+
+/*
+ * Retrieve the current drive mode (which will be one of `enum mgos_ccs811_drive_mode_t`
+ * values into the byte pointed to by `mode`.
+ * Returns true on success, false otherwise.
+ */
+bool mgos_ccs811_getDriveMode(struct mgos_ccs811 *sensor, uint8_t *mode);
+
+/*
+ *
+ * Returns a value on success, NAN otherwise.
+ */
+float mgos_ccs811_get_eco2(struct mgos_ccs811 *sensor);
+
+/*
+ *
+ * Returns a value on success, NAN otherwise.
+ */
+float mgos_ccs811_get_tvoc(struct mgos_ccs811 *sensor);
+
+/*
+ * Initialization function for MGOS -- currently a noop.
+ */
+bool mgos_ccs811_i2c_init(void);
+
+  #ifdef __cplusplus
+}
+#endif
diff --git a/src/main.c b/src/main.c
index 6ba1bf8..33445a3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -20,103 +20,124 @@
 #include "mgos_si7021.h"
 #include "mgos_htu21df.h"
 #include "mgos_mcp9808.h"
+#include "mgos_ccs811.h"
 #include <fcntl.h>
 #include <sys/ioctl.h>
 
-#define I2CBUSNR 5
+#define I2CBUSNR    5
 
 void i2c_scanner(struct mgos_i2c *i2c) {
   int i;
+
   if (!i2c) {
     LOG(LL_ERROR, ("No global I2C bus configured"));
     return;
   }
 
-  for(i=0x3; i<0x77; i++) {
+  for (i = 0x3; i < 0x77; i++) {
     bool ret;
-    ret=mgos_i2c_read(i2c, i, NULL, 0, true);
-    if (ret)
-      LOG(LL_INFO, ("I2C Address 0x%02x %s", i, ret?"true":"false"));
+    ret = mgos_i2c_read(i2c, i, NULL, 0, true);
+    if (ret) {
+      LOG(LL_INFO, ("I2C Address 0x%02x %s", i, ret ? "true" : "false"));
+    }
   }
 }
 
-
 bool i2c_dumpregs(struct mgos_i2c *i2c, uint8_t i2caddr) {
   uint16_t reg;
-  int value;
+  int      value;
 
-  for(reg=0; reg<256; reg++) {
-    value=mgos_i2c_read_reg_b(i2c, i2caddr, reg);
-    if (value<0) {
+  for (reg = 0; reg < 256; reg++) {
+    value = mgos_i2c_read_reg_b(i2c, i2caddr, reg);
+    if (value < 0) {
       printf(" XX");
-    }
-    else {
+    }else {
       printf(" %02x", value);
     }
-    if (reg%16==15) printf("\n");
+    if (reg % 16 == 15) {
+      printf("\n");
+    }
   }
   printf("\n");
   return true;
 }
 
+bool do_ccs811(struct mgos_ccs811 *sensor) {
+  float eco2, tvoc;
+
+  if (!sensor) {
+    return false;
+  }
+
+  eco2 = mgos_ccs811_get_eco2(sensor);
+  tvoc = mgos_ccs811_get_tvoc(sensor);
+  LOG(LL_INFO, ("eCO2=%.0fppm TVOC=%.0fppb", eco2, tvoc));
+
+  return true;
+}
 
 bool do_sht31(struct mgos_sht31 *sensor) {
   float temp, humid;
 
-  if (!sensor) return false;
+  if (!sensor) {
+    return false;
+  }
 
-  temp=mgos_sht31_getTemperature(sensor);
-  humid=mgos_sht31_getHumidity(sensor);
+  temp  = mgos_sht31_getTemperature(sensor);
+  humid = mgos_sht31_getHumidity(sensor);
   LOG(LL_INFO, ("temperature=%.2fC humidity=%.1f%%", temp, humid));
 
   return true;
 }
 
-
 bool do_si7021(struct mgos_si7021 *sensor) {
   float temp, humid;
 
-  if (!sensor) return false;
+  if (!sensor) {
+    return false;
+  }
 
-  temp=mgos_si7021_getTemperature(sensor);
-  humid=mgos_si7021_getHumidity(sensor);
+  temp  = mgos_si7021_getTemperature(sensor);
+  humid = mgos_si7021_getHumidity(sensor);
   LOG(LL_INFO, ("temperature=%.2fC humidity=%.1f%%", temp, humid));
 
   return true;
 }
 
-
 bool do_htu21df(struct mgos_htu21df *sensor) {
   float temp, humid;
 
-  if (!sensor) return false;
+  if (!sensor) {
+    return false;
+  }
 
-  temp=mgos_htu21df_getTemperature(sensor);
-  humid=mgos_htu21df_getHumidity(sensor);
+  temp  = mgos_htu21df_getTemperature(sensor);
+  humid = mgos_htu21df_getHumidity(sensor);
   LOG(LL_INFO, ("temperature=%.2fC humidity=%.1f%%", temp, humid));
 
   return true;
 }
 
-
 bool do_mcp9808(struct mgos_mcp9808 *sensor) {
   float temp;
 
-  if (!sensor) return false;
+  if (!sensor) {
+    return false;
+  }
 
-  temp=mgos_mcp9808_getTemperature(sensor);
+  temp = mgos_mcp9808_getTemperature(sensor);
   LOG(LL_INFO, ("temperature=%.2fC", temp));
 
   return true;
 }
 
-
 int main() {
-  struct mgos_i2c *i2c;
-  struct mgos_si7021 *si7021;
-  struct mgos_sht31 *sht31;
+  struct mgos_i2c *    i2c;
+  struct mgos_si7021 * si7021;
+  struct mgos_sht31 *  sht31;
   struct mgos_htu21df *htu21df;
   struct mgos_mcp9808 *mcp9808;
+  struct mgos_ccs811 * ccs811;
 
   if (!mgos_i2c_open(I2CBUSNR)) {
     LOG(LL_ERROR, ("Cannot open I2C bus %u", I2CBUSNR));
@@ -129,23 +150,32 @@ int main() {
 
   i2c_scanner(i2c);
 
-  if (!(sht31 = mgos_sht31_create(i2c, 0x44)))
-    LOG(LL_ERROR, ("Cannot create SHT31 device"));
+  /*
+   * if (!(sht31 = mgos_sht31_create(i2c, 0x44)))
+   * LOG(LL_ERROR, ("Cannot create SHT31 device"));
+   *
+   * if (!(si7021 = mgos_si7021_create(i2c, 0x40)))
+   * LOG(LL_ERROR, ("Cannot create SI7021 device"));
+   *
+   * if (!(htu21df = mgos_htu21df_create(i2c, 0x40)))
+   * LOG(LL_ERROR, ("Cannot create HTU21DF device"));
+   *
+   * if (!(mcp9808 = mgos_mcp9808_create(i2c, 0x18)))
+   * LOG(LL_ERROR, ("Cannot create MCP9808 device"));
+   */
 
-  if (!(si7021 = mgos_si7021_create(i2c, 0x40)))
-    LOG(LL_ERROR, ("Cannot create SI7021 device"));
-
-  if (!(htu21df = mgos_htu21df_create(i2c, 0x40)))
-    LOG(LL_ERROR, ("Cannot create HTU21DF device"));
-
-  if (!(mcp9808 = mgos_mcp9808_create(i2c, 0x18)))
-    LOG(LL_ERROR, ("Cannot create MCP9808 device"));
+  if (!(ccs811 = mgos_ccs811_create(i2c, 0x5A))) {
+    LOG(LL_ERROR, ("Cannot create CCS811 device"));
+  }
 
   for (;;) {
-    do_sht31(sht31);
-    do_si7021(si7021);
-    do_htu21df(htu21df);
-    do_mcp9808(mcp9808);
+    /*
+     * do_sht31(sht31);
+     * do_si7021(si7021);
+     * do_htu21df(htu21df);
+     * do_mcp9808(mcp9808);
+     */
+    do_ccs811(ccs811);
     sleep(5);
   }
 
@@ -153,6 +183,7 @@ int main() {
   mgos_si7021_destroy(&si7021);
   mgos_htu21df_destroy(&htu21df);
   mgos_mcp9808_destroy(&mcp9808);
+  mgos_ccs811_destroy(&ccs811);
 
   return 0;
 }
diff --git a/src/mgos_ccs811.c b/src/mgos_ccs811.c
new file mode 100644
index 0000000..fa2fcfa
--- /dev/null
+++ b/src/mgos_ccs811.c
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mgos.h"
+#include "mgos_ccs811_internal.h"
+#include "mgos_i2c.h"
+
+// Datasheet:
+// https://cdn-shop.adafruit.com/product-files/2857/Sensirion_Humidity_SHT3x_Datasheet_digital-767294.pdf
+
+// Private functions follow
+static bool mgos_ccs811_getStatus(struct mgos_ccs811 *sensor, uint8_t *status) {
+  int ret;
+
+  if (!sensor) {
+    return false;
+  }
+  ret = mgos_i2c_read_reg_b(sensor->i2c, sensor->i2caddr, MGOS_CCS811_REG_STATUS);
+  if (ret < 0) {
+    return false;
+  }
+  *status = (uint8_t)ret;
+  return true;
+}
+
+static bool mgos_ccs811_getMeasMode(struct mgos_ccs811 *sensor, uint8_t *meas_mode) {
+  int ret;
+
+  if (!sensor) {
+    return false;
+  }
+  ret = mgos_i2c_read_reg_b(sensor->i2c, sensor->i2caddr, MGOS_CCS811_REG_MEAS_MODE);
+  if (ret < 0) {
+    return false;
+  }
+  *meas_mode = (uint8_t)ret;
+  return true;
+}
+
+// Return true if sensor status register has error-bit set (or upon read failure)
+
+/*
+ * static bool mgos_ccs811_error(struct mgos_ccs811 *sensor) {
+ * uint8_t status;
+ *
+ * if (!mgos_ccs811_getStatus(sensor, &status))
+ *  return true;
+ * // bits -- 7:FW_MODE 6:APP_ERASE 5:APP_VERIFY 4:APP_VALID 3:DATA_READY 0:ERROR
+ * return (status & MGOS_CCS811_STATUS_ERR);
+ * }
+ */
+
+// Return true if sensor status register has data-ready set (false upon read failure)
+static bool mgos_ccs811_dataready(struct mgos_ccs811 *sensor) {
+  uint8_t status;
+
+  if (!mgos_ccs811_getStatus(sensor, &status)) {
+    return false;
+  }
+  return status & MGOS_CCS811_STATUS_DATA_READY;
+}
+
+static bool mgos_ccs811_reset(struct mgos_ccs811 *sensor) {
+  uint8_t data[5] = { MGOS_CCS811_REG_SW_RESET, 0x11, 0xE5, 0x72, 0x8A };
+
+  if (!sensor) {
+    return false;
+  }
+  return mgos_i2c_write(sensor->i2c, sensor->i2caddr, data, 5, true);
+}
+
+// Private functions end
+
+// Public functions follow
+struct mgos_ccs811 *mgos_ccs811_create(struct mgos_i2c *i2c, uint8_t i2caddr) {
+  struct mgos_ccs811 *sensor;
+  int success = 0;
+  int ret;
+
+  if (!i2c) {
+    return NULL;
+  }
+
+  ret = mgos_i2c_read_reg_b(i2c, i2caddr, MGOS_CCS811_REG_HW_ID);
+  if (ret != MGOS_CCS811_HW_ID_CODE) {
+    LOG(LL_ERROR, ("Failed to detect CCS811 at I2C 0x%02x", i2caddr));
+    return NULL;
+  }
+
+  sensor = calloc(1, sizeof(struct mgos_ccs811));
+  if (!sensor) {
+    return NULL;
+  }
+
+  memset(sensor, 0, sizeof(struct mgos_ccs811));
+  sensor->i2caddr = i2caddr;
+  sensor->i2c     = i2c;
+  sensor->eco2    = 400;
+
+  // Boot the application on CCS811.
+  mgos_ccs811_reset(sensor);
+  mgos_usleep(2000);
+
+  uint8_t cmd        = MGOS_CCS811_BOOTLOADER_REG_APP_START;
+  uint8_t status     = MGOS_CCS811_STATUS_ERR;
+  uint8_t drive_mode = CCS811_DRIVE_MODE_IDLE;
+  mgos_i2c_write(sensor->i2c, sensor->i2caddr, &cmd, 1, true);
+  mgos_usleep(2000);
+
+  // Read status (expecting FW_MODE to be set and ERR to be clear)
+  mgos_ccs811_getStatus(sensor, &status);
+  if (!(status & MGOS_CCS811_STATUS_FW_MODE) || (status & MGOS_CCS811_STATUS_ERR)) {
+    LOG(LL_ERROR, ("CCS811 invalid firmware mode, and/or status error"));
+    goto exit;
+  }
+
+  mgos_ccs811_setDriveMode(sensor, CCS811_DRIVE_MODE_1SEC);
+  mgos_ccs811_getDriveMode(sensor, &drive_mode);
+  if (drive_mode != CCS811_DRIVE_MODE_1SEC) {
+    LOG(LL_ERROR, ("CCS811 failed to set drive mode"));
+    goto exit;
+  }
+
+  success = 1;
+  LOG(LL_INFO, ("CCS811 created at I2C 0x%02x", i2caddr));
+exit:
+  if (!success) {
+    free(sensor);
+    sensor = NULL;
+  }
+  return sensor;
+}
+
+bool mgos_ccs811_getDriveMode(struct mgos_ccs811 *sensor, uint8_t *mode) {
+  uint8_t meas_mode;
+
+  if (!mgos_ccs811_getMeasMode(sensor, &meas_mode)) {
+    return false;
+  }
+  // bits -- 6:4 DRIVE_MOCE 3: Interrupt enable 2: Int on Threshhold
+  meas_mode >>= 4;
+  meas_mode  &= 0x07;
+  *mode       = meas_mode;
+
+  return true;
+}
+
+bool mgos_ccs811_setDriveMode(struct mgos_ccs811 *sensor, enum mgos_ccs811_drive_mode_t mode) {
+  uint8_t meas_mode;
+
+  // bits -- 6:4 DRIVE_MOCE 3: Interrupt enable 2: Int on Threshhold
+  meas_mode = (mode << 4);
+  return mgos_i2c_write_reg_b(sensor->i2c, sensor->i2caddr, MGOS_CCS811_REG_MEAS_MODE, meas_mode);
+}
+
+void mgos_ccs811_destroy(struct mgos_ccs811 **sensor) {
+  if (!*sensor) {
+    return;
+  }
+  free(*sensor);
+  *sensor = NULL;
+  return;
+}
+
+bool mgos_ccs811_read(struct mgos_ccs811 *sensor) {
+  double start = mg_time();
+
+  if (!sensor || !sensor->i2c) {
+    return false;
+  }
+
+  sensor->stats.read++;
+
+  if (start - sensor->stats.last_read_time < MGOS_CCS811_READ_DELAY) {
+    sensor->stats.read_success_cached++;
+    return true;
+  }
+  // Read out sensor data here
+  //
+  if (!mgos_ccs811_dataready(sensor)) {
+    sensor->stats.read_success_cached++;
+    return true;
+  }
+
+  uint8_t data[8];
+  uint8_t cmd = MGOS_CCS811_REG_ALG_RESULT_DATA;
+
+  data[4] = MGOS_CCS811_STATUS_ERR;
+  mgos_i2c_write(sensor->i2c, sensor->i2caddr, &cmd, 1, false);
+  mgos_i2c_read(sensor->i2c, sensor->i2caddr, data, 8, true);
+
+  // bytes 0-1:eco2 2-3:tvoc 4:status 5:error 6-7: raw_data
+  if (data[4] & MGOS_CCS811_STATUS_ERR) {
+    LOG(LL_ERROR, ("Read error 0x%02x", data[5]));
+    return false;
+  }
+  sensor->eco2 = ((uint16_t)data[0] << 8) | ((uint16_t)data[1]);
+  sensor->tvoc = ((uint16_t)data[2] << 8) | ((uint16_t)data[3]);
+  LOG(LL_DEBUG, ("eCO2=%u TVOC=%u", sensor->eco2, sensor->tvoc));
+
+  sensor->stats.read_success++;
+  sensor->stats.read_success_usecs += 1000000 * (mg_time() - start);
+  sensor->stats.last_read_time      = start;
+  return true;
+}
+
+float mgos_ccs811_get_eco2(struct mgos_ccs811 *sensor) {
+  if (!mgos_ccs811_read(sensor)) {
+    return NAN;
+  }
+  return (float)sensor->eco2;
+}
+
+float mgos_ccs811_get_tvoc(struct mgos_ccs811 *sensor) {
+  if (!mgos_ccs811_read(sensor)) {
+    return NAN;
+  }
+  return (float)sensor->tvoc;
+}
+
+bool mgos_ccs811_i2c_init(void) {
+  return true;
+}
+
+// Public functions end
diff --git a/src/mgos_ccs811_internal.h b/src/mgos_ccs811_internal.h
new file mode 100644
index 0000000..27ee1fa
--- /dev/null
+++ b/src/mgos_ccs811_internal.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "mgos.h"
+#include "mgos_i2c.h"
+#include "mgos_ccs811.h"
+#include <math.h>
+
+#define MGOS_SHT31_DEFAULT_I2CADDR               (0x5A)
+
+// Registers
+#define MGOS_CCS811_REG_STATUS                   (0x00)
+#define MGOS_CCS811_REG_MEAS_MODE                (0x01)
+#define MGOS_CCS811_REG_ALG_RESULT_DATA          (0x02)
+#define MGOS_CCS811_REG_RAW_DATA                 (0x03)
+#define MGOS_CCS811_REG_ENV_DATA                 (0x05)
+#define MGOS_CCS811_REG_NTC                      (0x06)
+#define MGOS_CCS811_REG_THRESHOLDS               (0x10)
+#define MGOS_CCS811_REG_BASELINE                 (0x11)
+#define MGOS_CCS811_REG_HW_ID                    (0x20)
+#define MGOS_CCS811_REG_HW_VERSION               (0x21)
+#define MGOS_CCS811_REG_FW_BOOT_VERSION          (0x23)
+#define MGOS_CCS811_REG_FW_APP_VERSION           (0x24)
+#define MGOS_CCS811_REG_ERROR_ID                 (0xE0)
+#define MGOS_CCS811_REG_SW_RESET                 (0xFF)
+
+// Bootloader registers
+#define MGOS_CCS811_BOOTLOADER_REG_APP_ERASE     (0xF1)
+#define MGOS_CCS811_BOOTLOADER_REG_APP_DATA      (0xF2)
+#define MGOS_CCS811_BOOTLOADER_REG_APP_VERIFY    (0xF3)
+#define MGOS_CCS811_BOOTLOADER_REG_APP_START     (0xF4)
+
+// Status register bits
+#define MGOS_CCS811_STATUS_ERR                   (0x01)
+#define MGOS_CCS811_STATUS_DATA_READY            (0x08)
+#define MGOS_CCS811_STATUS_APP_VALID             (0x10)
+#define MGOS_CCS811_STATUS_FW_MODE               (0x80)
+
+// Other defines
+#define MGOS_CCS811_HW_ID_CODE                   (0x81)
+#define MGOS_CCS811_REF_RESISTOR                 (100000)
+
+#ifdef __cplusplus
+extern "C" {
+  #endif
+
+struct mgos_ccs811 {
+  struct mgos_i2c *        i2c;
+  uint8_t                  i2caddr;
+  struct mgos_ccs811_stats stats;
+
+  float                    temperature_offset;
+  uint16_t                 tvoc;
+  uint16_t                 eco2;
+};
+
+  #ifdef __cplusplus
+}
+#endif