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