mirror of
https://github.com/esphome/esphome.git
synced 2024-12-05 15:28:47 +01:00
LTR-501, LTR-301, LTR-558 Series of Lite-On Light (ALS) and Proximity(PS) sensors (#6262)
Co-authored-by: root <root@LAOX1> Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
parent
7a93dde5d4
commit
c90dcfc0ca
@ -227,6 +227,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
esphome/components/lock/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @latonita @sjtrny
|
||||
esphome/components/ltr501/* @latonita
|
||||
esphome/components/ltr_als_ps/* @latonita
|
||||
esphome/components/lvgl/* @clydebarrow
|
||||
esphome/components/m5stack_8angle/* @rnauber
|
||||
|
1
esphome/components/ltr501/__init__.py
Normal file
1
esphome/components/ltr501/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@latonita"]
|
542
esphome/components/ltr501/ltr501.cpp
Normal file
542
esphome/components/ltr501/ltr501.cpp
Normal file
@ -0,0 +1,542 @@
|
||||
#include "ltr501.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
using esphome::i2c::ErrorCode;
|
||||
|
||||
namespace esphome {
|
||||
namespace ltr501 {
|
||||
|
||||
static const char *const TAG = "ltr501";
|
||||
|
||||
static const uint8_t MAX_TRIES = 5;
|
||||
static const uint8_t MAX_SENSITIVITY_ADJUSTMENTS = 10;
|
||||
|
||||
struct GainTimePair {
|
||||
AlsGain501 gain;
|
||||
IntegrationTime501 time;
|
||||
};
|
||||
|
||||
bool operator==(const GainTimePair &lhs, const GainTimePair &rhs) {
|
||||
return lhs.gain == rhs.gain && lhs.time == rhs.time;
|
||||
}
|
||||
|
||||
bool operator!=(const GainTimePair &lhs, const GainTimePair &rhs) {
|
||||
return !(lhs.gain == rhs.gain && lhs.time == rhs.time);
|
||||
}
|
||||
|
||||
template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {
|
||||
size_t i = 0;
|
||||
size_t idx = -1;
|
||||
while (idx == -1 && i < size) {
|
||||
if (array[i] == val) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (idx == -1 || i + 1 >= size)
|
||||
return val;
|
||||
return array[i + 1];
|
||||
}
|
||||
|
||||
template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) {
|
||||
size_t i = size - 1;
|
||||
size_t idx = -1;
|
||||
while (idx == -1 && i > 0) {
|
||||
if (array[i] == val) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
if (idx == -1 || i == 0)
|
||||
return val;
|
||||
return array[i - 1];
|
||||
}
|
||||
|
||||
static uint16_t get_itime_ms(IntegrationTime501 time) {
|
||||
static const uint16_t ALS_INT_TIME[4] = {100, 50, 200, 400};
|
||||
return ALS_INT_TIME[time & 0b11];
|
||||
}
|
||||
|
||||
static uint16_t get_meas_time_ms(MeasurementRepeatRate rate) {
|
||||
static const uint16_t ALS_MEAS_RATE[8] = {50, 100, 200, 500, 1000, 2000, 2000, 2000};
|
||||
return ALS_MEAS_RATE[rate & 0b111];
|
||||
}
|
||||
|
||||
static float get_gain_coeff(AlsGain501 gain) { return gain == AlsGain501::GAIN_1 ? 1.0f : 150.0f; }
|
||||
|
||||
static float get_ps_gain_coeff(PsGain501 gain) {
|
||||
static const float PS_GAIN[4] = {1, 4, 8, 16};
|
||||
return PS_GAIN[gain & 0b11];
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up LTR-501/301/558");
|
||||
// As per datasheet we need to wait at least 100ms after power on to get ALS chip responsive
|
||||
this->set_timeout(100, [this]() { this->state_ = State::DELAYED_SETUP; });
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::dump_config() {
|
||||
auto get_device_type = [](LtrType typ) {
|
||||
switch (typ) {
|
||||
case LtrType::LTR_TYPE_ALS_ONLY:
|
||||
return "ALS only";
|
||||
case LtrType::LTR_TYPE_PS_ONLY:
|
||||
return "PS only";
|
||||
case LtrType::LTR_TYPE_ALS_AND_PS:
|
||||
return "Als + PS";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
};
|
||||
|
||||
LOG_I2C_DEVICE(this);
|
||||
ESP_LOGCONFIG(TAG, " Device type: %s", get_device_type(this->ltr_type_));
|
||||
ESP_LOGCONFIG(TAG, " Automatic mode: %s", ONOFF(this->automatic_mode_enabled_));
|
||||
ESP_LOGCONFIG(TAG, " Gain: %.0fx", get_gain_coeff(this->gain_));
|
||||
ESP_LOGCONFIG(TAG, " Integration time: %d ms", get_itime_ms(this->integration_time_));
|
||||
ESP_LOGCONFIG(TAG, " Measurement repeat rate: %d ms", get_meas_time_ms(this->repeat_rate_));
|
||||
ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_);
|
||||
ESP_LOGCONFIG(TAG, " Proximity gain: %.0fx", get_ps_gain_coeff(this->ps_gain_));
|
||||
ESP_LOGCONFIG(TAG, " Proximity cooldown time: %d s", this->ps_cooldown_time_s_);
|
||||
ESP_LOGCONFIG(TAG, " Proximity high threshold: %d", this->ps_threshold_high_);
|
||||
ESP_LOGCONFIG(TAG, " Proximity low threshold: %d", this->ps_threshold_low_);
|
||||
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "ALS calculated lux", this->ambient_light_sensor_);
|
||||
LOG_SENSOR(" ", "CH1 Infrared counts", this->infrared_counts_sensor_);
|
||||
LOG_SENSOR(" ", "CH0 Visible+IR counts", this->full_spectrum_counts_sensor_);
|
||||
LOG_SENSOR(" ", "Actual gain", this->actual_gain_sensor_);
|
||||
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with I2C LTR-501/301/558 failed!");
|
||||
}
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::update() {
|
||||
if (!this->is_als_()) {
|
||||
ESP_LOGW(TAG, "Update. ALS data not available. Change configuration to ALS or ALS_PS.");
|
||||
return;
|
||||
}
|
||||
if (this->is_ready() && this->is_als_() && this->state_ == State::IDLE) {
|
||||
ESP_LOGV(TAG, "Update. Initiating new ALS data collection.");
|
||||
|
||||
this->state_ = this->automatic_mode_enabled_ ? State::COLLECTING_DATA_AUTO : State::WAITING_FOR_DATA;
|
||||
|
||||
this->als_readings_.ch0 = 0;
|
||||
this->als_readings_.ch1 = 0;
|
||||
this->als_readings_.gain = this->gain_;
|
||||
this->als_readings_.integration_time = this->integration_time_;
|
||||
this->als_readings_.lux = 0;
|
||||
this->als_readings_.number_of_adjustments = 0;
|
||||
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Update. Component not ready yet.");
|
||||
}
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::loop() {
|
||||
ErrorCode err = i2c::ERROR_OK;
|
||||
static uint8_t tries{0};
|
||||
|
||||
switch (this->state_) {
|
||||
case State::DELAYED_SETUP:
|
||||
err = this->write(nullptr, 0);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "i2c connection failed");
|
||||
this->mark_failed();
|
||||
}
|
||||
this->configure_reset_();
|
||||
if (this->is_als_()) {
|
||||
this->configure_als_();
|
||||
this->configure_integration_time_(this->integration_time_);
|
||||
}
|
||||
if (this->is_ps_()) {
|
||||
this->configure_ps_();
|
||||
}
|
||||
|
||||
this->state_ = State::IDLE;
|
||||
break;
|
||||
|
||||
case State::IDLE:
|
||||
if (this->is_ps_()) {
|
||||
this->check_and_trigger_ps_();
|
||||
}
|
||||
break;
|
||||
|
||||
case State::WAITING_FOR_DATA:
|
||||
if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) {
|
||||
tries = 0;
|
||||
ESP_LOGV(TAG, "Reading sensor data assuming gain = %.0fx, time = %d ms",
|
||||
get_gain_coeff(this->als_readings_.gain), get_itime_ms(this->als_readings_.integration_time));
|
||||
this->read_sensor_data_(this->als_readings_);
|
||||
this->apply_lux_calculation_(this->als_readings_);
|
||||
this->state_ = State::DATA_COLLECTED;
|
||||
} else if (tries >= MAX_TRIES) {
|
||||
ESP_LOGW(TAG, "Can't get data after several tries. Aborting.");
|
||||
tries = 0;
|
||||
this->status_set_warning();
|
||||
this->state_ = State::IDLE;
|
||||
return;
|
||||
} else {
|
||||
tries++;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::COLLECTING_DATA_AUTO:
|
||||
case State::DATA_COLLECTED:
|
||||
// first measurement in auto mode (COLLECTING_DATA_AUTO state) require device reconfiguration
|
||||
if (this->state_ == State::COLLECTING_DATA_AUTO || this->are_adjustments_required_(this->als_readings_)) {
|
||||
this->state_ = State::ADJUSTMENT_IN_PROGRESS;
|
||||
ESP_LOGD(TAG, "Reconfiguring sensitivity: gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain),
|
||||
get_itime_ms(this->als_readings_.integration_time));
|
||||
this->configure_integration_time_(this->als_readings_.integration_time);
|
||||
this->configure_gain_(this->als_readings_.gain);
|
||||
// if sensitivity adjustment needed - need to wait for first data samples after setting new parameters
|
||||
this->set_timeout(2 * get_meas_time_ms(this->repeat_rate_),
|
||||
[this]() { this->state_ = State::WAITING_FOR_DATA; });
|
||||
} else {
|
||||
this->state_ = State::READY_TO_PUBLISH;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::ADJUSTMENT_IN_PROGRESS:
|
||||
// nothing to be done, just waiting for the timeout
|
||||
break;
|
||||
|
||||
case State::READY_TO_PUBLISH:
|
||||
this->publish_data_part_1_(this->als_readings_);
|
||||
this->state_ = State::KEEP_PUBLISHING;
|
||||
break;
|
||||
|
||||
case State::KEEP_PUBLISHING:
|
||||
this->publish_data_part_2_(this->als_readings_);
|
||||
this->status_clear_warning();
|
||||
this->state_ = State::IDLE;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::check_and_trigger_ps_() {
|
||||
static uint32_t last_high_trigger_time{0};
|
||||
static uint32_t last_low_trigger_time{0};
|
||||
uint16_t ps_data = this->read_ps_data_();
|
||||
uint32_t now = millis();
|
||||
|
||||
if (ps_data != this->ps_readings_) {
|
||||
this->ps_readings_ = ps_data;
|
||||
// Higher values - object is closer to sensor
|
||||
if (ps_data > this->ps_threshold_high_ && now - last_high_trigger_time >= this->ps_cooldown_time_s_ * 1000) {
|
||||
last_high_trigger_time = now;
|
||||
ESP_LOGD(TAG, "Proximity high threshold triggered. Value = %d, Trigger level = %d", ps_data,
|
||||
this->ps_threshold_high_);
|
||||
this->on_ps_high_trigger_callback_.call();
|
||||
} else if (ps_data < this->ps_threshold_low_ && now - last_low_trigger_time >= this->ps_cooldown_time_s_ * 1000) {
|
||||
last_low_trigger_time = now;
|
||||
ESP_LOGD(TAG, "Proximity low threshold triggered. Value = %d, Trigger level = %d", ps_data,
|
||||
this->ps_threshold_low_);
|
||||
this->on_ps_low_trigger_callback_.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LTRAlsPs501Component::check_part_number_() {
|
||||
uint8_t manuf_id = this->reg((uint8_t) CommandRegisters::MANUFAC_ID).get();
|
||||
if (manuf_id != 0x05) { // 0x05 is Lite-On Semiconductor Corp. ID
|
||||
ESP_LOGW(TAG, "Unknown manufacturer ID: 0x%02X", manuf_id);
|
||||
this->mark_failed();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Things getting not really funny here, we can't identify device type by part number ID
|
||||
// ======================== ========= ===== =================
|
||||
// Device Part ID Rev Capabilities
|
||||
// ======================== ========= ===== =================
|
||||
// ltr-558als 0x08 0 als + ps
|
||||
// ltr-501als 0x08 0 als + ps
|
||||
// ltr-301als - 0x08 0 als only
|
||||
|
||||
PartIdRegister part_id{0};
|
||||
part_id.raw = this->reg((uint8_t) CommandRegisters::PART_ID).get();
|
||||
if (part_id.part_number_id != 0x08) {
|
||||
ESP_LOGW(TAG, "Unknown part number ID: 0x%02X. LTR-501/301 shall have 0x08. It might not work properly.",
|
||||
part_id.part_number_id);
|
||||
this->status_set_warning();
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::configure_reset_() {
|
||||
ESP_LOGV(TAG, "Resetting");
|
||||
|
||||
AlsControlRegister501 als_ctrl{0};
|
||||
als_ctrl.sw_reset = true;
|
||||
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
|
||||
delay(2);
|
||||
|
||||
uint8_t tries = MAX_TRIES;
|
||||
do {
|
||||
ESP_LOGV(TAG, "Waiting chip to reset");
|
||||
delay(2);
|
||||
als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
|
||||
} while (als_ctrl.sw_reset && tries--); // while sw reset bit is on - keep waiting
|
||||
|
||||
if (als_ctrl.sw_reset) {
|
||||
ESP_LOGW(TAG, "Reset failed");
|
||||
}
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::configure_als_() {
|
||||
AlsControlRegister501 als_ctrl{0};
|
||||
als_ctrl.sw_reset = false;
|
||||
als_ctrl.als_mode_active = true;
|
||||
als_ctrl.gain = this->gain_;
|
||||
|
||||
ESP_LOGV(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw);
|
||||
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
|
||||
delay(5);
|
||||
|
||||
uint8_t tries = MAX_TRIES;
|
||||
do {
|
||||
ESP_LOGV(TAG, "Waiting for ALS device to become active...");
|
||||
delay(2);
|
||||
als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
|
||||
} while (!als_ctrl.als_mode_active && tries--); // while active mode is not set - keep waiting
|
||||
|
||||
if (!als_ctrl.als_mode_active) {
|
||||
ESP_LOGW(TAG, "Failed to activate ALS device");
|
||||
}
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::configure_ps_() {
|
||||
PsMeasurementRateRegister ps_meas{0};
|
||||
ps_meas.ps_measurement_rate = PsMeasurementRate::PS_MEAS_RATE_50MS;
|
||||
this->reg((uint8_t) CommandRegisters::PS_MEAS_RATE) = ps_meas.raw;
|
||||
|
||||
PsControlRegister501 ps_ctrl{0};
|
||||
ps_ctrl.ps_mode_active = true;
|
||||
ps_ctrl.ps_mode_xxx = true;
|
||||
this->reg((uint8_t) CommandRegisters::PS_CONTR) = ps_ctrl.raw;
|
||||
}
|
||||
|
||||
uint16_t LTRAlsPs501Component::read_ps_data_() {
|
||||
AlsPsStatusRegister als_status{0};
|
||||
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
|
||||
if (!als_status.ps_new_data) {
|
||||
return this->ps_readings_;
|
||||
}
|
||||
|
||||
uint8_t ps_low = this->reg((uint8_t) CommandRegisters::PS_DATA_0).get();
|
||||
PsData1Register ps_high;
|
||||
ps_high.raw = this->reg((uint8_t) CommandRegisters::PS_DATA_1).get();
|
||||
|
||||
uint16_t val = encode_uint16(ps_high.ps_data_high, ps_low);
|
||||
return val;
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::configure_gain_(AlsGain501 gain) {
|
||||
AlsControlRegister501 als_ctrl{0};
|
||||
als_ctrl.als_mode_active = true;
|
||||
als_ctrl.gain = gain;
|
||||
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
|
||||
delay(2);
|
||||
|
||||
AlsControlRegister501 read_als_ctrl{0};
|
||||
read_als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
|
||||
if (read_als_ctrl.gain != gain) {
|
||||
ESP_LOGW(TAG, "Failed to set gain. We will try one more time.");
|
||||
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
|
||||
delay(2);
|
||||
}
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::configure_integration_time_(IntegrationTime501 time) {
|
||||
MeasurementRateRegister501 meas{0};
|
||||
meas.measurement_repeat_rate = this->repeat_rate_;
|
||||
meas.integration_time = time;
|
||||
this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw;
|
||||
delay(2);
|
||||
|
||||
MeasurementRateRegister501 read_meas{0};
|
||||
read_meas.raw = this->reg((uint8_t) CommandRegisters::MEAS_RATE).get();
|
||||
if (read_meas.integration_time != time) {
|
||||
ESP_LOGW(TAG, "Failed to set integration time. We will try one more time.");
|
||||
this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw;
|
||||
delay(2);
|
||||
}
|
||||
}
|
||||
|
||||
DataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) {
|
||||
AlsPsStatusRegister als_status{0};
|
||||
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
|
||||
if (!als_status.als_new_data)
|
||||
return DataAvail::NO_DATA;
|
||||
ESP_LOGV(TAG, "Data ready, reported gain is %.0fx", get_gain_coeff(als_status.gain));
|
||||
if (data.gain != als_status.gain) {
|
||||
ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain));
|
||||
return DataAvail::BAD_DATA;
|
||||
}
|
||||
data.gain = als_status.gain;
|
||||
return DataAvail::DATA_OK;
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::read_sensor_data_(AlsReadings &data) {
|
||||
data.ch1 = 0;
|
||||
data.ch0 = 0;
|
||||
uint8_t ch1_0 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH1_0).get();
|
||||
uint8_t ch1_1 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH1_1).get();
|
||||
uint8_t ch0_0 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH0_0).get();
|
||||
uint8_t ch0_1 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH0_1).get();
|
||||
data.ch1 = encode_uint16(ch1_1, ch1_0);
|
||||
data.ch0 = encode_uint16(ch0_1, ch0_0);
|
||||
|
||||
ESP_LOGD(TAG, "Got sensor data: CH1 = %d, CH0 = %d", data.ch1, data.ch0);
|
||||
}
|
||||
|
||||
bool LTRAlsPs501Component::are_adjustments_required_(AlsReadings &data) {
|
||||
if (!this->automatic_mode_enabled_)
|
||||
return false;
|
||||
|
||||
// sometimes sensors fail to change sensitivity. this prevents us from infinite loop
|
||||
if (data.number_of_adjustments++ > MAX_SENSITIVITY_ADJUSTMENTS) {
|
||||
ESP_LOGW(TAG, "Too many sensitivity adjustments done. Something wrong with the sensor. Stopping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Adjusting sensitivity, run #%d", data.number_of_adjustments);
|
||||
|
||||
// available combinations of gain and integration times:
|
||||
static const GainTimePair GAIN_TIME_PAIRS[] = {
|
||||
{AlsGain501::GAIN_1, INTEGRATION_TIME_50MS}, {AlsGain501::GAIN_1, INTEGRATION_TIME_100MS},
|
||||
{AlsGain501::GAIN_150, INTEGRATION_TIME_100MS}, {AlsGain501::GAIN_150, INTEGRATION_TIME_200MS},
|
||||
{AlsGain501::GAIN_150, INTEGRATION_TIME_400MS},
|
||||
};
|
||||
|
||||
GainTimePair current_pair = {data.gain, data.integration_time};
|
||||
|
||||
// Here comes funky business with this sensor. it has no internal error checking mechanism
|
||||
// as in later versions (LTR-303/329/559/..) and sensor gets overwhelmed when saturated
|
||||
// and readings are strange. We only check high sensitivity mode for now.
|
||||
// Nothing is documented and it is a result of real-world testing.
|
||||
if (data.gain == AlsGain501::GAIN_150) {
|
||||
// when sensor is saturated it returns various crazy numbers
|
||||
// CH1 = 1, CH0 = 0
|
||||
if (data.ch1 == 1 && data.ch0 == 0) {
|
||||
ESP_LOGV(TAG, "Looks like sensor got saturated (?) CH1 = 1, CH0 = 0, Gain 150x");
|
||||
// fake saturation
|
||||
data.ch0 = 0xffff;
|
||||
data.ch1 = 0xffff;
|
||||
} else if (data.ch1 == 65535 && data.ch0 == 0) {
|
||||
ESP_LOGV(TAG, "Looks like sensor got saturated (?) CH1 = 65535, CH0 = 0, Gain 150x");
|
||||
data.ch0 = 0xffff;
|
||||
} else if (data.ch1 > 1000 && data.ch0 == 0) {
|
||||
ESP_LOGV(TAG, "Looks like sensor got saturated (?) CH1 = %d, CH0 = 0, Gain 150x", data.ch1);
|
||||
data.ch0 = 0xffff;
|
||||
}
|
||||
}
|
||||
|
||||
static const uint16_t LOW_INTENSITY_THRESHOLD_1 = 100;
|
||||
static const uint16_t LOW_INTENSITY_THRESHOLD_200 = 2000;
|
||||
static const uint16_t HIGH_INTENSITY_THRESHOLD = 25000;
|
||||
|
||||
if (data.ch0 <= (data.gain == AlsGain501::GAIN_1 ? LOW_INTENSITY_THRESHOLD_1 : LOW_INTENSITY_THRESHOLD_200) ||
|
||||
(data.gain == AlsGain501::GAIN_1 && data.lux < 320)) {
|
||||
GainTimePair next_pair = get_next(GAIN_TIME_PAIRS, current_pair);
|
||||
if (next_pair != current_pair) {
|
||||
data.gain = next_pair.gain;
|
||||
data.integration_time = next_pair.time;
|
||||
ESP_LOGV(TAG, "Low illuminance. Increasing sensitivity.");
|
||||
return true;
|
||||
}
|
||||
|
||||
} else if (data.ch0 >= HIGH_INTENSITY_THRESHOLD || data.ch1 >= HIGH_INTENSITY_THRESHOLD) {
|
||||
GainTimePair prev_pair = get_prev(GAIN_TIME_PAIRS, current_pair);
|
||||
if (prev_pair != current_pair) {
|
||||
data.gain = prev_pair.gain;
|
||||
data.integration_time = prev_pair.time;
|
||||
ESP_LOGV(TAG, "High illuminance. Decreasing sensitivity.");
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Illuminance is good enough.");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Can't adjust sensitivity anymore.");
|
||||
return false;
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::apply_lux_calculation_(AlsReadings &data) {
|
||||
if ((data.ch0 == 0xFFFF) || (data.ch1 == 0xFFFF)) {
|
||||
ESP_LOGW(TAG, "Sensors got saturated");
|
||||
data.lux = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((data.ch0 == 0x0000) && (data.ch1 == 0x0000)) {
|
||||
ESP_LOGW(TAG, "Sensors blacked out");
|
||||
data.lux = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
float ch0 = data.ch0;
|
||||
float ch1 = data.ch1;
|
||||
float ratio = ch1 / (ch0 + ch1);
|
||||
float als_gain = get_gain_coeff(data.gain);
|
||||
float als_time = ((float) get_itime_ms(data.integration_time)) / 100.0f;
|
||||
float inv_pfactor = this->glass_attenuation_factor_;
|
||||
float lux = 0.0f;
|
||||
|
||||
// method from
|
||||
// https://github.com/fards/Ainol_fire_kernel/blob/83832cf8a3082fd8e963230f4b1984479d1f1a84/customer/drivers/lightsensor/ltr501als.c#L295
|
||||
|
||||
if (ratio < 0.45) {
|
||||
lux = 1.7743 * ch0 + 1.1059 * ch1;
|
||||
} else if (ratio < 0.64) {
|
||||
lux = 3.7725 * ch0 - 1.3363 * ch1;
|
||||
} else if (ratio < 0.85) {
|
||||
lux = 1.6903 * ch0 - 0.1693 * ch1;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Impossible ch1/(ch0 + ch1) ratio");
|
||||
lux = 0.0f;
|
||||
}
|
||||
|
||||
lux = inv_pfactor * lux / als_gain / als_time;
|
||||
data.lux = lux;
|
||||
|
||||
ESP_LOGD(TAG, "Lux calculation: ratio %.3f, gain %.0fx, int time %.1f, inv_pfactor %.3f, lux %.3f", ratio, als_gain,
|
||||
als_time, inv_pfactor, lux);
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::publish_data_part_1_(AlsReadings &data) {
|
||||
if (this->proximity_counts_sensor_ != nullptr) {
|
||||
this->proximity_counts_sensor_->publish_state(this->ps_readings_);
|
||||
}
|
||||
if (this->ambient_light_sensor_ != nullptr) {
|
||||
this->ambient_light_sensor_->publish_state(data.lux);
|
||||
}
|
||||
if (this->infrared_counts_sensor_ != nullptr) {
|
||||
this->infrared_counts_sensor_->publish_state(data.ch1);
|
||||
}
|
||||
if (this->full_spectrum_counts_sensor_ != nullptr) {
|
||||
this->full_spectrum_counts_sensor_->publish_state(data.ch0);
|
||||
}
|
||||
}
|
||||
|
||||
void LTRAlsPs501Component::publish_data_part_2_(AlsReadings &data) {
|
||||
if (this->actual_gain_sensor_ != nullptr) {
|
||||
this->actual_gain_sensor_->publish_state(get_gain_coeff(data.gain));
|
||||
}
|
||||
if (this->actual_integration_time_sensor_ != nullptr) {
|
||||
this->actual_integration_time_sensor_->publish_state(get_itime_ms(data.integration_time));
|
||||
}
|
||||
}
|
||||
} // namespace ltr501
|
||||
} // namespace esphome
|
184
esphome/components/ltr501/ltr501.h
Normal file
184
esphome/components/ltr501/ltr501.h
Normal file
@ -0,0 +1,184 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
#include "ltr_definitions_501.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ltr501 {
|
||||
|
||||
enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK };
|
||||
|
||||
enum LtrType : uint8_t {
|
||||
LTR_TYPE_UNKNOWN = 0,
|
||||
LTR_TYPE_ALS_ONLY = 1,
|
||||
LTR_TYPE_PS_ONLY = 2,
|
||||
LTR_TYPE_ALS_AND_PS = 3,
|
||||
};
|
||||
|
||||
class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
//
|
||||
// EspHome framework functions
|
||||
//
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
|
||||
// Configuration setters : General
|
||||
//
|
||||
void set_ltr_type(LtrType type) { this->ltr_type_ = type; }
|
||||
|
||||
// Configuration setters : ALS
|
||||
//
|
||||
void set_als_auto_mode(bool enable) { this->automatic_mode_enabled_ = enable; }
|
||||
void set_als_gain(AlsGain501 gain) { this->gain_ = gain; }
|
||||
void set_als_integration_time(IntegrationTime501 time) { this->integration_time_ = time; }
|
||||
void set_als_meas_repeat_rate(MeasurementRepeatRate rate) { this->repeat_rate_ = rate; }
|
||||
void set_als_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; }
|
||||
|
||||
// Configuration setters : PS
|
||||
//
|
||||
void set_ps_high_threshold(uint16_t threshold) { this->ps_threshold_high_ = threshold; }
|
||||
void set_ps_low_threshold(uint16_t threshold) { this->ps_threshold_low_ = threshold; }
|
||||
void set_ps_cooldown_time_s(uint16_t time) { this->ps_cooldown_time_s_ = time; }
|
||||
void set_ps_gain(PsGain501 gain) { this->ps_gain_ = gain; }
|
||||
|
||||
// Sensors setters
|
||||
//
|
||||
void set_ambient_light_sensor(sensor::Sensor *sensor) { this->ambient_light_sensor_ = sensor; }
|
||||
void set_full_spectrum_counts_sensor(sensor::Sensor *sensor) { this->full_spectrum_counts_sensor_ = sensor; }
|
||||
void set_infrared_counts_sensor(sensor::Sensor *sensor) { this->infrared_counts_sensor_ = sensor; }
|
||||
void set_actual_gain_sensor(sensor::Sensor *sensor) { this->actual_gain_sensor_ = sensor; }
|
||||
void set_actual_integration_time_sensor(sensor::Sensor *sensor) { this->actual_integration_time_sensor_ = sensor; }
|
||||
void set_proximity_counts_sensor(sensor::Sensor *sensor) { this->proximity_counts_sensor_ = sensor; }
|
||||
|
||||
protected:
|
||||
//
|
||||
// Internal state machine, used to split all the actions into
|
||||
// small steps in loop() to make sure we are not blocking execution
|
||||
//
|
||||
enum class State : uint8_t {
|
||||
NOT_INITIALIZED,
|
||||
DELAYED_SETUP,
|
||||
IDLE,
|
||||
WAITING_FOR_DATA,
|
||||
COLLECTING_DATA_AUTO,
|
||||
DATA_COLLECTED,
|
||||
ADJUSTMENT_IN_PROGRESS,
|
||||
READY_TO_PUBLISH,
|
||||
KEEP_PUBLISHING
|
||||
} state_{State::NOT_INITIALIZED};
|
||||
|
||||
LtrType ltr_type_{LtrType::LTR_TYPE_ALS_ONLY};
|
||||
|
||||
//
|
||||
// Current measurements data
|
||||
//
|
||||
struct AlsReadings {
|
||||
uint16_t ch0{0};
|
||||
uint16_t ch1{0};
|
||||
AlsGain501 gain{AlsGain501::GAIN_1};
|
||||
IntegrationTime501 integration_time{IntegrationTime501::INTEGRATION_TIME_100MS};
|
||||
float lux{0.0f};
|
||||
uint8_t number_of_adjustments{0};
|
||||
} als_readings_;
|
||||
uint16_t ps_readings_{0xfffe};
|
||||
|
||||
inline bool is_als_() const {
|
||||
return this->ltr_type_ == LtrType::LTR_TYPE_ALS_ONLY || this->ltr_type_ == LtrType::LTR_TYPE_ALS_AND_PS;
|
||||
}
|
||||
inline bool is_ps_() const {
|
||||
return this->ltr_type_ == LtrType::LTR_TYPE_PS_ONLY || this->ltr_type_ == LtrType::LTR_TYPE_ALS_AND_PS;
|
||||
}
|
||||
|
||||
//
|
||||
// Device interaction and data manipulation
|
||||
//
|
||||
bool check_part_number_();
|
||||
|
||||
void configure_reset_();
|
||||
void configure_als_();
|
||||
void configure_integration_time_(IntegrationTime501 time);
|
||||
void configure_gain_(AlsGain501 gain);
|
||||
DataAvail is_als_data_ready_(AlsReadings &data);
|
||||
void read_sensor_data_(AlsReadings &data);
|
||||
bool are_adjustments_required_(AlsReadings &data);
|
||||
void apply_lux_calculation_(AlsReadings &data);
|
||||
void publish_data_part_1_(AlsReadings &data);
|
||||
void publish_data_part_2_(AlsReadings &data);
|
||||
|
||||
void configure_ps_();
|
||||
uint16_t read_ps_data_();
|
||||
void check_and_trigger_ps_();
|
||||
|
||||
//
|
||||
// Component configuration
|
||||
//
|
||||
bool automatic_mode_enabled_{false};
|
||||
AlsGain501 gain_{AlsGain501::GAIN_1};
|
||||
IntegrationTime501 integration_time_{IntegrationTime501::INTEGRATION_TIME_100MS};
|
||||
MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS};
|
||||
float glass_attenuation_factor_{1.0};
|
||||
|
||||
uint16_t ps_cooldown_time_s_{5};
|
||||
PsGain501 ps_gain_{PsGain501::PS_GAIN_1};
|
||||
uint16_t ps_threshold_high_{0xffff};
|
||||
uint16_t ps_threshold_low_{0x0000};
|
||||
|
||||
//
|
||||
// Sensors for publishing data
|
||||
//
|
||||
sensor::Sensor *infrared_counts_sensor_{nullptr}; // direct reading CH1, infrared only
|
||||
sensor::Sensor *full_spectrum_counts_sensor_{nullptr}; // direct reading CH0, infrared + visible light
|
||||
sensor::Sensor *ambient_light_sensor_{nullptr}; // calculated lux
|
||||
sensor::Sensor *actual_gain_sensor_{nullptr}; // actual gain of reading
|
||||
sensor::Sensor *actual_integration_time_sensor_{nullptr}; // actual integration time
|
||||
sensor::Sensor *proximity_counts_sensor_{nullptr}; // proximity sensor
|
||||
|
||||
bool is_any_als_sensor_enabled_() const {
|
||||
return this->ambient_light_sensor_ != nullptr || this->full_spectrum_counts_sensor_ != nullptr ||
|
||||
this->infrared_counts_sensor_ != nullptr || this->actual_gain_sensor_ != nullptr ||
|
||||
this->actual_integration_time_sensor_ != nullptr;
|
||||
}
|
||||
bool is_any_ps_sensor_enabled_() const { return this->proximity_counts_sensor_ != nullptr; }
|
||||
|
||||
//
|
||||
// Trigger section for the automations
|
||||
//
|
||||
friend class LTRPsHighTrigger;
|
||||
friend class LTRPsLowTrigger;
|
||||
|
||||
CallbackManager<void()> on_ps_high_trigger_callback_;
|
||||
CallbackManager<void()> on_ps_low_trigger_callback_;
|
||||
|
||||
void add_on_ps_high_trigger_callback_(std::function<void()> callback) {
|
||||
this->on_ps_high_trigger_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
void add_on_ps_low_trigger_callback_(std::function<void()> callback) {
|
||||
this->on_ps_low_trigger_callback_.add(std::move(callback));
|
||||
}
|
||||
};
|
||||
|
||||
class LTRPsHighTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit LTRPsHighTrigger(LTRAlsPs501Component *parent) {
|
||||
parent->add_on_ps_high_trigger_callback_([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
class LTRPsLowTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit LTRPsLowTrigger(LTRAlsPs501Component *parent) {
|
||||
parent->add_on_ps_low_trigger_callback_([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
} // namespace ltr501
|
||||
} // namespace esphome
|
260
esphome/components/ltr501/ltr_definitions_501.h
Normal file
260
esphome/components/ltr501/ltr_definitions_501.h
Normal file
@ -0,0 +1,260 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
namespace ltr501 {
|
||||
|
||||
enum class CommandRegisters : uint8_t {
|
||||
ALS_CONTR = 0x80, // ALS operation mode control and SW reset
|
||||
PS_CONTR = 0x81, // PS operation mode control
|
||||
PS_LED = 0x82, // PS LED pulse frequency control
|
||||
PS_N_PULSES = 0x83, // PS number of pulses control
|
||||
PS_MEAS_RATE = 0x84, // PS measurement rate in active mode
|
||||
MEAS_RATE = 0x85, // ALS measurement rate in active mode
|
||||
PART_ID = 0x86, // Part Number ID and Revision ID
|
||||
MANUFAC_ID = 0x87, // Manufacturer ID
|
||||
ALS_DATA_CH1_0 = 0x88, // ALS measurement CH1 data, lower byte - infrared only
|
||||
ALS_DATA_CH1_1 = 0x89, // ALS measurement CH1 data, upper byte - infrared only
|
||||
ALS_DATA_CH0_0 = 0x8A, // ALS measurement CH0 data, lower byte - visible + infrared
|
||||
ALS_DATA_CH0_1 = 0x8B, // ALS measurement CH0 data, upper byte - visible + infrared
|
||||
ALS_PS_STATUS = 0x8C, // ALS PS new data status
|
||||
PS_DATA_0 = 0x8D, // PS measurement data, lower byte
|
||||
PS_DATA_1 = 0x8E, // PS measurement data, upper byte
|
||||
ALS_PS_INTERRUPT = 0x8F, // Interrupt status
|
||||
PS_THRES_UP_0 = 0x90, // PS interrupt upper threshold, lower byte
|
||||
PS_THRES_UP_1 = 0x91, // PS interrupt upper threshold, upper byte
|
||||
PS_THRES_LOW_0 = 0x92, // PS interrupt lower threshold, lower byte
|
||||
PS_THRES_LOW_1 = 0x93, // PS interrupt lower threshold, upper byte
|
||||
PS_OFFSET_1 = 0x94, // PS offset, upper byte
|
||||
PS_OFFSET_0 = 0x95, // PS offset, lower byte
|
||||
// 0x96 - reserved
|
||||
ALS_THRES_UP_0 = 0x97, // ALS interrupt upper threshold, lower byte
|
||||
ALS_THRES_UP_1 = 0x98, // ALS interrupt upper threshold, upper byte
|
||||
ALS_THRES_LOW_0 = 0x99, // ALS interrupt lower threshold, lower byte
|
||||
ALS_THRES_LOW_1 = 0x9A, // ALS interrupt lower threshold, upper byte
|
||||
// 0x9B - reserved
|
||||
// 0x9C - reserved
|
||||
// 0x9D - reserved
|
||||
INTERRUPT_PERSIST = 0x9E // Interrupt persistence filter
|
||||
};
|
||||
|
||||
// ALS Sensor gain levels
|
||||
enum AlsGain501 : uint8_t {
|
||||
GAIN_1 = 0, // GAIN_RANGE_2 // default
|
||||
GAIN_150 = 1, // GAIN_RANGE_1
|
||||
};
|
||||
static const uint8_t GAINS_COUNT = 2;
|
||||
|
||||
// ALS Sensor integration times
|
||||
enum IntegrationTime501 : uint8_t {
|
||||
INTEGRATION_TIME_100MS = 0, // default
|
||||
INTEGRATION_TIME_50MS = 1, // only in Dynamic GAIN_RANGE_2
|
||||
INTEGRATION_TIME_200MS = 2, // only in Dynamic GAIN_RANGE_1
|
||||
INTEGRATION_TIME_400MS = 3, // only in Dynamic GAIN_RANGE_1
|
||||
};
|
||||
static const uint8_t TIMES_COUNT = 4;
|
||||
|
||||
// ALS Sensor measurement repeat rate
|
||||
enum MeasurementRepeatRate {
|
||||
REPEAT_RATE_50MS = 0,
|
||||
REPEAT_RATE_100MS = 1,
|
||||
REPEAT_RATE_200MS = 2,
|
||||
REPEAT_RATE_500MS = 3, // default
|
||||
REPEAT_RATE_1000MS = 4,
|
||||
REPEAT_RATE_2000MS = 5
|
||||
};
|
||||
|
||||
// PS Sensor gain levels
|
||||
enum PsGain501 : uint8_t {
|
||||
PS_GAIN_1 = 0, // default
|
||||
PS_GAIN_4 = 1,
|
||||
PS_GAIN_8 = 2,
|
||||
PS_GAIN_16 = 3,
|
||||
};
|
||||
|
||||
// LED Pulse Modulation Frequency
|
||||
enum PsLedFreq : uint8_t {
|
||||
PS_LED_FREQ_30KHZ = 0,
|
||||
PS_LED_FREQ_40KHZ = 1,
|
||||
PS_LED_FREQ_50KHZ = 2,
|
||||
PS_LED_FREQ_60KHZ = 3, // default
|
||||
PS_LED_FREQ_70KHZ = 4,
|
||||
PS_LED_FREQ_80KHZ = 5,
|
||||
PS_LED_FREQ_90KHZ = 6,
|
||||
PS_LED_FREQ_100KHZ = 7,
|
||||
};
|
||||
|
||||
// LED current duty
|
||||
enum PsLedDuty : uint8_t {
|
||||
PS_LED_DUTY_25 = 0,
|
||||
PS_LED_DUTY_50 = 1, // default
|
||||
PS_LED_DUTY_75 = 2,
|
||||
PS_LED_DUTY_100 = 3,
|
||||
};
|
||||
|
||||
// LED pulsed current level
|
||||
enum PsLedCurrent : uint8_t {
|
||||
PS_LED_CURRENT_5MA = 0,
|
||||
PS_LED_CURRENT_10MA = 1,
|
||||
PS_LED_CURRENT_20MA = 2,
|
||||
PS_LED_CURRENT_50MA = 3, // default
|
||||
PS_LED_CURRENT_100MA = 4,
|
||||
PS_LED_CURRENT_100MA1 = 5,
|
||||
PS_LED_CURRENT_100MA2 = 6,
|
||||
PS_LED_CURRENT_100MA3 = 7,
|
||||
};
|
||||
|
||||
// PS measurement rate
|
||||
enum PsMeasurementRate : uint8_t {
|
||||
PS_MEAS_RATE_50MS = 0,
|
||||
PS_MEAS_RATE_70MS = 1,
|
||||
PS_MEAS_RATE_100MS = 2, // default
|
||||
PS_MEAS_RATE_200MS = 3,
|
||||
PS_MEAS_RATE_500MS = 4,
|
||||
PS_MEAS_RATE_1000MS = 5,
|
||||
PS_MEAS_RATE_2000MS = 6,
|
||||
PS_MEAS_RATE_2000MS1 = 7,
|
||||
};
|
||||
|
||||
//
|
||||
// ALS_CONTR Register (0x80)
|
||||
//
|
||||
union AlsControlRegister501 {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
bool asl_mode_xxx : 1;
|
||||
bool als_mode_active : 1;
|
||||
bool sw_reset : 1;
|
||||
AlsGain501 gain : 1;
|
||||
uint8_t reserved : 4;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
//
|
||||
// PS_CONTR Register (0x81)
|
||||
//
|
||||
union PsControlRegister501 {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
bool ps_mode_xxx : 1;
|
||||
bool ps_mode_active : 1;
|
||||
PsGain501 ps_gain : 2;
|
||||
bool reserved_4 : 1;
|
||||
bool reserved_5 : 1;
|
||||
bool reserved_6 : 1;
|
||||
bool reserved_7 : 1;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
//
|
||||
// PS_LED Register (0x82)
|
||||
//
|
||||
union PsLedRegister {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
PsLedCurrent ps_led_current : 3;
|
||||
PsLedDuty ps_led_duty : 2;
|
||||
PsLedFreq ps_led_freq : 3;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
//
|
||||
// PS_N_PULSES Register (0x83)
|
||||
//
|
||||
union PsNPulsesRegister501 {
|
||||
uint8_t raw;
|
||||
uint8_t number_of_pulses;
|
||||
};
|
||||
|
||||
//
|
||||
// PS_MEAS_RATE Register (0x84)
|
||||
//
|
||||
union PsMeasurementRateRegister {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
PsMeasurementRate ps_measurement_rate : 4;
|
||||
uint8_t reserved : 4;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
//
|
||||
// ALS_MEAS_RATE Register (0x85)
|
||||
//
|
||||
union MeasurementRateRegister501 {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
MeasurementRepeatRate measurement_repeat_rate : 3;
|
||||
IntegrationTime501 integration_time : 2;
|
||||
bool reserved_5 : 1;
|
||||
bool reserved_6 : 1;
|
||||
bool reserved_7 : 1;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
//
|
||||
// PART_ID Register (0x86) (Read Only)
|
||||
//
|
||||
union PartIdRegister {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
uint8_t part_number_id : 4;
|
||||
uint8_t revision_id : 4;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
//
|
||||
// ALS_PS_STATUS Register (0x8C) (Read Only)
|
||||
//
|
||||
union AlsPsStatusRegister {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
bool ps_new_data : 1; // 0 - old data, 1 - new data
|
||||
bool ps_interrupt : 1; // 0 - interrupt signal not active, 1 - interrupt signal active
|
||||
bool als_new_data : 1; // 0 - old data, 1 - new data
|
||||
bool als_interrupt : 1; // 0 - interrupt signal not active, 1 - interrupt signal active
|
||||
AlsGain501 gain : 1; // current ALS gain
|
||||
bool reserved_5 : 1;
|
||||
bool reserved_6 : 1;
|
||||
bool reserved_7 : 1;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
//
|
||||
// PS_DATA_1 Register (0x8E) (Read Only)
|
||||
//
|
||||
union PsData1Register {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
uint8_t ps_data_high : 3;
|
||||
uint8_t reserved : 4;
|
||||
bool ps_saturation_flag : 1;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
//
|
||||
// INTERRUPT Register (0x8F) (Read Only)
|
||||
//
|
||||
union InterruptRegister {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
bool ps_interrupt : 1;
|
||||
bool als_interrupt : 1;
|
||||
bool interrupt_polarity : 1; // 0 - active low (default), 1 - active high
|
||||
uint8_t reserved : 5;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
//
|
||||
// INTERRUPT_PERSIST Register (0x9E)
|
||||
//
|
||||
union InterruptPersistRegister {
|
||||
uint8_t raw;
|
||||
struct {
|
||||
uint8_t als_persist : 4; // 0 - every ALS cycle, 1 - every 2 ALS cycles, ... 15 - every 16 ALS cycles
|
||||
uint8_t ps_persist : 4; // 0 - every PS cycle, 1 - every 2 PS cycles, ... 15 - every 16 PS cycles
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
} // namespace ltr501
|
||||
} // namespace esphome
|
274
esphome/components/ltr501/sensor.py
Normal file
274
esphome/components/ltr501/sensor.py
Normal file
@ -0,0 +1,274 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ACTUAL_GAIN,
|
||||
CONF_ACTUAL_INTEGRATION_TIME,
|
||||
CONF_AMBIENT_LIGHT,
|
||||
CONF_AUTO_MODE,
|
||||
CONF_FULL_SPECTRUM_COUNTS,
|
||||
CONF_GAIN,
|
||||
CONF_GLASS_ATTENUATION_FACTOR,
|
||||
CONF_ID,
|
||||
CONF_INTEGRATION_TIME,
|
||||
CONF_NAME,
|
||||
CONF_REPEAT,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
ICON_BRIGHTNESS_5,
|
||||
ICON_BRIGHTNESS_6,
|
||||
ICON_TIMER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_LUX,
|
||||
UNIT_MILLISECOND,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@latonita"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
CONF_INFRARED_COUNTS = "infrared_counts"
|
||||
CONF_ON_PS_HIGH_THRESHOLD = "on_ps_high_threshold"
|
||||
CONF_ON_PS_LOW_THRESHOLD = "on_ps_low_threshold"
|
||||
CONF_PS_COOLDOWN = "ps_cooldown"
|
||||
CONF_PS_COUNTS = "ps_counts"
|
||||
CONF_PS_GAIN = "ps_gain"
|
||||
CONF_PS_HIGH_THRESHOLD = "ps_high_threshold"
|
||||
CONF_PS_LOW_THRESHOLD = "ps_low_threshold"
|
||||
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
|
||||
ICON_GAIN = "mdi:multiplication"
|
||||
ICON_PROXIMITY = "mdi:hand-wave-outline"
|
||||
UNIT_COUNTS = "#"
|
||||
|
||||
ltr501_ns = cg.esphome_ns.namespace("ltr501")
|
||||
|
||||
LTRAlsPsComponent = ltr501_ns.class_(
|
||||
"LTRAlsPs501Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
LtrType = ltr501_ns.enum("LtrType")
|
||||
LTR_TYPES = {
|
||||
"ALS": LtrType.LTR_TYPE_ALS_ONLY,
|
||||
"PS": LtrType.LTR_TYPE_PS_ONLY,
|
||||
"ALS_PS": LtrType.LTR_TYPE_ALS_AND_PS,
|
||||
}
|
||||
|
||||
AlsGain = ltr501_ns.enum("AlsGain501")
|
||||
ALS_GAINS = {
|
||||
"1X": AlsGain.GAIN_1,
|
||||
"150X": AlsGain.GAIN_150,
|
||||
}
|
||||
|
||||
IntegrationTime = ltr501_ns.enum("IntegrationTime501")
|
||||
INTEGRATION_TIMES = {
|
||||
50: IntegrationTime.INTEGRATION_TIME_50MS,
|
||||
100: IntegrationTime.INTEGRATION_TIME_100MS,
|
||||
200: IntegrationTime.INTEGRATION_TIME_200MS,
|
||||
400: IntegrationTime.INTEGRATION_TIME_400MS,
|
||||
}
|
||||
|
||||
MeasurementRepeatRate = ltr501_ns.enum("MeasurementRepeatRate")
|
||||
MEASUREMENT_REPEAT_RATES = {
|
||||
50: MeasurementRepeatRate.REPEAT_RATE_50MS,
|
||||
100: MeasurementRepeatRate.REPEAT_RATE_100MS,
|
||||
200: MeasurementRepeatRate.REPEAT_RATE_200MS,
|
||||
500: MeasurementRepeatRate.REPEAT_RATE_500MS,
|
||||
1000: MeasurementRepeatRate.REPEAT_RATE_1000MS,
|
||||
2000: MeasurementRepeatRate.REPEAT_RATE_2000MS,
|
||||
}
|
||||
|
||||
PsGain = ltr501_ns.enum("PsGain501")
|
||||
PS_GAINS = {
|
||||
"1X": PsGain.PS_GAIN_1,
|
||||
"4X": PsGain.PS_GAIN_4,
|
||||
"8X": PsGain.PS_GAIN_8,
|
||||
"16X": PsGain.PS_GAIN_16,
|
||||
}
|
||||
|
||||
LTRPsHighTrigger = ltr501_ns.class_("LTRPsHighTrigger", automation.Trigger.template())
|
||||
LTRPsLowTrigger = ltr501_ns.class_("LTRPsLowTrigger", automation.Trigger.template())
|
||||
|
||||
|
||||
def validate_integration_time(value):
|
||||
value = cv.positive_time_period_milliseconds(value).total_milliseconds
|
||||
return cv.enum(INTEGRATION_TIMES, int=True)(value)
|
||||
|
||||
|
||||
def validate_repeat_rate(value):
|
||||
value = cv.positive_time_period_milliseconds(value).total_milliseconds
|
||||
return cv.enum(MEASUREMENT_REPEAT_RATES, int=True)(value)
|
||||
|
||||
|
||||
def validate_time_and_repeat_rate(config):
|
||||
integraton_time = config[CONF_INTEGRATION_TIME]
|
||||
repeat_rate = config[CONF_REPEAT]
|
||||
if integraton_time > repeat_rate:
|
||||
raise cv.Invalid(
|
||||
f"Measurement repeat rate ({repeat_rate}ms) shall be greater or equal to integration time ({integraton_time}ms)"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def validate_als_gain_and_integration_time(config):
|
||||
integraton_time = config[CONF_INTEGRATION_TIME]
|
||||
if config[CONF_GAIN] == "1X" and integraton_time > 100:
|
||||
raise cv.Invalid(
|
||||
"ALS gain 1X can only be used with integration time 50ms or 100ms"
|
||||
)
|
||||
if config[CONF_GAIN] == "200X" and integraton_time == 50:
|
||||
raise cv.Invalid("ALS gain 200X can not be used with integration time 50ms")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LTRAlsPsComponent),
|
||||
cv.Optional(CONF_TYPE, default="ALS_PS"): cv.enum(LTR_TYPES, upper=True),
|
||||
cv.Optional(CONF_AUTO_MODE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_GAIN, default="1X"): cv.enum(ALS_GAINS, upper=True),
|
||||
cv.Optional(
|
||||
CONF_INTEGRATION_TIME, default="100ms"
|
||||
): validate_integration_time,
|
||||
cv.Optional(CONF_REPEAT, default="500ms"): validate_repeat_rate,
|
||||
cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range(
|
||||
min=1.0
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_PS_COOLDOWN, default="5s"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_PS_GAIN, default="1X"): cv.enum(PS_GAINS, upper=True),
|
||||
cv.Optional(CONF_PS_HIGH_THRESHOLD, default=65535): cv.int_range(
|
||||
min=0, max=65535
|
||||
),
|
||||
cv.Optional(CONF_PS_LOW_THRESHOLD, default=0): cv.int_range(
|
||||
min=0, max=65535
|
||||
),
|
||||
cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsHighTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsLowTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_AMBIENT_LIGHT): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_LUX,
|
||||
icon=ICON_BRIGHTNESS_6,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_INFRARED_COUNTS): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_BRIGHTNESS_5,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_FULL_SPECTRUM_COUNTS): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_BRIGHTNESS_7,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_PS_COUNTS): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_PROXIMITY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_ACTUAL_GAIN): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
icon=ICON_GAIN,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_ACTUAL_INTEGRATION_TIME): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLISECOND,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x23)),
|
||||
validate_time_and_repeat_rate,
|
||||
validate_als_gain_and_integration_time,
|
||||
)
|
||||
|
||||
|
||||
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 als_config := config.get(CONF_AMBIENT_LIGHT):
|
||||
sens = await sensor.new_sensor(als_config)
|
||||
cg.add(var.set_ambient_light_sensor(sens))
|
||||
|
||||
if infrared_cnt_config := config.get(CONF_INFRARED_COUNTS):
|
||||
sens = await sensor.new_sensor(infrared_cnt_config)
|
||||
cg.add(var.set_infrared_counts_sensor(sens))
|
||||
|
||||
if full_spect_cnt_config := config.get(CONF_FULL_SPECTRUM_COUNTS):
|
||||
sens = await sensor.new_sensor(full_spect_cnt_config)
|
||||
cg.add(var.set_full_spectrum_counts_sensor(sens))
|
||||
|
||||
if act_gain_config := config.get(CONF_ACTUAL_GAIN):
|
||||
sens = await sensor.new_sensor(act_gain_config)
|
||||
cg.add(var.set_actual_gain_sensor(sens))
|
||||
|
||||
if act_itime_config := config.get(CONF_ACTUAL_INTEGRATION_TIME):
|
||||
sens = await sensor.new_sensor(act_itime_config)
|
||||
cg.add(var.set_actual_integration_time_sensor(sens))
|
||||
|
||||
if prox_cnt_config := config.get(CONF_PS_COUNTS):
|
||||
sens = await sensor.new_sensor(prox_cnt_config)
|
||||
cg.add(var.set_proximity_counts_sensor(sens))
|
||||
|
||||
for prox_high_tr in config.get(CONF_ON_PS_HIGH_THRESHOLD, []):
|
||||
trigger = cg.new_Pvariable(prox_high_tr[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], prox_high_tr)
|
||||
|
||||
for prox_low_tr in config.get(CONF_ON_PS_LOW_THRESHOLD, []):
|
||||
trigger = cg.new_Pvariable(prox_low_tr[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], prox_low_tr)
|
||||
|
||||
cg.add(var.set_ltr_type(config[CONF_TYPE]))
|
||||
|
||||
cg.add(var.set_als_auto_mode(config[CONF_AUTO_MODE]))
|
||||
cg.add(var.set_als_gain(config[CONF_GAIN]))
|
||||
cg.add(var.set_als_integration_time(config[CONF_INTEGRATION_TIME]))
|
||||
cg.add(var.set_als_meas_repeat_rate(config[CONF_REPEAT]))
|
||||
cg.add(var.set_als_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR]))
|
||||
|
||||
cg.add(var.set_ps_cooldown_time_s(config[CONF_PS_COOLDOWN]))
|
||||
cg.add(var.set_ps_gain(config[CONF_PS_GAIN]))
|
||||
cg.add(var.set_ps_high_threshold(config[CONF_PS_HIGH_THRESHOLD]))
|
||||
cg.add(var.set_ps_low_threshold(config[CONF_PS_LOW_THRESHOLD]))
|
@ -4,8 +4,10 @@ from esphome import automation
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ACTUAL_GAIN,
|
||||
CONF_ACTUAL_INTEGRATION_TIME,
|
||||
CONF_AMBIENT_LIGHT,
|
||||
CONF_AUTO_MODE,
|
||||
CONF_FULL_SPECTRUM_COUNTS,
|
||||
CONF_GAIN,
|
||||
CONF_GLASS_ATTENUATION_FACTOR,
|
||||
CONF_ID,
|
||||
@ -27,8 +29,6 @@ from esphome.const import (
|
||||
CODEOWNERS = ["@latonita"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time"
|
||||
CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts"
|
||||
CONF_INFRARED_COUNTS = "infrared_counts"
|
||||
CONF_ON_PS_HIGH_THRESHOLD = "on_ps_high_threshold"
|
||||
CONF_ON_PS_LOW_THRESHOLD = "on_ps_low_threshold"
|
||||
|
@ -3,9 +3,11 @@ import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ACTUAL_GAIN,
|
||||
CONF_ACTUAL_INTEGRATION_TIME,
|
||||
CONF_AMBIENT_LIGHT,
|
||||
CONF_AUTO_MODE,
|
||||
CONF_FULL_SPECTRUM,
|
||||
CONF_FULL_SPECTRUM_COUNTS,
|
||||
CONF_GAIN,
|
||||
CONF_GLASS_ATTENUATION_FACTOR,
|
||||
CONF_ID,
|
||||
@ -28,9 +30,7 @@ UNIT_COUNTS = "#"
|
||||
ICON_MULTIPLICATION = "mdi:multiplication"
|
||||
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
|
||||
|
||||
CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time"
|
||||
CONF_AMBIENT_LIGHT_COUNTS = "ambient_light_counts"
|
||||
CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts"
|
||||
CONF_LUX_COMPENSATION = "lux_compensation"
|
||||
|
||||
veml7700_ns = cg.esphome_ns.namespace("veml7700")
|
||||
|
@ -44,6 +44,7 @@ CONF_ACTIONS = "actions"
|
||||
CONF_ACTIVE = "active"
|
||||
CONF_ACTIVE_POWER = "active_power"
|
||||
CONF_ACTUAL_GAIN = "actual_gain"
|
||||
CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time"
|
||||
CONF_ADDRESS = "address"
|
||||
CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
|
||||
CONF_ADVANCED = "advanced"
|
||||
@ -323,6 +324,7 @@ CONF_FREQUENCY = "frequency"
|
||||
CONF_FRIENDLY_NAME = "friendly_name"
|
||||
CONF_FROM = "from"
|
||||
CONF_FULL_SPECTRUM = "full_spectrum"
|
||||
CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts"
|
||||
CONF_FULL_UPDATE_EVERY = "full_update_every"
|
||||
CONF_GAIN = "gain"
|
||||
CONF_GAMMA_CORRECT = "gamma_correct"
|
||||
|
9
tests/components/ltr501/common.yaml
Normal file
9
tests/components/ltr501/common.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
sensor:
|
||||
- platform: ltr501
|
||||
address: 0x23
|
||||
i2c_id: i2c_ltr501
|
||||
type: ALS_PS
|
||||
gain: 1X
|
||||
integration_time: 100ms
|
||||
ambient_light: "Ambient light"
|
||||
ps_counts: "Proximity counts"
|
6
tests/components/ltr501/test.esp32-ard.yaml
Normal file
6
tests/components/ltr501/test.esp32-ard.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
i2c:
|
||||
- id: i2c_ltr501
|
||||
scl: 16
|
||||
sda: 17
|
||||
|
||||
<<: !include common.yaml
|
6
tests/components/ltr501/test.esp32-c3-ard.yaml
Normal file
6
tests/components/ltr501/test.esp32-c3-ard.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
i2c:
|
||||
- id: i2c_ltr501
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
<<: !include common.yaml
|
6
tests/components/ltr501/test.esp32-c3-idf.yaml
Normal file
6
tests/components/ltr501/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
i2c:
|
||||
- id: i2c_ltr501
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
<<: !include common.yaml
|
6
tests/components/ltr501/test.esp32-idf.yaml
Normal file
6
tests/components/ltr501/test.esp32-idf.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
i2c:
|
||||
- id: i2c_ltr501
|
||||
scl: 16
|
||||
sda: 17
|
||||
|
||||
<<: !include common.yaml
|
6
tests/components/ltr501/test.esp8266-ard.yaml
Normal file
6
tests/components/ltr501/test.esp8266-ard.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
i2c:
|
||||
- id: i2c_ltr501
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
<<: !include common.yaml
|
6
tests/components/ltr501/test.rp2040-ard.yaml
Normal file
6
tests/components/ltr501/test.rp2040-ard.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
i2c:
|
||||
- id: i2c_ltr501
|
||||
scl: 5
|
||||
sda: 4
|
||||
|
||||
<<: !include common.yaml
|
Loading…
Reference in New Issue
Block a user