From fb9d49453041ac3f6a2b0239fe74aa56c74a6874 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Thu, 11 Jan 2024 00:52:22 +0000 Subject: [PATCH 01/32] LTR303 and LTR329 light sensors --- CODEOWNERS | 1 + esphome/components/ltr303/__init__.py | 1 + esphome/components/ltr303/ltr303.cpp | 219 ++++++++++++++++++++++++++ esphome/components/ltr303/ltr303.h | 145 +++++++++++++++++ esphome/components/ltr303/sensor.py | 164 +++++++++++++++++++ 5 files changed, 530 insertions(+) create mode 100644 esphome/components/ltr303/__init__.py create mode 100644 esphome/components/ltr303/ltr303.cpp create mode 100644 esphome/components/ltr303/ltr303.h create mode 100644 esphome/components/ltr303/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 9577d7df47..734b049ebf 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -178,6 +178,7 @@ esphome/components/lightwaverf/* @max246 esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core +esphome/components/ltr303/* @latonita esphome/components/ltr390/* @sjtrny esphome/components/matrix_keypad/* @ssieb esphome/components/max31865/* @DAVe3283 diff --git a/esphome/components/ltr303/__init__.py b/esphome/components/ltr303/__init__.py new file mode 100644 index 0000000000..dd06cfffea --- /dev/null +++ b/esphome/components/ltr303/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@latonita"] diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp new file mode 100644 index 0000000000..81e98d3e3e --- /dev/null +++ b/esphome/components/ltr303/ltr303.cpp @@ -0,0 +1,219 @@ +#include "ltr303.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace ltr303 { + +static const char *const TAG = "ltr303"; +static const int ALS_GAIN[8] = {1, 2, 4, 8, 0, 0, 48, 96}; // multiplier +static const int ALS_INT_TIME[8] = {100, 50, 200, 400, 150, 250, 300, 350}; // ms +static const int ALS_MEAS_RATE[8] = {50, 100, 200, 500, 1000, 2000, 2000, 2000}; // ms + +float LTR303Component::calculate_lux_(uint16_t channel0, uint16_t channel1, uint8_t gain, uint8_t time) { + if ((channel0 == 0xFFFF) || (channel1 == 0xFFFF)) { + ESP_LOGW(TAG, "Sensors got saturated"); + return 0.0f; + } + + if ((channel0 == 0x0000) && (channel1 == 0x0000)) { + ESP_LOGW(TAG, "Sensors blacked out"); + return 0.0f; + } + + float ch0 = channel0; + float ch1 = channel1; + float ratio = ch1 / (ch0 + ch1); + float als_gain = ALS_GAIN[gain]; + float als_time = ((float) ALS_INT_TIME[time]) / 100.0f; + float inv_pfactor = this->attenuation_factor_; + float lux = 0.0f; + + ESP_LOGD(TAG, "CALC LUX: ratio %f, gain %f, int time %f, inv_pfactor %f", ratio, als_gain, als_time, inv_pfactor); + + if (ratio < 0.45) { + lux = (1.7743 * ch0 + 1.1059 * ch1); + } else if (ratio < 0.64 && ratio >= 0.45) { + lux = (4.2785 * ch0 - 1.9548 * ch1); + } else if (ratio < 0.85 && ratio >= 0.64) { + lux = (0.5926 * ch0 + 0.1185 * ch1); + } else { + ESP_LOGW(TAG, "Impossible ch1/(ch0 + ch1) ratio"); + lux = 0.0f; + } + lux = inv_pfactor * lux / als_gain / als_time; + return lux; +} + +bool LTR303Component::read_data_() { + ESP_LOGD(TAG, "Reading sensor data"); + + StatusRegister als_status{0}; + + const uint32_t start = millis(); + while (true) { + als_status.raw = this->reg(CommandRegisters::CR_ALS_STATUS).get(); + ESP_LOGD(TAG, "LTR303_ALS_STATUS = 0x%02X", als_status.raw); + + if (als_status.new_data) { + ESP_LOGD(TAG, "New data is ready"); + break; + } + if (millis() - start > 100) { + ESP_LOGW(TAG, "No data returned from sensor"); + return false; + } + ESP_LOGW(TAG, "Waiting for data"); + delay(5); + } + + if (als_status.data_invalid) { + ESP_LOGW(TAG, "Data available but not valid"); + return false; + } + + // finally + uint8_t ch1_0 = this->reg(CommandRegisters::CR_CH1_0).get(); + uint8_t ch1_1 = this->reg(CommandRegisters::CR_CH1_1).get(); + uint8_t ch0_0 = this->reg(CommandRegisters::CR_CH0_0).get(); + uint8_t ch0_1 = this->reg(CommandRegisters::CR_CH0_1).get(); + + this->channel1_ = encode_uint16(ch1_1, ch1_0); + this->channel0_ = encode_uint16(ch0_1, ch0_0); + + ESP_LOGD(TAG, "Channel data CH1 = %d, CH0 = %d", this->channel1_, this->channel0_); + + uint8_t actual_gain = als_status.gain; + if (actual_gain != this->gain_) { + ESP_LOGW(TAG, "Actual gain (%d) differs from requested (%d)", ALS_GAIN[actual_gain], ALS_GAIN[this->gain_]); + } + + if (this->infrared_counts_sensor_ != nullptr) { + this->infrared_counts_sensor_->publish_state(this->channel1_); + } + + if (this->full_spectrum_counts_sensor_ != nullptr) { + this->full_spectrum_counts_sensor_->publish_state(this->channel0_); + } + + if (this->actual_gain_sensor_ != nullptr) { + this->actual_gain_sensor_->publish_state(ALS_GAIN[actual_gain]); + } + + if (this->ambient_light_sensor_ != nullptr) { + float lux = this->calculate_lux_(this->channel0_, this->channel1_, actual_gain, this->integration_time_); + ESP_LOGD(TAG, "Calculated lux: %f", lux); + this->ambient_light_sensor_->publish_state(lux); + } + + return true; +} + +void LTR303Component::reset_() { + ESP_LOGD(TAG, "Resetting"); + + ControlRegister als_ctrl{0}; + als_ctrl.sw_reset = true; + this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; + delay(10); + + uint8_t tries = 10; + do { + ESP_LOGD(TAG, "Waiting chip to reset"); + delay(5); + als_ctrl.raw = this->reg(CommandRegisters::CR_ALS_CTRL).get(); + } while (als_ctrl.sw_reset && tries--); // while sw reset bit is on - keep waiting + + if (als_ctrl.sw_reset) { + ESP_LOGW(TAG, "Failed to finalize reset procedure"); + } +} + +void LTR303Component::activate_() { + ESP_LOGD(TAG, "Activating"); + + ControlRegister als_ctrl{0}; + als_ctrl.active_mode = true; + als_ctrl.gain = this->gain_; + + ESP_LOGD(TAG, "Setting active mode and gain 0x%02X", als_ctrl.raw); + this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; + delay(10); + + uint8_t tries = 10; + do { + ESP_LOGD(TAG, "Waiting chip to activate"); + delay(5); + als_ctrl.raw = this->reg(CommandRegisters::CR_ALS_CTRL).get(); + } while (!als_ctrl.active_mode && tries--); // while active mode is not set - keep waiting + + if (!als_ctrl.active_mode) { + ESP_LOGW(TAG, "Failed to activate chip"); + } +} + +void LTR303Component::configure_() { + MeasurementRateRegister meas{0}; + meas.measurement_repeat_rate = this->repeat_rate_; + meas.integration_time = this->integration_time_; + ESP_LOGD(TAG, "Setting integration time and measurement repeat rate 0x%02X", meas.raw); + this->reg(CommandRegisters::CR_MEAS_RATE) = meas.raw; + delay(10); +} + +void LTR303Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up LTR-303/329"); + delay(100); // need to wait at least 100ms after power on to get ALS chip responsive + App.feed_wdt(); + + this->reset_(); + this->activate_(); + this->configure_(); + + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { + ESP_LOGD(TAG, "i2c connection failed"); + this->mark_failed(); + return; + } +} + +void LTR303Component::dump_config() { + LOG_I2C_DEVICE(this); + ESP_LOGCONFIG(TAG, " Gain: x%d", ALS_GAIN[this->gain_]); + ESP_LOGCONFIG(TAG, " Integration time: %d ms", ALS_INT_TIME[this->integration_time_]); + ESP_LOGCONFIG(TAG, " Measurement repeat rate: %d ms", ALS_MEAS_RATE[this->repeat_rate_]); + ESP_LOGCONFIG(TAG, " Attenuation factor: %f", this->attenuation_factor_); + LOG_UPDATE_INTERVAL(this); + + if (this->infrared_counts_sensor_ != nullptr) { + LOG_SENSOR(" ", "CH1 Infrared", this->infrared_counts_sensor_); + } + if (this->full_spectrum_counts_sensor_ != nullptr) { + LOG_SENSOR(" ", "CH0 Visible + IR", this->full_spectrum_counts_sensor_); + } + if (this->actual_gain_sensor_ != nullptr) { + LOG_SENSOR(" ", "Actual gain", this->actual_gain_sensor_); + } + if (this->ambient_light_sensor_ != nullptr) { + LOG_SENSOR(" ", "Ambient light calculated lux", this->ambient_light_sensor_); + } + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with I2C LTR-303/329 failed!"); + } +} + +void LTR303Component::update() { + ESP_LOGD(TAG, "Updating"); + + if (this->is_ready() && !this->reading_data_) { + this->reading_data_ = true; + this->read_data_(); + this->reading_data_ = false; + } +} + +} // namespace ltr303 +} // namespace esphome diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h new file mode 100644 index 0000000000..ea543555a3 --- /dev/null +++ b/esphome/components/ltr303/ltr303.h @@ -0,0 +1,145 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" +#include "esphome/core/optional.h" + +namespace esphome { +namespace ltr303 { + +// https://www.mouser.com/datasheet/2/239/Lite-On_LTR-303ALS-01_DS_ver%201.1-1175269.pdf + +enum CommandRegisters : uint8_t { + CR_ALS_CTRL = 0x80, // ALS operation mode control SW reset + CR_MEAS_RATE = 0x85, // ALS measurement rate in active mode + CR_PART_ID = 0x86, // Part Number ID and Revision ID + CR_MANU_ID = 0x87, // Manufacturer ID + CR_CH1_0 = 0x88, // ALS measurement CH1 data, lower byte - infrared only + CR_CH1_1 = 0x89, // ALS measurement CH1 data, upper byte - infrared only + CR_CH0_0 = 0x8A, // ALS measurement CH0 data, lower byte - visible + infrared + CR_CH0_1 = 0x8B, // ALS measurement CH0 data, upper byte - visible + infrared + CR_ALS_STATUS = 0x8c // ALS new data status +}; + +#pragma pack(push) +#pragma pack(1) +// +// ALS_CONTR Register (0x80) +// +union ControlRegister { + uint8_t raw; + struct { + bool active_mode : 1; + bool sw_reset : 1; + uint8_t gain : 3; + uint8_t reserved : 3; + }; +}; + +// +// ALS_MEAS_RATE Register (0x85) +// +union MeasurementRateRegister { + uint8_t raw; + struct { + uint8_t measurement_repeat_rate : 3; + uint8_t integration_time : 3; + bool reserved_6 : 1; + bool reserved_7 : 1; + }; +}; + +// +// ALS_STATUS Register (0x8C) (Read Only) +// +union StatusRegister { + uint8_t raw; + struct { + bool reserved_0 : 1; + bool reserved_1 : 1; + bool new_data : 1; + bool reserved_3 : 1; + uint8_t gain : 3; + bool data_invalid : 1; + }; +}; +#pragma pack(pop) + +// Sensor gain levels +enum Gain : uint8_t { + GAIN_1 = 0, // default + GAIN_2 = 1, + GAIN_4 = 2, + GAIN_8 = 3, + GAIN_48 = 6, + GAIN_96 = 7, +}; + +enum IntegrationTime : uint8_t { + INTEGRATION_TIME_100MS = 0, // default + INTEGRATION_TIME_50MS = 1, + INTEGRATION_TIME_200MS = 2, + INTEGRATION_TIME_400MS = 3, + INTEGRATION_TIME_150MS = 4, + INTEGRATION_TIME_250MS = 5, + INTEGRATION_TIME_300MS = 6, + INTEGRATION_TIME_350MS = 7 +}; + +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 +}; + +class LTR303Component : public PollingComponent, public i2c::I2CDevice { + public: + float get_setup_priority() const override { return setup_priority::DATA; } + void setup() override; + void dump_config() override; + void update() override; + + void set_gain(Gain gain) { this->gain_ = gain; } + void set_integration_time(IntegrationTime time) { this->integration_time_ = time; } + void set_repeat_rate(MeasurementRepeatRate rate) { this->repeat_rate_ = rate; } + void set_attenuation_factor(float factor) { this->attenuation_factor_ = factor; } + + 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; } + + protected: + void reset_(); + void activate_(); + void configure_(); + + bool read_data_(); + + float calculate_lux_(uint16_t channel0, uint16_t channel1, uint8_t gain, uint8_t time); + + bool reading_data_{false}; + + uint16_t channel0_{0}; + uint16_t channel1_{0}; + float lux{0}; + + Gain gain_{Gain::GAIN_1}; + IntegrationTime integration_time_{IntegrationTime::INTEGRATION_TIME_100MS}; + MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS}; + float attenuation_factor_{1.0}; + + 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 + + // enum ErrorCode { NONE = 0, COMMUNICATION_FAILED } error_code_{NONE}; +}; + +} // namespace ltr303 +} // namespace esphome diff --git a/esphome/components/ltr303/sensor.py b/esphome/components/ltr303/sensor.py new file mode 100644 index 0000000000..fbc7073277 --- /dev/null +++ b/esphome/components/ltr303/sensor.py @@ -0,0 +1,164 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_ACTUAL_GAIN, + CONF_GAIN, + CONF_GLASS_ATTENUATION_FACTOR, + CONF_INTEGRATION_TIME, + CONF_REPEAT, + UNIT_LUX, + ICON_BRIGHTNESS_5, + ICON_BRIGHTNESS_6, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, +) + +CODEOWNERS = ["@latonita"] +DEPENDENCIES = ["i2c"] + +UNIT_COUNTS = "#" +ICON_GAIN = "mdi:multiplication" +ICON_BRIGHTNESS_7 = "mdi:brightness-7" +CONF_AMBIENT_LIGHT = "ambient_light" +CONF_INFRARED_COUNTS = "infrared_counts" +CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts" + +ltr303_ns = cg.esphome_ns.namespace("ltr303") + +LTR303Component = ltr303_ns.class_( + "LTR303Component", cg.PollingComponent, i2c.I2CDevice +) + +Gain = ltr303_ns.enum("Gain") +GAINS = { + "1X": Gain.GAIN_1, + "2X": Gain.GAIN_2, + "4X": Gain.GAIN_4, + "8X": Gain.GAIN_8, + "48X": Gain.GAIN_48, + "96X": Gain.GAIN_96, +} + +IntegrationTime = ltr303_ns.enum("IntegrationTime") +INTEGRATION_TIMES = { + 50: IntegrationTime.INTEGRATION_TIME_50MS, + 100: IntegrationTime.INTEGRATION_TIME_100MS, + 150: IntegrationTime.INTEGRATION_TIME_150MS, + 200: IntegrationTime.INTEGRATION_TIME_200MS, + 250: IntegrationTime.INTEGRATION_TIME_250MS, + 300: IntegrationTime.INTEGRATION_TIME_300MS, + 350: IntegrationTime.INTEGRATION_TIME_350MS, + 400: IntegrationTime.INTEGRATION_TIME_400MS, +} + +MeasurementRepeatRate = ltr303_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, +} + + +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 + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LTR303Component), + cv.Optional(CONF_GAIN, default="X1"): cv.enum(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_AMBIENT_LIGHT): 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, + ), + cv.Optional(CONF_INFRARED_COUNTS): 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, + ), + cv.Optional(CONF_FULL_SPECTRUM_COUNTS): 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, + ), + cv.Optional(CONF_ACTUAL_GAIN): sensor.sensor_schema( + icon=ICON_GAIN, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x29)), + cv.has_at_least_one_key( + CONF_AMBIENT_LIGHT, + CONF_INFRARED_COUNTS, + CONF_FULL_SPECTRUM_COUNTS, + CONF_ACTUAL_GAIN, + ), + validate_time_and_repeat_rate, +) + + +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)) + + cg.add(var.set_gain(config[CONF_GAIN])) + cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) + cg.add(var.set_repeat_rate(config[CONF_REPEAT])) + cg.add(var.set_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) From 640f7e272d611efd60b342d14c4e46396de2a446 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Thu, 11 Jan 2024 10:51:12 +0000 Subject: [PATCH 02/32] LTR303 tidy up --- esphome/components/ltr303/ltr303.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index 81e98d3e3e..51ae834c2b 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -164,7 +164,10 @@ void LTR303Component::configure_() { void LTR303Component::setup() { ESP_LOGCONFIG(TAG, "Setting up LTR-303/329"); - delay(100); // need to wait at least 100ms after power on to get ALS chip responsive + + // As per datasheet we need to wait at least 100ms after power on to get ALS chip responsive + // It' only done once during setup phase + delay(100); // NOLINT App.feed_wdt(); this->reset_(); From 349c794923788cedd5f9c9231fdafed7b2264fb6 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Thu, 11 Jan 2024 11:55:54 +0000 Subject: [PATCH 03/32] LTR303 unused var --- esphome/components/ltr303/ltr303.h | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index ea543555a3..4406e2d870 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -126,7 +126,6 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { uint16_t channel0_{0}; uint16_t channel1_{0}; - float lux{0}; Gain gain_{Gain::GAIN_1}; IntegrationTime integration_time_{IntegrationTime::INTEGRATION_TIME_100MS}; From 4067b040ab479db1a55207bb5ed51f0dc608aaff Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Thu, 11 Jan 2024 14:28:07 +0000 Subject: [PATCH 04/32] LTR303 tidy up + test --- esphome/components/ltr303/ltr303.cpp | 16 ++++------------ tests/test2.yaml | 13 +++++++++++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index 51ae834c2b..eafb694f94 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -190,18 +190,10 @@ void LTR303Component::dump_config() { ESP_LOGCONFIG(TAG, " Attenuation factor: %f", this->attenuation_factor_); LOG_UPDATE_INTERVAL(this); - if (this->infrared_counts_sensor_ != nullptr) { - LOG_SENSOR(" ", "CH1 Infrared", this->infrared_counts_sensor_); - } - if (this->full_spectrum_counts_sensor_ != nullptr) { - LOG_SENSOR(" ", "CH0 Visible + IR", this->full_spectrum_counts_sensor_); - } - if (this->actual_gain_sensor_ != nullptr) { - LOG_SENSOR(" ", "Actual gain", this->actual_gain_sensor_); - } - if (this->ambient_light_sensor_ != nullptr) { - LOG_SENSOR(" ", "Ambient light calculated lux", this->ambient_light_sensor_); - } + 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-303/329 failed!"); diff --git a/tests/test2.yaml b/tests/test2.yaml index f7a690709a..73dbd7c823 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -311,6 +311,19 @@ sensor: id: motion_rtcgq02lm battery_level: name: Mi Motion Sensor 2 Battery level + - platform: ltr303 + address: 0x29 + gain: 1x + integration_time: 100ms + ambient_light: + name: "Ambient light" + full_spectrum_counts: + name: "Full spectrum counts" + infrared_counts: + name: "Infrared counts" + actual_gain: + name: "Actual gain" + update_interval: 60s - platform: ltr390 uv: name: LTR390 UV From c7f2fac1553bda1e17c2bf443cae03dd99a0bdbc Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Fri, 12 Jan 2024 16:07:28 +0000 Subject: [PATCH 05/32] LTR303 auto sensitivity mode --- esphome/components/ltr303/ltr303.cpp | 186 +++++++++++++++++++++------ esphome/components/ltr303/ltr303.h | 103 ++++++++------- esphome/components/ltr303/sensor.py | 17 +++ 3 files changed, 226 insertions(+), 80 deletions(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index eafb694f94..e182bed6aa 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -11,26 +11,27 @@ static const int ALS_GAIN[8] = {1, 2, 4, 8, 0, 0, 48, 96}; static const int ALS_INT_TIME[8] = {100, 50, 200, 400, 150, 250, 300, 350}; // ms static const int ALS_MEAS_RATE[8] = {50, 100, 200, 500, 1000, 2000, 2000, 2000}; // ms -float LTR303Component::calculate_lux_(uint16_t channel0, uint16_t channel1, uint8_t gain, uint8_t time) { - if ((channel0 == 0xFFFF) || (channel1 == 0xFFFF)) { +float LTR303Component::calculate_lux_(Readings &data) { + if ((data.ch0 == 0xFFFF) || (data.ch1 == 0xFFFF)) { ESP_LOGW(TAG, "Sensors got saturated"); return 0.0f; } - if ((channel0 == 0x0000) && (channel1 == 0x0000)) { + if ((data.ch0 == 0x0000) && (data.ch1 == 0x0000)) { ESP_LOGW(TAG, "Sensors blacked out"); return 0.0f; } - float ch0 = channel0; - float ch1 = channel1; + float ch0 = data.ch0; + float ch1 = data.ch1; float ratio = ch1 / (ch0 + ch1); - float als_gain = ALS_GAIN[gain]; - float als_time = ((float) ALS_INT_TIME[time]) / 100.0f; + float als_gain = ALS_GAIN[data.gain]; + float als_time = ((float) ALS_INT_TIME[data.integration_time]) / 100.0f; float inv_pfactor = this->attenuation_factor_; float lux = 0.0f; - ESP_LOGD(TAG, "CALC LUX: ratio %f, gain %f, int time %f, inv_pfactor %f", ratio, als_gain, als_time, inv_pfactor); + ESP_LOGD(TAG, "Lux calculation: ratio %f, gain %f, int time %f, inv_pfactor %f", ratio, als_gain, als_time, + inv_pfactor); if (ratio < 0.45) { lux = (1.7743 * ch0 + 1.1059 * ch1); @@ -46,18 +47,116 @@ float LTR303Component::calculate_lux_(uint16_t channel0, uint16_t channel1, uint return lux; } -bool LTR303Component::read_data_() { - ESP_LOGD(TAG, "Reading sensor data"); +bool LTR303Component::read_data_manual_(Readings &data) { + if (!this->read_sensor_data_(data)) + return false; + if (data.gain != this->gain_) { + ESP_LOGW(TAG, "Actual gain (%d) differs from requested (%d)", ALS_GAIN[data.gain], ALS_GAIN[this->gain_]); + } + return true; +} + +static const uint16_t LOW_THRESHOLD = 0.10 * 0xffff; +static const uint16_t HIGH_THRESHOLD = 0.75 * 0xffff; + +bool LTR303Component::read_data_automatic_(Readings &data) { + Gain gains[GAINS_COUNT] = {GAIN_1, GAIN_2, GAIN_4, GAIN_8, GAIN_48, GAIN_96}; + IntegrationTime times[TIMES_COUNT] = {INTEGRATION_TIME_50MS, INTEGRATION_TIME_100MS, INTEGRATION_TIME_150MS, + INTEGRATION_TIME_200MS, INTEGRATION_TIME_250MS, INTEGRATION_TIME_300MS, + INTEGRATION_TIME_350MS, INTEGRATION_TIME_400MS}; + uint8_t gain_idx{0}; + uint8_t time_idx{0}; + + bool readings_are_okay{false}; + static Gain current_gain = this->gain_; + static IntegrationTime current_time = this->integration_time_; + bool wait_needed = false; + do { + ESP_LOGD(TAG, "Auto - gain = %dx, time = %d ms", ALS_GAIN[gains[gain_idx]], ALS_INT_TIME[times[time_idx]]); + + if (current_time != times[time_idx]) { + this->configure_integration_time_(times[time_idx]); + current_time = times[time_idx]; + wait_needed = true; + } + + if (current_gain != gains[gain_idx]) { + this->configure_gain_(gains[gain_idx]); + current_gain = gains[gain_idx]; + wait_needed = true; + } + // wait for new data samples to come + // delay(ALS_INT_TIME[current_time] * 2); + if (wait_needed) { + delay(ALS_MEAS_RATE[this->repeat_rate_]); + App.feed_wdt(); + } + + if (!this->read_sensor_data_(data)) + return false; + + if (data.ch0 < LOW_THRESHOLD) { + // low ? time+ -> gain+ + if (time_idx < TIMES_COUNT - 1) { + ESP_LOGD(TAG, "Low illuminance. Increasing int.time"); + time_idx++; + } else if (gain_idx < GAINS_COUNT - 1) { + ESP_LOGD(TAG, "Low illuminance. Increasing gain"); + gain_idx++; + } else { + ESP_LOGD(TAG, "Low illuminance. We will live with it"); + readings_are_okay = true; + } + } else if (data.ch0 > HIGH_THRESHOLD) { + // high ? gain- -> time- + if (gain_idx > 0) { + ESP_LOGD(TAG, "High illuminance. Decreasing gain"); + gain_idx--; + } else if (time_idx > 0) { + ESP_LOGD(TAG, "High illuminance. Decreasing int.time"); + time_idx--; + } else { + ESP_LOGD(TAG, "High illuminance. We will live with it"); + readings_are_okay = true; + } + } else { + ESP_LOGD(TAG, "Illiminance is good enough"); + readings_are_okay = true; + } + } while (!readings_are_okay); + + data.gain = gains[gain_idx]; + data.integration_time = times[time_idx]; + + return true; +} + +bool LTR303Component::read_sensor_data_(Readings &data) { + if (!this->data_ready_(data)) + return false; + + uint8_t ch1_0 = this->reg(CommandRegisters::CR_CH1_0).get(); + uint8_t ch1_1 = this->reg(CommandRegisters::CR_CH1_1).get(); + uint8_t ch0_0 = this->reg(CommandRegisters::CR_CH0_0).get(); + uint8_t ch0_1 = this->reg(CommandRegisters::CR_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); + return true; +} + +bool LTR303Component::data_ready_(Readings &data) { StatusRegister als_status{0}; const uint32_t start = millis(); while (true) { als_status.raw = this->reg(CommandRegisters::CR_ALS_STATUS).get(); - ESP_LOGD(TAG, "LTR303_ALS_STATUS = 0x%02X", als_status.raw); + // ESP_LOGD(TAG, "LTR303_ALS_STATUS = 0x%02X", als_status.raw); if (als_status.new_data) { - ESP_LOGD(TAG, "New data is ready"); + // ESP_LOGD(TAG, "New data is ready"); break; } if (millis() - start > 100) { @@ -72,38 +171,42 @@ bool LTR303Component::read_data_() { ESP_LOGW(TAG, "Data available but not valid"); return false; } + data.gain = als_status.gain; + return true; +} - // finally - uint8_t ch1_0 = this->reg(CommandRegisters::CR_CH1_0).get(); - uint8_t ch1_1 = this->reg(CommandRegisters::CR_CH1_1).get(); - uint8_t ch0_0 = this->reg(CommandRegisters::CR_CH0_0).get(); - uint8_t ch0_1 = this->reg(CommandRegisters::CR_CH0_1).get(); +bool LTR303Component::read_data_() { + ESP_LOGD(TAG, "Reading sensor data"); - this->channel1_ = encode_uint16(ch1_1, ch1_0); - this->channel0_ = encode_uint16(ch0_1, ch0_0); - - ESP_LOGD(TAG, "Channel data CH1 = %d, CH0 = %d", this->channel1_, this->channel0_); - - uint8_t actual_gain = als_status.gain; - if (actual_gain != this->gain_) { - ESP_LOGW(TAG, "Actual gain (%d) differs from requested (%d)", ALS_GAIN[actual_gain], ALS_GAIN[this->gain_]); + Readings data; + data.gain = this->gain_; + data.integration_time = this->integration_time_; + bool ret = this->automatic_mode_enabled_ ? this->read_data_automatic_(data) : this->read_data_manual_(data); + if (ret) { + this->status_clear_warning(); + } else { + this->status_set_warning(); } if (this->infrared_counts_sensor_ != nullptr) { - this->infrared_counts_sensor_->publish_state(this->channel1_); + this->infrared_counts_sensor_->publish_state(data.ch1); } if (this->full_spectrum_counts_sensor_ != nullptr) { - this->full_spectrum_counts_sensor_->publish_state(this->channel0_); + this->full_spectrum_counts_sensor_->publish_state(data.ch0); } if (this->actual_gain_sensor_ != nullptr) { - this->actual_gain_sensor_->publish_state(ALS_GAIN[actual_gain]); + this->actual_gain_sensor_->publish_state(ALS_GAIN[data.gain]); + } + + if (this->actual_integration_time_sensor_ != nullptr) { + this->actual_integration_time_sensor_->publish_state(ALS_INT_TIME[data.integration_time]); } if (this->ambient_light_sensor_ != nullptr) { - float lux = this->calculate_lux_(this->channel0_, this->channel1_, actual_gain, this->integration_time_); - ESP_LOGD(TAG, "Calculated lux: %f", lux); + float lux = this->calculate_lux_(data); + ESP_LOGD(TAG, "Calculated ALS: %f lx", lux); this->ambient_light_sensor_->publish_state(lux); } @@ -137,27 +240,36 @@ void LTR303Component::activate_() { als_ctrl.active_mode = true; als_ctrl.gain = this->gain_; - ESP_LOGD(TAG, "Setting active mode and gain 0x%02X", als_ctrl.raw); + ESP_LOGD(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw); this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; delay(10); uint8_t tries = 10; do { - ESP_LOGD(TAG, "Waiting chip to activate"); + ESP_LOGD(TAG, "Waiting for device to become active..."); delay(5); als_ctrl.raw = this->reg(CommandRegisters::CR_ALS_CTRL).get(); } while (!als_ctrl.active_mode && tries--); // while active mode is not set - keep waiting if (!als_ctrl.active_mode) { - ESP_LOGW(TAG, "Failed to activate chip"); + ESP_LOGW(TAG, "Failed to activate device"); } } -void LTR303Component::configure_() { +void LTR303Component::configure_gain_(Gain gain) { + ControlRegister als_ctrl{0}; + als_ctrl.active_mode = true; + als_ctrl.gain = gain; +// ESP_LOGD(TAG, "Setting gain reg to 0x%02X", als_ctrl.raw); + this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; + delay(10); +} + +void LTR303Component::configure_integration_time_(IntegrationTime time) { MeasurementRateRegister meas{0}; meas.measurement_repeat_rate = this->repeat_rate_; - meas.integration_time = this->integration_time_; - ESP_LOGD(TAG, "Setting integration time and measurement repeat rate 0x%02X", meas.raw); + meas.integration_time = time; +// ESP_LOGD(TAG, "Setting integration time and measurement repeat rate reg to 0x%02X", meas.raw); this->reg(CommandRegisters::CR_MEAS_RATE) = meas.raw; delay(10); } @@ -172,7 +284,7 @@ void LTR303Component::setup() { this->reset_(); this->activate_(); - this->configure_(); + this->configure_integration_time_(this->integration_time_); auto err = this->write(nullptr, 0); if (err != i2c::ERROR_OK) { diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index 4406e2d870..f799a6dab6 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -22,6 +22,39 @@ enum CommandRegisters : uint8_t { CR_ALS_STATUS = 0x8c // ALS new data status }; +// Sensor gain levels +enum Gain : uint8_t { + GAIN_1 = 0, // default + GAIN_2 = 1, + GAIN_4 = 2, + GAIN_8 = 3, + GAIN_48 = 6, + GAIN_96 = 7, +}; +static const uint8_t GAINS_COUNT = 6; + + +enum IntegrationTime : uint8_t { + INTEGRATION_TIME_100MS = 0, // default + INTEGRATION_TIME_50MS = 1, + INTEGRATION_TIME_200MS = 2, + INTEGRATION_TIME_400MS = 3, + INTEGRATION_TIME_150MS = 4, + INTEGRATION_TIME_250MS = 5, + INTEGRATION_TIME_300MS = 6, + INTEGRATION_TIME_350MS = 7 +}; +static const uint8_t TIMES_COUNT = 8; + +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 +}; + #pragma pack(push) #pragma pack(1) // @@ -32,7 +65,7 @@ union ControlRegister { struct { bool active_mode : 1; bool sw_reset : 1; - uint8_t gain : 3; + Gain gain : 3; uint8_t reserved : 3; }; }; @@ -43,8 +76,8 @@ union ControlRegister { union MeasurementRateRegister { uint8_t raw; struct { - uint8_t measurement_repeat_rate : 3; - uint8_t integration_time : 3; + MeasurementRepeatRate measurement_repeat_rate : 3; + IntegrationTime integration_time : 3; bool reserved_6 : 1; bool reserved_7 : 1; }; @@ -60,42 +93,12 @@ union StatusRegister { bool reserved_1 : 1; bool new_data : 1; bool reserved_3 : 1; - uint8_t gain : 3; + Gain gain : 3; bool data_invalid : 1; }; }; #pragma pack(pop) -// Sensor gain levels -enum Gain : uint8_t { - GAIN_1 = 0, // default - GAIN_2 = 1, - GAIN_4 = 2, - GAIN_8 = 3, - GAIN_48 = 6, - GAIN_96 = 7, -}; - -enum IntegrationTime : uint8_t { - INTEGRATION_TIME_100MS = 0, // default - INTEGRATION_TIME_50MS = 1, - INTEGRATION_TIME_200MS = 2, - INTEGRATION_TIME_400MS = 3, - INTEGRATION_TIME_150MS = 4, - INTEGRATION_TIME_250MS = 5, - INTEGRATION_TIME_300MS = 6, - INTEGRATION_TIME_350MS = 7 -}; - -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 -}; - class LTR303Component : public PollingComponent, public i2c::I2CDevice { public: float get_setup_priority() const override { return setup_priority::DATA; } @@ -107,35 +110,49 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { void set_integration_time(IntegrationTime time) { this->integration_time_ = time; } void set_repeat_rate(MeasurementRepeatRate rate) { this->repeat_rate_ = rate; } void set_attenuation_factor(float factor) { this->attenuation_factor_ = factor; } + void set_enable_automatic_mode(bool enable) { this->automatic_mode_enabled_ = enable; } 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; } protected: void reset_(); void activate_(); - void configure_(); + void configure_integration_time_(IntegrationTime time); + void configure_gain_(Gain gain); bool read_data_(); - float calculate_lux_(uint16_t channel0, uint16_t channel1, uint8_t gain, uint8_t time); + struct Readings { + uint16_t ch0{0}; + uint16_t ch1{0}; + Gain gain{Gain::GAIN_1}; + IntegrationTime integration_time{IntegrationTime::INTEGRATION_TIME_100MS}; + }; + + bool read_data_manual_(Readings &data); + bool read_data_automatic_(Readings &data); + bool read_sensor_data_(Readings &data); + bool data_ready_(Readings &data); + + float calculate_lux_(Readings &data); bool reading_data_{false}; - - uint16_t channel0_{0}; - uint16_t channel1_{0}; + bool automatic_mode_enabled_{true}; Gain gain_{Gain::GAIN_1}; IntegrationTime integration_time_{IntegrationTime::INTEGRATION_TIME_100MS}; MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS}; float attenuation_factor_{1.0}; - 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 *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 // enum ErrorCode { NONE = 0, COMMUNICATION_FAILED } error_code_{NONE}; }; diff --git a/esphome/components/ltr303/sensor.py b/esphome/components/ltr303/sensor.py index fbc7073277..80b6b507ed 100644 --- a/esphome/components/ltr303/sensor.py +++ b/esphome/components/ltr303/sensor.py @@ -4,13 +4,16 @@ from esphome.components import i2c, sensor from esphome.const import ( CONF_ID, CONF_ACTUAL_GAIN, + CONF_AUTO_MODE, CONF_GAIN, CONF_GLASS_ATTENUATION_FACTOR, CONF_INTEGRATION_TIME, CONF_REPEAT, UNIT_LUX, + UNIT_MILLISECOND, ICON_BRIGHTNESS_5, ICON_BRIGHTNESS_6, + ICON_TIMER, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, ) @@ -21,6 +24,7 @@ DEPENDENCIES = ["i2c"] UNIT_COUNTS = "#" ICON_GAIN = "mdi:multiplication" ICON_BRIGHTNESS_7 = "mdi:brightness-7" +CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time" CONF_AMBIENT_LIGHT = "ambient_light" CONF_INFRARED_COUNTS = "infrared_counts" CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts" @@ -88,6 +92,7 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(LTR303Component), + cv.Optional(CONF_AUTO_MODE, default=True): cv.boolean, cv.Optional(CONF_GAIN, default="X1"): cv.enum(GAINS, upper=True), cv.Optional( CONF_INTEGRATION_TIME, default="100ms" @@ -123,6 +128,12 @@ CONFIG_SCHEMA = cv.All( device_class=DEVICE_CLASS_ILLUMINANCE, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_ACTUAL_INTEGRATION_TIME): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLISECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), } ) .extend(cv.polling_component_schema("60s")) @@ -132,6 +143,7 @@ CONFIG_SCHEMA = cv.All( CONF_INFRARED_COUNTS, CONF_FULL_SPECTRUM_COUNTS, CONF_ACTUAL_GAIN, + CONF_ACTUAL_INTEGRATION_TIME, ), validate_time_and_repeat_rate, ) @@ -158,6 +170,11 @@ async def to_code(config): 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)) + + cg.add(var.set_enable_automatic_mode(config[CONF_AUTO_MODE])) cg.add(var.set_gain(config[CONF_GAIN])) cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) cg.add(var.set_repeat_rate(config[CONF_REPEAT])) From 8748f97ed3931dbcde53f80ce810d36ce317b2ab Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Fri, 12 Jan 2024 16:15:33 +0000 Subject: [PATCH 06/32] LTR303 auto sensitivity mode tidy --- esphome/components/ltr303/ltr303.cpp | 4 ---- esphome/components/ltr303/ltr303.h | 1 - 2 files changed, 5 deletions(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index e182bed6aa..f63d288b10 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -153,10 +153,8 @@ bool LTR303Component::data_ready_(Readings &data) { const uint32_t start = millis(); while (true) { als_status.raw = this->reg(CommandRegisters::CR_ALS_STATUS).get(); - // ESP_LOGD(TAG, "LTR303_ALS_STATUS = 0x%02X", als_status.raw); if (als_status.new_data) { - // ESP_LOGD(TAG, "New data is ready"); break; } if (millis() - start > 100) { @@ -260,7 +258,6 @@ void LTR303Component::configure_gain_(Gain gain) { ControlRegister als_ctrl{0}; als_ctrl.active_mode = true; als_ctrl.gain = gain; -// ESP_LOGD(TAG, "Setting gain reg to 0x%02X", als_ctrl.raw); this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; delay(10); } @@ -269,7 +266,6 @@ void LTR303Component::configure_integration_time_(IntegrationTime time) { MeasurementRateRegister meas{0}; meas.measurement_repeat_rate = this->repeat_rate_; meas.integration_time = time; -// ESP_LOGD(TAG, "Setting integration time and measurement repeat rate reg to 0x%02X", meas.raw); this->reg(CommandRegisters::CR_MEAS_RATE) = meas.raw; delay(10); } diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index f799a6dab6..8f23553e90 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -33,7 +33,6 @@ enum Gain : uint8_t { }; static const uint8_t GAINS_COUNT = 6; - enum IntegrationTime : uint8_t { INTEGRATION_TIME_100MS = 0, // default INTEGRATION_TIME_50MS = 1, From a8fbbd7e65888bee995b9803af64717c799eac21 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Sat, 20 Jan 2024 16:29:56 +0000 Subject: [PATCH 07/32] LTR303 State machine version --- esphome/components/ltr303/ltr303.cpp | 573 ++++++++++++++------------- esphome/components/ltr303/ltr303.h | 77 ++-- esphome/components/ltr303/sensor.py | 11 +- 3 files changed, 357 insertions(+), 304 deletions(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index f63d288b10..21dedb9beb 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -3,31 +3,320 @@ #include "esphome/core/log.h" #include "esphome/core/helpers.h" +using esphome::i2c::ErrorCode; + namespace esphome { namespace ltr303 { static const char *const TAG = "ltr303"; -static const int ALS_GAIN[8] = {1, 2, 4, 8, 0, 0, 48, 96}; // multiplier -static const int ALS_INT_TIME[8] = {100, 50, 200, 400, 150, 250, 300, 350}; // ms -static const int ALS_MEAS_RATE[8] = {50, 100, 200, 500, 1000, 2000, 2000, 2000}; // ms -float LTR303Component::calculate_lux_(Readings &data) { +static const uint8_t MAX_TRIES = 5; + +template 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 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(IntegrationTime time) { + static const uint16_t ALS_INT_TIME[8] = {100, 50, 200, 400, 150, 250, 300, 350}; + return ALS_INT_TIME[time & 0b111]; +} + +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(Gain gain) { + static const float ALS_GAIN[8] = {1, 2, 4, 8, 0, 0, 48, 96}; + return ALS_GAIN[gain & 0b111]; +} + +void LTR303Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up LTR-303/329"); + // 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 LTR303Component::dump_config() { + LOG_I2C_DEVICE(this); + 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_); + 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-303/329 failed!"); + } +} + +void LTR303Component::update() { + ESP_LOGD(TAG, "Updating"); + if (this->is_ready() && this->state_ == State::IDLE) { + ESP_LOGD(TAG, "Initiating new data collection"); + + this->state_ = this->automatic_mode_enabled_ ? State::COLLECTING_DATA_AUTO : State::WAITING_FOR_DATA; + + this->readings_.ch0 = 0; + this->readings_.ch1 = 0; + this->readings_.actual_gain = this->gain_; + this->readings_.integration_time = this->integration_time_; + this->readings_.lux = 0; + + } else { + ESP_LOGD(TAG, "Component not ready yet"); + } +} + +void LTR303Component::loop() { + ErrorCode err = i2c::ERROR_OK; + static uint8_t tries{0}; + + switch (this->state_) { + case State::DELAYED_SETUP: + this->configure_reset_and_activate_(); + this->configure_integration_time_(this->integration_time_); + err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { + ESP_LOGD(TAG, "i2c connection failed"); + this->mark_failed(); + } + this->state_ = State::IDLE; + break; + + case State::IDLE: + break; + + case State::WAITING_FOR_DATA: + if (this->is_data_ready(this->readings_) == DataAvail::DATA_OK) { + tries = 0; + ESP_LOGD(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", + get_gain_coeff(this->readings_.actual_gain), get_itime_ms(this->readings_.integration_time)); + this->read_sensor_data_(this->readings_); + this->state_ = State::DATA_COLLECTED; + } else if (tries >= MAX_TRIES) { + ESP_LOGW(TAG, "Can't get data after several tries."); + tries = 0; + this->status_set_warning(); + this->state_ = State::IDLE; + return; + } else { + tries++; + } + break; + + case COLLECTING_DATA_AUTO: + case DATA_COLLECTED: + if (this->state_ == State::COLLECTING_DATA_AUTO || this->are_adjustments_required_(this->readings_)) { + this->state_ = State::ADJUSTMENT_IN_PROGRESS; + ESP_LOGD(TAG, "Reconfiguring sensitivity"); + this->configure_integration_time_(this->readings_.integration_time); + this->configure_gain_(this->readings_.actual_gain); + // if sensitivity adjustment needed - need to wait for first data samples after setting new parameters + this->set_timeout(1 * get_meas_time_ms(this->repeat_rate_), + [this]() { this->state_ = State::WAITING_FOR_DATA; }); + } else { + this->apply_lux_calculation_(this->readings_); + 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->state_ = State::IDLE; + this->status_clear_warning(); + this->publish_data_(this->readings_); + break; + + default: + break; + } +} +void LTR303Component::configure_reset_and_activate_() { + ESP_LOGD(TAG, "Resetting"); + + ControlRegister als_ctrl{0}; + als_ctrl.sw_reset = true; + this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; + delay(2); + + uint8_t tries = MAX_TRIES; + do { + ESP_LOGD(TAG, "Waiting chip to reset"); + delay(2); + als_ctrl.raw = this->reg(CommandRegisters::CR_ALS_CTRL).get(); + } while (als_ctrl.sw_reset && tries--); // while sw reset bit is on - keep waiting + + if (als_ctrl.sw_reset) { + ESP_LOGW(TAG, "Failed to finalize reset procedure"); + } + + als_ctrl.sw_reset = false; + als_ctrl.active_mode = true; + als_ctrl.gain = this->gain_; + + ESP_LOGD(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw); + this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; + delay(5); + + tries = MAX_TRIES; + do { + ESP_LOGD(TAG, "Waiting for device to become active..."); + delay(2); + als_ctrl.raw = this->reg(CommandRegisters::CR_ALS_CTRL).get(); + } while (!als_ctrl.active_mode && tries--); // while active mode is not set - keep waiting + + if (!als_ctrl.active_mode) { + ESP_LOGW(TAG, "Failed to activate device"); + } +} + +void LTR303Component::configure_gain_(Gain gain) { + ControlRegister als_ctrl{0}; + als_ctrl.active_mode = true; + als_ctrl.gain = gain; + this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; + delay(2); +} + +void LTR303Component::configure_integration_time_(IntegrationTime time) { + MeasurementRateRegister meas{0}; + meas.measurement_repeat_rate = this->repeat_rate_; + meas.integration_time = time; + this->reg(CommandRegisters::CR_MEAS_RATE) = meas.raw; + delay(2); +} + +DataAvail LTR303Component::is_data_ready(Readings &data) { + StatusRegister als_status{0}; + + als_status.raw = this->reg(CommandRegisters::CR_ALS_STATUS).get(); + if (!als_status.new_data) + return DataAvail::NO_DATA; + + if (als_status.data_invalid) { + ESP_LOGW(TAG, "Data available but not valid"); + return DataAvail::BAD_DATA; + } + + // data.actual_gain = als_status.gain; + ESP_LOGD(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain)); + return DataAvail::DATA_OK; +} + +void LTR303Component::read_sensor_data_(Readings &data) { + uint8_t ch1_0 = this->reg(CommandRegisters::CR_CH1_0).get(); + uint8_t ch1_1 = this->reg(CommandRegisters::CR_CH1_1).get(); + uint8_t ch0_0 = this->reg(CommandRegisters::CR_CH0_0).get(); + uint8_t ch0_1 = this->reg(CommandRegisters::CR_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 LTR303Component::are_adjustments_required_(Readings &data) { + // skip first sample in auto mode - + // we need to reconfigure device after last measurement + if (!this->automatic_mode_enabled_) + return false; + + // Recommended thresholds as per datasheet + static const uint16_t LOW_INTENSITY_THRESHOLD = 1000; + static const uint16_t HIGH_INTENSITY_THRESHOLD = 20000; + static const Gain GAINS[GAINS_COUNT] = {GAIN_1, GAIN_2, GAIN_4, GAIN_8, GAIN_48, GAIN_96}; + static const IntegrationTime INT_TIMES[TIMES_COUNT] = { + INTEGRATION_TIME_50MS, INTEGRATION_TIME_100MS, INTEGRATION_TIME_150MS, INTEGRATION_TIME_200MS, + INTEGRATION_TIME_250MS, INTEGRATION_TIME_300MS, INTEGRATION_TIME_350MS, INTEGRATION_TIME_400MS}; + + if (data.ch0 <= LOW_INTENSITY_THRESHOLD) { + Gain next_gain = get_next(GAINS, data.actual_gain); + if (next_gain != data.actual_gain) { + data.actual_gain = next_gain; + ESP_LOGD(TAG, "Low illuminance. Increasing gain."); + return true; + } + IntegrationTime next_time = get_next(INT_TIMES, data.integration_time); + if (next_time != data.integration_time) { + data.integration_time = next_time; + ESP_LOGD(TAG, "Low illuminance. Increasing integration time."); + return true; + } + } else if (data.ch0 >= HIGH_INTENSITY_THRESHOLD) { + Gain prev_gain = get_prev(GAINS, data.actual_gain); + if (prev_gain != data.actual_gain) { + data.actual_gain = prev_gain; + ESP_LOGD(TAG, "High illuminance. Decreasing gain."); + return true; + } + IntegrationTime prev_time = get_prev(INT_TIMES, data.integration_time); + if (prev_time != data.integration_time) { + data.integration_time = prev_time; + ESP_LOGD(TAG, "High illuminance. Decreasing integration time."); + return true; + } + } else { + ESP_LOGD(TAG, "Illuminance is good enough."); + return false; + } + ESP_LOGD(TAG, "Can't adjust sensitivity anymore."); + return false; +} + +void LTR303Component::apply_lux_calculation_(Readings &data) { if ((data.ch0 == 0xFFFF) || (data.ch1 == 0xFFFF)) { ESP_LOGW(TAG, "Sensors got saturated"); - return 0.0f; + data.lux = 0.0f; + return; } if ((data.ch0 == 0x0000) && (data.ch1 == 0x0000)) { ESP_LOGW(TAG, "Sensors blacked out"); - return 0.0f; + data.lux = 0.0f; + return; } float ch0 = data.ch0; float ch1 = data.ch1; float ratio = ch1 / (ch0 + ch1); - float als_gain = ALS_GAIN[data.gain]; - float als_time = ((float) ALS_INT_TIME[data.integration_time]) / 100.0f; - float inv_pfactor = this->attenuation_factor_; + float als_gain = get_gain_coeff(data.actual_gain); + float als_time = ((float) get_itime_ms(data.integration_time)) / 100.0f; + float inv_pfactor = this->glass_attenuation_factor_; float lux = 0.0f; ESP_LOGD(TAG, "Lux calculation: ratio %f, gain %f, int time %f, inv_pfactor %f", ratio, als_gain, als_time, @@ -44,279 +333,25 @@ float LTR303Component::calculate_lux_(Readings &data) { lux = 0.0f; } lux = inv_pfactor * lux / als_gain / als_time; - return lux; + data.lux = lux; } -bool LTR303Component::read_data_manual_(Readings &data) { - if (!this->read_sensor_data_(data)) - return false; - - if (data.gain != this->gain_) { - ESP_LOGW(TAG, "Actual gain (%d) differs from requested (%d)", ALS_GAIN[data.gain], ALS_GAIN[this->gain_]); +void LTR303Component::publish_data_(Readings &data) { + if (this->ambient_light_sensor_ != nullptr) { + this->ambient_light_sensor_->publish_state(data.lux); } - return true; -} - -static const uint16_t LOW_THRESHOLD = 0.10 * 0xffff; -static const uint16_t HIGH_THRESHOLD = 0.75 * 0xffff; - -bool LTR303Component::read_data_automatic_(Readings &data) { - Gain gains[GAINS_COUNT] = {GAIN_1, GAIN_2, GAIN_4, GAIN_8, GAIN_48, GAIN_96}; - IntegrationTime times[TIMES_COUNT] = {INTEGRATION_TIME_50MS, INTEGRATION_TIME_100MS, INTEGRATION_TIME_150MS, - INTEGRATION_TIME_200MS, INTEGRATION_TIME_250MS, INTEGRATION_TIME_300MS, - INTEGRATION_TIME_350MS, INTEGRATION_TIME_400MS}; - uint8_t gain_idx{0}; - uint8_t time_idx{0}; - - bool readings_are_okay{false}; - static Gain current_gain = this->gain_; - static IntegrationTime current_time = this->integration_time_; - bool wait_needed = false; - do { - ESP_LOGD(TAG, "Auto - gain = %dx, time = %d ms", ALS_GAIN[gains[gain_idx]], ALS_INT_TIME[times[time_idx]]); - - if (current_time != times[time_idx]) { - this->configure_integration_time_(times[time_idx]); - current_time = times[time_idx]; - wait_needed = true; - } - - if (current_gain != gains[gain_idx]) { - this->configure_gain_(gains[gain_idx]); - current_gain = gains[gain_idx]; - wait_needed = true; - } - // wait for new data samples to come - // delay(ALS_INT_TIME[current_time] * 2); - if (wait_needed) { - delay(ALS_MEAS_RATE[this->repeat_rate_]); - App.feed_wdt(); - } - - if (!this->read_sensor_data_(data)) - return false; - - if (data.ch0 < LOW_THRESHOLD) { - // low ? time+ -> gain+ - if (time_idx < TIMES_COUNT - 1) { - ESP_LOGD(TAG, "Low illuminance. Increasing int.time"); - time_idx++; - } else if (gain_idx < GAINS_COUNT - 1) { - ESP_LOGD(TAG, "Low illuminance. Increasing gain"); - gain_idx++; - } else { - ESP_LOGD(TAG, "Low illuminance. We will live with it"); - readings_are_okay = true; - } - } else if (data.ch0 > HIGH_THRESHOLD) { - // high ? gain- -> time- - if (gain_idx > 0) { - ESP_LOGD(TAG, "High illuminance. Decreasing gain"); - gain_idx--; - } else if (time_idx > 0) { - ESP_LOGD(TAG, "High illuminance. Decreasing int.time"); - time_idx--; - } else { - ESP_LOGD(TAG, "High illuminance. We will live with it"); - readings_are_okay = true; - } - } else { - ESP_LOGD(TAG, "Illiminance is good enough"); - readings_are_okay = true; - } - } while (!readings_are_okay); - - data.gain = gains[gain_idx]; - data.integration_time = times[time_idx]; - - return true; -} - -bool LTR303Component::read_sensor_data_(Readings &data) { - if (!this->data_ready_(data)) - return false; - - uint8_t ch1_0 = this->reg(CommandRegisters::CR_CH1_0).get(); - uint8_t ch1_1 = this->reg(CommandRegisters::CR_CH1_1).get(); - uint8_t ch0_0 = this->reg(CommandRegisters::CR_CH0_0).get(); - uint8_t ch0_1 = this->reg(CommandRegisters::CR_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); - return true; -} - -bool LTR303Component::data_ready_(Readings &data) { - StatusRegister als_status{0}; - - const uint32_t start = millis(); - while (true) { - als_status.raw = this->reg(CommandRegisters::CR_ALS_STATUS).get(); - - if (als_status.new_data) { - break; - } - if (millis() - start > 100) { - ESP_LOGW(TAG, "No data returned from sensor"); - return false; - } - ESP_LOGW(TAG, "Waiting for data"); - delay(5); - } - - if (als_status.data_invalid) { - ESP_LOGW(TAG, "Data available but not valid"); - return false; - } - data.gain = als_status.gain; - return true; -} - -bool LTR303Component::read_data_() { - ESP_LOGD(TAG, "Reading sensor data"); - - Readings data; - data.gain = this->gain_; - data.integration_time = this->integration_time_; - bool ret = this->automatic_mode_enabled_ ? this->read_data_automatic_(data) : this->read_data_manual_(data); - if (ret) { - this->status_clear_warning(); - } else { - this->status_set_warning(); - } - 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); } - if (this->actual_gain_sensor_ != nullptr) { - this->actual_gain_sensor_->publish_state(ALS_GAIN[data.gain]); + this->actual_gain_sensor_->publish_state(get_gain_coeff(data.actual_gain)); } - if (this->actual_integration_time_sensor_ != nullptr) { - this->actual_integration_time_sensor_->publish_state(ALS_INT_TIME[data.integration_time]); - } - - if (this->ambient_light_sensor_ != nullptr) { - float lux = this->calculate_lux_(data); - ESP_LOGD(TAG, "Calculated ALS: %f lx", lux); - this->ambient_light_sensor_->publish_state(lux); - } - - return true; -} - -void LTR303Component::reset_() { - ESP_LOGD(TAG, "Resetting"); - - ControlRegister als_ctrl{0}; - als_ctrl.sw_reset = true; - this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; - delay(10); - - uint8_t tries = 10; - do { - ESP_LOGD(TAG, "Waiting chip to reset"); - delay(5); - als_ctrl.raw = this->reg(CommandRegisters::CR_ALS_CTRL).get(); - } while (als_ctrl.sw_reset && tries--); // while sw reset bit is on - keep waiting - - if (als_ctrl.sw_reset) { - ESP_LOGW(TAG, "Failed to finalize reset procedure"); + this->actual_integration_time_sensor_->publish_state(get_itime_ms(data.integration_time)); } } - -void LTR303Component::activate_() { - ESP_LOGD(TAG, "Activating"); - - ControlRegister als_ctrl{0}; - als_ctrl.active_mode = true; - als_ctrl.gain = this->gain_; - - ESP_LOGD(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw); - this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; - delay(10); - - uint8_t tries = 10; - do { - ESP_LOGD(TAG, "Waiting for device to become active..."); - delay(5); - als_ctrl.raw = this->reg(CommandRegisters::CR_ALS_CTRL).get(); - } while (!als_ctrl.active_mode && tries--); // while active mode is not set - keep waiting - - if (!als_ctrl.active_mode) { - ESP_LOGW(TAG, "Failed to activate device"); - } -} - -void LTR303Component::configure_gain_(Gain gain) { - ControlRegister als_ctrl{0}; - als_ctrl.active_mode = true; - als_ctrl.gain = gain; - this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; - delay(10); -} - -void LTR303Component::configure_integration_time_(IntegrationTime time) { - MeasurementRateRegister meas{0}; - meas.measurement_repeat_rate = this->repeat_rate_; - meas.integration_time = time; - this->reg(CommandRegisters::CR_MEAS_RATE) = meas.raw; - delay(10); -} - -void LTR303Component::setup() { - ESP_LOGCONFIG(TAG, "Setting up LTR-303/329"); - - // As per datasheet we need to wait at least 100ms after power on to get ALS chip responsive - // It' only done once during setup phase - delay(100); // NOLINT - App.feed_wdt(); - - this->reset_(); - this->activate_(); - this->configure_integration_time_(this->integration_time_); - - auto err = this->write(nullptr, 0); - if (err != i2c::ERROR_OK) { - ESP_LOGD(TAG, "i2c connection failed"); - this->mark_failed(); - return; - } -} - -void LTR303Component::dump_config() { - LOG_I2C_DEVICE(this); - ESP_LOGCONFIG(TAG, " Gain: x%d", ALS_GAIN[this->gain_]); - ESP_LOGCONFIG(TAG, " Integration time: %d ms", ALS_INT_TIME[this->integration_time_]); - ESP_LOGCONFIG(TAG, " Measurement repeat rate: %d ms", ALS_MEAS_RATE[this->repeat_rate_]); - ESP_LOGCONFIG(TAG, " Attenuation factor: %f", this->attenuation_factor_); - 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-303/329 failed!"); - } -} - -void LTR303Component::update() { - ESP_LOGD(TAG, "Updating"); - - if (this->is_ready() && !this->reading_data_) { - this->reading_data_ = true; - this->read_data_(); - this->reading_data_ = false; - } -} - } // namespace ltr303 } // namespace esphome diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index 8f23553e90..45ed55223a 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -54,8 +54,6 @@ enum MeasurementRepeatRate { REPEAT_RATE_2000MS = 5 }; -#pragma pack(push) -#pragma pack(1) // // ALS_CONTR Register (0x80) // @@ -66,7 +64,7 @@ union ControlRegister { bool sw_reset : 1; Gain gain : 3; uint8_t reserved : 3; - }; + } __attribute__((packed)); }; // @@ -79,7 +77,7 @@ union MeasurementRateRegister { IntegrationTime integration_time : 3; bool reserved_6 : 1; bool reserved_7 : 1; - }; + } __attribute__((packed)); }; // @@ -94,21 +92,29 @@ union StatusRegister { bool reserved_3 : 1; Gain gain : 3; bool data_invalid : 1; - }; + } __attribute__((packed)); }; -#pragma pack(pop) + +enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK }; class LTR303Component : 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 + // void set_gain(Gain gain) { this->gain_ = gain; } void set_integration_time(IntegrationTime time) { this->integration_time_ = time; } void set_repeat_rate(MeasurementRepeatRate rate) { this->repeat_rate_ = rate; } - void set_attenuation_factor(float factor) { this->attenuation_factor_ = factor; } + void set_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; } void set_enable_automatic_mode(bool enable) { this->automatic_mode_enabled_ = enable; } void set_ambient_light_sensor(sensor::Sensor *sensor) { this->ambient_light_sensor_ = sensor; } @@ -118,42 +124,61 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { void set_actual_integration_time_sensor(sensor::Sensor *sensor) { this->actual_integration_time_sensor_ = sensor; } protected: - void reset_(); - void activate_(); - void configure_integration_time_(IntegrationTime time); - void configure_gain_(Gain gain); - - bool read_data_(); + // + // Internal state machine, used to split all the actions into + // small steps in loop() to make sure we are not blocking execution + // + enum State : uint8_t { + NOT_INITIALIZED, + DELAYED_SETUP, + IDLE, + WAITING_FOR_DATA, + COLLECTING_DATA_AUTO, + DATA_COLLECTED, + ADJUSTMENT_IN_PROGRESS, + READY_TO_PUBLISH + } state_{State::NOT_INITIALIZED}; + // + // Current measurements data + // struct Readings { uint16_t ch0{0}; uint16_t ch1{0}; - Gain gain{Gain::GAIN_1}; + Gain actual_gain{Gain::GAIN_1}; IntegrationTime integration_time{IntegrationTime::INTEGRATION_TIME_100MS}; - }; + float lux{0.0f}; + } readings_; - bool read_data_manual_(Readings &data); - bool read_data_automatic_(Readings &data); - bool read_sensor_data_(Readings &data); - bool data_ready_(Readings &data); + // + // Device interaction and data manipulation + // + void configure_reset_and_activate_(); + void configure_integration_time_(IntegrationTime time); + void configure_gain_(Gain gain); + DataAvail is_data_ready(Readings &data); + void read_sensor_data_(Readings &data); + bool are_adjustments_required_(Readings &data); + void apply_lux_calculation_(Readings &data); + void publish_data_(Readings &data); - float calculate_lux_(Readings &data); - - bool reading_data_{false}; + // + // Component configuration + // bool automatic_mode_enabled_{true}; - Gain gain_{Gain::GAIN_1}; IntegrationTime integration_time_{IntegrationTime::INTEGRATION_TIME_100MS}; MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS}; - float attenuation_factor_{1.0}; + float glass_attenuation_factor_{1.0}; + // + // 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 - - // enum ErrorCode { NONE = 0, COMMUNICATION_FAILED } error_code_{NONE}; }; } // namespace ltr303 diff --git a/esphome/components/ltr303/sensor.py b/esphome/components/ltr303/sensor.py index 80b6b507ed..e1b783b77a 100644 --- a/esphome/components/ltr303/sensor.py +++ b/esphome/components/ltr303/sensor.py @@ -93,7 +93,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(LTR303Component), cv.Optional(CONF_AUTO_MODE, default=True): cv.boolean, - cv.Optional(CONF_GAIN, default="X1"): cv.enum(GAINS, upper=True), + cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAINS, upper=True), cv.Optional( CONF_INTEGRATION_TIME, default="100ms" ): validate_integration_time, @@ -138,13 +138,6 @@ CONFIG_SCHEMA = cv.All( ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x29)), - cv.has_at_least_one_key( - CONF_AMBIENT_LIGHT, - CONF_INFRARED_COUNTS, - CONF_FULL_SPECTRUM_COUNTS, - CONF_ACTUAL_GAIN, - CONF_ACTUAL_INTEGRATION_TIME, - ), validate_time_and_repeat_rate, ) @@ -178,4 +171,4 @@ async def to_code(config): cg.add(var.set_gain(config[CONF_GAIN])) cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) cg.add(var.set_repeat_rate(config[CONF_REPEAT])) - cg.add(var.set_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) + cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) From 7ae8cd8ff2863e6b6036f1ae4866e380ed31c3d6 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Sat, 20 Jan 2024 17:22:29 +0000 Subject: [PATCH 08/32] LTR303 name fix --- esphome/components/ltr303/ltr303.cpp | 4 ++-- esphome/components/ltr303/ltr303.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index 21dedb9beb..c24ff03f0a 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -120,7 +120,7 @@ void LTR303Component::loop() { break; case State::WAITING_FOR_DATA: - if (this->is_data_ready(this->readings_) == DataAvail::DATA_OK) { + if (this->is_data_ready_(this->readings_) == DataAvail::DATA_OK) { tries = 0; ESP_LOGD(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", get_gain_coeff(this->readings_.actual_gain), get_itime_ms(this->readings_.integration_time)); @@ -222,7 +222,7 @@ void LTR303Component::configure_integration_time_(IntegrationTime time) { delay(2); } -DataAvail LTR303Component::is_data_ready(Readings &data) { +DataAvail LTR303Component::is_data_ready_(Readings &data) { StatusRegister als_status{0}; als_status.raw = this->reg(CommandRegisters::CR_ALS_STATUS).get(); diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index 45ed55223a..33f9e030cd 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -156,7 +156,7 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { void configure_reset_and_activate_(); void configure_integration_time_(IntegrationTime time); void configure_gain_(Gain gain); - DataAvail is_data_ready(Readings &data); + DataAvail is_data_ready_(Readings &data); void read_sensor_data_(Readings &data); bool are_adjustments_required_(Readings &data); void apply_lux_calculation_(Readings &data); From 5cca7db1e39aca6f712fa3d47eb3dd3dfe3a28ed Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Thu, 25 Jan 2024 00:16:02 +0000 Subject: [PATCH 09/32] publish split --- esphome/components/ltr303/ltr303.cpp | 19 ++++++++++++++----- esphome/components/ltr303/ltr303.h | 8 +++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index c24ff03f0a..c0a108fcd8 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -117,6 +117,7 @@ void LTR303Component::loop() { break; case State::IDLE: + // having fun, waiting for work break; case State::WAITING_FOR_DATA: @@ -137,8 +138,8 @@ void LTR303Component::loop() { } break; - case COLLECTING_DATA_AUTO: - case DATA_COLLECTED: + case State::COLLECTING_DATA_AUTO: + case State::DATA_COLLECTED: if (this->state_ == State::COLLECTING_DATA_AUTO || this->are_adjustments_required_(this->readings_)) { this->state_ = State::ADJUSTMENT_IN_PROGRESS; ESP_LOGD(TAG, "Reconfiguring sensitivity"); @@ -158,9 +159,14 @@ void LTR303Component::loop() { break; case State::READY_TO_PUBLISH: - this->state_ = State::IDLE; + this->publish_data_part_1_(this->readings_); + this->state_ = State::KEEP_PUBLISHING; + break; + + case State::KEEP_PUBLISHING: + this->publish_data_part_2_(this->readings_); this->status_clear_warning(); - this->publish_data_(this->readings_); + this->state_ = State::IDLE; break; default: @@ -336,7 +342,7 @@ void LTR303Component::apply_lux_calculation_(Readings &data) { data.lux = lux; } -void LTR303Component::publish_data_(Readings &data) { +void LTR303Component::publish_data_part_1_(Readings &data) { if (this->ambient_light_sensor_ != nullptr) { this->ambient_light_sensor_->publish_state(data.lux); } @@ -346,6 +352,9 @@ void LTR303Component::publish_data_(Readings &data) { if (this->full_spectrum_counts_sensor_ != nullptr) { this->full_spectrum_counts_sensor_->publish_state(data.ch0); } +} + +void LTR303Component::publish_data_part_2_(Readings &data) { if (this->actual_gain_sensor_ != nullptr) { this->actual_gain_sensor_->publish_state(get_gain_coeff(data.actual_gain)); } diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index 33f9e030cd..5b3e660ae8 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -128,7 +128,7 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { // Internal state machine, used to split all the actions into // small steps in loop() to make sure we are not blocking execution // - enum State : uint8_t { + enum class State : uint8_t { NOT_INITIALIZED, DELAYED_SETUP, IDLE, @@ -136,7 +136,8 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { COLLECTING_DATA_AUTO, DATA_COLLECTED, ADJUSTMENT_IN_PROGRESS, - READY_TO_PUBLISH + READY_TO_PUBLISH, + KEEP_PUBLISHING } state_{State::NOT_INITIALIZED}; // @@ -160,7 +161,8 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { void read_sensor_data_(Readings &data); bool are_adjustments_required_(Readings &data); void apply_lux_calculation_(Readings &data); - void publish_data_(Readings &data); + void publish_data_part_1_(Readings &data); + void publish_data_part_2_(Readings &data); // // Component configuration From b6c03eefde140e10cc115ee544c109cf88a4ab3d Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Thu, 25 Jan 2024 00:33:49 +0000 Subject: [PATCH 10/32] minor --- esphome/components/ltr303/ltr303.cpp | 22 +++++++++++----------- esphome/components/ltr303/ltr303.h | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index c0a108fcd8..b0b0a26716 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -178,14 +178,14 @@ void LTR303Component::configure_reset_and_activate_() { ControlRegister als_ctrl{0}; als_ctrl.sw_reset = true; - this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; + this->reg((uint8_t) CommandRegisters::ALS_CTRL) = als_ctrl.raw; delay(2); uint8_t tries = MAX_TRIES; do { ESP_LOGD(TAG, "Waiting chip to reset"); delay(2); - als_ctrl.raw = this->reg(CommandRegisters::CR_ALS_CTRL).get(); + als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CTRL).get(); } while (als_ctrl.sw_reset && tries--); // while sw reset bit is on - keep waiting if (als_ctrl.sw_reset) { @@ -197,14 +197,14 @@ void LTR303Component::configure_reset_and_activate_() { als_ctrl.gain = this->gain_; ESP_LOGD(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw); - this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; + this->reg((uint8_t) CommandRegisters::ALS_CTRL) = als_ctrl.raw; delay(5); tries = MAX_TRIES; do { ESP_LOGD(TAG, "Waiting for device to become active..."); delay(2); - als_ctrl.raw = this->reg(CommandRegisters::CR_ALS_CTRL).get(); + als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CTRL).get(); } while (!als_ctrl.active_mode && tries--); // while active mode is not set - keep waiting if (!als_ctrl.active_mode) { @@ -216,7 +216,7 @@ void LTR303Component::configure_gain_(Gain gain) { ControlRegister als_ctrl{0}; als_ctrl.active_mode = true; als_ctrl.gain = gain; - this->reg(CommandRegisters::CR_ALS_CTRL) = als_ctrl.raw; + this->reg((uint8_t) CommandRegisters::ALS_CTRL) = als_ctrl.raw; delay(2); } @@ -224,14 +224,14 @@ void LTR303Component::configure_integration_time_(IntegrationTime time) { MeasurementRateRegister meas{0}; meas.measurement_repeat_rate = this->repeat_rate_; meas.integration_time = time; - this->reg(CommandRegisters::CR_MEAS_RATE) = meas.raw; + this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw; delay(2); } DataAvail LTR303Component::is_data_ready_(Readings &data) { StatusRegister als_status{0}; - als_status.raw = this->reg(CommandRegisters::CR_ALS_STATUS).get(); + als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_STATUS).get(); if (!als_status.new_data) return DataAvail::NO_DATA; @@ -246,10 +246,10 @@ DataAvail LTR303Component::is_data_ready_(Readings &data) { } void LTR303Component::read_sensor_data_(Readings &data) { - uint8_t ch1_0 = this->reg(CommandRegisters::CR_CH1_0).get(); - uint8_t ch1_1 = this->reg(CommandRegisters::CR_CH1_1).get(); - uint8_t ch0_0 = this->reg(CommandRegisters::CR_CH0_0).get(); - uint8_t ch0_1 = this->reg(CommandRegisters::CR_CH0_1).get(); + uint8_t ch1_0 = this->reg((uint8_t) CommandRegisters::CH1_0).get(); + uint8_t ch1_1 = this->reg((uint8_t) CommandRegisters::CH1_1).get(); + uint8_t ch0_0 = this->reg((uint8_t) CommandRegisters::CH0_0).get(); + uint8_t ch0_1 = this->reg((uint8_t) CommandRegisters::CH0_1).get(); data.ch1 = encode_uint16(ch1_1, ch1_0); data.ch0 = encode_uint16(ch0_1, ch0_0); diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index 5b3e660ae8..5a99e26177 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -10,16 +10,16 @@ namespace ltr303 { // https://www.mouser.com/datasheet/2/239/Lite-On_LTR-303ALS-01_DS_ver%201.1-1175269.pdf -enum CommandRegisters : uint8_t { - CR_ALS_CTRL = 0x80, // ALS operation mode control SW reset - CR_MEAS_RATE = 0x85, // ALS measurement rate in active mode - CR_PART_ID = 0x86, // Part Number ID and Revision ID - CR_MANU_ID = 0x87, // Manufacturer ID - CR_CH1_0 = 0x88, // ALS measurement CH1 data, lower byte - infrared only - CR_CH1_1 = 0x89, // ALS measurement CH1 data, upper byte - infrared only - CR_CH0_0 = 0x8A, // ALS measurement CH0 data, lower byte - visible + infrared - CR_CH0_1 = 0x8B, // ALS measurement CH0 data, upper byte - visible + infrared - CR_ALS_STATUS = 0x8c // ALS new data status +enum class CommandRegisters : uint8_t { + ALS_CTRL = 0x80, // ALS operation mode control SW reset + MEAS_RATE = 0x85, // ALS measurement rate in active mode + PART_ID = 0x86, // Part Number ID and Revision ID + MANU_ID = 0x87, // Manufacturer ID + CH1_0 = 0x88, // ALS measurement CH1 data, lower byte - infrared only + CH1_1 = 0x89, // ALS measurement CH1 data, upper byte - infrared only + CH0_0 = 0x8A, // ALS measurement CH0 data, lower byte - visible + infrared + CH0_1 = 0x8B, // ALS measurement CH0 data, upper byte - visible + infrared + ALS_STATUS = 0x8c // ALS new data status }; // Sensor gain levels From 12b097405527921166b5bb408df7345404095c7f Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Thu, 25 Jan 2024 18:56:10 +0000 Subject: [PATCH 11/32] new definitions for LTR --- esphome/components/ltr303/ltr-definitions.h | 271 ++++++++++++++++++++ esphome/components/ltr303/ltr303.cpp | 34 +-- esphome/components/ltr303/ltr303.h | 97 +------ 3 files changed, 294 insertions(+), 108 deletions(-) create mode 100644 esphome/components/ltr303/ltr-definitions.h diff --git a/esphome/components/ltr303/ltr-definitions.h b/esphome/components/ltr303/ltr-definitions.h new file mode 100644 index 0000000000..4d77264d11 --- /dev/null +++ b/esphome/components/ltr303/ltr-definitions.h @@ -0,0 +1,271 @@ +#pragma once + +#include + +namespace esphome { +namespace ltr303 { + +enum class CommandRegisters : uint8_t { + ALS_CONTR = 0x80, // ALS operation mode control 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 + MANU_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 + 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 + INTERRUPT_PERSIST = 0x9E // Interrupt persistence filter +}; + +// ALS Sensor gain levels +enum AlsGain : uint8_t { + GAIN_1 = 0, // default + GAIN_2 = 1, + GAIN_4 = 2, + GAIN_8 = 3, + GAIN_48 = 6, + GAIN_96 = 7, +}; +static const uint8_t GAINS_COUNT = 6; + +// ALS Sensor integration times +enum IntegrationTime : uint8_t { + INTEGRATION_TIME_100MS = 0, // default + INTEGRATION_TIME_50MS = 1, + INTEGRATION_TIME_200MS = 2, + INTEGRATION_TIME_400MS = 3, + INTEGRATION_TIME_150MS = 4, + INTEGRATION_TIME_250MS = 5, + INTEGRATION_TIME_300MS = 6, + INTEGRATION_TIME_350MS = 7 +}; +static const uint8_t TIMES_COUNT = 8; + +// 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 PsGain : uint8_t { + PS_GAIN_16 = 0, // default + PS_GAIN_32 = 2, + PS_GAIN_64 = 3, +}; + +// PS Mode +enum PsMode : uint8_t { + PS_MODE_STANDBY_00 = 0, // default + PS_MODE_STANDBY_01 = 1, + PS_MODE_ACTIVE_10 = 2, + PS_MODE_ACTIVE_11 = 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, + PS_LED_DUTY_75 = 2, + PS_LED_DUTY_100 = 3, // default +}; + +// 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, + PS_LED_CURRENT_100MA = 4, // default + 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, + PS_MEAS_RATE_200MS = 3, + PS_MEAS_RATE_500MS = 4, // default + PS_MEAS_RATE_1000MS = 5, + PS_MEAS_RATE_2000MS = 6, + PS_MEAS_RATE_2000MS1 = 7, + PS_MEAS_RATE_10MS = 8, +}; + +// +// ALS_CONTR Register (0x80) +// +union ControlRegister { + uint8_t raw; + struct { + bool active_mode : 1; + bool sw_reset : 1; + AlsGain gain : 3; + uint8_t reserved : 3; + } __attribute__((packed)); +}; + +// +// PS_CONTR Register (0x81) +// +union PsControlRegister { + uint8_t raw; + struct { + bool ps_mode_xxx : 1; + bool ps_mode_active : 1; + PsGain ps_gain : 2; + bool reserved_4 : 1; + bool ps_saturation_indicator_enable : 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 PsNPulsesRegister { + uint8_t raw; + struct { + uint8_t number_of_pulses : 4; + uint8_t reserved : 4; + } __attribute__((packed)); +}; + +// +// 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 MeasurementRateRegister { + uint8_t raw; + struct { + MeasurementRepeatRate measurement_repeat_rate : 3; + IntegrationTime integration_time : 3; + 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 + AlsGain gain : 3; // current ALS gain + bool data_invalid : 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 ltr303 +} // namespace esphome diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index b0b0a26716..effe1c707a 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -52,7 +52,7 @@ static uint16_t get_meas_time_ms(MeasurementRepeatRate rate) { return ALS_MEAS_RATE[rate & 0b111]; } -static float get_gain_coeff(Gain gain) { +static float get_gain_coeff(AlsGain gain) { static const float ALS_GAIN[8] = {1, 2, 4, 8, 0, 0, 48, 96}; return ALS_GAIN[gain & 0b111]; } @@ -178,14 +178,14 @@ void LTR303Component::configure_reset_and_activate_() { ControlRegister als_ctrl{0}; als_ctrl.sw_reset = true; - this->reg((uint8_t) CommandRegisters::ALS_CTRL) = als_ctrl.raw; + this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw; delay(2); uint8_t tries = MAX_TRIES; do { ESP_LOGD(TAG, "Waiting chip to reset"); delay(2); - als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CTRL).get(); + 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) { @@ -197,14 +197,14 @@ void LTR303Component::configure_reset_and_activate_() { als_ctrl.gain = this->gain_; ESP_LOGD(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw); - this->reg((uint8_t) CommandRegisters::ALS_CTRL) = als_ctrl.raw; + this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw; delay(5); tries = MAX_TRIES; do { ESP_LOGD(TAG, "Waiting for device to become active..."); delay(2); - als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CTRL).get(); + als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get(); } while (!als_ctrl.active_mode && tries--); // while active mode is not set - keep waiting if (!als_ctrl.active_mode) { @@ -212,11 +212,11 @@ void LTR303Component::configure_reset_and_activate_() { } } -void LTR303Component::configure_gain_(Gain gain) { +void LTR303Component::configure_gain_(AlsGain gain) { ControlRegister als_ctrl{0}; als_ctrl.active_mode = true; als_ctrl.gain = gain; - this->reg((uint8_t) CommandRegisters::ALS_CTRL) = als_ctrl.raw; + this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw; delay(2); } @@ -229,10 +229,10 @@ void LTR303Component::configure_integration_time_(IntegrationTime time) { } DataAvail LTR303Component::is_data_ready_(Readings &data) { - StatusRegister als_status{0}; + AlsPsStatusRegister als_status{0}; - als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_STATUS).get(); - if (!als_status.new_data) + als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get(); + if (!als_status.als_new_data) return DataAvail::NO_DATA; if (als_status.data_invalid) { @@ -246,10 +246,10 @@ DataAvail LTR303Component::is_data_ready_(Readings &data) { } void LTR303Component::read_sensor_data_(Readings &data) { - uint8_t ch1_0 = this->reg((uint8_t) CommandRegisters::CH1_0).get(); - uint8_t ch1_1 = this->reg((uint8_t) CommandRegisters::CH1_1).get(); - uint8_t ch0_0 = this->reg((uint8_t) CommandRegisters::CH0_0).get(); - uint8_t ch0_1 = this->reg((uint8_t) CommandRegisters::CH0_1).get(); + 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); @@ -265,13 +265,13 @@ bool LTR303Component::are_adjustments_required_(Readings &data) { // Recommended thresholds as per datasheet static const uint16_t LOW_INTENSITY_THRESHOLD = 1000; static const uint16_t HIGH_INTENSITY_THRESHOLD = 20000; - static const Gain GAINS[GAINS_COUNT] = {GAIN_1, GAIN_2, GAIN_4, GAIN_8, GAIN_48, GAIN_96}; + static const AlsGain GAINS[GAINS_COUNT] = {GAIN_1, GAIN_2, GAIN_4, GAIN_8, GAIN_48, GAIN_96}; static const IntegrationTime INT_TIMES[TIMES_COUNT] = { INTEGRATION_TIME_50MS, INTEGRATION_TIME_100MS, INTEGRATION_TIME_150MS, INTEGRATION_TIME_200MS, INTEGRATION_TIME_250MS, INTEGRATION_TIME_300MS, INTEGRATION_TIME_350MS, INTEGRATION_TIME_400MS}; if (data.ch0 <= LOW_INTENSITY_THRESHOLD) { - Gain next_gain = get_next(GAINS, data.actual_gain); + AlsGain next_gain = get_next(GAINS, data.actual_gain); if (next_gain != data.actual_gain) { data.actual_gain = next_gain; ESP_LOGD(TAG, "Low illuminance. Increasing gain."); @@ -284,7 +284,7 @@ bool LTR303Component::are_adjustments_required_(Readings &data) { return true; } } else if (data.ch0 >= HIGH_INTENSITY_THRESHOLD) { - Gain prev_gain = get_prev(GAINS, data.actual_gain); + AlsGain prev_gain = get_prev(GAINS, data.actual_gain); if (prev_gain != data.actual_gain) { data.actual_gain = prev_gain; ESP_LOGD(TAG, "High illuminance. Decreasing gain."); diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index 5a99e26177..3e51f4bddb 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -5,96 +5,11 @@ #include "esphome/core/component.h" #include "esphome/core/optional.h" +#include "ltr-definitions.h" + namespace esphome { namespace ltr303 { -// https://www.mouser.com/datasheet/2/239/Lite-On_LTR-303ALS-01_DS_ver%201.1-1175269.pdf - -enum class CommandRegisters : uint8_t { - ALS_CTRL = 0x80, // ALS operation mode control SW reset - MEAS_RATE = 0x85, // ALS measurement rate in active mode - PART_ID = 0x86, // Part Number ID and Revision ID - MANU_ID = 0x87, // Manufacturer ID - CH1_0 = 0x88, // ALS measurement CH1 data, lower byte - infrared only - CH1_1 = 0x89, // ALS measurement CH1 data, upper byte - infrared only - CH0_0 = 0x8A, // ALS measurement CH0 data, lower byte - visible + infrared - CH0_1 = 0x8B, // ALS measurement CH0 data, upper byte - visible + infrared - ALS_STATUS = 0x8c // ALS new data status -}; - -// Sensor gain levels -enum Gain : uint8_t { - GAIN_1 = 0, // default - GAIN_2 = 1, - GAIN_4 = 2, - GAIN_8 = 3, - GAIN_48 = 6, - GAIN_96 = 7, -}; -static const uint8_t GAINS_COUNT = 6; - -enum IntegrationTime : uint8_t { - INTEGRATION_TIME_100MS = 0, // default - INTEGRATION_TIME_50MS = 1, - INTEGRATION_TIME_200MS = 2, - INTEGRATION_TIME_400MS = 3, - INTEGRATION_TIME_150MS = 4, - INTEGRATION_TIME_250MS = 5, - INTEGRATION_TIME_300MS = 6, - INTEGRATION_TIME_350MS = 7 -}; -static const uint8_t TIMES_COUNT = 8; - -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 -}; - -// -// ALS_CONTR Register (0x80) -// -union ControlRegister { - uint8_t raw; - struct { - bool active_mode : 1; - bool sw_reset : 1; - Gain gain : 3; - uint8_t reserved : 3; - } __attribute__((packed)); -}; - -// -// ALS_MEAS_RATE Register (0x85) -// -union MeasurementRateRegister { - uint8_t raw; - struct { - MeasurementRepeatRate measurement_repeat_rate : 3; - IntegrationTime integration_time : 3; - bool reserved_6 : 1; - bool reserved_7 : 1; - } __attribute__((packed)); -}; - -// -// ALS_STATUS Register (0x8C) (Read Only) -// -union StatusRegister { - uint8_t raw; - struct { - bool reserved_0 : 1; - bool reserved_1 : 1; - bool new_data : 1; - bool reserved_3 : 1; - Gain gain : 3; - bool data_invalid : 1; - } __attribute__((packed)); -}; - enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK }; class LTR303Component : public PollingComponent, public i2c::I2CDevice { @@ -111,7 +26,7 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { // // Configuration setters // - void set_gain(Gain gain) { this->gain_ = gain; } + void set_gain(AlsGain gain) { this->gain_ = gain; } void set_integration_time(IntegrationTime time) { this->integration_time_ = time; } void set_repeat_rate(MeasurementRepeatRate rate) { this->repeat_rate_ = rate; } void set_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; } @@ -146,7 +61,7 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { struct Readings { uint16_t ch0{0}; uint16_t ch1{0}; - Gain actual_gain{Gain::GAIN_1}; + AlsGain actual_gain{AlsGain::GAIN_1}; IntegrationTime integration_time{IntegrationTime::INTEGRATION_TIME_100MS}; float lux{0.0f}; } readings_; @@ -156,7 +71,7 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { // void configure_reset_and_activate_(); void configure_integration_time_(IntegrationTime time); - void configure_gain_(Gain gain); + void configure_gain_(AlsGain gain); DataAvail is_data_ready_(Readings &data); void read_sensor_data_(Readings &data); bool are_adjustments_required_(Readings &data); @@ -168,7 +83,7 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { // Component configuration // bool automatic_mode_enabled_{true}; - Gain gain_{Gain::GAIN_1}; + AlsGain gain_{AlsGain::GAIN_1}; IntegrationTime integration_time_{IntegrationTime::INTEGRATION_TIME_100MS}; MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS}; float glass_attenuation_factor_{1.0}; From 58a8b01f2cb45df4054244ca72c316c2bde21cbd Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Fri, 26 Jan 2024 13:52:29 +0000 Subject: [PATCH 12/32] als-ps test --- esphome/components/ltr303/ltr-definitions.h | 6 +- esphome/components/ltr303/ltr303.cpp | 77 +++++++++++++++++++-- esphome/components/ltr303/ltr303.h | 18 +++++ esphome/components/ltr303/sensor.py | 19 ++++- 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/esphome/components/ltr303/ltr-definitions.h b/esphome/components/ltr303/ltr-definitions.h index 4d77264d11..9c6722b42d 100644 --- a/esphome/components/ltr303/ltr-definitions.h +++ b/esphome/components/ltr303/ltr-definitions.h @@ -6,14 +6,14 @@ namespace esphome { namespace ltr303 { enum class CommandRegisters : uint8_t { - ALS_CONTR = 0x80, // ALS operation mode control SW reset + 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 - MANU_ID = 0x87, // Manufacturer 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 @@ -132,7 +132,7 @@ enum PsMeasurementRate : uint8_t { // // ALS_CONTR Register (0x80) // -union ControlRegister { +union AlsControlRegister { uint8_t raw; struct { bool active_mode : 1; diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index effe1c707a..44c7724206 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -106,18 +106,30 @@ void LTR303Component::loop() { switch (this->state_) { case State::DELAYED_SETUP: - this->configure_reset_and_activate_(); - this->configure_integration_time_(this->integration_time_); err = this->write(nullptr, 0); if (err != i2c::ERROR_OK) { ESP_LOGD(TAG, "i2c connection failed"); this->mark_failed(); } + this->configure_reset_and_activate_(); + this->configure_integration_time_(this->integration_time_); + if (this->proximity_mode_enabled_) { + this->configure_ps_(); + } + this->state_ = State::IDLE; break; case State::IDLE: // having fun, waiting for work + if (this->proximity_mode_enabled_) { + uint16_t ps_data = this->read_ps_data_(); + if (ps_data != this->last_ps_data_) { + this->last_ps_data_ = ps_data; + if (this->proximity_counts_sensor_ != nullptr) + proximity_counts_sensor_->publish_state(ps_data); + } + } break; case State::WAITING_FOR_DATA: @@ -173,10 +185,37 @@ void LTR303Component::loop() { break; } } + +bool LTR303Component::identify_device_type_() { + ESP_LOGD(TAG, "Identifying device type"); + + 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; + } + + PartIdRegister part_id{0}; + part_id.raw = this->reg((uint8_t) CommandRegisters::PART_ID).get(); + // Things getting not really funny here. + // ================= ========= ===== ================= + // Device Part ID Rev Capabilities + // ================= ========= ===== ================= + // Ltr-329/ltr-303 0x0a 0x00 Als + // Ltr-553 0x09 0x02 Als 16 + Ps 11 + // Ltr-556 0x09 0x02 Als 16 + Ps 11 diff nm sens than 553 + // Ltr-659 0x09 0x02 Ps only + + // to do + + return true; +} + void LTR303Component::configure_reset_and_activate_() { ESP_LOGD(TAG, "Resetting"); - ControlRegister als_ctrl{0}; + AlsControlRegister als_ctrl{0}; als_ctrl.sw_reset = true; this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw; delay(2); @@ -212,8 +251,38 @@ void LTR303Component::configure_reset_and_activate_() { } } +void LTR303Component::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; + + PsControlRegister 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 LTR303Component::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 || als_status.data_invalid) { + return this->last_ps_data_; + } + + 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); + ESP_LOGD(TAG, "Got sensor data: PS = %5d, Saturation flag = %d", val, ps_high.ps_saturation_flag); + if (ps_high.ps_saturation_flag) { + return 0xfff; + } + return val; +} + void LTR303Component::configure_gain_(AlsGain gain) { - ControlRegister als_ctrl{0}; + AlsControlRegister als_ctrl{0}; als_ctrl.active_mode = true; als_ctrl.gain = gain; this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw; diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index 3e51f4bddb..525f58964e 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -31,12 +31,14 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { void set_repeat_rate(MeasurementRepeatRate rate) { this->repeat_rate_ = rate; } void set_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; } void set_enable_automatic_mode(bool enable) { this->automatic_mode_enabled_ = enable; } + void set_enable_proximity_mode(bool enable) { this->proximity_mode_enabled_ = enable; } 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: // @@ -66,9 +68,19 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { float lux{0.0f}; } readings_; + // + // LTR sensor type. 303/329 - als, 553 - als + proximity + enum class LtrType : uint8_t { + LtrUnknown = 0, + LtrAlsOnly, + LtrAlsAndProximity, + LtrProximityOnly + } ltr_type_{LtrType::LtrAlsOnly}; + // // Device interaction and data manipulation // + bool identify_device_type_(); void configure_reset_and_activate_(); void configure_integration_time_(IntegrationTime time); void configure_gain_(AlsGain gain); @@ -79,10 +91,15 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { void publish_data_part_1_(Readings &data); void publish_data_part_2_(Readings &data); + void configure_ps_(); + uint16_t read_ps_data_(); + uint16_t last_ps_data_{0xffff}; + // // Component configuration // bool automatic_mode_enabled_{true}; + bool proximity_mode_enabled_{false}; AlsGain gain_{AlsGain::GAIN_1}; IntegrationTime integration_time_{IntegrationTime::INTEGRATION_TIME_100MS}; MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS}; @@ -96,6 +113,7 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { 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 }; } // namespace ltr303 diff --git a/esphome/components/ltr303/sensor.py b/esphome/components/ltr303/sensor.py index e1b783b77a..751f14a953 100644 --- a/esphome/components/ltr303/sensor.py +++ b/esphome/components/ltr303/sensor.py @@ -15,6 +15,7 @@ from esphome.const import ( ICON_BRIGHTNESS_6, ICON_TIMER, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_DISTANCE, STATE_CLASS_MEASUREMENT, ) @@ -24,10 +25,13 @@ DEPENDENCIES = ["i2c"] UNIT_COUNTS = "#" ICON_GAIN = "mdi:multiplication" ICON_BRIGHTNESS_7 = "mdi:brightness-7" +ICON_PROXIMITY = "mdi:hand-wave-outline" CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time" CONF_AMBIENT_LIGHT = "ambient_light" -CONF_INFRARED_COUNTS = "infrared_counts" +CONF_ENABLE_PROXIMITY = "enable_proximity" CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts" +CONF_INFRARED_COUNTS = "infrared_counts" +CONF_PROXIMITY_COUNTS = "proximity_counts" ltr303_ns = cg.esphome_ns.namespace("ltr303") @@ -93,6 +97,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(LTR303Component), cv.Optional(CONF_AUTO_MODE, default=True): cv.boolean, + cv.Optional(CONF_ENABLE_PROXIMITY, default=False): cv.boolean, cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAINS, upper=True), cv.Optional( CONF_INTEGRATION_TIME, default="100ms" @@ -122,6 +127,13 @@ CONFIG_SCHEMA = cv.All( device_class=DEVICE_CLASS_ILLUMINANCE, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_PROXIMITY_COUNTS): sensor.sensor_schema( + unit_of_measurement=UNIT_COUNTS, + icon=ICON_PROXIMITY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ), cv.Optional(CONF_ACTUAL_GAIN): sensor.sensor_schema( icon=ICON_GAIN, accuracy_decimals=0, @@ -167,7 +179,12 @@ async def to_code(config): sens = await sensor.new_sensor(act_itime_config) cg.add(var.set_actual_integration_time_sensor(sens)) + if prox_cnt_config := config.get(CONF_PROXIMITY_COUNTS): + sens = await sensor.new_sensor(prox_cnt_config) + cg.add(var.set_proximity_counts_sensor(sens)) + cg.add(var.set_enable_automatic_mode(config[CONF_AUTO_MODE])) + cg.add(var.set_enable_proximity_mode(config[CONF_ENABLE_PROXIMITY])) cg.add(var.set_gain(config[CONF_GAIN])) cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) cg.add(var.set_repeat_rate(config[CONF_REPEAT])) From 62ad1a0f886080c0b7c3e5fcd6c19ae498542efd Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Fri, 26 Jan 2024 14:02:35 +0000 Subject: [PATCH 13/32] als-ps test --- esphome/components/ltr303/ltr303.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index 44c7724206..0cbf546089 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -124,10 +124,29 @@ void LTR303Component::loop() { // having fun, waiting for work if (this->proximity_mode_enabled_) { uint16_t ps_data = this->read_ps_data_(); + + static const uint16_t ps_distance_table[] = {790, 337, 195, 114, 78, 62, 50}; + static uint8_t ps_distance = 0; + uint8_t distance = 0; + if (ps_data != this->last_ps_data_) { this->last_ps_data_ = ps_data; - if (this->proximity_counts_sensor_ != nullptr) - proximity_counts_sensor_->publish_state(ps_data); + if (ps_data == 0xfff) { + distance = 0; + } else { + uint8_t i; + for (i = 0; i < 7; i++) { + if (ps_data > ps_distance_table[i]) { + distance = i; + break; + } + } + distance = i; + } + if (distance != ps_distance) { + ps_distance = distance; + ESP_LOGD(TAG, "Distance changed %d", distance); + } } } break; @@ -412,6 +431,9 @@ void LTR303Component::apply_lux_calculation_(Readings &data) { } void LTR303Component::publish_data_part_1_(Readings &data) { + if (this->proximity_counts_sensor_ != nullptr) { + this->proximity_counts_sensor_->publish_state(this->last_ps_data_); + } if (this->ambient_light_sensor_ != nullptr) { this->ambient_light_sensor_->publish_state(data.lux); } From dadf645dc9acac6ef9b9e52a39461dfd4dbce034 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Fri, 26 Jan 2024 14:22:19 +0000 Subject: [PATCH 14/32] als-ps test --- esphome/components/ltr303/ltr303.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index 525f58964e..d35e3fec1b 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -114,6 +114,13 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { 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; } }; } // namespace ltr303 From 98d7ac3b11c7c588b7f5a482a437a34ce82bae00 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Fri, 26 Jan 2024 17:15:41 +0000 Subject: [PATCH 15/32] ps options --- esphome/components/ltr303/ltr303.cpp | 56 +++++++++++++++------------- esphome/components/ltr303/ltr303.h | 42 ++++++++++++++++++++- esphome/components/ltr303/sensor.py | 43 ++++++++++++++++++++- 3 files changed, 113 insertions(+), 28 deletions(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index 0cbf546089..f7903c3866 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -70,6 +70,11 @@ void LTR303Component::dump_config() { 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 mode: %s", ONOFF(this->proximity_mode_enabled_)); + ESP_LOGCONFIG(TAG, " Proximity high threshold: %d", this->proximity_threshold_high_); + ESP_LOGCONFIG(TAG, " Proximity low threshold: %d", this->proximity_threshold_low_); + ESP_LOGCONFIG(TAG, " Proximity cooldown time: %d s", this->proximity_cooldown_time_s_); + LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "ALS calculated lux", this->ambient_light_sensor_); @@ -123,31 +128,7 @@ void LTR303Component::loop() { case State::IDLE: // having fun, waiting for work if (this->proximity_mode_enabled_) { - uint16_t ps_data = this->read_ps_data_(); - - static const uint16_t ps_distance_table[] = {790, 337, 195, 114, 78, 62, 50}; - static uint8_t ps_distance = 0; - uint8_t distance = 0; - - if (ps_data != this->last_ps_data_) { - this->last_ps_data_ = ps_data; - if (ps_data == 0xfff) { - distance = 0; - } else { - uint8_t i; - for (i = 0; i < 7; i++) { - if (ps_data > ps_distance_table[i]) { - distance = i; - break; - } - } - distance = i; - } - if (distance != ps_distance) { - ps_distance = distance; - ESP_LOGD(TAG, "Distance changed %d", distance); - } - } + check_and_trigger_ps_(); } break; @@ -205,6 +186,31 @@ void LTR303Component::loop() { } } +void LTR303Component::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->last_ps_data_) { + this->last_ps_data_ = ps_data; + // Higher values - object is closer to sensor + if (ps_data > this->proximity_threshold_high_ && + now - last_high_trigger_time_ >= this->proximity_cooldown_time_s_ * 1000) { + last_high_trigger_time_ = now; + ESP_LOGD(TAG, "Proximity high threshold triggered. Value = %d, Trigger level = %d", ps_data, + this->proximity_threshold_high_); + this->on_high_trigger_callback_.call(); + } else if (ps_data < this->proximity_threshold_low_ && + now - last_low_trigger_time_ >= this->proximity_cooldown_time_s_ * 1000) { + last_low_trigger_time_ = now; + ESP_LOGD(TAG, "Proximity low threshold triggered. Value = %d, Trigger level = %d", ps_data, + this->proximity_threshold_low_); + this->on_low_trigger_callback_.call(); + } + } +} + bool LTR303Component::identify_device_type_() { ESP_LOGD(TAG, "Identifying device type"); diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index d35e3fec1b..ebbf6f8d41 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -4,6 +4,7 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/core/component.h" #include "esphome/core/optional.h" +#include "esphome/core/automation.h" #include "ltr-definitions.h" @@ -32,6 +33,9 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { void set_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; } void set_enable_automatic_mode(bool enable) { this->automatic_mode_enabled_ = enable; } void set_enable_proximity_mode(bool enable) { this->proximity_mode_enabled_ = enable; } + void set_proximity_high_threshold(uint16_t threshold) { this->proximity_threshold_high_ = threshold; } + void set_proximity_low_threshold(uint16_t threshold) { this->proximity_threshold_low_ = threshold; } + void set_proximity_cooldown_time_s(uint16_t time) { this->proximity_cooldown_time_s_ = time; } 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; } @@ -93,17 +97,21 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { void configure_ps_(); uint16_t read_ps_data_(); - uint16_t last_ps_data_{0xffff}; + void check_and_trigger_ps_(); + uint16_t last_ps_data_{0xfffe}; // // Component configuration // bool automatic_mode_enabled_{true}; - bool proximity_mode_enabled_{false}; AlsGain gain_{AlsGain::GAIN_1}; IntegrationTime integration_time_{IntegrationTime::INTEGRATION_TIME_100MS}; MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS}; float glass_attenuation_factor_{1.0}; + bool proximity_mode_enabled_{false}; + uint16_t proximity_threshold_high_{0x0fff}; + uint16_t proximity_threshold_low_{0x0000}; + uint16_t proximity_cooldown_time_s_{0x0000}; // // Sensors for publishing data @@ -121,7 +129,37 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { 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 LTR303HighTrigger; + friend class LTR303LowTrigger; + + CallbackManager on_high_trigger_callback_; + CallbackManager on_low_trigger_callback_; + + void add_on_high_trigger_callback(std::function callback) { + this->on_high_trigger_callback_.add(std::move(callback)); + } + + void add_on_low_trigger_callback(std::function callback) { + this->on_low_trigger_callback_.add(std::move(callback)); + } }; +class LTR303HighTrigger : public Trigger<> { + public: + explicit LTR303HighTrigger(LTR303Component *parent) { + parent->add_on_high_trigger_callback([this]() { this->trigger(); }); + } +}; + +class LTR303LowTrigger : public Trigger<> { + public: + explicit LTR303LowTrigger(LTR303Component *parent) { + parent->add_on_low_trigger_callback([this]() { this->trigger(); }); + } +}; } // namespace ltr303 } // namespace esphome diff --git a/esphome/components/ltr303/sensor.py b/esphome/components/ltr303/sensor.py index 751f14a953..1d51c2c84d 100644 --- a/esphome/components/ltr303/sensor.py +++ b/esphome/components/ltr303/sensor.py @@ -1,5 +1,6 @@ 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_ID, @@ -9,6 +10,7 @@ from esphome.const import ( CONF_GLASS_ATTENUATION_FACTOR, CONF_INTEGRATION_TIME, CONF_REPEAT, + CONF_TRIGGER_ID, UNIT_LUX, UNIT_MILLISECOND, ICON_BRIGHTNESS_5, @@ -32,6 +34,11 @@ CONF_ENABLE_PROXIMITY = "enable_proximity" CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts" CONF_INFRARED_COUNTS = "infrared_counts" CONF_PROXIMITY_COUNTS = "proximity_counts" +CONF_PROXIMITY_HIGH_THRESHOLD = "proximity_high_threshold" +CONF_PROXIMITY_LOW_THRESHOLD = "proximity_low_threshold" +CONF_PROXIMITY_COOLDOWN = "proximity_cooldown" +CONF_ON_HIGH_THRESHOLD = "on_high_threshold" +CONF_ON_LOW_THRESHOLD = "on_low_threshold" ltr303_ns = cg.esphome_ns.namespace("ltr303") @@ -39,6 +46,10 @@ LTR303Component = ltr303_ns.class_( "LTR303Component", cg.PollingComponent, i2c.I2CDevice ) +LTR303HighTrigger = ltr303_ns.class_("LTR303HighTrigger", automation.Trigger) + +LTR303LowTrigger = ltr303_ns.class_("LTR303LowTrigger", automation.Trigger) + Gain = ltr303_ns.enum("Gain") GAINS = { "1X": Gain.GAIN_1, @@ -97,7 +108,6 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(LTR303Component), cv.Optional(CONF_AUTO_MODE, default=True): cv.boolean, - cv.Optional(CONF_ENABLE_PROXIMITY, default=False): cv.boolean, cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAINS, upper=True), cv.Optional( CONF_INTEGRATION_TIME, default="100ms" @@ -106,6 +116,26 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range( min=1.0 ), + cv.Optional(CONF_ENABLE_PROXIMITY, default=False): cv.boolean, + cv.Optional(CONF_ON_HIGH_THRESHOLD): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTR303HighTrigger), + } + ), + cv.Optional(CONF_ON_LOW_THRESHOLD): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTR303LowTrigger), + } + ), + cv.Optional( + CONF_PROXIMITY_COOLDOWN, default="5s" + ): cv.positive_time_period_seconds, + cv.Optional(CONF_PROXIMITY_HIGH_THRESHOLD, default=65535): cv.int_range( + min=0, max=65535 + ), + cv.Optional(CONF_PROXIMITY_LOW_THRESHOLD, default=0): cv.int_range( + min=0, max=65535 + ), cv.Optional(CONF_AMBIENT_LIGHT): sensor.sensor_schema( unit_of_measurement=UNIT_LUX, icon=ICON_BRIGHTNESS_6, @@ -183,9 +213,20 @@ async def to_code(config): 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_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_LOW_THRESHOLD, []): + trigger = cg.new_Pvariable(prox_high_tr[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], prox_low_tr) + cg.add(var.set_enable_automatic_mode(config[CONF_AUTO_MODE])) cg.add(var.set_enable_proximity_mode(config[CONF_ENABLE_PROXIMITY])) cg.add(var.set_gain(config[CONF_GAIN])) cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) cg.add(var.set_repeat_rate(config[CONF_REPEAT])) cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) + cg.add(var.set_proximity_high_threshold(config[CONF_PROXIMITY_HIGH_THRESHOLD])) + cg.add(var.set_proximity_low_threshold(config[CONF_PROXIMITY_LOW_THRESHOLD])) + cg.add(var.set_proximity_cooldown_time_s(config[CONF_PROXIMITY_COOLDOWN])) From 65e0409d8b8e7fa3f40640beecc703be803f4ee4 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Fri, 26 Jan 2024 17:16:38 +0000 Subject: [PATCH 16/32] ps options --- esphome/components/ltr303/ltr303.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr303/ltr303.h index ebbf6f8d41..2e464dc278 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr303/ltr303.h @@ -109,9 +109,9 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS}; float glass_attenuation_factor_{1.0}; bool proximity_mode_enabled_{false}; - uint16_t proximity_threshold_high_{0x0fff}; + uint16_t proximity_threshold_high_{0xffff}; uint16_t proximity_threshold_low_{0x0000}; - uint16_t proximity_cooldown_time_s_{0x0000}; + uint16_t proximity_cooldown_time_s_{5}; // // Sensors for publishing data From e9629e600fbae4ced72b1f9a97b16a4b33eef232 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Fri, 26 Jan 2024 22:15:18 +0000 Subject: [PATCH 17/32] trgger bug fixed --- esphome/components/ltr303/sensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/ltr303/sensor.py b/esphome/components/ltr303/sensor.py index 1d51c2c84d..ce1329d581 100644 --- a/esphome/components/ltr303/sensor.py +++ b/esphome/components/ltr303/sensor.py @@ -46,9 +46,9 @@ LTR303Component = ltr303_ns.class_( "LTR303Component", cg.PollingComponent, i2c.I2CDevice ) -LTR303HighTrigger = ltr303_ns.class_("LTR303HighTrigger", automation.Trigger) +LTR303HighTrigger = ltr303_ns.class_("LTR303HighTrigger", automation.Trigger.template()) -LTR303LowTrigger = ltr303_ns.class_("LTR303LowTrigger", automation.Trigger) +LTR303LowTrigger = ltr303_ns.class_("LTR303LowTrigger", automation.Trigger.template()) Gain = ltr303_ns.enum("Gain") GAINS = { @@ -218,7 +218,7 @@ async def to_code(config): await automation.build_automation(trigger, [], prox_high_tr) for prox_low_tr in config.get(CONF_ON_LOW_THRESHOLD, []): - trigger = cg.new_Pvariable(prox_high_tr[CONF_TRIGGER_ID], var) + trigger = cg.new_Pvariable(prox_low_tr[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], prox_low_tr) cg.add(var.set_enable_automatic_mode(config[CONF_AUTO_MODE])) From a2140a25401440699530ffd070c2ab20bb2faa1a Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Fri, 26 Jan 2024 22:20:27 +0000 Subject: [PATCH 18/32] trgger bug fixed --- esphome/components/ltr303/ltr303.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr303/ltr303.cpp index f7903c3866..26b3539c74 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr303/ltr303.cpp @@ -299,9 +299,9 @@ uint16_t LTR303Component::read_ps_data_() { ps_high.raw = this->reg((uint8_t) CommandRegisters::PS_DATA_1).get(); uint16_t val = encode_uint16(ps_high.ps_data_high, ps_low); - ESP_LOGD(TAG, "Got sensor data: PS = %5d, Saturation flag = %d", val, ps_high.ps_saturation_flag); + // ESP_LOGD(TAG, "Got sensor data: PS = %5d, Saturation flag = %d", val, ps_high.ps_saturation_flag); if (ps_high.ps_saturation_flag) { - return 0xfff; + return 0x7ff; // full 11 bit range } return val; } From f3ffd27518ddbd32fece9dc21bb56c389192088f Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Sat, 27 Jan 2024 12:29:03 +0000 Subject: [PATCH 19/32] Minor comments --- esphome/components/ltr303/ltr-definitions.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/ltr303/ltr-definitions.h b/esphome/components/ltr303/ltr-definitions.h index 9c6722b42d..b56be48f0a 100644 --- a/esphome/components/ltr303/ltr-definitions.h +++ b/esphome/components/ltr303/ltr-definitions.h @@ -28,10 +28,14 @@ enum class CommandRegisters : uint8_t { 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 }; @@ -150,7 +154,7 @@ union PsControlRegister { struct { bool ps_mode_xxx : 1; bool ps_mode_active : 1; - PsGain ps_gain : 2; + PsGain ps_gain : 2; // only LTR-659/558 bool reserved_4 : 1; bool ps_saturation_indicator_enable : 1; bool reserved_6 : 1; From 992bc698222da72d0bb02f146a43d928e76fd4d6 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Sat, 27 Jan 2024 17:46:15 +0000 Subject: [PATCH 20/32] ltr303->ltr_als_ps --- .../{ltr303 => ltr_als_ps}/__init__.py | 0 .../ltr303.cpp => ltr_als_ps/ltr_als_ps.cpp} | 157 ++++++++++-------- .../ltr303.h => ltr_als_ps/ltr_als_ps.h} | 120 +++++++------ .../ltr_definitions.h} | 4 +- .../{ltr303 => ltr_als_ps}/sensor.py | 129 ++++++++------ 5 files changed, 236 insertions(+), 174 deletions(-) rename esphome/components/{ltr303 => ltr_als_ps}/__init__.py (100%) rename esphome/components/{ltr303/ltr303.cpp => ltr_als_ps/ltr_als_ps.cpp} (74%) rename esphome/components/{ltr303/ltr303.h => ltr_als_ps/ltr_als_ps.h} (54%) rename esphome/components/{ltr303/ltr-definitions.h => ltr_als_ps/ltr_definitions.h} (99%) rename esphome/components/{ltr303 => ltr_als_ps}/sensor.py (69%) diff --git a/esphome/components/ltr303/__init__.py b/esphome/components/ltr_als_ps/__init__.py similarity index 100% rename from esphome/components/ltr303/__init__.py rename to esphome/components/ltr_als_ps/__init__.py diff --git a/esphome/components/ltr303/ltr303.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp similarity index 74% rename from esphome/components/ltr303/ltr303.cpp rename to esphome/components/ltr_als_ps/ltr_als_ps.cpp index 26b3539c74..df6023b98d 100644 --- a/esphome/components/ltr303/ltr303.cpp +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -1,4 +1,4 @@ -#include "ltr303.h" +#include "ltr_als_ps.h" #include "esphome/core/application.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" @@ -6,9 +6,9 @@ using esphome::i2c::ErrorCode; namespace esphome { -namespace ltr303 { +namespace ltr_als_ps { -static const char *const TAG = "ltr303"; +static const char *const TAG = "ltr_als_ps"; static const uint8_t MAX_TRIES = 5; @@ -57,23 +57,42 @@ static float get_gain_coeff(AlsGain gain) { return ALS_GAIN[gain & 0b111]; } -void LTR303Component::setup() { +static float get_ps_gain_coeff(PsGain gain) { + static const float PS_GAIN[4] = {16, 0, 32, 64}; + return PS_GAIN[gain & 0b11]; +} + +void LTRAlsPsComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up LTR-303/329"); // 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 LTR303Component::dump_config() { +void LTRAlsPsComponent::dump_config() { + auto get_device_type = [](LtrType typ) { + switch (typ) { + case LtrType::LtrTypeAlsOnly: + return "ALS only"; + case LtrType::LtrTypePsOnly: + return "PS only"; + case LtrType::LtrTypeAlsAndPs: + 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 mode: %s", ONOFF(this->proximity_mode_enabled_)); - ESP_LOGCONFIG(TAG, " Proximity high threshold: %d", this->proximity_threshold_high_); - ESP_LOGCONFIG(TAG, " Proximity low threshold: %d", this->proximity_threshold_low_); - ESP_LOGCONFIG(TAG, " Proximity cooldown time: %d s", this->proximity_cooldown_time_s_); + 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); @@ -87,25 +106,25 @@ void LTR303Component::dump_config() { } } -void LTR303Component::update() { +void LTRAlsPsComponent::update() { ESP_LOGD(TAG, "Updating"); if (this->is_ready() && this->state_ == State::IDLE) { ESP_LOGD(TAG, "Initiating new data collection"); this->state_ = this->automatic_mode_enabled_ ? State::COLLECTING_DATA_AUTO : State::WAITING_FOR_DATA; - this->readings_.ch0 = 0; - this->readings_.ch1 = 0; - this->readings_.actual_gain = this->gain_; - this->readings_.integration_time = this->integration_time_; - this->readings_.lux = 0; + this->als_readings_.ch0 = 0; + this->als_readings_.ch1 = 0; + this->als_readings_.actual_gain = this->gain_; + this->als_readings_.integration_time = this->integration_time_; + this->als_readings_.lux = 0; } else { ESP_LOGD(TAG, "Component not ready yet"); } } -void LTR303Component::loop() { +void LTRAlsPsComponent::loop() { ErrorCode err = i2c::ERROR_OK; static uint8_t tries{0}; @@ -117,8 +136,10 @@ void LTR303Component::loop() { this->mark_failed(); } this->configure_reset_and_activate_(); - this->configure_integration_time_(this->integration_time_); - if (this->proximity_mode_enabled_) { + if (this->is_als_()) { + this->configure_integration_time_(this->integration_time_); + } + if (this->is_ps_()) { this->configure_ps_(); } @@ -126,18 +147,17 @@ void LTR303Component::loop() { break; case State::IDLE: - // having fun, waiting for work - if (this->proximity_mode_enabled_) { + if (this->is_ps_()) { check_and_trigger_ps_(); } break; case State::WAITING_FOR_DATA: - if (this->is_data_ready_(this->readings_) == DataAvail::DATA_OK) { + if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) { tries = 0; ESP_LOGD(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", - get_gain_coeff(this->readings_.actual_gain), get_itime_ms(this->readings_.integration_time)); - this->read_sensor_data_(this->readings_); + get_gain_coeff(this->als_readings_.actual_gain), get_itime_ms(this->als_readings_.integration_time)); + this->read_sensor_data_(this->als_readings_); this->state_ = State::DATA_COLLECTED; } else if (tries >= MAX_TRIES) { ESP_LOGW(TAG, "Can't get data after several tries."); @@ -152,16 +172,16 @@ void LTR303Component::loop() { case State::COLLECTING_DATA_AUTO: case State::DATA_COLLECTED: - if (this->state_ == State::COLLECTING_DATA_AUTO || this->are_adjustments_required_(this->readings_)) { + 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"); - this->configure_integration_time_(this->readings_.integration_time); - this->configure_gain_(this->readings_.actual_gain); + this->configure_integration_time_(this->als_readings_.integration_time); + this->configure_gain_(this->als_readings_.actual_gain); // if sensitivity adjustment needed - need to wait for first data samples after setting new parameters this->set_timeout(1 * get_meas_time_ms(this->repeat_rate_), [this]() { this->state_ = State::WAITING_FOR_DATA; }); } else { - this->apply_lux_calculation_(this->readings_); + this->apply_lux_calculation_(this->als_readings_); this->state_ = State::READY_TO_PUBLISH; } break; @@ -171,12 +191,12 @@ void LTR303Component::loop() { break; case State::READY_TO_PUBLISH: - this->publish_data_part_1_(this->readings_); + this->publish_data_part_1_(this->als_readings_); this->state_ = State::KEEP_PUBLISHING; break; case State::KEEP_PUBLISHING: - this->publish_data_part_2_(this->readings_); + this->publish_data_part_2_(this->als_readings_); this->status_clear_warning(); this->state_ = State::IDLE; break; @@ -186,34 +206,30 @@ void LTR303Component::loop() { } } -void LTR303Component::check_and_trigger_ps_() { +void LTRAlsPsComponent::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->last_ps_data_) { - this->last_ps_data_ = ps_data; + if (ps_data != this->ps_readings_) { + this->ps_readings_ = ps_data; // Higher values - object is closer to sensor - if (ps_data > this->proximity_threshold_high_ && - now - last_high_trigger_time_ >= this->proximity_cooldown_time_s_ * 1000) { + 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->proximity_threshold_high_); - this->on_high_trigger_callback_.call(); - } else if (ps_data < this->proximity_threshold_low_ && - now - last_low_trigger_time_ >= this->proximity_cooldown_time_s_ * 1000) { + 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->proximity_threshold_low_); - this->on_low_trigger_callback_.call(); + this->ps_threshold_low_); + this->on_ps_low_trigger_callback_.call(); } } } -bool LTR303Component::identify_device_type_() { - ESP_LOGD(TAG, "Identifying device type"); - +bool LTRAlsPsComponent::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); @@ -221,23 +237,28 @@ bool LTR303Component::identify_device_type_() { return false; } + // Things getting not really funny here, we can't identify device type by part number ID + // ======================== ========= ===== ================= + // Device Part ID Rev Capabilities + // ======================== ========= ===== ================= + // Ltr-329/ltr-303 0x0a 0x00 Als 16b + // Ltr-553/ltr-556/ltr-556 0x09 0x02 Als 16b + Ps 11b diff nm sens + // Ltr-659 0x09 0x02 Ps 11b and ps gain + // + // There are other devices which might potentially work with default settings, + // but registers layout is different and we can't use them properly. For ex. ltr-558 + PartIdRegister part_id{0}; part_id.raw = this->reg((uint8_t) CommandRegisters::PART_ID).get(); - // Things getting not really funny here. - // ================= ========= ===== ================= - // Device Part ID Rev Capabilities - // ================= ========= ===== ================= - // Ltr-329/ltr-303 0x0a 0x00 Als - // Ltr-553 0x09 0x02 Als 16 + Ps 11 - // Ltr-556 0x09 0x02 Als 16 + Ps 11 diff nm sens than 553 - // Ltr-659 0x09 0x02 Ps only - - // to do - + if (part_id.part_number_id != 0x0a && part_id.part_number_id != 0x09) { + ESP_LOGW(TAG, "Unknown part number ID: 0x%02X. It might not work properly.", part_id.part_number_id); + this->status_set_warning(); + return true; + } return true; } -void LTR303Component::configure_reset_and_activate_() { +void LTRAlsPsComponent::configure_reset_and_activate_() { ESP_LOGD(TAG, "Resetting"); AlsControlRegister als_ctrl{0}; @@ -276,7 +297,7 @@ void LTR303Component::configure_reset_and_activate_() { } } -void LTR303Component::configure_ps_() { +void LTRAlsPsComponent::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; @@ -287,11 +308,11 @@ void LTR303Component::configure_ps_() { this->reg((uint8_t) CommandRegisters::PS_CONTR) = ps_ctrl.raw; } -uint16_t LTR303Component::read_ps_data_() { +uint16_t LTRAlsPsComponent::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 || als_status.data_invalid) { - return this->last_ps_data_; + return this->ps_readings_; } uint8_t ps_low = this->reg((uint8_t) CommandRegisters::PS_DATA_0).get(); @@ -306,7 +327,7 @@ uint16_t LTR303Component::read_ps_data_() { return val; } -void LTR303Component::configure_gain_(AlsGain gain) { +void LTRAlsPsComponent::configure_gain_(AlsGain gain) { AlsControlRegister als_ctrl{0}; als_ctrl.active_mode = true; als_ctrl.gain = gain; @@ -314,7 +335,7 @@ void LTR303Component::configure_gain_(AlsGain gain) { delay(2); } -void LTR303Component::configure_integration_time_(IntegrationTime time) { +void LTRAlsPsComponent::configure_integration_time_(IntegrationTime time) { MeasurementRateRegister meas{0}; meas.measurement_repeat_rate = this->repeat_rate_; meas.integration_time = time; @@ -322,7 +343,7 @@ void LTR303Component::configure_integration_time_(IntegrationTime time) { delay(2); } -DataAvail LTR303Component::is_data_ready_(Readings &data) { +DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) { AlsPsStatusRegister als_status{0}; als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get(); @@ -339,7 +360,7 @@ DataAvail LTR303Component::is_data_ready_(Readings &data) { return DataAvail::DATA_OK; } -void LTR303Component::read_sensor_data_(Readings &data) { +void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) { 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(); @@ -350,7 +371,7 @@ void LTR303Component::read_sensor_data_(Readings &data) { ESP_LOGD(TAG, "Got sensor data: CH1 = %d, CH0 = %d", data.ch1, data.ch0); } -bool LTR303Component::are_adjustments_required_(Readings &data) { +bool LTRAlsPsComponent::are_adjustments_required_(AlsReadings &data) { // skip first sample in auto mode - // we need to reconfigure device after last measurement if (!this->automatic_mode_enabled_) @@ -398,7 +419,7 @@ bool LTR303Component::are_adjustments_required_(Readings &data) { return false; } -void LTR303Component::apply_lux_calculation_(Readings &data) { +void LTRAlsPsComponent::apply_lux_calculation_(AlsReadings &data) { if ((data.ch0 == 0xFFFF) || (data.ch1 == 0xFFFF)) { ESP_LOGW(TAG, "Sensors got saturated"); data.lux = 0.0f; @@ -436,9 +457,9 @@ void LTR303Component::apply_lux_calculation_(Readings &data) { data.lux = lux; } -void LTR303Component::publish_data_part_1_(Readings &data) { +void LTRAlsPsComponent::publish_data_part_1_(AlsReadings &data) { if (this->proximity_counts_sensor_ != nullptr) { - this->proximity_counts_sensor_->publish_state(this->last_ps_data_); + this->proximity_counts_sensor_->publish_state(this->ps_readings_); } if (this->ambient_light_sensor_ != nullptr) { this->ambient_light_sensor_->publish_state(data.lux); @@ -451,7 +472,7 @@ void LTR303Component::publish_data_part_1_(Readings &data) { } } -void LTR303Component::publish_data_part_2_(Readings &data) { +void LTRAlsPsComponent::publish_data_part_2_(AlsReadings &data) { if (this->actual_gain_sensor_ != nullptr) { this->actual_gain_sensor_->publish_state(get_gain_coeff(data.actual_gain)); } @@ -459,5 +480,5 @@ void LTR303Component::publish_data_part_2_(Readings &data) { this->actual_integration_time_sensor_->publish_state(get_itime_ms(data.integration_time)); } } -} // namespace ltr303 +} // namespace ltr_als_ps } // namespace esphome diff --git a/esphome/components/ltr303/ltr303.h b/esphome/components/ltr_als_ps/ltr_als_ps.h similarity index 54% rename from esphome/components/ltr303/ltr303.h rename to esphome/components/ltr_als_ps/ltr_als_ps.h index 2e464dc278..956daf0f35 100644 --- a/esphome/components/ltr303/ltr303.h +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -6,14 +6,21 @@ #include "esphome/core/optional.h" #include "esphome/core/automation.h" -#include "ltr-definitions.h" +#include "ltr_definitions.h" namespace esphome { -namespace ltr303 { +namespace ltr_als_ps { enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK }; -class LTR303Component : public PollingComponent, public i2c::I2CDevice { +enum LtrType : uint8_t { + LtrTypeUnknown = 0, + LtrTypeAlsOnly = 1, + LtrTypePsOnly = 2, + LtrTypeAlsAndPs = 3, +}; + +class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { public: // // EspHome framework functions @@ -24,19 +31,27 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { void update() override; void loop() override; + // Configuration setters : General // - // Configuration setters - // - void set_gain(AlsGain gain) { this->gain_ = gain; } - void set_integration_time(IntegrationTime time) { this->integration_time_ = time; } - void set_repeat_rate(MeasurementRepeatRate rate) { this->repeat_rate_ = rate; } - void set_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; } - void set_enable_automatic_mode(bool enable) { this->automatic_mode_enabled_ = enable; } - void set_enable_proximity_mode(bool enable) { this->proximity_mode_enabled_ = enable; } - void set_proximity_high_threshold(uint16_t threshold) { this->proximity_threshold_high_ = threshold; } - void set_proximity_low_threshold(uint16_t threshold) { this->proximity_threshold_low_ = threshold; } - void set_proximity_cooldown_time_s(uint16_t time) { this->proximity_cooldown_time_s_ = time; } + 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(AlsGain gain) { this->gain_ = gain; } + void set_als_integration_time(IntegrationTime 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(PsGain 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; } @@ -61,44 +76,46 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { KEEP_PUBLISHING } state_{State::NOT_INITIALIZED}; + LtrType ltr_type_{LtrType::LtrTypeAlsOnly}; + // // Current measurements data // - struct Readings { + struct AlsReadings { uint16_t ch0{0}; uint16_t ch1{0}; AlsGain actual_gain{AlsGain::GAIN_1}; IntegrationTime integration_time{IntegrationTime::INTEGRATION_TIME_100MS}; float lux{0.0f}; - } readings_; + } als_readings_; + uint16_t ps_readings_{0xfffe}; - // - // LTR sensor type. 303/329 - als, 553 - als + proximity - enum class LtrType : uint8_t { - LtrUnknown = 0, - LtrAlsOnly, - LtrAlsAndProximity, - LtrProximityOnly - } ltr_type_{LtrType::LtrAlsOnly}; + inline const bool is_als_() const { + return this->ltr_type_ == LtrType::LtrTypeAlsOnly || this->ltr_type_ == LtrType::LtrTypeAlsAndPs; + } + inline const bool is_ps_() const { + return this->ltr_type_ == LtrType::LtrTypePsOnly || this->ltr_type_ == LtrType::LtrTypeAlsAndPs; + } // // Device interaction and data manipulation // - bool identify_device_type_(); + bool check_part_number_(); + void configure_reset_and_activate_(); + void configure_integration_time_(IntegrationTime time); void configure_gain_(AlsGain gain); - DataAvail is_data_ready_(Readings &data); - void read_sensor_data_(Readings &data); - bool are_adjustments_required_(Readings &data); - void apply_lux_calculation_(Readings &data); - void publish_data_part_1_(Readings &data); - void publish_data_part_2_(Readings &data); + 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_(); - uint16_t last_ps_data_{0xfffe}; // // Component configuration @@ -108,10 +125,11 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { IntegrationTime integration_time_{IntegrationTime::INTEGRATION_TIME_100MS}; MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS}; float glass_attenuation_factor_{1.0}; - bool proximity_mode_enabled_{false}; - uint16_t proximity_threshold_high_{0xffff}; - uint16_t proximity_threshold_low_{0x0000}; - uint16_t proximity_cooldown_time_s_{5}; + + uint16_t ps_cooldown_time_s_{5}; + PsGain ps_gain_{PsGain::PS_GAIN_16}; + uint16_t ps_threshold_high_{0xffff}; + uint16_t ps_threshold_low_{0x0000}; // // Sensors for publishing data @@ -133,33 +151,33 @@ class LTR303Component : public PollingComponent, public i2c::I2CDevice { // // Trigger section for the automations // - friend class LTR303HighTrigger; - friend class LTR303LowTrigger; + friend class LTRPsHighTrigger; + friend class LTRPsLowTrigger; - CallbackManager on_high_trigger_callback_; - CallbackManager on_low_trigger_callback_; + CallbackManager on_ps_high_trigger_callback_; + CallbackManager on_ps_low_trigger_callback_; - void add_on_high_trigger_callback(std::function callback) { - this->on_high_trigger_callback_.add(std::move(callback)); + void add_on_ps_high_trigger_callback(std::function callback) { + this->on_ps_high_trigger_callback_.add(std::move(callback)); } - void add_on_low_trigger_callback(std::function callback) { - this->on_low_trigger_callback_.add(std::move(callback)); + void add_on_ps_low_trigger_callback(std::function callback) { + this->on_ps_low_trigger_callback_.add(std::move(callback)); } }; -class LTR303HighTrigger : public Trigger<> { +class LTRPsHighTrigger : public Trigger<> { public: - explicit LTR303HighTrigger(LTR303Component *parent) { - parent->add_on_high_trigger_callback([this]() { this->trigger(); }); + explicit LTRPsHighTrigger(LTRAlsPsComponent *parent) { + parent->add_on_ps_high_trigger_callback([this]() { this->trigger(); }); } }; -class LTR303LowTrigger : public Trigger<> { +class LTRPsLowTrigger : public Trigger<> { public: - explicit LTR303LowTrigger(LTR303Component *parent) { - parent->add_on_low_trigger_callback([this]() { this->trigger(); }); + explicit LTRPsLowTrigger(LTRAlsPsComponent *parent) { + parent->add_on_ps_low_trigger_callback([this]() { this->trigger(); }); } }; -} // namespace ltr303 +} // namespace ltr_als_ps } // namespace esphome diff --git a/esphome/components/ltr303/ltr-definitions.h b/esphome/components/ltr_als_ps/ltr_definitions.h similarity index 99% rename from esphome/components/ltr303/ltr-definitions.h rename to esphome/components/ltr_als_ps/ltr_definitions.h index b56be48f0a..739445e9a0 100644 --- a/esphome/components/ltr303/ltr-definitions.h +++ b/esphome/components/ltr_als_ps/ltr_definitions.h @@ -3,7 +3,7 @@ #include namespace esphome { -namespace ltr303 { +namespace ltr_als_ps { enum class CommandRegisters : uint8_t { ALS_CONTR = 0x80, // ALS operation mode control and SW reset @@ -271,5 +271,5 @@ union InterruptPersistRegister { } __attribute__((packed)); }; -} // namespace ltr303 +} // namespace ltr_als_ps } // namespace esphome diff --git a/esphome/components/ltr303/sensor.py b/esphome/components/ltr_als_ps/sensor.py similarity index 69% rename from esphome/components/ltr303/sensor.py rename to esphome/components/ltr_als_ps/sensor.py index ce1329d581..4f45625e87 100644 --- a/esphome/components/ltr303/sensor.py +++ b/esphome/components/ltr_als_ps/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( CONF_INTEGRATION_TIME, CONF_REPEAT, CONF_TRIGGER_ID, + CONF_TYPE, UNIT_LUX, UNIT_MILLISECOND, ICON_BRIGHTNESS_5, @@ -25,42 +26,48 @@ CODEOWNERS = ["@latonita"] DEPENDENCIES = ["i2c"] UNIT_COUNTS = "#" + ICON_GAIN = "mdi:multiplication" ICON_BRIGHTNESS_7 = "mdi:brightness-7" ICON_PROXIMITY = "mdi:hand-wave-outline" + CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time" CONF_AMBIENT_LIGHT = "ambient_light" -CONF_ENABLE_PROXIMITY = "enable_proximity" CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts" CONF_INFRARED_COUNTS = "infrared_counts" -CONF_PROXIMITY_COUNTS = "proximity_counts" -CONF_PROXIMITY_HIGH_THRESHOLD = "proximity_high_threshold" -CONF_PROXIMITY_LOW_THRESHOLD = "proximity_low_threshold" -CONF_PROXIMITY_COOLDOWN = "proximity_cooldown" -CONF_ON_HIGH_THRESHOLD = "on_high_threshold" -CONF_ON_LOW_THRESHOLD = "on_low_threshold" -ltr303_ns = cg.esphome_ns.namespace("ltr303") +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" +CONF_ON_PS_HIGH_THRESHOLD = "on_ps_high_threshold" +CONF_ON_PS_LOW_THRESHOLD = "on_ps_low_threshold" -LTR303Component = ltr303_ns.class_( - "LTR303Component", cg.PollingComponent, i2c.I2CDevice +ltr_als_ps_ns = cg.esphome_ns.namespace("ltr_als_ps") + +LTRAlsPsComponent = ltr_als_ps_ns.class_( + "LTRAlsPsComponent", cg.PollingComponent, i2c.I2CDevice ) -LTR303HighTrigger = ltr303_ns.class_("LTR303HighTrigger", automation.Trigger.template()) - -LTR303LowTrigger = ltr303_ns.class_("LTR303LowTrigger", automation.Trigger.template()) - -Gain = ltr303_ns.enum("Gain") -GAINS = { - "1X": Gain.GAIN_1, - "2X": Gain.GAIN_2, - "4X": Gain.GAIN_4, - "8X": Gain.GAIN_8, - "48X": Gain.GAIN_48, - "96X": Gain.GAIN_96, +LtrType = ltr_als_ps_ns.enum("LtrType") +LTR_TYPES = { + "ALS": LtrType.LtrTypeAlsOnly, + "PS": LtrType.LtrTypePsOnly, + "ALS_PS": LtrType.LtrTypeAlsAndPs, } -IntegrationTime = ltr303_ns.enum("IntegrationTime") +AlsGain = ltr_als_ps_ns.enum("Gain") +ALS_GAINS = { + "1X": AlsGain.GAIN_1, + "2X": AlsGain.GAIN_2, + "4X": AlsGain.GAIN_4, + "8X": AlsGain.GAIN_8, + "48X": AlsGain.GAIN_48, + "96X": AlsGain.GAIN_96, +} + +IntegrationTime = ltr_als_ps_ns.enum("IntegrationTime") INTEGRATION_TIMES = { 50: IntegrationTime.INTEGRATION_TIME_50MS, 100: IntegrationTime.INTEGRATION_TIME_100MS, @@ -72,7 +79,7 @@ INTEGRATION_TIMES = { 400: IntegrationTime.INTEGRATION_TIME_400MS, } -MeasurementRepeatRate = ltr303_ns.enum("MeasurementRepeatRate") +MeasurementRepeatRate = ltr_als_ps_ns.enum("MeasurementRepeatRate") MEASUREMENT_REPEAT_RATES = { 50: MeasurementRepeatRate.REPEAT_RATE_50MS, 100: MeasurementRepeatRate.REPEAT_RATE_100MS, @@ -82,6 +89,18 @@ MEASUREMENT_REPEAT_RATES = { 2000: MeasurementRepeatRate.REPEAT_RATE_2000MS, } +PsGain = ltr_als_ps_ns.enum("PsGain") +PS_GAINS = { + "16X": PsGain.PS_GAIN_16, + "32X": PsGain.PS_GAIN_32, + "64X": PsGain.PS_GAIN_64, +} + +LTRPsHighTrigger = ltr_als_ps_ns.class_( + "LTRPsHighTrigger", automation.Trigger.template() +) +LTRPsLowTrigger = ltr_als_ps_ns.class_("LTRPsLowTrigger", automation.Trigger.template()) + def validate_integration_time(value): value = cv.positive_time_period_milliseconds(value).total_milliseconds @@ -106,9 +125,10 @@ def validate_time_and_repeat_rate(config): CONFIG_SCHEMA = cv.All( cv.Schema( { - cv.GenerateID(): cv.declare_id(LTR303Component), + 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(GAINS, upper=True), + cv.Optional(CONF_GAIN, default="1X"): cv.enum(ALS_GAINS, upper=True), cv.Optional( CONF_INTEGRATION_TIME, default="100ms" ): validate_integration_time, @@ -116,26 +136,26 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range( min=1.0 ), - cv.Optional(CONF_ENABLE_PROXIMITY, default=False): cv.boolean, - cv.Optional(CONF_ON_HIGH_THRESHOLD): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTR303HighTrigger), - } - ), - cv.Optional(CONF_ON_LOW_THRESHOLD): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTR303LowTrigger), - } - ), cv.Optional( - CONF_PROXIMITY_COOLDOWN, default="5s" + CONF_PS_COOLDOWN, default="5s" ): cv.positive_time_period_seconds, - cv.Optional(CONF_PROXIMITY_HIGH_THRESHOLD, default=65535): cv.int_range( + cv.Optional(CONF_PS_GAIN, default="16X"): cv.enum(PS_GAINS, upper=True), + cv.Optional(CONF_PS_HIGH_THRESHOLD, default=65535): cv.int_range( min=0, max=65535 ), - cv.Optional(CONF_PROXIMITY_LOW_THRESHOLD, default=0): cv.int_range( + 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): sensor.sensor_schema( unit_of_measurement=UNIT_LUX, icon=ICON_BRIGHTNESS_6, @@ -157,7 +177,7 @@ CONFIG_SCHEMA = cv.All( device_class=DEVICE_CLASS_ILLUMINANCE, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Optional(CONF_PROXIMITY_COUNTS): sensor.sensor_schema( + cv.Optional(CONF_PS_COUNTS): sensor.sensor_schema( unit_of_measurement=UNIT_COUNTS, icon=ICON_PROXIMITY, accuracy_decimals=0, @@ -209,24 +229,27 @@ async def to_code(config): sens = await sensor.new_sensor(act_itime_config) cg.add(var.set_actual_integration_time_sensor(sens)) - if prox_cnt_config := config.get(CONF_PROXIMITY_COUNTS): + 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_HIGH_THRESHOLD, []): + 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_LOW_THRESHOLD, []): + 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_enable_automatic_mode(config[CONF_AUTO_MODE])) - cg.add(var.set_enable_proximity_mode(config[CONF_ENABLE_PROXIMITY])) - cg.add(var.set_gain(config[CONF_GAIN])) - cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) - cg.add(var.set_repeat_rate(config[CONF_REPEAT])) - cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) - cg.add(var.set_proximity_high_threshold(config[CONF_PROXIMITY_HIGH_THRESHOLD])) - cg.add(var.set_proximity_low_threshold(config[CONF_PROXIMITY_LOW_THRESHOLD])) - cg.add(var.set_proximity_cooldown_time_s(config[CONF_PROXIMITY_COOLDOWN])) + 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])) From 0a5e0206002ae85721ec151e1910dfd2e3e17bab Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Wed, 31 Jan 2024 21:10:28 +0000 Subject: [PATCH 21/32] codeowners, tests --- CODEOWNERS | 2 +- tests/test2.yaml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 734b049ebf..00e05a37f8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -178,8 +178,8 @@ esphome/components/lightwaverf/* @max246 esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core -esphome/components/ltr303/* @latonita esphome/components/ltr390/* @sjtrny +esphome/components/ltr_als_ps/* @latonita esphome/components/matrix_keypad/* @ssieb esphome/components/max31865/* @DAVe3283 esphome/components/max44009/* @berfenger diff --git a/tests/test2.yaml b/tests/test2.yaml index 73dbd7c823..4ae3537102 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -311,10 +311,11 @@ sensor: id: motion_rtcgq02lm battery_level: name: Mi Motion Sensor 2 Battery level - - platform: ltr303 - address: 0x29 + - platform: ltr_als_ps + address: 0x23 gain: 1x integration_time: 100ms + ps_cooldown: 5 s ambient_light: name: "Ambient light" full_spectrum_counts: From d6a48e9e51eaf7e7888b81b0cdd5724069968171 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Wed, 31 Jan 2024 21:44:49 +0000 Subject: [PATCH 22/32] tidy up --- esphome/components/ltr_als_ps/ltr_als_ps.cpp | 18 ++++++++-------- esphome/components/ltr_als_ps/ltr_als_ps.h | 22 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp index df6023b98d..003f07054d 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.cpp +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -71,11 +71,11 @@ void LTRAlsPsComponent::setup() { void LTRAlsPsComponent::dump_config() { auto get_device_type = [](LtrType typ) { switch (typ) { - case LtrType::LtrTypeAlsOnly: + case LtrType::LTR_TYPE_ALS_ONLY: return "ALS only"; - case LtrType::LtrTypePsOnly: + case LtrType::LTR_TYPE_PS_ONLY: return "PS only"; - case LtrType::LtrTypeAlsAndPs: + case LtrType::LTR_TYPE_ALS_AND_PS: return "Als + PS"; default: return "Unknown"; @@ -207,21 +207,21 @@ void LTRAlsPsComponent::loop() { } void LTRAlsPsComponent::check_and_trigger_ps_() { - static uint32_t last_high_trigger_time_{0}; - static uint32_t last_low_trigger_time_{0}; + 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; + 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; + } 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(); diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.h b/esphome/components/ltr_als_ps/ltr_als_ps.h index 956daf0f35..d758a48f3a 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.h +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -14,10 +14,10 @@ namespace ltr_als_ps { enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK }; enum LtrType : uint8_t { - LtrTypeUnknown = 0, - LtrTypeAlsOnly = 1, - LtrTypePsOnly = 2, - LtrTypeAlsAndPs = 3, + LTR_TYPE_UNKNOWN = 0, + LTR_TYPE_ALS_ONLY = 1, + LTR_TYPE_PS_ONLY = 2, + LTR_TYPE_ALS_AND_PS = 3, }; class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { @@ -76,7 +76,7 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { KEEP_PUBLISHING } state_{State::NOT_INITIALIZED}; - LtrType ltr_type_{LtrType::LtrTypeAlsOnly}; + LtrType ltr_type_{LtrType::LTR_TYPE_ALS_ONLY}; // // Current measurements data @@ -91,10 +91,10 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { uint16_t ps_readings_{0xfffe}; inline const bool is_als_() const { - return this->ltr_type_ == LtrType::LtrTypeAlsOnly || this->ltr_type_ == LtrType::LtrTypeAlsAndPs; + return this->ltr_type_ == LtrType::LTR_TYPE_ALS_ONLY || this->ltr_type_ == LtrType::LTR_TYPE_ALS_AND_PS; } inline const bool is_ps_() const { - return this->ltr_type_ == LtrType::LtrTypePsOnly || this->ltr_type_ == LtrType::LtrTypeAlsAndPs; + return this->ltr_type_ == LtrType::LTR_TYPE_PS_ONLY || this->ltr_type_ == LtrType::LTR_TYPE_ALS_AND_PS; } // @@ -157,11 +157,11 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { CallbackManager on_ps_high_trigger_callback_; CallbackManager on_ps_low_trigger_callback_; - void add_on_ps_high_trigger_callback(std::function callback) { + void add_on_ps_high_trigger_callback_(std::function callback) { this->on_ps_high_trigger_callback_.add(std::move(callback)); } - void add_on_ps_low_trigger_callback(std::function callback) { + void add_on_ps_low_trigger_callback_(std::function callback) { this->on_ps_low_trigger_callback_.add(std::move(callback)); } }; @@ -169,14 +169,14 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { class LTRPsHighTrigger : public Trigger<> { public: explicit LTRPsHighTrigger(LTRAlsPsComponent *parent) { - parent->add_on_ps_high_trigger_callback([this]() { this->trigger(); }); + parent->add_on_ps_high_trigger_callback_([this]() { this->trigger(); }); } }; class LTRPsLowTrigger : public Trigger<> { public: explicit LTRPsLowTrigger(LTRAlsPsComponent *parent) { - parent->add_on_ps_low_trigger_callback([this]() { this->trigger(); }); + parent->add_on_ps_low_trigger_callback_([this]() { this->trigger(); }); } }; } // namespace ltr_als_ps From e866486956559140bdfe616247aae710fd95b065 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Wed, 31 Jan 2024 21:46:26 +0000 Subject: [PATCH 23/32] tidy up --- esphome/components/ltr_als_ps/ltr_als_ps.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.h b/esphome/components/ltr_als_ps/ltr_als_ps.h index d758a48f3a..7654f8e9ba 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.h +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -90,10 +90,10 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { } als_readings_; uint16_t ps_readings_{0xfffe}; - inline const bool is_als_() const { + inline bool is_als_() const { return this->ltr_type_ == LtrType::LTR_TYPE_ALS_ONLY || this->ltr_type_ == LtrType::LTR_TYPE_ALS_AND_PS; } - inline const bool is_ps_() const { + inline bool is_ps_() const { return this->ltr_type_ == LtrType::LTR_TYPE_PS_ONLY || this->ltr_type_ == LtrType::LTR_TYPE_ALS_AND_PS; } From 6a9eeb775a670fc7dbc33cbe27cf7c65e19d257d Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Wed, 31 Jan 2024 21:47:51 +0000 Subject: [PATCH 24/32] tidy up --- esphome/components/ltr_als_ps/sensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/ltr_als_ps/sensor.py b/esphome/components/ltr_als_ps/sensor.py index 4f45625e87..25e8ad803b 100644 --- a/esphome/components/ltr_als_ps/sensor.py +++ b/esphome/components/ltr_als_ps/sensor.py @@ -52,9 +52,9 @@ LTRAlsPsComponent = ltr_als_ps_ns.class_( LtrType = ltr_als_ps_ns.enum("LtrType") LTR_TYPES = { - "ALS": LtrType.LtrTypeAlsOnly, - "PS": LtrType.LtrTypePsOnly, - "ALS_PS": LtrType.LtrTypeAlsAndPs, + "ALS": LtrType.LTR_TYPE_ALS_ONLY, + "PS": LtrType.LTR_TYPE_PS_ONLY, + "ALS_PS": LtrType.LTR_TYPE_ALS_AND_PS, } AlsGain = ltr_als_ps_ns.enum("Gain") From c450053749c78abf4f4f000c2e13399d2dbbea57 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Mon, 19 Feb 2024 10:44:17 +0200 Subject: [PATCH 25/32] gain enum name fix --- esphome/components/ltr_als_ps/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ltr_als_ps/sensor.py b/esphome/components/ltr_als_ps/sensor.py index 25e8ad803b..8554b6f99d 100644 --- a/esphome/components/ltr_als_ps/sensor.py +++ b/esphome/components/ltr_als_ps/sensor.py @@ -57,7 +57,7 @@ LTR_TYPES = { "ALS_PS": LtrType.LTR_TYPE_ALS_AND_PS, } -AlsGain = ltr_als_ps_ns.enum("Gain") +AlsGain = ltr_als_ps_ns.enum("AlsGain") ALS_GAINS = { "1X": AlsGain.GAIN_1, "2X": AlsGain.GAIN_2, From 3b7102f6c31458029eb3f1ca1afe9655cbefc428 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Wed, 21 Feb 2024 02:12:43 +0100 Subject: [PATCH 26/32] auto gain fix --- esphome/components/ltr_als_ps/ltr_als_ps.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp index 003f07054d..dac7b751e6 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.cpp +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -374,6 +374,9 @@ void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) { bool LTRAlsPsComponent::are_adjustments_required_(AlsReadings &data) { // skip first sample in auto mode - // we need to reconfigure device after last measurement + if (this->state_ == State::COLLECTING_DATA_AUTO) + return true; + if (!this->automatic_mode_enabled_) return false; From e02459cf913a741d1ff8bea07e38e729d557ea5a Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Wed, 21 Feb 2024 18:09:34 +0100 Subject: [PATCH 27/32] tweaks --- esphome/components/ltr_als_ps/ltr_als_ps.cpp | 49 +++++++++++++++----- esphome/components/ltr_als_ps/ltr_als_ps.h | 1 + 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp index dac7b751e6..4c7375f9c4 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.cpp +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -118,6 +118,7 @@ void LTRAlsPsComponent::update() { this->als_readings_.actual_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_LOGD(TAG, "Component not ready yet"); @@ -159,6 +160,7 @@ void LTRAlsPsComponent::loop() { get_gain_coeff(this->als_readings_.actual_gain), get_itime_ms(this->als_readings_.integration_time)); this->read_sensor_data_(this->als_readings_); this->state_ = State::DATA_COLLECTED; + this->apply_lux_calculation_(this->als_readings_); } else if (tries >= MAX_TRIES) { ESP_LOGW(TAG, "Can't get data after several tries."); tries = 0; @@ -172,16 +174,17 @@ void LTRAlsPsComponent::loop() { 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"); + ESP_LOGD(TAG, "Reconfiguring sensitivity: gain = %.0fx, time = %d ms", + get_gain_coeff(this->als_readings_.actual_gain), get_itime_ms(this->als_readings_.integration_time)); this->configure_integration_time_(this->als_readings_.integration_time); this->configure_gain_(this->als_readings_.actual_gain); // if sensitivity adjustment needed - need to wait for first data samples after setting new parameters this->set_timeout(1 * get_meas_time_ms(this->repeat_rate_), [this]() { this->state_ = State::WAITING_FOR_DATA; }); } else { - this->apply_lux_calculation_(this->als_readings_); this->state_ = State::READY_TO_PUBLISH; } break; @@ -333,6 +336,14 @@ void LTRAlsPsComponent::configure_gain_(AlsGain gain) { als_ctrl.gain = gain; this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw; delay(2); + + AlsControlRegister 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 LTRAlsPsComponent::configure_integration_time_(IntegrationTime time) { @@ -341,6 +352,14 @@ void LTRAlsPsComponent::configure_integration_time_(IntegrationTime time) { meas.integration_time = time; this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw; delay(2); + + MeasurementRateRegister 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 LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) { @@ -354,13 +373,17 @@ DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) { ESP_LOGW(TAG, "Data available but not valid"); return DataAvail::BAD_DATA; } - - // data.actual_gain = als_status.gain; ESP_LOGD(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain)); + if (data.actual_gain != als_status.gain) { + ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.actual_gain)); + return DataAvail::BAD_DATA; + } return DataAvail::DATA_OK; } void LTRAlsPsComponent::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(); @@ -372,14 +395,16 @@ void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) { } bool LTRAlsPsComponent::are_adjustments_required_(AlsReadings &data) { - // skip first sample in auto mode - - // we need to reconfigure device after last measurement - if (this->state_ == State::COLLECTING_DATA_AUTO) - return true; - if (!this->automatic_mode_enabled_) return false; + if (data.number_of_adjustments > 10) { + // sometimes sensors fail to change sensitivity. this prevents us from infinite loop + ESP_LOGW(TAG, "Too many sensitivity adjustments done. Apparently, sensor reconfiguration fails. Stopping."); + return false; + } + data.number_of_adjustments++; + // Recommended thresholds as per datasheet static const uint16_t LOW_INTENSITY_THRESHOLD = 1000; static const uint16_t HIGH_INTENSITY_THRESHOLD = 20000; @@ -443,9 +468,6 @@ void LTRAlsPsComponent::apply_lux_calculation_(AlsReadings &data) { float inv_pfactor = this->glass_attenuation_factor_; float lux = 0.0f; - ESP_LOGD(TAG, "Lux calculation: ratio %f, gain %f, int time %f, inv_pfactor %f", ratio, als_gain, als_time, - inv_pfactor); - if (ratio < 0.45) { lux = (1.7743 * ch0 + 1.1059 * ch1); } else if (ratio < 0.64 && ratio >= 0.45) { @@ -458,6 +480,9 @@ void LTRAlsPsComponent::apply_lux_calculation_(AlsReadings &data) { } lux = inv_pfactor * lux / als_gain / als_time; data.lux = lux; + + ESP_LOGD(TAG, "Lux calculation: ratio %.2f, gain %.0f, int time %.1f, inv_pfactor %.3f, lux %.3f", ratio, als_gain, + als_time, inv_pfactor, lux); } void LTRAlsPsComponent::publish_data_part_1_(AlsReadings &data) { diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.h b/esphome/components/ltr_als_ps/ltr_als_ps.h index 7654f8e9ba..16c3125080 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.h +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -87,6 +87,7 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { AlsGain actual_gain{AlsGain::GAIN_1}; IntegrationTime integration_time{IntegrationTime::INTEGRATION_TIME_100MS}; float lux{0.0f}; + uint8_t number_of_adjustments{0}; } als_readings_; uint16_t ps_readings_{0xfffe}; From 2fa069ea49d3195a4f6c66cf07791936fbafdf66 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Thu, 22 Feb 2024 15:02:36 +0100 Subject: [PATCH 28/32] new style tests --- esphome/components/ltr_als_ps/sensor.py | 89 +++++++++++-------- tests/components/ltr_als_ps/common.yaml | 11 +++ .../ltr_als_ps/test.esp32-c3-idf.yaml | 6 ++ .../components/ltr_als_ps/test.esp32-c3.yaml | 6 ++ .../components/ltr_als_ps/test.esp32-idf.yaml | 6 ++ tests/components/ltr_als_ps/test.esp32.yaml | 6 ++ tests/components/ltr_als_ps/test.esp8266.yaml | 6 ++ tests/components/ltr_als_ps/test.rp2040.yaml | 6 ++ 8 files changed, 101 insertions(+), 35 deletions(-) create mode 100644 tests/components/ltr_als_ps/common.yaml create mode 100644 tests/components/ltr_als_ps/test.esp32-c3-idf.yaml create mode 100644 tests/components/ltr_als_ps/test.esp32-c3.yaml create mode 100644 tests/components/ltr_als_ps/test.esp32-idf.yaml create mode 100644 tests/components/ltr_als_ps/test.esp32.yaml create mode 100644 tests/components/ltr_als_ps/test.esp8266.yaml create mode 100644 tests/components/ltr_als_ps/test.rp2040.yaml diff --git a/esphome/components/ltr_als_ps/sensor.py b/esphome/components/ltr_als_ps/sensor.py index 8554b6f99d..f01455e4b1 100644 --- a/esphome/components/ltr_als_ps/sensor.py +++ b/esphome/components/ltr_als_ps/sensor.py @@ -3,12 +3,13 @@ import esphome.config_validation as cv from esphome import automation from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, CONF_ACTUAL_GAIN, CONF_AUTO_MODE, CONF_GAIN, CONF_GLASS_ATTENUATION_FACTOR, + CONF_ID, CONF_INTEGRATION_TIME, + CONF_NAME, CONF_REPEAT, CONF_TRIGGER_ID, CONF_TYPE, @@ -156,45 +157,63 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsLowTrigger), } ), - cv.Optional(CONF_AMBIENT_LIGHT): 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, + 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): 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, + 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): 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, + 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): sensor.sensor_schema( - unit_of_measurement=UNIT_COUNTS, - icon=ICON_PROXIMITY, - accuracy_decimals=0, - device_class=DEVICE_CLASS_DISTANCE, - state_class=STATE_CLASS_MEASUREMENT, + 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): sensor.sensor_schema( - icon=ICON_GAIN, - accuracy_decimals=0, - device_class=DEVICE_CLASS_ILLUMINANCE, - state_class=STATE_CLASS_MEASUREMENT, + 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): sensor.sensor_schema( - unit_of_measurement=UNIT_MILLISECOND, - icon=ICON_TIMER, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, + 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, ), } ) diff --git a/tests/components/ltr_als_ps/common.yaml b/tests/components/ltr_als_ps/common.yaml new file mode 100644 index 0000000000..aa5c8abed7 --- /dev/null +++ b/tests/components/ltr_als_ps/common.yaml @@ -0,0 +1,11 @@ +sensor: + - platform: ltr_als_ps + address: 0x23 + i2c_id: i2c_als_ps + gain: 1x + integration_time: 100ms + ps_cooldown: 5 s + ambient_light: "Ambient light" + full_spectrum_counts: "Full spectrum counts" + infrared_counts: "Infrared counts" + actual_gain: "Actual gain" diff --git a/tests/components/ltr_als_ps/test.esp32-c3-idf.yaml b/tests/components/ltr_als_ps/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..d64d70f018 --- /dev/null +++ b/tests/components/ltr_als_ps/test.esp32-c3-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.esp32-c3.yaml b/tests/components/ltr_als_ps/test.esp32-c3.yaml new file mode 100644 index 0000000000..d64d70f018 --- /dev/null +++ b/tests/components/ltr_als_ps/test.esp32-c3.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.esp32-idf.yaml b/tests/components/ltr_als_ps/test.esp32-idf.yaml new file mode 100644 index 0000000000..2349292a64 --- /dev/null +++ b/tests/components/ltr_als_ps/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.esp32.yaml b/tests/components/ltr_als_ps/test.esp32.yaml new file mode 100644 index 0000000000..2349292a64 --- /dev/null +++ b/tests/components/ltr_als_ps/test.esp32.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 16 + sda: 17 + +<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.esp8266.yaml b/tests/components/ltr_als_ps/test.esp8266.yaml new file mode 100644 index 0000000000..d64d70f018 --- /dev/null +++ b/tests/components/ltr_als_ps/test.esp8266.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 5 + sda: 4 + +<<: !include common.yaml diff --git a/tests/components/ltr_als_ps/test.rp2040.yaml b/tests/components/ltr_als_ps/test.rp2040.yaml new file mode 100644 index 0000000000..d64d70f018 --- /dev/null +++ b/tests/components/ltr_als_ps/test.rp2040.yaml @@ -0,0 +1,6 @@ +i2c: + - id: i2c_als_ps + scl: 5 + sda: 4 + +<<: !include common.yaml From 14d1f3e2c121d4d8853dd01ce45eb31f1d967624 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Thu, 22 Feb 2024 21:01:19 +0100 Subject: [PATCH 29/32] als/ps separate init --- esphome/components/ltr_als_ps/ltr_als_ps.cpp | 43 +++++++++++--------- esphome/components/ltr_als_ps/ltr_als_ps.h | 4 +- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp index 4c7375f9c4..0632df7163 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.cpp +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -107,9 +107,9 @@ void LTRAlsPsComponent::dump_config() { } void LTRAlsPsComponent::update() { - ESP_LOGD(TAG, "Updating"); + ESP_LOGV(TAG, "Updating"); if (this->is_ready() && this->state_ == State::IDLE) { - ESP_LOGD(TAG, "Initiating new data collection"); + ESP_LOGV(TAG, "Initiating new data collection"); this->state_ = this->automatic_mode_enabled_ ? State::COLLECTING_DATA_AUTO : State::WAITING_FOR_DATA; @@ -121,7 +121,7 @@ void LTRAlsPsComponent::update() { this->als_readings_.number_of_adjustments = 0; } else { - ESP_LOGD(TAG, "Component not ready yet"); + ESP_LOGV(TAG, "Component not ready yet"); } } @@ -133,11 +133,12 @@ void LTRAlsPsComponent::loop() { case State::DELAYED_SETUP: err = this->write(nullptr, 0); if (err != i2c::ERROR_OK) { - ESP_LOGD(TAG, "i2c connection failed"); + ESP_LOGV(TAG, "i2c connection failed"); this->mark_failed(); } - this->configure_reset_and_activate_(); + this->configure_reset_(); if (this->is_als_()) { + this->configure_als_(); this->configure_integration_time_(this->integration_time_); } if (this->is_ps_()) { @@ -156,7 +157,7 @@ void LTRAlsPsComponent::loop() { case State::WAITING_FOR_DATA: if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) { tries = 0; - ESP_LOGD(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", + ESP_LOGV(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.actual_gain), get_itime_ms(this->als_readings_.integration_time)); this->read_sensor_data_(this->als_readings_); this->state_ = State::DATA_COLLECTED; @@ -261,8 +262,8 @@ bool LTRAlsPsComponent::check_part_number_() { return true; } -void LTRAlsPsComponent::configure_reset_and_activate_() { - ESP_LOGD(TAG, "Resetting"); +void LTRAlsPsComponent::configure_reset_() { + ESP_LOGV(TAG, "Resetting"); AlsControlRegister als_ctrl{0}; als_ctrl.sw_reset = true; @@ -271,7 +272,7 @@ void LTRAlsPsComponent::configure_reset_and_activate_() { uint8_t tries = MAX_TRIES; do { - ESP_LOGD(TAG, "Waiting chip to reset"); + 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 @@ -279,18 +280,22 @@ void LTRAlsPsComponent::configure_reset_and_activate_() { if (als_ctrl.sw_reset) { ESP_LOGW(TAG, "Failed to finalize reset procedure"); } +} + +void LTRAlsPsComponent::configure_als_() { + AlsControlRegister als_ctrl{0}; als_ctrl.sw_reset = false; als_ctrl.active_mode = true; als_ctrl.gain = this->gain_; - ESP_LOGD(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw); + 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); - tries = MAX_TRIES; + uint8_t tries = MAX_TRIES; do { - ESP_LOGD(TAG, "Waiting for device to become active..."); + ESP_LOGV(TAG, "Waiting for device to become active..."); delay(2); als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get(); } while (!als_ctrl.active_mode && tries--); // while active mode is not set - keep waiting @@ -323,7 +328,7 @@ uint16_t LTRAlsPsComponent::read_ps_data_() { ps_high.raw = this->reg((uint8_t) CommandRegisters::PS_DATA_1).get(); uint16_t val = encode_uint16(ps_high.ps_data_high, ps_low); - // ESP_LOGD(TAG, "Got sensor data: PS = %5d, Saturation flag = %d", val, ps_high.ps_saturation_flag); + // ESP_LOGV(TAG, "Got sensor data: PS = %5d, Saturation flag = %d", val, ps_high.ps_saturation_flag); if (ps_high.ps_saturation_flag) { return 0x7ff; // full 11 bit range } @@ -373,7 +378,7 @@ DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) { ESP_LOGW(TAG, "Data available but not valid"); return DataAvail::BAD_DATA; } - ESP_LOGD(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain)); + ESP_LOGV(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain)); if (data.actual_gain != als_status.gain) { ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.actual_gain)); return DataAvail::BAD_DATA; @@ -417,26 +422,26 @@ bool LTRAlsPsComponent::are_adjustments_required_(AlsReadings &data) { AlsGain next_gain = get_next(GAINS, data.actual_gain); if (next_gain != data.actual_gain) { data.actual_gain = next_gain; - ESP_LOGD(TAG, "Low illuminance. Increasing gain."); + ESP_LOGV(TAG, "Low illuminance. Increasing gain."); return true; } IntegrationTime next_time = get_next(INT_TIMES, data.integration_time); if (next_time != data.integration_time) { data.integration_time = next_time; - ESP_LOGD(TAG, "Low illuminance. Increasing integration time."); + ESP_LOGV(TAG, "Low illuminance. Increasing integration time."); return true; } } else if (data.ch0 >= HIGH_INTENSITY_THRESHOLD) { AlsGain prev_gain = get_prev(GAINS, data.actual_gain); if (prev_gain != data.actual_gain) { data.actual_gain = prev_gain; - ESP_LOGD(TAG, "High illuminance. Decreasing gain."); + ESP_LOGV(TAG, "High illuminance. Decreasing gain."); return true; } IntegrationTime prev_time = get_prev(INT_TIMES, data.integration_time); if (prev_time != data.integration_time) { data.integration_time = prev_time; - ESP_LOGD(TAG, "High illuminance. Decreasing integration time."); + ESP_LOGV(TAG, "High illuminance. Decreasing integration time."); return true; } } else { @@ -481,7 +486,7 @@ void LTRAlsPsComponent::apply_lux_calculation_(AlsReadings &data) { lux = inv_pfactor * lux / als_gain / als_time; data.lux = lux; - ESP_LOGD(TAG, "Lux calculation: ratio %.2f, gain %.0f, int time %.1f, inv_pfactor %.3f, lux %.3f", ratio, als_gain, + 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); } diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.h b/esphome/components/ltr_als_ps/ltr_als_ps.h index 16c3125080..68ed4592b0 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.h +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -103,8 +103,8 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { // bool check_part_number_(); - void configure_reset_and_activate_(); - + void configure_reset_(); + void configure_als_(); void configure_integration_time_(IntegrationTime time); void configure_gain_(AlsGain gain); DataAvail is_als_data_ready_(AlsReadings &data); From 2e1e0b4666591ed782d1ed098c25f7379aaa7881 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Tue, 27 Feb 2024 10:42:41 +0100 Subject: [PATCH 30/32] logd->logv --- esphome/components/ltr_als_ps/ltr_als_ps.cpp | 44 ++++++++++---------- esphome/components/ltr_als_ps/ltr_als_ps.h | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp index 0632df7163..2ed288abcb 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.cpp +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -115,7 +115,7 @@ void LTRAlsPsComponent::update() { this->als_readings_.ch0 = 0; this->als_readings_.ch1 = 0; - this->als_readings_.actual_gain = this->gain_; + 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; @@ -157,8 +157,8 @@ void LTRAlsPsComponent::loop() { 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 having gain = %.0fx, time = %d ms", - get_gain_coeff(this->als_readings_.actual_gain), get_itime_ms(this->als_readings_.integration_time)); + ESP_LOGV(TAG, "Reading sensor data having 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->state_ = State::DATA_COLLECTED; this->apply_lux_calculation_(this->als_readings_); @@ -178,12 +178,12 @@ void LTRAlsPsComponent::loop() { // 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_.actual_gain), get_itime_ms(this->als_readings_.integration_time)); + 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_.actual_gain); + 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(1 * get_meas_time_ms(this->repeat_rate_), + 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; @@ -221,12 +221,12 @@ void LTRAlsPsComponent::check_and_trigger_ps_() { // 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, + ESP_LOGV(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, + ESP_LOGV(TAG, "Proximity low threshold triggered. Value = %d, Trigger level = %d", ps_data, this->ps_threshold_low_); this->on_ps_low_trigger_callback_.call(); } @@ -379,8 +379,8 @@ DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) { return DataAvail::BAD_DATA; } ESP_LOGV(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain)); - if (data.actual_gain != als_status.gain) { - ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.actual_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; } return DataAvail::DATA_OK; @@ -396,7 +396,7 @@ void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) { 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); + ESP_LOGV(TAG, "Got sensor data: CH1 = %d, CH0 = %d", data.ch1, data.ch0); } bool LTRAlsPsComponent::are_adjustments_required_(AlsReadings &data) { @@ -412,16 +412,16 @@ bool LTRAlsPsComponent::are_adjustments_required_(AlsReadings &data) { // Recommended thresholds as per datasheet static const uint16_t LOW_INTENSITY_THRESHOLD = 1000; - static const uint16_t HIGH_INTENSITY_THRESHOLD = 20000; + static const uint16_t HIGH_INTENSITY_THRESHOLD = 30000; static const AlsGain GAINS[GAINS_COUNT] = {GAIN_1, GAIN_2, GAIN_4, GAIN_8, GAIN_48, GAIN_96}; static const IntegrationTime INT_TIMES[TIMES_COUNT] = { INTEGRATION_TIME_50MS, INTEGRATION_TIME_100MS, INTEGRATION_TIME_150MS, INTEGRATION_TIME_200MS, INTEGRATION_TIME_250MS, INTEGRATION_TIME_300MS, INTEGRATION_TIME_350MS, INTEGRATION_TIME_400MS}; if (data.ch0 <= LOW_INTENSITY_THRESHOLD) { - AlsGain next_gain = get_next(GAINS, data.actual_gain); - if (next_gain != data.actual_gain) { - data.actual_gain = next_gain; + AlsGain next_gain = get_next(GAINS, data.gain); + if (next_gain != data.gain) { + data.gain = next_gain; ESP_LOGV(TAG, "Low illuminance. Increasing gain."); return true; } @@ -432,9 +432,9 @@ bool LTRAlsPsComponent::are_adjustments_required_(AlsReadings &data) { return true; } } else if (data.ch0 >= HIGH_INTENSITY_THRESHOLD) { - AlsGain prev_gain = get_prev(GAINS, data.actual_gain); - if (prev_gain != data.actual_gain) { - data.actual_gain = prev_gain; + AlsGain prev_gain = get_prev(GAINS, data.gain); + if (prev_gain != data.gain) { + data.gain = prev_gain; ESP_LOGV(TAG, "High illuminance. Decreasing gain."); return true; } @@ -468,7 +468,7 @@ void LTRAlsPsComponent::apply_lux_calculation_(AlsReadings &data) { float ch0 = data.ch0; float ch1 = data.ch1; float ratio = ch1 / (ch0 + ch1); - float als_gain = get_gain_coeff(data.actual_gain); + 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; @@ -486,7 +486,7 @@ void LTRAlsPsComponent::apply_lux_calculation_(AlsReadings &data) { 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, + ESP_LOGV(TAG, "Lux calculation: ratio %.3f, gain %.0fx, int time %.1f, inv_pfactor %.3f, lux %.3f", ratio, als_gain, als_time, inv_pfactor, lux); } @@ -507,7 +507,7 @@ void LTRAlsPsComponent::publish_data_part_1_(AlsReadings &data) { void LTRAlsPsComponent::publish_data_part_2_(AlsReadings &data) { if (this->actual_gain_sensor_ != nullptr) { - this->actual_gain_sensor_->publish_state(get_gain_coeff(data.actual_gain)); + 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)); diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.h b/esphome/components/ltr_als_ps/ltr_als_ps.h index 68ed4592b0..4cbbcea54c 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.h +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -84,7 +84,7 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { struct AlsReadings { uint16_t ch0{0}; uint16_t ch1{0}; - AlsGain actual_gain{AlsGain::GAIN_1}; + AlsGain gain{AlsGain::GAIN_1}; IntegrationTime integration_time{IntegrationTime::INTEGRATION_TIME_100MS}; float lux{0.0f}; uint8_t number_of_adjustments{0}; From 8e6b29d2ce6bc77809b5d7335883d17af31a6464 Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Sun, 3 Mar 2024 16:12:10 +0100 Subject: [PATCH 31/32] reconfiguration count changed --- esphome/components/ltr_als_ps/ltr_als_ps.cpp | 35 +++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp index 2ed288abcb..599e67169c 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.cpp +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -84,23 +84,26 @@ void LTRAlsPsComponent::dump_config() { 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_); - + if (this->is_als_()) { + 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_); + 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_ps_()) { + 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_SENSOR(" ", "Proximity counts", this->proximity_counts_sensor_); + } 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-303/329 failed!"); } @@ -403,7 +406,7 @@ bool LTRAlsPsComponent::are_adjustments_required_(AlsReadings &data) { if (!this->automatic_mode_enabled_) return false; - if (data.number_of_adjustments > 10) { + if (data.number_of_adjustments > 15) { // sometimes sensors fail to change sensitivity. this prevents us from infinite loop ESP_LOGW(TAG, "Too many sensitivity adjustments done. Apparently, sensor reconfiguration fails. Stopping."); return false; From d08986560091b3d347f748ca734668ba28477c4b Mon Sep 17 00:00:00 2001 From: Anton Viktorov Date: Tue, 23 Apr 2024 11:50:18 +0200 Subject: [PATCH 32/32] old-style tests removed --- tests/test2.yaml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/test2.yaml b/tests/test2.yaml index 9f58618cb9..2fdef72c08 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -323,20 +323,6 @@ sensor: id: motion_rtcgq02lm battery_level: name: Mi Motion Sensor 2 Battery level - - platform: ltr_als_ps - address: 0x23 - gain: 1x - integration_time: 100ms - ps_cooldown: 5 s - ambient_light: - name: "Ambient light" - full_spectrum_counts: - name: "Full spectrum counts" - infrared_counts: - name: "Infrared counts" - actual_gain: - name: "Actual gain" - update_interval: 60s - platform: ltr390 uv: name: LTR390 UV