ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
veml3235.cpp
Go to the documentation of this file.
1#include "veml3235.h"
3#include "esphome/core/log.h"
4
6
7ESPHOME_LOG_TAG(TAG, "veml3235.sensor");
8
9static constexpr uint32_t REREAD_TIMEOUT_ID = 0;
10
11// ADC counts at or above this value (98% of full scale) are treated as clipped: the true light level cannot
12// be estimated from such a reading, so auto-gain restarts from minimum sensitivity instead
13static const uint16_t CLIPPED_COUNTS = 64224;
14
15// Maximum sensitivity multiplier: integration time 800 ms (16x) * gain 4x * digital gain 2x
16static const uint16_t MAX_SENSITIVITY_FACTOR = 128;
17
18// At most one restart from clipping plus one proportional adjustment per update cycle
19static const uint8_t MAX_ADJUSTMENTS_PER_UPDATE = 2;
20
22 uint8_t device_id[] = {0, 0};
23 if (!this->refresh_config_reg()) {
24 this->mark_failed(LOG_STR("Unable to write configuration"));
25 return;
26 }
27 if ((this->read_register(ID_REG, device_id, sizeof device_id) != i2c::ERROR_OK)) {
28 this->mark_failed(LOG_STR("Unable to read ID"));
29 } else if (device_id[0] != DEVICE_ID) {
30 ESP_LOGE(TAG, "Incorrect device ID - expected 0x%.2x, read 0x%.2x", DEVICE_ID, device_id[0]);
31 this->mark_failed(LOG_STR("Incorrect device ID"));
32 }
33}
34
36 uint16_t data = 0x100; // mandatory 1 per RM; shutdown bits cleared (device powered on)
37
38 data |= (uint16_t(this->integration_time_) << CONFIG_REG_IT_BIT);
39 data |= (uint16_t(this->digital_gain_) << CONFIG_REG_DG_BIT);
40 data |= (uint16_t(this->gain_) << CONFIG_REG_G_BIT);
41
42 ESP_LOGVV(TAG, "Writing 0x%.4x to register 0x%.2x", data, CONFIG_REG);
43 return this->write_byte_16_le(CONFIG_REG, data);
44}
45
47 if (this->measurement_in_progress_) {
48 ESP_LOGV(TAG, "'%s': Previous measurement still in progress; skipping update", LOG_STR_ARG(this->get_log_name()));
49 return;
50 }
51 this->measurement_in_progress_ = true;
52 this->read_and_publish_(MAX_ADJUSTMENTS_PER_UPDATE);
53}
54
55void VEML3235Sensor::read_and_publish_(uint8_t adjustments_left) {
56 uint8_t als_regs[] = {0, 0};
57 if (!this->status_comm_result(this->read_register(ALS_REG, als_regs, sizeof als_regs) == i2c::ERROR_OK)) {
58 this->publish_state(NAN);
59 this->measurement_in_progress_ = false;
60 return;
61 }
62
63 uint16_t als_counts = encode_value_le<uint16_t>(als_regs);
64
65 if (this->auto_gain_ && adjustments_left > 0) {
66 // A sample integrated with the previous settings may still be in the data register after the
67 // configuration changes, so wait out the old integration period plus two new ones before re-reading
68 const uint32_t old_integration_time_ms = this->integration_time_ms_();
69 if (this->adjust_sensitivity_(als_counts)) {
70 const uint32_t wait_ms = old_integration_time_ms + 2 * this->integration_time_ms_();
71 this->set_timeout(REREAD_TIMEOUT_ID, wait_ms,
72 [this, adjustments_left]() { this->read_and_publish_(adjustments_left - 1); });
73 return;
74 }
75 }
76
77 float lux = this->counts_to_lux_(als_counts);
78 ESP_LOGVV(TAG, "'%s': ALS counts = %u, sensitivity = %ux", LOG_STR_ARG(this->get_log_name()), als_counts,
79 this->sensitivity_factor_());
80 ESP_LOGV(TAG, "'%s': Illuminance = %.4flx", LOG_STR_ARG(this->get_log_name()), lux);
81 this->publish_state(lux);
82 this->measurement_in_progress_ = false;
83}
84
85float VEML3235Sensor::counts_to_lux_(uint16_t counts) const {
86 float resolution = LUX_MULTIPLIER_BASE * (float(MAX_SENSITIVITY_FACTOR) / float(this->sensitivity_factor_()));
87 return float(counts) * resolution;
88}
89
91 switch (this->gain_) {
93 return 4;
95 return 2;
96 default:
97 return 1;
98 }
99}
100
102 const uint8_t digital_gain_factor = this->digital_gain_ == VEML3235_DIGITAL_GAIN_2X ? 2 : 1;
103 return (1 << this->integration_time_) * this->gain_factor_() * digital_gain_factor;
104}
105
107 // The factor is a power of two in [1, 128]. Prefer integration time (improves the signal-to-noise ratio),
108 // then analog gain; digital gain is a plain doubling of the output and is used only as a last resort.
109 uint8_t it_exponent = 0; // integration time is 2^n * 50 ms
110 while (it_exponent < VEML3235_INTEGRATION_TIME_800MS && (1u << it_exponent) < factor) {
111 it_exponent++;
112 }
113 this->integration_time_ = static_cast<VEML3235ComponentIntegrationTime>(it_exponent);
114 factor >>= it_exponent;
115
116 if (factor >= 4) {
117 this->gain_ = VEML3235_GAIN_4X;
118 factor >>= 2;
119 } else if (factor == 2) {
120 this->gain_ = VEML3235_GAIN_2X;
121 factor >>= 1;
122 } else {
123 this->gain_ = VEML3235_GAIN_1X;
124 }
125
127}
128
130 // Test for clipping before the window test: with an upper threshold configured at or above the clip
131 // point, a saturated reading would otherwise count as "in window" and sensitivity would never recover
132 const bool clipped = counts >= CLIPPED_COUNTS;
133 const uint16_t low = uint16_t(UINT16_MAX * this->auto_gain_threshold_low_);
134 const uint16_t high = uint16_t(UINT16_MAX * this->auto_gain_threshold_high_);
135 if (!clipped && counts >= low && counts <= high) {
136 return false;
137 }
138
139 const uint16_t current_factor = this->sensitivity_factor_();
140 uint16_t new_factor;
141 if (clipped) {
142 new_factor = 1;
143 } else if (counts == 0) {
144 new_factor = MAX_SENSITIVITY_FACTOR;
145 } else {
146 // Counts scale linearly with the sensitivity factor: in one step, pick the power of two that puts the
147 // next reading closest below the middle of the configured window. Rounding down means the target is
148 // never overshot, which also keeps the sensitivity stable when the window is narrower than one step.
149 float desired = float(current_factor) * ((float(low) + float(high)) * 0.5f / float(counts));
150 desired = clamp(desired, 1.0f, float(MAX_SENSITIVITY_FACTOR));
151 new_factor = 1;
152 while (new_factor * 2 <= uint16_t(desired)) {
153 new_factor *= 2;
154 }
155 }
156
157 if (new_factor == current_factor) {
158 return false;
159 }
160
161 const VEML3235ComponentIntegrationTime old_integration_time = this->integration_time_;
162 const VEML3235ComponentGain old_gain = this->gain_;
163 const VEML3235ComponentDigitalGain old_digital_gain = this->digital_gain_;
164
165 this->set_sensitivity_factor_(new_factor);
166 if (!this->refresh_config_reg()) {
167 // Keep our state consistent with the device, which still has the old configuration
168 this->integration_time_ = old_integration_time;
169 this->gain_ = old_gain;
170 this->digital_gain_ = old_digital_gain;
171 this->status_set_warning();
172 return false;
173 }
174
175 ESP_LOGV(TAG, "'%s': Sensitivity adjusted from %ux to %ux (ALS counts = %u)", LOG_STR_ARG(this->get_log_name()),
176 current_factor, new_factor, counts);
177 return true;
178}
179
181 const uint8_t digital_gain = this->digital_gain_ == VEML3235_DIGITAL_GAIN_2X ? 2 : 1;
182
183 LOG_SENSOR("", "VEML3235", this);
184 LOG_I2C_DEVICE(this);
185 LOG_UPDATE_INTERVAL(this);
186 ESP_LOGCONFIG(TAG, " Auto-gain enabled: %s", YESNO(this->auto_gain_));
187 if (this->auto_gain_) {
188 ESP_LOGCONFIG(TAG,
189 " Auto-gain thresholds:\n"
190 " Upper: %.0f%%\n"
191 " Lower: %.0f%%\n"
192 " Values below will be used as initial values only",
193 this->auto_gain_threshold_high_ * 100.0f, this->auto_gain_threshold_low_ * 100.0f);
194 }
195 ESP_LOGCONFIG(TAG,
196 " Digital gain: %uX\n"
197 " Gain: %uX\n"
198 " Integration time: %ums",
200}
201
202} // namespace esphome::veml3235
void mark_failed()
Mark this component as failed.
bool status_comm_result(bool ok)
Clears the warning when ok, else sets it with message (default "Communication failed"); returns ok.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
const LogString * get_log_name() const
Get the name for a "%s" log argument (LOG_STR_ARG).
bool write_byte_16_le(uint8_t a_register, uint16_t data) const
Write a 16-bit value least significant byte first.
Definition i2c.cpp:106
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len)
reads an array of bytes from a specific register in the I²C device
Definition i2c.cpp:28
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
VEML3235ComponentIntegrationTime integration_time_
Definition veml3235.h:111
float counts_to_lux_(uint16_t counts) const
Definition veml3235.cpp:85
void set_sensitivity_factor_(uint16_t factor)
Definition veml3235.cpp:106
uint16_t integration_time_ms_() const
Definition veml3235.h:104
bool adjust_sensitivity_(uint16_t counts)
Definition veml3235.cpp:129
void read_and_publish_(uint8_t adjustments_left)
Definition veml3235.cpp:55
VEML3235ComponentGain gain_
Definition veml3235.h:110
uint16_t sensitivity_factor_() const
Definition veml3235.cpp:101
VEML3235ComponentDigitalGain digital_gain()
Definition veml3235.h:83
VEML3235ComponentDigitalGain digital_gain_
Definition veml3235.h:109
Resolution resolution
Definition msa3xx.h:1
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:15
ESPHOME_LOG_TAG(TAG, "veml3235.sensor")
VEML3235ComponentIntegrationTime
Definition veml3235.h:35
@ VEML3235_INTEGRATION_TIME_800MS
Definition veml3235.h:40
constexpr T encode_value_le(const uint8_t *bytes)
Encode a value from its constituent bytes (from least to most significant) in an array with length si...
Definition helpers.h:1039
static void uint32_t