Support component tsl2591 (#2131)

Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: WJCarpenter <bill@carpenter.org>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
WJCarpenter 2021-08-10 01:48:06 -07:00 committed by GitHub
parent c6c2842bdb
commit 183e2a8471
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 813 additions and 2 deletions

View file

@ -138,6 +138,7 @@ esphome/components/tmp102/* @timsavage
esphome/components/tmp117/* @Azimath
esphome/components/tof10120/* @wstrzalka
esphome/components/toshiba/* @kbx81
esphome/components/tsl2591/* @wjcarpenter
esphome/components/tuya/binary_sensor/* @jesserockz
esphome/components/tuya/climate/* @jesserockz
esphome/components/tuya/sensor/* @jesserockz

View file

@ -2,7 +2,9 @@ from string import ascii_letters, digits
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.components import color
from esphome.const import (
CONF_VISIBLE,
)
from . import CONF_NEXTION_ID
from . import Nextion
@ -25,7 +27,6 @@ CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color"
CONF_FOREGROUND_COLOR = "foreground_color"
CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color"
CONF_FONT_ID = "font_id"
CONF_VISIBLE = "visible"
def NextionName(value):

View file

@ -0,0 +1 @@
CODEOWNERS = ["@wjcarpenter"]

View file

@ -0,0 +1,168 @@
# Credit where due....
# I put a certain amount of work into this, but a lot of ESPHome integration is
# "look for other examples and see what they do" programming-by-example. Here are
# things that helped me along with this:
#
# - I mined the existing tsl2561 integration for basic structural framing for both
# the code and documentation.
#
# - I looked at the existing bme280 integration as an example of a single device
# with multiple sensors.
#
# - Comments and code in this thread got me going with the Adafruit TSL2591 library
# and prompted my desired to have tsl2591 as a standard component instead of a
# custom/external component.
#
# - And, of course, the handy and available Adafruit TSL2591 library was very
# helpful in understanding what the device is actually talking about.
#
# Here is the project that started me down the TSL2591 device trail in the first
# place: https://hackaday.io/project/176690-the-water-watcher
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_GAIN,
CONF_ID,
CONF_NAME,
CONF_INTEGRATION_TIME,
CONF_FULL_SPECTRUM,
CONF_INFRARED,
CONF_POWER_SAVE_MODE,
CONF_VISIBLE,
CONF_CALCULATED_LUX,
CONF_DEVICE_FACTOR,
CONF_GLASS_ATTENUATION_FACTOR,
ICON_BRIGHTNESS_6,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_ILLUMINANCE,
STATE_CLASS_MEASUREMENT,
UNIT_EMPTY,
UNIT_LUX,
)
DEPENDENCIES = ["i2c"]
tsl2591_ns = cg.esphome_ns.namespace("tsl2591")
TSL2591IntegrationTime = tsl2591_ns.enum("TSL2591IntegrationTime")
INTEGRATION_TIMES = {
100: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_100MS,
200: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_200MS,
300: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_300MS,
400: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_400MS,
500: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_500MS,
600: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_600MS,
}
TSL2591Gain = tsl2591_ns.enum("TSL2591Gain")
GAINS = {
"1X": TSL2591Gain.TSL2591_GAIN_LOW,
"LOW": TSL2591Gain.TSL2591_GAIN_LOW,
"25X": TSL2591Gain.TSL2591_GAIN_MED,
"MED": TSL2591Gain.TSL2591_GAIN_MED,
"MEDIUM": TSL2591Gain.TSL2591_GAIN_MED,
"400X": TSL2591Gain.TSL2591_GAIN_HIGH,
"HIGH": TSL2591Gain.TSL2591_GAIN_HIGH,
"9500X": TSL2591Gain.TSL2591_GAIN_MAX,
"MAX": TSL2591Gain.TSL2591_GAIN_MAX,
"MAXIMUM": TSL2591Gain.TSL2591_GAIN_MAX,
}
def validate_integration_time(value):
value = cv.positive_time_period_milliseconds(value).total_milliseconds
return cv.enum(INTEGRATION_TIMES, int=True)(value)
TSL2591Component = tsl2591_ns.class_(
"TSL2591Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(TSL2591Component),
cv.Optional(CONF_INFRARED): sensor.sensor_schema(
UNIT_EMPTY,
ICON_BRIGHTNESS_6,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VISIBLE): sensor.sensor_schema(
UNIT_EMPTY,
ICON_BRIGHTNESS_6,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_FULL_SPECTRUM): sensor.sensor_schema(
UNIT_EMPTY,
ICON_BRIGHTNESS_6,
0,
DEVICE_CLASS_EMPTY,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CALCULATED_LUX): sensor.sensor_schema(
UNIT_LUX,
ICON_BRIGHTNESS_6,
4,
DEVICE_CLASS_ILLUMINANCE,
STATE_CLASS_MEASUREMENT,
),
cv.Optional(
CONF_INTEGRATION_TIME, default="100ms"
): validate_integration_time,
cv.Optional(CONF_NAME, default="TLS2591"): cv.string,
cv.Optional(CONF_GAIN, default="MEDIUM"): cv.enum(GAINS, upper=True),
cv.Optional(CONF_POWER_SAVE_MODE, default=True): cv.boolean,
cv.Optional(CONF_DEVICE_FACTOR, default=53.0): cv.float_with_unit(
"device_factor", "", True
),
cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=7.7): cv.float_with_unit(
"glass_attenuation_factor", "", True
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x29))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if CONF_FULL_SPECTRUM in config:
conf = config[CONF_FULL_SPECTRUM]
sens = await sensor.new_sensor(conf)
cg.add(var.set_full_spectrum_sensor(sens))
if CONF_INFRARED in config:
conf = config[CONF_INFRARED]
sens = await sensor.new_sensor(conf)
cg.add(var.set_infrared_sensor(sens))
if CONF_VISIBLE in config:
conf = config[CONF_VISIBLE]
sens = await sensor.new_sensor(conf)
cg.add(var.set_visible_sensor(sens))
if CONF_CALCULATED_LUX in config:
conf = config[CONF_CALCULATED_LUX]
sens = await sensor.new_sensor(conf)
cg.add(var.set_calculated_lux_sensor(sens))
cg.add(var.set_name(config[CONF_NAME]))
cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE]))
cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME]))
cg.add(var.set_gain(config[CONF_GAIN]))
cg.add(
var.set_device_and_glass_attenuation_factors(
config[CONF_DEVICE_FACTOR], config[CONF_GLASS_ATTENUATION_FACTOR]
)
)

View file

@ -0,0 +1,369 @@
#include "tsl2591.h"
#include "esphome/core/log.h"
namespace esphome {
namespace tsl2591 {
static const char *const TAG = "tsl2591.sensor";
// Various constants used in TSL2591 register manipulation
#define TSL2591_COMMAND_BIT (0xA0) // 1010 0000: bits 7 and 5 for 'command, normal'
#define TSL2591_ENABLE_POWERON (0x01) // Flag for ENABLE register, to enable
#define TSL2591_ENABLE_POWEROFF (0x00) // Flag for ENABLE register, to disable
#define TSL2591_ENABLE_AEN (0x02) // Flag for ENABLE register, to turn on ADCs
// TSL2591 registers from the datasheet. We only define what we use.
#define TSL2591_REGISTER_ENABLE (0x00)
#define TSL2591_REGISTER_CONTROL (0x01)
#define TSL2591_REGISTER_DEVICE_ID (0x12)
#define TSL2591_REGISTER_STATUS (0x13)
#define TSL2591_REGISTER_CHAN0_LOW (0x14)
#define TSL2591_REGISTER_CHAN0_HIGH (0x15)
#define TSL2591_REGISTER_CHAN1_LOW (0x16)
#define TSL2591_REGISTER_CHAN1_HIGH (0x17)
void TSL2591Component::enable() {
// Enable the device by setting the control bit to 0x01. Also turn on ADCs.
if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE, TSL2591_ENABLE_POWERON | TSL2591_ENABLE_AEN)) {
ESP_LOGE(TAG, "Failed I2C write during enable()");
}
}
void TSL2591Component::disable() {
if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_ENABLE, TSL2591_ENABLE_POWEROFF)) {
ESP_LOGE(TAG, "Failed I2C write during disable()");
}
}
void TSL2591Component::disable_if_power_saving_() {
if (this->power_save_mode_enabled_) {
this->disable();
}
}
void TSL2591Component::setup() {
uint8_t address = this->address_;
ESP_LOGI(TAG, "Setting up TSL2591 sensor at I2C address 0x%02X", address);
uint8_t id;
if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_DEVICE_ID, &id)) {
ESP_LOGE(TAG, "Failed I2C read during setup()");
this->mark_failed();
return;
}
if (id != 0x50) {
ESP_LOGE(TAG,
"Could not find the TSL2591 sensor. The ID register of the device at address 0x%02X reported 0x%02X "
"instead of 0x50.",
address, id);
this->mark_failed();
return;
}
this->set_integration_time_and_gain(this->integration_time_, this->gain_);
this->disable_if_power_saving_();
}
void TSL2591Component::dump_config() {
ESP_LOGCONFIG(TAG, "TSL2591:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with TSL2591 failed earlier, during setup");
return;
}
ESP_LOGCONFIG(TAG, " Name: %s", this->name_);
TSL2591Gain raw_gain = this->gain_;
int gain = 0;
std::string gain_word = "unknown";
switch (raw_gain) {
case TSL2591_GAIN_LOW:
gain = 1;
gain_word = "low";
break;
case TSL2591_GAIN_MED:
gain = 25;
gain_word = "medium";
break;
case TSL2591_GAIN_HIGH:
gain = 400;
gain_word = "high";
break;
case TSL2591_GAIN_MAX:
gain = 9500;
gain_word = "maximum";
break;
}
ESP_LOGCONFIG(TAG, " Gain: %dx (%s)", gain, gain_word.c_str());
TSL2591IntegrationTime raw_timing = this->integration_time_;
int timing_ms = (1 + raw_timing) * 100;
ESP_LOGCONFIG(TAG, " Integration Time: %d ms", timing_ms);
ESP_LOGCONFIG(TAG, " Power save mode enabled: %s", ONOFF(this->power_save_mode_enabled_));
ESP_LOGCONFIG(TAG, " Device factor: %f", this->device_factor_);
ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_);
LOG_SENSOR(" ", "Full spectrum:", this->full_spectrum_sensor_);
LOG_SENSOR(" ", "Infrared:", this->infrared_sensor_);
LOG_SENSOR(" ", "Visible:", this->visible_sensor_);
LOG_SENSOR(" ", "Calculated lux:", this->calculated_lux_sensor_);
LOG_UPDATE_INTERVAL(this);
}
void TSL2591Component::process_update_() {
uint32_t combined = this->get_combined_illuminance();
uint16_t visible = this->get_illuminance(TSL2591_SENSOR_CHANNEL_VISIBLE, combined);
uint16_t infrared = this->get_illuminance(TSL2591_SENSOR_CHANNEL_INFRARED, combined);
uint16_t full = this->get_illuminance(TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM, combined);
float lux = this->get_calculated_lux(full, infrared);
ESP_LOGD(TAG, "Got illuminance: combined 0x%X, full %d, IR %d, vis %d. Calc lux: %f", combined, full, infrared,
visible, lux);
if (this->full_spectrum_sensor_ != nullptr) {
this->full_spectrum_sensor_->publish_state(full);
}
if (this->infrared_sensor_ != nullptr) {
this->infrared_sensor_->publish_state(infrared);
}
if (this->visible_sensor_ != nullptr) {
this->visible_sensor_->publish_state(visible);
}
if (this->calculated_lux_sensor_ != nullptr) {
this->calculated_lux_sensor_->publish_state(lux);
}
this->status_clear_warning();
}
#define interval_name "tsl2591_interval_for_update"
void TSL2591Component::interval_function_for_update_() {
if (!this->is_adc_valid()) {
uint64_t now = millis();
ESP_LOGD(TAG, "Elapsed %3llu ms; still waiting for valid ADC", (now - this->interval_start_));
if (now > this->interval_timeout_) {
ESP_LOGW(TAG, "Interval timeout for TSL2591 '%s' expired before ADCs became valid.", this->name_);
this->cancel_interval(interval_name);
}
return;
}
this->cancel_interval(interval_name);
this->process_update_();
}
void TSL2591Component::update() {
if (!is_failed()) {
if (this->power_save_mode_enabled_) {
// we enabled it here, else ADC will never become valid
// but actually doing the reads will disable device if needed
this->enable();
}
if (this->is_adc_valid()) {
this->process_update_();
} else {
this->interval_start_ = millis();
this->interval_timeout_ = this->interval_start_ + 620;
this->set_interval(interval_name, 100, [this] { this->interval_function_for_update_(); });
}
}
}
void TSL2591Component::set_infrared_sensor(sensor::Sensor *infrared_sensor) {
this->infrared_sensor_ = infrared_sensor;
}
void TSL2591Component::set_visible_sensor(sensor::Sensor *visible_sensor) { this->visible_sensor_ = visible_sensor; }
void TSL2591Component::set_full_spectrum_sensor(sensor::Sensor *full_spectrum_sensor) {
this->full_spectrum_sensor_ = full_spectrum_sensor;
}
void TSL2591Component::set_calculated_lux_sensor(sensor::Sensor *calculated_lux_sensor) {
this->calculated_lux_sensor_ = calculated_lux_sensor;
}
void TSL2591Component::set_integration_time(TSL2591IntegrationTime integration_time) {
this->integration_time_ = integration_time;
}
void TSL2591Component::set_gain(TSL2591Gain gain) { this->gain_ = gain; }
void TSL2591Component::set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor) {
this->device_factor_ = device_factor;
this->glass_attenuation_factor_ = glass_attenuation_factor;
}
void TSL2591Component::set_integration_time_and_gain(TSL2591IntegrationTime integration_time, TSL2591Gain gain) {
this->enable();
this->integration_time_ = integration_time;
this->gain_ = gain;
if (!this->write_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CONTROL,
this->integration_time_ | this->gain_)) { // NOLINT
ESP_LOGE(TAG, "Failed I2C write during set_integration_time_and_gain()");
}
// The ADC values can be confused if gain or integration time are changed in the middle of a cycle.
// So, we unconditionally disable the device to turn the ADCs off. When re-enabling, the ADCs
// will tell us when they are ready again. That avoids an initial bogus reading.
this->disable();
if (!this->power_save_mode_enabled_) {
this->enable();
}
}
void TSL2591Component::set_power_save_mode(bool enable) { this->power_save_mode_enabled_ = enable; }
void TSL2591Component::set_name(const char *name) { this->name_ = name; }
float TSL2591Component::get_setup_priority() const { return setup_priority::DATA; }
bool TSL2591Component::is_adc_valid() {
uint8_t status;
if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_STATUS, &status)) {
ESP_LOGE(TAG, "Failed I2C read during is_adc_valid()");
return false;
}
return status & 0x01;
}
uint32_t TSL2591Component::get_combined_illuminance() {
this->enable();
// Wait x ms for ADC to complete and signal valid.
// The max integration time is 600ms, so that's our max delay.
// (But we use 620ms as a bit of slack.)
// We'll do mini-delays and break out as soon as the ADC is good.
bool avalid;
const uint8_t mini_delay = 100;
for (uint16_t d = 0; d < 620; d += mini_delay) {
avalid = this->is_adc_valid();
if (avalid) {
break;
}
// we only log this if we need any delay, since normally we don't
ESP_LOGD(TAG, " after %3d ms: ADC valid? %s", d, avalid ? "true" : "false");
delay(mini_delay);
}
if (!avalid) {
// still not valid after a sutiable delay
// we don't mark the device as failed since it might come around in the future (probably not :-()
ESP_LOGE(TAG, "tsl2591 device '%s' did not return valid readings.", this->name_);
this->disable_if_power_saving_();
return 0;
}
// CHAN0 must be read before CHAN1
// See: https://forums.adafruit.com/viewtopic.php?f=19&t=124176
// Also, low byte must be read before high byte..
// We read the registers in the order described in the datasheet.
uint32_t x32;
uint8_t ch0low, ch0high, ch1low, ch1high;
uint16_t ch0_16;
uint16_t ch1_16;
if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_LOW, &ch0low)) {
ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
return 0;
}
if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN0_HIGH, &ch0high)) {
ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
return 0;
}
ch0_16 = (ch0high << 8) | ch0low;
if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_LOW, &ch1low)) {
ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
return 0;
}
if (!this->read_byte(TSL2591_COMMAND_BIT | TSL2591_REGISTER_CHAN1_HIGH, &ch1high)) {
ESP_LOGE(TAG, "Failed I2C read during get_combined_illuminance()");
return 0;
}
ch1_16 = (ch1high << 8) | ch1low;
x32 = (ch1_16 << 16) | ch0_16;
this->disable_if_power_saving_();
return x32;
}
uint16_t TSL2591Component::get_illuminance(TSL2591SensorChannel channel) {
uint32_t combined = this->get_combined_illuminance();
return this->get_illuminance(channel, combined);
}
// logic cloned from Adafruit TSL2591 library
uint16_t TSL2591Component::get_illuminance(TSL2591SensorChannel channel, uint32_t combined_illuminance) {
if (channel == TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM) {
// Reads two byte value from channel 0 (visible + infrared)
return (combined_illuminance & 0xFFFF);
} else if (channel == TSL2591_SENSOR_CHANNEL_INFRARED) {
// Reads two byte value from channel 1 (infrared)
return (combined_illuminance >> 16);
} else if (channel == TSL2591_SENSOR_CHANNEL_VISIBLE) {
// Reads all and subtracts out the infrared
return ((combined_illuminance & 0xFFFF) - (combined_illuminance >> 16));
}
// unknown channel!
ESP_LOGE(TAG, "TSL2591Component::get_illuminance() caller requested an unknown channel: %d", channel);
return 0;
}
/** Calculates a lux value from the two TSL2591 physical sensor ADC readings.
*
* The lux calculation is copied from the Adafruit TSL2591 library.
* There is some debate about whether it is the correct lux equation to use.
* We use that lux equation because (a) it helps with a transition from
* using that Adafruit library to using this ESPHome integration, and (b) we
* don't have a definitive better idea.
*
* Since the raw ADC readings are available, you can ignore this method and
* implement your own lux equation.
*
* @param full_spectrum The ADC reading for TSL2591 channel 0.
* @param infrared The ADC reading for TSL2591 channel 1.
*/
float TSL2591Component::get_calculated_lux(uint16_t full_spectrum, uint16_t infrared) {
// Check for overflow conditions first
uint16_t max_count = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS ? 36863 : 65535);
if ((full_spectrum == max_count) || (infrared == max_count)) {
// Signal an overflow
ESP_LOGW(TAG, "Apparent saturation on TSL2591 (%s). You could reduce the gain.", this->name_);
return -1.0F;
}
if ((full_spectrum == 0) && (infrared == 0)) {
// trivial conversion; avoids divide by 0
ESP_LOGW(TAG, "Zero reading on both TSL2591 (%s) sensors. Is the device having a problem?", this->name_);
return 0.0F;
}
float atime = 100.F + (this->integration_time_ * 100);
float again;
switch (this->gain_) {
case TSL2591_GAIN_LOW:
again = 1.0F;
break;
case TSL2591_GAIN_MED:
again = 25.0F;
break;
case TSL2591_GAIN_HIGH:
again = 400.0F;
break;
case TSL2591_GAIN_MAX:
again = 9500.0F;
break;
default:
again = 1.0F;
break;
}
// This lux equation is copied from the Adafruit TSL2591 v1.4.0 and modified slightly.
// See: https://github.com/adafruit/Adafruit_TSL2591_Library/issues/14
// and that library code.
// They said:
// Note: This algorithm is based on preliminary coefficients
// provided by AMS and may need to be updated in the future
// However, we use gain multipliers that are more in line with the midpoints
// of ranges from the datasheet. We don't know why the other libraries
// used the values they did for HIGH and MAX.
// If cpl or full_spectrum are 0, this will return NaN due to divide by 0.
// For the curious "cpl" is counts per lux, a term used in AMS application notes.
float cpl = (atime * again) / (this->device_factor_ * this->glass_attenuation_factor_);
float lux = (((float) full_spectrum - (float) infrared)) * (1.0F - ((float) infrared / (float) full_spectrum)) / cpl;
return max(lux, 0.0F);
}
} // namespace tsl2591
} // namespace esphome

