ESPHome 2026.8.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
7static const char *const TAG = "veml3235.sensor";
8
9// ADC counts at or above this value (98% of full scale) are treated as clipped: the true light level cannot
10// be estimated from such a reading, so auto-gain restarts from minimum sensitivity instead
11static const uint16_t CLIPPED_COUNTS = 64224;
12
13// Maximum sensitivity multiplier: integration time 800 ms (16x) * gain 4x * digital gain 2x
14static const uint16_t MAX_SENSITIVITY_FACTOR = 128;
15
16// At most one restart from clipping plus one proportional adjustment per update cycle
17static const uint8_t MAX_ADJUSTMENTS_PER_UPDATE = 2;
18
20 uint8_t device_id[] = {0, 0};
21 if (!this->refresh_config_reg()) {
22 ESP_LOGE(TAG, "Unable to write configuration");
23 this->mark_failed();
24 return;
25 }
26 if ((this->read_register(ID_REG, device_id, sizeof device_id) != i2c::ERROR_OK)) {
27 ESP_LOGE(TAG, "Unable to read ID");
28 this->mark_failed();
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();
32 }
33}
34
36 uint16_t data = 0x1; // 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(CONFIG_REG, data);
44}
45
47 if (this->measurement_in_progress_) {
48 ESP_LOGV(TAG, "'%s': Previous measurement still in progress; skipping update", this->get_name().c_str());
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->read_register(ALS_REG, als_regs, sizeof als_regs) != i2c::ERROR_OK)) {
58 this->status_set_warning();
59 this->publish_state(NAN);
60 this->measurement_in_progress_ = false;
61 return;
62 }
63
65
66 uint16_t als_counts = encode_uint16(als_regs[1], als_regs[0]);
67
68 if (this->auto_gain_ && adjustments_left > 0) {
69 // A sample integrated with the previous settings may still be in the data register after the
70 // configuration changes, so wait out the old integration period plus two new ones before re-reading
71 const uint32_t old_integration_time_ms = this->integration_time_ms_();
72 if (this->adjust_sensitivity_(als_counts)) {
73 const uint32_t wait_ms = old_integration_time_ms + 2 * this->integration_time_ms_();
74 this->set_timeout("reread", wait_ms,
75 [this, adjustments_left]() { this->read_and_publish_(adjustments_left - 1); });
76 return;
77 }
78 }
79
80 float lux = this->counts_to_lux_(als_counts);
81 ESP_LOGVV(TAG, "'%s': ALS counts = %u, sensitivity = %ux", this->get_name().c_str(), als_counts,
82 this->sensitivity_factor_());
83 ESP_LOGV(TAG, "'%s': Illuminance = %.4flx", this->get_name().c_str(), lux);
84 this->publish_state(lux);
85 this->measurement_in_progress_ = false;
86}
87
88float VEML3235Sensor::counts_to_lux_(uint16_t counts) const {
89 float resolution = LUX_MULTIPLIER_BASE * (float(MAX_SENSITIVITY_FACTOR) / float(this->sensitivity_factor_()));
90 return float(counts) * resolution;
91}
92
94 switch (this->gain_) {
96 return 4;
98 return 2;
99 default:
100 return 1;
101 }
102}
103
105 const uint8_t digital_gain_factor = this->digital_gain_ == VEML3235_DIGITAL_GAIN_2X ? 2 : 1;
106 return (1 << this->integration_time_) * this->gain_factor_() * digital_gain_factor;
107}
108
110 // The factor is a power of two in [1, 128]. Prefer integration time (improves the signal-to-noise ratio),
111 // then analog gain; digital gain is a plain doubling of the output and is used only as a last resort.
112 uint8_t it_exponent = 0; // integration time is 2^n * 50 ms
113 while (it_exponent < VEML3235_INTEGRATION_TIME_800MS && (1u << it_exponent) < factor) {
114 it_exponent++;
115 }
116 this->integration_time_ = static_cast<VEML3235ComponentIntegrationTime>(it_exponent);
117 factor >>= it_exponent;
118
119 if (factor >= 4) {
120 this->gain_ = VEML3235_GAIN_4X;
121 factor >>= 2;
122 } else if (factor == 2) {
123 this->gain_ = VEML3235_GAIN_2X;
124 factor >>= 1;
125 } else {
126 this->gain_ = VEML3235_GAIN_1X;
127 }
128
130}
131
133 // Test for clipping before the window test: with an upper threshold configured at or above the clip
134 // point, a saturated reading would otherwise count as "in window" and sensitivity would never recover
135 const bool clipped = counts >= CLIPPED_COUNTS;
136 const uint16_t low = uint16_t(UINT16_MAX * this->auto_gain_threshold_low_);
137 const uint16_t high = uint16_t(UINT16_MAX * this->auto_gain_threshold_high_);
138 if (!clipped && counts >= low && counts <= high) {
139 return false;
140 }
141
142 const uint16_t current_factor = this->sensitivity_factor_();
143 uint16_t new_factor;
144 if (clipped) {
145 new_factor = 1;
146 } else if (counts == 0) {
147 new_factor = MAX_SENSITIVITY_FACTOR;
148 } else {
149 // Counts scale linearly with the sensitivity factor: in one step, pick the power of two that puts the
150 // next reading closest below the middle of the configured window. Rounding down means the target is
151 // never overshot, which also keeps the sensitivity stable when the window is narrower than one step.
152 float desired = float(current_factor) * ((float(low) + float(high)) * 0.5f / float(counts));
153 desired = clamp(desired, 1.0f, float(MAX_SENSITIVITY_FACTOR));
154 new_factor = 1;
155 while (new_factor * 2 <= uint16_t(desired)) {
156 new_factor *= 2;
157 }
158 }
159
160 if (new_factor == current_factor) {
161 return false;
162 }
163
164 const VEML3235ComponentIntegrationTime old_integration_time = this->integration_time_;
165 const VEML3235ComponentGain old_gain = this->gain_;
166 const VEML3235ComponentDigitalGain old_digital_gain = this->digital_gain_;
167
168 this->set_sensitivity_factor_(new_factor);
169 if (!this->refresh_config_reg()) {
170 // Keep our state consistent with the device, which still has the old configuration
171 this->integration_time_ = old_integration_time;
172 this->gain_ = old_gain;
173 this->digital_gain_ = old_digital_gain;
174 this->status_set_warning();
175 return false;
176 }
177
178 ESP_LOGV(TAG, "'%s': Sensitivity adjusted from %ux to %ux (ALS counts = %u)", this->get_name().c_str(),
179 current_factor, new_factor, counts);
180 return true;
181}
182
184 const uint8_t digital_gain = this->digital_gain_ == VEML3235_DIGITAL_GAIN_2X ? 2 : 1;
185
186 LOG_SENSOR("", "VEML3235", this);
187 LOG_I2C_DEVICE(this);
188 if (this->is_failed()) {
189 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
190 }
191 LOG_UPDATE_INTERVAL(this);
192 ESP_LOGCONFIG(TAG, " Auto-gain enabled: %s", YESNO(this->auto_gain_));
193 if (this->auto_gain_) {
194 ESP_LOGCONFIG(TAG,
195 " Auto-gain thresholds:\n"
196 " Upper: %.0f%%\n"
197 " Lower: %.0f%%\n"
198 " Values below will be used as initial values only",
199 this->auto_gain_threshold_high_ * 100.0f, this->auto_gain_threshold_low_ * 100.0f);
200 }
201 ESP_LOGCONFIG(TAG,
202 " Digital gain: %uX\n"
203 " Gain: %uX\n"
204 " Integration time: %ums",
206}
207
208} // namespace esphome::veml3235
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:272
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
void status_clear_warning()
Definition component.h:289
const StringRef & get_name() const
Definition entity_base.h:71
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:25
bool write_byte_16(uint8_t a_register, uint16_t data) const
Definition i2c.h:267
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
VEML3235ComponentIntegrationTime integration_time_
Definition veml3235.h:115
float counts_to_lux_(uint16_t counts) const
Definition veml3235.cpp:88
void set_sensitivity_factor_(uint16_t factor)
Definition veml3235.cpp:109
uint16_t integration_time_ms_() const
Definition veml3235.h:108
bool adjust_sensitivity_(uint16_t counts)
Definition veml3235.cpp:132
void read_and_publish_(uint8_t adjustments_left)
Definition veml3235.cpp:55
VEML3235ComponentGain gain_
Definition veml3235.h:114
uint16_t sensitivity_factor_() const
Definition veml3235.cpp:104
VEML3235ComponentDigitalGain digital_gain()
Definition veml3235.h:87
VEML3235ComponentDigitalGain digital_gain_
Definition veml3235.h:113
Resolution resolution
Definition msa3xx.h:1
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:14
VEML3235ComponentIntegrationTime
Definition veml3235.h:39
@ VEML3235_INTEGRATION_TIME_800MS
Definition veml3235.h:44
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:873
static void uint32_t