ESPHome 2025.5.0
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 {
6namespace tsl2591 {
7
8static 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
44
46 switch (this->component_gain_) {
48 this->gain_ = TSL2591_GAIN_LOW;
49 break;
51 this->gain_ = TSL2591_GAIN_MED;
52 break;
55 break;
57 this->gain_ = TSL2591_GAIN_MAX;
58 break;
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_);
98 int gain = 0;
99 std::string gain_word = "unknown";
100 switch (raw_gain) {
102 gain = 1;
103 gain_word = "low";
104 break;
106 gain = 25;
107 gain_word = "medium";
108 break;
110 gain = 400;
111 gain_word = "high";
112 break;
114 gain = 9500;
115 gain_word = "maximum";
116 break;
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 LOG_SENSOR(" ", "Actual gain:", this->actual_gain_sensor_);
134
135 LOG_UPDATE_INTERVAL(this);
136}
137
139 uint32_t combined = this->get_combined_illuminance();
140 uint16_t visible = this->get_illuminance(TSL2591_SENSOR_CHANNEL_VISIBLE, combined);
141 uint16_t infrared = this->get_illuminance(TSL2591_SENSOR_CHANNEL_INFRARED, combined);
142 uint16_t full = this->get_illuminance(TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM, combined);
143 float lux = this->get_calculated_lux(full, infrared);
144 uint16_t actual_gain = this->get_actual_gain();
145 ESP_LOGD(TAG, "Got illuminance: combined 0x%" PRIX32 ", full %d, IR %d, vis %d. Calc lux: %f. Actual gain: %d.",
146 combined, full, infrared, visible, lux, actual_gain);
147 if (this->full_spectrum_sensor_ != nullptr) {
149 }
150 if (this->infrared_sensor_ != nullptr) {
151 this->infrared_sensor_->publish_state(infrared);
152 }
153 if (this->visible_sensor_ != nullptr) {
154 this->visible_sensor_->publish_state(visible);
155 }
156 if (this->calculated_lux_sensor_ != nullptr) {
158 }
159
160 if (this->component_gain_ == TSL2591_CGAIN_AUTO) {
161 this->automatic_gain_update(full);
162 }
163
164 if (this->actual_gain_sensor_ != nullptr) {
165 this->actual_gain_sensor_->publish_state(actual_gain);
166 }
167 this->status_clear_warning();
168}
169
170#define interval_name "tsl2591_interval_for_update"
171
173 if (!this->is_adc_valid()) {
174 uint64_t now = millis();
175 ESP_LOGD(TAG, "Elapsed %3llu ms; still waiting for valid ADC", (now - this->interval_start_));
176 if (now > this->interval_timeout_) {
177 ESP_LOGW(TAG, "Interval timeout for TSL2591 '%s' expired before ADCs became valid.", this->name_);
178 this->cancel_interval(interval_name);
179 }
180 return;
181 }
182 this->cancel_interval(interval_name);
183 this->process_update_();
184}
185
187 if (!is_failed()) {
188 if (this->power_save_mode_enabled_) {
189 // we enabled it here, else ADC will never become valid
190 // but actually doing the reads will disable device if needed
191 this->enable();
192 }
193 if (this->is_adc_valid()) {
194 this->process_update_();
195 } else {
196 this->interval_start_ = millis();
197 this->interval_timeout_ = this->interval_start_ + 620;
198 this->set_interval(interval_name, 100, [this] { this->interval_function_for_update_(); });
199 }
200 }
201}
202
204 this->infrared_sensor_ = infrared_sensor;
205}
206
207void TSL2591Component::set_visible_sensor(sensor::Sensor *visible_sensor) { this->visible_sensor_ = visible_sensor; }
208
210 this->full_spectrum_sensor_ = full_spectrum_sensor;
211}
212
214 this->calculated_lux_sensor_ = calculated_lux_sensor;
215}
216
218 this->actual_gain_sensor_ = actual_gain_sensor;
219}
220
224
226
227void TSL2591Component::set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor) {
228 this->device_factor_ = device_factor;
229 this->glass_attenuation_factor_ = glass_attenuation_factor;
230}
231
233 this->enable();
235 this->gain_ = gain;
236 if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL,
237 this->integration_time_ | this->gain_)) { // NOLINT
238 ESP_LOGE(TAG, "Failed I2C write during set_integration_time_and_gain()");
239 }
240 // The ADC values can be confused if gain or integration time are changed in the middle of a cycle.
241 // So, we unconditionally disable the device to turn the ADCs off. When re-enabling, the ADCs
242 // will tell us when they are ready again. That avoids an initial bogus reading.
243 this->disable();
244 if (!this->power_save_mode_enabled_) {
245 this->enable();
246 }
247}
248
250
251void TSL2591Component::set_name(const char *name) { this->name_ = name; }
252
254
256 uint8_t status;
257 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_STATUS, &status)) {
258 ESP_LOGE(TAG, "Failed I2C read during is_adc_valid()");
259 return false;
260 }
261 return status & 0x01;
262}
263
265 this->enable();
266 // Wait x ms for ADC to complete and signal valid.
267 // The max integration time is 600ms, so that's our max delay.
268 // (But we use 620ms as a bit of slack.)
269 // We'll do mini-delays and break out as soon as the ADC is good.
270 bool avalid;
271 const uint8_t mini_delay = 100;
272 for (uint16_t d = 0; d < 620; d += mini_delay) {
273 avalid = this->is_adc_valid();
274 if (avalid) {
275 break;
276 }
277 // we only log this if we need any delay, since normally we don't
278 ESP_LOGD(TAG, " after %3d ms: ADC valid? %s", d, avalid ? "true" : "false");
279 delay(mini_delay);
280 }
281 if (!avalid) {
282 // still not valid after a sutiable delay
283 // we don't mark the device as failed since it might come around in the future (probably not :-()
284 ESP_LOGE(TAG, "tsl2591 device '%s' did not return valid readings.", this->name_);
286 return 0;
287 }
288
289 // CHAN0 must be read before CHAN1
290 // See: https://forums.adafruit.com/viewtopic.php?f=19&t=124176
291 // Also, low byte must be read before high byte..
292 // We read the registers in the order described in the datasheet.
293 uint32_t x32;
294 uint8_t ch0low, ch0high, ch1low, ch1high;
295 uint16_t ch0_16;
296 uint16_t ch1_16;
297 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_LOW, &ch0low)) {
298 ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
299 return 0;
300 }
301 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_HIGH, &ch0high)) {
302 ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
303 return 0;
304 }
305 ch0_16 = (ch0high << 8) | ch0low;
306 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_LOW, &ch1low)) {
307 ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
308 return 0;
309 }
310 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_HIGH, &ch1high)) {
311 ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
312 return 0;
313 }
314 ch1_16 = (ch1high << 8) | ch1low;
315 x32 = (ch1_16 << 16) | ch0_16;
316
318 return x32;
319}
320
322 uint32_t combined = this->get_combined_illuminance();
323 return this->get_illuminance(channel, combined);
324}
325// logic cloned from Adafruit TSL2591 library
326uint16_t TSL2591Component::get_illuminance(TSL2591SensorChannel channel, uint32_t combined_illuminance) {
328 // Reads two byte value from channel 0 (visible + infrared)
329 return (combined_illuminance & 0xFFFF);
330 } else if (channel == TSL2591_SENSOR_CHANNEL_INFRARED) {
331 // Reads two byte value from channel 1 (infrared)
332 return (combined_illuminance >> 16);
333 } else if (channel == TSL2591_SENSOR_CHANNEL_VISIBLE) {
334 // Reads all and subtracts out the infrared
335 return ((combined_illuminance & 0xFFFF) - (combined_illuminance >> 16));
336 }
337 // unknown channel!
338 ESP_LOGE(TAG, "TSL2591Component::get_illuminance() caller requested an unknown channel: %d", channel);
339 return 0;
340}
341
356float TSL2591Component::get_calculated_lux(uint16_t full_spectrum, uint16_t infrared) {
357 // Check for overflow conditions first
358 uint16_t max_count = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS ? 36863 : 65535);
359 if ((full_spectrum == max_count) || (infrared == max_count)) {
360 // Signal an overflow
361 ESP_LOGW(TAG, "Apparent saturation on TSL2591 (%s). You could reduce the gain or integration time.", this->name_);
362 return NAN;
363 }
364
365 if ((full_spectrum == 0) && (infrared == 0)) {
366 // trivial conversion; avoids divide by 0
367 ESP_LOGW(TAG, "Zero reading on both TSL2591 (%s) sensors. Is the device having a problem?", this->name_);
368 return 0.0F;
369 }
370
371 float atime = 100.F + (this->integration_time_ * 100);
372
373 float again;
374 switch (this->gain_) {
375 case TSL2591_GAIN_LOW:
376 again = 1.0F;
377 break;
378 case TSL2591_GAIN_MED:
379 again = 25.0F;
380 break;
382 again = 400.0F;
383 break;
384 case TSL2591_GAIN_MAX:
385 again = 9500.0F;
386 break;
387 default:
388 again = 1.0F;
389 break;
390 }
391 // This lux equation is copied from the Adafruit TSL2591 v1.4.0 and modified slightly.
392 // See: https://github.com/adafruit/Adafruit_TSL2591_Library/issues/14
393 // and that library code.
394 // They said:
395 // Note: This algorithm is based on preliminary coefficients
396 // provided by AMS and may need to be updated in the future
397 // However, we use gain multipliers that are more in line with the midpoints
398 // of ranges from the datasheet. We don't know why the other libraries
399 // used the values they did for HIGH and MAX.
400 // If cpl or full_spectrum are 0, this will return NaN due to divide by 0.
401 // For the curious "cpl" is counts per lux, a term used in AMS application notes.
402 float cpl = (atime * again) / (this->device_factor_ * this->glass_attenuation_factor_);
403 float lux = (((float) full_spectrum - (float) infrared)) * (1.0F - ((float) infrared / (float) full_spectrum)) / cpl;
404 return std::max(lux, 0.0F);
405}
406
420void TSL2591Component::automatic_gain_update(uint16_t full_spectrum) {
421 TSL2591Gain new_gain = this->gain_;
422 uint fs_divider = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS) ? 2 : 1;
423
424 switch (this->gain_) {
425 case TSL2591_GAIN_LOW:
426 if (full_spectrum < 54) { // 1/3 FS / GAIN_HIGH
427 new_gain = TSL2591_GAIN_HIGH;
428 } else if (full_spectrum < 875) { // 1/3 FS / GAIN_MED
429 new_gain = TSL2591_GAIN_MED;
430 }
431 break;
432 case TSL2591_GAIN_MED:
433 if (full_spectrum < 57) { // 1/3 FS / (GAIN_MAX/GAIN_MED)
434 new_gain = TSL2591_GAIN_MAX;
435 } else if (full_spectrum < 1365) { // 1/3 FS / (GAIN_HIGH/GAIN_MED)
436 new_gain = TSL2591_GAIN_HIGH;
437 } else if (full_spectrum > 62000 / fs_divider) { // 2/3 FS / (GAIN_LOW/GAIN_MED) clipped to 95% FS
438 new_gain = TSL2591_GAIN_LOW;
439 }
440 break;
442 if (full_spectrum < 920) { // 1/3 FS / (GAIN_MAX/GAIN_HIGH)
443 new_gain = TSL2591_GAIN_MAX;
444 } else if (full_spectrum > 62000 / fs_divider) { // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS
445 new_gain = TSL2591_GAIN_LOW;
446 }
447 break;
448 case TSL2591_GAIN_MAX:
449 if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS
450 new_gain = TSL2591_GAIN_MED;
451 break;
452 }
453
454 if (this->gain_ != new_gain) {
455 this->gain_ = new_gain;
457 }
458 ESP_LOGD(TAG, "Gain setting: %d", this->gain_);
459}
460
466 switch (this->gain_) {
467 case TSL2591_GAIN_LOW:
468 return 1.0F;
469 case TSL2591_GAIN_MED:
470 return 25.0F;
472 return 400.0F;
473 case TSL2591_GAIN_MAX:
474 return 9500.0F;
475 default:
476 // Shouldn't get here, but just in case.
477 return NAN;
478 }
479}
480
481} // namespace tsl2591
482} // namespace esphome
uint8_t address
Definition bl0906.h:4
uint8_t status
Definition bl0942.h:8
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
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:55
bool cancel_interval(const std::string &name)
Cancel an interval function.
Definition component.cpp:59
void status_clear_warning()
uint8_t address_
store the address of the device on the bus
Definition i2c.h:273
bool write_byte(uint8_t a_register, uint8_t data, bool stop=true)
Definition i2c.h:266
bool read_byte(uint8_t a_register, uint8_t *data, bool stop=true)
Definition i2c.h:239
Base-class for all sensors.
Definition sensor.h:57
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:39
TSL2591IntegrationTime integration_time_
Definition tsl2591.h:262
void dump_config() override
Used by ESPHome framework.
Definition tsl2591.cpp:87
float get_setup_priority() const override
Used by ESPHome framework.
Definition tsl2591.cpp:253
TSL2591ComponentGain component_gain_
Definition tsl2591.h:263
void automatic_gain_update(uint16_t full_spectrum)
Updates the gain setting based on the most recent full spectrum reading.
Definition tsl2591.cpp:420
bool is_adc_valid()
Are the device ADC values valid?
Definition tsl2591.cpp:255
sensor::Sensor * infrared_sensor_
Definition tsl2591.h:258
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:356
sensor::Sensor * calculated_lux_sensor_
Definition tsl2591.h:260
void set_visible_sensor(sensor::Sensor *visible_sensor)
Used by ESPHome framework.
Definition tsl2591.cpp:207
void set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor)
Sets the device and glass attenuation factors.
Definition tsl2591.cpp:227
sensor::Sensor * full_spectrum_sensor_
Definition tsl2591.h:257
void set_integration_time(TSL2591IntegrationTime integration_time)
Used by ESPHome framework.
Definition tsl2591.cpp:221
void set_calculated_lux_sensor(sensor::Sensor *calculated_lux_sensor)
Used by ESPHome framework.
Definition tsl2591.cpp:213
uint32_t get_combined_illuminance()
Get the combined illuminance value.
Definition tsl2591.cpp:264
void set_name(const char *name)
Sets the name for this instance of the device.
Definition tsl2591.cpp:251
sensor::Sensor * visible_sensor_
Definition tsl2591.h:259
void set_infrared_sensor(sensor::Sensor *infrared_sensor)
Used by ESPHome framework.
Definition tsl2591.cpp:203
sensor::Sensor * actual_gain_sensor_
Definition tsl2591.h:261
void set_actual_gain_sensor(sensor::Sensor *actual_gain_sensor)
Used by ESPHome framework.
Definition tsl2591.cpp:217
void set_gain(TSL2591ComponentGain gain)
Used by ESPHome framework.
Definition tsl2591.cpp:225
void enable()
Powers on the TSL2591 device and enables its sensors.
Definition tsl2591.cpp:26
void update() override
Used by ESPHome framework.
Definition tsl2591.cpp:186
void set_integration_time_and_gain(TSL2591IntegrationTime integration_time, TSL2591Gain gain)
Set device integration time and gain.
Definition tsl2591.cpp:232
void set_full_spectrum_sensor(sensor::Sensor *full_spectrum_sensor)
Used by ESPHome framework.
Definition tsl2591.cpp:209
void disable()
Powers off the TSL2591 device.
Definition tsl2591.cpp:33
float get_actual_gain()
Reads the actual gain used.
Definition tsl2591.cpp:465
uint16_t get_illuminance(TSL2591SensorChannel channel)
Get an individual sensor channel reading.
Definition tsl2591.cpp:321
void setup() override
Used by ESPHome framework.
Definition tsl2591.cpp:45
void set_power_save_mode(bool enable)
Should the device be powered down between readings?
Definition tsl2591.cpp:249
AlsGain501 gain
IntegrationTime501 integration_time
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:19
TSL2591Gain
Enum listing all gain settings for the TSL2591.
Definition tsl2591.h:44
TSL2591ComponentGain
Enum listing all gain settings for the TSL2591 component.
Definition tsl2591.h:31
TSL2591IntegrationTime
Enum listing all conversion/integration time settings for the TSL2591.
Definition tsl2591.h:17
@ TSL2591_INTEGRATION_TIME_100MS
Definition tsl2591.h:18
TSL2591SensorChannel
Enum listing sensor channels.
Definition tsl2591.h:55
@ TSL2591_SENSOR_CHANNEL_INFRARED
Definition tsl2591.h:57
@ TSL2591_SENSOR_CHANNEL_VISIBLE
Definition tsl2591.h:56
@ TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM
Definition tsl2591.h:58
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27
T id(T value)
Helper function to make id(var) known from lambdas work in custom components.
Definition helpers.h:798