View file

@ -0,0 +1,245 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace tsl2591 {
/** Enum listing all conversion/integration time settings for the TSL2591.
*
* Specific values of the enum constants are register values taken from the TSL2591 datasheet.
* Longer times mean more accurate results, but will take more energy/more time.
*/
enum TSL2591IntegrationTime {
TSL2591_INTEGRATION_TIME_100MS = 0b000,
TSL2591_INTEGRATION_TIME_200MS = 0b001,
TSL2591_INTEGRATION_TIME_300MS = 0b010,
TSL2591_INTEGRATION_TIME_400MS = 0b011,
TSL2591_INTEGRATION_TIME_500MS = 0b100,
TSL2591_INTEGRATION_TIME_600MS = 0b101,
};
/** Enum listing all gain settings for the TSL2591.
*
* Specific values of the enum constants are register values taken from the TSL2591 datasheet.
* Higher values are better for low light situations, but can increase noise.
*/
enum TSL2591Gain {
TSL2591_GAIN_LOW = 0b00 << 4, // 1x
TSL2591_GAIN_MED = 0b01 << 4, // 25x
TSL2591_GAIN_HIGH = 0b10 << 4, // 400x
TSL2591_GAIN_MAX = 0b11 << 4, // 9500x
};
/** Enum listing sensor channels.
*
* They identify the type of light to report.
*/
enum TSL2591SensorChannel {
TSL2591_SENSOR_CHANNEL_VISIBLE,
TSL2591_SENSOR_CHANNEL_INFRARED,
TSL2591_SENSOR_CHANNEL_FULL_SPECTRUM,
};
/// This class includes support for the TSL2591 i2c ambient light
/// sensor. The device has two distinct sensors. One is for visible
/// light plus infrared light, and the other is for infrared
/// light. They are reported as separate sensors, and the difference
/// between the values is reported as a third sensor as a convenience
/// for visible light only.
class TSL2591Component : public PollingComponent, public i2c::I2CDevice {
public:
/** Set device integration time and gain.
*
* These are set as a single I2C transaction, so you must supply values
* for both.
*
* Longer integration times provides more accurate values, but also
* means more power consumption. Higher gain values are useful for
* lower light intensities but are also subject to more noise. The
* device might use a slightly different gain multiplier than those
* indicated; see the datasheet for details.
*
* Possible values for integration_time (from enum
* TSL2591IntegrationTime) are:
*
* - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_100MS`
* - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_200MS`
* - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_300MS`
* - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_400MS`
* - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_500MS`
* - `esphome::tsl2591::TSL2591_INTEGRATION_TIME_600MS`
*
* Possible values for gain (from enum TSL2591Gain) are:
*
* - `esphome::tsl2591::TSL2591_GAIN_LOW` (1x)
* - `esphome::tsl2591::TSL2591_GAIN_MED` (25x)
* - `esphome::tsl2591::TSL2591_GAIN_HIGH` (400x)
* - `esphome::tsl2591::TSL2591_GAIN_MAX` (9500x)
*
* @param integration_time The new integration time.
* @param gain The new gain.
*/
void set_integration_time_and_gain(TSL2591IntegrationTime integration_time, TSL2591Gain gain);
/** Should the device be powered down between readings?
*
* The disadvantage of powering down the device between readings
* is that you have to wait for the ADC to go through an
* integration cycle before a reliable reading is available.
* This happens during ESPHome's update loop, so waiting slows
* down the entire ESP device. You should only enable this if
* you need to minimize power consumption and you can tolerate
* that delay. Otherwise, keep the default of disabling
* power save mode.
*
* @param enable Enable or disable power save mode.
*/
void set_power_save_mode(bool enable);
/** Sets the name for this instance of the device.
*
* @param name The user-friendly name.
*/
void set_name(const char *name);
/** Sets the device and glass attenuation factors.
*
* The lux equation, used to calculate the lux from the ADC readings,
* involves a scaling coefficient that is the product of a device
* factor (specific to the type of device being used) and a glass
* attenuation factor (specific to whatever glass or plastic cover
* is installed in front of the light sensors.
*
* AMS does not publish the device factor for the TSL2591. In the
* datasheet for the earlier TSL2571 and in application notes, they
* use the value 53, so we use that as the default.
*
* The glass attenuation factor depends on factors external to the
* TSL2591 and is best obtained through experimental measurements.
* The Adafruit TSL2591 library use a value of ~7.7, which we use as
* a default. Waveshare uses a value of ~14.4. Presumably, those
* factors are appropriate to the breakout boards from those vendors,
* but we have not verified that.
*
* @param device_factor The device factor.
* @param glass_attenuation_factor The glass attenuation factor.
*/
void set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor);
/** Calculates and returns a lux value based on the ADC readings.
*
* @param full_spectrum The ADC reading for the full spectrum sensor.
* @param infrared The ADC reading for the infrared sensor.
*/
float get_calculated_lux(uint16_t full_spectrum, uint16_t infrared);
/** Get the combined illuminance value.
*
* This is encoded into a 32 bit value. The high 16 bits are the value of the
* infrared sensor. The low 16 bits are the sum of the combined sensor values.
*
* If power saving mode is enabled, there can be a delay (up to the value of the integration
* time) while waiting for the device ADCs to signal that values are valid.
*/
uint32_t get_combined_illuminance();
/** Get an individual sensor channel reading.
*
* This gets an individual light sensor reading. Since it goes through
* the entire component read cycle to get one value, it's not optimal if
* you want to get all possible channel values. If you want that, first
* call `get_combined_illuminance()` and pass that value to the companion
* method with a different signature.
*
* If power saving mode is enabled, there can be a delay (up to the value of the integration
* time) while waiting for the device ADCs to signal that values are valid.
*
* @param channel The sensor channel of interest.
*/
uint16_t get_illuminance(TSL2591SensorChannel channel);
/** Get an individual sensor channel reading from combined illuminance.
*
* This gets an individual light sensor reading from a combined illuminance
* value, which you would obtain from calling `getCombinedIlluminance()`.
* This method does not communicate with the sensor at all. It's strictly
* local calculations, so it is efficient if you call it multiple times.
*
* @param channel The sensor channel of interest.
* @param combined_illuminance The previously obtained combined illuminance value.
*/
uint16_t get_illuminance(TSL2591SensorChannel channel, uint32_t combined_illuminance);
/** Are the device ADC values valid?
*
* Useful for scripting. This should be checked before calling update().
* It asks the TSL2591 if the ADC has completed an integration cycle
* and has reliable values in the device registers. If you call update()
* before the ADC values are valid, you may cause a general delay in
* the ESPHome update loop.
*
* It should take no more than the configured integration time for
* the ADC values to become valid after the TSL2591 device is enabled.
*/
bool is_adc_valid();
/** Powers on the TSL2591 device and enables its sensors.
*
* You only need to call this if you have disabled the device.
* The device starts enabled in ESPHome unless power save mode is enabled.
*/
void enable();
/** Powers off the TSL2591 device.
*
* You can call this from an ESPHome script if you are explicitly
* controlling TSL2591 power consumption.
* The device starts enabled in ESPHome unless power save mode is enabled.
*/
void disable();
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these. They're for ESPHome integration use.)
/** Used by ESPHome framework. */
void set_full_spectrum_sensor(sensor::Sensor *full_spectrum_sensor);
/** Used by ESPHome framework. */
void set_infrared_sensor(sensor::Sensor *infrared_sensor);
/** Used by ESPHome framework. */
void set_visible_sensor(sensor::Sensor *visible_sensor);
/** Used by ESPHome framework. */
void set_calculated_lux_sensor(sensor::Sensor *calculated_lux_sensor);
/** Used by ESPHome framework. Does NOT actually set the value on the device. */
void set_integration_time(TSL2591IntegrationTime integration_time);
/** Used by ESPHome framework. Does NOT actually set the value on the device. */
void set_gain(TSL2591Gain gain);
/** Used by ESPHome framework. */
void setup() override;
/** Used by ESPHome framework. */
void dump_config() override;
/** Used by ESPHome framework. */
void update() override;
/** Used by ESPHome framework. */
float get_setup_priority() const override;
protected:
const char *name_; // TODO: extend esphome::Nameable
sensor::Sensor *full_spectrum_sensor_;
sensor::Sensor *infrared_sensor_;
sensor::Sensor *visible_sensor_;
sensor::Sensor *calculated_lux_sensor_;
TSL2591IntegrationTime integration_time_;
TSL2591Gain gain_;
bool power_save_mode_enabled_;
float device_factor_;
float glass_attenuation_factor_;
uint64_t interval_start_;
uint64_t interval_timeout_;
void disable_if_power_saving_();
void process_update_();
void interval_function_for_update_();
};
} // namespace tsl2591
} // namespace esphome

