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