ESPHome 2025.9.0-dev
Loading...
Searching...
No Matches
tcs34725.cpp
Go to the documentation of this file.
1#include "tcs34725.h"
2#include "esphome/core/hal.h"
4#include "esphome/core/log.h"
5#include <algorithm>
6
7namespace esphome {
8namespace tcs34725 {
9
10static const char *const TAG = "tcs34725";
11
12static const uint8_t TCS34725_ADDRESS = 0x29;
13static const uint8_t TCS34725_COMMAND_BIT = 0x80;
14static const uint8_t TCS34725_REGISTER_ID = TCS34725_COMMAND_BIT | 0x12;
15static const uint8_t TCS34725_REGISTER_ATIME = TCS34725_COMMAND_BIT | 0x01;
16static const uint8_t TCS34725_REGISTER_CONTROL = TCS34725_COMMAND_BIT | 0x0F;
17static const uint8_t TCS34725_REGISTER_ENABLE = TCS34725_COMMAND_BIT | 0x00;
18static const uint8_t TCS34725_REGISTER_CRGBDATAL = TCS34725_COMMAND_BIT | 0x14;
19
21 uint8_t id;
22 if (this->read_register(TCS34725_REGISTER_ID, &id, 1) != i2c::ERROR_OK) {
23 this->mark_failed();
24 return;
25 }
26 if (this->write_config_register_(TCS34725_REGISTER_ATIME, this->integration_reg_) != i2c::ERROR_OK ||
27 this->write_config_register_(TCS34725_REGISTER_CONTROL, this->gain_reg_) != i2c::ERROR_OK) {
28 this->mark_failed();
29 return;
30 }
31 if (this->write_config_register_(TCS34725_REGISTER_ENABLE, 0x01) !=
32 i2c::ERROR_OK) { // Power on (internal oscillator on)
33 this->mark_failed();
34 return;
35 }
36 delay(3);
37 if (this->write_config_register_(TCS34725_REGISTER_ENABLE, 0x03) !=
38 i2c::ERROR_OK) { // Power on (internal oscillator on) + RGBC ADC Enable
39 this->mark_failed();
40 return;
41 }
42}
43
45 ESP_LOGCONFIG(TAG, "TCS34725:");
46 LOG_I2C_DEVICE(this);
47 if (this->is_failed()) {
48 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
49 }
50 LOG_UPDATE_INTERVAL(this);
51
52 LOG_SENSOR(" ", "Clear Channel", this->clear_sensor_);
53 LOG_SENSOR(" ", "Red Channel", this->red_sensor_);
54 LOG_SENSOR(" ", "Green Channel", this->green_sensor_);
55 LOG_SENSOR(" ", "Blue Channel", this->blue_sensor_);
56 LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_);
57 LOG_SENSOR(" ", "Color Temperature", this->color_temperature_sensor_);
58}
60
74void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c) {
75 float sat; /* Digital saturation level */
76
77 this->illuminance_ = NAN;
78 this->color_temperature_ = NAN;
79
80 const float ga = this->glass_attenuation_; // Glass Attenuation Factor
81 static const float DF = 310.f; // Device Factor
82 static const float R_COEF = 0.136f; //
83 static const float G_COEF = 1.f; // used in lux computation
84 static const float B_COEF = -0.444f; //
85 static const float CT_COEF = 3810.f; // Color Temperature Coefficient
86 static const float CT_OFFSET = 1391.f; // Color Temperatuer Offset
87 static const float MAX_ILLUMINANCE = 100000.0f; // Cap illuminance at 100,000 lux
88 static const float MAX_COLOR_TEMPERATURE = 15000.0f; // Maximum expected color temperature in Kelvin
89 static const float MIN_COLOR_TEMPERATURE = 1000.0f; // Maximum reasonable color temperature in Kelvin
90
91 if (c == 0) {
92 return;
93 }
94
95 /* Analog/Digital saturation:
96 *
97 * (a) As light becomes brighter, the clear channel will tend to
98 * saturate first since R+G+B is approximately equal to C.
99 * (b) The TCS34725 accumulates 1024 counts per 2.4ms of integration
100 * time, up to a maximum values of 65535. This means analog
101 * saturation can occur up to an integration time of 153.6ms
102 * (64*2.4ms=153.6ms).
103 * (c) If the integration time is > 153.6ms, digital saturation will
104 * occur before analog saturation. Digital saturation occurs when
105 * the count reaches 65535.
106 */
107 if ((256 - this->integration_reg_) > 63) {
108 /* Track digital saturation */
109 sat = 65535.f;
110 } else {
111 /* Track analog saturation */
112 sat = 1024.f * (256.f - this->integration_reg_);
113 }
114
115 /* Ripple rejection:
116 *
117 * (a) An integration time of 50ms or multiples of 50ms are required to
118 * reject both 50Hz and 60Hz ripple.
119 * (b) If an integration time faster than 50ms is required, you may need
120 * to average a number of samples over a 50ms period to reject ripple
121 * from fluorescent and incandescent light sources.
122 *
123 * Ripple saturation notes:
124 *
125 * (a) If there is ripple in the received signal, the value read from C
126 * will be less than the max, but still have some effects of being
127 * saturated. This means that you can be below the 'sat' value, but
128 * still be saturating. At integration times >150ms this can be
129 * ignored, but <= 150ms you should calculate the 75% saturation
130 * level to avoid this problem.
131 */
132 if (this->integration_time_ < 150) {
133 /* Adjust sat to 75% to avoid analog saturation if atime < 153.6ms */
134 sat -= sat / 4.f;
135 }
136 /* Check for saturation and mark the sample as invalid if true */
137 if (c >= sat) {
138 if (this->integration_time_auto_) {
139 ESP_LOGI(TAG, "Saturation too high, sample discarded, autogain ongoing");
140 return;
141 } else {
142 ESP_LOGW(TAG,
143 "Saturation too high, sample with saturation %.1f and clear %d lux/color temperature cannot reliably "
144 "calculated, reduce integration/gain or use a grey filter.",
145 sat, c);
146 return;
147 }
148 }
149
150 // Lux Calculation (DN40 3.2)
151
152 float g1 = R_COEF * (float) r + G_COEF * (float) g + B_COEF * (float) b;
153 float cpl = (this->integration_time_ * this->gain_) / (ga * DF);
154
155 this->illuminance_ = std::max(g1 / cpl, 0.0f);
156
157 if (this->illuminance_ > MAX_ILLUMINANCE) {
158 ESP_LOGW(TAG, "Calculated illuminance greater than limit (%f), setting to NAN", this->illuminance_);
159 this->illuminance_ = NAN;
160 return;
161 }
162
163 if (r == 0) {
164 ESP_LOGW(TAG, "Red channel is zero, cannot compute color temperature");
165 return;
166 }
167
168 // Color Temperature Calculation (DN40)
169 /* A simple method of measuring color temp is to use the ratio of blue */
170 /* to red light. */
171
172 this->color_temperature_ = (CT_COEF * (float) b) / (float) r + CT_OFFSET;
173
174 // Ensure the color temperature stays within reasonable bounds
175 if (this->color_temperature_ < MIN_COLOR_TEMPERATURE) {
176 ESP_LOGW(TAG, "Calculated color temperature value too low (%f), setting to NAN", this->color_temperature_);
177 this->color_temperature_ = NAN;
178 } else if (this->color_temperature_ > MAX_COLOR_TEMPERATURE) {
179 ESP_LOGW(TAG, "Calculated color temperature value too high (%f), setting to NAN", this->color_temperature_);
180 this->color_temperature_ = NAN;
181 }
182}
183
185 uint8_t data[8]; // Buffer to hold the 8 bytes (2 bytes for each of the 4 channels)
186
187 // Perform burst
188 if (this->read_register(TCS34725_REGISTER_CRGBDATAL, data, 8) != i2c::ERROR_OK) {
189 this->status_set_warning();
190 ESP_LOGW(TAG, "Error reading TCS34725 sensor data");
191 return;
192 }
193
194 // Extract the data
195 uint16_t raw_c = encode_uint16(data[1], data[0]); // Clear channel
196 uint16_t raw_r = encode_uint16(data[3], data[2]); // Red channel
197 uint16_t raw_g = encode_uint16(data[5], data[4]); // Green channel
198 uint16_t raw_b = encode_uint16(data[7], data[6]); // Blue channel
199
200 ESP_LOGV(TAG, "Raw values clear=%d red=%d green=%d blue=%d", raw_c, raw_r, raw_g, raw_b);
201
202 float channel_c;
203 float channel_r;
204 float channel_g;
205 float channel_b;
206 // avoid division by 0 and return black if clear is 0
207 if (raw_c == 0) {
208 channel_c = channel_r = channel_g = channel_b = 0.0f;
209 } else {
210 float max_count = this->integration_time_ <= 153.6f ? this->integration_time_ * 1024.0f / 2.4f : 65535.0f;
211 float sum = raw_c;
212 channel_r = raw_r / sum * 100.0f;
213 channel_g = raw_g / sum * 100.0f;
214 channel_b = raw_b / sum * 100.0f;
215 channel_c = raw_c / max_count * 100.0f;
216 }
217
218 if (this->clear_sensor_ != nullptr)
219 this->clear_sensor_->publish_state(channel_c);
220 if (this->red_sensor_ != nullptr)
221 this->red_sensor_->publish_state(channel_r);
222 if (this->green_sensor_ != nullptr)
223 this->green_sensor_->publish_state(channel_g);
224 if (this->blue_sensor_ != nullptr)
225 this->blue_sensor_->publish_state(channel_b);
226
228 calculate_temperature_and_lux_(raw_r, raw_g, raw_b, raw_c);
229 }
230
231 // do not publish values if auto gain finding ongoing, and oversaturated
232 // so: publish when:
233 // - not auto mode
234 // - clear not oversaturated
235 // - clear oversaturated but gain and timing cannot go lower
236 if (!this->integration_time_auto_ || raw_c < 65530 || (this->gain_reg_ == 0 && this->integration_time_ < 200)) {
237 if (this->illuminance_sensor_ != nullptr)
239
240 if (this->color_temperature_sensor_ != nullptr)
242 }
243
244 ESP_LOGD(TAG,
245 "Got Red=%.1f%%,Green=%.1f%%,Blue=%.1f%%,Clear=%.1f%% Illuminance=%.1flx Color "
246 "Temperature=%.1fK",
247 channel_r, channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_);
248
249 if (this->integration_time_auto_) {
250 // change integration time an gain to achieve maximum resolution an dynamic range
251 // calculate optimal integration time to achieve 70% satuaration
252 float integration_time_ideal;
253
254 integration_time_ideal = 60 / ((float) std::max((uint16_t) 1, raw_c) / 655.35f) * this->integration_time_;
255
256 uint8_t gain_reg_val_new = this->gain_reg_;
257 // increase gain if less than 20% of white channel used and high integration time
258 // increase only if not already maximum
259 // do not use max gain, as ist will not get better
260 if (this->gain_reg_ < 3) {
261 if (((float) raw_c / 655.35 < 20.f) && (this->integration_time_ > 600.f)) {
262 gain_reg_val_new = this->gain_reg_ + 1;
263 // update integration time to new situation
264 integration_time_ideal = integration_time_ideal / 4;
265 }
266 }
267
268 // decrease gain, if very high clear values and integration times alreadey low
269 if (this->gain_reg_ > 0) {
270 if (70 < ((float) raw_c / 655.35) && (this->integration_time_ < 200)) {
271 gain_reg_val_new = this->gain_reg_ - 1;
272 // update integration time to new situation
273 integration_time_ideal = integration_time_ideal * 4;
274 }
275 }
276
277 // saturate integration times
278 float integration_time_next = integration_time_ideal;
279 if (integration_time_ideal > 2.4f * 256) {
280 integration_time_next = 2.4f * 256;
281 }
282 if (integration_time_ideal < 154) {
283 integration_time_next = 154;
284 }
285
286 // calculate register value from timing
287 uint8_t regval_atime = (uint8_t) (256.f - integration_time_next / 2.4f);
288 ESP_LOGD(TAG, "Integration time: %.1fms, ideal: %.1fms regval_new %d Gain: %.f Clear channel raw: %d gain reg: %d",
289 this->integration_time_, integration_time_next, regval_atime, this->gain_, raw_c, this->gain_reg_);
290
291 if (this->integration_reg_ != regval_atime || gain_reg_val_new != this->gain_reg_) {
292 this->integration_reg_ = regval_atime;
293 this->gain_reg_ = gain_reg_val_new;
294 set_gain((TCS34725Gain) gain_reg_val_new);
295 if (this->write_config_register_(TCS34725_REGISTER_ATIME, this->integration_reg_) != i2c::ERROR_OK ||
296 this->write_config_register_(TCS34725_REGISTER_CONTROL, this->gain_reg_) != i2c::ERROR_OK) {
297 this->mark_failed();
298 ESP_LOGW(TAG, "TCS34725I update timing failed!");
299 } else {
300 this->integration_time_ = integration_time_next;
301 }
302 }
303 }
304 this->status_clear_warning();
305}
307 // if an integration time is 0x100, this is auto start with 154ms as this gives best starting point
308 TCS34725IntegrationTime my_integration_time_regval;
309
311 this->integration_time_auto_ = true;
312 this->integration_reg_ = TCS34725_INTEGRATION_TIME_154MS;
313 my_integration_time_regval = TCS34725_INTEGRATION_TIME_154MS;
314 } else {
315 this->integration_reg_ = integration_time;
316 my_integration_time_regval = integration_time;
317 this->integration_time_auto_ = false;
318 }
319 this->integration_time_ = (256.f - my_integration_time_regval) * 2.4f;
320 ESP_LOGI(TAG, "TCS34725I Integration time set to: %.1fms", this->integration_time_);
321}
323 this->gain_reg_ = gain;
324 switch (gain) {
326 this->gain_ = 1.f;
327 break;
329 this->gain_ = 4.f;
330 break;
332 this->gain_ = 16.f;
333 break;
335 this->gain_ = 60.f;
336 break;
337 default:
338 this->gain_ = 1.f;
339 break;
340 }
341}
342
344 // The Glass Attenuation (FA) factor used to compensate for lower light
345 // levels at the device due to the possible presence of glass. The GA is
346 // the inverse of the glass transmissivity (T), so GA = 1/T. A transmissivity
347 // of 50% gives GA = 1 / 0.50 = 2. If no glass is present, use GA = 1.
348 // See Application Note: DN40-Rev 1.0
349 this->glass_attenuation_ = ga;
350}
351
352} // namespace tcs34725
353} // namespace esphome
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_set_warning(const char *message=nullptr)
void status_clear_warning()
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop=true)
reads an array of bytes from a specific register in the I²C device
Definition i2c.cpp:10
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:45
float get_setup_priority() const override
Definition tcs34725.cpp:59
sensor::Sensor * illuminance_sensor_
Definition tcs34725.h:74
i2c::ErrorCode write_config_register_(uint8_t a_register, uint8_t data)
Definition tcs34725.h:67
void set_integration_time(TCS34725IntegrationTime integration_time)
Definition tcs34725.cpp:306
void set_gain(TCS34725Gain gain)
Definition tcs34725.cpp:322
sensor::Sensor * color_temperature_sensor_
Definition tcs34725.h:75
AlsGain501 gain
IntegrationTime501 integration_time
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:13
FLAG_HAS_TRANSITION float
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:50
@ TCS34725_INTEGRATION_TIME_154MS
Definition tcs34725.h:16
@ TCS34725_INTEGRATION_TIME_AUTO
Definition tcs34725.h:29
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
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:173
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
T id(T value)
Helper function to make id(var) known from lambdas work in custom components.
Definition helpers.h:933