tcs34725: implement CIE1931 colors

This commit is contained in:
RubenKelevra 2024-10-17 23:03:54 +02:00
parent d349a67364
commit c53c277a0d
10 changed files with 144 additions and 3 deletions

View file

@ -17,6 +17,8 @@ from esphome.const import (
UNIT_KELVIN, UNIT_KELVIN,
UNIT_LUX, UNIT_LUX,
UNIT_IRRADIANCE, UNIT_IRRADIANCE,
UNIT_EMPTY,
ICON_COLOR,
) )
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
@ -29,6 +31,9 @@ CONF_RED_CHANNEL_IRRADIANCE = "red_channel_irradiance"
CONF_GREEN_CHANNEL_IRRADIANCE = "green_channel_irradiance" CONF_GREEN_CHANNEL_IRRADIANCE = "green_channel_irradiance"
CONF_BLUE_CHANNEL_IRRADIANCE = "blue_channel_irradiance" CONF_BLUE_CHANNEL_IRRADIANCE = "blue_channel_irradiance"
CONF_SENSOR_SATURATION = "sensor_saturation" CONF_SENSOR_SATURATION = "sensor_saturation"
CONF_CIE1931_X = "cie1931_x"
CONF_CIE1931_Y = "cie1931_y"
CONF_CIE1931_Z = "cie1931_z"
tcs34725_ns = cg.esphome_ns.namespace("tcs34725") tcs34725_ns = cg.esphome_ns.namespace("tcs34725")
TCS34725Component = tcs34725_ns.class_( TCS34725Component = tcs34725_ns.class_(
@ -90,6 +95,12 @@ illuminance_schema = sensor.sensor_schema(
device_class=DEVICE_CLASS_ILLUMINANCE, device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
) )
cie1931_schema = sensor.sensor_schema(
unit_of_measurement=UNIT_EMPTY,
icon=ICON_COLOR,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
)
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
cv.Schema( cv.Schema(
@ -113,6 +124,9 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_SENSOR_SATURATION): sensor_saturation_schema, cv.Optional(CONF_SENSOR_SATURATION): sensor_saturation_schema,
cv.Optional(CONF_ILLUMINANCE): illuminance_schema, cv.Optional(CONF_ILLUMINANCE): illuminance_schema,
cv.Optional(CONF_COLOR_TEMPERATURE): color_temperature_schema, cv.Optional(CONF_COLOR_TEMPERATURE): color_temperature_schema,
cv.Optional(CONF_CIE1931_X): cie1931_schema,
cv.Optional(CONF_CIE1931_Y): cie1931_schema,
cv.Optional(CONF_CIE1931_Z): cie1931_schema,
cv.Optional(CONF_INTEGRATION_TIME, default="auto"): cv.enum( cv.Optional(CONF_INTEGRATION_TIME, default="auto"): cv.enum(
TCS34725_INTEGRATION_TIMES, lower=True TCS34725_INTEGRATION_TIMES, lower=True
), ),
@ -136,6 +150,15 @@ async def to_code(config):
cg.add(var.set_gain(config[CONF_GAIN])) cg.add(var.set_gain(config[CONF_GAIN]))
cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR])) cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR]))
if CONF_CIE1931_X in config:
sens = await sensor.new_sensor(config[CONF_CIE1931_X])
cg.add(var.set_cie1931_x_sensor(sens))
if CONF_CIE1931_Y in config:
sens = await sensor.new_sensor(config[CONF_CIE1931_Y])
cg.add(var.set_cie1931_y_sensor(sens))
if CONF_CIE1931_Z in config:
sens = await sensor.new_sensor(config[CONF_CIE1931_Z])
cg.add(var.set_cie1931_z_sensor(sens))
if CONF_RED_CHANNEL_IRRADIANCE in config: if CONF_RED_CHANNEL_IRRADIANCE in config:
sens = await sensor.new_sensor(config[CONF_RED_CHANNEL_IRRADIANCE]) sens = await sensor.new_sensor(config[CONF_RED_CHANNEL_IRRADIANCE])
cg.add(var.set_red_irradiance_sensor(sens)) cg.add(var.set_red_irradiance_sensor(sens))

View file

