Split files in light component (#1893)

This commit is contained in:
Oxan van Leeuwen 2021-06-14 18:01:56 +02:00 committed by GitHub
parent 9ad9d64ac7
commit 0efc1f06f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1334 additions and 1231 deletions

View file

@ -6,158 +6,6 @@ namespace light {
static const char *const TAG = "light.addressable"; static const char *const TAG = "light.addressable";
Color ESPHSVColor::to_rgb() const {
// based on FastLED's hsv rainbow to rgb
const uint8_t hue = this->hue;
const uint8_t sat = this->saturation;
const uint8_t val = this->value;
// upper 3 hue bits are for branch selection, lower 5 are for values
const uint8_t offset8 = (hue & 0x1F) << 3; // 0..248
// third of the offset, 255/3 = 85 (actually only up to 82; 164)
const uint8_t third = esp_scale8(offset8, 85);
const uint8_t two_thirds = esp_scale8(offset8, 170);
Color rgb(255, 255, 255, 0);
switch (hue >> 5) {
case 0b000:
rgb.r = 255 - third;
rgb.g = third;
rgb.b = 0;
break;
case 0b001:
rgb.r = 171;
rgb.g = 85 + third;
rgb.b = 0;
break;
case 0b010:
rgb.r = 171 - two_thirds;
rgb.g = 170 + third;
rgb.b = 0;
break;
case 0b011:
rgb.r = 0;
rgb.g = 255 - third;
rgb.b = third;
break;
case 0b100:
rgb.r = 0;
rgb.g = 171 - two_thirds;
rgb.b = 85 + two_thirds;
break;
case 0b101:
rgb.r = third;
rgb.g = 0;
rgb.b = 255 - third;
break;
case 0b110:
rgb.r = 85 + third;
rgb.g = 0;
rgb.b = 171 - third;
break;
case 0b111:
rgb.r = 170 + third;
rgb.g = 0;
rgb.b = 85 - third;
break;
default:
break;
}
// low saturation -> add uniform color to orig. hue
// high saturation -> use hue directly
// scales with square of saturation
// (r,g,b) = (r,g,b) * sat + (1 - sat)^2
rgb *= sat;
const uint8_t desat = 255 - sat;
rgb += esp_scale8(desat, desat);
// (r,g,b) = (r,g,b) * val
rgb *= val;
return rgb;
}
void ESPRangeView::set(const Color &color) {
for (int32_t i = this->begin_; i < this->end_; i++) {
(*this->parent_)[i] = color;
}
}
ESPColorView ESPRangeView::operator[](int32_t index) const {
index = interpret_index(index, this->size()) + this->begin_;
return (*this->parent_)[index];
}
ESPRangeIterator ESPRangeView::begin() { return {*this, this->begin_}; }
ESPRangeIterator ESPRangeView::end() { return {*this, this->end_}; }
void ESPRangeView::set_red(uint8_t red) {
for (auto c : *this)
c.set_red(red);
}
void ESPRangeView::set_green(uint8_t green) {
for (auto c : *this)
c.set_green(green);
}
void ESPRangeView::set_blue(uint8_t blue) {
for (auto c : *this)
c.set_blue(blue);
}
void ESPRangeView::set_white(uint8_t white) {
for (auto c : *this)
c.set_white(white);
}
void ESPRangeView::set_effect_data(uint8_t effect_data) {
for (auto c : *this)
c.set_effect_data(effect_data);
}
void ESPRangeView::fade_to_white(uint8_t amnt) {
for (auto c : *this)
c.fade_to_white(amnt);
}
void ESPRangeView::fade_to_black(uint8_t amnt) {
for (auto c : *this)
c.fade_to_black(amnt);
}
void ESPRangeView::lighten(uint8_t delta) {
for (auto c : *this)
c.lighten(delta);
}
void ESPRangeView::darken(uint8_t delta) {
for (auto c : *this)
c.darken(delta);
}
ESPRangeView &ESPRangeView::operator=(const ESPRangeView &rhs) { // NOLINT
// If size doesn't match, error (todo warning)
if (rhs.size() != this->size())
return *this;
if (this->parent_ != rhs.parent_) {
for (int32_t i = 0; i < this->size(); i++)
(*this)[i].set(rhs[i].get());
return *this;
}
// If both equal, already done
if (rhs.begin_ == this->begin_)
return *this;
if (rhs.begin_ > this->begin_) {
// Copy from left
for (int32_t i = 0; i < this->size(); i++) {
(*this)[i].set(rhs[i].get());
}
} else {
// Copy from right
for (int32_t i = this->size() - 1; i >= 0; i--) {
(*this)[i].set(rhs[i].get());
}
}
return *this;
}
ESPColorView ESPRangeIterator::operator*() const { return this->range_.parent_->get(this->i_); }
int32_t HOT interpret_index(int32_t index, int32_t size) {
if (index < 0)
return size + index;
return index;
}
void AddressableLight::call_setup() { void AddressableLight::call_setup() {
this->setup(); this->setup();
@ -254,23 +102,5 @@ void AddressableLight::write_state(LightState *state) {
this->schedule_show(); this->schedule_show();
} }
void ESPColorCorrection::calculate_gamma_table(float gamma) {
for (uint16_t i = 0; i < 256; i++) {
// corrected = val ^ gamma
auto corrected = static_cast<uint8_t>(roundf(255.0f * gamma_correct(i / 255.0f, gamma)));
this->gamma_table_[i] = corrected;
}
if (gamma == 0.0f) {
for (uint16_t i = 0; i < 256; i++)
this->gamma_reverse_table_[i] = i;
return;
}
for (uint16_t i = 0; i < 256; i++) {
// val = corrected ^ (1/gamma)
auto uncorrected = static_cast<uint8_t>(roundf(255.0f * powf(i / 255.0f, 1.0f / gamma)));
this->gamma_reverse_table_[i] = uncorrected;
}
}
} // namespace light } // namespace light
} // namespace esphome } // namespace esphome

View file

@ -3,6 +3,9 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/color.h" #include "esphome/core/color.h"
#include "esp_color_correction.h"
#include "esp_color_view.h"
#include "esp_range_view.h"
#include "light_output.h" #include "light_output.h"
#include "light_state.h" #include "light_state.h"
@ -15,266 +18,6 @@ namespace light {
using ESPColor = Color; using ESPColor = Color;
struct ESPHSVColor {
union {
struct {
union {
uint8_t hue;
uint8_t h;
};
union {
uint8_t saturation;
uint8_t s;
};
union {
uint8_t value;
uint8_t v;
};
};
uint8_t raw[3];
};
inline ESPHSVColor() ALWAYS_INLINE : h(0), s(0), v(0) { // NOLINT
}
inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue),
saturation(saturation),
value(value) {}
Color to_rgb() const;
};
class ESPColorCorrection {
public:
ESPColorCorrection() : max_brightness_(255, 255, 255, 255) {}
void set_max_brightness(const Color &max_brightness) { this->max_brightness_ = max_brightness; }
void set_local_brightness(uint8_t local_brightness) { this->local_brightness_ = local_brightness; }
void calculate_gamma_table(float gamma);
inline Color color_correct(Color color) const ALWAYS_INLINE {
// corrected = (uncorrected * max_brightness * local_brightness) ^ gamma
return Color(this->color_correct_red(color.red), this->color_correct_green(color.green),
this->color_correct_blue(color.blue), this->color_correct_white(color.white));
}
inline uint8_t color_correct_red(uint8_t red) const ALWAYS_INLINE {
uint8_t res = esp_scale8(esp_scale8(red, this->max_brightness_.red), this->local_brightness_);
return this->gamma_table_[res];
}
inline uint8_t color_correct_green(uint8_t green) const ALWAYS_INLINE {
uint8_t res = esp_scale8(esp_scale8(green, this->max_brightness_.green), this->local_brightness_);
return this->gamma_table_[res];
}
inline uint8_t color_correct_blue(uint8_t blue) const ALWAYS_INLINE {
uint8_t res = esp_scale8(esp_scale8(blue, this->max_brightness_.blue), this->local_brightness_);
return this->gamma_table_[res];
}
inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE {
// do not scale white value with brightness
uint8_t res = esp_scale8(white, this->max_brightness_.white);
return this->gamma_table_[res];
}
inline Color color_uncorrect(Color color) const ALWAYS_INLINE {
// uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness)
return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green),
this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white));
}
inline uint8_t color_uncorrect_red(uint8_t red) const ALWAYS_INLINE {
if (this->max_brightness_.red == 0 || this->local_brightness_ == 0)
return 0;
uint16_t uncorrected = this->gamma_reverse_table_[red] * 255UL;
uint8_t res = ((uncorrected / this->max_brightness_.red) * 255UL) / this->local_brightness_;
return res;
}
inline uint8_t color_uncorrect_green(uint8_t green) const ALWAYS_INLINE {
if (this->max_brightness_.green == 0 || this->local_brightness_ == 0)
return 0;
uint16_t uncorrected = this->gamma_reverse_table_[green] * 255UL;
uint8_t res = ((uncorrected / this->max_brightness_.green) * 255UL) / this->local_brightness_;
return res;
}
inline uint8_t color_uncorrect_blue(uint8_t blue) const ALWAYS_INLINE {
if (this->max_brightness_.blue == 0 || this->local_brightness_ == 0)
return 0;
uint16_t uncorrected = this->gamma_reverse_table_[blue] * 255UL;
uint8_t res = ((uncorrected / this->max_brightness_.blue) * 255UL) / this->local_brightness_;
return res;
}
inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE {
if (this->max_brightness_.white == 0)
return 0;
uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL;
uint8_t res = uncorrected / this->max_brightness_.white;
return res;
}
protected:
uint8_t gamma_table_[256];
uint8_t gamma_reverse_table_[256];
Color max_brightness_;
uint8_t local_brightness_{255};
};
class ESPColorSettable {
public:
virtual void set(const Color &color) = 0;
virtual void set_red(uint8_t red) = 0;
virtual void set_green(uint8_t green) = 0;
virtual void set_blue(uint8_t blue) = 0;
virtual void set_white(uint8_t white) = 0;
virtual void set_effect_data(uint8_t effect_data) = 0;
virtual void fade_to_white(uint8_t amnt) = 0;
virtual void fade_to_black(uint8_t amnt) = 0;
virtual void lighten(uint8_t delta) = 0;
virtual void darken(uint8_t delta) = 0;
void set(const ESPHSVColor &color) { this->set_hsv(color); }
void set_hsv(const ESPHSVColor &color) {
Color rgb = color.to_rgb();
this->set_rgb(rgb.r, rgb.g, rgb.b);
}
void set_rgb(uint8_t red, uint8_t green, uint8_t blue) {
this->set_red(red);
this->set_green(green);
this->set_blue(blue);
}
void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) {
this->set_rgb(red, green, blue);
this->set_white(white);
}
};
class ESPColorView : public ESPColorSettable {
public:
ESPColorView(uint8_t *red, uint8_t *green, uint8_t *blue, uint8_t *white, uint8_t *effect_data,
const ESPColorCorrection *color_correction)
: red_(red),
green_(green),
blue_(blue),
white_(white),
effect_data_(effect_data),
color_correction_(color_correction) {}
ESPColorView &operator=(const Color &rhs) {
this->set(rhs);
return *this;
}
ESPColorView &operator=(const ESPHSVColor &rhs) {
this->set_hsv(rhs);
return *this;
}
void set(const Color &color) override { this->set_rgbw(color.r, color.g, color.b, color.w); }
void set_red(uint8_t red) override { *this->red_ = this->color_correction_->color_correct_red(red); }
void set_green(uint8_t green) override { *this->green_ = this->color_correction_->color_correct_green(green); }
void set_blue(uint8_t blue) override { *this->blue_ = this->color_correction_->color_correct_blue(blue); }
void set_white(uint8_t white) override {
if (this->white_ == nullptr)
return;
*this->white_ = this->color_correction_->color_correct_white(white);
}
void set_effect_data(uint8_t effect_data) override {
if (this->effect_data_ == nullptr)
return;
*this->effect_data_ = effect_data;
}
void fade_to_white(uint8_t amnt) override { this->set(this->get().fade_to_white(amnt)); }
void fade_to_black(uint8_t amnt) override { this->set(this->get().fade_to_black(amnt)); }
void lighten(uint8_t delta) override { this->set(this->get().lighten(delta)); }
void darken(uint8_t delta) override { this->set(this->get().darken(delta)); }
Color get() const { return Color(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); }
uint8_t get_red() const { return this->color_correction_->color_uncorrect_red(*this->red_); }
uint8_t get_red_raw() const { return *this->red_; }
uint8_t get_green() const { return this->color_correction_->color_uncorrect_green(*this->green_); }
uint8_t get_green_raw() const { return *this->green_; }
uint8_t get_blue() const { return this->color_correction_->color_uncorrect_blue(*this->blue_); }
uint8_t get_blue_raw() const { return *this->blue_; }
uint8_t get_white() const {
if (this->white_ == nullptr)
return 0;
return this->color_correction_->color_uncorrect_white(*this->white_);
}
uint8_t get_white_raw() const {
if (this->white_ == nullptr)
return 0;
return *this->white_;
}
uint8_t get_effect_data() const {
if (this->effect_data_ == nullptr)
return 0;
return *this->effect_data_;
}
void raw_set_color_correction(const ESPColorCorrection *color_correction) {
this->color_correction_ = color_correction;
}
protected:
uint8_t *const red_;
uint8_t *const green_;
uint8_t *const blue_;
uint8_t *const white_;
uint8_t *const effect_data_;
const ESPColorCorrection *color_correction_;
};
class AddressableLight;
int32_t interpret_index(int32_t index, int32_t size);
class ESPRangeIterator;
class ESPRangeView : public ESPColorSettable {
public:
ESPRangeView(AddressableLight *parent, int32_t begin, int32_t an_end) : parent_(parent), begin_(begin), end_(an_end) {
if (this->end_ < this->begin_) {
this->end_ = this->begin_;
}
}
ESPColorView operator[](int32_t index) const;
ESPRangeIterator begin();
ESPRangeIterator end();
void set(const Color &color) override;
ESPRangeView &operator=(const Color &rhs) {
this->set(rhs);
return *this;
}
ESPRangeView &operator=(const ESPColorView &rhs) {
this->set(rhs.get());
return *this;
}
ESPRangeView &operator=(const ESPHSVColor &rhs) {
this->set_hsv(rhs);
return *this;
}
ESPRangeView &operator=(const ESPRangeView &rhs);
void set_red(uint8_t red) override;
void set_green(uint8_t green) override;
void set_blue(uint8_t blue) override;
void set_white(uint8_t white) override;
void set_effect_data(uint8_t effect_data) override;
void fade_to_white(uint8_t amnt) override;
void fade_to_black(uint8_t amnt) override;
void lighten(uint8_t delta) override;
void darken(uint8_t delta) override;
int32_t size() const { return this->end_ - this->begin_; }
protected:
friend ESPRangeIterator;
AddressableLight *parent_;
int32_t begin_;
int32_t end_;
};
class ESPRangeIterator {
public:
ESPRangeIterator(const ESPRangeView &range, int32_t i) : range_(range), i_(i) {}
ESPRangeIterator operator++() {
this->i_++;
return *this;
}
bool operator!=(const ESPRangeIterator &other) const { return this->i_ != other.i_; }
ESPColorView operator*() const;
protected:
ESPRangeView range_;
int32_t i_;
};
class AddressableLight : public LightOutput, public Component { class AddressableLight : public LightOutput, public Component {
public: public:
virtual int32_t size() const = 0; virtual int32_t size() const = 0;

View file

@ -0,0 +1,26 @@
#include "esp_color_correction.h"
#include "esphome/core/log.h"
namespace esphome {
namespace light {
void ESPColorCorrection::calculate_gamma_table(float gamma) {
for (uint16_t i = 0; i < 256; i++) {
// corrected = val ^ gamma
auto corrected = static_cast<uint8_t>(roundf(255.0f * gamma_correct(i / 255.0f, gamma)));
this->gamma_table_[i] = corrected;
}
if (gamma == 0.0f) {
for (uint16_t i = 0; i < 256; i++)
this->gamma_reverse_table_[i] = i;
return;
}
for (uint16_t i = 0; i < 256; i++) {
// val = corrected ^ (1/gamma)
auto uncorrected = static_cast<uint8_t>(roundf(255.0f * powf(i / 255.0f, 1.0f / gamma)));
this->gamma_reverse_table_[i] = uncorrected;
}
}
} // namespace light
} // namespace esphome

View file

@ -0,0 +1,78 @@
#pragma once
#include "esphome/core/color.h"
namespace esphome {
namespace light {
class ESPColorCorrection {
public:
ESPColorCorrection() : max_brightness_(255, 255, 255, 255) {}
void set_max_brightness(const Color &max_brightness) { this->max_brightness_ = max_brightness; }
void set_local_brightness(uint8_t local_brightness) { this->local_brightness_ = local_brightness; }
void calculate_gamma_table(float gamma);
inline Color color_correct(Color color) const ALWAYS_INLINE {
// corrected = (uncorrected * max_brightness * local_brightness) ^ gamma
return Color(this->color_correct_red(color.red), this->color_correct_green(color.green),
this->color_correct_blue(color.blue), this->color_correct_white(color.white));
}
inline uint8_t color_correct_red(uint8_t red) const ALWAYS_INLINE {
uint8_t res = esp_scale8(esp_scale8(red, this->max_brightness_.red), this->local_brightness_);
return this->gamma_table_[res];
}
inline uint8_t color_correct_green(uint8_t green) const ALWAYS_INLINE {
uint8_t res = esp_scale8(esp_scale8(green, this->max_brightness_.green), this->local_brightness_);
return this->gamma_table_[res];
}
inline uint8_t color_correct_blue(uint8_t blue) const ALWAYS_INLINE {
uint8_t res = esp_scale8(esp_scale8(blue, this->max_brightness_.blue), this->local_brightness_);
return this->gamma_table_[res];
}
inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE {
// do not scale white value with brightness
uint8_t res = esp_scale8(white, this->max_brightness_.white);
return this->gamma_table_[res];
}
inline Color color_uncorrect(Color color) const ALWAYS_INLINE {
// uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness)
return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green),
this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white));
}
inline uint8_t color_uncorrect_red(uint8_t red) const ALWAYS_INLINE {
if (this->max_brightness_.red == 0 || this->local_brightness_ == 0)
return 0;
uint16_t uncorrected = this->gamma_reverse_table_[red] * 255UL;
uint8_t res = ((uncorrected / this->max_brightness_.red) * 255UL) / this->local_brightness_;
return res;
}
inline uint8_t color_uncorrect_green(uint8_t green) const ALWAYS_INLINE {
if (this->max_brightness_.green == 0 || this->local_brightness_ == 0)
return 0;
uint16_t uncorrected = this->gamma_reverse_table_[green] * 255UL;
uint8_t res = ((uncorrected / this->max_brightness_.green) * 255UL) / this->local_brightness_;
return res;
}
inline uint8_t color_uncorrect_blue(uint8_t blue) const ALWAYS_INLINE {
if (this->max_brightness_.blue == 0 || this->local_brightness_ == 0)
return 0;
uint16_t uncorrected = this->gamma_reverse_table_[blue] * 255UL;
uint8_t res = ((uncorrected / this->max_brightness_.blue) * 255UL) / this->local_brightness_;
return res;
}
inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE {
if (this->max_brightness_.white == 0)
return 0;
uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL;
uint8_t res = uncorrected / this->max_brightness_.white;
return res;
}
protected:
uint8_t gamma_table_[256];
uint8_t gamma_reverse_table_[256];
Color max_brightness_;
uint8_t local_brightness_{255};
};
} // namespace light
} // namespace esphome

