ESPHome  2022.12.8
tsl2591.cpp
Go to the documentation of this file.
1 #include "tsl2591.h"
2 #include "esphome/core/log.h"
3 #include "esphome/core/hal.h"
4 
5 namespace esphome {
6 namespace tsl2591 {
7 
8 static const char *const TAG = "tsl2591.sensor";
9 
10 // Various constants used in TSL2591 register manipulation
11 #define TSL2591_COMMAND_BIT (0xA0) // 1010 0000: bits 7 and 5 for 'command, normal'
12 #define TSL2591_ENABLE_POWERON (0x01) // Flag for ENABLE register, to enable
13 #define TSL2591_ENABLE_POWEROFF (0x00) // Flag for ENABLE register, to disable
14 #define TSL2591_ENABLE_AEN (0x02) // Flag for ENABLE register, to turn on ADCs
15 
16 // TSL2591 registers from the datasheet. We only define what we use.
17 #define TSL2591_REGISTER_ENABLE (0x00)
18 #define TSL2591_REGISTER_CONTROL (0x01)
19 #define TSL2591_REGISTER_DEVICE_ID (0x12)
20 #define TSL2591_REGISTER_STATUS (0x13)
21 #define TSL2591_REGISTER_CHAN0_LOW (0x14)
22 #define TSL2591_REGISTER_CHAN0_HIGH (0x15)
23 #define TSL2591_REGISTER_CHAN1_LOW (0x16)
24 #define TSL2591_REGISTER_CHAN1_HIGH (0x17)
25 
27  // Enable the device by setting the control bit to 0x01. Also turn on ADCs.
28  if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE, TSL2591_ENABLE_POWERON | TSL2591_ENABLE_AEN)) {
29  ESP_LOGE(TAG, "Failed I2C write during enable()");
30  }
31 }
32 
34  if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE, TSL2591_ENABLE_POWEROFF)) {
35  ESP_LOGE(TAG, "Failed I2C write during disable()");
36  }
37 }
38 
40  if (this->power_save_mode_enabled_) {
41  this->disable();
42  }
43 }
44 
46  switch (this->component_gain_) {
47  case TSL2591_CGAIN_LOW:
48  this->gain_ = TSL2591_GAIN_LOW;
49  break;
50  case TSL2591_CGAIN_MED:
51  this->gain_ = TSL2591_GAIN_MED;
52  break;
53  case TSL2591_CGAIN_HIGH:
54  this->gain_ = TSL2591_GAIN_HIGH;
55  break;
56  case TSL2591_CGAIN_MAX:
57  this->gain_ = TSL2591_GAIN_MAX;
58  break;
59  case TSL2591_CGAIN_AUTO:
60  this->gain_ = TSL2591_GAIN_MED;
61  break;
62  }
63 
64  uint8_t address = this->address_;
65  ESP_LOGI(TAG, "Setting up TSL2591 sensor at I2C address 0x%02X", address);
66 
67  uint8_t id;
68  if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_DEVICE_ID, &id)) {
69  ESP_LOGE(TAG, "Failed I2C read during setup()");
70  this->mark_failed();
71  return;
72  }
73 
74  if (id != 0x50) {
75  ESP_LOGE(TAG,
76  "Could not find the TSL2591 sensor. The ID register of the device at address 0x%02X reported 0x%02X "
77  "instead of 0x50.",
78  address, id);
79  this->mark_failed();
80  return;
81  }
82 
85 }
86 
88  ESP_LOGCONFIG(TAG, "TSL2591:");
89  LOG_I2C_DEVICE(this);
90 
91  if (this->is_failed()) {
92  ESP_LOGE(TAG, "Communication with TSL2591 failed earlier, during setup");
93  return;
94  }
95 
96  ESP_LOGCONFIG(TAG, " Name: %s", this->name_);
97  TSL2591ComponentGain raw_gain = this->component_gain_;
98  int gain = 0;
99  std::string gain_word = "unknown";
100  switch (raw_gain) {
101  case TSL2591_CGAIN_LOW:
102  gain = 1;
103  gain_word = "low";
104  break;
105  case TSL2591_CGAIN_MED:
106  gain = 25;
107  gain_word = "medium";
108  break;
109  case TSL2591_CGAIN_HIGH:
110  gain = 400;
111  gain_word = "high";
112  break;
113  case TSL2591_CGAIN_MAX:
114  gain = 9500;
115  gain_word = "maximum";
116  break;
117  case TSL2591_CGAIN_AUTO:
118  gain = -1;
119  gain_word = "auto";
120  break;
121  }
122  ESP_LOGCONFIG(TAG, " Gain: %dx (%s)", gain, gain_word.c_str());
123  TSL2591IntegrationTime raw_timing = this->integration_time_;
124  int timing_ms = (1 + raw_timing) * 100;
125  ESP_LOGCONFIG(TAG, " Integration Time: %d ms", timing_ms);
126  ESP_LOGCONFIG(TAG, " Power save mode enabled: %s", ONOFF(this->power_save_mode_enabled_));
127  ESP_LOGCONFIG(TAG, " Device factor: %f", this->device_factor_);
128  ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_);
129  LOG_SENSOR(" ", "Full spectrum:", this->full_spectrum_sensor_);
130  LOG_SENSOR(" ", "Infrared:", this->infrared_sensor_);
131  LOG_SENSOR(" ", "Visible:", this->visible_sensor_);
132  LOG_SENSOR(" ", "Calculated lux:", this->calculated_lux_sensor_);
133 
134  LOG_UPDATE_INTERVAL(this);
135 }
136 
138  uint32_t combined = this->get_combined_illuminance();
139  uint16_t visible = this->get_illuminance(TSL2591_SENSOR_CHANNEL_VISIBLE, combined);
140  uint16_t infrared = this->get_illuminance(TSL2591_SENSOR_CHANNEL_INFRARED, combined);
141  uint16_t full = this->get_illuminance(TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM, combined);
142  float lux = this->get_calculated_lux(full, infrared);
143  ESP_LOGD(TAG, "Got illuminance: combined 0x%X, full %d, IR %d, vis %d. Calc lux: %f", combined, full, infrared,
144  visible, lux);
145  if (this->full_spectrum_sensor_ != nullptr) {
147  }
148  if (this->infrared_sensor_ != nullptr) {
149  this->infrared_sensor_->publish_state(infrared);
150  }
151  if (this->visible_sensor_ != nullptr) {
152  this->visible_sensor_->publish_state(visible);
153  }
154  if (this->calculated_lux_sensor_ != nullptr) {
156  }
157  if (this->component_gain_ == TSL2591_CGAIN_AUTO) {
158  this->automatic_gain_update(full);
159  }
160  this->status_clear_warning();
161 }
162 
163 #define interval_name "tsl2591_interval_for_update"
164 
166  if (!this->is_adc_valid()) {
167  uint64_t now = millis();
168  ESP_LOGD(TAG, "Elapsed %3llu ms; still waiting for valid ADC", (now - this->interval_start_));
169  if (now > this->interval_timeout_) {
170  ESP_LOGW(TAG, "Interval timeout for TSL2591 '%s' expired before ADCs became valid.", this->name_);
171  this->cancel_interval(interval_name);
172  }
173  return;
174  }
175  this->cancel_interval(interval_name);
176  this->process_update_();
177 }
178 
180  if (!is_failed()) {
181  if (this->power_save_mode_enabled_) {
182  // we enabled it here, else ADC will never become valid
183  // but actually doing the reads will disable device if needed
184  this->enable();
185  }
186  if (this->is_adc_valid()) {
187  this->process_update_();
188  } else {
189  this->interval_start_ = millis();
190  this->interval_timeout_ = this->interval_start_ + 620;
191  this->set_interval(interval_name, 100, [this] { this->interval_function_for_update_(); });
192  }
193  }
194 }
195 
197  this->infrared_sensor_ = infrared_sensor;
198 }
199 
200 void TSL2591Component::set_visible_sensor(sensor::Sensor *visible_sensor) { this->visible_sensor_ = visible_sensor; }
201 
203  this->full_spectrum_sensor_ = full_spectrum_sensor;
204 }
205 
207  this->calculated_lux_sensor_ = calculated_lux_sensor;
208 }
209 
211  this->integration_time_ = integration_time;
212 }
213 
215 
216 void TSL2591Component::set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor) {
217  this->device_factor_ = device_factor;
218  this->glass_attenuation_factor_ = glass_attenuation_factor;
219 }
220 
222  this->enable();
223  this->integration_time_ = integration_time;
224  this->gain_ = gain;
225  if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL,
226  this->integration_time_ | this->gain_)) { // NOLINT
227  ESP_LOGE(TAG, "Failed I2C write during set_integration_time_and_gain()");
228  }
229  // The ADC values can be confused if gain or integration time are changed in the middle of a cycle.
230  // So, we unconditionally disable the device to turn the ADCs off. When re-enabling, the ADCs
231  // will tell us when they are ready again. That avoids an initial bogus reading.
232  this->disable();
233  if (!this->power_save_mode_enabled_) {
234  this->enable();
235  }
236 }
237 
239 
240 void TSL2591Component::set_name(const char *name) { this->name_ = name; }
241 
243 
245  uint8_t status;
246  if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_STATUS, &status)) {
247  ESP_LOGE(TAG, "Failed I2C read during is_adc_valid()");
248  return false;
249  }
250  return status & 0x01;
251 }
252 
254  this->enable();
255  // Wait x ms for ADC to complete and signal valid.
256  // The max integration time is 600ms, so that's our max delay.
257  // (But we use 620ms as a bit of slack.)
258  // We'll do mini-delays and break out as soon as the ADC is good.
259  bool avalid;
260  const uint8_t mini_delay = 100;
261  for (uint16_t d = 0; d < 620; d += mini_delay) {
262  avalid = this->is_adc_valid();
263  if (avalid) {
264  break;
265  }
266  // we only log this if we need any delay, since normally we don't
267  ESP_LOGD(TAG, " after %3d ms: ADC valid? %s", d, avalid ? "true" : "false");
268  delay(mini_delay);
269  }
270  if (!avalid) {
271  // still not valid after a sutiable delay
272  // we don't mark the device as failed since it might come around in the future (probably not :-()
273  ESP_LOGE(TAG, "tsl2591 device '%s' did not return valid readings.", this->name_);
274  this->disable_if_power_saving_();
275  return 0;
276  }
277 
278  // CHAN0 must be read before CHAN1
279  // See: https://forums.adafruit.com/viewtopic.php?f=19&t=124176
280  // Also, low byte must be read before high byte..
281  // We read the registers in the order described in the datasheet.
282  uint32_t x32;
283  uint8_t ch0low, ch0high, ch1low, ch1high;
284  uint16_t ch0_16;
285  uint16_t ch1_16;
286  if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_LOW, &ch0low)) {
287  ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
288  return 0;
289  }
290  if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_HIGH, &ch0high)) {
291  ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
292  return 0;
293  }
294  ch0_16 = (ch0high << 8) | ch0low;
295  if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_LOW, &ch1low)) {
296  ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
297  return 0;
298  }
299  if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_HIGH, &ch1high)) {
300  ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
301  return 0;
302  }
303  ch1_16 = (ch1high << 8) | ch1low;
304  x32 = (ch1_16 << 16) | ch0_16;
305 
306  this->disable_if_power_saving_();
307  return x32;
308 }
309 
311  uint32_t combined = this->get_combined_illuminance();
312  return this->get_illuminance(channel, combined);
313 }
314 // logic cloned from Adafruit TSL2591 library
315 uint16_t TSL2591Component::get_illuminance(TSL2591SensorChannel channel, uint32_t combined_illuminance) {
316  if (channel == TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM) {
317  // Reads two byte value from channel 0 (visible + infrared)
318  return (combined_illuminance & 0xFFFF);
319  } else if (channel == TSL2591_SENSOR_CHANNEL_INFRARED) {
320  // Reads two byte value from channel 1 (infrared)
321  return (combined_illuminance >> 16);
322  } else if (channel == TSL2591_SENSOR_CHANNEL_VISIBLE) {
323  // Reads all and subtracts out the infrared
324  return ((combined_illuminance & 0xFFFF) - (combined_illuminance >> 16));
325  }
326  // unknown channel!
327  ESP_LOGE(TAG, "TSL2591Component::get_illuminance() caller requested an unknown channel: %d", channel);
328  return 0;
329 }
330 
345 float TSL2591Component::get_calculated_lux(uint16_t full_spectrum, uint16_t infrared) {
346  // Check for overflow conditions first
347  uint16_t max_count = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS ? 36863 : 65535);
348  if ((full_spectrum == max_count) || (infrared == max_count)) {
349  // Signal an overflow
350  ESP_LOGW(TAG, "Apparent saturation on TSL2591 (%s). You could reduce the gain.", this->name_);
351  return -1.0F;
352  }
353 
354  if ((full_spectrum == 0) && (infrared == 0)) {
355  // trivial conversion; avoids divide by 0
356  ESP_LOGW(TAG, "Zero reading on both TSL2591 (%s) sensors. Is the device having a problem?", this->name_);
357  return 0.0F;
358  }
359 
360  float atime = 100.F + (this->integration_time_ * 100);
361 
362  float again;
363  switch (this->gain_) {
364  case TSL2591_GAIN_LOW:
365  again = 1.0F;
366  break;
367  case TSL2591_GAIN_MED:
368  again = 25.0F;
369  break;
370  case TSL2591_GAIN_HIGH:
371  again = 400.0F;
372  break;
373  case TSL2591_GAIN_MAX:
374  again = 9500.0F;
375  break;
376  default:
377  again = 1.0F;
378  break;
379  }
380 
381  // This lux equation is copied from the Adafruit TSL2591 v1.4.0 and modified slightly.
382  // See: https://github.com/adafruit/Adafruit_TSL2591_Library/issues/14
383  // and that library code.
384  // They said:
385  // Note: This algorithm is based on preliminary coefficients
386  // provided by AMS and may need to be updated in the future
387  // However, we use gain multipliers that are more in line with the midpoints
388  // of ranges from the datasheet. We don't know why the other libraries
389  // used the values they did for HIGH and MAX.
390  // If cpl or full_spectrum are 0, this will return NaN due to divide by 0.
391  // For the curious "cpl" is counts per lux, a term used in AMS application notes.
392  float cpl = (atime * again) / (this->device_factor_ * this->glass_attenuation_factor_);
393  float lux = (((float) full_spectrum - (float) infrared)) * (1.0F - ((float) infrared / (float) full_spectrum)) / cpl;
394  return std::max(lux, 0.0F);
395 }
396 
410 void TSL2591Component::automatic_gain_update(uint16_t full_spectrum) {
411  TSL2591Gain new_gain = this->gain_;
412  uint fs_divider = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS) ? 2 : 1;
413 
414  switch (this->gain_) {
415  case TSL2591_GAIN_LOW:
416  if (full_spectrum < 54) { // 1/3 FS / GAIN_HIGH
417  new_gain = TSL2591_GAIN_HIGH;
418  } else if (full_spectrum < 875) { // 1/3 FS / GAIN_MED
419  new_gain = TSL2591_GAIN_MED;
420  }
421  break;
422  case TSL2591_GAIN_MED:
423  if (full_spectrum < 57) { // 1/3 FS / (GAIN_MAX/GAIN_MED)
424  new_gain = TSL2591_GAIN_MAX;
425  } else if (full_spectrum < 1365) { // 1/3 FS / (GAIN_HIGH/GAIN_MED)
426  new_gain = TSL2591_GAIN_HIGH;
427  } else if (full_spectrum > 62000 / fs_divider) { // 2/3 FS / (GAIN_LOW/GAIN_MED) clipped to 95% FS
428  new_gain = TSL2591_GAIN_LOW;
429  }
430  break;
431  case TSL2591_GAIN_HIGH:
432  if (full_spectrum < 920) { // 1/3 FS / (GAIN_MAX/GAIN_HIGH)
433  new_gain = TSL2591_GAIN_MAX;
434  } else if (full_spectrum > 62000 / fs_divider) { // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS
435  new_gain = TSL2591_GAIN_LOW;
436  }
437  break;
438  case TSL2591_GAIN_MAX:
439  if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS
440  new_gain = TSL2591_GAIN_MED;
441  break;
442  }
443 
444  if (this->gain_ != new_gain) {
445  this->gain_ = new_gain;
447  }
448  ESP_LOGD(TAG, "Gain setting: %d", this->gain_);
449 }
450 
451 } // namespace tsl2591
452 } // namespace esphome
void dump_config() override
Used by ESPHome framework.
Definition: tsl2591.cpp:87
const char * name
Definition: stm32flash.h:78
bool read_byte(uint8_t a_register, uint8_t *data, bool stop=true)
Definition: i2c.h:96
sensor::Sensor * visible_sensor_
Definition: tsl2591.h:250
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:18
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 set_full_spectrum_sensor(sensor::Sensor *full_spectrum_sensor)
Used by ESPHome framework.
Definition: tsl2591.cpp:202
sensor::Sensor * calculated_lux_sensor_
Definition: tsl2591.h:251
uint16_t get_illuminance(TSL2591SensorChannel channel)
Get an individual sensor channel reading.
Definition: tsl2591.cpp:310
bool cancel_interval(const std::string &name)
Cancel an interval function.
Definition: component.cpp:55
void set_name(const char *name)
Sets the name for this instance of the device.
Definition: tsl2591.cpp:240
T id(T value)
Helper function to make id(var) known from lambdas work in custom components.
Definition: helpers.h:641
void set_calculated_lux_sensor(sensor::Sensor *calculated_lux_sensor)
Used by ESPHome framework.
Definition: tsl2591.cpp:206
TSL2591IntegrationTime
Enum listing all conversion/integration time settings for the TSL2591.
Definition: tsl2591.h:15
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:26
sensor::Sensor * infrared_sensor_
Definition: tsl2591.h:249
void update() override
Used by ESPHome framework.
Definition: tsl2591.cpp:179
TSL2591ComponentGain component_gain_
Definition: tsl2591.h:253
void set_power_save_mode(bool enable)
Should the device be powered down between readings?
Definition: tsl2591.cpp:238
void set_integration_time_and_gain(TSL2591IntegrationTime integration_time, TSL2591Gain gain)
Set device integration time and gain.
Definition: tsl2591.cpp:221
void set_integration_time(TSL2591IntegrationTime integration_time)
Used by ESPHome framework.
Definition: tsl2591.cpp:210
void status_clear_warning()
Definition: component.cpp:149
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:72
void setup() override
Used by ESPHome framework.
Definition: tsl2591.cpp:45
void set_infrared_sensor(sensor::Sensor *infrared_sensor)
Used by ESPHome framework.
Definition: tsl2591.cpp:196
sensor::Sensor * full_spectrum_sensor_
Definition: tsl2591.h:248
uint8_t address_
Definition: i2c.h:130
TSL2591ComponentGain
Enum listing all gain settings for the TSL2591 component.
Definition: tsl2591.h:29
uint8_t status
Definition: bl0942.h:23
void enable()
Powers on the TSL2591 device and enables its sensors.
Definition: tsl2591.cpp:26
TSL2591Gain
Enum listing all gain settings for the TSL2591.
Definition: tsl2591.h:42
TSL2591IntegrationTime integration_time_
Definition: tsl2591.h:252
TSL2591SensorChannel
Enum listing sensor channels.
Definition: tsl2591.h:53
bool write_byte(uint8_t a_register, uint8_t data, bool stop=true)
Definition: i2c.h:123
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:112
void set_gain(TSL2591ComponentGain gain)
Used by ESPHome framework.
Definition: tsl2591.cpp:214
void disable()
Powers off the TSL2591 device.
Definition: tsl2591.cpp:33
Definition: a4988.cpp:4
bool is_adc_valid()
Are the device ADC values valid?
Definition: tsl2591.cpp:244
float get_setup_priority() const override
Used by ESPHome framework.
Definition: tsl2591.cpp:242
Base-class for all sensors.
Definition: sensor.h:50
void automatic_gain_update(uint16_t full_spectrum)
Updates the gain setting based on the most recent full spectrum reading.
Definition: tsl2591.cpp:410
void set_visible_sensor(sensor::Sensor *visible_sensor)
Used by ESPHome framework.
Definition: tsl2591.cpp:200
void set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor)
Sets the device and glass attenuation factors.
Definition: tsl2591.cpp:216
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:27
float get_calculated_lux(uint16_t full_spectrum, uint16_t infrared)
Calculates and returns a lux value based on the ADC readings.
Definition: tsl2591.cpp:345
uint32_t get_combined_illuminance()
Get the combined illuminance value.
Definition: tsl2591.cpp:253