ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
scd30.cpp
Go to the documentation of this file.
1#include "scd30.h"
2#include "esphome/core/log.h"
3#include "esphome/core/hal.h"
4
5#ifdef USE_ESP8266
6#include <Wire.h>
7#endif
8
9namespace esphome::scd30 {
10
11static const char *const TAG = "scd30";
12
13static const uint16_t SCD30_CMD_GET_FIRMWARE_VERSION = 0xd100;
14static const uint16_t SCD30_CMD_START_CONTINUOUS_MEASUREMENTS = 0x0010;
15static const uint16_t SCD30_CMD_ALTITUDE_COMPENSATION = 0x5102;
16static const uint16_t SCD30_CMD_AUTOMATIC_SELF_CALIBRATION = 0x5306;
17static const uint16_t SCD30_CMD_GET_DATA_READY_STATUS = 0x0202;
18static const uint16_t SCD30_CMD_READ_MEASUREMENT = 0x0300;
19
21static const uint16_t SCD30_CMD_STOP_MEASUREMENTS = 0x0104;
22static const uint16_t SCD30_CMD_MEASUREMENT_INTERVAL = 0x4600;
23static const uint16_t SCD30_CMD_FORCED_CALIBRATION = 0x5204;
24static const uint16_t SCD30_CMD_TEMPERATURE_OFFSET = 0x5403;
25static const uint16_t SCD30_CMD_SOFT_RESET = 0xD304;
26
28#ifdef USE_ESP8266
29 Wire.setClockStretchLimit(150000);
30#endif
31
33 uint16_t raw_firmware_version[3];
34 if (!this->get_register(SCD30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 3)) {
35 this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED;
36 this->mark_failed();
37 return;
38 }
39 ESP_LOGD(TAG, "SCD30 Firmware v%0d.%02d", (uint16_t(raw_firmware_version[0]) >> 8),
40 uint16_t(raw_firmware_version[0] & 0xFF));
41
42 uint16_t temp_offset;
43 if (this->temperature_offset_ > 0) {
44 temp_offset = (this->temperature_offset_ * 100);
45 } else {
46 temp_offset = 0;
47 }
48
49 if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, temp_offset)) {
50 ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
51 this->error_code_ = MEASUREMENT_INIT_FAILED;
52 this->mark_failed();
53 return;
54 }
55#ifdef USE_ESP32
56 // According ESP32 clock stretching is typically 30ms and up to 150ms "due to
57 // internal calibration processes". The I2C peripheral only supports 13ms (at
58 // least when running at 80MHz).
59 // In practice it seems that clock stretching occurs during this calibration
60 // calls. It also seems that delays in between calls makes them
61 // disappear/shorter. Hence work around with delays for ESP32.
62 //
63 // By experimentation a delay of 20ms as already sufficient. Let's go
64 // safe and use 30ms delays.
65 delay(30);
66#endif
67
68 if (!this->write_command(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) {
69 ESP_LOGE(TAG, "Sensor SCD30 error setting update interval.");
70 this->error_code_ = MEASUREMENT_INIT_FAILED;
71 this->mark_failed();
72 return;
73 }
74#ifdef USE_ESP32
75 delay(30);
76#endif
77
78 // The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on
79 if (this->altitude_compensation_ != 0xFFFF) {
80 if (!this->write_command(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
81 ESP_LOGE(TAG, "Sensor SCD30 error setting altitude compensation.");
82 this->error_code_ = MEASUREMENT_INIT_FAILED;
83 this->mark_failed();
84 return;
85 }
86 }
87#ifdef USE_ESP32
88 delay(30);
89#endif
90
91 if (!this->write_command(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
92 ESP_LOGE(TAG, "Sensor SCD30 error setting automatic self calibration.");
93 this->error_code_ = MEASUREMENT_INIT_FAILED;
94 this->mark_failed();
95 return;
96 }
97#ifdef USE_ESP32
98 delay(30);
99#endif
100
102 if (!this->write_command(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) {
103 ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements.");
104 this->error_code_ = MEASUREMENT_INIT_FAILED;
105 this->mark_failed();
106 return;
107 }
108
109 // check each 500ms if data is ready, and read it in that case
110 this->set_interval("status-check", 500, [this]() {
111 if (this->is_data_ready_())
112 this->update();
113 });
114}
115
117 ESP_LOGCONFIG(TAG, "scd30:");
118 LOG_I2C_DEVICE(this);
119 if (this->is_failed()) {
120 switch (this->error_code_) {
122 ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
123 break;
125 ESP_LOGW(TAG, "Measurement Initialization failed");
126 break;
128 ESP_LOGW(TAG, "Unable to read sensor firmware version");
129 break;
130 default:
131 ESP_LOGW(TAG, "Unknown setup error");
132 break;
133 }
134 }
135 if (this->altitude_compensation_ == 0xFFFF) {
136 ESP_LOGCONFIG(TAG, " Altitude compensation: OFF");
137 } else {
138 ESP_LOGCONFIG(TAG, " Altitude compensation: %dm", this->altitude_compensation_);
139 }
140 ESP_LOGCONFIG(TAG,
141 " Automatic self calibration: %s\n"
142 " Ambient pressure compensation: %dmBar\n"
143 " Temperature offset: %.2f °C\n"
144 " Update interval: %ds",
146 this->update_interval_);
147 LOG_SENSOR(" ", "CO2", this->co2_sensor_);
148 LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
149 LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
150}
151
153 uint16_t raw_read_status;
154 if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
155 this->status_set_warning();
156 ESP_LOGW(TAG, "Data not ready yet!");
157 return;
158 }
159
160 if (!this->write_command(SCD30_CMD_READ_MEASUREMENT)) {
161 ESP_LOGW(TAG, "Error reading measurement!");
162 this->status_set_warning();
163 return;
164 }
165
166 this->set_timeout(50, [this]() {
167 uint16_t raw_data[6];
168 if (!this->read_data(raw_data, 6)) {
169 this->status_set_warning();
170 return;
171 }
172
173 union uint32_float_t {
174 uint32_t uint32;
175 float value;
176 };
177 uint32_t temp_c_o2_u32 = (((uint32_t(raw_data[0])) << 16) | (uint32_t(raw_data[1])));
178 uint32_float_t co2{.uint32 = temp_c_o2_u32};
179
180 uint32_t temp_temp_u32 = (((uint32_t(raw_data[2])) << 16) | (uint32_t(raw_data[3])));
181 uint32_float_t temperature{.uint32 = temp_temp_u32};
182
183 uint32_t temp_hum_u32 = (((uint32_t(raw_data[4])) << 16) | (uint32_t(raw_data[5])));
184 uint32_float_t humidity{.uint32 = temp_hum_u32};
185
186 ESP_LOGD(TAG, "Got CO2=%.2fppm temperature=%.2f°C humidity=%.2f%%", co2.value, temperature.value, humidity.value);
187 if (this->co2_sensor_ != nullptr)
188 this->co2_sensor_->publish_state(co2.value);
189 if (this->temperature_sensor_ != nullptr)
191 if (this->humidity_sensor_ != nullptr)
192 this->humidity_sensor_->publish_state(humidity.value);
193
194 this->status_clear_warning();
195 });
196}
197
199 if (!this->write_command(SCD30_CMD_GET_DATA_READY_STATUS)) {
200 return false;
201 }
202 delay(4);
203 uint16_t is_data_ready;
204 if (!this->read_data(&is_data_ready, 1)) {
205 return false;
206 }
207 return is_data_ready == 1;
208}
209
211 ESP_LOGD(TAG, "Performing CO2 force recalibration with reference %dppm.", co2_reference);
212 if (this->write_command(SCD30_CMD_FORCED_CALIBRATION, co2_reference)) {
213 ESP_LOGD(TAG, "Force recalibration complete.");
214 return true;
215 } else {
216 ESP_LOGE(TAG, "Failed to force recalibration with reference.");
217 this->error_code_ = FORCE_RECALIBRATION_FAILED;
218 this->status_set_warning();
219 return false;
220 }
221}
222
224 uint16_t forced_calibration_reference = 0;
225 // Get current CO2 calibration
226 if (!this->get_register(SCD30_CMD_FORCED_CALIBRATION, forced_calibration_reference)) {
227 ESP_LOGE(TAG, "Unable to read forced calibration reference.");
228 }
229 return forced_calibration_reference;
230}
231
232} // namespace esphome::scd30
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:272
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.h:493
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_interval(const std voi set_interval)(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a unique name.
Definition component.h:400
void status_clear_warning()
Definition component.h:289
uint16_t ambient_pressure_compensation_
Definition scd30.h:41
sensor::Sensor * humidity_sensor_
Definition scd30.h:46
sensor::Sensor * temperature_sensor_
Definition scd30.h:47
uint16_t get_forced_calibration_reference()
Definition scd30.cpp:223
bool force_recalibration_with_reference(uint16_t co2_reference)
Definition scd30.cpp:210
sensor::Sensor * co2_sensor_
Definition scd30.h:45
void dump_config() override
Definition scd30.cpp:116
bool get_register(uint16_t command, uint16_t *data, uint8_t len, uint8_t delay=0)
get data words from I2C register.
bool write_command(T i2c_register)
Write a command to the I2C device.
bool read_data(uint16_t *data, uint8_t len)
Read data words from I2C device.
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
void HOT delay(uint32_t ms)
Definition hal.cpp:85
static void uint32_t
uint16_t temperature
Definition sun_gtil2.cpp:12