View file

@ -0,0 +1,110 @@
#pragma once
#include "esphome/core/color.h"
#include "esp_hsv_color.h"
#include "esp_color_correction.h"
namespace esphome {
namespace light {
class ESPColorSettable {
public:
virtual void set(const Color &color) = 0;
virtual void set_red(uint8_t red) = 0;
virtual void set_green(uint8_t green) = 0;
virtual void set_blue(uint8_t blue) = 0;
virtual void set_white(uint8_t white) = 0;
virtual void set_effect_data(uint8_t effect_data) = 0;
virtual void fade_to_white(uint8_t amnt) = 0;
virtual void fade_to_black(uint8_t amnt) = 0;
virtual void lighten(uint8_t delta) = 0;
virtual void darken(uint8_t delta) = 0;
void set(const ESPHSVColor &color) { this->set_hsv(color); }
void set_hsv(const ESPHSVColor &color) {
Color rgb = color.to_rgb();
this->set_rgb(rgb.r, rgb.g, rgb.b);
}
void set_rgb(uint8_t red, uint8_t green, uint8_t blue) {
this->set_red(red);
this->set_green(green);
this->set_blue(blue);
}
void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) {
this->set_rgb(red, green, blue);
this->set_white(white);
}
};
class ESPColorView : public ESPColorSettable {
public:
ESPColorView(uint8_t *red, uint8_t *green, uint8_t *blue, uint8_t *white, uint8_t *effect_data,
const ESPColorCorrection *color_correction)
: red_(red),
green_(green),
blue_(blue),
white_(white),
effect_data_(effect_data),
color_correction_(color_correction) {}
ESPColorView &operator=(const Color &rhs) {
this->set(rhs);
return *this;
}
ESPColorView &operator=(const ESPHSVColor &rhs) {
this->set_hsv(rhs);
return *this;
}
void set(const Color &color) override { this->set_rgbw(color.r, color.g, color.b, color.w); }
void set_red(uint8_t red) override { *this->red_ = this->color_correction_->color_correct_red(red); }
void set_green(uint8_t green) override { *this->green_ = this->color_correction_->color_correct_green(green); }
void set_blue(uint8_t blue) override { *this->blue_ = this->color_correction_->color_correct_blue(blue); }
void set_white(uint8_t white) override {
if (this->white_ == nullptr)
return;
*this->white_ = this->color_correction_->color_correct_white(white);
}
void set_effect_data(uint8_t effect_data) override {
if (this->effect_data_ == nullptr)
return;
*this->effect_data_ = effect_data;
}
void fade_to_white(uint8_t amnt) override { this->set(this->get().fade_to_white(amnt)); }
void fade_to_black(uint8_t amnt) override { this->set(this->get().fade_to_black(amnt)); }
void lighten(uint8_t delta) override { this->set(this->get().lighten(delta)); }
void darken(uint8_t delta) override { this->set(this->get().darken(delta)); }
Color get() const { return Color(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); }
uint8_t get_red() const { return this->color_correction_->color_uncorrect_red(*this->red_); }
uint8_t get_red_raw() const { return *this->red_; }
uint8_t get_green() const { return this->color_correction_->color_uncorrect_green(*this->green_); }
uint8_t get_green_raw() const { return *this->green_; }
uint8_t get_blue() const { return this->color_correction_->color_uncorrect_blue(*this->blue_); }
uint8_t get_blue_raw() const { return *this->blue_; }
uint8_t get_white() const {
if (this->white_ == nullptr)
return 0;
return this->color_correction_->color_uncorrect_white(*this->white_);
}
uint8_t get_white_raw() const {
if (this->white_ == nullptr)
return 0;
return *this->white_;
}
uint8_t get_effect_data() const {
if (this->effect_data_ == nullptr)
return 0;
return *this->effect_data_;
}
void raw_set_color_correction(const ESPColorCorrection *color_correction) {
this->color_correction_ = color_correction;
}
protected:
uint8_t *const red_;
uint8_t *const green_;
uint8_t *const blue_;
uint8_t *const white_;
uint8_t *const effect_data_;
const ESPColorCorrection *color_correction_;
};
} // namespace light
} // namespace esphome