@ -58,6 +58,9 @@ void TCS34725Component::dump_config() {
LOG_SENSOR(" ", "Red Channel Irradiance", this->red_irradiance_sensor_); LOG_SENSOR(" ", "Red Channel Irradiance", this->red_irradiance_sensor_);
LOG_SENSOR(" ", "Green Channel Irradiance", this->green_irradiance_sensor_); LOG_SENSOR(" ", "Green Channel Irradiance", this->green_irradiance_sensor_);
LOG_SENSOR(" ", "Blue Channel Irradiance", this->blue_irradiance_sensor_); LOG_SENSOR(" ", "Blue Channel Irradiance", this->blue_irradiance_sensor_);
LOG_SENSOR(" ", "CIE1931 X", this->cie1931_x_sensor_);
LOG_SENSOR(" ", "CIE1931 Y", this->cie1931_y_sensor_);
LOG_SENSOR(" ", "CIE1931 Z", this->cie1931_z_sensor_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_); LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_);
LOG_SENSOR(" ", "Color Temperature", this->color_temperature_sensor_); LOG_SENSOR(" ", "Color Temperature", this->color_temperature_sensor_);
} }
@ -270,6 +273,64 @@ void TCS34725Component::calculate_irradiance_(uint16_t r, uint16_t g, uint16_t b
this->irradiance_g_, this->irradiance_b_); this->irradiance_g_, this->irradiance_b_);
} }
void TCS34725Component::calculate_cie1931_(uint16_t r, uint16_t g, uint16_t b, float current_saturation,
uint16_t min_raw_value) {
this->cie1931_x_ = NAN;
this->cie1931_y_ = NAN;
this->cie1931_z_ = NAN;
uint16_t min_raw_limit = get_min_raw_limit_();
float sat_limit = get_saturation_limit_();
if (min_raw_value < min_raw_limit) {
ESP_LOGW(TAG,
"Sensor Saturation too low, sample with saturation %d (raw value) below limit (%d). CIE1931 colors cannot "
"be reliably calculated.",
min_raw_value, min_raw_limit);
return;
}
if (current_saturation >= sat_limit) {
if (this->integration_time_auto_) {
ESP_LOGI(TAG, "Saturation too high, skip CIE1931 calculation, autogain ongoing");
return;
} else {
ESP_LOGW(TAG,
"Sensor Saturation too high, sample with saturation %.1f above limit (%.1f). CIE1931 colors cannot be "
"reliably calculated, reduce integration/gain or use a grey filter.",
current_saturation, sat_limit);
return;
}
}
// CIE1931 transformation matrix
static const float C11 = 1.89883277f;
static const float C12 = -0.58883081f;
static const float C13 = 0.29150381f;
static const float C21 = -0.24840751f;
static const float C22 = 1.14289469f;
static const float C23 = 0.07957766f;
static const float C31 = -0.3095245f;
static const float C32 = 0.64687886f;
static const float C33 = 0.98121083f;
// Calc scaling based on integration time
float integration_time_scaling = this->integration_time_ / 24.f;
float gain_scaling = this->gain_ / 16.f;
// Adjust the raw RGB values based on the integration time scaling factor and gain
float scaled_r = (float) r / (integration_time_scaling * gain_scaling);
float scaled_g = (float) g / (integration_time_scaling * gain_scaling);
float scaled_b = (float) b / (integration_time_scaling * gain_scaling);
// Calculate CIE1931 values
this->cie1931_x_ = std::max(C11 * scaled_r + C12 * scaled_g + C13 * scaled_b, 0.0f);
this->cie1931_y_ = std::max(C21 * scaled_r + C22 * scaled_g + C23 * scaled_b, 0.0f);
this->cie1931_z_ = std::max(C31 * scaled_r + C32 * scaled_g + C33 * scaled_b, 0.0f);
ESP_LOGD(TAG, "Calculated CIE1931 - X: %.2f, Y: %.2f, Z: %.2f", this->cie1931_x_, this->cie1931_y_, this->cie1931_z_);
}
void TCS34725Component::update() { void TCS34725Component::update() {
uint8_t data[8]; // Buffer to hold the 8 bytes (2 bytes for each of the 4 channels) uint8_t data[8]; // Buffer to hold the 8 bytes (2 bytes for each of the 4 channels)
@ -307,6 +368,10 @@ void TCS34725Component::update() {
calculate_irradiance_(raw_r, raw_g, raw_b, current_saturation, min_raw_value); calculate_irradiance_(raw_r, raw_g, raw_b, current_saturation, min_raw_value);
} }
if (this->cie1931_x_sensor_ || this->cie1931_y_sensor_ || this->cie1931_z_sensor_) {
calculate_cie1931_(raw_r, raw_g, raw_b, current_saturation, min_raw_value);
}
if (this->illuminance_sensor_ || this->color_temperature_sensor_) { if (this->illuminance_sensor_ || this->color_temperature_sensor_) {
calculate_temperature_and_lux_(raw_r, raw_g, raw_b, current_saturation, min_raw_value); calculate_temperature_and_lux_(raw_r, raw_g, raw_b, current_saturation, min_raw_value);
} }
@ -318,6 +383,12 @@ void TCS34725Component::update() {
// - sensor oversaturated but gain and timing cannot go lower // - sensor oversaturated but gain and timing cannot go lower
if (!this->integration_time_auto_ || current_saturation < 99.99f || if (!this->integration_time_auto_ || current_saturation < 99.99f ||
(this->gain_reg_ == 0 && this->integration_time_ < 200)) { (this->gain_reg_ == 0 && this->integration_time_ < 200)) {
if (this->cie1931_x_sensor_ != nullptr)
this->cie1931_x_sensor_->publish_state(this->cie1931_x_);
if (this->cie1931_y_sensor_ != nullptr)
this->cie1931_y_sensor_->publish_state(this->cie1931_y_);
if (this->cie1931_z_sensor_ != nullptr)
this->cie1931_z_sensor_->publish_state(this->cie1931_z_);
if (this->illuminance_sensor_ != nullptr) if (this->illuminance_sensor_ != nullptr)
this->illuminance_sensor_->publish_state(this->illuminance_); this->illuminance_sensor_->publish_state(this->illuminance_);
if (this->color_temperature_sensor_ != nullptr) if (this->color_temperature_sensor_ != nullptr)
@ -334,9 +405,9 @@ void TCS34725Component::update() {
ESP_LOGD(TAG, ESP_LOGD(TAG,
"Calculated: Red Irad=%.2f µW/cm², Green Irad=%.2f µW/cm², Blue Irad=%.2f µW/cm², Sensor Sat=%.2f%%, " "Calculated: Red Irad=%.2f µW/cm², Green Irad=%.2f µW/cm², Blue Irad=%.2f µW/cm², Sensor Sat=%.2f%%, "
"Illum=%.1f lx, Color Temp=%.1f K", "CIE1931 (X: %.2f, Y: %.2f, Z: %.2f), Illum=%.1f lx, Color Temp=%.1f K",
this->irradiance_r_, this->irradiance_g_, this->irradiance_b_, current_saturation, this->illuminance_, this->irradiance_r_, this->irradiance_g_, this->irradiance_b_, current_saturation, this->cie1931_x_,
this->color_temperature_); this->cie1931_y_, this->cie1931_z_, this->illuminance_, this->color_temperature_);
if (this->integration_time_auto_) { if (this->integration_time_auto_) {
// change integration time an gain to achieve maximum resolution an dynamic range // change integration time an gain to achieve maximum resolution an dynamic range

View file

@ -56,6 +56,9 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice {
void set_color_temperature_sensor(sensor::Sensor *color_temperature_sensor) { void set_color_temperature_sensor(sensor::Sensor *color_temperature_sensor) {
color_temperature_sensor_ = color_temperature_sensor; color_temperature_sensor_ = color_temperature_sensor;
} }
void set_cie1931_x_sensor(sensor::Sensor *cie1931_x_sensor) { cie1931_x_sensor_ = cie1931_x_sensor; }
void set_cie1931_y_sensor(sensor::Sensor *cie1931_y_sensor) { cie1931_y_sensor_ = cie1931_y_sensor; }
void set_cie1931_z_sensor(sensor::Sensor *cie1931_z_sensor) { cie1931_z_sensor_ = cie1931_z_sensor; }
void setup() override; void setup() override;
float get_setup_priority() const override; float get_setup_priority() const override;
@ -79,6 +82,9 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice {
sensor::Sensor *blue_irradiance_sensor_{nullptr}; sensor::Sensor *blue_irradiance_sensor_{nullptr};
sensor::Sensor *illuminance_sensor_{nullptr}; sensor::Sensor *illuminance_sensor_{nullptr};
sensor::Sensor *color_temperature_sensor_{nullptr}; sensor::Sensor *color_temperature_sensor_{nullptr};
sensor::Sensor *cie1931_x_sensor_{nullptr};
sensor::Sensor *cie1931_y_sensor_{nullptr};
sensor::Sensor *cie1931_z_sensor_{nullptr};
float integration_time_{2.4}; float integration_time_{2.4};
float gain_{1.0}; float gain_{1.0};
float glass_attenuation_{1.0}; float glass_attenuation_{1.0};
@ -87,11 +93,15 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice {
float irradiance_r_{NAN}; float irradiance_r_{NAN};
float irradiance_g_{NAN}; float irradiance_g_{NAN};
float irradiance_b_{NAN}; float irradiance_b_{NAN};
float cie1931_x_{NAN};
float cie1931_y_{NAN};
float cie1931_z_{NAN};
bool integration_time_auto_{true}; bool integration_time_auto_{true};
private: private:
void calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, float current_saturation, void calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, float current_saturation,
uint16_t min_raw_value); uint16_t min_raw_value);
void calculate_cie1931_(uint16_t r, uint16_t g, uint16_t b, float current_saturation, uint16_t min_raw_value);
void calculate_irradiance_(uint16_t r, uint16_t g, uint16_t b, float current_saturation, uint16_t min_raw_value); void calculate_irradiance_(uint16_t r, uint16_t g, uint16_t b, float current_saturation, uint16_t min_raw_value);
float get_saturation_limit_() const; float get_saturation_limit_() const;
uint16_t get_min_raw_limit_() const; uint16_t get_min_raw_limit_() const;

View file

@ -978,6 +978,7 @@ ICON_CELLPHONE_ARROW_DOWN = "mdi:cellphone-arrow-down"
ICON_CHECK_CIRCLE_OUTLINE = "mdi:check-circle-outline" ICON_CHECK_CIRCLE_OUTLINE = "mdi:check-circle-outline"
ICON_CHEMICAL_WEAPON = "mdi:chemical-weapon" ICON_CHEMICAL_WEAPON = "mdi:chemical-weapon"
ICON_CHIP = "mdi:chip" ICON_CHIP = "mdi:chip"
ICON_COLOR = "mdi:color"
ICON_COUNTER = "mdi:counter" ICON_COUNTER = "mdi:counter"
ICON_CURRENT_AC = "mdi:current-ac" ICON_CURRENT_AC = "mdi:current-ac"
ICON_DATABASE = "mdi:database" ICON_DATABASE = "mdi:database"

View file

@ -5,6 +5,12 @@ i2c:
sensor: sensor:
- platform: tcs34725 - platform: tcs34725
cie1931_x:
name: CIE1931 X
cie1931_y:
name: CIE1931 Y
cie1931_z:
name: CIE1931 Z
red_channel_irradiance: red_channel_irradiance:
name: Red Channel Irradiance name: Red Channel Irradiance
green_channel_irradiance: green_channel_irradiance:

View file

@ -5,6 +5,12 @@ i2c:
sensor: sensor:
- platform: tcs34725 - platform: tcs34725
cie1931_x:
name: CIE1931 X
cie1931_y:
name: CIE1931 Y
cie1931_z:
name: CIE1931 Z
red_channel_irradiance: red_channel_irradiance:
name: Red Channel Irradiance name: Red Channel Irradiance
green_channel_irradiance: green_channel_irradiance:

View file

@ -5,6 +5,12 @@ i2c:
sensor: sensor:
- platform: tcs34725 - platform: tcs34725
cie1931_x:
name: CIE1931 X
cie1931_y:
name: CIE1931 Y
cie1931_z:
name: CIE1931 Z
red_channel_irradiance: red_channel_irradiance:
name: Red Channel Irradiance name: Red Channel Irradiance
green_channel_irradiance: green_channel_irradiance:

View file

@ -5,6 +5,12 @@ i2c:
sensor: sensor:
- platform: tcs34725 - platform: tcs34725
cie1931_x:
name: CIE1931 X
cie1931_y:
name: CIE1931 Y
cie1931_z:
name: CIE1931 Z
red_channel_irradiance: red_channel_irradiance:
name: Red Channel Irradiance name: Red Channel Irradiance
green_channel_irradiance: green_channel_irradiance:

View file

@ -5,6 +5,12 @@ i2c:
sensor: sensor:
- platform: tcs34725 - platform: tcs34725
cie1931_x:
name: CIE1931 X
cie1931_y:
name: CIE1931 Y
cie1931_z:
name: CIE1931 Z
red_channel_irradiance: red_channel_irradiance:
name: Red Channel Irradiance name: Red Channel Irradiance
green_channel_irradiance: green_channel_irradiance:

View file

@ -5,6 +5,12 @@ i2c:
sensor: sensor:
- platform: tcs34725 - platform: tcs34725
cie1931_x:
name: CIE1931 X
cie1931_y:
name: CIE1931 Y
cie1931_z:
name: CIE1931 Z
red_channel_irradiance: red_channel_irradiance:
name: Red Channel Irradiance name: Red Channel Irradiance
green_channel_irradiance: green_channel_irradiance: