ESPHome 2025.6.3
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, "I2C write failed");
30 }
31}
32
34 if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE, TSL2591_ENABLE_POWEROFF)) {
35 ESP_LOGE(TAG, "I2C write failed");
36 }
37}
38
44
46 ESP_LOGCONFIG(TAG, "Running setup for address 0x%02X", this->address_);
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 uint8_t id;
66 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_DEVICE_ID, &id)) {
67 ESP_LOGE(TAG, "I2C read failed");
68 this->mark_failed();
69 return;
70 }
71
72 if (id != 0x50) {
73 ESP_LOGE(TAG, "Unknown chip ID");
74 this->mark_failed();
75 return;
76 }
77
80}
81
83 ESP_LOGCONFIG(TAG, "TSL2591:");
84 LOG_I2C_DEVICE(this);
85
86 if (this->is_failed()) {
87 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
88 return;
89 }
90
91 ESP_LOGCONFIG(TAG, " Name: %s", this->name_);
93 int gain = 0;
94 std::string gain_word = "unknown";
95 switch (raw_gain) {
97 gain = 1;
98 gain_word = "low";
99 break;
101 gain = 25;
102 gain_word = "medium";
103 break;
105 gain = 400;
106 gain_word = "high";
107 break;
109 gain = 9500;
110 gain_word = "maximum";
111 break;
113 gain = -1;
114 gain_word = "auto";
115 break;
116 }
117 TSL2591IntegrationTime raw_timing = this->integration_time_;
118 int timing_ms = (1 + raw_timing) * 100;
119 ESP_LOGCONFIG(TAG,
120 " Gain: %dx (%s)"
121 " Integration Time: %d ms\n"
122 " Power save mode enabled: %s\n"
123 " Device factor: %f\n"
124 " Glass attenuation factor: %f",
125 gain, gain_word.c_str(), timing_ms, ONOFF(this->power_save_mode_enabled_), this->device_factor_,
127 LOG_SENSOR(" ", "Full spectrum:", this->full_spectrum_sensor_);
128 LOG_SENSOR(" ", "Infrared:", this->infrared_sensor_);
129 LOG_SENSOR(" ", "Visible:", this->visible_sensor_);
130 LOG_SENSOR(" ", "Calculated lux:", this->calculated_lux_sensor_);
131 LOG_SENSOR(" ", "Actual gain:", this->actual_gain_sensor_);
132
133 LOG_UPDATE_INTERVAL(this);
134}
135
137 uint32_t combined = this->get_combined_illuminance();
138 uint16_t visible = this->get_illuminance(TSL2591_SENSOR_CHANNEL_VISIBLE, combined);
139 uint16_t infrared = this->get_illuminance(TSL2591_SENSOR_CHANNEL_INFRARED, combined);
140 uint16_t full = this->get_illuminance(TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM, combined);
141 float lux = this->get_calculated_lux(full, infrared);
142 uint16_t actual_gain = this->get_actual_gain();
143 ESP_LOGD(TAG, "Got illuminance: combined 0x%" PRIX32 ", full %d, IR %d, vis %d. Calc lux: %f. Actual gain: %d.",
144 combined, full, infrared, visible, lux, actual_gain);
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
158 if (this->component_gain_ == TSL2591_CGAIN_AUTO) {
159 this->automatic_gain_update(full);
160 }
161
162 if (this->actual_gain_sensor_ != nullptr) {
163 this->actual_gain_sensor_->publish_state(actual_gain);
164 }
165 this->status_clear_warning();
166}
167
168#define interval_name "tsl2591_interval_for_update"
169
171 if (!this->is_adc_valid()) {
172 uint64_t now = millis();
173 ESP_LOGD(TAG, "Elapsed %3llu ms; still waiting for valid ADC", (now - this->interval_start_));
174 if (now > this->interval_timeout_) {
175 ESP_LOGW(TAG, "Interval timeout for '%s' expired before ADCs became valid", this->name_);
176 this->cancel_interval(interval_name);
177 }
178 return;
179 }
180 this->cancel_interval(interval_name);
181 this->process_update_();
182}
183
185 if (!is_failed()) {
186 if (this->power_save_mode_enabled_) {
187 // we enabled it here, else ADC will never become valid
188 // but actually doing the reads will disable device if needed
189 this->enable();
190 }
191 if (this->is_adc_valid()) {
192 this->process_update_();
193 } else {
194 this->interval_start_ = millis();
195 this->interval_timeout_ = this->interval_start_ + 620;
196 this->set_interval(interval_name, 100, [this] { this->interval_function_for_update_(); });
197 }
198 }
199}
200
202 this->infrared_sensor_ = infrared_sensor;
203}
204
205void TSL2591Component::set_visible_sensor(sensor::Sensor *visible_sensor) { this->visible_sensor_ = visible_sensor; }
206
208 this->full_spectrum_sensor_ = full_spectrum_sensor;
209}
210
212 this->calculated_lux_sensor_ = calculated_lux_sensor;
213}
214
216 this->actual_gain_sensor_ = actual_gain_sensor;
217}
218
222
224
225void TSL2591Component::set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor) {
226 this->device_factor_ = device_factor;
227 this->glass_attenuation_factor_ = glass_attenuation_factor;
228}
229
231 this->enable();
233 this->gain_ = gain;
234 if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL,
235 this->integration_time_ | this->gain_)) { // NOLINT
236 ESP_LOGE(TAG, "I2C write failed");
237 }
238 // The ADC values can be confused if gain or integration time are changed in the middle of a cycle.
239 // So, we unconditionally disable the device to turn the ADCs off. When re-enabling, the ADCs
240 // will tell us when they are ready again. That avoids an initial bogus reading.
241 this->disable();
242 if (!this->power_save_mode_enabled_) {
243 this->enable();
244 }
245}
246
248
249void TSL2591Component::set_name(const char *name) { this->name_ = name; }
250
252
254 uint8_t status;
255 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_STATUS, &status)) {
256 ESP_LOGE(TAG, "I2C read failed");
257 return false;
258 }
259 return status & 0x01;
260}
261
263 this->enable();
264 // Wait x ms for ADC to complete and signal valid.
265 // The max integration time is 600ms, so that's our max delay.
266 // (But we use 620ms as a bit of slack.)
267 // We'll do mini-delays and break out as soon as the ADC is good.
268 bool avalid;
269 const uint8_t mini_delay = 100;
270 for (uint16_t d = 0; d < 620; d += mini_delay) {
271 avalid = this->is_adc_valid();
272 if (avalid) {
273 break;
274 }
275 // we only log this if we need any delay, since normally we don't
276 ESP_LOGD(TAG, " after %3d ms: ADC valid? %s", d, avalid ? "true" : "false");
277 delay(mini_delay);
278 }
279 if (!avalid) {
280 // still not valid after a sutiable delay
281 // we don't mark the device as failed since it might come around in the future (probably not :-()
282 ESP_LOGE(TAG, "Device '%s' returned invalid readings", this->name_);
284 return 0;
285 }
286
287 // CHAN0 must be read before CHAN1
288 // See: https://forums.adafruit.com/viewtopic.php?f=19&t=124176
289 // Also, low byte must be read before high byte..
290 // We read the registers in the order described in the datasheet.
291 uint32_t x32;
292 uint8_t ch0low, ch0high, ch1low, ch1high;
293 uint16_t ch0_16;
294 uint16_t ch1_16;
295 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_LOW, &ch0low)) {
296 ESP_LOGE(TAG, "I2C read failed");
297 return 0;
298 }
299 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_HIGH, &ch0high)) {
300 ESP_LOGE(TAG, "I2C read failed");
301 return 0;
302 }
303 ch0_16 = (ch0high << 8) | ch0low;
304 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_LOW, &ch1low)) {
305 ESP_LOGE(TAG, "I2C read failed");
306 return 0;
307 }
308 if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_HIGH, &ch1high)) {
309 ESP_LOGE(TAG, "I2C read failed");
310 return 0;
311 }
312 ch1_16 = (ch1high << 8) | ch1low;
313 x32 = (ch1_16 << 16) | ch0_16;
314
316 return x32;
317}
318
320 uint32_t combined = this->get_combined_illuminance();
321 return this->get_illuminance(channel, combined);
322}
323// logic cloned from Adafruit TSL2591 library
324uint16_t TSL2591Component::get_illuminance(TSL2591SensorChannel channel, uint32_t combined_illuminance) {
326 // Reads two byte value from channel 0 (visible + infrared)
327 return (combined_illuminance & 0xFFFF);
328 } else if (channel == TSL2591_SENSOR_CHANNEL_INFRARED) {
329 // Reads two byte value from channel 1 (infrared)
330 return (combined_illuminance >> 16);
331 } else if (channel == TSL2591_SENSOR_CHANNEL_VISIBLE) {
332 // Reads all and subtracts out the infrared
333 return ((combined_illuminance & 0xFFFF) - (combined_illuminance >> 16));
334 }
335 // unknown channel!
336 ESP_LOGE(TAG, "get_illuminance() caller requested an unknown channel: %d", channel);
337 return 0;
338}
339
354float TSL2591Component::get_calculated_lux(uint16_t full_spectrum, uint16_t infrared) {
355 // Check for overflow conditions first
356 uint16_t max_count = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS ? 36863 : 65535);
357 if ((full_spectrum == max_count) || (infrared == max_count)) {
358 // Signal an overflow
359 ESP_LOGW(TAG, "Apparent saturation on '%s'; try reducing the gain or integration time", this->name_);
360 return NAN;
361 }
362
363 if ((full_spectrum == 0) && (infrared == 0)) {
364 // trivial conversion; avoids divide by 0
365 ESP_LOGW(TAG, "Zero reading on both '%s' sensors", this->name_);
366 return 0.0F;
367 }
368
369 float atime = 100.F + (this->integration_time_ * 100);
370
371 float again;
372 switch (this->gain_) {
373 case TSL2591_GAIN_LOW:
374 again = 1.0F;
375 break;
376 case TSL2591_GAIN_MED:
377 again = 25.0F;
378 break;
380 again = 400.0F;
381 break;
382 case TSL2591_GAIN_MAX:
383 again = 9500.0F;
384 break;
385 default:
386 again = 1.0F;
387 break;
388 }
389 // This lux equation is copied from the Adafruit TSL2591 v1.4.0 and modified slightly.
390 // See: https://github.com/adafruit/Adafruit_TSL2591_Library/issues/14
391 // and that library code.
392 // They said:
393 // Note: This algorithm is based on preliminary coefficients
394 // provided by AMS and may need to be updated in the future
395 // However, we use gain multipliers that are more in line with the midpoints
396 // of ranges from the datasheet. We don't know why the other libraries
397 // used the values they did for HIGH and MAX.
398 // If cpl or full_spectrum are 0, this will return NaN due to divide by 0.
399 // For the curious "cpl" is counts per lux, a term used in AMS application notes.
400 float cpl = (atime * again) / (this->device_factor_ * this->glass_attenuation_factor_);
401 float lux = (((float) full_spectrum - (float) infrared)) * (1.0F - ((float) infrared / (float) full_spectrum)) / cpl;
402 return std::max(lux, 0.0F);
403}
404
418void TSL2591Component::automatic_gain_update(uint16_t full_spectrum) {
419 TSL2591Gain new_gain = this->gain_;
420 uint fs_divider = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS) ? 2 : 1;
421
422 switch (this->gain_) {
423 case TSL2591_GAIN_LOW:
424 if (full_spectrum < 54) { // 1/3 FS / GAIN_HIGH
425 new_gain = TSL2591_GAIN_HIGH;
426 } else if (full_spectrum < 875) { // 1/3 FS / GAIN_MED
427 new_gain = TSL2591_GAIN_MED;
428 }
429 break;
430 case TSL2591_GAIN_MED:
431 if (full_spectrum < 57) { // 1/3 FS / (GAIN_MAX/GAIN_MED)
432 new_gain = TSL2591_GAIN_MAX;
433 } else if (full_spectrum < 1365) { // 1/3 FS / (GAIN_HIGH/GAIN_MED)
434 new_gain = TSL2591_GAIN_HIGH;
435 } else if (full_spectrum > 62000 / fs_divider) { // 2/3 FS / (GAIN_LOW/GAIN_MED) clipped to 95% FS
436 new_gain = TSL2591_GAIN_LOW;
437 }
438 break;
440 if (full_spectrum < 920) { // 1/3 FS / (GAIN_MAX/GAIN_HIGH)
441 new_gain = TSL2591_GAIN_MAX;
442 } else if (full_spectrum > 62000 / fs_divider) { // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS
443 new_gain = TSL2591_GAIN_LOW;
444 }
445 break;
446 case TSL2591_GAIN_MAX:
447 if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS
448 new_gain = TSL2591_GAIN_MED;
449 break;
450 }
451
452 if (this->gain_ != new_gain) {
453 this->gain_ = new_gain;
455 }
456 ESP_LOGD(TAG, "Gain setting: %d", this->gain_);
457}
458
464 switch (this->gain_) {
465 case TSL2591_GAIN_LOW:
466 return 1.0F;
467 case TSL2591_GAIN_MED:
468 return 25.0F;
470 return 400.0F;
471 case TSL2591_GAIN_MAX:
472 return 9500.0F;
473 default:
474 // Shouldn't get here, but just in case.
475 return NAN;
476 }
477}
478
479} // namespace tsl2591
480} // namespace esphome
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:58
bool cancel_interval(const std::string &name)
Cancel an interval function.
Definition component.cpp:62
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:62
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:82
float get_setup_priority() const override
Used by ESPHome framework.
Definition tsl2591.cpp:251
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:418
bool is_adc_valid()
Are the device ADC values valid?
Definition tsl2591.cpp:253
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:354
sensor::Sensor * calculated_lux_sensor_
Definition tsl2591.h:260
void set_visible_sensor(sensor::Sensor *visible_sensor)
Used by ESPHome framework.
Definition tsl2591.cpp:205
void set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor)
Sets the device and glass attenuation factors.
Definition tsl2591.cpp:225
sensor::Sensor * full_spectrum_sensor_
Definition tsl2591.h:257
void set_integration_time(TSL2591IntegrationTime integration_time)
Used by ESPHome framework.
Definition tsl2591.cpp:219
void set_calculated_lux_sensor(sensor::Sensor *calculated_lux_sensor)
Used by ESPHome framework.
Definition tsl2591.cpp:211
uint32_t get_combined_illuminance()
Get the combined illuminance value.
Definition tsl2591.cpp:262
void set_name(const char *name)
Sets the name for this instance of the device.
Definition tsl2591.cpp:249
sensor::Sensor * visible_sensor_
Definition tsl2591.h:259
void set_infrared_sensor(sensor::Sensor *infrared_sensor)
Used by ESPHome framework.
Definition tsl2591.cpp:201
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:215
void set_gain(TSL2591ComponentGain gain)
Used by ESPHome framework.
Definition tsl2591.cpp:223
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:184
void set_integration_time_and_gain(TSL2591IntegrationTime integration_time, TSL2591Gain gain)
Set device integration time and gain.
Definition tsl2591.cpp:230
void set_full_spectrum_sensor(sensor::Sensor *full_spectrum_sensor)
Used by ESPHome framework.
Definition tsl2591.cpp:207
void disable()
Powers off the TSL2591 device.
Definition tsl2591.cpp:33
float get_actual_gain()
Reads the actual gain used.
Definition tsl2591.cpp:463
uint16_t get_illuminance(TSL2591SensorChannel channel)
Get an individual sensor channel reading.
Definition tsl2591.cpp:319
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:247
AlsGain501 gain
IntegrationTime501 integration_time
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:20
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:29
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
T id(T value)
Helper function to make id(var) known from lambdas work in custom components.
Definition helpers.h:799