From 0efc1f06f2538cb5caf78ccd666e2171adc97c8d Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Mon, 14 Jun 2021 18:01:56 +0200 Subject: [PATCH] Split files in light component (#1893) --- .../components/light/addressable_light.cpp | 170 ---- esphome/components/light/addressable_light.h | 263 +------ .../components/light/esp_color_correction.cpp | 26 + .../components/light/esp_color_correction.h | 78 ++ esphome/components/light/esp_color_view.h | 110 +++ esphome/components/light/esp_hsv_color.cpp | 74 ++ esphome/components/light/esp_hsv_color.h | 36 + esphome/components/light/esp_range_view.cpp | 96 +++ esphome/components/light/esp_range_view.h | 75 ++ esphome/components/light/light_call.cpp | 522 +++++++++++++ esphome/components/light/light_call.h | 160 ++++ esphome/components/light/light_state.cpp | 737 +++--------------- esphome/components/light/light_state.h | 216 +---- esphome/core/optional.h | 2 + 14 files changed, 1334 insertions(+), 1231 deletions(-) create mode 100644 esphome/components/light/esp_color_correction.cpp create mode 100644 esphome/components/light/esp_color_correction.h create mode 100644 esphome/components/light/esp_color_view.h create mode 100644 esphome/components/light/esp_hsv_color.cpp create mode 100644 esphome/components/light/esp_hsv_color.h create mode 100644 esphome/components/light/esp_range_view.cpp create mode 100644 esphome/components/light/esp_range_view.h create mode 100644 esphome/components/light/light_call.cpp create mode 100644 esphome/components/light/light_call.h diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index ecc48e32b8..e3ab9596d9 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -6,158 +6,6 @@ namespace light { 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() { this->setup(); @@ -254,23 +102,5 @@ void AddressableLight::write_state(LightState *state) { 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(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(roundf(255.0f * powf(i / 255.0f, 1.0f / gamma))); - this->gamma_reverse_table_[i] = uncorrected; - } -} - } // namespace light } // namespace esphome diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 39bd905c65..460cb88935 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -3,6 +3,9 @@ #include "esphome/core/component.h" #include "esphome/core/defines.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_state.h" @@ -15,266 +18,6 @@ namespace light { 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 { public: virtual int32_t size() const = 0; diff --git a/esphome/components/light/esp_color_correction.cpp b/esphome/components/light/esp_color_correction.cpp new file mode 100644 index 0000000000..19a2af3da1 --- /dev/null +++ b/esphome/components/light/esp_color_correction.cpp @@ -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(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(roundf(255.0f * powf(i / 255.0f, 1.0f / gamma))); + this->gamma_reverse_table_[i] = uncorrected; + } +} + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/esp_color_correction.h b/esphome/components/light/esp_color_correction.h new file mode 100644 index 0000000000..32fee8c8ea --- /dev/null +++ b/esphome/components/light/esp_color_correction.h @@ -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 diff --git a/esphome/components/light/esp_color_view.h b/esphome/components/light/esp_color_view.h new file mode 100644 index 0000000000..35117e7dd8 --- /dev/null +++ b/esphome/components/light/esp_color_view.h @@ -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 diff --git a/esphome/components/light/esp_hsv_color.cpp b/esphome/components/light/esp_hsv_color.cpp new file mode 100644 index 0000000000..450c2e11ce --- /dev/null +++ b/esphome/components/light/esp_hsv_color.cpp @@ -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 diff --git a/esphome/components/light/esp_hsv_color.h b/esphome/components/light/esp_hsv_color.h new file mode 100644 index 0000000000..e0aa388875 --- /dev/null +++ b/esphome/components/light/esp_hsv_color.h @@ -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 diff --git a/esphome/components/light/esp_range_view.cpp b/esphome/components/light/esp_range_view.cpp new file mode 100644 index 0000000000..e1f0a507bd --- /dev/null +++ b/esphome/components/light/esp_range_view.cpp @@ -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 diff --git a/esphome/components/light/esp_range_view.h b/esphome/components/light/esp_range_view.h new file mode 100644 index 0000000000..f2cc347176 --- /dev/null +++ b/esphome/components/light/esp_range_view.h @@ -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 diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp new file mode 100644 index 0000000000..359da51de9 --- /dev/null +++ b/esphome/components/light/light_call.cpp @@ -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(1.0f); + this->green_ = optional(1.0f); + this->blue_ = optional(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(1.0f); + } else { + this->white_ = optional(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(1.0f); + } else { + this->white_ = optional(0.0f); + } + } + } + // If only a color temperature is specified, change to white light + else if (this->color_temperature_.has_value()) { + this->red_ = optional(1.0f); + this->green_ = optional(1.0f); + this->blue_ = optional(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(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 state) { + this->state_ = state; + return *this; +} +LightCall &LightCall::set_state(bool state) { + this->state_ = state; + return *this; +} +LightCall &LightCall::set_transition_length(optional 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 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 brightness) { + this->brightness_ = brightness; + return *this; +} +LightCall &LightCall::set_brightness(float brightness) { + this->brightness_ = brightness; + return *this; +} +LightCall &LightCall::set_red(optional red) { + this->red_ = red; + return *this; +} +LightCall &LightCall::set_red(float red) { + this->red_ = red; + return *this; +} +LightCall &LightCall::set_green(optional green) { + this->green_ = green; + return *this; +} +LightCall &LightCall::set_green(float green) { + this->green_ = green; + return *this; +} +LightCall &LightCall::set_blue(optional blue) { + this->blue_ = blue; + return *this; +} +LightCall &LightCall::set_blue(float blue) { + this->blue_ = blue; + return *this; +} +LightCall &LightCall::set_white(optional white) { + this->white_ = white; + return *this; +} +LightCall &LightCall::set_white(float white) { + this->white_ = white; + return *this; +} +LightCall &LightCall::set_color_temperature(optional 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 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 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 diff --git a/esphome/components/light/light_call.h b/esphome/components/light/light_call.h new file mode 100644 index 0000000000..becea50004 --- /dev/null +++ b/esphome/components/light/light_call.h @@ -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 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 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 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 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 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 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 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 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 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 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 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 state_; + optional transition_length_; + optional flash_length_; + optional brightness_; + optional red_; + optional green_; + optional blue_; + optional white_; + optional color_temperature_; + optional effect_; + bool publish_{true}; + bool save_{true}; +}; + +} // namespace light +} // namespace esphome diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 177dbb8d4e..69d147b6f3 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -7,80 +7,13 @@ namespace light { static const char *const TAG = "light"; -void LightState::start_transition_(const LightColorValues &target, uint32_t length) { - this->transformer_ = make_unique(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(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) {} -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; -} - -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 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 +LightTraits LightState::get_traits() { return this->output_->get_traits(); } +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); } struct LightStateRTCState { bool state{false}; @@ -144,6 +77,17 @@ void LightState::setup() { } 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() { // Apply effect (if any) auto *effect = this->get_active_effect_(); @@ -171,7 +115,47 @@ void LightState::loop() { 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 &&send_callback) { + this->remote_values_callback_.add(std::move(send_callback)); +} +void LightState::add_new_target_state_reached_callback(std::function &&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 &LightState::get_effects() const { return this->effects_; } void LightState::add_effects(const std::vector &effects) { this->effects_.reserve(this->effects_.size() + effects.size()); @@ -179,551 +163,7 @@ void LightState::add_effects(const std::vector &effects) { 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(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(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(1.0f); - } else { - this->white_ = optional(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(1.0f); - } else { - this->white_ = optional(0.0f); - } - } - } - // If only a color temperature is specified, change to white light - else if (this->color_temperature_.has_value()) { - this->red_ = optional(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(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(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 state) { - this->state_ = state; - return *this; -} -LightCall &LightCall::set_state(bool state) { - this->state_ = state; - return *this; -} -LightCall &LightCall::set_transition_length(optional 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 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 brightness) { - this->brightness_ = brightness; - return *this; -} -LightCall &LightCall::set_brightness(float brightness) { - this->brightness_ = brightness; - return *this; -} -LightCall &LightCall::set_red(optional red) { - this->red_ = red; - return *this; -} -LightCall &LightCall::set_red(float red) { - this->red_ = red; - return *this; -} -LightCall &LightCall::set_green(optional green) { - this->green_ = green; - return *this; -} -LightCall &LightCall::set_green(float green) { - this->green_ = green; - return *this; -} -LightCall &LightCall::set_blue(optional blue) { - this->blue_ = blue; - return *this; -} -LightCall &LightCall::set_blue(float blue) { - this->blue_ = blue; - return *this; -} -LightCall &LightCall::set_white(optional white) { - this->white_ = white; - return *this; -} -LightCall &LightCall::set_white(float white) { - this->white_ = white; - return *this; -} -LightCall &LightCall::set_color_temperature(optional 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 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 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_brightness(float *brightness) { 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->gamma_correct_, constant_brightness); } -void LightState::add_new_remote_values_callback(std::function &&send_callback) { - this->remote_values_callback_.add(std::move(send_callback)); -} -void LightState::add_new_target_state_reached_callback(std::function &&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_() { if (this->active_effect_index_ == 0) return nullptr; else 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(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(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 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 esphome diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index 10bda2d17b..5f3441daf7 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -5,161 +5,15 @@ #include "esphome/core/preferences.h" #include "light_effect.h" #include "light_color_values.h" +#include "light_call.h" #include "light_traits.h" #include "light_transformer.h" namespace esphome { namespace light { -class LightState; class LightOutput; -class LightCall { - public: - explicit LightCall(LightState *parent) : parent_(parent) {} - - /// Set the binary ON/OFF state of the light. - LightCall &set_state(optional 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 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 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 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 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 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 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 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 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 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 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 state_; - optional transition_length_; - optional flash_length_; - optional brightness_; - optional red_; - optional green_; - optional blue_; - optional white_; - optional color_temperature_; - optional effect_; - bool publish_{true}; - bool save_{true}; -}; - enum LightRestoreMode { LIGHT_RESTORE_DEFAULT_OFF, LIGHT_RESTORE_DEFAULT_ON, @@ -204,14 +58,6 @@ class LightState : public Nameable, public Component { */ 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. * * These are different from the "current" values: For example transitions will @@ -222,6 +68,14 @@ class LightState : public Nameable, public Component { */ 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. 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". std::string get_effect_name(); - /** This lets front-end components subscribe to light change events. - * - * This is different from add_new_current_values_callback in that it only sends events for start - * 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(). + /** + * This lets front-end components subscribe to light change events. This callback is called once + * when the remote color values are changed. * * @param send_callback The callback. */ void add_new_remote_values_callback(std::function &&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 */ void add_new_target_state_reached_callback(std::function &&send_callback); - /// Return whether the light has any effects that meet the trait requirements. - bool supports_effects(); - #ifdef USE_JSON /// Dump the state of this light as JSON. void dump_json(JsonObject &root); @@ -265,10 +112,17 @@ class LightState : public Nameable, public Component { /// Set the gamma correction factor void set_gamma_correct(float 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 &get_effects() const; + /// Add effects for this light state. void add_effects(const std::vector &effects); 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 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). void stop_effect_(); /// 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. void set_transformer_(std::unique_ptr 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. - ESPPreferenceObject rtc_; - /// Restore mode of the light. - LightRestoreMode restore_mode_; - /// Default transition length for all transitions in ms. - uint32_t default_transition_length_{}; + /// Store the output to allow effects to have more access. + LightOutput *output_; /// Value for storing the index of the currently active effect. 0 if no effect is active uint32_t active_effect_index_{}; /// The currently active transformer for this light (transition/flash). std::unique_ptr 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. * * "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 target_state_reached_callback_{}; - LightOutput *output_; ///< Store the output to allow effects to have more access. - /// Whether the light value should be written in the next cycle. - bool next_write_{true}; + /// Default transition length for all transitions in ms. + uint32_t default_transition_length_{}; /// Gamma correction factor for the light. float gamma_correct_{}; + /// Restore mode of the light. + LightRestoreMode restore_mode_; /// List of effects for this light. std::vector effects_; }; diff --git a/esphome/core/optional.h b/esphome/core/optional.h index 7ace7b122a..5b96781e63 100644 --- a/esphome/core/optional.h +++ b/esphome/core/optional.h @@ -16,6 +16,8 @@ // // Modified by Otto Winter on 18.05.18 +#include + namespace esphome { // type for nullopt