ESPHome  2023.5.5
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  if (this->temperature_offset_ != 0) {
46  if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t) (temperature_offset_ * 100.0))) {
47  ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
48  this->error_code_ = MEASUREMENT_INIT_FAILED;
49  this->mark_failed();
50  return;
51  }
52  }
53 #ifdef USE_ESP32
54  // According ESP32 clock stretching is typically 30ms and up to 150ms "due to
55  // internal calibration processes". The I2C peripheral only supports 13ms (at
56  // least when running at 80MHz).
57  // In practice it seems that clock stretching occurs during this calibration
58  // calls. It also seems that delays in between calls makes them
59  // disappear/shorter. Hence work around with delays for ESP32.
60  //
61  // By experimentation a delay of 20ms as already sufficient. Let's go
62  // safe and use 30ms delays.
63  delay(30);
64 #endif
65 
66  if (!this->write_command(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) {
67  ESP_LOGE(TAG, "Sensor SCD30 error setting update interval.");
68  this->error_code_ = MEASUREMENT_INIT_FAILED;
69  this->mark_failed();
70  return;
71  }
72 #ifdef USE_ESP32
73  delay(30);
74 #endif
75 
76  // The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on
77  if (this->altitude_compensation_ != 0xFFFF) {
78  if (!this->write_command(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
79  ESP_LOGE(TAG, "Sensor SCD30 error setting altitude compensation.");
80  this->error_code_ = MEASUREMENT_INIT_FAILED;
81  this->mark_failed();
82  return;
83  }
84  }
85 #ifdef USE_ESP32
86  delay(30);
87 #endif
88 
89  if (!this->write_command(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
90  ESP_LOGE(TAG, "Sensor SCD30 error setting automatic self calibration.");
91  this->error_code_ = MEASUREMENT_INIT_FAILED;
92  this->mark_failed();
93  return;
94  }
95 #ifdef USE_ESP32
96  delay(30);
97 #endif
98 
100  if (!this->write_command(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) {
101  ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements.");
102  this->error_code_ = MEASUREMENT_INIT_FAILED;
103  this->mark_failed();
104  return;
105  }
106 
107  // check each 500ms if data is ready, and read it in that case
108  this->set_interval("status-check", 500, [this]() {
109  if (this->is_data_ready_())
110  this->update();
111  });
112 }
113 
115  ESP_LOGCONFIG(TAG, "scd30:");
116  LOG_I2C_DEVICE(this);
117  if (this->is_failed()) {
118  switch (this->error_code_) {
120  ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
121  break;
123  ESP_LOGW(TAG, "Measurement Initialization failed!");
124  break;
126  ESP_LOGW(TAG, "Unable to read sensor firmware version");
127  break;
128  default:
129  ESP_LOGW(TAG, "Unknown setup error!");
130  break;
131  }
132  }
133  if (this->altitude_compensation_ == 0xFFFF) {
134  ESP_LOGCONFIG(TAG, " Altitude compensation: OFF");
135  } else {
136  ESP_LOGCONFIG(TAG, " Altitude compensation: %dm", this->altitude_compensation_);
137  }
138  ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_));
139  ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_compensation_);
140  ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_);
141  ESP_LOGCONFIG(TAG, " Update interval: %ds", this->update_interval_);
142  LOG_SENSOR(" ", "CO2", this->co2_sensor_);
143  LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
144  LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
145 }
146 
148  uint16_t raw_read_status;
149  if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
150  this->status_set_warning();
151  ESP_LOGW(TAG, "Data not ready yet!");
152  return;
153  }
154 
155  if (!this->write_command(SCD30_CMD_READ_MEASUREMENT)) {
156  ESP_LOGW(TAG, "Error reading measurement!");
157  this->status_set_warning();
158  return;
159  }
160 
161  this->set_timeout(50, [this]() {
162  uint16_t raw_data[6];
163  if (!this->read_data(raw_data, 6)) {
164  this->status_set_warning();
165  return;
166  }
167 
168  union uint32_float_t {
169  uint32_t uint32;
170  float value;
171  };
172  uint32_t temp_c_o2_u32 = (((uint32_t(raw_data[0])) << 16) | (uint32_t(raw_data[1])));
173  uint32_float_t co2{.uint32 = temp_c_o2_u32};
174 
175  uint32_t temp_temp_u32 = (((uint32_t(raw_data[2])) << 16) | (uint32_t(raw_data[3])));
176  uint32_float_t temperature{.uint32 = temp_temp_u32};
177 
178  uint32_t temp_hum_u32 = (((uint32_t(raw_data[4])) << 16) | (uint32_t(raw_data[5])));
179  uint32_float_t humidity{.uint32 = temp_hum_u32};
180 
181  ESP_LOGD(TAG, "Got CO2=%.2fppm temperature=%.2f°C humidity=%.2f%%", co2.value, temperature.value, humidity.value);
182  if (this->co2_sensor_ != nullptr)
183  this->co2_sensor_->publish_state(co2.value);
184  if (this->temperature_sensor_ != nullptr)
186  if (this->humidity_sensor_ != nullptr)
187  this->humidity_sensor_->publish_state(humidity.value);
188 
189  this->status_clear_warning();
190  });
191 }
192 
194  if (!this->write_command(SCD30_CMD_GET_DATA_READY_STATUS)) {
195  return false;
196  }
197  delay(4);
198  uint16_t is_data_ready;
199  if (!this->read_data(&is_data_ready, 1)) {
200  return false;
201  }
202  return is_data_ready == 1;
203 }
204 
206  ESP_LOGD(TAG, "Performing CO2 force recalibration with reference %dppm.", co2_reference);
207  if (this->write_command(SCD30_CMD_FORCED_CALIBRATION, co2_reference)) {
208  ESP_LOGD(TAG, "Force recalibration complete.");
209  return true;
210  } else {
211  ESP_LOGE(TAG, "Failed to force recalibration with reference.");
212  this->error_code_ = FORCE_RECALIBRATION_FAILED;
213  this->status_set_warning();
214  return false;
215  }
216 }
217 
219  uint16_t forced_calibration_reference;
220  // Get current CO2 calibration
221  if (!this->get_register(SCD30_CMD_FORCED_CALIBRATION, forced_calibration_reference)) {
222  ESP_LOGE(TAG, "Unable to read forced calibration reference.");
223  }
224  return forced_calibration_reference;
225 }
226 
227 } // namespace scd30
228 } // 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:51
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:68
float temperature
Definition: qmp6988.h:71
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:205
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:153
uint16_t get_forced_calibration_reference()
Definition: scd30.cpp:218
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
void status_set_warning()
Definition: component.cpp:145
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:112
Definition: a4988.cpp:4
uint16_t ambient_pressure_compensation_
Definition: scd30.h:43
void dump_config() override
Definition: scd30.cpp:114
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:28