ESPHome  2024.4.0
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 
9 namespace esphome {
10 namespace scd30 {
11 
12 static const char *const TAG = "scd30";
13 
14 static const uint16_t SCD30_CMD_GET_FIRMWARE_VERSION = 0xd100;
15 static const uint16_t SCD30_CMD_START_CONTINUOUS_MEASUREMENTS = 0x0010;
16 static const uint16_t SCD30_CMD_ALTITUDE_COMPENSATION = 0x5102;
17 static const uint16_t SCD30_CMD_AUTOMATIC_SELF_CALIBRATION = 0x5306;
18 static const uint16_t SCD30_CMD_GET_DATA_READY_STATUS = 0x0202;
19 static const uint16_t SCD30_CMD_READ_MEASUREMENT = 0x0300;
20 
22 static const uint16_t SCD30_CMD_STOP_MEASUREMENTS = 0x0104;
23 static const uint16_t SCD30_CMD_MEASUREMENT_INTERVAL = 0x4600;
24 static const uint16_t SCD30_CMD_FORCED_CALIBRATION = 0x5204;
25 static const uint16_t SCD30_CMD_TEMPERATURE_OFFSET = 0x5403;
26 static const uint16_t SCD30_CMD_SOFT_RESET = 0xD304;
27 
29  ESP_LOGCONFIG(TAG, "Setting up scd30...");
30 
31 #ifdef USE_ESP8266
32  Wire.setClockStretchLimit(150000);
33 #endif
34 
36  uint16_t raw_firmware_version[3];
37  if (!this->get_register(SCD30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 3)) {
38  this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED;
39  this->mark_failed();
40  return;
41  }
42  ESP_LOGD(TAG, "SCD30 Firmware v%0d.%02d", (uint16_t(raw_firmware_version[0]) >> 8),
43  uint16_t(raw_firmware_version[0] & 0xFF));
44 
45  uint16_t temp_offset;
46  if (this->temperature_offset_ > 0) {
47  temp_offset = (this->temperature_offset_ * 100);
48  } else {
49  temp_offset = 0;
50  }
51 
52  if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, temp_offset)) {
53  ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
54  this->error_code_ = MEASUREMENT_INIT_FAILED;
55  this->mark_failed();
56  return;
57  }
58 #ifdef USE_ESP32
59  // According ESP32 clock stretching is typically 30ms and up to 150ms "due to
60  // internal calibration processes". The I2C peripheral only supports 13ms (at
61  // least when running at 80MHz).
62  // In practice it seems that clock stretching occurs during this calibration
63  // calls. It also seems that delays in between calls makes them
64  // disappear/shorter. Hence work around with delays for ESP32.
65  //
66  // By experimentation a delay of 20ms as already sufficient. Let's go
67  // safe and use 30ms delays.
68  delay(30);
69 #endif
70 
71  if (!this->write_command(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) {
72  ESP_LOGE(TAG, "Sensor SCD30 error setting update interval.");
73  this->error_code_ = MEASUREMENT_INIT_FAILED;
74  this->mark_failed();
75  return;
76  }
77 #ifdef USE_ESP32
78  delay(30);
79 #endif
80 
81  // The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on
82  if (this->altitude_compensation_ != 0xFFFF) {
83  if (!this->write_command(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
84  ESP_LOGE(TAG, "Sensor SCD30 error setting altitude compensation.");
85  this->error_code_ = MEASUREMENT_INIT_FAILED;
86  this->mark_failed();
87  return;
88  }
89  }
90 #ifdef USE_ESP32
91  delay(30);
92 #endif
93 
94  if (!this->write_command(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
95  ESP_LOGE(TAG, "Sensor SCD30 error setting automatic self calibration.");
96  this->error_code_ = MEASUREMENT_INIT_FAILED;
97  this->mark_failed();
98  return;
99  }
100 #ifdef USE_ESP32
101  delay(30);
102 #endif
103 
105  if (!this->write_command(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) {
106  ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements.");
107  this->error_code_ = MEASUREMENT_INIT_FAILED;
108  this->mark_failed();
109  return;
110  }
111 
112  // check each 500ms if data is ready, and read it in that case
113  this->set_interval("status-check", 500, [this]() {
114  if (this->is_data_ready_())
115  this->update();
116  });
117 }
118 
120  ESP_LOGCONFIG(TAG, "scd30:");
121  LOG_I2C_DEVICE(this);
122  if (this->is_failed()) {
123  switch (this->error_code_) {
125  ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
126  break;
128  ESP_LOGW(TAG, "Measurement Initialization failed!");
129  break;
131  ESP_LOGW(TAG, "Unable to read sensor firmware version");
132  break;
133  default:
134  ESP_LOGW(TAG, "Unknown setup error!");
135  break;
136  }
137  }
138  if (this->altitude_compensation_ == 0xFFFF) {
139  ESP_LOGCONFIG(TAG, " Altitude compensation: OFF");
140  } else {
141  ESP_LOGCONFIG(TAG, " Altitude compensation: %dm", this->altitude_compensation_);
142  }
143  ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_));
144  ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_compensation_);
145  ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_);
146  ESP_LOGCONFIG(TAG, " Update interval: %ds", 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;
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 scd30
233 } // namespace esphome
void set_interval(const std::string &name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a unique name.
Definition: component.cpp:52
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
void setup() override
Definition: scd30.cpp:28
bool write_command(T i2c_register)
Write a command to the i2c device.
Definition: i2c_sensirion.h:82
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition: component.cpp:69
bool read_data(uint16_t *data, uint8_t len)
Read data words from i2c device.
bool force_recalibration_with_reference(uint16_t co2_reference)
Definition: scd30.cpp:210
sensor::Sensor * humidity_sensor_
Definition: scd30.h:48
sensor::Sensor * co2_sensor_
Definition: scd30.h:47
sensor::Sensor * temperature_sensor_
Definition: scd30.h:49
void status_clear_warning()
Definition: component.cpp:166
uint16_t get_forced_calibration_reference()
Definition: scd30.cpp:223
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
uint16_t altitude_compensation_
Definition: scd30.h:42
uint16_t temperature
Definition: sun_gtil2.cpp:26
bool get_register(uint16_t command, uint16_t *data, uint8_t len, uint8_t delay=0)
get data words from i2c register.
Definition: i2c_sensirion.h:43
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
uint16_t ambient_pressure_compensation_
Definition: scd30.h:43
void dump_config() override
Definition: scd30.cpp:119
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26