View file

@ -0,0 +1,74 @@
#include "esp_hsv_color.h"
namespace esphome {
namespace light {
Color ESPHSVColor::to_rgb() const {
// based on FastLED's hsv rainbow to rgb
const uint8_t hue = this->hue;
const uint8_t sat = this->saturation;
const uint8_t val = this->value;
// upper 3 hue bits are for branch selection, lower 5 are for values
const uint8_t offset8 = (hue & 0x1F) << 3; // 0..248
// third of the offset, 255/3 = 85 (actually only up to 82; 164)
const uint8_t third = esp_scale8(offset8, 85);
const uint8_t two_thirds = esp_scale8(offset8, 170);
Color rgb(255, 255, 255, 0);
switch (hue >> 5) {
case 0b000:
rgb.r = 255 - third;
rgb.g = third;
rgb.b = 0;
break;
case 0b001:
rgb.r = 171;
rgb.g = 85 + third;
rgb.b = 0;
break;
case 0b010:
rgb.r = 171 - two_thirds;
rgb.g = 170 + third;
rgb.b = 0;
break;
case 0b011:
rgb.r = 0;
rgb.g = 255 - third;
rgb.b = third;
break;
case 0b100:
rgb.r = 0;
rgb.g = 171 - two_thirds;
rgb.b = 85 + two_thirds;
break;
case 0b101:
rgb.r = third;
rgb.g = 0;
rgb.b = 255 - third;
break;
case 0b110:
rgb.r = 85 + third;
rgb.g = 0;
rgb.b = 171 - third;
break;
case 0b111:
rgb.r = 170 + third;
rgb.g = 0;
rgb.b = 85 - third;
break;
default:
break;
}
// low saturation -> add uniform color to orig. hue
// high saturation -> use hue directly
// scales with square of saturation
// (r,g,b) = (r,g,b) * sat + (1 - sat)^2
rgb *= sat;
const uint8_t desat = 255 - sat;
rgb += esp_scale8(desat, desat);
// (r,g,b) = (r,g,b) * val
rgb *= val;
return rgb;
}
} // namespace light
} // namespace esphome

View file

@ -0,0 +1,36 @@
#pragma once
#include "esphome/core/helpers.h"
#include "esphome/core/color.h"
namespace esphome {
namespace light {
struct ESPHSVColor {
union {
struct {
union {
uint8_t hue;
uint8_t h;
};
union {
uint8_t saturation;
uint8_t s;
};
union {
uint8_t value;
uint8_t v;
};
};
uint8_t raw[3];
};
inline ESPHSVColor() ALWAYS_INLINE : h(0), s(0), v(0) { // NOLINT
}
inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue),
saturation(saturation),
value(value) {}
Color to_rgb() const;
};
} // namespace light
} // namespace esphome

View file

@ -0,0 +1,96 @@
#include "esp_range_view.h"
#include "addressable_light.h"
namespace esphome {
namespace light {
int32_t HOT interpret_index(int32_t index, int32_t size) {
if (index < 0)
return size + index;
return index;
}
ESPColorView ESPRangeView::operator[](int32_t index) const {
index = interpret_index(index, this->size()) + this->begin_;
return (*this->parent_)[index];
}
ESPRangeIterator ESPRangeView::begin() { return {*this, this->begin_}; }
ESPRangeIterator ESPRangeView::end() { return {*this, this->end_}; }
void ESPRangeView::set(const Color &color) {
for (int32_t i = this->begin_; i < this->end_; i++) {
(*this->parent_)[i] = color;
}
}
void ESPRangeView::set_red(uint8_t red) {
for (auto c : *this)
c.set_red(red);
}
void ESPRangeView::set_green(uint8_t green) {
for (auto c : *this)
c.set_green(green);
}
void ESPRangeView::set_blue(uint8_t blue) {
for (auto c : *this)
c.set_blue(blue);
}
void ESPRangeView::set_white(uint8_t white) {
for (auto c : *this)
c.set_white(white);
}
void ESPRangeView::set_effect_data(uint8_t effect_data) {
for (auto c : *this)
c.set_effect_data(effect_data);
}
void ESPRangeView::fade_to_white(uint8_t amnt) {
for (auto c : *this)
c.fade_to_white(amnt);
}
void ESPRangeView::fade_to_black(uint8_t amnt) {
for (auto c : *this)
c.fade_to_black(amnt);
}
void ESPRangeView::lighten(uint8_t delta) {
for (auto c : *this)
c.lighten(delta);
}
void ESPRangeView::darken(uint8_t delta) {
for (auto c : *this)
c.darken(delta);
}
ESPRangeView &ESPRangeView::operator=(const ESPRangeView &rhs) { // NOLINT
// If size doesn't match, error (todo warning)
if (rhs.size() != this->size())
return *this;
if (this->parent_ != rhs.parent_) {
for (int32_t i = 0; i < this->size(); i++)
(*this)[i].set(rhs[i].get());
return *this;
}
// If both equal, already done
if (rhs.begin_ == this->begin_)
return *this;
if (rhs.begin_ > this->begin_) {
// Copy from left
for (int32_t i = 0; i < this->size(); i++) {
(*this)[i].set(rhs[i].get());
}
} else {
// Copy from right
for (int32_t i = this->size() - 1; i >= 0; i--) {
(*this)[i].set(rhs[i].get());
}
}
return *this;
}
ESPColorView ESPRangeIterator::operator*() const { return this->range_.parent_->get(this->i_); }
} // namespace light
} // namespace esphome

View file

@ -0,0 +1,75 @@
#pragma once
#include "esp_color_view.h"
#include "esp_hsv_color.h"
namespace esphome {
namespace light {
int32_t interpret_index(int32_t index, int32_t size);
class AddressableLight;
class ESPRangeIterator;
class ESPRangeView : public ESPColorSettable {
public:
ESPRangeView(AddressableLight *parent, int32_t begin, int32_t end)
: parent_(parent), begin_(begin), end_(end < begin ? begin : end) {}
int32_t size() const { return this->end_ - this->begin_; }
ESPColorView operator[](int32_t index) const;
ESPRangeIterator begin();
ESPRangeIterator end();
void set(const Color &color) override;
void set(const ESPHSVColor &color) { this->set(color.to_rgb()); }
void set_red(uint8_t red) override;
void set_green(uint8_t green) override;
void set_blue(uint8_t blue) override;
void set_white(uint8_t white) override;
void set_effect_data(uint8_t effect_data) override;
void fade_to_white(uint8_t amnt) override;
void fade_to_black(uint8_t amnt) override;
void lighten(uint8_t delta) override;
void darken(uint8_t delta) override;
ESPRangeView &operator=(const Color &rhs) {
this->set(rhs);
return *this;
}
ESPRangeView &operator=(const ESPColorView &rhs) {
this->set(rhs.get());
return *this;
}
ESPRangeView &operator=(const ESPHSVColor &rhs) {
this->set_hsv(rhs);
return *this;
}
ESPRangeView &operator=(const ESPRangeView &rhs);
protected:
friend ESPRangeIterator;
AddressableLight *parent_;
int32_t begin_;
int32_t end_;
};
class ESPRangeIterator {
public:
ESPRangeIterator(const ESPRangeView &range, int32_t i) : range_(range), i_(i) {}
ESPRangeIterator operator++() {
this->i_++;
return *this;
}
bool operator!=(const ESPRangeIterator &other) const { return this->i_ != other.i_; }
ESPColorView operator*() const;
protected:
ESPRangeView range_;
int32_t i_;
};
} // namespace light
} // namespace esphome

View file

