ESPHome  2022.5.1
sgp40.cpp
Go to the documentation of this file.
1 #include "sgp40.h"
2 #include "esphome/core/log.h"
3 #include "esphome/core/hal.h"
4 #include <cinttypes>
5 
6 namespace esphome {
7 namespace sgp40 {
8 
9 static const char *const TAG = "sgp40";
10 
12  ESP_LOGCONFIG(TAG, "Setting up SGP40...");
13 
14  // Serial Number identification
15  if (!this->write_command(SGP40_CMD_GET_SERIAL_ID)) {
16  this->error_code_ = COMMUNICATION_FAILED;
17  this->mark_failed();
18  return;
19  }
20  uint16_t raw_serial_number[3];
21 
22  if (!this->read_data(raw_serial_number, 3)) {
23  this->mark_failed();
24  return;
25  }
26  this->serial_number_ = (uint64_t(raw_serial_number[0]) << 24) | (uint64_t(raw_serial_number[1]) << 16) |
27  (uint64_t(raw_serial_number[2]));
28  ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
29 
30  // Featureset identification for future use
31  if (!this->write_command(SGP40_CMD_GET_FEATURESET)) {
32  ESP_LOGD(TAG, "raw_featureset write_command_ failed");
33  this->mark_failed();
34  return;
35  }
36  uint16_t raw_featureset;
37  if (!this->read_data(raw_featureset)) {
38  ESP_LOGD(TAG, "raw_featureset read_data_ failed");
39  this->mark_failed();
40  return;
41  }
42 
43  this->featureset_ = raw_featureset;
44  if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) {
45  ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF),
46  SGP40_FEATURESET);
47  this->mark_failed();
48  return;
49  }
50 
51  ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF));
52 
54 
55  if (this->store_baseline_) {
56  // Hash with compilation time
57  // This ensures the baseline storage is cleared after OTA
58  uint32_t hash = fnv1_hash(App.get_compilation_time());
60 
61  if (this->pref_.load(&this->baselines_storage_)) {
62  this->state0_ = this->baselines_storage_.state0;
63  this->state1_ = this->baselines_storage_.state1;
64  ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04X, state1: 0x%04X", this->baselines_storage_.state0,
66  }
67 
68  // Initialize storage timestamp
69  this->seconds_since_last_store_ = 0;
70 
71  if (this->baselines_storage_.state0 > 0 && this->baselines_storage_.state1 > 0) {
72  ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04X, state1: 0x%04X", this->baselines_storage_.state0,
75  this->baselines_storage_.state1);
76  }
77  }
78 
79  this->self_test_();
80 
81  /* The official spec for this sensor at https://docs.rs-online.com/1956/A700000007055193.pdf
82  indicates this sensor should be driven at 1Hz. Comments from the developers at:
83  https://github.com/Sensirion/embedded-sgp/issues/136 indicate the algorithm should be a bit
84  resilient to slight timing variations so the software timer should be accurate enough for
85  this.
86 
87  This block starts sampling from the sensor at 1Hz, and is done seperately from the call
88  to the update method. This seperation is to support getting accurate measurements but
89  limit the amount of communication done over wifi for power consumption or to keep the
90  number of records reported from being overwhelming.
91  */
92  ESP_LOGD(TAG, "Component requires sampling of 1Hz, setting up background sampler");
93  this->set_interval(1000, [this]() { this->update_voc_index(); });
94 }
95 
97  ESP_LOGD(TAG, "Self-test started");
98  if (!this->write_command(SGP40_CMD_SELF_TEST)) {
99  this->error_code_ = COMMUNICATION_FAILED;
100  ESP_LOGD(TAG, "Self-test communication failed");
101  this->mark_failed();
102  }
103 
104  this->set_timeout(250, [this]() {
105  uint16_t reply;
106  if (!this->read_data(reply)) {
107  ESP_LOGD(TAG, "Self-test read_data_ failed");
108  this->mark_failed();
109  return;
110  }
111 
112  if (reply == 0xD400) {
113  this->self_test_complete_ = true;
114  ESP_LOGD(TAG, "Self-test completed");
115  return;
116  }
117 
118  ESP_LOGD(TAG, "Self-test failed");
119  this->mark_failed();
120  });
121 }
122 
132  int32_t voc_index;
133 
134  uint16_t sraw = measure_raw_();
135 
136  if (sraw == UINT16_MAX)
137  return UINT16_MAX;
138 
139  this->status_clear_warning();
140 
141  voc_algorithm_process(&voc_algorithm_params_, sraw, &voc_index);
142 
143  // Store baselines after defined interval or if the difference between current and stored baseline becomes too
144  // much
147  if ((uint32_t) abs(this->baselines_storage_.state0 - this->state0_) > MAXIMUM_STORAGE_DIFF ||
148  (uint32_t) abs(this->baselines_storage_.state1 - this->state1_) > MAXIMUM_STORAGE_DIFF) {
149  this->seconds_since_last_store_ = 0;
150  this->baselines_storage_.state0 = this->state0_;
151  this->baselines_storage_.state1 = this->state1_;
152 
153  if (this->pref_.save(&this->baselines_storage_)) {
154  ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04X ,state1: 0x%04X", this->baselines_storage_.state0,
156  } else {
157  ESP_LOGW(TAG, "Could not store VOC baselines");
158  }
159  }
160  }
161 
162  return voc_index;
163 }
164 
173  float humidity = NAN;
174 
175  if (!this->self_test_complete_) {
176  ESP_LOGD(TAG, "Self-test not yet complete");
177  return UINT16_MAX;
178  }
179 
180  if (this->humidity_sensor_ != nullptr) {
181  humidity = this->humidity_sensor_->state;
182  }
183  if (std::isnan(humidity) || humidity < 0.0f || humidity > 100.0f) {
184  humidity = 50;
185  }
186 
187  float temperature = NAN;
188  if (this->temperature_sensor_ != nullptr) {
189  temperature = float(this->temperature_sensor_->state);
190  }
191  if (std::isnan(temperature) || temperature < -40.0f || temperature > 85.0f) {
192  temperature = 25;
193  }
194 
195  uint16_t data[2];
196  uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100));
197  uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175);
198  // first paramater is the relative humidity ticks
199  data[0] = rhticks;
200  // second paramater is the temperature ticks
201  data[1] = tempticks;
202 
203  if (!this->write_command(SGP40_CMD_MEASURE_RAW, data, 2)) {
204  this->status_set_warning();
205  ESP_LOGD(TAG, "write error (%d)", this->last_error_);
206  return false;
207  }
208  delay(30);
209 
210  uint16_t raw_data;
211  if (!this->read_data(raw_data)) {
212  this->status_set_warning();
213  ESP_LOGD(TAG, "read_data_ error");
214  return UINT16_MAX;
215  }
216  return raw_data;
217 }
218 
220  this->seconds_since_last_store_ += 1;
221 
222  this->voc_index_ = this->measure_voc_index_();
223  if (this->samples_read_ < this->samples_to_stabalize_) {
224  this->samples_read_++;
225  ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %u", this->samples_read_,
226  this->samples_to_stabalize_, this->voc_index_);
227  return;
228  }
229 }
230 
232  if (this->samples_read_ < this->samples_to_stabalize_) {
233  return;
234  }
235 
236  if (this->voc_index_ != UINT16_MAX) {
237  this->status_clear_warning();
238  this->publish_state(this->voc_index_);
239  } else {
240  this->status_set_warning();
241  }
242 }
243 
245  ESP_LOGCONFIG(TAG, "SGP40:");
246  LOG_I2C_DEVICE(this);
247  ESP_LOGCONFIG(TAG, " store_baseline: %d", this->store_baseline_);
248 
249  if (this->is_failed()) {
250  switch (this->error_code_) {
252  ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
253  break;
254  default:
255  ESP_LOGW(TAG, "Unknown setup error!");
256  break;
257  }
258  } else {
259  ESP_LOGCONFIG(TAG, " Serial number: %" PRIu64, this->serial_number_);
260  ESP_LOGCONFIG(TAG, " Minimum Samples: %f", VOC_ALGORITHM_INITIAL_BLACKOUT);
261  }
262  LOG_UPDATE_INTERVAL(this);
263 
264  if (this->humidity_sensor_ != nullptr && this->temperature_sensor_ != nullptr) {
265  ESP_LOGCONFIG(TAG, " Compensation:");
266  LOG_SENSOR(" ", "Temperature Source:", this->temperature_sensor_);
267  LOG_SENSOR(" ", "Humidity Source:", this->humidity_sensor_);
268  } else {
269  ESP_LOGCONFIG(TAG, " Compensation: No source configured");
270  }
271 }
272 
273 } // namespace sgp40
274 } // 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:50
ESPPreferenceObject pref_
Definition: sgp40.h:66
int32_t measure_voc_index_()
Combined the measured gasses, temperature, and humidity to calculate the VOC Index.
Definition: sgp40.cpp:131
void voc_algorithm_set_states(VocAlgorithmParams *params, int32_t state0, int32_t state1)
Set previously retrieved algorithm states to resume operation after a short interruption, skipping initial learning phase.
bool write_command(T i2c_register)
Write a command to the i2c device.
Definition: i2c_sensirion.h:80
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:67
float temperature
Definition: qmp6988.h:71
bool read_data(uint16_t *data, uint8_t len)
Read data words from i2c device.
void self_test_()
Request the sensor to perform a self-test, returning the result.
Definition: sgp40.cpp:96
bool save(const T *src)
Definition: preferences.h:21
SGP40Baselines baselines_storage_
Definition: sgp40.h:68
void voc_algorithm_init(VocAlgorithmParams *params)
Initialize the VOC algorithm parameters.
float state
This member variable stores the last state that has passed through all filters.
Definition: sensor.h:132
void update() override
Definition: sgp40.cpp:231
sensor::Sensor * temperature_sensor_
Definition: sgp40.h:58
ESPPreferences * global_preferences
uint16_t measure_raw_()
Return the raw gas measurement.
Definition: sgp40.cpp:172
void status_clear_warning()
Definition: component.cpp:148
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:70
Application App
Global storage of Application pointer - only one Application can exist.
void status_set_warning()
Definition: component.cpp:140
void voc_algorithm_process(VocAlgorithmParams *params, int32_t sraw, int32_t *voc_index)
Calculate the VOC index value from the raw sensor value.
const std::string & get_compilation_time() const
Definition: application.h:132
VocAlgorithmParams voc_algorithm_params_
Definition: sgp40.h:69
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
uint32_t fnv1_hash(const std::string &str)
Calculate a FNV-1 hash of str.
Definition: helpers.cpp:65
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:111
i2c::ErrorCode last_error_
last error code from i2c operation
const uint32_t MAXIMUM_STORAGE_DIFF
Definition: sgp40.h:38
const uint32_t SHORTEST_BASELINE_STORE_INTERVAL
Definition: sgp40.h:35
Definition: a4988.cpp:4
sensor::Sensor * humidity_sensor_
Input sensor for humidity and temperature compensation.
Definition: sgp40.h:57
void setup() override
Definition: sgp40.cpp:11
void dump_config() override
Definition: sgp40.cpp:244
uint32_t seconds_since_last_store_
Definition: sgp40.h:67
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:27
void voc_algorithm_get_states(VocAlgorithmParams *params, int32_t *state0, int32_t *state1)
Get current algorithm states.