ESPHome  2024.4.0
ccs811.cpp
Go to the documentation of this file.
1 #include "ccs811.h"
2 #include "esphome/core/log.h"
3 #include "esphome/core/hal.h"
4 
5 namespace esphome {
6 namespace ccs811 {
7 
8 static const char *const TAG = "ccs811";
9 
10 // based on
11 // - https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf
12 
13 #define CHECK_TRUE(f, error_code) \
14  if (!(f)) { \
15  this->mark_failed(); \
16  this->error_code_ = (error_code); \
17  return; \
18  }
19 
20 #define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICATION_FAILED)
21 
23  // page 9 programming guide - hwid is always 0x81
24  uint8_t hw_id;
25  CHECKED_IO(this->read_byte(0x20, &hw_id))
26  CHECK_TRUE(hw_id == 0x81, INVALID_ID)
27 
28  // software reset, page 3 - allowed to fail
29  this->write_bytes(0xFF, {0x11, 0xE5, 0x72, 0x8A});
30  delay(5);
31 
32  // page 10, APP_START
33  CHECK_TRUE(!this->status_has_error_(), SENSOR_REPORTED_ERROR)
34  CHECK_TRUE(this->status_app_is_valid_(), APP_INVALID)
35  CHECK_TRUE(this->write_bytes(0xF4, {}), APP_START_FAILED)
36  // App setup, wait for it to load
37  delay(1);
38 
39  // set MEAS_MODE (page 5)
40  uint8_t meas_mode = 0;
41  uint32_t interval = this->get_update_interval();
42  if (interval >= 60 * 1000) {
43  meas_mode = 3 << 4; // sensor takes a reading every 60 seconds
44  } else if (interval >= 10 * 1000) {
45  meas_mode = 2 << 4; // sensor takes a reading every 10 seconds
46  } else if (interval >= 1 * 1000) {
47  meas_mode = 1 << 4; // sensor takes a reading every second
48  } else {
49  meas_mode = 4 << 4; // sensor takes a reading every 250ms
50  }
51 
52  CHECKED_IO(this->write_byte(0x01, meas_mode))
53 
54  if (this->baseline_.has_value()) {
55  // baseline available, write to sensor
56  this->write_bytes(0x11, decode_value(*this->baseline_));
57  }
58 
59  auto hardware_version_data = this->read_bytes<1>(0x21);
60  auto bootloader_version_data = this->read_bytes<2>(0x23);
61  auto application_version_data = this->read_bytes<2>(0x24);
62 
63  uint8_t hardware_version = 0;
64  uint16_t bootloader_version = 0;
65  uint16_t application_version = 0;
66 
67  if (hardware_version_data.has_value()) {
68  hardware_version = (*hardware_version_data)[0];
69  }
70 
71  if (bootloader_version_data.has_value()) {
72  bootloader_version = encode_uint16((*bootloader_version_data)[0], (*bootloader_version_data)[1]);
73  }
74 
75  if (application_version_data.has_value()) {
76  application_version = encode_uint16((*application_version_data)[0], (*application_version_data)[1]);
77  }
78 
79  ESP_LOGD(TAG, "hardware_version=0x%x bootloader_version=0x%x application_version=0x%x\n", hardware_version,
80  bootloader_version, application_version);
81  if (this->version_ != nullptr) {
82  char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room
83  sprintf(version, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15), (application_version >> 8 & 15),
84  (application_version >> 4 & 15), application_version);
85  ESP_LOGD(TAG, "publishing version state: %s", version);
86  this->version_->publish_state(version);
87  }
88 }
90  if (!this->status_has_data_()) {
91  ESP_LOGD(TAG, "Status indicates no data ready!");
92  this->status_set_warning();
93  return;
94  }
95 
96  // page 12 - alg result data
97  auto alg_data = this->read_bytes<4>(0x02);
98  if (!alg_data.has_value()) {
99  ESP_LOGW(TAG, "Reading CCS811 data failed!");
100  this->status_set_warning();
101  return;
102  }
103  auto res = *alg_data;
104  uint16_t co2 = encode_uint16(res[0], res[1]);
105  uint16_t tvoc = encode_uint16(res[2], res[3]);
106 
107  // also print baseline
108  auto baseline_data = this->read_bytes<2>(0x11);
109  uint16_t baseline = 0;
110  if (baseline_data.has_value()) {
111  baseline = encode_uint16((*baseline_data)[0], (*baseline_data)[1]);
112  }
113 
114  ESP_LOGD(TAG, "Got co2=%u ppm, tvoc=%u ppb, baseline=0x%04X", co2, tvoc, baseline);
115 
116  if (this->co2_ != nullptr)
117  this->co2_->publish_state(co2);
118  if (this->tvoc_ != nullptr)
119  this->tvoc_->publish_state(tvoc);
120 
121  this->status_clear_warning();
122 
123  this->send_env_data_();
124 }
126  if (this->humidity_ == nullptr && this->temperature_ == nullptr)
127  return;
128 
129  float humidity = NAN;
130  if (this->humidity_ != nullptr)
131  humidity = this->humidity_->state;
132  if (std::isnan(humidity) || humidity < 0 || humidity > 100)
133  humidity = 50;
134  float temperature = NAN;
135  if (this->temperature_ != nullptr)
136  temperature = this->temperature_->state;
137  if (std::isnan(temperature) || temperature < -25 || temperature > 50)
138  temperature = 25;
139  // temperature has a 25° offset to allow negative temperatures
140  temperature += 25;
141 
142  // At page 18 of:
143  // https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf
144  // Reference code:
145  // https://github.com/adafruit/Adafruit_CCS811/blob/0990f5c620354d8bc087c4706bec091d8e6e5dfd/Adafruit_CCS811.cpp#L135-L142
146  uint16_t hum_conv = static_cast<uint16_t>(lroundf(humidity * 512.0f + 0.5f));
147  uint16_t temp_conv = static_cast<uint16_t>(lroundf(temperature * 512.0f + 0.5f));
148  this->write_bytes(0x05, {(uint8_t) ((hum_conv >> 8) & 0xff), (uint8_t) ((hum_conv & 0xff)),
149  (uint8_t) ((temp_conv >> 8) & 0xff), (uint8_t) ((temp_conv & 0xff))});
150 }
152  ESP_LOGCONFIG(TAG, "CCS811");
153  LOG_I2C_DEVICE(this)
154  LOG_UPDATE_INTERVAL(this)
155  LOG_SENSOR(" ", "CO2 Sensor", this->co2_)
156  LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_)
157  LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)
158  if (this->baseline_) {
159  ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);
160  } else {
161  ESP_LOGCONFIG(TAG, " Baseline: NOT SET");
162  }
163  if (this->is_failed()) {
164  switch (this->error_code_) {
166  ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
167  break;
168  case INVALID_ID:
169  ESP_LOGW(TAG, "Sensor reported an invalid ID. Is this a CCS811?");
170  break;
172  ESP_LOGW(TAG, "Sensor reported internal error");
173  break;
174  case APP_INVALID:
175  ESP_LOGW(TAG, "Sensor reported invalid APP installed.");
176  break;
177  case APP_START_FAILED:
178  ESP_LOGW(TAG, "Sensor reported APP start failed.");
179  break;
180  case UNKNOWN:
181  default:
182  ESP_LOGW(TAG, "Unknown setup error!");
183  break;
184  }
185  }
186 }
187 
188 } // namespace ccs811
189 } // namespace esphome
bool read_byte(uint8_t a_register, uint8_t *data, bool stop=true)
Definition: i2c.h:235
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
sensor::Sensor * humidity_
Input sensor for humidity reading.
Definition: ccs811.h:51
void publish_state(const std::string &state)
Definition: text_sensor.cpp:9
bool has_value() const
Definition: optional.h:87
sensor::Sensor * temperature_
Input sensor for temperature reading.
Definition: ccs811.h:53
text_sensor::TextSensor * version_
Definition: ccs811.h:48
void update() override
Schedule temperature+pressure readings.
Definition: ccs811.cpp:89
enum esphome::ccs811::CCS811Component::ErrorCode UNKNOWN
void setup() override
Setup the sensor and test for a connection.
Definition: ccs811.cpp:22
float state
This member variable stores the last state that has passed through all filters.
Definition: sensor.h:131
void status_clear_warning()
Definition: component.cpp:166
constexpr14 std::array< uint8_t, sizeof(T)> decode_value(T val)
Decode a value into its constituent bytes (from most to least significant).
Definition: helpers.h:212
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
uint16_t temperature
Definition: sun_gtil2.cpp:26
optional< uint16_t > baseline_
Definition: ccs811.h:49
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition: helpers.h:182
virtual uint32_t get_update_interval() const
Get the update interval in ms of this sensor.
Definition: component.cpp:228
bool write_byte(uint8_t a_register, uint8_t data, bool stop=true)
Definition: i2c.h:262
sensor::Sensor * tvoc_
Definition: ccs811.h:47
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
sensor::Sensor * co2_
Definition: ccs811.h:46
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop=true)
Definition: i2c.h:248