@ -0,0 +1,522 @@
#include "light_call.h"
#include "light_state.h"
#include "esphome/core/log.h"
namespace esphome {
namespace light {
static const char *TAG = "light";
#ifdef USE_JSON
LightCall &LightCall::parse_color_json(JsonObject &root) {
if (root.containsKey("state")) {
auto val = parse_on_off(root["state"]);
switch (val) {
case PARSE_ON:
this->set_state(true);
break;
case PARSE_OFF:
this->set_state(false);
break;
case PARSE_TOGGLE:
this->set_state(!this->parent_->remote_values.is_on());
break;
case PARSE_NONE:
break;
}
}
if (root.containsKey("brightness")) {
this->set_brightness(float(root["brightness"]) / 255.0f);
}
if (root.containsKey("color")) {
JsonObject &color = root["color"];
if (color.containsKey("r")) {
this->set_red(float(color["r"]) / 255.0f);
}
if (color.containsKey("g")) {
this->set_green(float(color["g"]) / 255.0f);
}
if (color.containsKey("b")) {
this->set_blue(float(color["b"]) / 255.0f);
}
}
if (root.containsKey("white_value")) {
this->set_white(float(root["white_value"]) / 255.0f);
}
if (root.containsKey("color_temp")) {
this->set_color_temperature(float(root["color_temp"]));
}
return *this;
}
LightCall &LightCall::parse_json(JsonObject &root) {
this->parse_color_json(root);
if (root.containsKey("flash")) {
auto length = uint32_t(float(root["flash"]) * 1000);
this->set_flash_length(length);
}
if (root.containsKey("transition")) {
auto length = uint32_t(float(root["transition"]) * 1000);
this->set_transition_length(length);
}
if (root.containsKey("effect")) {
const char *effect = root["effect"];
this->set_effect(effect);
}
return *this;
}
#endif
void LightCall::perform() {
// use remote values for fallback
const char *name = this->parent_->get_name().c_str();
if (this->publish_) {
ESP_LOGD(TAG, "'%s' Setting:", name);
}
LightColorValues v = this->validate_();
if (this->publish_) {
// Only print state when it's being changed
bool current_state = this->parent_->remote_values.is_on();
if (this->state_.value_or(current_state) != current_state) {
ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on()));
}
if (this->brightness_.has_value()) {
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f);
}
if (this->color_temperature_.has_value()) {
ESP_LOGD(TAG, " Color Temperature: %.1f mireds", v.get_color_temperature());
}
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
ESP_LOGD(TAG, " Red=%.0f%%, Green=%.0f%%, Blue=%.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
v.get_blue() * 100.0f);
}
if (this->white_.has_value()) {
ESP_LOGD(TAG, " White Value: %.0f%%", v.get_white() * 100.0f);
}
}
if (this->has_flash_()) {
// FLASH
if (this->publish_) {
ESP_LOGD(TAG, " Flash Length: %.1fs", *this->flash_length_ / 1e3f);
}
this->parent_->start_flash_(v, *this->flash_length_);
} else if (this->has_transition_()) {
// TRANSITION
if (this->publish_) {
ESP_LOGD(TAG, " Transition Length: %.1fs", *this->transition_length_ / 1e3f);
}
// Special case: Transition and effect can be set when turning off
if (this->has_effect_()) {
if (this->publish_) {
ESP_LOGD(TAG, " Effect: 'None'");
}
this->parent_->stop_effect_();
}
this->parent_->start_transition_(v, *this->transition_length_);
} else if (this->has_effect_()) {
// EFFECT
auto effect = this->effect_;
const char *effect_s;
if (effect == 0)
effect_s = "None";
else
effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();
if (this->publish_) {
ESP_LOGD(TAG, " Effect: '%s'", effect_s);
}
this->parent_->start_effect_(*this->effect_);
// Also set light color values when starting an effect
// For example to turn off the light
this->parent_->set_immediately_(v, true);
} else {
// INSTANT CHANGE
this->parent_->set_immediately_(v, this->publish_);
}
if (!this->has_transition_()) {
this->parent_->target_state_reached_callback_.call();
}
if (this->publish_) {
this->parent_->publish_state();
}
if (this->save_) {
this->parent_->save_remote_values_();
}
}
LightColorValues LightCall::validate_() {
// use remote values for fallback
auto *name = this->parent_->get_name().c_str();
auto traits = this->parent_->get_traits();
// Brightness exists check
if (this->brightness_.has_value() && !traits.get_supports_brightness()) {
ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name);
this->brightness_.reset();
}
// Transition length possible check
if (this->transition_length_.has_value() && *this->transition_length_ != 0 && !traits.get_supports_brightness()) {
ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name);
this->transition_length_.reset();
}
// RGB exists check
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (!traits.get_supports_rgb()) {
ESP_LOGW(TAG, "'%s' - This light does not support setting RGB color!", name);
this->red_.reset();
this->green_.reset();
this->blue_.reset();
}
}
// White value exists check
if (this->white_.has_value() && !traits.get_supports_rgb_white_value()) {
ESP_LOGW(TAG, "'%s' - This light does not support setting white value!", name);
this->white_.reset();
}
// Color temperature exists check
if (this->color_temperature_.has_value() && !traits.get_supports_color_temperature()) {
ESP_LOGW(TAG, "'%s' - This light does not support setting color temperature!", name);
this->color_temperature_.reset();
}
// If white channel is specified, set RGB to white color (when interlock is enabled)
if (this->white_.has_value()) {
if (traits.get_supports_color_interlock()) {
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
this->red_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f);
}
// make white values binary aka 0.0f or 1.0f... this allows brightness to do its job
if (*this->white_ > 0.0f) {
this->white_ = optional<float>(1.0f);
} else {
this->white_ = optional<float>(0.0f);
}
}
}
// If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled)
else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (traits.get_supports_color_interlock()) {
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) {
this->white_ = optional<float>(1.0f);
} else {
this->white_ = optional<float>(0.0f);
}
}
}
// If only a color temperature is specified, change to white light
else if (this->color_temperature_.has_value()) {
this->red_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f);
// if setting color temperature from color (i.e. switching to white light), set White to 100%
auto cv = this->parent_->remote_values;
bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f;
if (traits.get_supports_color_interlock() || was_color) {
this->white_ = optional<float>(1.0f);
}
}
#define VALIDATE_RANGE_(name_, upper_name) \
if (name_##_.has_value()) { \
auto val = *name_##_; \
if (val < 0.0f || val > 1.0f) { \
ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [0.0 - 1.0]!", name, upper_name, val); \
name_##_ = clamp(val, 0.0f, 1.0f); \
} \
}
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name)
// Range checks
VALIDATE_RANGE(brightness, "Brightness")
VALIDATE_RANGE(red, "Red")
VALIDATE_RANGE(green, "Green")
VALIDATE_RANGE(blue, "Blue")
VALIDATE_RANGE(white, "White")
auto v = this->parent_->remote_values;
if (this->state_.has_value())
v.set_state(*this->state_);
if (this->brightness_.has_value())
v.set_brightness(*this->brightness_);
if (this->red_.has_value())
v.set_red(*this->red_);
if (this->green_.has_value())
v.set_green(*this->green_);
if (this->blue_.has_value())
v.set_blue(*this->blue_);
if (this->white_.has_value())
v.set_white(*this->white_);
if (this->color_temperature_.has_value())
v.set_color_temperature(*this->color_temperature_);
v.normalize_color(traits);
// Flash length check
if (this->has_flash_() && *this->flash_length_ == 0) {
ESP_LOGW(TAG, "'%s' - Flash length must be greater than zero!", name);
this->flash_length_.reset();
}
// validate transition length/flash length/effect not used at the same time
bool supports_transition = traits.get_supports_brightness();
// If effect is already active, remove effect start
if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) {
this->effect_.reset();
}
// validate effect index
if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) {
ESP_LOGW(TAG, "'%s' Invalid effect index %u", name, *this->effect_);
this->effect_.reset();
}
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
ESP_LOGW(TAG, "'%s' - Effect cannot be used together with transition/flash!", name);
this->transition_length_.reset();
this->flash_length_.reset();
}
if (this->has_flash_() && this->has_transition_()) {
ESP_LOGW(TAG, "'%s' - Flash cannot be used together with transition!", name);
this->transition_length_.reset();
}
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || *this->effect_ == 0) &&
supports_transition) {
// nothing specified and light supports transitions, set default transition length
this->transition_length_ = this->parent_->default_transition_length_;
}
if (this->transition_length_.value_or(0) == 0) {
// 0 transition is interpreted as no transition (instant change)
this->transition_length_.reset();
}
if (this->has_transition_() && !supports_transition) {
ESP_LOGW(TAG, "'%s' - Light does not support transitions!", name);
this->transition_length_.reset();
}
// If not a flash and turning the light off, then disable the light
// Do not use light color values directly, so that effects can set 0% brightness
// Reason: When user turns off the light in frontend, the effect should also stop
if (!this->has_flash_() && !this->state_.value_or(v.is_on())) {
if (this->has_effect_()) {
ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name);
this->effect_.reset();
} else if (this->parent_->active_effect_index_ != 0) {
// Auto turn off effect
this->effect_ = 0;
}
}
// Disable saving for flashes
if (this->has_flash_())
this->save_ = false;
return v;
}
LightCall &LightCall::set_effect(const std::string &effect) {
if (strcasecmp(effect.c_str(), "none") == 0) {
this->set_effect(0);
return *this;
}
bool found = false;
for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) {
LightEffect *e = this->parent_->effects_[i];
if (strcasecmp(effect.c_str(), e->get_name().c_str()) == 0) {
this->set_effect(i + 1);
found = true;
break;
}
}
if (!found) {
ESP_LOGW(TAG, "'%s' - No such effect '%s'", this->parent_->get_name().c_str(), effect.c_str());
}
return *this;
}
LightCall &LightCall::from_light_color_values(const LightColorValues &values) {
this->set_state(values.is_on());
this->set_brightness_if_supported(values.get_brightness());
this->set_red_if_supported(values.get_red());
this->set_green_if_supported(values.get_green());
this->set_blue_if_supported(values.get_blue());
this->set_white_if_supported(values.get_white());
this->set_color_temperature_if_supported(values.get_color_temperature());
return *this;
}
LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) {
if (this->parent_->get_traits().get_supports_brightness())
this->set_transition_length(transition_length);
return *this;
}
LightCall &LightCall::set_brightness_if_supported(float brightness) {
if (this->parent_->get_traits().get_supports_brightness())
this->set_brightness(brightness);
return *this;
}
LightCall &LightCall::set_red_if_supported(float red) {
if (this->parent_->get_traits().get_supports_rgb())
this->set_red(red);
return *this;
}
LightCall &LightCall::set_green_if_supported(float green) {
if (this->parent_->get_traits().get_supports_rgb())
this->set_green(green);
return *this;
}
LightCall &LightCall::set_blue_if_supported(float blue) {
if (this->parent_->get_traits().get_supports_rgb())
this->set_blue(blue);
return *this;
}
LightCall &LightCall::set_white_if_supported(float white) {
if (this->parent_->get_traits().get_supports_rgb_white_value())
this->set_white(white);
return *this;
}
LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) {
if (this->parent_->get_traits().get_supports_color_temperature())
this->set_color_temperature(color_temperature);
return *this;
}
LightCall &LightCall::set_state(optional<bool> state) {
this->state_ = state;
return *this;
}
LightCall &LightCall::set_state(bool state) {
this->state_ = state;
return *this;
}
LightCall &LightCall::set_transition_length(optional<uint32_t> transition_length) {
this->transition_length_ = transition_length;
return *this;
}
LightCall &LightCall::set_transition_length(uint32_t transition_length) {
this->transition_length_ = transition_length;
return *this;
}
LightCall &LightCall::set_flash_length(optional<uint32_t> flash_length) {
this->flash_length_ = flash_length;
return *this;
}
LightCall &LightCall::set_flash_length(uint32_t flash_length) {
this->flash_length_ = flash_length;
return *this;
}
LightCall &LightCall::set_brightness(optional<float> brightness) {
this->brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_brightness(float brightness) {
this->brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_red(optional<float> red) {
this->red_ = red;
return *this;
}
LightCall &LightCall::set_red(float red) {
this->red_ = red;
return *this;
}
LightCall &LightCall::set_green(optional<float> green) {
this->green_ = green;
return *this;
}
LightCall &LightCall::set_green(float green) {
this->green_ = green;
return *this;
}
LightCall &LightCall::set_blue(optional<float> blue) {
this->blue_ = blue;
return *this;
}
LightCall &LightCall::set_blue(float blue) {
this->blue_ = blue;
return *this;
}
LightCall &LightCall::set_white(optional<float> white) {
this->white_ = white;
return *this;
}
LightCall &LightCall::set_white(float white) {
this->white_ = white;
return *this;
}
LightCall &LightCall::set_color_temperature(optional<float> color_temperature) {
this->color_temperature_ = color_temperature;
return *this;
}
LightCall &LightCall::set_color_temperature(float color_temperature) {
this->color_temperature_ = color_temperature;
return *this;
}
LightCall &LightCall::set_effect(optional<std::string> effect) {
if (effect.has_value())
this->set_effect(*effect);
return *this;
}
LightCall &LightCall::set_effect(uint32_t effect_number) {
this->effect_ = effect_number;
return *this;
}
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
this->effect_ = effect_number;
return *this;
}
LightCall &LightCall::set_publish(bool publish) {
this->publish_ = publish;
return *this;
}
LightCall &LightCall::set_save(bool save) {
this->save_ = save;
return *this;
}
LightCall &LightCall::set_rgb(float red, float green, float blue) {
this->set_red(red);
this->set_green(green);
this->set_blue(blue);
return *this;
}
LightCall &LightCall::set_rgbw(float red, float green, float blue, float white) {
this->set_rgb(red, green, blue);
this->set_white(white);
return *this;
}
} // namespace light
} // namespace esphome

