ESPHome  2024.12.2
ltr501.cpp
Go to the documentation of this file.
1 #include "ltr501.h"
3 #include "esphome/core/log.h"
4 #include "esphome/core/helpers.h"
5 
7 
8 namespace esphome {
9 namespace ltr501 {
10 
11 static const char *const TAG = "ltr501";
12 
13 static const uint8_t MAX_TRIES = 5;
14 static const uint8_t MAX_SENSITIVITY_ADJUSTMENTS = 10;
15 
16 struct GainTimePair {
18  IntegrationTime501 time;
19 };
20 
21 bool operator==(const GainTimePair &lhs, const GainTimePair &rhs) {
22  return lhs.gain == rhs.gain && lhs.time == rhs.time;
23 }
24 
25 bool operator!=(const GainTimePair &lhs, const GainTimePair &rhs) {
26  return lhs.gain != rhs.gain || lhs.time != rhs.time;
27 }
28 
29 template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {
30  size_t i = 0;
31  size_t idx = -1;
32  while (idx == -1 && i < size) {
33  if (array[i] == val) {
34  idx = i;
35  break;
36  }
37  i++;
38  }
39  if (idx == -1 || i + 1 >= size)
40  return val;
41  return array[i + 1];
42 }
43 
44 template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) {
45  size_t i = size - 1;
46  size_t idx = -1;
47  while (idx == -1 && i > 0) {
48  if (array[i] == val) {
49  idx = i;
50  break;
51  }
52  i--;
53  }
54  if (idx == -1 || i == 0)
55  return val;
56  return array[i - 1];
57 }
58 
59 static uint16_t get_itime_ms(IntegrationTime501 time) {
60  static const uint16_t ALS_INT_TIME[4] = {100, 50, 200, 400};
61  return ALS_INT_TIME[time & 0b11];
62 }
63 
64 static uint16_t get_meas_time_ms(MeasurementRepeatRate rate) {
65  static const uint16_t ALS_MEAS_RATE[8] = {50, 100, 200, 500, 1000, 2000, 2000, 2000};
66  return ALS_MEAS_RATE[rate & 0b111];
67 }
68 
69 static float get_gain_coeff(AlsGain501 gain) { return gain == AlsGain501::GAIN_1 ? 1.0f : 150.0f; }
70 
71 static float get_ps_gain_coeff(PsGain501 gain) {
72  static const float PS_GAIN[4] = {1, 4, 8, 16};
73  return PS_GAIN[gain & 0b11];
74 }
75 
77  ESP_LOGCONFIG(TAG, "Setting up LTR-501/301/558");
78  // As per datasheet we need to wait at least 100ms after power on to get ALS chip responsive
79  this->set_timeout(100, [this]() { this->state_ = State::DELAYED_SETUP; });
80 }
81 
83  auto get_device_type = [](LtrType typ) {
84  switch (typ) {
86  return "ALS only";
88  return "PS only";
90  return "Als + PS";
91  default:
92  return "Unknown";
93  }
94  };
95 
96  LOG_I2C_DEVICE(this);
97  ESP_LOGCONFIG(TAG, " Device type: %s", get_device_type(this->ltr_type_));
98  ESP_LOGCONFIG(TAG, " Automatic mode: %s", ONOFF(this->automatic_mode_enabled_));
99  ESP_LOGCONFIG(TAG, " Gain: %.0fx", get_gain_coeff(this->gain_));
100  ESP_LOGCONFIG(TAG, " Integration time: %d ms", get_itime_ms(this->integration_time_));
101  ESP_LOGCONFIG(TAG, " Measurement repeat rate: %d ms", get_meas_time_ms(this->repeat_rate_));
102  ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_);
103  ESP_LOGCONFIG(TAG, " Proximity gain: %.0fx", get_ps_gain_coeff(this->ps_gain_));
104  ESP_LOGCONFIG(TAG, " Proximity cooldown time: %d s", this->ps_cooldown_time_s_);
105  ESP_LOGCONFIG(TAG, " Proximity high threshold: %d", this->ps_threshold_high_);
106  ESP_LOGCONFIG(TAG, " Proximity low threshold: %d", this->ps_threshold_low_);
107 
108  LOG_UPDATE_INTERVAL(this);
109 
110  LOG_SENSOR(" ", "ALS calculated lux", this->ambient_light_sensor_);
111  LOG_SENSOR(" ", "CH1 Infrared counts", this->infrared_counts_sensor_);
112  LOG_SENSOR(" ", "CH0 Visible+IR counts", this->full_spectrum_counts_sensor_);
113  LOG_SENSOR(" ", "Actual gain", this->actual_gain_sensor_);
114 
115  if (this->is_failed()) {
116  ESP_LOGE(TAG, "Communication with I2C LTR-501/301/558 failed!");
117  }
118 }
119 
121  if (!this->is_als_()) {
122  ESP_LOGW(TAG, "Update. ALS data not available. Change configuration to ALS or ALS_PS.");
123  return;
124  }
125  if (this->is_ready() && this->is_als_() && this->state_ == State::IDLE) {
126  ESP_LOGV(TAG, "Update. Initiating new ALS data collection.");
127 
128  this->state_ = this->automatic_mode_enabled_ ? State::COLLECTING_DATA_AUTO : State::WAITING_FOR_DATA;
129 
130  this->als_readings_.ch0 = 0;
131  this->als_readings_.ch1 = 0;
132  this->als_readings_.gain = this->gain_;
133  this->als_readings_.integration_time = this->integration_time_;
134  this->als_readings_.lux = 0;
135  this->als_readings_.number_of_adjustments = 0;
136 
137  } else {
138  ESP_LOGV(TAG, "Update. Component not ready yet.");
139  }
140 }
141 
143  ErrorCode err = i2c::ERROR_OK;
144  static uint8_t tries{0};
145 
146  switch (this->state_) {
147  case State::DELAYED_SETUP:
148  err = this->write(nullptr, 0);
149  if (err != i2c::ERROR_OK) {
150  ESP_LOGW(TAG, "i2c connection failed");
151  this->mark_failed();
152  }
153  this->configure_reset_();
154  if (this->is_als_()) {
155  this->configure_als_();
156  this->configure_integration_time_(this->integration_time_);
157  }
158  if (this->is_ps_()) {
159  this->configure_ps_();
160  }
161 
162  this->state_ = State::IDLE;
163  break;
164 
165  case State::IDLE:
166  if (this->is_ps_()) {
167  this->check_and_trigger_ps_();
168  }
169  break;
170 
171  case State::WAITING_FOR_DATA:
172  if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) {
173  tries = 0;
174  ESP_LOGV(TAG, "Reading sensor data assuming gain = %.0fx, time = %d ms",
175  get_gain_coeff(this->als_readings_.gain), get_itime_ms(this->als_readings_.integration_time));
176  this->read_sensor_data_(this->als_readings_);
177  this->apply_lux_calculation_(this->als_readings_);
178  this->state_ = State::DATA_COLLECTED;
179  } else if (tries >= MAX_TRIES) {
180  ESP_LOGW(TAG, "Can't get data after several tries. Aborting.");
181  tries = 0;
182  this->status_set_warning();
183  this->state_ = State::IDLE;
184  return;
185  } else {
186  tries++;
187  }
188  break;
189 
190  case State::COLLECTING_DATA_AUTO:
191  case State::DATA_COLLECTED:
192  // first measurement in auto mode (COLLECTING_DATA_AUTO state) require device reconfiguration
193  if (this->state_ == State::COLLECTING_DATA_AUTO || this->are_adjustments_required_(this->als_readings_)) {
194  this->state_ = State::ADJUSTMENT_IN_PROGRESS;
195  ESP_LOGD(TAG, "Reconfiguring sensitivity: gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain),
196  get_itime_ms(this->als_readings_.integration_time));
197  this->configure_integration_time_(this->als_readings_.integration_time);
198  this->configure_gain_(this->als_readings_.gain);
199  // if sensitivity adjustment needed - need to wait for first data samples after setting new parameters
200  this->set_timeout(2 * get_meas_time_ms(this->repeat_rate_),
201  [this]() { this->state_ = State::WAITING_FOR_DATA; });
202  } else {
203  this->state_ = State::READY_TO_PUBLISH;
204  }
205  break;
206 
207  case State::ADJUSTMENT_IN_PROGRESS:
208  // nothing to be done, just waiting for the timeout
209  break;
210 
211  case State::READY_TO_PUBLISH:
212  this->publish_data_part_1_(this->als_readings_);
213  this->state_ = State::KEEP_PUBLISHING;
214  break;
215 
216  case State::KEEP_PUBLISHING:
217  this->publish_data_part_2_(this->als_readings_);
218  this->status_clear_warning();
219  this->state_ = State::IDLE;
220  break;
221 
222  default:
223  break;
224  }
225 }
226 
228  static uint32_t last_high_trigger_time{0};
229  static uint32_t last_low_trigger_time{0};
230  uint16_t ps_data = this->read_ps_data_();
231  uint32_t now = millis();
232 
233  if (ps_data != this->ps_readings_) {
234  this->ps_readings_ = ps_data;
235  // Higher values - object is closer to sensor
236  if (ps_data > this->ps_threshold_high_ && now - last_high_trigger_time >= this->ps_cooldown_time_s_ * 1000) {
237  last_high_trigger_time = now;
238  ESP_LOGD(TAG, "Proximity high threshold triggered. Value = %d, Trigger level = %d", ps_data,
239  this->ps_threshold_high_);
240  this->on_ps_high_trigger_callback_.call();
241  } else if (ps_data < this->ps_threshold_low_ && now - last_low_trigger_time >= this->ps_cooldown_time_s_ * 1000) {
242  last_low_trigger_time = now;
243  ESP_LOGD(TAG, "Proximity low threshold triggered. Value = %d, Trigger level = %d", ps_data,
244  this->ps_threshold_low_);
245  this->on_ps_low_trigger_callback_.call();
246  }
247  }
248 }
249 
251  uint8_t manuf_id = this->reg((uint8_t) CommandRegisters::MANUFAC_ID).get();
252  if (manuf_id != 0x05) { // 0x05 is Lite-On Semiconductor Corp. ID
253  ESP_LOGW(TAG, "Unknown manufacturer ID: 0x%02X", manuf_id);
254  this->mark_failed();
255  return false;
256  }
257 
258  // Things getting not really funny here, we can't identify device type by part number ID
259  // ======================== ========= ===== =================
260  // Device Part ID Rev Capabilities
261  // ======================== ========= ===== =================
262  // ltr-558als 0x08 0 als + ps
263  // ltr-501als 0x08 0 als + ps
264  // ltr-301als - 0x08 0 als only
265 
266  PartIdRegister part_id{0};
267  part_id.raw = this->reg((uint8_t) CommandRegisters::PART_ID).get();
268  if (part_id.part_number_id != 0x08) {
269  ESP_LOGW(TAG, "Unknown part number ID: 0x%02X. LTR-501/301 shall have 0x08. It might not work properly.",
270  part_id.part_number_id);
271  this->status_set_warning();
272  return true;
273  }
274  return true;
275 }
276 
278  ESP_LOGV(TAG, "Resetting");
279 
280  AlsControlRegister501 als_ctrl{0};
281  als_ctrl.sw_reset = true;
282  this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
283  delay(2);
284 
285  uint8_t tries = MAX_TRIES;
286  do {
287  ESP_LOGV(TAG, "Waiting chip to reset");
288  delay(2);
289  als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
290  } while (als_ctrl.sw_reset && tries--); // while sw reset bit is on - keep waiting
291 
292  if (als_ctrl.sw_reset) {
293  ESP_LOGW(TAG, "Reset failed");
294  }
295 }
296 
298  AlsControlRegister501 als_ctrl{0};
299  als_ctrl.sw_reset = false;
300  als_ctrl.als_mode_active = true;
301  als_ctrl.gain = this->gain_;
302 
303  ESP_LOGV(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw);
304  this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
305  delay(5);
306 
307  uint8_t tries = MAX_TRIES;
308  do {
309  ESP_LOGV(TAG, "Waiting for ALS device to become active...");
310  delay(2);
311  als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
312  } while (!als_ctrl.als_mode_active && tries--); // while active mode is not set - keep waiting
313 
314  if (!als_ctrl.als_mode_active) {
315  ESP_LOGW(TAG, "Failed to activate ALS device");
316  }
317 }
318 
320  PsMeasurementRateRegister ps_meas{0};
322  this->reg((uint8_t) CommandRegisters::PS_MEAS_RATE) = ps_meas.raw;
323 
324  PsControlRegister501 ps_ctrl{0};
325  ps_ctrl.ps_mode_active = true;
326  ps_ctrl.ps_mode_xxx = true;
327  this->reg((uint8_t) CommandRegisters::PS_CONTR) = ps_ctrl.raw;
328 }
329 
331  AlsPsStatusRegister als_status{0};
332  als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
333  if (!als_status.ps_new_data) {
334  return this->ps_readings_;
335  }
336 
337  uint8_t ps_low = this->reg((uint8_t) CommandRegisters::PS_DATA_0).get();
338  PsData1Register ps_high;
339  ps_high.raw = this->reg((uint8_t) CommandRegisters::PS_DATA_1).get();
340 
341  uint16_t val = encode_uint16(ps_high.ps_data_high, ps_low);
342  return val;
343 }
344 
346  AlsControlRegister501 als_ctrl{0};
347  als_ctrl.als_mode_active = true;
348  als_ctrl.gain = gain;
349  this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
350  delay(2);
351 
352  AlsControlRegister501 read_als_ctrl{0};
353  read_als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
354  if (read_als_ctrl.gain != gain) {
355  ESP_LOGW(TAG, "Failed to set gain. We will try one more time.");
356  this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
357  delay(2);
358  }
359 }
360 
363  meas.measurement_repeat_rate = this->repeat_rate_;
364  meas.integration_time = time;
365  this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw;
366  delay(2);
367 
368  MeasurementRateRegister501 read_meas{0};
369  read_meas.raw = this->reg((uint8_t) CommandRegisters::MEAS_RATE).get();
370  if (read_meas.integration_time != time) {
371  ESP_LOGW(TAG, "Failed to set integration time. We will try one more time.");
372  this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw;
373  delay(2);
374  }
375 }
376 
378  AlsPsStatusRegister als_status{0};
379  als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
380  if (!als_status.als_new_data)
381  return DataAvail::NO_DATA;
382  ESP_LOGV(TAG, "Data ready, reported gain is %.0fx", get_gain_coeff(als_status.gain));
383  if (data.gain != als_status.gain) {
384  ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain));
385  return DataAvail::BAD_DATA;
386  }
387  data.gain = als_status.gain;
388  return DataAvail::DATA_OK;
389 }
390 
392  data.ch1 = 0;
393  data.ch0 = 0;
394  uint8_t ch1_0 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH1_0).get();
395  uint8_t ch1_1 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH1_1).get();
396  uint8_t ch0_0 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH0_0).get();
397  uint8_t ch0_1 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH0_1).get();
398  data.ch1 = encode_uint16(ch1_1, ch1_0);
399  data.ch0 = encode_uint16(ch0_1, ch0_0);
400 
401  ESP_LOGD(TAG, "Got sensor data: CH1 = %d, CH0 = %d", data.ch1, data.ch0);
402 }
403 
405  if (!this->automatic_mode_enabled_)
406  return false;
407 
408  // sometimes sensors fail to change sensitivity. this prevents us from infinite loop
409  if (data.number_of_adjustments++ > MAX_SENSITIVITY_ADJUSTMENTS) {
410  ESP_LOGW(TAG, "Too many sensitivity adjustments done. Something wrong with the sensor. Stopping.");
411  return false;
412  }
413 
414  ESP_LOGV(TAG, "Adjusting sensitivity, run #%d", data.number_of_adjustments);
415 
416  // available combinations of gain and integration times:
417  static const GainTimePair GAIN_TIME_PAIRS[] = {
421  };
422 
423  GainTimePair current_pair = {data.gain, data.integration_time};
424 
425  // Here comes funky business with this sensor. it has no internal error checking mechanism
426  // as in later versions (LTR-303/329/559/..) and sensor gets overwhelmed when saturated
427  // and readings are strange. We only check high sensitivity mode for now.
428  // Nothing is documented and it is a result of real-world testing.
429  if (data.gain == AlsGain501::GAIN_150) {
430  // when sensor is saturated it returns various crazy numbers
431  // CH1 = 1, CH0 = 0
432  if (data.ch1 == 1 && data.ch0 == 0) {
433  ESP_LOGV(TAG, "Looks like sensor got saturated (?) CH1 = 1, CH0 = 0, Gain 150x");
434  // fake saturation
435  data.ch0 = 0xffff;
436  data.ch1 = 0xffff;
437  } else if (data.ch1 == 65535 && data.ch0 == 0) {
438  ESP_LOGV(TAG, "Looks like sensor got saturated (?) CH1 = 65535, CH0 = 0, Gain 150x");
439  data.ch0 = 0xffff;
440  } else if (data.ch1 > 1000 && data.ch0 == 0) {
441  ESP_LOGV(TAG, "Looks like sensor got saturated (?) CH1 = %d, CH0 = 0, Gain 150x", data.ch1);
442  data.ch0 = 0xffff;
443  }
444  }
445 
446  static const uint16_t LOW_INTENSITY_THRESHOLD_1 = 100;
447  static const uint16_t LOW_INTENSITY_THRESHOLD_200 = 2000;
448  static const uint16_t HIGH_INTENSITY_THRESHOLD = 25000;
449 
450  if (data.ch0 <= (data.gain == AlsGain501::GAIN_1 ? LOW_INTENSITY_THRESHOLD_1 : LOW_INTENSITY_THRESHOLD_200) ||
451  (data.gain == AlsGain501::GAIN_1 && data.lux < 320)) {
452  GainTimePair next_pair = get_next(GAIN_TIME_PAIRS, current_pair);
453  if (next_pair != current_pair) {
454  data.gain = next_pair.gain;
455  data.integration_time = next_pair.time;
456  ESP_LOGV(TAG, "Low illuminance. Increasing sensitivity.");
457  return true;
458  }
459 
460  } else if (data.ch0 >= HIGH_INTENSITY_THRESHOLD || data.ch1 >= HIGH_INTENSITY_THRESHOLD) {
461  GainTimePair prev_pair = get_prev(GAIN_TIME_PAIRS, current_pair);
462  if (prev_pair != current_pair) {
463  data.gain = prev_pair.gain;
464  data.integration_time = prev_pair.time;
465  ESP_LOGV(TAG, "High illuminance. Decreasing sensitivity.");
466  return true;
467  }
468  } else {
469  ESP_LOGD(TAG, "Illuminance is good enough.");
470  return false;
471  }
472  ESP_LOGD(TAG, "Can't adjust sensitivity anymore.");
473  return false;
474 }
475 
477  if ((data.ch0 == 0xFFFF) || (data.ch1 == 0xFFFF)) {
478  ESP_LOGW(TAG, "Sensors got saturated");
479  data.lux = 0.0f;
480  return;
481  }
482 
483  if ((data.ch0 == 0x0000) && (data.ch1 == 0x0000)) {
484  ESP_LOGW(TAG, "Sensors blacked out");
485  data.lux = 0.0f;
486  return;
487  }
488 
489  float ch0 = data.ch0;
490  float ch1 = data.ch1;
491  float ratio = ch1 / (ch0 + ch1);
492  float als_gain = get_gain_coeff(data.gain);
493  float als_time = ((float) get_itime_ms(data.integration_time)) / 100.0f;
494  float inv_pfactor = this->glass_attenuation_factor_;
495  float lux = 0.0f;
496 
497  // method from
498  // https://github.com/fards/Ainol_fire_kernel/blob/83832cf8a3082fd8e963230f4b1984479d1f1a84/customer/drivers/lightsensor/ltr501als.c#L295
499 
500  if (ratio < 0.45) {
501  lux = 1.7743 * ch0 + 1.1059 * ch1;
502  } else if (ratio < 0.64) {
503  lux = 3.7725 * ch0 - 1.3363 * ch1;
504  } else if (ratio < 0.85) {
505  lux = 1.6903 * ch0 - 0.1693 * ch1;
506  } else {
507  ESP_LOGW(TAG, "Impossible ch1/(ch0 + ch1) ratio");
508  lux = 0.0f;
509  }
510 
511  lux = inv_pfactor * lux / als_gain / als_time;
512  data.lux = lux;
513 
514  ESP_LOGD(TAG, "Lux calculation: ratio %.3f, gain %.0fx, int time %.1f, inv_pfactor %.3f, lux %.3f", ratio, als_gain,
515  als_time, inv_pfactor, lux);
516 }
517 
519  if (this->proximity_counts_sensor_ != nullptr) {
520  this->proximity_counts_sensor_->publish_state(this->ps_readings_);
521  }
522  if (this->ambient_light_sensor_ != nullptr) {
523  this->ambient_light_sensor_->publish_state(data.lux);
524  }
525  if (this->infrared_counts_sensor_ != nullptr) {
526  this->infrared_counts_sensor_->publish_state(data.ch1);
527  }
528  if (this->full_spectrum_counts_sensor_ != nullptr) {
529  this->full_spectrum_counts_sensor_->publish_state(data.ch0);
530  }
531 }
532 
534  if (this->actual_gain_sensor_ != nullptr) {
535  this->actual_gain_sensor_->publish_state(get_gain_coeff(data.gain));
536  }
537  if (this->actual_integration_time_sensor_ != nullptr) {
538  this->actual_integration_time_sensor_->publish_state(get_itime_ms(data.integration_time));
539  }
540 }
541 } // namespace ltr501
542 } // namespace esphome
void apply_lux_calculation_(AlsReadings &data)
Definition: ltr501.cpp:476
bool are_adjustments_required_(AlsReadings &data)
Definition: ltr501.cpp:404
void publish_data_part_2_(AlsReadings &data)
Definition: ltr501.cpp:533
bool operator!=(const GainTimePair &lhs, const GainTimePair &rhs)
Definition: ltr501.cpp:25
T get_next(const T(&array)[size], const T val)
Definition: ltr501.cpp:29
void read_sensor_data_(AlsReadings &data)
Definition: ltr501.cpp:391
mopeka_std_values val[4]
AlsGain501 gain
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
bool operator==(const GainTimePair &lhs, const GainTimePair &rhs)
Definition: ltr501.cpp:21
No error found during execution of method.
Definition: i2c_bus.h:13
DataAvail is_als_data_ready_(AlsReadings &data)
Definition: ltr501.cpp:377
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition: helpers.h:183
void configure_integration_time_(IntegrationTime501 time)
Definition: ltr501.cpp:361
void publish_data_part_1_(AlsReadings &data)
Definition: ltr501.cpp:518
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
ErrorCode
Error codes returned by I2CBus and I2CDevice methods.
Definition: i2c_bus.h:11
T get_prev(const T(&array)[size], const T val)
Definition: ltr501.cpp:44
void configure_gain_(AlsGain501 gain)
Definition: ltr501.cpp:345
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26