mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01: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:
parent
bb86db869a
commit
534ce11d54
4 changed files with 197 additions and 31 deletions
|
@ -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
|
||||
|
|
|
@ -403,7 +403,7 @@ sensor:
|
|||
name: Illuminance
|
||||
color_temperature:
|
||||
name: Color Temperature
|
||||
integration_time: 700ms
|
||||
integration_time: 614ms
|
||||
gain: 60x
|
||||
- platform: custom
|
||||
lambda: |-
|
||||
|
|
Loading…
Reference in a new issue