View file

@ -0,0 +1,160 @@
#pragma once
#include "esphome/core/optional.h"
#include "light_color_values.h"
namespace esphome {
namespace light {
class LightState;
/** This class represents a requested change in a light state.
*/
class LightCall {
public:
explicit LightCall(LightState *parent) : parent_(parent) {}
/// Set the binary ON/OFF state of the light.
LightCall &set_state(optional<bool> state);
/// Set the binary ON/OFF state of the light.
LightCall &set_state(bool state);
/** Set the transition length of this call in milliseconds.
*
* This argument is ignored for starting flashes and effects.
*
* Defaults to the default transition length defined in the light configuration.
*/
LightCall &set_transition_length(optional<uint32_t> transition_length);
/** Set the transition length of this call in milliseconds.
*
* This argument is ignored for starting flashes and effects.
*
* Defaults to the default transition length defined in the light configuration.
*/
LightCall &set_transition_length(uint32_t transition_length);
/// Set the transition length property if the light supports transitions.
LightCall &set_transition_length_if_supported(uint32_t transition_length);
/// Start and set the flash length of this call in milliseconds.
LightCall &set_flash_length(optional<uint32_t> flash_length);
/// Start and set the flash length of this call in milliseconds.
LightCall &set_flash_length(uint32_t flash_length);
/// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on)
LightCall &set_brightness(optional<float> brightness);
/// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on)
LightCall &set_brightness(float brightness);
/// Set the brightness property if the light supports brightness.
LightCall &set_brightness_if_supported(float brightness);
/** Set the red RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_red(optional<float> red);
/** Set the red RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_red(float red);
/// Set the red property if the light supports RGB.
LightCall &set_red_if_supported(float red);
/** Set the green RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_green(optional<float> green);
/** Set the green RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_green(float green);
/// Set the green property if the light supports RGB.
LightCall &set_green_if_supported(float green);
/** Set the blue RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_blue(optional<float> blue);
/** Set the blue RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_blue(float blue);
/// Set the blue property if the light supports RGB.
LightCall &set_blue_if_supported(float blue);
/// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights.
LightCall &set_white(optional<float> white);
/// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights.
LightCall &set_white(float white);
/// Set the white property if the light supports RGB.
LightCall &set_white_if_supported(float white);
/// Set the color temperature of the light in mireds for CWWW or RGBWW lights.
LightCall &set_color_temperature(optional<float> color_temperature);
/// Set the color temperature of the light in mireds for CWWW or RGBWW lights.
LightCall &set_color_temperature(float color_temperature);
/// Set the color_temperature property if the light supports color temperature.
LightCall &set_color_temperature_if_supported(float color_temperature);
/// Set the effect of the light by its name.
LightCall &set_effect(optional<std::string> effect);
/// Set the effect of the light by its name.
LightCall &set_effect(const std::string &effect);
/// Set the effect of the light by its internal index number (only for internal use).
LightCall &set_effect(uint32_t effect_number);
LightCall &set_effect(optional<uint32_t> effect_number);
/// Set whether this light call should trigger a publish state.
LightCall &set_publish(bool publish);
/// Set whether this light call should trigger a save state to recover them at startup..
LightCall &set_save(bool save);
/** Set the RGB color of the light by RGB values.
*
* Please note that this only changes the color of the light, not the brightness.
*
* @param red The red color value from 0.0 to 1.0.
* @param green The green color value from 0.0 to 1.0.
* @param blue The blue color value from 0.0 to 1.0.
* @return The light call for chaining setters.
*/
LightCall &set_rgb(float red, float green, float blue);
/** Set the RGBW color of the light by RGB values.
*
* Please note that this only changes the color of the light, not the brightness.
*
* @param red The red color value from 0.0 to 1.0.
* @param green The green color value from 0.0 to 1.0.
* @param blue The blue color value from 0.0 to 1.0.
* @param white The white color value from 0.0 to 1.0.
* @return The light call for chaining setters.
*/
LightCall &set_rgbw(float red, float green, float blue, float white);
#ifdef USE_JSON
LightCall &parse_color_json(JsonObject &root);
LightCall &parse_json(JsonObject &root);
#endif
LightCall &from_light_color_values(const LightColorValues &values);
void perform();
protected:
/// Validate all properties and return the target light color values.
LightColorValues validate_();
bool has_transition_() { return this->transition_length_.has_value(); }
bool has_flash_() { return this->flash_length_.has_value(); }
bool has_effect_() { return this->effect_.has_value(); }
LightState *parent_;
optional<bool> state_;
optional<uint32_t> transition_length_;
optional<uint32_t> flash_length_;
optional<float> brightness_;
optional<float> red_;
optional<float> green_;
optional<float> blue_;
optional<float> white_;
optional<float> color_temperature_;
optional<uint32_t> effect_;
bool publish_{true};
bool save_{true};
};
} // namespace light
} // namespace esphome

View file

