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