View file

@ -96,6 +96,7 @@ CONF_BUFFER_SIZE = "buffer_size"
CONF_BUILD_PATH = "build_path"
CONF_BUS_VOLTAGE = "bus_voltage"
CONF_BUSY_PIN = "busy_pin"
CONF_CALCULATED_LUX = "calculated_lux"
CONF_CALIBRATE_LINEAR = "calibrate_linear"
CONF_CALIBRATION = "calibration"
CONF_CAPACITANCE = "capacitance"
@ -171,6 +172,7 @@ CONF_DELAY = "delay"
CONF_DELTA = "delta"
CONF_DEVICE = "device"
CONF_DEVICE_CLASS = "device_class"
CONF_DEVICE_FACTOR = "device_factor"
CONF_DIMENSIONS = "dimensions"
CONF_DIO_PIN = "dio_pin"
CONF_DIR_PIN = "dir_pin"
@ -244,11 +246,13 @@ CONF_FORMAT = "format"
CONF_FORWARD_ACTIVE_ENERGY = "forward_active_energy"
CONF_FREQUENCY = "frequency"
CONF_FROM = "from"
CONF_FULL_SPECTRUM = "full_spectrum"
CONF_FULL_UPDATE_EVERY = "full_update_every"
CONF_GAIN = "gain"
CONF_GAMMA_CORRECT = "gamma_correct"
CONF_GAS_RESISTANCE = "gas_resistance"
CONF_GATEWAY = "gateway"
CONF_GLASS_ATTENUATION_FACTOR = "glass_attenuation_factor"
CONF_GLYPHS = "glyphs"
CONF_GPIO = "gpio"
CONF_GREEN = "green"
@ -287,6 +291,7 @@ CONF_IMPORT_REACTIVE_ENERGY = "import_reactive_energy"
CONF_INCLUDES = "includes"
CONF_INDEX = "index"
CONF_INDOOR = "indoor"
CONF_INFRARED = "infrared"
CONF_INITIAL_MODE = "initial_mode"
CONF_INITIAL_OPTION = "initial_option"
CONF_INITIAL_VALUE = "initial_value"
@ -660,6 +665,7 @@ CONF_VALUE = "value"
CONF_VARIABLES = "variables"
CONF_VARIANT = "variant"
CONF_VERSION = "version"
CONF_VISIBLE = "visible"
CONF_VISUAL = "visual"
CONF_VOLTAGE = "voltage"
CONF_VOLTAGE_ATTENUATION = "voltage_attenuation"
@ -696,6 +702,7 @@ ICON_BLUETOOTH = "mdi:bluetooth"
ICON_BLUR = "mdi:blur"
ICON_BRIEFCASE_DOWNLOAD = "mdi:briefcase-download"
ICON_BRIGHTNESS_5 = "mdi:brightness-5"
ICON_BRIGHTNESS_6 = "mdi:brightness-6"
ICON_BUG = "mdi:bug"
ICON_CHECK_CIRCLE_OUTLINE = "mdi:check-circle-outline"
ICON_CHEMICAL_WEAPON = "mdi:chemical-weapon"

View file

@ -840,6 +840,25 @@ sensor:
is_cs_package: true
integration_time: 402ms
gain: 16x
- platform: tsl2591
id: this_little_light_of_mine
address: 0x29
update_interval: 15s
integration_time: 600ms
gain: high
visible:
name: "tsl2591 visible"
id: tsl2591_vis
unit_of_measurement: 'pH'
infrared:
name: "tsl2591 infrared"
id: tsl2591_ir
full_spectrum:
name: "tsl2591 full_spectrum"
id: tsl2591_fs
calculated_lux:
name: "tsl2591 calculated_lux"
id: tsl2591_cl
- platform: ultrasonic
trigger_pin: GPIO25
echo_pin: