mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	TCS34725 BugFix and GA factor (#2445)
- Fixed endianness bug on tcs34725 data read - Fixed lux adjustments based on gain, integration time and GA factor - Added glass attenuation factor to allow using this sensor behind semi transparent glass Co-authored-by: Razorback16 <razorback16@users.noreply.github.com>
This commit is contained in:
		| @@ -6,6 +6,7 @@ from esphome.const import ( | ||||
|     CONF_GAIN, | ||||
|     CONF_ID, | ||||
|     CONF_ILLUMINANCE, | ||||
|     CONF_GLASS_ATTENUATION_FACTOR, | ||||
|     CONF_INTEGRATION_TIME, | ||||
|     DEVICE_CLASS_ILLUMINANCE, | ||||
|     ICON_LIGHTBULB, | ||||
| @@ -34,8 +35,20 @@ TCS34725_INTEGRATION_TIMES = { | ||||
|     "24ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_24MS, | ||||
|     "50ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_50MS, | ||||
|     "101ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_101MS, | ||||
|     "120ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_120MS, | ||||
|     "154ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_154MS, | ||||
|     "700ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_700MS, | ||||
|     "180ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_180MS, | ||||
|     "199ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_199MS, | ||||
|     "240ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_240MS, | ||||
|     "300ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_300MS, | ||||
|     "360ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_360MS, | ||||
|     "401ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_401MS, | ||||
|     "420ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_420MS, | ||||
|     "480ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_480MS, | ||||
|     "499ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_499MS, | ||||
|     "540ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_540MS, | ||||
|     "600ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_600MS, | ||||
|     "614ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_614MS, | ||||
| } | ||||
|  | ||||
| TCS34725Gain = tcs34725_ns.enum("TCS34725Gain") | ||||
| @@ -79,6 +92,9 @@ CONFIG_SCHEMA = ( | ||||
|                 TCS34725_INTEGRATION_TIMES, lower=True | ||||
|             ), | ||||
|             cv.Optional(CONF_GAIN, default="1X"): cv.enum(TCS34725_GAINS, upper=True), | ||||
|             cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range( | ||||
|                 min=1.0 | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
| @@ -93,6 +109,7 @@ async def to_code(config): | ||||
|  | ||||
|     cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) | ||||
|     cg.add(var.set_gain(config[CONF_GAIN])) | ||||
|     cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) | ||||
|  | ||||
|     if CONF_RED_CHANNEL in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_RED_CHANNEL]) | ||||
|   | ||||
| @@ -26,10 +26,8 @@ void TCS34725Component::setup() { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   uint8_t integration_reg = this->integration_time_; | ||||
|   uint8_t gain_reg = this->gain_; | ||||
|   if (!this->write_byte(TCS34725_REGISTER_ATIME, integration_reg) || | ||||
|       !this->write_byte(TCS34725_REGISTER_CONTROL, gain_reg)) { | ||||
|   if (!this->write_byte(TCS34725_REGISTER_ATIME, this->integration_reg_) || | ||||
|       !this->write_byte(TCS34725_REGISTER_CONTROL, this->gain_reg_)) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
| @@ -61,6 +59,114 @@ void TCS34725Component::dump_config() { | ||||
|   LOG_SENSOR("  ", "Color Temperature", this->color_temperature_sensor_); | ||||
| } | ||||
| float TCS34725Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
|  | ||||
| /*! | ||||
|  *  @brief  Converts the raw R/G/B values to color temperature in degrees | ||||
|  *          Kelvin using the algorithm described in DN40 from Taos (now AMS). | ||||
|  *  @param  r | ||||
|  *          Red value | ||||
|  *  @param  g | ||||
|  *          Green value | ||||
|  *  @param  b | ||||
|  *          Blue value | ||||
|  *  @param  c | ||||
|  *          Clear channel value | ||||
|  *  @return Color temperature in degrees Kelvin | ||||
|  */ | ||||
| void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c) { | ||||
|   float r2, g2, b2; /* RGB values minus IR component */ | ||||
|   float sat;        /* Digital saturation level */ | ||||
|   float ir;         /* Inferred IR content */ | ||||
|  | ||||
|   this->illuminance_ = 0;  // Assign 0 value before calculation | ||||
|   this->color_temperature_ = 0; | ||||
|  | ||||
|   const float ga = this->glass_attenuation_;  // Glass Attenuation Factor | ||||
|   static const float DF = 310.f;              // Device Factor | ||||
|   static const float R_COEF = 0.136f;         // | ||||
|   static const float G_COEF = 1.f;            // used in lux computation | ||||
|   static const float B_COEF = -0.444f;        // | ||||
|   static const float CT_COEF = 3810.f;        // Color Temperature Coefficient | ||||
|   static const float CT_OFFSET = 1391.f;      // Color Temperatuer Offset | ||||
|  | ||||
|   if (c == 0) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   /* Analog/Digital saturation: | ||||
|    * | ||||
|    * (a) As light becomes brighter, the clear channel will tend to | ||||
|    *     saturate first since R+G+B is approximately equal to C. | ||||
|    * (b) The TCS34725 accumulates 1024 counts per 2.4ms of integration | ||||
|    *     time, up to a maximum values of 65535. This means analog | ||||
|    *     saturation can occur up to an integration time of 153.6ms | ||||
|    *     (64*2.4ms=153.6ms). | ||||
|    * (c) If the integration time is > 153.6ms, digital saturation will | ||||
|    *     occur before analog saturation. Digital saturation occurs when | ||||
|    *     the count reaches 65535. | ||||
|    */ | ||||
|   if ((256 - this->integration_reg_) > 63) { | ||||
|     /* Track digital saturation */ | ||||
|     sat = 65535.f; | ||||
|   } else { | ||||
|     /* Track analog saturation */ | ||||
|     sat = 1024.f * (256.f - this->integration_reg_); | ||||
|   } | ||||
|  | ||||
|   /* Ripple rejection: | ||||
|    * | ||||
|    * (a) An integration time of 50ms or multiples of 50ms are required to | ||||
|    *     reject both 50Hz and 60Hz ripple. | ||||
|    * (b) If an integration time faster than 50ms is required, you may need | ||||
|    *     to average a number of samples over a 50ms period to reject ripple | ||||
|    *     from fluorescent and incandescent light sources. | ||||
|    * | ||||
|    * Ripple saturation notes: | ||||
|    * | ||||
|    * (a) If there is ripple in the received signal, the value read from C | ||||
|    *     will be less than the max, but still have some effects of being | ||||
|    *     saturated. This means that you can be below the 'sat' value, but | ||||
|    *     still be saturating. At integration times >150ms this can be | ||||
|    *     ignored, but <= 150ms you should calculate the 75% saturation | ||||
|    *     level to avoid this problem. | ||||
|    */ | ||||
|   if (this->integration_time_ < 150) { | ||||
|     /* Adjust sat to 75% to avoid analog saturation if atime < 153.6ms */ | ||||
|     sat -= sat / 4.f; | ||||
|   } | ||||
|  | ||||
|   /* Check for saturation and mark the sample as invalid if true */ | ||||
|   if (c >= sat) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   /* AMS RGB sensors have no IR channel, so the IR content must be */ | ||||
|   /* calculated indirectly. */ | ||||
|   ir = ((r + g + b) > c) ? (r + g + b - c) / 2 : 0; | ||||
|  | ||||
|   /* Remove the IR component from the raw RGB values */ | ||||
|   r2 = r - ir; | ||||
|   g2 = g - ir; | ||||
|   b2 = b - ir; | ||||
|  | ||||
|   if (r2 == 0) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Lux Calculation (DN40 3.2) | ||||
|  | ||||
|   float g1 = R_COEF * r2 + G_COEF * g2 + B_COEF * b2; | ||||
|   float cpl = (this->integration_time_ * this->gain_) / (ga * DF); | ||||
|   this->illuminance_ = g1 / cpl; | ||||
|  | ||||
|   // Color Temperature Calculation (DN40) | ||||
|   /* A simple method of measuring color temp is to use the ratio of blue */ | ||||
|   /* to red light, taking IR cancellation into account. */ | ||||
|   this->color_temperature_ = (CT_COEF * b2) / /** Color temp coefficient. */ | ||||
|                                  r2 + | ||||
|                              CT_OFFSET; /** Color temp offset. */ | ||||
| } | ||||
|  | ||||
| void TCS34725Component::update() { | ||||
|   uint16_t raw_c; | ||||
|   uint16_t raw_r; | ||||
| @@ -74,6 +180,12 @@ void TCS34725Component::update() { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // May need to fix endianness as the data read over I2C is big-endian, but most ESP platforms are little-endian | ||||
|   raw_c = i2c::i2ctohs(raw_c); | ||||
|   raw_r = i2c::i2ctohs(raw_r); | ||||
|   raw_g = i2c::i2ctohs(raw_g); | ||||
|   raw_b = i2c::i2ctohs(raw_b); | ||||
|  | ||||
|   const float channel_c = raw_c / 655.35f; | ||||
|   const float channel_r = raw_r / 655.35f; | ||||
|   const float channel_g = raw_g / 655.35f; | ||||
| @@ -87,38 +199,54 @@ void TCS34725Component::update() { | ||||
|   if (this->blue_sensor_ != nullptr) | ||||
|     this->blue_sensor_->publish_state(channel_b); | ||||
|  | ||||
|   // Formulae taken from Adafruit TCS35725 library | ||||
|   float illuminance = (-0.32466f * channel_r) + (1.57837f * channel_g) + (-0.73191f * channel_b); | ||||
|   if (this->illuminance_sensor_ || this->color_temperature_sensor_) { | ||||
|     calculate_temperature_and_lux_(raw_r, raw_g, raw_b, raw_c); | ||||
|   } | ||||
|  | ||||
|   if (this->illuminance_sensor_ != nullptr) | ||||
|     this->illuminance_sensor_->publish_state(illuminance); | ||||
|     this->illuminance_sensor_->publish_state(this->illuminance_); | ||||
|  | ||||
|   // Color temperature | ||||
|   // 1. Convert RGB to XYZ color space | ||||
|   const float x = (-0.14282f * raw_r) + (1.54924f * raw_g) + (-0.95641f * raw_b); | ||||
|   const float y = (-0.32466f * raw_r) + (1.57837f * raw_g) + (-0.73191f * raw_b); | ||||
|   const float z = (-0.68202f * raw_r) + (0.77073f * raw_g) + (0.56332f * raw_b); | ||||
|  | ||||
|   // 2. Calculate chromacity coordinates | ||||
|   const float xc = (x) / (x + y + z); | ||||
|   const float yc = (y) / (x + y + z); | ||||
|  | ||||
|   // 3. Use McCamy's formula to determine the color temperature | ||||
|   const float n = (xc - 0.3320f) / (0.1858f - yc); | ||||
|  | ||||
|   // 4. final color temperature in Kelvin. | ||||
|   const float color_temperature = (449.0f * powf(n, 3.0f)) + (3525.0f * powf(n, 2.0f)) + (6823.3f * n) + 5520.33f; | ||||
|   if (this->color_temperature_sensor_ != nullptr) | ||||
|     this->color_temperature_sensor_->publish_state(color_temperature); | ||||
|     this->color_temperature_sensor_->publish_state(this->color_temperature_); | ||||
|  | ||||
|   ESP_LOGD(TAG, "Got R=%.1f%%,G=%.1f%%,B=%.1f%%,C=%.1f%% Illuminance=%.1flx Color Temperature=%.1fK", channel_r, | ||||
|            channel_g, channel_b, channel_c, illuminance, color_temperature); | ||||
|            channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_); | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
| } | ||||
| void TCS34725Component::set_integration_time(TCS34725IntegrationTime integration_time) { | ||||
|   this->integration_time_ = integration_time; | ||||
|   this->integration_reg_ = integration_time; | ||||
|   this->integration_time_ = (256.f - integration_time) * 2.4f; | ||||
| } | ||||
| void TCS34725Component::set_gain(TCS34725Gain gain) { | ||||
|   this->gain_reg_ = gain; | ||||
|   switch (gain) { | ||||
|     case TCS34725Gain::TCS34725_GAIN_1X: | ||||
|       this->gain_ = 1.f; | ||||
|       break; | ||||
|     case TCS34725Gain::TCS34725_GAIN_4X: | ||||
|       this->gain_ = 4.f; | ||||
|       break; | ||||
|     case TCS34725Gain::TCS34725_GAIN_16X: | ||||
|       this->gain_ = 16.f; | ||||
|       break; | ||||
|     case TCS34725Gain::TCS34725_GAIN_60X: | ||||
|       this->gain_ = 60.f; | ||||
|       break; | ||||
|     default: | ||||
|       this->gain_ = 1.f; | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void TCS34725Component::set_glass_attenuation_factor(float ga) { | ||||
|   // The Glass Attenuation (FA) factor used to compensate for lower light | ||||
|   // levels at the device due to the possible presence of glass. The GA is | ||||
|   // the inverse of the glass transmissivity (T), so GA = 1/T. A transmissivity | ||||
|   // of 50% gives GA = 1 / 0.50 = 2. If no glass is present, use GA = 1. | ||||
|   // See Application Note: DN40-Rev 1.0 | ||||
|   this->glass_attenuation_ = ga; | ||||
| } | ||||
| void TCS34725Component::set_gain(TCS34725Gain gain) { this->gain_ = gain; } | ||||
|  | ||||
| }  // namespace tcs34725 | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -12,8 +12,20 @@ enum TCS34725IntegrationTime { | ||||
|   TCS34725_INTEGRATION_TIME_24MS = 0xF6, | ||||
|   TCS34725_INTEGRATION_TIME_50MS = 0xEB, | ||||
|   TCS34725_INTEGRATION_TIME_101MS = 0xD5, | ||||
|   TCS34725_INTEGRATION_TIME_120MS = 0xCE, | ||||
|   TCS34725_INTEGRATION_TIME_154MS = 0xC0, | ||||
|   TCS34725_INTEGRATION_TIME_700MS = 0x00, | ||||
|   TCS34725_INTEGRATION_TIME_180MS = 0xB5, | ||||
|   TCS34725_INTEGRATION_TIME_199MS = 0xAD, | ||||
|   TCS34725_INTEGRATION_TIME_240MS = 0x9C, | ||||
|   TCS34725_INTEGRATION_TIME_300MS = 0x83, | ||||
|   TCS34725_INTEGRATION_TIME_360MS = 0x6A, | ||||
|   TCS34725_INTEGRATION_TIME_401MS = 0x59, | ||||
|   TCS34725_INTEGRATION_TIME_420MS = 0x51, | ||||
|   TCS34725_INTEGRATION_TIME_480MS = 0x38, | ||||
|   TCS34725_INTEGRATION_TIME_499MS = 0x30, | ||||
|   TCS34725_INTEGRATION_TIME_540MS = 0x1F, | ||||
|   TCS34725_INTEGRATION_TIME_600MS = 0x06, | ||||
|   TCS34725_INTEGRATION_TIME_614MS = 0x00, | ||||
| }; | ||||
|  | ||||
| enum TCS34725Gain { | ||||
| @@ -27,6 +39,7 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void set_integration_time(TCS34725IntegrationTime integration_time); | ||||
|   void set_gain(TCS34725Gain gain); | ||||
|   void set_glass_attenuation_factor(float ga); | ||||
|  | ||||
|   void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_sensor_ = clear_sensor; } | ||||
|   void set_red_sensor(sensor::Sensor *red_sensor) { red_sensor_ = red_sensor; } | ||||
| @@ -49,8 +62,16 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { | ||||
|   sensor::Sensor *blue_sensor_{nullptr}; | ||||
|   sensor::Sensor *illuminance_sensor_{nullptr}; | ||||
|   sensor::Sensor *color_temperature_sensor_{nullptr}; | ||||
|   TCS34725IntegrationTime integration_time_{TCS34725_INTEGRATION_TIME_2_4MS}; | ||||
|   TCS34725Gain gain_{TCS34725_GAIN_1X}; | ||||
|   float integration_time_{2.4}; | ||||
|   float gain_{1.0}; | ||||
|   float glass_attenuation_{1.0}; | ||||
|   float illuminance_; | ||||
|   float color_temperature_; | ||||
|  | ||||
|  private: | ||||
|   void calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c); | ||||
|   uint8_t integration_reg_{TCS34725_INTEGRATION_TIME_2_4MS}; | ||||
|   uint8_t gain_reg_{TCS34725_GAIN_1X}; | ||||
| }; | ||||
|  | ||||
| }  // namespace tcs34725 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user