@ -7,80 +7,13 @@ namespace light {
static const char *const TAG = "light"; static const char *const TAG = "light";
void LightState::start_transition_(const LightColorValues &target, uint32_t length) {
this->transformer_ = make_unique<LightTransitionTransformer>(millis(), length, this->current_values, target);
this->remote_values = this->transformer_->get_remote_values();
}
void LightState::start_flash_(const LightColorValues &target, uint32_t length) {
LightColorValues end_colors = this->current_values;
// If starting a flash if one is already happening, set end values to end values of current flash
// Hacky but works
if (this->transformer_ != nullptr)
end_colors = this->transformer_->get_end_values();
this->transformer_ = make_unique<LightFlashTransformer>(millis(), length, end_colors, target);
this->remote_values = this->transformer_->get_remote_values();
}
LightState::LightState(const std::string &name, LightOutput *output) : Nameable(name), output_(output) {} LightState::LightState(const std::string &name, LightOutput *output) : Nameable(name), output_(output) {}
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { LightTraits LightState::get_traits() { return this->output_->get_traits(); }
this->transformer_ = nullptr; LightCall LightState::turn_on() { return this->make_call().set_state(true); }
this->current_values = target; LightCall LightState::turn_off() { return this->make_call().set_state(false); }
if (set_remote_values) { LightCall LightState::toggle() { return this->make_call().set_state(!this->remote_values.is_on()); }
this->remote_values = target; LightCall LightState::make_call() { return LightCall(this); }
}
this->next_write_ = true;
}
LightColorValues LightState::get_current_values() { return this->current_values; }
void LightState::publish_state() {
this->remote_values_callback_.call();
this->next_write_ = true;
}
LightColorValues LightState::get_remote_values() { return this->remote_values; }
std::string LightState::get_effect_name() {
if (this->active_effect_index_ > 0)
return this->effects_[this->active_effect_index_ - 1]->get_name();
else
return "None";
}
void LightState::start_effect_(uint32_t effect_index) {
this->stop_effect_();
if (effect_index == 0)
return;
this->active_effect_index_ = effect_index;
auto *effect = this->get_active_effect_();
effect->start_internal();
}
bool LightState::supports_effects() { return !this->effects_.empty(); }
void LightState::set_transformer_(std::unique_ptr<LightTransformer> transformer) {
this->transformer_ = std::move(transformer);
}
void LightState::stop_effect_() {
auto *effect = this->get_active_effect_();
if (effect != nullptr) {
effect->stop();
}
this->active_effect_index_ = 0;
}
void LightState::set_default_transition_length(uint32_t default_transition_length) {
this->default_transition_length_ = default_transition_length;
}
#ifdef USE_JSON
void LightState::dump_json(JsonObject &root) {
if (this->supports_effects())
root["effect"] = this->get_effect_name();
this->remote_values.dump_json(root, this->output_->get_traits());
}
#endif
struct LightStateRTCState { struct LightStateRTCState {
bool state{false}; bool state{false};
@ -144,6 +77,17 @@ void LightState::setup() {
} }
call.perform(); call.perform();
} }
void LightState::dump_config() {
ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str());
if (this->get_traits().get_supports_brightness()) {
ESP_LOGCONFIG(TAG, " Default Transition Length: %.1fs", this->default_transition_length_ / 1e3f);
ESP_LOGCONFIG(TAG, " Gamma Correct: %.2f", this->gamma_correct_);
}
if (this->get_traits().get_supports_color_temperature()) {
ESP_LOGCONFIG(TAG, " Min Mireds: %.1f", this->get_traits().get_min_mireds());
ESP_LOGCONFIG(TAG, " Max Mireds: %.1f", this->get_traits().get_max_mireds());
}
}
void LightState::loop() { void LightState::loop() {
// Apply effect (if any) // Apply effect (if any)
auto *effect = this->get_active_effect_(); auto *effect = this->get_active_effect_();
@ -171,7 +115,47 @@ void LightState::loop() {
this->next_write_ = false; this->next_write_ = false;
} }
} }
LightTraits LightState::get_traits() { return this->output_->get_traits(); }
float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
uint32_t LightState::hash_base() { return 1114400283; }
LightColorValues LightState::get_current_values() { return this->current_values; }
LightColorValues LightState::get_remote_values() { return this->remote_values; }
void LightState::publish_state() {
this->remote_values_callback_.call();
this->next_write_ = true;
}
LightOutput *LightState::get_output() const { return this->output_; }
std::string LightState::get_effect_name() {
if (this->active_effect_index_ > 0)
return this->effects_[this->active_effect_index_ - 1]->get_name();
else
return "None";
}
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
this->remote_values_callback_.add(std::move(send_callback));
}
void LightState::add_new_target_state_reached_callback(std::function<void()> &&send_callback) {
this->target_state_reached_callback_.add(std::move(send_callback));
}
#ifdef USE_JSON
void LightState::dump_json(JsonObject &root) {
if (this->supports_effects())
root["effect"] = this->get_effect_name();
this->remote_values.dump_json(root, this->output_->get_traits());
}
#endif
void LightState::set_default_transition_length(uint32_t default_transition_length) {
this->default_transition_length_ = default_transition_length;
}
void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
bool LightState::supports_effects() { return !this->effects_.empty(); }
const std::vector<LightEffect *> &LightState::get_effects() const { return this->effects_; } const std::vector<LightEffect *> &LightState::get_effects() const { return this->effects_; }
void LightState::add_effects(const std::vector<LightEffect *> &effects) { void LightState::add_effects(const std::vector<LightEffect *> &effects) {
this->effects_.reserve(this->effects_.size() + effects.size()); this->effects_.reserve(this->effects_.size() + effects.size());
@ -179,551 +163,7 @@ void LightState::add_effects(const std::vector<LightEffect *> &effects) {
this->effects_.push_back(effect); this->effects_.push_back(effect);
} }
} }
LightCall LightState::turn_on() { return this->make_call().set_state(true); }
LightCall LightState::turn_off() { return this->make_call().set_state(false); }
LightCall LightState::toggle() { return this->make_call().set_state(!this->remote_values.is_on()); }
LightCall LightState::make_call() { return LightCall(this); }
uint32_t LightState::hash_base() { return 1114400283; }
void LightState::dump_config() {
ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str());
if (this->get_traits().get_supports_brightness()) {
ESP_LOGCONFIG(TAG, " Default Transition Length: %.1fs", this->default_transition_length_ / 1e3f);
ESP_LOGCONFIG(TAG, " Gamma Correct: %.2f", this->gamma_correct_);
}
if (this->get_traits().get_supports_color_temperature()) {
ESP_LOGCONFIG(TAG, " Min Mireds: %.1f", this->get_traits().get_min_mireds());
ESP_LOGCONFIG(TAG, " Max Mireds: %.1f", this->get_traits().get_max_mireds());
}
}
#ifdef USE_MQTT_LIGHT
MQTTJSONLightComponent *LightState::get_mqtt() const { return this->mqtt_; }
void LightState::set_mqtt(MQTTJSONLightComponent *mqtt) { this->mqtt_ = mqtt; }
#endif
#ifdef USE_JSON
LightCall &LightCall::parse_color_json(JsonObject &root) {
if (root.containsKey("state")) {
auto val = parse_on_off(root["state"]);
switch (val) {
case PARSE_ON:
this->set_state(true);
break;
case PARSE_OFF:
this->set_state(false);
break;
case PARSE_TOGGLE:
this->set_state(!this->parent_->remote_values.is_on());
break;
case PARSE_NONE:
break;
}
}
if (root.containsKey("brightness")) {
this->set_brightness(float(root["brightness"]) / 255.0f);
}
if (root.containsKey("color")) {
JsonObject &color = root["color"];
if (color.containsKey("r")) {
this->set_red(float(color["r"]) / 255.0f);
}
if (color.containsKey("g")) {
this->set_green(float(color["g"]) / 255.0f);
}
if (color.containsKey("b")) {
this->set_blue(float(color["b"]) / 255.0f);
}
}
if (root.containsKey("white_value")) {
this->set_white(float(root["white_value"]) / 255.0f);
}
if (root.containsKey("color_temp")) {
this->set_color_temperature(float(root["color_temp"]));
}
return *this;
}
LightCall &LightCall::parse_json(JsonObject &root) {
this->parse_color_json(root);
if (root.containsKey("flash")) {
auto length = uint32_t(float(root["flash"]) * 1000);
this->set_flash_length(length);
}
if (root.containsKey("transition")) {
auto length = uint32_t(float(root["transition"]) * 1000);
this->set_transition_length(length);
}
if (root.containsKey("effect")) {
const char *effect = root["effect"];
this->set_effect(effect);
}
return *this;
}
#endif
void LightCall::perform() {
// use remote values for fallback
const char *name = this->parent_->get_name().c_str();
if (this->publish_) {
ESP_LOGD(TAG, "'%s' Setting:", name);
}
LightColorValues v = this->validate_();
if (this->publish_) {
// Only print state when it's being changed
bool current_state = this->parent_->remote_values.is_on();
if (this->state_.value_or(current_state) != current_state) {
ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on()));
}
if (this->brightness_.has_value()) {
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f);
}
if (this->color_temperature_.has_value()) {
ESP_LOGD(TAG, " Color Temperature: %.1f mireds", v.get_color_temperature());
}
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
ESP_LOGD(TAG, " Red=%.0f%%, Green=%.0f%%, Blue=%.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
v.get_blue() * 100.0f);
}
if (this->white_.has_value()) {
ESP_LOGD(TAG, " White Value: %.0f%%", v.get_white() * 100.0f);
}
}
if (this->has_flash_()) {
// FLASH
if (this->publish_) {
ESP_LOGD(TAG, " Flash Length: %.1fs", *this->flash_length_ / 1e3f);
}
this->parent_->start_flash_(v, *this->flash_length_);
} else if (this->has_transition_()) {
// TRANSITION
if (this->publish_) {
ESP_LOGD(TAG, " Transition Length: %.1fs", *this->transition_length_ / 1e3f);
}
// Special case: Transition and effect can be set when turning off
if (this->has_effect_()) {
if (this->publish_) {
ESP_LOGD(TAG, " Effect: 'None'");
}
this->parent_->stop_effect_();
}
this->parent_->start_transition_(v, *this->transition_length_);
} else if (this->has_effect_()) {
// EFFECT
auto effect = this->effect_;
const char *effect_s;
if (effect == 0)
effect_s = "None";
else
effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();
if (this->publish_) {
ESP_LOGD(TAG, " Effect: '%s'", effect_s);
}
this->parent_->start_effect_(*this->effect_);
// Also set light color values when starting an effect
// For example to turn off the light
this->parent_->set_immediately_(v, true);
} else {
// INSTANT CHANGE
this->parent_->set_immediately_(v, this->publish_);
}
if (!this->has_transition_()) {
this->parent_->target_state_reached_callback_.call();
}
if (this->publish_) {
this->parent_->publish_state();
}
if (this->save_) {
LightStateRTCState saved;
saved.state = v.is_on();
saved.brightness = v.get_brightness();
saved.red = v.get_red();
saved.green = v.get_green();
saved.blue = v.get_blue();
saved.white = v.get_white();
saved.color_temp = v.get_color_temperature();
saved.effect = this->parent_->active_effect_index_;
this->parent_->rtc_.save(&saved);
}
}
LightColorValues LightCall::validate_() {
// use remote values for fallback
auto *name = this->parent_->get_name().c_str();
auto traits = this->parent_->get_traits();
// Brightness exists check
if (this->brightness_.has_value() && !traits.get_supports_brightness()) {
ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name);
this->brightness_.reset();
}
// Transition length possible check
if (this->transition_length_.has_value() && *this->transition_length_ != 0 && !traits.get_supports_brightness()) {
ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name);
this->transition_length_.reset();
}
// RGB exists check
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (!traits.get_supports_rgb()) {
ESP_LOGW(TAG, "'%s' - This light does not support setting RGB color!", name);
this->red_.reset();
this->green_.reset();
this->blue_.reset();
}
}
// White value exists check
if (this->white_.has_value() && !traits.get_supports_rgb_white_value()) {
ESP_LOGW(TAG, "'%s' - This light does not support setting white value!", name);
this->white_.reset();
}
// Color temperature exists check
if (this->color_temperature_.has_value() && !traits.get_supports_color_temperature()) {
ESP_LOGW(TAG, "'%s' - This light does not support setting color temperature!", name);
this->color_temperature_.reset();
}
// If white channel is specified, set RGB to white color (when interlock is enabled)
if (this->white_.has_value()) {
if (traits.get_supports_color_interlock()) {
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
this->red_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f);
}
// make white values binary aka 0.0f or 1.0f... this allows brightness to do its job
if (*this->white_ > 0.0f) {
this->white_ = optional<float>(1.0f);
} else {
this->white_ = optional<float>(0.0f);
}
}
}
// If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled)
else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (traits.get_supports_color_interlock()) {
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) {
this->white_ = optional<float>(1.0f);
} else {
this->white_ = optional<float>(0.0f);
}
}
}
// If only a color temperature is specified, change to white light
else if (this->color_temperature_.has_value()) {
this->red_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f);
// if setting color temperature from color (i.e. switching to white light), set White to 100%
auto cv = this->parent_->remote_values;
bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f;
if (traits.get_supports_color_interlock() || was_color) {
this->white_ = optional<float>(1.0f);
}
}
#define VALIDATE_RANGE_(name_, upper_name) \
if (name_##_.has_value()) { \
auto val = *name_##_; \
if (val < 0.0f || val > 1.0f) { \
ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [0.0 - 1.0]!", name, upper_name, val); \
name_##_ = clamp(val, 0.0f, 1.0f); \
} \
}
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name)
// Range checks
VALIDATE_RANGE(brightness, "Brightness")
VALIDATE_RANGE(red, "Red")
VALIDATE_RANGE(green, "Green")
VALIDATE_RANGE(blue, "Blue")
VALIDATE_RANGE(white, "White")
auto v = this->parent_->remote_values;
if (this->state_.has_value())
v.set_state(*this->state_);
if (this->brightness_.has_value())
v.set_brightness(*this->brightness_);
if (this->red_.has_value())
v.set_red(*this->red_);
if (this->green_.has_value())
v.set_green(*this->green_);
if (this->blue_.has_value())
v.set_blue(*this->blue_);
if (this->white_.has_value())
v.set_white(*this->white_);
if (this->color_temperature_.has_value())
v.set_color_temperature(*this->color_temperature_);
v.normalize_color(traits);
// Flash length check
if (this->has_flash_() && *this->flash_length_ == 0) {
ESP_LOGW(TAG, "'%s' - Flash length must be greater than zero!", name);
this->flash_length_.reset();
}
// validate transition length/flash length/effect not used at the same time
bool supports_transition = traits.get_supports_brightness();
// If effect is already active, remove effect start
if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) {
this->effect_.reset();
}
// validate effect index
if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) {
ESP_LOGW(TAG, "'%s' Invalid effect index %u", name, *this->effect_);
this->effect_.reset();
}
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
ESP_LOGW(TAG, "'%s' - Effect cannot be used together with transition/flash!", name);
this->transition_length_.reset();
this->flash_length_.reset();
}
if (this->has_flash_() && this->has_transition_()) {
ESP_LOGW(TAG, "'%s' - Flash cannot be used together with transition!", name);
this->transition_length_.reset();
}
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || *this->effect_ == 0) &&
supports_transition) {
// nothing specified and light supports transitions, set default transition length
this->transition_length_ = this->parent_->default_transition_length_;
}
if (this->transition_length_.value_or(0) == 0) {
// 0 transition is interpreted as no transition (instant change)
this->transition_length_.reset();
}
if (this->has_transition_() && !supports_transition) {
ESP_LOGW(TAG, "'%s' - Light does not support transitions!", name);
this->transition_length_.reset();
}
// If not a flash and turning the light off, then disable the light
// Do not use light color values directly, so that effects can set 0% brightness
// Reason: When user turns off the light in frontend, the effect should also stop
if (!this->has_flash_() && !this->state_.value_or(v.is_on())) {
if (this->has_effect_()) {
ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name);
this->effect_.reset();
} else if (this->parent_->active_effect_index_ != 0) {
// Auto turn off effect
this->effect_ = 0;
}
}
// Disable saving for flashes
if (this->has_flash_())
this->save_ = false;
return v;
}
LightCall &LightCall::set_effect(const std::string &effect) {
if (strcasecmp(effect.c_str(), "none") == 0) {
this->set_effect(0);
return *this;
}
bool found = false;
for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) {
LightEffect *e = this->parent_->effects_[i];
if (strcasecmp(effect.c_str(), e->get_name().c_str()) == 0) {
this->set_effect(i + 1);
found = true;
break;
}
}
if (!found) {
ESP_LOGW(TAG, "'%s' - No such effect '%s'", this->parent_->get_name().c_str(), effect.c_str());
}
return *this;
}
LightCall &LightCall::from_light_color_values(const LightColorValues &values) {
this->set_state(values.is_on());
this->set_brightness_if_supported(values.get_brightness());
this->set_red_if_supported(values.get_red());
this->set_green_if_supported(values.get_green());
this->set_blue_if_supported(values.get_blue());
this->set_white_if_supported(values.get_white());
this->set_color_temperature_if_supported(values.get_color_temperature());
return *this;
}
LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) {
if (this->parent_->get_traits().get_supports_brightness())
this->set_transition_length(transition_length);
return *this;
}
LightCall &LightCall::set_brightness_if_supported(float brightness) {
if (this->parent_->get_traits().get_supports_brightness())
this->set_brightness(brightness);
return *this;
}
LightCall &LightCall::set_red_if_supported(float red) {
if (this->parent_->get_traits().get_supports_rgb())
this->set_red(red);
return *this;
}
LightCall &LightCall::set_green_if_supported(float green) {
if (this->parent_->get_traits().get_supports_rgb())
this->set_green(green);
return *this;
}
LightCall &LightCall::set_blue_if_supported(float blue) {
if (this->parent_->get_traits().get_supports_rgb())
this->set_blue(blue);
return *this;
}
LightCall &LightCall::set_white_if_supported(float white) {
if (this->parent_->get_traits().get_supports_rgb_white_value())
this->set_white(white);
return *this;
}
LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) {
if (this->parent_->get_traits().get_supports_color_temperature())
this->set_color_temperature(color_temperature);
return *this;
}
LightCall &LightCall::set_state(optional<bool> state) {
this->state_ = state;
return *this;
}
LightCall &LightCall::set_state(bool state) {
this->state_ = state;
return *this;
}
LightCall &LightCall::set_transition_length(optional<uint32_t> transition_length) {
this->transition_length_ = transition_length;
return *this;
}
LightCall &LightCall::set_transition_length(uint32_t transition_length) {
this->transition_length_ = transition_length;
return *this;
}
LightCall &LightCall::set_flash_length(optional<uint32_t> flash_length) {
this->flash_length_ = flash_length;
return *this;
}
LightCall &LightCall::set_flash_length(uint32_t flash_length) {
this->flash_length_ = flash_length;
return *this;
}
LightCall &LightCall::set_brightness(optional<float> brightness) {
this->brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_brightness(float brightness) {
this->brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_red(optional<float> red) {
this->red_ = red;
return *this;
}
LightCall &LightCall::set_red(float red) {
this->red_ = red;
return *this;
}
LightCall &LightCall::set_green(optional<float> green) {
this->green_ = green;
return *this;
}
LightCall &LightCall::set_green(float green) {
this->green_ = green;
return *this;
}
LightCall &LightCall::set_blue(optional<float> blue) {
this->blue_ = blue;
return *this;
}
LightCall &LightCall::set_blue(float blue) {
this->blue_ = blue;
return *this;
}
LightCall &LightCall::set_white(optional<float> white) {
this->white_ = white;
return *this;
}
LightCall &LightCall::set_white(float white) {
this->white_ = white;
return *this;
}
LightCall &LightCall::set_color_temperature(optional<float> color_temperature) {
this->color_temperature_ = color_temperature;
return *this;
}
LightCall &LightCall::set_color_temperature(float color_temperature) {
this->color_temperature_ = color_temperature;
return *this;
}
LightCall &LightCall::set_effect(optional<std::string> effect) {
if (effect.has_value())
this->set_effect(*effect);
return *this;
}
LightCall &LightCall::set_effect(uint32_t effect_number) {
this->effect_ = effect_number;
return *this;
}
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
this->effect_ = effect_number;
return *this;
}
LightCall &LightCall::set_publish(bool publish) {
this->publish_ = publish;
return *this;
}
LightCall &LightCall::set_save(bool save) {
this->save_ = save;
return *this;
}
LightCall &LightCall::set_rgb(float red, float green, float blue) {
this->set_red(red);
this->set_green(green);
this->set_blue(blue);
return *this;
}
LightCall &LightCall::set_rgbw(float red, float green, float blue, float white) {
this->set_rgb(red, green, blue);
this->set_white(white);
return *this;
}
float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
LightOutput *LightState::get_output() const { return this->output_; }
void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); } void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); }
void LightState::current_values_as_brightness(float *brightness) { void LightState::current_values_as_brightness(float *brightness) {
this->current_values.as_brightness(brightness, this->gamma_correct_); this->current_values.as_brightness(brightness, this->gamma_correct_);
@ -748,19 +188,70 @@ void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bo
this->current_values.as_cwww(traits.get_min_mireds(), traits.get_max_mireds(), cold_white, warm_white, this->current_values.as_cwww(traits.get_min_mireds(), traits.get_max_mireds(), cold_white, warm_white,
this->gamma_correct_, constant_brightness); this->gamma_correct_, constant_brightness);
} }
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
this->remote_values_callback_.add(std::move(send_callback));
}
void LightState::add_new_target_state_reached_callback(std::function<void()> &&send_callback) {
this->target_state_reached_callback_.add(std::move(send_callback));
}
void LightState::start_effect_(uint32_t effect_index) {
this->stop_effect_();
if (effect_index == 0)
return;
this->active_effect_index_ = effect_index;
auto *effect = this->get_active_effect_();
effect->start_internal();
}
LightEffect *LightState::get_active_effect_() { LightEffect *LightState::get_active_effect_() {
if (this->active_effect_index_ == 0) if (this->active_effect_index_ == 0)
return nullptr; return nullptr;
else else
return this->effects_[this->active_effect_index_ - 1]; return this->effects_[this->active_effect_index_ - 1];
} }
void LightState::stop_effect_() {
auto *effect = this->get_active_effect_();
if (effect != nullptr) {
effect->stop();
}
this->active_effect_index_ = 0;
}
void LightState::start_transition_(const LightColorValues &target, uint32_t length) {
this->transformer_ = make_unique<LightTransitionTransformer>(millis(), length, this->current_values, target);
this->remote_values = this->transformer_->get_remote_values();
}
void LightState::start_flash_(const LightColorValues &target, uint32_t length) {
LightColorValues end_colors = this->current_values;
// If starting a flash if one is already happening, set end values to end values of current flash
// Hacky but works
if (this->transformer_ != nullptr)
end_colors = this->transformer_->get_end_values();
this->transformer_ = make_unique<LightFlashTransformer>(millis(), length, end_colors, target);
this->remote_values = this->transformer_->get_remote_values();
}
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
this->transformer_ = nullptr;
this->current_values = target;
if (set_remote_values) {
this->remote_values = target;
}
this->next_write_ = true;
}
void LightState::set_transformer_(std::unique_ptr<LightTransformer> transformer) {
this->transformer_ = std::move(transformer);
}
void LightState::save_remote_values_() {
LightStateRTCState saved;
saved.state = this->remote_values.is_on();
saved.brightness = this->remote_values.get_brightness();
saved.red = this->remote_values.get_red();
saved.green = this->remote_values.get_green();
saved.blue = this->remote_values.get_blue();
saved.white = this->remote_values.get_white();
saved.color_temp = this->remote_values.get_color_temperature();
saved.effect = this->active_effect_index_;
this->rtc_.save(&saved);
}
} // namespace light } // namespace light
} // namespace esphome } // namespace esphome

