ESPHome 2025.9.0-dev
Loading...
Searching...
No Matches
scd4x.cpp
Go to the documentation of this file.
1#include "scd4x.h"
2#include "esphome/core/hal.h"
3#include "esphome/core/log.h"
4
5namespace esphome {
6namespace scd4x {
7
8static const char *const TAG = "scd4x";
9
10static const uint16_t SCD41_ID = 0x1408;
11static const uint16_t SCD40_ID = 0x440;
12static const uint16_t SCD4X_CMD_GET_SERIAL_NUMBER = 0x3682;
13static const uint16_t SCD4X_CMD_TEMPERATURE_OFFSET = 0x241d;
14static const uint16_t SCD4X_CMD_ALTITUDE_COMPENSATION = 0x2427;
15static const uint16_t SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION = 0xe000;
16static const uint16_t SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION = 0x2416;
17static const uint16_t SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS = 0x21b1;
18static const uint16_t SCD4X_CMD_START_LOW_POWER_CONTINUOUS_MEASUREMENTS = 0x21ac;
19static const uint16_t SCD4X_CMD_START_LOW_POWER_SINGLE_SHOT = 0x219d; // SCD41 only
20static const uint16_t SCD4X_CMD_START_LOW_POWER_SINGLE_SHOT_RHT_ONLY = 0x2196;
21static const uint16_t SCD4X_CMD_GET_DATA_READY_STATUS = 0xe4b8;
22static const uint16_t SCD4X_CMD_READ_MEASUREMENT = 0xec05;
23static const uint16_t SCD4X_CMD_PERFORM_FORCED_CALIBRATION = 0x362f;
24static const uint16_t SCD4X_CMD_STOP_MEASUREMENTS = 0x3f86;
25static const uint16_t SCD4X_CMD_FACTORY_RESET = 0x3632;
26static const uint16_t SCD4X_CMD_GET_FEATURESET = 0x202f;
27static const float SCD4X_TEMPERATURE_OFFSET_MULTIPLIER = (1 << 16) / 175.0f;
28
30 // the sensor needs 1000 ms to enter the idle state
31 this->set_timeout(1000, [this]() {
32 this->status_clear_error();
33 if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) {
34 ESP_LOGE(TAG, "Failed to stop measurements");
35 this->mark_failed();
36 return;
37 }
38 // According to the SCD4x datasheet the sensor will only respond to other commands after waiting 500 ms after
39 // issuing the stop_periodic_measurement command
40 this->set_timeout(500, [this]() {
41 uint16_t raw_serial_number[3];
42 if (!this->get_register(SCD4X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 3, 1)) {
43 ESP_LOGE(TAG, "Failed to read serial number");
45 this->mark_failed();
46 return;
47 }
48 ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8),
49 uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8));
50
51 if (!this->write_command(SCD4X_CMD_TEMPERATURE_OFFSET,
52 (uint16_t) (temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) {
53 ESP_LOGE(TAG, "Error setting temperature offset");
55 this->mark_failed();
56 return;
57 }
58
59 // If pressure compensation available use it, else use altitude
60 if (this->ambient_pressure_) {
62 ESP_LOGE(TAG, "Error setting ambient pressure compensation");
64 this->mark_failed();
65 return;
66 }
67 } else {
68 if (!this->write_command(SCD4X_CMD_ALTITUDE_COMPENSATION, this->altitude_compensation_)) {
69 ESP_LOGE(TAG, "Error setting altitude compensation");
71 this->mark_failed();
72 return;
73 }
74 }
75
76 if (!this->write_command(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, this->enable_asc_ ? 1 : 0)) {
77 ESP_LOGE(TAG, "Error setting automatic self calibration");
79 this->mark_failed();
80 return;
81 }
82
83 this->initialized_ = true;
84 // Finally start sensor measurements
85 this->start_measurement_();
86 });
87 });
88}
89
91 static const char *const MM_PERIODIC_STR = "Periodic (5s)";
92 static const char *const MM_LOW_POWER_PERIODIC_STR = "Low power periodic (30s)";
93 static const char *const MM_SINGLE_SHOT_STR = "Single shot";
94 static const char *const MM_SINGLE_SHOT_RHT_ONLY_STR = "Single shot rht only";
95 const char *measurement_mode_str = MM_PERIODIC_STR;
96
97 switch (this->measurement_mode_) {
98 case PERIODIC:
99 // measurement_mode_str = MM_PERIODIC_STR;
100 break;
102 measurement_mode_str = MM_LOW_POWER_PERIODIC_STR;
103 break;
104 case SINGLE_SHOT:
105 measurement_mode_str = MM_SINGLE_SHOT_STR;
106 break;
108 measurement_mode_str = MM_SINGLE_SHOT_RHT_ONLY_STR;
109 break;
110 }
111
112 ESP_LOGCONFIG(TAG, "SCD4X:");
113 LOG_I2C_DEVICE(this);
114 if (this->is_failed()) {
115 switch (this->error_code_) {
117 ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
118 break;
120 ESP_LOGW(TAG, "Measurement Initialization failed");
121 break;
123 ESP_LOGW(TAG, "Unable to read firmware version");
124 break;
125 default:
126 ESP_LOGW(TAG, "Unknown setup error");
127 break;
128 }
129 }
130 ESP_LOGCONFIG(TAG,
131 " Automatic self calibration: %s\n"
132 " Measurement mode: %s\n"
133 " Temperature offset: %.2f °C",
134 ONOFF(this->enable_asc_), measurement_mode_str, this->temperature_offset_);
135 if (this->ambient_pressure_source_ != nullptr) {
136 ESP_LOGCONFIG(TAG, " Dynamic ambient pressure compensation using '%s'",
138 } else {
139 if (this->ambient_pressure_) {
140 ESP_LOGCONFIG(TAG,
141 " Altitude compensation disabled\n"
142 " Ambient pressure compensation: %dmBar",
143 this->ambient_pressure_);
144 } else {
145 ESP_LOGCONFIG(TAG,
146 " Ambient pressure compensation disabled\n"
147 " Altitude compensation: %dm",
149 }
150 }
151 LOG_UPDATE_INTERVAL(this);
152 LOG_SENSOR(" ", "CO2", this->co2_sensor_);
153 LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
154 LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
155}
156
158 if (!this->initialized_) {
159 return;
160 }
161
162 if (this->ambient_pressure_source_ != nullptr) {
164 if (!std::isnan(pressure)) {
165 this->set_ambient_pressure_compensation(pressure);
166 }
167 }
168
169 uint32_t wait_time = 0;
171 this->start_measurement_();
172 wait_time =
173 this->measurement_mode_ == SINGLE_SHOT ? 5000 : 50; // Single shot measurement takes 5 secs rht mode 50 ms
174 }
175 this->set_timeout(wait_time, [this]() {
176 // Check if data is ready
177 if (!this->write_command(SCD4X_CMD_GET_DATA_READY_STATUS)) {
178 this->status_set_warning();
179 return;
180 }
181
182 uint16_t raw_read_status;
183
184 if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
185 this->status_set_warning();
186 ESP_LOGW(TAG, "Data not ready");
187 return;
188 }
189
190 if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) {
191 ESP_LOGW(TAG, "Error reading measurement");
192 this->status_set_warning();
193 return; // NO RETRY
194 }
195 // Read off sensor data
196 uint16_t raw_data[3];
197 if (!this->read_data(raw_data, 3)) {
198 this->status_set_warning();
199 return;
200 }
201 if (this->co2_sensor_ != nullptr)
202 this->co2_sensor_->publish_state(raw_data[0]);
203
204 if (this->temperature_sensor_ != nullptr) {
205 const float temperature = -45.0f + (175.0f * (raw_data[1])) / (1 << 16);
206 this->temperature_sensor_->publish_state(temperature);
207 }
208 if (this->humidity_sensor_ != nullptr) {
209 const float humidity = (100.0f * raw_data[2]) / (1 << 16);
210 this->humidity_sensor_->publish_state(humidity);
211 }
212 this->status_clear_warning();
213 }); // set_timeout
214}
215
216bool SCD4XComponent::perform_forced_calibration(uint16_t current_co2_concentration) {
217 /*
218 Operate the SCD4x in the operation mode later used in normal sensor operation (periodic measurement, low power
219 periodic measurement or single shot) for > 3 minutes in an environment with homogeneous and constant CO2
220 concentration before performing a forced recalibration.
221 */
222 if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) {
223 ESP_LOGE(TAG, "Failed to stop measurements");
224 this->status_set_warning();
225 }
226 this->set_timeout(500, [this, current_co2_concentration]() {
227 if (this->write_command(SCD4X_CMD_PERFORM_FORCED_CALIBRATION, current_co2_concentration)) {
228 ESP_LOGD(TAG, "Setting forced calibration Co2 level %d ppm", current_co2_concentration);
229 // frc takes 400 ms
230 // because this method will be used very rarly
231 // the simple approach with delay is ok
232 delay(400); // NOLINT
233 if (!this->start_measurement_()) {
234 return false;
235 } else {
236 ESP_LOGD(TAG, "Forced calibration complete");
237 }
238 return true;
239 } else {
240 ESP_LOGE(TAG, "Force calibration failed");
241 this->error_code_ = FRC_FAILED;
242 this->status_set_warning();
243 return false;
244 }
245 });
246 return true;
247}
248
250 if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) {
251 ESP_LOGE(TAG, "Failed to stop measurements");
252 this->status_set_warning();
253 return false;
254 }
255
256 this->set_timeout(500, [this]() {
257 if (!this->write_command(SCD4X_CMD_FACTORY_RESET)) {
258 ESP_LOGE(TAG, "Failed to send factory reset command");
259 this->status_set_warning();
260 return false;
261 }
262 ESP_LOGD(TAG, "Factory reset complete");
263 return true;
264 });
265 return true;
266}
267
269 uint16_t new_ambient_pressure = static_cast<uint16_t>(pressure_in_hpa);
270 if (!this->initialized_) {
271 this->ambient_pressure_ = new_ambient_pressure;
272 return;
273 }
274 // Only send pressure value if it has changed since last update
275 if (new_ambient_pressure != this->ambient_pressure_) {
276 this->update_ambient_pressure_compensation_(new_ambient_pressure);
277 this->ambient_pressure_ = new_ambient_pressure;
278 } else {
279 ESP_LOGD(TAG, "Ambient pressure compensation skipped; no change required");
280 }
281}
282
284 if (this->write_command(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) {
285 ESP_LOGD(TAG, "Setting ambient pressure compensation to %d hPa", pressure_in_hpa);
286 return true;
287 } else {
288 ESP_LOGE(TAG, "Error setting ambient pressure compensation");
289 return false;
290 }
291}
292
294 uint16_t measurement_command = SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS;
295 switch (this->measurement_mode_) {
296 case PERIODIC:
297 measurement_command = SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS;
298 break;
300 measurement_command = SCD4X_CMD_START_LOW_POWER_CONTINUOUS_MEASUREMENTS;
301 break;
302 case SINGLE_SHOT:
303 measurement_command = SCD4X_CMD_START_LOW_POWER_SINGLE_SHOT;
304 break;
306 measurement_command = SCD4X_CMD_START_LOW_POWER_SINGLE_SHOT_RHT_ONLY;
307 break;
308 }
309
310 static uint8_t remaining_retries = 3;
311 while (remaining_retries) {
312 if (!this->write_command(measurement_command)) {
313 ESP_LOGE(TAG, "Error starting measurements");
315 this->status_set_warning();
316 if (--remaining_retries == 0)
317 return false;
318 delay(50); // NOLINT wait 50 ms and try again
319 }
320 this->status_clear_warning();
321 return true;
322 }
323 return false;
324}
325
326} // namespace scd4x
327} // namespace esphome
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_set_warning(const char *message=nullptr)
void status_clear_warning()
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
const StringRef & get_name() const
constexpr const char * c_str() const
Definition string_ref.h:69
sensor::Sensor * humidity_sensor_
Definition scd4x.h:51
bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa)
Definition scd4x.cpp:283
sensor::Sensor * ambient_pressure_source_
Definition scd4x.h:52
bool perform_forced_calibration(uint16_t current_co2_concentration)
Definition scd4x.cpp:216
sensor::Sensor * temperature_sensor_
Definition scd4x.h:50
MeasurementMode measurement_mode_
Definition scd4x.h:59
sensor::Sensor * co2_sensor_
Definition scd4x.h:49
void set_ambient_pressure_compensation(float pressure_in_hpa)
Definition scd4x.cpp:268
void dump_config() override
Definition scd4x.cpp:90
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:45
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:133
@ COMMUNICATION_FAILED
Definition scd4x.h:12
@ SERIAL_NUMBER_IDENTIFICATION_FAILED
Definition scd4x.h:13
@ MEASUREMENT_INIT_FAILED
Definition scd4x.h:14
@ SINGLE_SHOT_RHT_ONLY
Definition scd4x.h:23
@ LOW_POWER_PERIODIC
Definition scd4x.h:21
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
uint16_t temperature
Definition sun_gtil2.cpp:12
uint8_t pressure
Definition tt21100.cpp:7