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:
razorback16 2021-10-13 09:45:41 -07:00 committed by GitHub
parent bb86db869a
commit 534ce11d54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 197 additions and 31 deletions

View file

@ -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])

View file

@ -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

View file

@ -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

View file

@ -403,7 +403,7 @@ sensor:
name: Illuminance
color_temperature:
name: Color Temperature
integration_time: 700ms
integration_time: 614ms
gain: 60x
- platform: custom
lambda: |-