View file

@ -5,161 +5,15 @@
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "light_effect.h" #include "light_effect.h"
#include "light_color_values.h" #include "light_color_values.h"
#include "light_call.h"
#include "light_traits.h" #include "light_traits.h"
#include "light_transformer.h" #include "light_transformer.h"
namespace esphome { namespace esphome {
namespace light { namespace light {
class LightState;
class LightOutput; class LightOutput;
class LightCall {
public:
explicit LightCall(LightState *parent) : parent_(parent) {}
/// Set the binary ON/OFF state of the light.
LightCall &set_state(optional<bool> state);
/// Set the binary ON/OFF state of the light.
LightCall &set_state(bool state);
/** Set the transition length of this call in milliseconds.
*
* This argument is ignored for starting flashes and effects.
*
* Defaults to the default transition length defined in the light configuration.
*/
LightCall &set_transition_length(optional<uint32_t> transition_length);
/** Set the transition length of this call in milliseconds.
*
* This argument is ignored for starting flashes and effects.
*
* Defaults to the default transition length defined in the light configuration.
*/
LightCall &set_transition_length(uint32_t transition_length);
/// Set the transition length property if the light supports transitions.
LightCall &set_transition_length_if_supported(uint32_t transition_length);
/// Start and set the flash length of this call in milliseconds.
LightCall &set_flash_length(optional<uint32_t> flash_length);
/// Start and set the flash length of this call in milliseconds.
LightCall &set_flash_length(uint32_t flash_length);
/// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on)
LightCall &set_brightness(optional<float> brightness);
/// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on)
LightCall &set_brightness(float brightness);
/// Set the brightness property if the light supports brightness.
LightCall &set_brightness_if_supported(float brightness);
/** Set the red RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_red(optional<float> red);
/** Set the red RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_red(float red);
/// Set the red property if the light supports RGB.
LightCall &set_red_if_supported(float red);
/** Set the green RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_green(optional<float> green);
/** Set the green RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_green(float green);
/// Set the green property if the light supports RGB.
LightCall &set_green_if_supported(float green);
/** Set the blue RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_blue(optional<float> blue);
/** Set the blue RGB value of the light from 0.0 to 1.0.
*
* Note that this only controls the color of the light, not its brightness.
*/
LightCall &set_blue(float blue);
/// Set the blue property if the light supports RGB.
LightCall &set_blue_if_supported(float blue);
/// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights.
LightCall &set_white(optional<float> white);
/// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights.
LightCall &set_white(float white);
/// Set the white property if the light supports RGB.
LightCall &set_white_if_supported(float white);
/// Set the color temperature of the light in mireds for CWWW or RGBWW lights.
LightCall &set_color_temperature(optional<float> color_temperature);
/// Set the color temperature of the light in mireds for CWWW or RGBWW lights.
LightCall &set_color_temperature(float color_temperature);
/// Set the color_temperature property if the light supports color temperature.
LightCall &set_color_temperature_if_supported(float color_temperature);
/// Set the effect of the light by its name.
LightCall &set_effect(optional<std::string> effect);
/// Set the effect of the light by its name.
LightCall &set_effect(const std::string &effect);
/// Set the effect of the light by its internal index number (only for internal use).
LightCall &set_effect(uint32_t effect_number);
LightCall &set_effect(optional<uint32_t> effect_number);
/// Set whether this light call should trigger a publish state.
LightCall &set_publish(bool publish);
/// Set whether this light call should trigger a save state to recover them at startup..
LightCall &set_save(bool save);
/** Set the RGB color of the light by RGB values.
*
* Please note that this only changes the color of the light, not the brightness.
*
* @param red The red color value from 0.0 to 1.0.
* @param green The green color value from 0.0 to 1.0.
* @param blue The blue color value from 0.0 to 1.0.
* @return The light call for chaining setters.
*/
LightCall &set_rgb(float red, float green, float blue);
/** Set the RGBW color of the light by RGB values.
*
* Please note that this only changes the color of the light, not the brightness.
*
* @param red The red color value from 0.0 to 1.0.
* @param green The green color value from 0.0 to 1.0.
* @param blue The blue color value from 0.0 to 1.0.
* @param white The white color value from 0.0 to 1.0.
* @return The light call for chaining setters.
*/
LightCall &set_rgbw(float red, float green, float blue, float white);
#ifdef USE_JSON
LightCall &parse_color_json(JsonObject &root);
LightCall &parse_json(JsonObject &root);
#endif
LightCall &from_light_color_values(const LightColorValues &values);
void perform();
protected:
/// Validate all properties and return the target light color values.
LightColorValues validate_();
bool has_transition_() { return this->transition_length_.has_value(); }
bool has_flash_() { return this->flash_length_.has_value(); }
bool has_effect_() { return this->effect_.has_value(); }
LightState *parent_;
optional<bool> state_;
optional<uint32_t> transition_length_;
optional<uint32_t> flash_length_;
optional<float> brightness_;
optional<float> red_;
optional<float> green_;
optional<float> blue_;
optional<float> white_;
optional<float> color_temperature_;
optional<uint32_t> effect_;
bool publish_{true};
bool save_{true};
};
enum LightRestoreMode { enum LightRestoreMode {
LIGHT_RESTORE_DEFAULT_OFF, LIGHT_RESTORE_DEFAULT_OFF,
LIGHT_RESTORE_DEFAULT_ON, LIGHT_RESTORE_DEFAULT_ON,
@ -204,14 +58,6 @@ class LightState : public Nameable, public Component {
*/ */
LightColorValues current_values; LightColorValues current_values;
/// Deprecated method to access current_values.
ESPDEPRECATED("get_current_values() is deprecated, please use .current_values instead.")
LightColorValues get_current_values();
/// Deprecated method to access remote_values.
ESPDEPRECATED("get_remote_values() is deprecated, please use .remote_values instead.")
LightColorValues get_remote_values();
/** The remote color values reported to the frontend. /** The remote color values reported to the frontend.
* *
* These are different from the "current" values: For example transitions will * These are different from the "current" values: For example transitions will
@ -222,6 +68,14 @@ class LightState : public Nameable, public Component {
*/ */
LightColorValues remote_values; LightColorValues remote_values;
/// Deprecated method to access current_values.
ESPDEPRECATED("get_current_values() is deprecated, please use .current_values instead.")
LightColorValues get_current_values();
/// Deprecated method to access remote_values.
ESPDEPRECATED("get_remote_values() is deprecated, please use .remote_values instead.")
LightColorValues get_remote_values();
/// Publish the currently active state to the frontend. /// Publish the currently active state to the frontend.
void publish_state(); void publish_state();
@ -231,29 +85,22 @@ class LightState : public Nameable, public Component {
/// Return the name of the current effect, or if no effect is active "None". /// Return the name of the current effect, or if no effect is active "None".
std::string get_effect_name(); std::string get_effect_name();
/** This lets front-end components subscribe to light change events. /**
* * This lets front-end components subscribe to light change events. This callback is called once
* This is different from add_new_current_values_callback in that it only sends events for start * when the remote color values are changed.
* and end values. For example, with transitions it will only send a single callback whereas
* the callback passed in add_new_current_values_callback will be called every loop() cycle when
* a transition is active
*
* Note the callback should get the output values through get_remote_values().
* *
* @param send_callback The callback. * @param send_callback The callback.
*/ */
void add_new_remote_values_callback(std::function<void()> &&send_callback); void add_new_remote_values_callback(std::function<void()> &&send_callback);
/** /**
* The callback is called once the state of current_values and remote_values are equal * The callback is called once the state of current_values and remote_values are equal (when the
* transition is finished).
* *
* @param send_callback * @param send_callback
*/ */
void add_new_target_state_reached_callback(std::function<void()> &&send_callback); void add_new_target_state_reached_callback(std::function<void()> &&send_callback);
/// Return whether the light has any effects that meet the trait requirements.
bool supports_effects();
#ifdef USE_JSON #ifdef USE_JSON
/// Dump the state of this light as JSON. /// Dump the state of this light as JSON.
void dump_json(JsonObject &root); void dump_json(JsonObject &root);
@ -265,10 +112,17 @@ class LightState : public Nameable, public Component {
/// Set the gamma correction factor /// Set the gamma correction factor
void set_gamma_correct(float gamma_correct); void set_gamma_correct(float gamma_correct);
float get_gamma_correct() const { return this->gamma_correct_; } float get_gamma_correct() const { return this->gamma_correct_; }
void set_restore_mode(LightRestoreMode restore_mode) { restore_mode_ = restore_mode; }
/// Set the restore mode of this light
void set_restore_mode(LightRestoreMode restore_mode);
/// Return whether the light has any effects that meet the trait requirements.
bool supports_effects();
/// Get all effects for this light state.
const std::vector<LightEffect *> &get_effects() const; const std::vector<LightEffect *> &get_effects() const;
/// Add effects for this light state.
void add_effects(const std::vector<LightEffect *> &effects); void add_effects(const std::vector<LightEffect *> &effects);
void current_values_as_binary(bool *binary); void current_values_as_binary(bool *binary);
@ -293,6 +147,8 @@ class LightState : public Nameable, public Component {
/// Internal method to start an effect with the given index /// Internal method to start an effect with the given index
void start_effect_(uint32_t effect_index); void start_effect_(uint32_t effect_index);
/// Internal method to get the currently active effect
LightEffect *get_active_effect_();
/// Internal method to stop the current effect (if one is active). /// Internal method to stop the current effect (if one is active).
void stop_effect_(); void stop_effect_();
/// Internal method to start a transition to the target color with the given length. /// Internal method to start a transition to the target color with the given length.
@ -307,18 +163,21 @@ class LightState : public Nameable, public Component {
/// Internal method to start a transformer. /// Internal method to start a transformer.
void set_transformer_(std::unique_ptr<LightTransformer> transformer); void set_transformer_(std::unique_ptr<LightTransformer> transformer);
LightEffect *get_active_effect_(); /// Internal method to save the current remote_values to the preferences
void save_remote_values_();
/// Object used to store the persisted values of the light. /// Store the output to allow effects to have more access.
ESPPreferenceObject rtc_; LightOutput *output_;
/// Restore mode of the light.
LightRestoreMode restore_mode_;
/// Default transition length for all transitions in ms.
uint32_t default_transition_length_{};
/// Value for storing the index of the currently active effect. 0 if no effect is active /// Value for storing the index of the currently active effect. 0 if no effect is active
uint32_t active_effect_index_{}; uint32_t active_effect_index_{};
/// The currently active transformer for this light (transition/flash). /// The currently active transformer for this light (transition/flash).
std::unique_ptr<LightTransformer> transformer_{nullptr}; std::unique_ptr<LightTransformer> transformer_{nullptr};
/// Whether the light value should be written in the next cycle.
bool next_write_{true};
/// Object used to store the persisted values of the light.
ESPPreferenceObject rtc_;
/** Callback to call when new values for the frontend are available. /** Callback to call when new values for the frontend are available.
* *
* "Remote values" are light color values that are reported to the frontend and have a lower * "Remote values" are light color values that are reported to the frontend and have a lower
@ -333,11 +192,12 @@ class LightState : public Nameable, public Component {
*/ */
CallbackManager<void()> target_state_reached_callback_{}; CallbackManager<void()> target_state_reached_callback_{};
LightOutput *output_; ///< Store the output to allow effects to have more access. /// Default transition length for all transitions in ms.
/// Whether the light value should be written in the next cycle. uint32_t default_transition_length_{};
bool next_write_{true};
/// Gamma correction factor for the light. /// Gamma correction factor for the light.
float gamma_correct_{}; float gamma_correct_{};
/// Restore mode of the light.
LightRestoreMode restore_mode_;
/// List of effects for this light. /// List of effects for this light.
std::vector<LightEffect *> effects_; std::vector<LightEffect *> effects_;
}; };

View file

@ -16,6 +16,8 @@
// //
// Modified by Otto Winter on 18.05.18 // Modified by Otto Winter on 18.05.18
#include <algorithm>
namespace esphome { namespace esphome {
// type for nullopt // type for nullopt