ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
ccs811.cpp
Go to the documentation of this file.
1#include "ccs811.h"
2#include "esphome/core/hal.h"
4#include "esphome/core/log.h"
5
6namespace esphome::ccs811 {
7
8ESPHOME_LOG_TAG(TAG, "ccs811");
9
10// based on
11// - https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf
12
13#define CHECK_TRUE(f, message) \
14 if (!(f)) { \
15 this->mark_failed(LOG_STR(message)); \
16 return; \
17 }
18
19#define CHECKED_IO(f) CHECK_TRUE(f, ESP_LOG_MSG_COMM_FAIL)
20
22 // page 9 programming guide - hwid is always 0x81
23 uint8_t hw_id;
24 CHECKED_IO(this->read_byte(0x20, &hw_id))
25 CHECK_TRUE(hw_id == 0x81, "Sensor reported an invalid ID. Is this a CCS811?")
26
27 // software reset, page 3 - allowed to fail
28 this->write_bytes(0xFF, {0x11, 0xE5, 0x72, 0x8A});
29 delay(5);
30
31 // page 10, APP_START
32 CHECK_TRUE(!this->status_has_error_(), "Sensor reported internal error")
33 CHECK_TRUE(this->status_app_is_valid_(), "Sensor reported invalid APP installed")
34 CHECK_TRUE(this->write_bytes(0xF4, {}), "Sensor reported APP start failed")
35 // App setup, wait for it to load
36 delay(1);
37
38 // set MEAS_MODE (page 5)
39 uint8_t meas_mode = 0;
40 uint32_t interval = this->get_update_interval();
41 if (interval >= 60 * 1000) {
42 meas_mode = 3 << 4; // sensor takes a reading every 60 seconds
43 } else if (interval >= 10 * 1000) {
44 meas_mode = 2 << 4; // sensor takes a reading every 10 seconds
45 } else if (interval >= 1 * 1000) {
46 meas_mode = 1 << 4; // sensor takes a reading every second
47 } else {
48 meas_mode = 4 << 4; // sensor takes a reading every 250ms
49 }
50
51 CHECKED_IO(this->write_byte(0x01, meas_mode))
52
53 if (this->baseline_.has_value()) {
54 // baseline available, write to sensor
55 this->write_bytes(0x11, decode_value(*this->baseline_));
56 }
57
58 auto hardware_version_data = this->read_bytes<1>(0x21);
59 auto bootloader_version_data = this->read_bytes<2>(0x23);
60 auto application_version_data = this->read_bytes<2>(0x24);
61
62 uint8_t hardware_version = 0;
63 uint16_t bootloader_version = 0;
64 uint16_t application_version = 0;
65
66 if (hardware_version_data.has_value()) {
67 hardware_version = (*hardware_version_data)[0];
68 }
69
70 if (bootloader_version_data.has_value()) {
71 bootloader_version = encode_uint16((*bootloader_version_data)[0], (*bootloader_version_data)[1]);
72 }
73
74 if (application_version_data.has_value()) {
75 application_version = encode_uint16((*application_version_data)[0], (*application_version_data)[1]);
76 }
77
78 ESP_LOGD(TAG, "hardware_version=0x%x bootloader_version=0x%x application_version=0x%x\n", hardware_version,
79 bootloader_version, application_version);
80 if (this->version_ != nullptr) {
81 char version[20]; // "15.15.15 (0xffff)" is 17 chars, plus NUL, plus wiggle room
82 buf_append_printf(version, sizeof(version), 0, "%d.%d.%d (0x%02x)", (application_version >> 12 & 15),
83 (application_version >> 8 & 15), (application_version >> 4 & 15), application_version);
84 ESP_LOGD(TAG, "publishing version state: %s", version);
85 this->version_->publish_state(version);
86 }
87}
89 if (!this->status_has_data_()) {
90 ESP_LOGD(TAG, "Status indicates no data ready!");
91 this->status_set_warning();
92 return;
93 }
94
95 // page 12 - alg result data
96 auto alg_data = this->read_bytes<4>(0x02);
97 if (!alg_data.has_value()) {
98 ESP_LOGW(TAG, "Reading CCS811 data failed!");
99 this->status_set_warning();
100 return;
101 }
102 auto res = *alg_data;
103 uint16_t co2 = encode_uint16(res[0], res[1]);
104 uint16_t tvoc = encode_uint16(res[2], res[3]);
105
106 // also print baseline
107 auto baseline_data = this->read_bytes<2>(0x11);
108 uint16_t baseline = 0;
109 if (baseline_data.has_value()) {
110 baseline = encode_uint16((*baseline_data)[0], (*baseline_data)[1]);
111 }
112
113 ESP_LOGD(TAG, "Got co2=%u ppm, tvoc=%u ppb, baseline=0x%04X", co2, tvoc, baseline);
114
115 if (this->co2_ != nullptr)
116 this->co2_->publish_state(co2);
117 if (this->tvoc_ != nullptr)
118 this->tvoc_->publish_state(tvoc);
119
120 this->status_clear_warning();
121
122 this->send_env_data_();
123}
125 if (this->humidity_ == nullptr && this->temperature_ == nullptr)
126 return;
127
128 float humidity = NAN;
129 if (this->humidity_ != nullptr)
130 humidity = this->humidity_->state;
131 if (std::isnan(humidity) || humidity < 0 || humidity > 100)
132 humidity = 50;
133 float temperature = NAN;
134 if (this->temperature_ != nullptr)
135 temperature = this->temperature_->state;
137 temperature = 25;
138 // temperature has a 25° offset to allow negative temperatures
139 temperature += 25;
140
141 // At page 18 of:
142 // https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf
143 // Reference code:
144 // https://github.com/adafruit/Adafruit_CCS811/blob/0990f5c620354d8bc087c4706bec091d8e6e5dfd/Adafruit_CCS811.cpp#L135-L142
145 uint16_t hum_conv = static_cast<uint16_t>(lroundf(humidity * 512.0f + 0.5f));
146 uint16_t temp_conv = static_cast<uint16_t>(lroundf(temperature * 512.0f + 0.5f));
147 this->write_bytes(0x05, {(uint8_t) ((hum_conv >> 8) & 0xff), (uint8_t) ((hum_conv & 0xff)),
148 (uint8_t) ((temp_conv >> 8) & 0xff), (uint8_t) ((temp_conv & 0xff))});
149}
151 ESP_LOGCONFIG(TAG, "CCS811");
152 LOG_I2C_DEVICE(this)
153 LOG_UPDATE_INTERVAL(this);
154 LOG_SENSOR(" ", "CO2 Sensor", this->co2_);
155 LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_);
156 LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_);
157 if (this->baseline_) {
158 ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);
159 } else {
160 ESP_LOGCONFIG(TAG, " Baseline: NOT SET");
161 }
162}
163
164} // namespace esphome::ccs811
void status_clear_warning()
Definition component.h:296
virtual uint32_t get_update_interval() const
Get the update interval in ms of this sensor.
optional< uint16_t > baseline_
Definition ccs811.h:37
text_sensor::TextSensor * version_
Definition ccs811.h:36
sensor::Sensor * temperature_
Input sensor for temperature reading.
Definition ccs811.h:41
sensor::Sensor * humidity_
Input sensor for humidity reading.
Definition ccs811.h:39
void setup() override
Setup the sensor and test for a connection.
Definition ccs811.cpp:21
void update() override
Schedule temperature+pressure readings.
Definition ccs811.cpp:88
bool write_byte(uint8_t a_register, uint8_t data) const
Definition i2c.h:312
bool read_byte(uint8_t a_register, uint8_t *data)
Definition i2c.h:272
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) const
Definition i2c.h:298
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len)
Compat APIs All methods below have been added for compatibility reasons.
Definition i2c.h:249
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:140
void publish_state(const std::string &state)
Definition text_sensor.h:42
ESPHOME_LOG_TAG(TAG, "ccs811")
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:998
void HOT delay(uint32_t ms)
Definition hal.cpp:85
constexpr 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:1056
static void uint32_t
uint16_t temperature
Definition sun_gtil2.cpp:12