ESPHome  2024.5.2
veml7700.cpp
Go to the documentation of this file.
1 #include "veml7700.h"
3 #include "esphome/core/log.h"
4 
5 namespace esphome {
6 namespace veml7700 {
7 
8 static const char *const TAG = "veml7700";
9 static const size_t VEML_REG_SIZE = 2;
10 
11 static float reduce_to_zero(float a, float b) { return (a > b) ? (a - b) : 0; }
12 
13 template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {
14  size_t i = 0;
15  size_t idx = -1;
16  while (idx == -1 && i < size) {
17  if (array[i] == val) {
18  idx = i;
19  break;
20  }
21  i++;
22  }
23  if (idx == -1 || i + 1 >= size)
24  return val;
25  return array[i + 1];
26 }
27 
28 template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) {
29  size_t i = size - 1;
30  size_t idx = -1;
31  while (idx == -1 && i > 0) {
32  if (array[i] == val) {
33  idx = i;
34  break;
35  }
36  i--;
37  }
38  if (idx == -1 || i == 0)
39  return val;
40  return array[i - 1];
41 }
42 
43 static uint16_t get_itime_ms(IntegrationTime time) {
44  uint16_t ms = 0;
45  switch (time) {
47  ms = 100;
48  break;
50  ms = 200;
51  break;
53  ms = 400;
54  break;
56  ms = 800;
57  break;
59  ms = 50;
60  break;
62  ms = 25;
63  break;
64  default:
65  ms = 100;
66  }
67  return ms;
68 }
69 
70 static float get_gain_coeff(Gain gain) {
71  static const float GAIN_FLOAT[GAINS_COUNT] = {1.0f, 2.0f, 0.125f, 0.25f};
72  return GAIN_FLOAT[gain & 0b11];
73 }
74 
75 static const char *get_gain_str(Gain gain) {
76  static const char *gain_str[GAINS_COUNT] = {"1x", "2x", "1/8x", "1/4x"};
77  return gain_str[gain & 0b11];
78 }
79 
81  ESP_LOGCONFIG(TAG, "Setting up VEML7700/6030...");
82 
83  auto err = this->configure_();
84  if (err != i2c::ERROR_OK) {
85  ESP_LOGW(TAG, "Sensor configuration failed");
86  this->mark_failed();
87  } else {
88  this->state_ = State::INITIAL_SETUP_COMPLETED;
89  }
90 }
91 
93  LOG_I2C_DEVICE(this);
94  ESP_LOGCONFIG(TAG, " Automatic gain/time: %s", YESNO(this->automatic_mode_enabled_));
95  if (!this->automatic_mode_enabled_) {
96  ESP_LOGCONFIG(TAG, " Gain: %s", get_gain_str(this->gain_));
97  ESP_LOGCONFIG(TAG, " Integration time: %d ms", get_itime_ms(this->integration_time_));
98  }
99  ESP_LOGCONFIG(TAG, " Lux compensation: %s", YESNO(this->lux_compensation_enabled_));
100  ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_);
101  LOG_UPDATE_INTERVAL(this);
102 
103  LOG_SENSOR(" ", "ALS channel lux", this->ambient_light_sensor_);
104  LOG_SENSOR(" ", "ALS channel counts", this->ambient_light_counts_sensor_);
105  LOG_SENSOR(" ", "WHITE channel lux", this->white_sensor_);
106  LOG_SENSOR(" ", "WHITE channel counts", this->white_counts_sensor_);
107  LOG_SENSOR(" ", "FAKE_IR channel lux", this->fake_infrared_sensor_);
108  LOG_SENSOR(" ", "Actual gain", this->actual_gain_sensor_);
109  LOG_SENSOR(" ", "Actual integration time", this->actual_integration_time_sensor_);
110 
111  if (this->is_failed()) {
112  ESP_LOGE(TAG, "Communication with I2C VEML7700/6030 failed!");
113  }
114 }
115 
117  if (this->is_ready() && this->state_ == State::IDLE) {
118  ESP_LOGV(TAG, "Update: Initiating new data collection");
119 
121 
122  this->readings_.als_counts = 0;
123  this->readings_.white_counts = 0;
125  this->readings_.actual_gain = this->gain_;
126  this->readings_.als_lux = 0;
127  this->readings_.white_lux = 0;
128  this->readings_.fake_infrared_lux = 0;
129  } else {
130  ESP_LOGV(TAG, "Update: Component not ready yet");
131  }
132 }
133 
135  ErrorCode err = i2c::ERROR_OK;
136 
137  if (this->state_ == State::INITIAL_SETUP_COMPLETED) {
138  // Datasheet: 2.5 ms before the first measurement is needed, allowing for the correct start of the signal processor
139  // and oscillator.
140  // Reality: wait for couple integration times to have first samples captured
141  this->set_timeout(2 * this->integration_time_, [this]() { this->state_ = State::IDLE; });
142  }
143 
144  if (this->is_ready()) {
145  switch (this->state_) {
146  case State::IDLE:
147  // doing nothing, having best time
148  break;
149 
151  err = this->read_sensor_output_(this->readings_);
152  this->state_ = (err == i2c::ERROR_OK) ? State::DATA_COLLECTED : State::IDLE;
153  break;
154 
155  case State::COLLECTING_DATA_AUTO: // Automatic mode - we start here to reconfigure device first
157  if (!this->are_adjustments_required_(this->readings_)) {
158  this->state_ = State::READY_TO_PUBLISH_PART_1;
159  } else {
160  // if sensitivity adjustment needed -
161  // shutdown device to change config and wait one integration time period
162  this->state_ = State::ADJUSTMENT_IN_PROGRESS;
163  err = this->reconfigure_time_and_gain_(this->readings_.actual_time, this->readings_.actual_gain, true);
164  if (err == i2c::ERROR_OK) {
165  this->set_timeout(1 * get_itime_ms(this->readings_.actual_time),
166  [this]() { this->state_ = State::READY_TO_APPLY_ADJUSTMENTS; });
167  } else {
168  this->state_ = State::IDLE;
169  }
170  }
171  break;
172 
174  // nothing to be done, just waiting for the timeout
175  break;
176 
178  // second stage of sensitivity adjustment - turn device back on
179  // and wait 2-3 integration time periods to get good data samples
180  this->state_ = State::ADJUSTMENT_IN_PROGRESS;
181  err = this->reconfigure_time_and_gain_(this->readings_.actual_time, this->readings_.actual_gain, false);
182  if (err == i2c::ERROR_OK) {
183  this->set_timeout(3 * get_itime_ms(this->readings_.actual_time),
184  [this]() { this->state_ = State::COLLECTING_DATA; });
185  } else {
186  this->state_ = State::IDLE;
187  }
188  break;
189 
191  this->status_clear_warning();
192 
193  this->apply_lux_calculation_(this->readings_);
194  this->apply_lux_compensation_(this->readings_);
195  this->apply_glass_attenuation_(this->readings_);
196 
197  this->publish_data_part_1_(this->readings_);
198  this->state_ = State::READY_TO_PUBLISH_PART_2;
199  break;
200 
202  this->publish_data_part_2_(this->readings_);
203  this->state_ = State::READY_TO_PUBLISH_PART_3;
204  break;
205 
207  this->publish_data_part_3_(this->readings_);
208  this->state_ = State::IDLE;
209  break;
210 
211  default:
212  break;
213  }
214  if (err != i2c::ERROR_OK)
215  this->status_set_warning();
216  }
217 }
218 
220  ESP_LOGV(TAG, "Configure");
221 
222  ConfigurationRegister als_conf{0};
223  als_conf.ALS_INT_EN = false;
224  als_conf.ALS_PERS = Persistence::PERSISTENCE_1;
225  als_conf.ALS_IT = this->integration_time_;
226  als_conf.ALS_GAIN = this->gain_;
227 
228  als_conf.ALS_SD = true;
229  ESP_LOGV(TAG, "Shutdown before config. ALS_CONF_0 to 0x%04X", als_conf.raw);
230  auto err = this->write_register((uint8_t) CommandRegisters::ALS_CONF_0, als_conf.raw_bytes, VEML_REG_SIZE);
231  if (err != i2c::ERROR_OK) {
232  ESP_LOGW(TAG, "Failed to shutdown, I2C error %d", err);
233  return err;
234  }
235  delay(3);
236 
237  als_conf.ALS_SD = false;
238  ESP_LOGV(TAG, "Turning on. Setting ALS_CONF_0 to 0x%04X", als_conf.raw);
239  err = this->write_register((uint8_t) CommandRegisters::ALS_CONF_0, als_conf.raw_bytes, VEML_REG_SIZE);
240  if (err != i2c::ERROR_OK) {
241  ESP_LOGW(TAG, "Failed to turn on, I2C error %d", err);
242  return err;
243  }
244 
245  PSMRegister psm{0};
246  psm.PSM = PSM::PSM_MODE_1;
247  psm.PSM_EN = false;
248  ESP_LOGV(TAG, "Setting PSM to 0x%04X", psm.raw);
249  err = this->write_register((uint8_t) CommandRegisters::PWR_SAVING, psm.raw_bytes, VEML_REG_SIZE);
250  if (err != i2c::ERROR_OK) {
251  ESP_LOGW(TAG, "Failed to set PSM, I2C error %d", err);
252  return err;
253  }
254 
255  return err;
256 }
257 
259  ESP_LOGV(TAG, "Reconfigure time and gain (%d ms, %s) %s", get_itime_ms(time), get_gain_str(gain),
260  shutdown ? "Shutting down" : "Turning back on");
261 
262  ConfigurationRegister als_conf{0};
263  als_conf.raw = 0;
264 
265  // We have to before changing parameters
266  als_conf.ALS_SD = shutdown;
267  als_conf.ALS_INT_EN = false;
268  als_conf.ALS_PERS = Persistence::PERSISTENCE_1;
269  als_conf.ALS_IT = time;
270  als_conf.ALS_GAIN = gain;
271  auto err = this->write_register((uint8_t) CommandRegisters::ALS_CONF_0, als_conf.raw_bytes, VEML_REG_SIZE);
272  if (err != i2c::ERROR_OK) {
273  ESP_LOGW(TAG, "%s failed", shutdown ? "Shutdown" : "Turn on");
274  }
275 
276  return err;
277 }
278 
280  auto als_err =
281  this->read_register((uint8_t) CommandRegisters::ALS, (uint8_t *) &data.als_counts, VEML_REG_SIZE, false);
282  if (als_err != i2c::ERROR_OK) {
283  ESP_LOGW(TAG, "Error reading ALS register, err = %d", als_err);
284  }
285  auto white_err =
286  this->read_register((uint8_t) CommandRegisters::WHITE, (uint8_t *) &data.white_counts, VEML_REG_SIZE, false);
287  if (white_err != i2c::ERROR_OK) {
288  ESP_LOGW(TAG, "Error reading WHITE register, err = %d", white_err);
289  }
290 
291  ConfigurationRegister conf{0};
292  auto err =
293  this->read_register((uint8_t) CommandRegisters::ALS_CONF_0, (uint8_t *) conf.raw_bytes, VEML_REG_SIZE, false);
294  if (err != i2c::ERROR_OK) {
295  ESP_LOGW(TAG, "Error reading ALS_CONF_0 register, err = %d", white_err);
296  }
297  data.actual_time = conf.ALS_IT;
298  data.actual_gain = conf.ALS_GAIN;
299 
300  ESP_LOGV(TAG, "Data from sensors: ALS = %d, WHITE = %d, Gain = %s, Time = %d ms", data.als_counts, data.white_counts,
301  get_gain_str(data.actual_gain), get_itime_ms(data.actual_time));
302  return std::max(als_err, white_err);
303 }
304 
306  // skip first sample in auto mode -
307  // we need to reconfigure device after last measurement
308  if (this->state_ == State::COLLECTING_DATA_AUTO)
309  return true;
310 
311  if (!this->automatic_mode_enabled_)
312  return false;
313 
314  // Recommended thresholds as per datasheet
315  static constexpr uint16_t LOW_INTENSITY_THRESHOLD = 100;
316  static constexpr uint16_t HIGH_INTENSITY_THRESHOLD = 10000;
317 
321  static const Gain GAINS[GAINS_COUNT] = {X_1_8, X_1_4, X_1, X_2};
322 
323  if (data.als_counts <= LOW_INTENSITY_THRESHOLD) {
324  Gain next_gain = get_next(GAINS, data.actual_gain);
325  if (next_gain != data.actual_gain) {
326  data.actual_gain = next_gain;
327  return true;
328  }
329  IntegrationTime next_time = get_next(TIMES, data.actual_time);
330  if (next_time != data.actual_time) {
331  data.actual_time = next_time;
332  return true;
333  }
334  } else if (data.als_counts >= HIGH_INTENSITY_THRESHOLD) {
335  Gain prev_gain = get_prev(GAINS, data.actual_gain);
336  if (prev_gain != data.actual_gain) {
337  data.actual_gain = prev_gain;
338  return true;
339  }
340  IntegrationTime prev_time = get_prev(TIMES, data.actual_time);
341  if (prev_time != data.actual_time) {
342  data.actual_time = prev_time;
343  return true;
344  }
345  }
346 
347  // Counts are either good (between thresholds)
348  // or there is no room to change sensitivity anymore
349  return false;
350 }
351 
353  static const float MAX_GAIN = 2.0f;
354  static const float MAX_ITIME_MS = 800.0f;
355  static const float MAX_LX_RESOLUTION = 0.0036f;
356  float lux_resolution = (MAX_ITIME_MS / (float) get_itime_ms(data.actual_time)) *
357  (MAX_GAIN / get_gain_coeff(data.actual_gain)) * MAX_LX_RESOLUTION;
358  ESP_LOGV(TAG, "Lux resolution for (%d, %s) = %.4f ", get_itime_ms(data.actual_time), get_gain_str(data.actual_gain),
359  lux_resolution);
360 
361  data.als_lux = lux_resolution * (float) data.als_counts;
362  data.white_lux = lux_resolution * (float) data.white_counts;
363  data.fake_infrared_lux = reduce_to_zero(data.white_lux, data.als_lux);
364 
365  ESP_LOGV(TAG, "%s mode - ALS = %.1f lx, WHITE = %.1f lx, FAKE_IR = %.1f lx",
366  this->automatic_mode_enabled_ ? "Automatic" : "Manual", data.als_lux, data.white_lux,
367  data.fake_infrared_lux);
368 }
369 
371  if (!this->lux_compensation_enabled_)
372  return;
373  auto &local_data = data;
374  // Always apply correction for G1/4 and G1/8
375  // Other Gains G1 and G2 are not supposed to be used for lux > 1000,
376  // corrections may help, but not a lot.
377  //
378  // "Illumination values higher than 1000 lx show non-linearity.
379  // This non-linearity is the same for all sensors, so a compensation formula can be applied
380  // if this light level is exceeded"
381  auto compensate = [&local_data](float &lux) {
382  auto calculate_high_lux_compensation = [](float lux_veml) -> float {
383  return (((6.0135e-13 * lux_veml - 9.3924e-9) * lux_veml + 8.1488e-5) * lux_veml + 1.0023) * lux_veml;
384  };
385 
386  if (lux > 1000.0f || local_data.actual_gain == Gain::X_1_8 || local_data.actual_gain == Gain::X_1_4) {
387  lux = calculate_high_lux_compensation(lux);
388  }
389  };
390 
391  compensate(data.als_lux);
392  compensate(data.white_lux);
393  data.fake_infrared_lux = reduce_to_zero(data.white_lux, data.als_lux);
394 
395  ESP_LOGV(TAG, "Lux compensation - ALS = %.1f lx, WHITE = %.1f lx, FAKE_IR = %.1f lx", data.als_lux, data.white_lux,
396  data.fake_infrared_lux);
397 }
398 
400  data.als_lux *= this->glass_attenuation_factor_;
401  data.white_lux *= this->glass_attenuation_factor_;
402  data.fake_infrared_lux = reduce_to_zero(data.white_lux, data.als_lux);
403  ESP_LOGV(TAG, "Glass attenuation - ALS = %.1f lx, WHITE = %.1f lx, FAKE_IR = %.1f lx", data.als_lux, data.white_lux,
404  data.fake_infrared_lux);
405 }
406 
408  if (this->ambient_light_sensor_ != nullptr) {
410  }
411  if (this->white_sensor_ != nullptr) {
413  }
414 }
415 
417  if (this->fake_infrared_sensor_ != nullptr) {
419  }
420  if (this->ambient_light_counts_sensor_ != nullptr) {
422  }
423  if (this->white_counts_sensor_ != nullptr) {
425  }
426 }
427 
429  if (this->actual_gain_sensor_ != nullptr) {
430  this->actual_gain_sensor_->publish_state(get_gain_coeff(data.actual_gain));
431  }
432  if (this->actual_integration_time_sensor_ != nullptr) {
433  this->actual_integration_time_sensor_->publish_state(get_itime_ms(data.actual_time));
434  }
435 }
436 } // namespace veml7700
437 } // namespace esphome
struct esphome::veml7700::VEML7700Component::Readings readings_
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop=true)
reads an array of bytes from a specific register in the I²C device
Definition: i2c.cpp:10
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
void apply_lux_compensation_(Readings &data)
Definition: veml7700.cpp:370
bool are_adjustments_required_(Readings &data)
Definition: veml7700.cpp:305
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
const uint8_t INTEGRATION_TIMES_COUNT
Definition: veml7700.h:43
mopeka_std_values val[4]
sensor::Sensor * actual_integration_time_sensor_
Definition: veml7700.h:198
sensor::Sensor * ambient_light_sensor_
Definition: veml7700.h:192
sensor::Sensor * fake_infrared_sensor_
Definition: veml7700.h:196
sensor::Sensor * ambient_light_counts_sensor_
Definition: veml7700.h:193
sensor::Sensor * actual_gain_sensor_
Definition: veml7700.h:197
void apply_glass_attenuation_(Readings &data)
Definition: veml7700.cpp:399
No error found during execution of method.
Definition: i2c_bus.h:13
void status_clear_warning()
Definition: component.cpp:166
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
void apply_lux_calculation_(Readings &data)
Definition: veml7700.cpp:352
const uint8_t GAINS_COUNT
Definition: veml7700.h:33
ErrorCode read_sensor_output_(Readings &data)
Definition: veml7700.cpp:279
T get_next(const T(&array)[size], const T val)
Definition: veml7700.cpp:13
void publish_data_part_1_(Readings &data)
Definition: veml7700.cpp:407
void publish_data_part_2_(Readings &data)
Definition: veml7700.cpp:416
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
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop=true)
writes an array of bytes to a specific register in the I²C device
Definition: i2c.cpp:25
T get_prev(const T(&array)[size], const T val)
Definition: veml7700.cpp:28
void publish_data_part_3_(Readings &data)
Definition: veml7700.cpp:428
ErrorCode
Error codes returned by I2CBus and I2CDevice methods.
Definition: i2c_bus.h:11
ErrorCode reconfigure_time_and_gain_(IntegrationTime time, Gain gain, bool shutdown)
Definition: veml7700.cpp:258
sensor::Sensor * white_counts_sensor_
Definition: veml7700.h:195
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26