mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
Split files in light component (#1893)
This commit is contained in:
parent
9ad9d64ac7
commit
0efc1f06f2
14 changed files with 1334 additions and 1231 deletions
|
@ -6,158 +6,6 @@ namespace light {
|
||||||
|
|
||||||
static const char *const TAG = "light.addressable";
|
static const char *const TAG = "light.addressable";
|
||||||
|
|
||||||
Color ESPHSVColor::to_rgb() const {
|
|
||||||
// based on FastLED's hsv rainbow to rgb
|
|
||||||
const uint8_t hue = this->hue;
|
|
||||||
const uint8_t sat = this->saturation;
|
|
||||||
const uint8_t val = this->value;
|
|
||||||
// upper 3 hue bits are for branch selection, lower 5 are for values
|
|
||||||
const uint8_t offset8 = (hue & 0x1F) << 3; // 0..248
|
|
||||||
// third of the offset, 255/3 = 85 (actually only up to 82; 164)
|
|
||||||
const uint8_t third = esp_scale8(offset8, 85);
|
|
||||||
const uint8_t two_thirds = esp_scale8(offset8, 170);
|
|
||||||
Color rgb(255, 255, 255, 0);
|
|
||||||
switch (hue >> 5) {
|
|
||||||
case 0b000:
|
|
||||||
rgb.r = 255 - third;
|
|
||||||
rgb.g = third;
|
|
||||||
rgb.b = 0;
|
|
||||||
break;
|
|
||||||
case 0b001:
|
|
||||||
rgb.r = 171;
|
|
||||||
rgb.g = 85 + third;
|
|
||||||
rgb.b = 0;
|
|
||||||
break;
|
|
||||||
case 0b010:
|
|
||||||
rgb.r = 171 - two_thirds;
|
|
||||||
rgb.g = 170 + third;
|
|
||||||
rgb.b = 0;
|
|
||||||
break;
|
|
||||||
case 0b011:
|
|
||||||
rgb.r = 0;
|
|
||||||
rgb.g = 255 - third;
|
|
||||||
rgb.b = third;
|
|
||||||
break;
|
|
||||||
case 0b100:
|
|
||||||
rgb.r = 0;
|
|
||||||
rgb.g = 171 - two_thirds;
|
|
||||||
rgb.b = 85 + two_thirds;
|
|
||||||
break;
|
|
||||||
case 0b101:
|
|
||||||
rgb.r = third;
|
|
||||||
rgb.g = 0;
|
|
||||||
rgb.b = 255 - third;
|
|
||||||
break;
|
|
||||||
case 0b110:
|
|
||||||
rgb.r = 85 + third;
|
|
||||||
rgb.g = 0;
|
|
||||||
rgb.b = 171 - third;
|
|
||||||
break;
|
|
||||||
case 0b111:
|
|
||||||
rgb.r = 170 + third;
|
|
||||||
rgb.g = 0;
|
|
||||||
rgb.b = 85 - third;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// low saturation -> add uniform color to orig. hue
|
|
||||||
// high saturation -> use hue directly
|
|
||||||
// scales with square of saturation
|
|
||||||
// (r,g,b) = (r,g,b) * sat + (1 - sat)^2
|
|
||||||
rgb *= sat;
|
|
||||||
const uint8_t desat = 255 - sat;
|
|
||||||
rgb += esp_scale8(desat, desat);
|
|
||||||
// (r,g,b) = (r,g,b) * val
|
|
||||||
rgb *= val;
|
|
||||||
return rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESPRangeView::set(const Color &color) {
|
|
||||||
for (int32_t i = this->begin_; i < this->end_; i++) {
|
|
||||||
(*this->parent_)[i] = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ESPColorView ESPRangeView::operator[](int32_t index) const {
|
|
||||||
index = interpret_index(index, this->size()) + this->begin_;
|
|
||||||
return (*this->parent_)[index];
|
|
||||||
}
|
|
||||||
ESPRangeIterator ESPRangeView::begin() { return {*this, this->begin_}; }
|
|
||||||
ESPRangeIterator ESPRangeView::end() { return {*this, this->end_}; }
|
|
||||||
void ESPRangeView::set_red(uint8_t red) {
|
|
||||||
for (auto c : *this)
|
|
||||||
c.set_red(red);
|
|
||||||
}
|
|
||||||
void ESPRangeView::set_green(uint8_t green) {
|
|
||||||
for (auto c : *this)
|
|
||||||
c.set_green(green);
|
|
||||||
}
|
|
||||||
void ESPRangeView::set_blue(uint8_t blue) {
|
|
||||||
for (auto c : *this)
|
|
||||||
c.set_blue(blue);
|
|
||||||
}
|
|
||||||
void ESPRangeView::set_white(uint8_t white) {
|
|
||||||
for (auto c : *this)
|
|
||||||
c.set_white(white);
|
|
||||||
}
|
|
||||||
void ESPRangeView::set_effect_data(uint8_t effect_data) {
|
|
||||||
for (auto c : *this)
|
|
||||||
c.set_effect_data(effect_data);
|
|
||||||
}
|
|
||||||
void ESPRangeView::fade_to_white(uint8_t amnt) {
|
|
||||||
for (auto c : *this)
|
|
||||||
c.fade_to_white(amnt);
|
|
||||||
}
|
|
||||||
void ESPRangeView::fade_to_black(uint8_t amnt) {
|
|
||||||
for (auto c : *this)
|
|
||||||
c.fade_to_black(amnt);
|
|
||||||
}
|
|
||||||
void ESPRangeView::lighten(uint8_t delta) {
|
|
||||||
for (auto c : *this)
|
|
||||||
c.lighten(delta);
|
|
||||||
}
|
|
||||||
void ESPRangeView::darken(uint8_t delta) {
|
|
||||||
for (auto c : *this)
|
|
||||||
c.darken(delta);
|
|
||||||
}
|
|
||||||
ESPRangeView &ESPRangeView::operator=(const ESPRangeView &rhs) { // NOLINT
|
|
||||||
// If size doesn't match, error (todo warning)
|
|
||||||
if (rhs.size() != this->size())
|
|
||||||
return *this;
|
|
||||||
|
|
||||||
if (this->parent_ != rhs.parent_) {
|
|
||||||
for (int32_t i = 0; i < this->size(); i++)
|
|
||||||
(*this)[i].set(rhs[i].get());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both equal, already done
|
|
||||||
if (rhs.begin_ == this->begin_)
|
|
||||||
return *this;
|
|
||||||
|
|
||||||
if (rhs.begin_ > this->begin_) {
|
|
||||||
// Copy from left
|
|
||||||
for (int32_t i = 0; i < this->size(); i++) {
|
|
||||||
(*this)[i].set(rhs[i].get());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Copy from right
|
|
||||||
for (int32_t i = this->size() - 1; i >= 0; i--) {
|
|
||||||
(*this)[i].set(rhs[i].get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESPColorView ESPRangeIterator::operator*() const { return this->range_.parent_->get(this->i_); }
|
|
||||||
|
|
||||||
int32_t HOT interpret_index(int32_t index, int32_t size) {
|
|
||||||
if (index < 0)
|
|
||||||
return size + index;
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddressableLight::call_setup() {
|
void AddressableLight::call_setup() {
|
||||||
this->setup();
|
this->setup();
|
||||||
|
|
||||||
|
@ -254,23 +102,5 @@ void AddressableLight::write_state(LightState *state) {
|
||||||
this->schedule_show();
|
this->schedule_show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESPColorCorrection::calculate_gamma_table(float gamma) {
|
|
||||||
for (uint16_t i = 0; i < 256; i++) {
|
|
||||||
// corrected = val ^ gamma
|
|
||||||
auto corrected = static_cast<uint8_t>(roundf(255.0f * gamma_correct(i / 255.0f, gamma)));
|
|
||||||
this->gamma_table_[i] = corrected;
|
|
||||||
}
|
|
||||||
if (gamma == 0.0f) {
|
|
||||||
for (uint16_t i = 0; i < 256; i++)
|
|
||||||
this->gamma_reverse_table_[i] = i;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (uint16_t i = 0; i < 256; i++) {
|
|
||||||
// val = corrected ^ (1/gamma)
|
|
||||||
auto uncorrected = static_cast<uint8_t>(roundf(255.0f * powf(i / 255.0f, 1.0f / gamma)));
|
|
||||||
this->gamma_reverse_table_[i] = uncorrected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/color.h"
|
#include "esphome/core/color.h"
|
||||||
|
#include "esp_color_correction.h"
|
||||||
|
#include "esp_color_view.h"
|
||||||
|
#include "esp_range_view.h"
|
||||||
#include "light_output.h"
|
#include "light_output.h"
|
||||||
#include "light_state.h"
|
#include "light_state.h"
|
||||||
|
|
||||||
|
@ -15,266 +18,6 @@ namespace light {
|
||||||
|
|
||||||
using ESPColor = Color;
|
using ESPColor = Color;
|
||||||
|
|
||||||
struct ESPHSVColor {
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
union {
|
|
||||||
uint8_t hue;
|
|
||||||
uint8_t h;
|
|
||||||
};
|
|
||||||
union {
|
|
||||||
uint8_t saturation;
|
|
||||||
uint8_t s;
|
|
||||||
};
|
|
||||||
union {
|
|
||||||
uint8_t value;
|
|
||||||
uint8_t v;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
uint8_t raw[3];
|
|
||||||
};
|
|
||||||
inline ESPHSVColor() ALWAYS_INLINE : h(0), s(0), v(0) { // NOLINT
|
|
||||||
}
|
|
||||||
inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue),
|
|
||||||
saturation(saturation),
|
|
||||||
value(value) {}
|
|
||||||
Color to_rgb() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ESPColorCorrection {
|
|
||||||
public:
|
|
||||||
ESPColorCorrection() : max_brightness_(255, 255, 255, 255) {}
|
|
||||||
void set_max_brightness(const Color &max_brightness) { this->max_brightness_ = max_brightness; }
|
|
||||||
void set_local_brightness(uint8_t local_brightness) { this->local_brightness_ = local_brightness; }
|
|
||||||
void calculate_gamma_table(float gamma);
|
|
||||||
inline Color color_correct(Color color) const ALWAYS_INLINE {
|
|
||||||
// corrected = (uncorrected * max_brightness * local_brightness) ^ gamma
|
|
||||||
return Color(this->color_correct_red(color.red), this->color_correct_green(color.green),
|
|
||||||
this->color_correct_blue(color.blue), this->color_correct_white(color.white));
|
|
||||||
}
|
|
||||||
inline uint8_t color_correct_red(uint8_t red) const ALWAYS_INLINE {
|
|
||||||
uint8_t res = esp_scale8(esp_scale8(red, this->max_brightness_.red), this->local_brightness_);
|
|
||||||
return this->gamma_table_[res];
|
|
||||||
}
|
|
||||||
inline uint8_t color_correct_green(uint8_t green) const ALWAYS_INLINE {
|
|
||||||
uint8_t res = esp_scale8(esp_scale8(green, this->max_brightness_.green), this->local_brightness_);
|
|
||||||
return this->gamma_table_[res];
|
|
||||||
}
|
|
||||||
inline uint8_t color_correct_blue(uint8_t blue) const ALWAYS_INLINE {
|
|
||||||
uint8_t res = esp_scale8(esp_scale8(blue, this->max_brightness_.blue), this->local_brightness_);
|
|
||||||
return this->gamma_table_[res];
|
|
||||||
}
|
|
||||||
inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE {
|
|
||||||
// do not scale white value with brightness
|
|
||||||
uint8_t res = esp_scale8(white, this->max_brightness_.white);
|
|
||||||
return this->gamma_table_[res];
|
|
||||||
}
|
|
||||||
inline Color color_uncorrect(Color color) const ALWAYS_INLINE {
|
|
||||||
// uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness)
|
|
||||||
return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green),
|
|
||||||
this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white));
|
|
||||||
}
|
|
||||||
inline uint8_t color_uncorrect_red(uint8_t red) const ALWAYS_INLINE {
|
|
||||||
if (this->max_brightness_.red == 0 || this->local_brightness_ == 0)
|
|
||||||
return 0;
|
|
||||||
uint16_t uncorrected = this->gamma_reverse_table_[red] * 255UL;
|
|
||||||
uint8_t res = ((uncorrected / this->max_brightness_.red) * 255UL) / this->local_brightness_;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
inline uint8_t color_uncorrect_green(uint8_t green) const ALWAYS_INLINE {
|
|
||||||
if (this->max_brightness_.green == 0 || this->local_brightness_ == 0)
|
|
||||||
return 0;
|
|
||||||
uint16_t uncorrected = this->gamma_reverse_table_[green] * 255UL;
|
|
||||||
uint8_t res = ((uncorrected / this->max_brightness_.green) * 255UL) / this->local_brightness_;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
inline uint8_t color_uncorrect_blue(uint8_t blue) const ALWAYS_INLINE {
|
|
||||||
if (this->max_brightness_.blue == 0 || this->local_brightness_ == 0)
|
|
||||||
return 0;
|
|
||||||
uint16_t uncorrected = this->gamma_reverse_table_[blue] * 255UL;
|
|
||||||
uint8_t res = ((uncorrected / this->max_brightness_.blue) * 255UL) / this->local_brightness_;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE {
|
|
||||||
if (this->max_brightness_.white == 0)
|
|
||||||
return 0;
|
|
||||||
uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL;
|
|
||||||
uint8_t res = uncorrected / this->max_brightness_.white;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint8_t gamma_table_[256];
|
|
||||||
uint8_t gamma_reverse_table_[256];
|
|
||||||
Color max_brightness_;
|
|
||||||
uint8_t local_brightness_{255};
|
|
||||||
};
|
|
||||||
|
|
||||||
class ESPColorSettable {
|
|
||||||
public:
|
|
||||||
virtual void set(const Color &color) = 0;
|
|
||||||
virtual void set_red(uint8_t red) = 0;
|
|
||||||
virtual void set_green(uint8_t green) = 0;
|
|
||||||
virtual void set_blue(uint8_t blue) = 0;
|
|
||||||
virtual void set_white(uint8_t white) = 0;
|
|
||||||
virtual void set_effect_data(uint8_t effect_data) = 0;
|
|
||||||
virtual void fade_to_white(uint8_t amnt) = 0;
|
|
||||||
virtual void fade_to_black(uint8_t amnt) = 0;
|
|
||||||
virtual void lighten(uint8_t delta) = 0;
|
|
||||||
virtual void darken(uint8_t delta) = 0;
|
|
||||||
void set(const ESPHSVColor &color) { this->set_hsv(color); }
|
|
||||||
void set_hsv(const ESPHSVColor &color) {
|
|
||||||
Color rgb = color.to_rgb();
|
|
||||||
this->set_rgb(rgb.r, rgb.g, rgb.b);
|
|
||||||
}
|
|
||||||
void set_rgb(uint8_t red, uint8_t green, uint8_t blue) {
|
|
||||||
this->set_red(red);
|
|
||||||
this->set_green(green);
|
|
||||||
this->set_blue(blue);
|
|
||||||
}
|
|
||||||
void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) {
|
|
||||||
this->set_rgb(red, green, blue);
|
|
||||||
this->set_white(white);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ESPColorView : public ESPColorSettable {
|
|
||||||
public:
|
|
||||||
ESPColorView(uint8_t *red, uint8_t *green, uint8_t *blue, uint8_t *white, uint8_t *effect_data,
|
|
||||||
const ESPColorCorrection *color_correction)
|
|
||||||
: red_(red),
|
|
||||||
green_(green),
|
|
||||||
blue_(blue),
|
|
||||||
white_(white),
|
|
||||||
effect_data_(effect_data),
|
|
||||||
color_correction_(color_correction) {}
|
|
||||||
ESPColorView &operator=(const Color &rhs) {
|
|
||||||
this->set(rhs);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
ESPColorView &operator=(const ESPHSVColor &rhs) {
|
|
||||||
this->set_hsv(rhs);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
void set(const Color &color) override { this->set_rgbw(color.r, color.g, color.b, color.w); }
|
|
||||||
void set_red(uint8_t red) override { *this->red_ = this->color_correction_->color_correct_red(red); }
|
|
||||||
void set_green(uint8_t green) override { *this->green_ = this->color_correction_->color_correct_green(green); }
|
|
||||||
void set_blue(uint8_t blue) override { *this->blue_ = this->color_correction_->color_correct_blue(blue); }
|
|
||||||
void set_white(uint8_t white) override {
|
|
||||||
if (this->white_ == nullptr)
|
|
||||||
return;
|
|
||||||
*this->white_ = this->color_correction_->color_correct_white(white);
|
|
||||||
}
|
|
||||||
void set_effect_data(uint8_t effect_data) override {
|
|
||||||
if (this->effect_data_ == nullptr)
|
|
||||||
return;
|
|
||||||
*this->effect_data_ = effect_data;
|
|
||||||
}
|
|
||||||
void fade_to_white(uint8_t amnt) override { this->set(this->get().fade_to_white(amnt)); }
|
|
||||||
void fade_to_black(uint8_t amnt) override { this->set(this->get().fade_to_black(amnt)); }
|
|
||||||
void lighten(uint8_t delta) override { this->set(this->get().lighten(delta)); }
|
|
||||||
void darken(uint8_t delta) override { this->set(this->get().darken(delta)); }
|
|
||||||
Color get() const { return Color(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); }
|
|
||||||
uint8_t get_red() const { return this->color_correction_->color_uncorrect_red(*this->red_); }
|
|
||||||
uint8_t get_red_raw() const { return *this->red_; }
|
|
||||||
uint8_t get_green() const { return this->color_correction_->color_uncorrect_green(*this->green_); }
|
|
||||||
uint8_t get_green_raw() const { return *this->green_; }
|
|
||||||
uint8_t get_blue() const { return this->color_correction_->color_uncorrect_blue(*this->blue_); }
|
|
||||||
uint8_t get_blue_raw() const { return *this->blue_; }
|
|
||||||
uint8_t get_white() const {
|
|
||||||
if (this->white_ == nullptr)
|
|
||||||
return 0;
|
|
||||||
return this->color_correction_->color_uncorrect_white(*this->white_);
|
|
||||||
}
|
|
||||||
uint8_t get_white_raw() const {
|
|
||||||
if (this->white_ == nullptr)
|
|
||||||
return 0;
|
|
||||||
return *this->white_;
|
|
||||||
}
|
|
||||||
uint8_t get_effect_data() const {
|
|
||||||
if (this->effect_data_ == nullptr)
|
|
||||||
return 0;
|
|
||||||
return *this->effect_data_;
|
|
||||||
}
|
|
||||||
void raw_set_color_correction(const ESPColorCorrection *color_correction) {
|
|
||||||
this->color_correction_ = color_correction;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint8_t *const red_;
|
|
||||||
uint8_t *const green_;
|
|
||||||
uint8_t *const blue_;
|
|
||||||
uint8_t *const white_;
|
|
||||||
uint8_t *const effect_data_;
|
|
||||||
const ESPColorCorrection *color_correction_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AddressableLight;
|
|
||||||
|
|
||||||
int32_t interpret_index(int32_t index, int32_t size);
|
|
||||||
|
|
||||||
class ESPRangeIterator;
|
|
||||||
|
|
||||||
class ESPRangeView : public ESPColorSettable {
|
|
||||||
public:
|
|
||||||
ESPRangeView(AddressableLight *parent, int32_t begin, int32_t an_end) : parent_(parent), begin_(begin), end_(an_end) {
|
|
||||||
if (this->end_ < this->begin_) {
|
|
||||||
this->end_ = this->begin_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ESPColorView operator[](int32_t index) const;
|
|
||||||
ESPRangeIterator begin();
|
|
||||||
ESPRangeIterator end();
|
|
||||||
|
|
||||||
void set(const Color &color) override;
|
|
||||||
ESPRangeView &operator=(const Color &rhs) {
|
|
||||||
this->set(rhs);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
ESPRangeView &operator=(const ESPColorView &rhs) {
|
|
||||||
this->set(rhs.get());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
ESPRangeView &operator=(const ESPHSVColor &rhs) {
|
|
||||||
this->set_hsv(rhs);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
ESPRangeView &operator=(const ESPRangeView &rhs);
|
|
||||||
void set_red(uint8_t red) override;
|
|
||||||
void set_green(uint8_t green) override;
|
|
||||||
void set_blue(uint8_t blue) override;
|
|
||||||
void set_white(uint8_t white) override;
|
|
||||||
void set_effect_data(uint8_t effect_data) override;
|
|
||||||
void fade_to_white(uint8_t amnt) override;
|
|
||||||
void fade_to_black(uint8_t amnt) override;
|
|
||||||
void lighten(uint8_t delta) override;
|
|
||||||
void darken(uint8_t delta) override;
|
|
||||||
int32_t size() const { return this->end_ - this->begin_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
friend ESPRangeIterator;
|
|
||||||
|
|
||||||
AddressableLight *parent_;
|
|
||||||
int32_t begin_;
|
|
||||||
int32_t end_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ESPRangeIterator {
|
|
||||||
public:
|
|
||||||
ESPRangeIterator(const ESPRangeView &range, int32_t i) : range_(range), i_(i) {}
|
|
||||||
ESPRangeIterator operator++() {
|
|
||||||
this->i_++;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
bool operator!=(const ESPRangeIterator &other) const { return this->i_ != other.i_; }
|
|
||||||
ESPColorView operator*() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
ESPRangeView range_;
|
|
||||||
int32_t i_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AddressableLight : public LightOutput, public Component {
|
class AddressableLight : public LightOutput, public Component {
|
||||||
public:
|
public:
|
||||||
virtual int32_t size() const = 0;
|
virtual int32_t size() const = 0;
|
||||||
|
|
26
esphome/components/light/esp_color_correction.cpp
Normal file
26
esphome/components/light/esp_color_correction.cpp
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#include "esp_color_correction.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
void ESPColorCorrection::calculate_gamma_table(float gamma) {
|
||||||
|
for (uint16_t i = 0; i < 256; i++) {
|
||||||
|
// corrected = val ^ gamma
|
||||||
|
auto corrected = static_cast<uint8_t>(roundf(255.0f * gamma_correct(i / 255.0f, gamma)));
|
||||||
|
this->gamma_table_[i] = corrected;
|
||||||
|
}
|
||||||
|
if (gamma == 0.0f) {
|
||||||
|
for (uint16_t i = 0; i < 256; i++)
|
||||||
|
this->gamma_reverse_table_[i] = i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (uint16_t i = 0; i < 256; i++) {
|
||||||
|
// val = corrected ^ (1/gamma)
|
||||||
|
auto uncorrected = static_cast<uint8_t>(roundf(255.0f * powf(i / 255.0f, 1.0f / gamma)));
|
||||||
|
this->gamma_reverse_table_[i] = uncorrected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
78
esphome/components/light/esp_color_correction.h
Normal file
78
esphome/components/light/esp_color_correction.h
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/color.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
class ESPColorCorrection {
|
||||||
|
public:
|
||||||
|
ESPColorCorrection() : max_brightness_(255, 255, 255, 255) {}
|
||||||
|
void set_max_brightness(const Color &max_brightness) { this->max_brightness_ = max_brightness; }
|
||||||
|
void set_local_brightness(uint8_t local_brightness) { this->local_brightness_ = local_brightness; }
|
||||||
|
void calculate_gamma_table(float gamma);
|
||||||
|
inline Color color_correct(Color color) const ALWAYS_INLINE {
|
||||||
|
// corrected = (uncorrected * max_brightness * local_brightness) ^ gamma
|
||||||
|
return Color(this->color_correct_red(color.red), this->color_correct_green(color.green),
|
||||||
|
this->color_correct_blue(color.blue), this->color_correct_white(color.white));
|
||||||
|
}
|
||||||
|
inline uint8_t color_correct_red(uint8_t red) const ALWAYS_INLINE {
|
||||||
|
uint8_t res = esp_scale8(esp_scale8(red, this->max_brightness_.red), this->local_brightness_);
|
||||||
|
return this->gamma_table_[res];
|
||||||
|
}
|
||||||
|
inline uint8_t color_correct_green(uint8_t green) const ALWAYS_INLINE {
|
||||||
|
uint8_t res = esp_scale8(esp_scale8(green, this->max_brightness_.green), this->local_brightness_);
|
||||||
|
return this->gamma_table_[res];
|
||||||
|
}
|
||||||
|
inline uint8_t color_correct_blue(uint8_t blue) const ALWAYS_INLINE {
|
||||||
|
uint8_t res = esp_scale8(esp_scale8(blue, this->max_brightness_.blue), this->local_brightness_);
|
||||||
|
return this->gamma_table_[res];
|
||||||
|
}
|
||||||
|
inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE {
|
||||||
|
// do not scale white value with brightness
|
||||||
|
uint8_t res = esp_scale8(white, this->max_brightness_.white);
|
||||||
|
return this->gamma_table_[res];
|
||||||
|
}
|
||||||
|
inline Color color_uncorrect(Color color) const ALWAYS_INLINE {
|
||||||
|
// uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness)
|
||||||
|
return Color(this->color_uncorrect_red(color.red), this->color_uncorrect_green(color.green),
|
||||||
|
this->color_uncorrect_blue(color.blue), this->color_uncorrect_white(color.white));
|
||||||
|
}
|
||||||
|
inline uint8_t color_uncorrect_red(uint8_t red) const ALWAYS_INLINE {
|
||||||
|
if (this->max_brightness_.red == 0 || this->local_brightness_ == 0)
|
||||||
|
return 0;
|
||||||
|
uint16_t uncorrected = this->gamma_reverse_table_[red] * 255UL;
|
||||||
|
uint8_t res = ((uncorrected / this->max_brightness_.red) * 255UL) / this->local_brightness_;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
inline uint8_t color_uncorrect_green(uint8_t green) const ALWAYS_INLINE {
|
||||||
|
if (this->max_brightness_.green == 0 || this->local_brightness_ == 0)
|
||||||
|
return 0;
|
||||||
|
uint16_t uncorrected = this->gamma_reverse_table_[green] * 255UL;
|
||||||
|
uint8_t res = ((uncorrected / this->max_brightness_.green) * 255UL) / this->local_brightness_;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
inline uint8_t color_uncorrect_blue(uint8_t blue) const ALWAYS_INLINE {
|
||||||
|
if (this->max_brightness_.blue == 0 || this->local_brightness_ == 0)
|
||||||
|
return 0;
|
||||||
|
uint16_t uncorrected = this->gamma_reverse_table_[blue] * 255UL;
|
||||||
|
uint8_t res = ((uncorrected / this->max_brightness_.blue) * 255UL) / this->local_brightness_;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE {
|
||||||
|
if (this->max_brightness_.white == 0)
|
||||||
|
return 0;
|
||||||
|
uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL;
|
||||||
|
uint8_t res = uncorrected / this->max_brightness_.white;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t gamma_table_[256];
|
||||||
|
uint8_t gamma_reverse_table_[256];
|
||||||
|
Color max_brightness_;
|
||||||
|
uint8_t local_brightness_{255};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
110
esphome/components/light/esp_color_view.h
Normal file
110
esphome/components/light/esp_color_view.h
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/color.h"
|
||||||
|
#include "esp_hsv_color.h"
|
||||||
|
#include "esp_color_correction.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
class ESPColorSettable {
|
||||||
|
public:
|
||||||
|
virtual void set(const Color &color) = 0;
|
||||||
|
virtual void set_red(uint8_t red) = 0;
|
||||||
|
virtual void set_green(uint8_t green) = 0;
|
||||||
|
virtual void set_blue(uint8_t blue) = 0;
|
||||||
|
virtual void set_white(uint8_t white) = 0;
|
||||||
|
virtual void set_effect_data(uint8_t effect_data) = 0;
|
||||||
|
virtual void fade_to_white(uint8_t amnt) = 0;
|
||||||
|
virtual void fade_to_black(uint8_t amnt) = 0;
|
||||||
|
virtual void lighten(uint8_t delta) = 0;
|
||||||
|
virtual void darken(uint8_t delta) = 0;
|
||||||
|
void set(const ESPHSVColor &color) { this->set_hsv(color); }
|
||||||
|
void set_hsv(const ESPHSVColor &color) {
|
||||||
|
Color rgb = color.to_rgb();
|
||||||
|
this->set_rgb(rgb.r, rgb.g, rgb.b);
|
||||||
|
}
|
||||||
|
void set_rgb(uint8_t red, uint8_t green, uint8_t blue) {
|
||||||
|
this->set_red(red);
|
||||||
|
this->set_green(green);
|
||||||
|
this->set_blue(blue);
|
||||||
|
}
|
||||||
|
void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) {
|
||||||
|
this->set_rgb(red, green, blue);
|
||||||
|
this->set_white(white);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ESPColorView : public ESPColorSettable {
|
||||||
|
public:
|
||||||
|
ESPColorView(uint8_t *red, uint8_t *green, uint8_t *blue, uint8_t *white, uint8_t *effect_data,
|
||||||
|
const ESPColorCorrection *color_correction)
|
||||||
|
: red_(red),
|
||||||
|
green_(green),
|
||||||
|
blue_(blue),
|
||||||
|
white_(white),
|
||||||
|
effect_data_(effect_data),
|
||||||
|
color_correction_(color_correction) {}
|
||||||
|
ESPColorView &operator=(const Color &rhs) {
|
||||||
|
this->set(rhs);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ESPColorView &operator=(const ESPHSVColor &rhs) {
|
||||||
|
this->set_hsv(rhs);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
void set(const Color &color) override { this->set_rgbw(color.r, color.g, color.b, color.w); }
|
||||||
|
void set_red(uint8_t red) override { *this->red_ = this->color_correction_->color_correct_red(red); }
|
||||||
|
void set_green(uint8_t green) override { *this->green_ = this->color_correction_->color_correct_green(green); }
|
||||||
|
void set_blue(uint8_t blue) override { *this->blue_ = this->color_correction_->color_correct_blue(blue); }
|
||||||
|
void set_white(uint8_t white) override {
|
||||||
|
if (this->white_ == nullptr)
|
||||||
|
return;
|
||||||
|
*this->white_ = this->color_correction_->color_correct_white(white);
|
||||||
|
}
|
||||||
|
void set_effect_data(uint8_t effect_data) override {
|
||||||
|
if (this->effect_data_ == nullptr)
|
||||||
|
return;
|
||||||
|
*this->effect_data_ = effect_data;
|
||||||
|
}
|
||||||
|
void fade_to_white(uint8_t amnt) override { this->set(this->get().fade_to_white(amnt)); }
|
||||||
|
void fade_to_black(uint8_t amnt) override { this->set(this->get().fade_to_black(amnt)); }
|
||||||
|
void lighten(uint8_t delta) override { this->set(this->get().lighten(delta)); }
|
||||||
|
void darken(uint8_t delta) override { this->set(this->get().darken(delta)); }
|
||||||
|
Color get() const { return Color(this->get_red(), this->get_green(), this->get_blue(), this->get_white()); }
|
||||||
|
uint8_t get_red() const { return this->color_correction_->color_uncorrect_red(*this->red_); }
|
||||||
|
uint8_t get_red_raw() const { return *this->red_; }
|
||||||
|
uint8_t get_green() const { return this->color_correction_->color_uncorrect_green(*this->green_); }
|
||||||
|
uint8_t get_green_raw() const { return *this->green_; }
|
||||||
|
uint8_t get_blue() const { return this->color_correction_->color_uncorrect_blue(*this->blue_); }
|
||||||
|
uint8_t get_blue_raw() const { return *this->blue_; }
|
||||||
|
uint8_t get_white() const {
|
||||||
|
if (this->white_ == nullptr)
|
||||||
|
return 0;
|
||||||
|
return this->color_correction_->color_uncorrect_white(*this->white_);
|
||||||
|
}
|
||||||
|
uint8_t get_white_raw() const {
|
||||||
|
if (this->white_ == nullptr)
|
||||||
|
return 0;
|
||||||
|
return *this->white_;
|
||||||
|
}
|
||||||
|
uint8_t get_effect_data() const {
|
||||||
|
if (this->effect_data_ == nullptr)
|
||||||
|
return 0;
|
||||||
|
return *this->effect_data_;
|
||||||
|
}
|
||||||
|
void raw_set_color_correction(const ESPColorCorrection *color_correction) {
|
||||||
|
this->color_correction_ = color_correction;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t *const red_;
|
||||||
|
uint8_t *const green_;
|
||||||
|
uint8_t *const blue_;
|
||||||
|
uint8_t *const white_;
|
||||||
|
uint8_t *const effect_data_;
|
||||||
|
const ESPColorCorrection *color_correction_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
74
esphome/components/light/esp_hsv_color.cpp
Normal file
74
esphome/components/light/esp_hsv_color.cpp
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
#include "esp_hsv_color.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
Color ESPHSVColor::to_rgb() const {
|
||||||
|
// based on FastLED's hsv rainbow to rgb
|
||||||
|
const uint8_t hue = this->hue;
|
||||||
|
const uint8_t sat = this->saturation;
|
||||||
|
const uint8_t val = this->value;
|
||||||
|
// upper 3 hue bits are for branch selection, lower 5 are for values
|
||||||
|
const uint8_t offset8 = (hue & 0x1F) << 3; // 0..248
|
||||||
|
// third of the offset, 255/3 = 85 (actually only up to 82; 164)
|
||||||
|
const uint8_t third = esp_scale8(offset8, 85);
|
||||||
|
const uint8_t two_thirds = esp_scale8(offset8, 170);
|
||||||
|
Color rgb(255, 255, 255, 0);
|
||||||
|
switch (hue >> 5) {
|
||||||
|
case 0b000:
|
||||||
|
rgb.r = 255 - third;
|
||||||
|
rgb.g = third;
|
||||||
|
rgb.b = 0;
|
||||||
|
break;
|
||||||
|
case 0b001:
|
||||||
|
rgb.r = 171;
|
||||||
|
rgb.g = 85 + third;
|
||||||
|
rgb.b = 0;
|
||||||
|
break;
|
||||||
|
case 0b010:
|
||||||
|
rgb.r = 171 - two_thirds;
|
||||||
|
rgb.g = 170 + third;
|
||||||
|
rgb.b = 0;
|
||||||
|
break;
|
||||||
|
case 0b011:
|
||||||
|
rgb.r = 0;
|
||||||
|
rgb.g = 255 - third;
|
||||||
|
rgb.b = third;
|
||||||
|
break;
|
||||||
|
case 0b100:
|
||||||
|
rgb.r = 0;
|
||||||
|
rgb.g = 171 - two_thirds;
|
||||||
|
rgb.b = 85 + two_thirds;
|
||||||
|
break;
|
||||||
|
case 0b101:
|
||||||
|
rgb.r = third;
|
||||||
|
rgb.g = 0;
|
||||||
|
rgb.b = 255 - third;
|
||||||
|
break;
|
||||||
|
case 0b110:
|
||||||
|
rgb.r = 85 + third;
|
||||||
|
rgb.g = 0;
|
||||||
|
rgb.b = 171 - third;
|
||||||
|
break;
|
||||||
|
case 0b111:
|
||||||
|
rgb.r = 170 + third;
|
||||||
|
rgb.g = 0;
|
||||||
|
rgb.b = 85 - third;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// low saturation -> add uniform color to orig. hue
|
||||||
|
// high saturation -> use hue directly
|
||||||
|
// scales with square of saturation
|
||||||
|
// (r,g,b) = (r,g,b) * sat + (1 - sat)^2
|
||||||
|
rgb *= sat;
|
||||||
|
const uint8_t desat = 255 - sat;
|
||||||
|
rgb += esp_scale8(desat, desat);
|
||||||
|
// (r,g,b) = (r,g,b) * val
|
||||||
|
rgb *= val;
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
36
esphome/components/light/esp_hsv_color.h
Normal file
36
esphome/components/light/esp_hsv_color.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/color.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
struct ESPHSVColor {
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
union {
|
||||||
|
uint8_t hue;
|
||||||
|
uint8_t h;
|
||||||
|
};
|
||||||
|
union {
|
||||||
|
uint8_t saturation;
|
||||||
|
uint8_t s;
|
||||||
|
};
|
||||||
|
union {
|
||||||
|
uint8_t value;
|
||||||
|
uint8_t v;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
uint8_t raw[3];
|
||||||
|
};
|
||||||
|
inline ESPHSVColor() ALWAYS_INLINE : h(0), s(0), v(0) { // NOLINT
|
||||||
|
}
|
||||||
|
inline ESPHSVColor(uint8_t hue, uint8_t saturation, uint8_t value) ALWAYS_INLINE : hue(hue),
|
||||||
|
saturation(saturation),
|
||||||
|
value(value) {}
|
||||||
|
Color to_rgb() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
96
esphome/components/light/esp_range_view.cpp
Normal file
96
esphome/components/light/esp_range_view.cpp
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
#include "esp_range_view.h"
|
||||||
|
#include "addressable_light.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
int32_t HOT interpret_index(int32_t index, int32_t size) {
|
||||||
|
if (index < 0)
|
||||||
|
return size + index;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPColorView ESPRangeView::operator[](int32_t index) const {
|
||||||
|
index = interpret_index(index, this->size()) + this->begin_;
|
||||||
|
return (*this->parent_)[index];
|
||||||
|
}
|
||||||
|
ESPRangeIterator ESPRangeView::begin() { return {*this, this->begin_}; }
|
||||||
|
ESPRangeIterator ESPRangeView::end() { return {*this, this->end_}; }
|
||||||
|
|
||||||
|
void ESPRangeView::set(const Color &color) {
|
||||||
|
for (int32_t i = this->begin_; i < this->end_; i++) {
|
||||||
|
(*this->parent_)[i] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPRangeView::set_red(uint8_t red) {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.set_red(red);
|
||||||
|
}
|
||||||
|
void ESPRangeView::set_green(uint8_t green) {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.set_green(green);
|
||||||
|
}
|
||||||
|
void ESPRangeView::set_blue(uint8_t blue) {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.set_blue(blue);
|
||||||
|
}
|
||||||
|
void ESPRangeView::set_white(uint8_t white) {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.set_white(white);
|
||||||
|
}
|
||||||
|
void ESPRangeView::set_effect_data(uint8_t effect_data) {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.set_effect_data(effect_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPRangeView::fade_to_white(uint8_t amnt) {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.fade_to_white(amnt);
|
||||||
|
}
|
||||||
|
void ESPRangeView::fade_to_black(uint8_t amnt) {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.fade_to_black(amnt);
|
||||||
|
}
|
||||||
|
void ESPRangeView::lighten(uint8_t delta) {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.lighten(delta);
|
||||||
|
}
|
||||||
|
void ESPRangeView::darken(uint8_t delta) {
|
||||||
|
for (auto c : *this)
|
||||||
|
c.darken(delta);
|
||||||
|
}
|
||||||
|
ESPRangeView &ESPRangeView::operator=(const ESPRangeView &rhs) { // NOLINT
|
||||||
|
// If size doesn't match, error (todo warning)
|
||||||
|
if (rhs.size() != this->size())
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
if (this->parent_ != rhs.parent_) {
|
||||||
|
for (int32_t i = 0; i < this->size(); i++)
|
||||||
|
(*this)[i].set(rhs[i].get());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both equal, already done
|
||||||
|
if (rhs.begin_ == this->begin_)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
if (rhs.begin_ > this->begin_) {
|
||||||
|
// Copy from left
|
||||||
|
for (int32_t i = 0; i < this->size(); i++) {
|
||||||
|
(*this)[i].set(rhs[i].get());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Copy from right
|
||||||
|
for (int32_t i = this->size() - 1; i >= 0; i--) {
|
||||||
|
(*this)[i].set(rhs[i].get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPColorView ESPRangeIterator::operator*() const { return this->range_.parent_->get(this->i_); }
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
75
esphome/components/light/esp_range_view.h
Normal file
75
esphome/components/light/esp_range_view.h
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esp_color_view.h"
|
||||||
|
#include "esp_hsv_color.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
int32_t interpret_index(int32_t index, int32_t size);
|
||||||
|
|
||||||
|
class AddressableLight;
|
||||||
|
class ESPRangeIterator;
|
||||||
|
|
||||||
|
class ESPRangeView : public ESPColorSettable {
|
||||||
|
public:
|
||||||
|
ESPRangeView(AddressableLight *parent, int32_t begin, int32_t end)
|
||||||
|
: parent_(parent), begin_(begin), end_(end < begin ? begin : end) {}
|
||||||
|
|
||||||
|
int32_t size() const { return this->end_ - this->begin_; }
|
||||||
|
ESPColorView operator[](int32_t index) const;
|
||||||
|
ESPRangeIterator begin();
|
||||||
|
ESPRangeIterator end();
|
||||||
|
|
||||||
|
void set(const Color &color) override;
|
||||||
|
void set(const ESPHSVColor &color) { this->set(color.to_rgb()); }
|
||||||
|
void set_red(uint8_t red) override;
|
||||||
|
void set_green(uint8_t green) override;
|
||||||
|
void set_blue(uint8_t blue) override;
|
||||||
|
void set_white(uint8_t white) override;
|
||||||
|
void set_effect_data(uint8_t effect_data) override;
|
||||||
|
|
||||||
|
void fade_to_white(uint8_t amnt) override;
|
||||||
|
void fade_to_black(uint8_t amnt) override;
|
||||||
|
void lighten(uint8_t delta) override;
|
||||||
|
void darken(uint8_t delta) override;
|
||||||
|
|
||||||
|
ESPRangeView &operator=(const Color &rhs) {
|
||||||
|
this->set(rhs);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ESPRangeView &operator=(const ESPColorView &rhs) {
|
||||||
|
this->set(rhs.get());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ESPRangeView &operator=(const ESPHSVColor &rhs) {
|
||||||
|
this->set_hsv(rhs);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ESPRangeView &operator=(const ESPRangeView &rhs);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend ESPRangeIterator;
|
||||||
|
|
||||||
|
AddressableLight *parent_;
|
||||||
|
int32_t begin_;
|
||||||
|
int32_t end_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ESPRangeIterator {
|
||||||
|
public:
|
||||||
|
ESPRangeIterator(const ESPRangeView &range, int32_t i) : range_(range), i_(i) {}
|
||||||
|
ESPRangeIterator operator++() {
|
||||||
|
this->i_++;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
bool operator!=(const ESPRangeIterator &other) const { return this->i_ != other.i_; }
|
||||||
|
ESPColorView operator*() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ESPRangeView range_;
|
||||||
|
int32_t i_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
522
esphome/components/light/light_call.cpp
Normal file
522
esphome/components/light/light_call.cpp
Normal file
|
@ -0,0 +1,522 @@
|
||||||
|
#include "light_call.h"
|
||||||
|
#include "light_state.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
static const char *TAG = "light";
|
||||||
|
|
||||||
|
#ifdef USE_JSON
|
||||||
|
LightCall &LightCall::parse_color_json(JsonObject &root) {
|
||||||
|
if (root.containsKey("state")) {
|
||||||
|
auto val = parse_on_off(root["state"]);
|
||||||
|
switch (val) {
|
||||||
|
case PARSE_ON:
|
||||||
|
this->set_state(true);
|
||||||
|
break;
|
||||||
|
case PARSE_OFF:
|
||||||
|
this->set_state(false);
|
||||||
|
break;
|
||||||
|
case PARSE_TOGGLE:
|
||||||
|
this->set_state(!this->parent_->remote_values.is_on());
|
||||||
|
break;
|
||||||
|
case PARSE_NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.containsKey("brightness")) {
|
||||||
|
this->set_brightness(float(root["brightness"]) / 255.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.containsKey("color")) {
|
||||||
|
JsonObject &color = root["color"];
|
||||||
|
if (color.containsKey("r")) {
|
||||||
|
this->set_red(float(color["r"]) / 255.0f);
|
||||||
|
}
|
||||||
|
if (color.containsKey("g")) {
|
||||||
|
this->set_green(float(color["g"]) / 255.0f);
|
||||||
|
}
|
||||||
|
if (color.containsKey("b")) {
|
||||||
|
this->set_blue(float(color["b"]) / 255.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.containsKey("white_value")) {
|
||||||
|
this->set_white(float(root["white_value"]) / 255.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.containsKey("color_temp")) {
|
||||||
|
this->set_color_temperature(float(root["color_temp"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::parse_json(JsonObject &root) {
|
||||||
|
this->parse_color_json(root);
|
||||||
|
|
||||||
|
if (root.containsKey("flash")) {
|
||||||
|
auto length = uint32_t(float(root["flash"]) * 1000);
|
||||||
|
this->set_flash_length(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.containsKey("transition")) {
|
||||||
|
auto length = uint32_t(float(root["transition"]) * 1000);
|
||||||
|
this->set_transition_length(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.containsKey("effect")) {
|
||||||
|
const char *effect = root["effect"];
|
||||||
|
this->set_effect(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void LightCall::perform() {
|
||||||
|
// use remote values for fallback
|
||||||
|
const char *name = this->parent_->get_name().c_str();
|
||||||
|
if (this->publish_) {
|
||||||
|
ESP_LOGD(TAG, "'%s' Setting:", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
LightColorValues v = this->validate_();
|
||||||
|
|
||||||
|
if (this->publish_) {
|
||||||
|
// Only print state when it's being changed
|
||||||
|
bool current_state = this->parent_->remote_values.is_on();
|
||||||
|
if (this->state_.value_or(current_state) != current_state) {
|
||||||
|
ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->brightness_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->color_temperature_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Color Temperature: %.1f mireds", v.get_color_temperature());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " Red=%.0f%%, Green=%.0f%%, Blue=%.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
|
||||||
|
v.get_blue() * 100.0f);
|
||||||
|
}
|
||||||
|
if (this->white_.has_value()) {
|
||||||
|
ESP_LOGD(TAG, " White Value: %.0f%%", v.get_white() * 100.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->has_flash_()) {
|
||||||
|
// FLASH
|
||||||
|
if (this->publish_) {
|
||||||
|
ESP_LOGD(TAG, " Flash Length: %.1fs", *this->flash_length_ / 1e3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->parent_->start_flash_(v, *this->flash_length_);
|
||||||
|
} else if (this->has_transition_()) {
|
||||||
|
// TRANSITION
|
||||||
|
if (this->publish_) {
|
||||||
|
ESP_LOGD(TAG, " Transition Length: %.1fs", *this->transition_length_ / 1e3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case: Transition and effect can be set when turning off
|
||||||
|
if (this->has_effect_()) {
|
||||||
|
if (this->publish_) {
|
||||||
|
ESP_LOGD(TAG, " Effect: 'None'");
|
||||||
|
}
|
||||||
|
this->parent_->stop_effect_();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->parent_->start_transition_(v, *this->transition_length_);
|
||||||
|
|
||||||
|
} else if (this->has_effect_()) {
|
||||||
|
// EFFECT
|
||||||
|
auto effect = this->effect_;
|
||||||
|
const char *effect_s;
|
||||||
|
if (effect == 0)
|
||||||
|
effect_s = "None";
|
||||||
|
else
|
||||||
|
effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();
|
||||||
|
|
||||||
|
if (this->publish_) {
|
||||||
|
ESP_LOGD(TAG, " Effect: '%s'", effect_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->parent_->start_effect_(*this->effect_);
|
||||||
|
|
||||||
|
// Also set light color values when starting an effect
|
||||||
|
// For example to turn off the light
|
||||||
|
this->parent_->set_immediately_(v, true);
|
||||||
|
} else {
|
||||||
|
// INSTANT CHANGE
|
||||||
|
this->parent_->set_immediately_(v, this->publish_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->has_transition_()) {
|
||||||
|
this->parent_->target_state_reached_callback_.call();
|
||||||
|
}
|
||||||
|
if (this->publish_) {
|
||||||
|
this->parent_->publish_state();
|
||||||
|
}
|
||||||
|
if (this->save_) {
|
||||||
|
this->parent_->save_remote_values_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LightColorValues LightCall::validate_() {
|
||||||
|
// use remote values for fallback
|
||||||
|
auto *name = this->parent_->get_name().c_str();
|
||||||
|
auto traits = this->parent_->get_traits();
|
||||||
|
|
||||||
|
// Brightness exists check
|
||||||
|
if (this->brightness_.has_value() && !traits.get_supports_brightness()) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name);
|
||||||
|
this->brightness_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition length possible check
|
||||||
|
if (this->transition_length_.has_value() && *this->transition_length_ != 0 && !traits.get_supports_brightness()) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name);
|
||||||
|
this->transition_length_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGB exists check
|
||||||
|
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
||||||
|
if (!traits.get_supports_rgb()) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - This light does not support setting RGB color!", name);
|
||||||
|
this->red_.reset();
|
||||||
|
this->green_.reset();
|
||||||
|
this->blue_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// White value exists check
|
||||||
|
if (this->white_.has_value() && !traits.get_supports_rgb_white_value()) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - This light does not support setting white value!", name);
|
||||||
|
this->white_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color temperature exists check
|
||||||
|
if (this->color_temperature_.has_value() && !traits.get_supports_color_temperature()) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - This light does not support setting color temperature!", name);
|
||||||
|
this->color_temperature_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If white channel is specified, set RGB to white color (when interlock is enabled)
|
||||||
|
if (this->white_.has_value()) {
|
||||||
|
if (traits.get_supports_color_interlock()) {
|
||||||
|
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
|
||||||
|
this->red_ = optional<float>(1.0f);
|
||||||
|
this->green_ = optional<float>(1.0f);
|
||||||
|
this->blue_ = optional<float>(1.0f);
|
||||||
|
}
|
||||||
|
// make white values binary aka 0.0f or 1.0f... this allows brightness to do its job
|
||||||
|
if (*this->white_ > 0.0f) {
|
||||||
|
this->white_ = optional<float>(1.0f);
|
||||||
|
} else {
|
||||||
|
this->white_ = optional<float>(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled)
|
||||||
|
else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
||||||
|
if (traits.get_supports_color_interlock()) {
|
||||||
|
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) {
|
||||||
|
this->white_ = optional<float>(1.0f);
|
||||||
|
} else {
|
||||||
|
this->white_ = optional<float>(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If only a color temperature is specified, change to white light
|
||||||
|
else if (this->color_temperature_.has_value()) {
|
||||||
|
this->red_ = optional<float>(1.0f);
|
||||||
|
this->green_ = optional<float>(1.0f);
|
||||||
|
this->blue_ = optional<float>(1.0f);
|
||||||
|
|
||||||
|
// if setting color temperature from color (i.e. switching to white light), set White to 100%
|
||||||
|
auto cv = this->parent_->remote_values;
|
||||||
|
bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f;
|
||||||
|
if (traits.get_supports_color_interlock() || was_color) {
|
||||||
|
this->white_ = optional<float>(1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define VALIDATE_RANGE_(name_, upper_name) \
|
||||||
|
if (name_##_.has_value()) { \
|
||||||
|
auto val = *name_##_; \
|
||||||
|
if (val < 0.0f || val > 1.0f) { \
|
||||||
|
ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [0.0 - 1.0]!", name, upper_name, val); \
|
||||||
|
name_##_ = clamp(val, 0.0f, 1.0f); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name)
|
||||||
|
|
||||||
|
// Range checks
|
||||||
|
VALIDATE_RANGE(brightness, "Brightness")
|
||||||
|
VALIDATE_RANGE(red, "Red")
|
||||||
|
VALIDATE_RANGE(green, "Green")
|
||||||
|
VALIDATE_RANGE(blue, "Blue")
|
||||||
|
VALIDATE_RANGE(white, "White")
|
||||||
|
|
||||||
|
auto v = this->parent_->remote_values;
|
||||||
|
if (this->state_.has_value())
|
||||||
|
v.set_state(*this->state_);
|
||||||
|
if (this->brightness_.has_value())
|
||||||
|
v.set_brightness(*this->brightness_);
|
||||||
|
|
||||||
|
if (this->red_.has_value())
|
||||||
|
v.set_red(*this->red_);
|
||||||
|
if (this->green_.has_value())
|
||||||
|
v.set_green(*this->green_);
|
||||||
|
if (this->blue_.has_value())
|
||||||
|
v.set_blue(*this->blue_);
|
||||||
|
if (this->white_.has_value())
|
||||||
|
v.set_white(*this->white_);
|
||||||
|
|
||||||
|
if (this->color_temperature_.has_value())
|
||||||
|
v.set_color_temperature(*this->color_temperature_);
|
||||||
|
|
||||||
|
v.normalize_color(traits);
|
||||||
|
|
||||||
|
// Flash length check
|
||||||
|
if (this->has_flash_() && *this->flash_length_ == 0) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Flash length must be greater than zero!", name);
|
||||||
|
this->flash_length_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate transition length/flash length/effect not used at the same time
|
||||||
|
bool supports_transition = traits.get_supports_brightness();
|
||||||
|
|
||||||
|
// If effect is already active, remove effect start
|
||||||
|
if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) {
|
||||||
|
this->effect_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate effect index
|
||||||
|
if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) {
|
||||||
|
ESP_LOGW(TAG, "'%s' Invalid effect index %u", name, *this->effect_);
|
||||||
|
this->effect_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Effect cannot be used together with transition/flash!", name);
|
||||||
|
this->transition_length_.reset();
|
||||||
|
this->flash_length_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->has_flash_() && this->has_transition_()) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Flash cannot be used together with transition!", name);
|
||||||
|
this->transition_length_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || *this->effect_ == 0) &&
|
||||||
|
supports_transition) {
|
||||||
|
// nothing specified and light supports transitions, set default transition length
|
||||||
|
this->transition_length_ = this->parent_->default_transition_length_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->transition_length_.value_or(0) == 0) {
|
||||||
|
// 0 transition is interpreted as no transition (instant change)
|
||||||
|
this->transition_length_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->has_transition_() && !supports_transition) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Light does not support transitions!", name);
|
||||||
|
this->transition_length_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not a flash and turning the light off, then disable the light
|
||||||
|
// Do not use light color values directly, so that effects can set 0% brightness
|
||||||
|
// Reason: When user turns off the light in frontend, the effect should also stop
|
||||||
|
if (!this->has_flash_() && !this->state_.value_or(v.is_on())) {
|
||||||
|
if (this->has_effect_()) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name);
|
||||||
|
this->effect_.reset();
|
||||||
|
} else if (this->parent_->active_effect_index_ != 0) {
|
||||||
|
// Auto turn off effect
|
||||||
|
this->effect_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable saving for flashes
|
||||||
|
if (this->has_flash_())
|
||||||
|
this->save_ = false;
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_effect(const std::string &effect) {
|
||||||
|
if (strcasecmp(effect.c_str(), "none") == 0) {
|
||||||
|
this->set_effect(0);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) {
|
||||||
|
LightEffect *e = this->parent_->effects_[i];
|
||||||
|
|
||||||
|
if (strcasecmp(effect.c_str(), e->get_name().c_str()) == 0) {
|
||||||
|
this->set_effect(i + 1);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
ESP_LOGW(TAG, "'%s' - No such effect '%s'", this->parent_->get_name().c_str(), effect.c_str());
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::from_light_color_values(const LightColorValues &values) {
|
||||||
|
this->set_state(values.is_on());
|
||||||
|
this->set_brightness_if_supported(values.get_brightness());
|
||||||
|
this->set_red_if_supported(values.get_red());
|
||||||
|
this->set_green_if_supported(values.get_green());
|
||||||
|
this->set_blue_if_supported(values.get_blue());
|
||||||
|
this->set_white_if_supported(values.get_white());
|
||||||
|
this->set_color_temperature_if_supported(values.get_color_temperature());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) {
|
||||||
|
if (this->parent_->get_traits().get_supports_brightness())
|
||||||
|
this->set_transition_length(transition_length);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_brightness_if_supported(float brightness) {
|
||||||
|
if (this->parent_->get_traits().get_supports_brightness())
|
||||||
|
this->set_brightness(brightness);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_red_if_supported(float red) {
|
||||||
|
if (this->parent_->get_traits().get_supports_rgb())
|
||||||
|
this->set_red(red);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_green_if_supported(float green) {
|
||||||
|
if (this->parent_->get_traits().get_supports_rgb())
|
||||||
|
this->set_green(green);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_blue_if_supported(float blue) {
|
||||||
|
if (this->parent_->get_traits().get_supports_rgb())
|
||||||
|
this->set_blue(blue);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_white_if_supported(float white) {
|
||||||
|
if (this->parent_->get_traits().get_supports_rgb_white_value())
|
||||||
|
this->set_white(white);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) {
|
||||||
|
if (this->parent_->get_traits().get_supports_color_temperature())
|
||||||
|
this->set_color_temperature(color_temperature);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_state(optional<bool> state) {
|
||||||
|
this->state_ = state;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_state(bool state) {
|
||||||
|
this->state_ = state;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_transition_length(optional<uint32_t> transition_length) {
|
||||||
|
this->transition_length_ = transition_length;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_transition_length(uint32_t transition_length) {
|
||||||
|
this->transition_length_ = transition_length;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_flash_length(optional<uint32_t> flash_length) {
|
||||||
|
this->flash_length_ = flash_length;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_flash_length(uint32_t flash_length) {
|
||||||
|
this->flash_length_ = flash_length;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_brightness(optional<float> brightness) {
|
||||||
|
this->brightness_ = brightness;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_brightness(float brightness) {
|
||||||
|
this->brightness_ = brightness;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_red(optional<float> red) {
|
||||||
|
this->red_ = red;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_red(float red) {
|
||||||
|
this->red_ = red;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_green(optional<float> green) {
|
||||||
|
this->green_ = green;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_green(float green) {
|
||||||
|
this->green_ = green;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_blue(optional<float> blue) {
|
||||||
|
this->blue_ = blue;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_blue(float blue) {
|
||||||
|
this->blue_ = blue;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_white(optional<float> white) {
|
||||||
|
this->white_ = white;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_white(float white) {
|
||||||
|
this->white_ = white;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_color_temperature(optional<float> color_temperature) {
|
||||||
|
this->color_temperature_ = color_temperature;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_color_temperature(float color_temperature) {
|
||||||
|
this->color_temperature_ = color_temperature;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_effect(optional<std::string> effect) {
|
||||||
|
if (effect.has_value())
|
||||||
|
this->set_effect(*effect);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_effect(uint32_t effect_number) {
|
||||||
|
this->effect_ = effect_number;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
|
||||||
|
this->effect_ = effect_number;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_publish(bool publish) {
|
||||||
|
this->publish_ = publish;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_save(bool save) {
|
||||||
|
this->save_ = save;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_rgb(float red, float green, float blue) {
|
||||||
|
this->set_red(red);
|
||||||
|
this->set_green(green);
|
||||||
|
this->set_blue(blue);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
LightCall &LightCall::set_rgbw(float red, float green, float blue, float white) {
|
||||||
|
this->set_rgb(red, green, blue);
|
||||||
|
this->set_white(white);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
160
esphome/components/light/light_call.h
Normal file
160
esphome/components/light/light_call.h
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/optional.h"
|
||||||
|
#include "light_color_values.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
class LightState;
|
||||||
|
|
||||||
|
/** This class represents a requested change in a light state.
|
||||||
|
*/
|
||||||
|
class LightCall {
|
||||||
|
public:
|
||||||
|
explicit LightCall(LightState *parent) : parent_(parent) {}
|
||||||
|
|
||||||
|
/// Set the binary ON/OFF state of the light.
|
||||||
|
LightCall &set_state(optional<bool> state);
|
||||||
|
/// Set the binary ON/OFF state of the light.
|
||||||
|
LightCall &set_state(bool state);
|
||||||
|
/** Set the transition length of this call in milliseconds.
|
||||||
|
*
|
||||||
|
* This argument is ignored for starting flashes and effects.
|
||||||
|
*
|
||||||
|
* Defaults to the default transition length defined in the light configuration.
|
||||||
|
*/
|
||||||
|
LightCall &set_transition_length(optional<uint32_t> transition_length);
|
||||||
|
/** Set the transition length of this call in milliseconds.
|
||||||
|
*
|
||||||
|
* This argument is ignored for starting flashes and effects.
|
||||||
|
*
|
||||||
|
* Defaults to the default transition length defined in the light configuration.
|
||||||
|
*/
|
||||||
|
LightCall &set_transition_length(uint32_t transition_length);
|
||||||
|
/// Set the transition length property if the light supports transitions.
|
||||||
|
LightCall &set_transition_length_if_supported(uint32_t transition_length);
|
||||||
|
/// Start and set the flash length of this call in milliseconds.
|
||||||
|
LightCall &set_flash_length(optional<uint32_t> flash_length);
|
||||||
|
/// Start and set the flash length of this call in milliseconds.
|
||||||
|
LightCall &set_flash_length(uint32_t flash_length);
|
||||||
|
/// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on)
|
||||||
|
LightCall &set_brightness(optional<float> brightness);
|
||||||
|
/// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on)
|
||||||
|
LightCall &set_brightness(float brightness);
|
||||||
|
/// Set the brightness property if the light supports brightness.
|
||||||
|
LightCall &set_brightness_if_supported(float brightness);
|
||||||
|
/** Set the red RGB value of the light from 0.0 to 1.0.
|
||||||
|
*
|
||||||
|
* Note that this only controls the color of the light, not its brightness.
|
||||||
|
*/
|
||||||
|
LightCall &set_red(optional<float> red);
|
||||||
|
/** Set the red RGB value of the light from 0.0 to 1.0.
|
||||||
|
*
|
||||||
|
* Note that this only controls the color of the light, not its brightness.
|
||||||
|
*/
|
||||||
|
LightCall &set_red(float red);
|
||||||
|
/// Set the red property if the light supports RGB.
|
||||||
|
LightCall &set_red_if_supported(float red);
|
||||||
|
/** Set the green RGB value of the light from 0.0 to 1.0.
|
||||||
|
*
|
||||||
|
* Note that this only controls the color of the light, not its brightness.
|
||||||
|
*/
|
||||||
|
LightCall &set_green(optional<float> green);
|
||||||
|
/** Set the green RGB value of the light from 0.0 to 1.0.
|
||||||
|
*
|
||||||
|
* Note that this only controls the color of the light, not its brightness.
|
||||||
|
*/
|
||||||
|
LightCall &set_green(float green);
|
||||||
|
/// Set the green property if the light supports RGB.
|
||||||
|
LightCall &set_green_if_supported(float green);
|
||||||
|
/** Set the blue RGB value of the light from 0.0 to 1.0.
|
||||||
|
*
|
||||||
|
* Note that this only controls the color of the light, not its brightness.
|
||||||
|
*/
|
||||||
|
LightCall &set_blue(optional<float> blue);
|
||||||
|
/** Set the blue RGB value of the light from 0.0 to 1.0.
|
||||||
|
*
|
||||||
|
* Note that this only controls the color of the light, not its brightness.
|
||||||
|
*/
|
||||||
|
LightCall &set_blue(float blue);
|
||||||
|
/// Set the blue property if the light supports RGB.
|
||||||
|
LightCall &set_blue_if_supported(float blue);
|
||||||
|
/// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights.
|
||||||
|
LightCall &set_white(optional<float> white);
|
||||||
|
/// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights.
|
||||||
|
LightCall &set_white(float white);
|
||||||
|
/// Set the white property if the light supports RGB.
|
||||||
|
LightCall &set_white_if_supported(float white);
|
||||||
|
/// Set the color temperature of the light in mireds for CWWW or RGBWW lights.
|
||||||
|
LightCall &set_color_temperature(optional<float> color_temperature);
|
||||||
|
/// Set the color temperature of the light in mireds for CWWW or RGBWW lights.
|
||||||
|
LightCall &set_color_temperature(float color_temperature);
|
||||||
|
/// Set the color_temperature property if the light supports color temperature.
|
||||||
|
LightCall &set_color_temperature_if_supported(float color_temperature);
|
||||||
|
/// Set the effect of the light by its name.
|
||||||
|
LightCall &set_effect(optional<std::string> effect);
|
||||||
|
/// Set the effect of the light by its name.
|
||||||
|
LightCall &set_effect(const std::string &effect);
|
||||||
|
/// Set the effect of the light by its internal index number (only for internal use).
|
||||||
|
LightCall &set_effect(uint32_t effect_number);
|
||||||
|
LightCall &set_effect(optional<uint32_t> effect_number);
|
||||||
|
/// Set whether this light call should trigger a publish state.
|
||||||
|
LightCall &set_publish(bool publish);
|
||||||
|
/// Set whether this light call should trigger a save state to recover them at startup..
|
||||||
|
LightCall &set_save(bool save);
|
||||||
|
|
||||||
|
/** Set the RGB color of the light by RGB values.
|
||||||
|
*
|
||||||
|
* Please note that this only changes the color of the light, not the brightness.
|
||||||
|
*
|
||||||
|
* @param red The red color value from 0.0 to 1.0.
|
||||||
|
* @param green The green color value from 0.0 to 1.0.
|
||||||
|
* @param blue The blue color value from 0.0 to 1.0.
|
||||||
|
* @return The light call for chaining setters.
|
||||||
|
*/
|
||||||
|
LightCall &set_rgb(float red, float green, float blue);
|
||||||
|
/** Set the RGBW color of the light by RGB values.
|
||||||
|
*
|
||||||
|
* Please note that this only changes the color of the light, not the brightness.
|
||||||
|
*
|
||||||
|
* @param red The red color value from 0.0 to 1.0.
|
||||||
|
* @param green The green color value from 0.0 to 1.0.
|
||||||
|
* @param blue The blue color value from 0.0 to 1.0.
|
||||||
|
* @param white The white color value from 0.0 to 1.0.
|
||||||
|
* @return The light call for chaining setters.
|
||||||
|
*/
|
||||||
|
LightCall &set_rgbw(float red, float green, float blue, float white);
|
||||||
|
#ifdef USE_JSON
|
||||||
|
LightCall &parse_color_json(JsonObject &root);
|
||||||
|
LightCall &parse_json(JsonObject &root);
|
||||||
|
#endif
|
||||||
|
LightCall &from_light_color_values(const LightColorValues &values);
|
||||||
|
|
||||||
|
void perform();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Validate all properties and return the target light color values.
|
||||||
|
LightColorValues validate_();
|
||||||
|
|
||||||
|
bool has_transition_() { return this->transition_length_.has_value(); }
|
||||||
|
bool has_flash_() { return this->flash_length_.has_value(); }
|
||||||
|
bool has_effect_() { return this->effect_.has_value(); }
|
||||||
|
|
||||||
|
LightState *parent_;
|
||||||
|
optional<bool> state_;
|
||||||
|
optional<uint32_t> transition_length_;
|
||||||
|
optional<uint32_t> flash_length_;
|
||||||
|
optional<float> brightness_;
|
||||||
|
optional<float> red_;
|
||||||
|
optional<float> green_;
|
||||||
|
optional<float> blue_;
|
||||||
|
optional<float> white_;
|
||||||
|
optional<float> color_temperature_;
|
||||||
|
optional<uint32_t> effect_;
|
||||||
|
bool publish_{true};
|
||||||
|
bool save_{true};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
|
@ -7,80 +7,13 @@ namespace light {
|
||||||
|
|
||||||
static const char *const TAG = "light";
|
static const char *const TAG = "light";
|
||||||
|
|
||||||
void LightState::start_transition_(const LightColorValues &target, uint32_t length) {
|
|
||||||
this->transformer_ = make_unique<LightTransitionTransformer>(millis(), length, this->current_values, target);
|
|
||||||
this->remote_values = this->transformer_->get_remote_values();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LightState::start_flash_(const LightColorValues &target, uint32_t length) {
|
|
||||||
LightColorValues end_colors = this->current_values;
|
|
||||||
// If starting a flash if one is already happening, set end values to end values of current flash
|
|
||||||
// Hacky but works
|
|
||||||
if (this->transformer_ != nullptr)
|
|
||||||
end_colors = this->transformer_->get_end_values();
|
|
||||||
this->transformer_ = make_unique<LightFlashTransformer>(millis(), length, end_colors, target);
|
|
||||||
this->remote_values = this->transformer_->get_remote_values();
|
|
||||||
}
|
|
||||||
|
|
||||||
LightState::LightState(const std::string &name, LightOutput *output) : Nameable(name), output_(output) {}
|
LightState::LightState(const std::string &name, LightOutput *output) : Nameable(name), output_(output) {}
|
||||||
|
|
||||||
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
LightTraits LightState::get_traits() { return this->output_->get_traits(); }
|
||||||
this->transformer_ = nullptr;
|
LightCall LightState::turn_on() { return this->make_call().set_state(true); }
|
||||||
this->current_values = target;
|
LightCall LightState::turn_off() { return this->make_call().set_state(false); }
|
||||||
if (set_remote_values) {
|
LightCall LightState::toggle() { return this->make_call().set_state(!this->remote_values.is_on()); }
|
||||||
this->remote_values = target;
|
LightCall LightState::make_call() { return LightCall(this); }
|
||||||
}
|
|
||||||
this->next_write_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
LightColorValues LightState::get_current_values() { return this->current_values; }
|
|
||||||
|
|
||||||
void LightState::publish_state() {
|
|
||||||
this->remote_values_callback_.call();
|
|
||||||
this->next_write_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
LightColorValues LightState::get_remote_values() { return this->remote_values; }
|
|
||||||
|
|
||||||
std::string LightState::get_effect_name() {
|
|
||||||
if (this->active_effect_index_ > 0)
|
|
||||||
return this->effects_[this->active_effect_index_ - 1]->get_name();
|
|
||||||
else
|
|
||||||
return "None";
|
|
||||||
}
|
|
||||||
|
|
||||||
void LightState::start_effect_(uint32_t effect_index) {
|
|
||||||
this->stop_effect_();
|
|
||||||
if (effect_index == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this->active_effect_index_ = effect_index;
|
|
||||||
auto *effect = this->get_active_effect_();
|
|
||||||
effect->start_internal();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LightState::supports_effects() { return !this->effects_.empty(); }
|
|
||||||
void LightState::set_transformer_(std::unique_ptr<LightTransformer> transformer) {
|
|
||||||
this->transformer_ = std::move(transformer);
|
|
||||||
}
|
|
||||||
void LightState::stop_effect_() {
|
|
||||||
auto *effect = this->get_active_effect_();
|
|
||||||
if (effect != nullptr) {
|
|
||||||
effect->stop();
|
|
||||||
}
|
|
||||||
this->active_effect_index_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LightState::set_default_transition_length(uint32_t default_transition_length) {
|
|
||||||
this->default_transition_length_ = default_transition_length;
|
|
||||||
}
|
|
||||||
#ifdef USE_JSON
|
|
||||||
void LightState::dump_json(JsonObject &root) {
|
|
||||||
if (this->supports_effects())
|
|
||||||
root["effect"] = this->get_effect_name();
|
|
||||||
this->remote_values.dump_json(root, this->output_->get_traits());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct LightStateRTCState {
|
struct LightStateRTCState {
|
||||||
bool state{false};
|
bool state{false};
|
||||||
|
@ -144,6 +77,17 @@ void LightState::setup() {
|
||||||
}
|
}
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
|
void LightState::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str());
|
||||||
|
if (this->get_traits().get_supports_brightness()) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Default Transition Length: %.1fs", this->default_transition_length_ / 1e3f);
|
||||||
|
ESP_LOGCONFIG(TAG, " Gamma Correct: %.2f", this->gamma_correct_);
|
||||||
|
}
|
||||||
|
if (this->get_traits().get_supports_color_temperature()) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Min Mireds: %.1f", this->get_traits().get_min_mireds());
|
||||||
|
ESP_LOGCONFIG(TAG, " Max Mireds: %.1f", this->get_traits().get_max_mireds());
|
||||||
|
}
|
||||||
|
}
|
||||||
void LightState::loop() {
|
void LightState::loop() {
|
||||||
// Apply effect (if any)
|
// Apply effect (if any)
|
||||||
auto *effect = this->get_active_effect_();
|
auto *effect = this->get_active_effect_();
|
||||||
|
@ -171,7 +115,47 @@ void LightState::loop() {
|
||||||
this->next_write_ = false;
|
this->next_write_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LightTraits LightState::get_traits() { return this->output_->get_traits(); }
|
|
||||||
|
float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
||||||
|
uint32_t LightState::hash_base() { return 1114400283; }
|
||||||
|
|
||||||
|
LightColorValues LightState::get_current_values() { return this->current_values; }
|
||||||
|
LightColorValues LightState::get_remote_values() { return this->remote_values; }
|
||||||
|
|
||||||
|
void LightState::publish_state() {
|
||||||
|
this->remote_values_callback_.call();
|
||||||
|
this->next_write_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LightOutput *LightState::get_output() const { return this->output_; }
|
||||||
|
std::string LightState::get_effect_name() {
|
||||||
|
if (this->active_effect_index_ > 0)
|
||||||
|
return this->effects_[this->active_effect_index_ - 1]->get_name();
|
||||||
|
else
|
||||||
|
return "None";
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
|
||||||
|
this->remote_values_callback_.add(std::move(send_callback));
|
||||||
|
}
|
||||||
|
void LightState::add_new_target_state_reached_callback(std::function<void()> &&send_callback) {
|
||||||
|
this->target_state_reached_callback_.add(std::move(send_callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_JSON
|
||||||
|
void LightState::dump_json(JsonObject &root) {
|
||||||
|
if (this->supports_effects())
|
||||||
|
root["effect"] = this->get_effect_name();
|
||||||
|
this->remote_values.dump_json(root, this->output_->get_traits());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void LightState::set_default_transition_length(uint32_t default_transition_length) {
|
||||||
|
this->default_transition_length_ = default_transition_length;
|
||||||
|
}
|
||||||
|
void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
|
||||||
|
void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
||||||
|
bool LightState::supports_effects() { return !this->effects_.empty(); }
|
||||||
const std::vector<LightEffect *> &LightState::get_effects() const { return this->effects_; }
|
const std::vector<LightEffect *> &LightState::get_effects() const { return this->effects_; }
|
||||||
void LightState::add_effects(const std::vector<LightEffect *> &effects) {
|
void LightState::add_effects(const std::vector<LightEffect *> &effects) {
|
||||||
this->effects_.reserve(this->effects_.size() + effects.size());
|
this->effects_.reserve(this->effects_.size() + effects.size());
|
||||||
|
@ -179,551 +163,7 @@ void LightState::add_effects(const std::vector<LightEffect *> &effects) {
|
||||||
this->effects_.push_back(effect);
|
this->effects_.push_back(effect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LightCall LightState::turn_on() { return this->make_call().set_state(true); }
|
|
||||||
LightCall LightState::turn_off() { return this->make_call().set_state(false); }
|
|
||||||
LightCall LightState::toggle() { return this->make_call().set_state(!this->remote_values.is_on()); }
|
|
||||||
LightCall LightState::make_call() { return LightCall(this); }
|
|
||||||
uint32_t LightState::hash_base() { return 1114400283; }
|
|
||||||
void LightState::dump_config() {
|
|
||||||
ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str());
|
|
||||||
if (this->get_traits().get_supports_brightness()) {
|
|
||||||
ESP_LOGCONFIG(TAG, " Default Transition Length: %.1fs", this->default_transition_length_ / 1e3f);
|
|
||||||
ESP_LOGCONFIG(TAG, " Gamma Correct: %.2f", this->gamma_correct_);
|
|
||||||
}
|
|
||||||
if (this->get_traits().get_supports_color_temperature()) {
|
|
||||||
ESP_LOGCONFIG(TAG, " Min Mireds: %.1f", this->get_traits().get_min_mireds());
|
|
||||||
ESP_LOGCONFIG(TAG, " Max Mireds: %.1f", this->get_traits().get_max_mireds());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef USE_MQTT_LIGHT
|
|
||||||
MQTTJSONLightComponent *LightState::get_mqtt() const { return this->mqtt_; }
|
|
||||||
void LightState::set_mqtt(MQTTJSONLightComponent *mqtt) { this->mqtt_ = mqtt; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_JSON
|
|
||||||
LightCall &LightCall::parse_color_json(JsonObject &root) {
|
|
||||||
if (root.containsKey("state")) {
|
|
||||||
auto val = parse_on_off(root["state"]);
|
|
||||||
switch (val) {
|
|
||||||
case PARSE_ON:
|
|
||||||
this->set_state(true);
|
|
||||||
break;
|
|
||||||
case PARSE_OFF:
|
|
||||||
this->set_state(false);
|
|
||||||
break;
|
|
||||||
case PARSE_TOGGLE:
|
|
||||||
this->set_state(!this->parent_->remote_values.is_on());
|
|
||||||
break;
|
|
||||||
case PARSE_NONE:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (root.containsKey("brightness")) {
|
|
||||||
this->set_brightness(float(root["brightness"]) / 255.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (root.containsKey("color")) {
|
|
||||||
JsonObject &color = root["color"];
|
|
||||||
if (color.containsKey("r")) {
|
|
||||||
this->set_red(float(color["r"]) / 255.0f);
|
|
||||||
}
|
|
||||||
if (color.containsKey("g")) {
|
|
||||||
this->set_green(float(color["g"]) / 255.0f);
|
|
||||||
}
|
|
||||||
if (color.containsKey("b")) {
|
|
||||||
this->set_blue(float(color["b"]) / 255.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (root.containsKey("white_value")) {
|
|
||||||
this->set_white(float(root["white_value"]) / 255.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (root.containsKey("color_temp")) {
|
|
||||||
this->set_color_temperature(float(root["color_temp"]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::parse_json(JsonObject &root) {
|
|
||||||
this->parse_color_json(root);
|
|
||||||
|
|
||||||
if (root.containsKey("flash")) {
|
|
||||||
auto length = uint32_t(float(root["flash"]) * 1000);
|
|
||||||
this->set_flash_length(length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (root.containsKey("transition")) {
|
|
||||||
auto length = uint32_t(float(root["transition"]) * 1000);
|
|
||||||
this->set_transition_length(length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (root.containsKey("effect")) {
|
|
||||||
const char *effect = root["effect"];
|
|
||||||
this->set_effect(effect);
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void LightCall::perform() {
|
|
||||||
// use remote values for fallback
|
|
||||||
const char *name = this->parent_->get_name().c_str();
|
|
||||||
if (this->publish_) {
|
|
||||||
ESP_LOGD(TAG, "'%s' Setting:", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
LightColorValues v = this->validate_();
|
|
||||||
|
|
||||||
if (this->publish_) {
|
|
||||||
// Only print state when it's being changed
|
|
||||||
bool current_state = this->parent_->remote_values.is_on();
|
|
||||||
if (this->state_.value_or(current_state) != current_state) {
|
|
||||||
ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->brightness_.has_value()) {
|
|
||||||
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->color_temperature_.has_value()) {
|
|
||||||
ESP_LOGD(TAG, " Color Temperature: %.1f mireds", v.get_color_temperature());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
|
||||||
ESP_LOGD(TAG, " Red=%.0f%%, Green=%.0f%%, Blue=%.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
|
|
||||||
v.get_blue() * 100.0f);
|
|
||||||
}
|
|
||||||
if (this->white_.has_value()) {
|
|
||||||
ESP_LOGD(TAG, " White Value: %.0f%%", v.get_white() * 100.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->has_flash_()) {
|
|
||||||
// FLASH
|
|
||||||
if (this->publish_) {
|
|
||||||
ESP_LOGD(TAG, " Flash Length: %.1fs", *this->flash_length_ / 1e3f);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->parent_->start_flash_(v, *this->flash_length_);
|
|
||||||
} else if (this->has_transition_()) {
|
|
||||||
// TRANSITION
|
|
||||||
if (this->publish_) {
|
|
||||||
ESP_LOGD(TAG, " Transition Length: %.1fs", *this->transition_length_ / 1e3f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case: Transition and effect can be set when turning off
|
|
||||||
if (this->has_effect_()) {
|
|
||||||
if (this->publish_) {
|
|
||||||
ESP_LOGD(TAG, " Effect: 'None'");
|
|
||||||
}
|
|
||||||
this->parent_->stop_effect_();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->parent_->start_transition_(v, *this->transition_length_);
|
|
||||||
|
|
||||||
} else if (this->has_effect_()) {
|
|
||||||
// EFFECT
|
|
||||||
auto effect = this->effect_;
|
|
||||||
const char *effect_s;
|
|
||||||
if (effect == 0)
|
|
||||||
effect_s = "None";
|
|
||||||
else
|
|
||||||
effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();
|
|
||||||
|
|
||||||
if (this->publish_) {
|
|
||||||
ESP_LOGD(TAG, " Effect: '%s'", effect_s);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->parent_->start_effect_(*this->effect_);
|
|
||||||
|
|
||||||
// Also set light color values when starting an effect
|
|
||||||
// For example to turn off the light
|
|
||||||
this->parent_->set_immediately_(v, true);
|
|
||||||
} else {
|
|
||||||
// INSTANT CHANGE
|
|
||||||
this->parent_->set_immediately_(v, this->publish_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->has_transition_()) {
|
|
||||||
this->parent_->target_state_reached_callback_.call();
|
|
||||||
}
|
|
||||||
if (this->publish_) {
|
|
||||||
this->parent_->publish_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->save_) {
|
|
||||||
LightStateRTCState saved;
|
|
||||||
saved.state = v.is_on();
|
|
||||||
saved.brightness = v.get_brightness();
|
|
||||||
saved.red = v.get_red();
|
|
||||||
saved.green = v.get_green();
|
|
||||||
saved.blue = v.get_blue();
|
|
||||||
saved.white = v.get_white();
|
|
||||||
saved.color_temp = v.get_color_temperature();
|
|
||||||
saved.effect = this->parent_->active_effect_index_;
|
|
||||||
this->parent_->rtc_.save(&saved);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LightColorValues LightCall::validate_() {
|
|
||||||
// use remote values for fallback
|
|
||||||
auto *name = this->parent_->get_name().c_str();
|
|
||||||
auto traits = this->parent_->get_traits();
|
|
||||||
|
|
||||||
// Brightness exists check
|
|
||||||
if (this->brightness_.has_value() && !traits.get_supports_brightness()) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name);
|
|
||||||
this->brightness_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transition length possible check
|
|
||||||
if (this->transition_length_.has_value() && *this->transition_length_ != 0 && !traits.get_supports_brightness()) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name);
|
|
||||||
this->transition_length_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// RGB exists check
|
|
||||||
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
|
||||||
if (!traits.get_supports_rgb()) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - This light does not support setting RGB color!", name);
|
|
||||||
this->red_.reset();
|
|
||||||
this->green_.reset();
|
|
||||||
this->blue_.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// White value exists check
|
|
||||||
if (this->white_.has_value() && !traits.get_supports_rgb_white_value()) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - This light does not support setting white value!", name);
|
|
||||||
this->white_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Color temperature exists check
|
|
||||||
if (this->color_temperature_.has_value() && !traits.get_supports_color_temperature()) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - This light does not support setting color temperature!", name);
|
|
||||||
this->color_temperature_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If white channel is specified, set RGB to white color (when interlock is enabled)
|
|
||||||
if (this->white_.has_value()) {
|
|
||||||
if (traits.get_supports_color_interlock()) {
|
|
||||||
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
|
|
||||||
this->red_ = optional<float>(1.0f);
|
|
||||||
this->green_ = optional<float>(1.0f);
|
|
||||||
this->blue_ = optional<float>(1.0f);
|
|
||||||
}
|
|
||||||
// make white values binary aka 0.0f or 1.0f... this allows brightness to do its job
|
|
||||||
if (*this->white_ > 0.0f) {
|
|
||||||
this->white_ = optional<float>(1.0f);
|
|
||||||
} else {
|
|
||||||
this->white_ = optional<float>(0.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled)
|
|
||||||
else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
|
||||||
if (traits.get_supports_color_interlock()) {
|
|
||||||
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) {
|
|
||||||
this->white_ = optional<float>(1.0f);
|
|
||||||
} else {
|
|
||||||
this->white_ = optional<float>(0.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If only a color temperature is specified, change to white light
|
|
||||||
else if (this->color_temperature_.has_value()) {
|
|
||||||
this->red_ = optional<float>(1.0f);
|
|
||||||
this->green_ = optional<float>(1.0f);
|
|
||||||
this->blue_ = optional<float>(1.0f);
|
|
||||||
|
|
||||||
// if setting color temperature from color (i.e. switching to white light), set White to 100%
|
|
||||||
auto cv = this->parent_->remote_values;
|
|
||||||
bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f;
|
|
||||||
if (traits.get_supports_color_interlock() || was_color) {
|
|
||||||
this->white_ = optional<float>(1.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define VALIDATE_RANGE_(name_, upper_name) \
|
|
||||||
if (name_##_.has_value()) { \
|
|
||||||
auto val = *name_##_; \
|
|
||||||
if (val < 0.0f || val > 1.0f) { \
|
|
||||||
ESP_LOGW(TAG, "'%s' - %s value %.2f is out of range [0.0 - 1.0]!", name, upper_name, val); \
|
|
||||||
name_##_ = clamp(val, 0.0f, 1.0f); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name)
|
|
||||||
|
|
||||||
// Range checks
|
|
||||||
VALIDATE_RANGE(brightness, "Brightness")
|
|
||||||
VALIDATE_RANGE(red, "Red")
|
|
||||||
VALIDATE_RANGE(green, "Green")
|
|
||||||
VALIDATE_RANGE(blue, "Blue")
|
|
||||||
VALIDATE_RANGE(white, "White")
|
|
||||||
|
|
||||||
auto v = this->parent_->remote_values;
|
|
||||||
if (this->state_.has_value())
|
|
||||||
v.set_state(*this->state_);
|
|
||||||
if (this->brightness_.has_value())
|
|
||||||
v.set_brightness(*this->brightness_);
|
|
||||||
|
|
||||||
if (this->red_.has_value())
|
|
||||||
v.set_red(*this->red_);
|
|
||||||
if (this->green_.has_value())
|
|
||||||
v.set_green(*this->green_);
|
|
||||||
if (this->blue_.has_value())
|
|
||||||
v.set_blue(*this->blue_);
|
|
||||||
if (this->white_.has_value())
|
|
||||||
v.set_white(*this->white_);
|
|
||||||
|
|
||||||
if (this->color_temperature_.has_value())
|
|
||||||
v.set_color_temperature(*this->color_temperature_);
|
|
||||||
|
|
||||||
v.normalize_color(traits);
|
|
||||||
|
|
||||||
// Flash length check
|
|
||||||
if (this->has_flash_() && *this->flash_length_ == 0) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - Flash length must be greater than zero!", name);
|
|
||||||
this->flash_length_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate transition length/flash length/effect not used at the same time
|
|
||||||
bool supports_transition = traits.get_supports_brightness();
|
|
||||||
|
|
||||||
// If effect is already active, remove effect start
|
|
||||||
if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) {
|
|
||||||
this->effect_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate effect index
|
|
||||||
if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) {
|
|
||||||
ESP_LOGW(TAG, "'%s' Invalid effect index %u", name, *this->effect_);
|
|
||||||
this->effect_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - Effect cannot be used together with transition/flash!", name);
|
|
||||||
this->transition_length_.reset();
|
|
||||||
this->flash_length_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->has_flash_() && this->has_transition_()) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - Flash cannot be used together with transition!", name);
|
|
||||||
this->transition_length_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || *this->effect_ == 0) &&
|
|
||||||
supports_transition) {
|
|
||||||
// nothing specified and light supports transitions, set default transition length
|
|
||||||
this->transition_length_ = this->parent_->default_transition_length_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->transition_length_.value_or(0) == 0) {
|
|
||||||
// 0 transition is interpreted as no transition (instant change)
|
|
||||||
this->transition_length_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->has_transition_() && !supports_transition) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - Light does not support transitions!", name);
|
|
||||||
this->transition_length_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not a flash and turning the light off, then disable the light
|
|
||||||
// Do not use light color values directly, so that effects can set 0% brightness
|
|
||||||
// Reason: When user turns off the light in frontend, the effect should also stop
|
|
||||||
if (!this->has_flash_() && !this->state_.value_or(v.is_on())) {
|
|
||||||
if (this->has_effect_()) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - Cannot start an effect when turning off!", name);
|
|
||||||
this->effect_.reset();
|
|
||||||
} else if (this->parent_->active_effect_index_ != 0) {
|
|
||||||
// Auto turn off effect
|
|
||||||
this->effect_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable saving for flashes
|
|
||||||
if (this->has_flash_())
|
|
||||||
this->save_ = false;
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_effect(const std::string &effect) {
|
|
||||||
if (strcasecmp(effect.c_str(), "none") == 0) {
|
|
||||||
this->set_effect(0);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool found = false;
|
|
||||||
for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) {
|
|
||||||
LightEffect *e = this->parent_->effects_[i];
|
|
||||||
|
|
||||||
if (strcasecmp(effect.c_str(), e->get_name().c_str()) == 0) {
|
|
||||||
this->set_effect(i + 1);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
ESP_LOGW(TAG, "'%s' - No such effect '%s'", this->parent_->get_name().c_str(), effect.c_str());
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::from_light_color_values(const LightColorValues &values) {
|
|
||||||
this->set_state(values.is_on());
|
|
||||||
this->set_brightness_if_supported(values.get_brightness());
|
|
||||||
this->set_red_if_supported(values.get_red());
|
|
||||||
this->set_green_if_supported(values.get_green());
|
|
||||||
this->set_blue_if_supported(values.get_blue());
|
|
||||||
this->set_white_if_supported(values.get_white());
|
|
||||||
this->set_color_temperature_if_supported(values.get_color_temperature());
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) {
|
|
||||||
if (this->parent_->get_traits().get_supports_brightness())
|
|
||||||
this->set_transition_length(transition_length);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_brightness_if_supported(float brightness) {
|
|
||||||
if (this->parent_->get_traits().get_supports_brightness())
|
|
||||||
this->set_brightness(brightness);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_red_if_supported(float red) {
|
|
||||||
if (this->parent_->get_traits().get_supports_rgb())
|
|
||||||
this->set_red(red);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_green_if_supported(float green) {
|
|
||||||
if (this->parent_->get_traits().get_supports_rgb())
|
|
||||||
this->set_green(green);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_blue_if_supported(float blue) {
|
|
||||||
if (this->parent_->get_traits().get_supports_rgb())
|
|
||||||
this->set_blue(blue);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_white_if_supported(float white) {
|
|
||||||
if (this->parent_->get_traits().get_supports_rgb_white_value())
|
|
||||||
this->set_white(white);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) {
|
|
||||||
if (this->parent_->get_traits().get_supports_color_temperature())
|
|
||||||
this->set_color_temperature(color_temperature);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_state(optional<bool> state) {
|
|
||||||
this->state_ = state;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_state(bool state) {
|
|
||||||
this->state_ = state;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_transition_length(optional<uint32_t> transition_length) {
|
|
||||||
this->transition_length_ = transition_length;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_transition_length(uint32_t transition_length) {
|
|
||||||
this->transition_length_ = transition_length;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_flash_length(optional<uint32_t> flash_length) {
|
|
||||||
this->flash_length_ = flash_length;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_flash_length(uint32_t flash_length) {
|
|
||||||
this->flash_length_ = flash_length;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_brightness(optional<float> brightness) {
|
|
||||||
this->brightness_ = brightness;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_brightness(float brightness) {
|
|
||||||
this->brightness_ = brightness;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_red(optional<float> red) {
|
|
||||||
this->red_ = red;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_red(float red) {
|
|
||||||
this->red_ = red;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_green(optional<float> green) {
|
|
||||||
this->green_ = green;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_green(float green) {
|
|
||||||
this->green_ = green;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_blue(optional<float> blue) {
|
|
||||||
this->blue_ = blue;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_blue(float blue) {
|
|
||||||
this->blue_ = blue;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_white(optional<float> white) {
|
|
||||||
this->white_ = white;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_white(float white) {
|
|
||||||
this->white_ = white;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_color_temperature(optional<float> color_temperature) {
|
|
||||||
this->color_temperature_ = color_temperature;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_color_temperature(float color_temperature) {
|
|
||||||
this->color_temperature_ = color_temperature;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_effect(optional<std::string> effect) {
|
|
||||||
if (effect.has_value())
|
|
||||||
this->set_effect(*effect);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_effect(uint32_t effect_number) {
|
|
||||||
this->effect_ = effect_number;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
|
|
||||||
this->effect_ = effect_number;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_publish(bool publish) {
|
|
||||||
this->publish_ = publish;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_save(bool save) {
|
|
||||||
this->save_ = save;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_rgb(float red, float green, float blue) {
|
|
||||||
this->set_red(red);
|
|
||||||
this->set_green(green);
|
|
||||||
this->set_blue(blue);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
LightCall &LightCall::set_rgbw(float red, float green, float blue, float white) {
|
|
||||||
this->set_rgb(red, green, blue);
|
|
||||||
this->set_white(white);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
|
||||||
LightOutput *LightState::get_output() const { return this->output_; }
|
|
||||||
void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
|
|
||||||
void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); }
|
void LightState::current_values_as_binary(bool *binary) { this->current_values.as_binary(binary); }
|
||||||
void LightState::current_values_as_brightness(float *brightness) {
|
void LightState::current_values_as_brightness(float *brightness) {
|
||||||
this->current_values.as_brightness(brightness, this->gamma_correct_);
|
this->current_values.as_brightness(brightness, this->gamma_correct_);
|
||||||
|
@ -748,19 +188,70 @@ void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bo
|
||||||
this->current_values.as_cwww(traits.get_min_mireds(), traits.get_max_mireds(), cold_white, warm_white,
|
this->current_values.as_cwww(traits.get_min_mireds(), traits.get_max_mireds(), cold_white, warm_white,
|
||||||
this->gamma_correct_, constant_brightness);
|
this->gamma_correct_, constant_brightness);
|
||||||
}
|
}
|
||||||
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
|
|
||||||
this->remote_values_callback_.add(std::move(send_callback));
|
|
||||||
}
|
|
||||||
void LightState::add_new_target_state_reached_callback(std::function<void()> &&send_callback) {
|
|
||||||
this->target_state_reached_callback_.add(std::move(send_callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
void LightState::start_effect_(uint32_t effect_index) {
|
||||||
|
this->stop_effect_();
|
||||||
|
if (effect_index == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->active_effect_index_ = effect_index;
|
||||||
|
auto *effect = this->get_active_effect_();
|
||||||
|
effect->start_internal();
|
||||||
|
}
|
||||||
LightEffect *LightState::get_active_effect_() {
|
LightEffect *LightState::get_active_effect_() {
|
||||||
if (this->active_effect_index_ == 0)
|
if (this->active_effect_index_ == 0)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
else
|
else
|
||||||
return this->effects_[this->active_effect_index_ - 1];
|
return this->effects_[this->active_effect_index_ - 1];
|
||||||
}
|
}
|
||||||
|
void LightState::stop_effect_() {
|
||||||
|
auto *effect = this->get_active_effect_();
|
||||||
|
if (effect != nullptr) {
|
||||||
|
effect->stop();
|
||||||
|
}
|
||||||
|
this->active_effect_index_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightState::start_transition_(const LightColorValues &target, uint32_t length) {
|
||||||
|
this->transformer_ = make_unique<LightTransitionTransformer>(millis(), length, this->current_values, target);
|
||||||
|
this->remote_values = this->transformer_->get_remote_values();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightState::start_flash_(const LightColorValues &target, uint32_t length) {
|
||||||
|
LightColorValues end_colors = this->current_values;
|
||||||
|
// If starting a flash if one is already happening, set end values to end values of current flash
|
||||||
|
// Hacky but works
|
||||||
|
if (this->transformer_ != nullptr)
|
||||||
|
end_colors = this->transformer_->get_end_values();
|
||||||
|
this->transformer_ = make_unique<LightFlashTransformer>(millis(), length, end_colors, target);
|
||||||
|
this->remote_values = this->transformer_->get_remote_values();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
||||||
|
this->transformer_ = nullptr;
|
||||||
|
this->current_values = target;
|
||||||
|
if (set_remote_values) {
|
||||||
|
this->remote_values = target;
|
||||||
|
}
|
||||||
|
this->next_write_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightState::set_transformer_(std::unique_ptr<LightTransformer> transformer) {
|
||||||
|
this->transformer_ = std::move(transformer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightState::save_remote_values_() {
|
||||||
|
LightStateRTCState saved;
|
||||||
|
saved.state = this->remote_values.is_on();
|
||||||
|
saved.brightness = this->remote_values.get_brightness();
|
||||||
|
saved.red = this->remote_values.get_red();
|
||||||
|
saved.green = this->remote_values.get_green();
|
||||||
|
saved.blue = this->remote_values.get_blue();
|
||||||
|
saved.white = this->remote_values.get_white();
|
||||||
|
saved.color_temp = this->remote_values.get_color_temperature();
|
||||||
|
saved.effect = this->active_effect_index_;
|
||||||
|
this->rtc_.save(&saved);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -5,161 +5,15 @@
|
||||||
#include "esphome/core/preferences.h"
|
#include "esphome/core/preferences.h"
|
||||||
#include "light_effect.h"
|
#include "light_effect.h"
|
||||||
#include "light_color_values.h"
|
#include "light_color_values.h"
|
||||||
|
#include "light_call.h"
|
||||||
#include "light_traits.h"
|
#include "light_traits.h"
|
||||||
#include "light_transformer.h"
|
#include "light_transformer.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace light {
|
namespace light {
|
||||||
|
|
||||||
class LightState;
|
|
||||||
class LightOutput;
|
class LightOutput;
|
||||||
|
|
||||||
class LightCall {
|
|
||||||
public:
|
|
||||||
explicit LightCall(LightState *parent) : parent_(parent) {}
|
|
||||||
|
|
||||||
/// Set the binary ON/OFF state of the light.
|
|
||||||
LightCall &set_state(optional<bool> state);
|
|
||||||
/// Set the binary ON/OFF state of the light.
|
|
||||||
LightCall &set_state(bool state);
|
|
||||||
/** Set the transition length of this call in milliseconds.
|
|
||||||
*
|
|
||||||
* This argument is ignored for starting flashes and effects.
|
|
||||||
*
|
|
||||||
* Defaults to the default transition length defined in the light configuration.
|
|
||||||
*/
|
|
||||||
LightCall &set_transition_length(optional<uint32_t> transition_length);
|
|
||||||
/** Set the transition length of this call in milliseconds.
|
|
||||||
*
|
|
||||||
* This argument is ignored for starting flashes and effects.
|
|
||||||
*
|
|
||||||
* Defaults to the default transition length defined in the light configuration.
|
|
||||||
*/
|
|
||||||
LightCall &set_transition_length(uint32_t transition_length);
|
|
||||||
/// Set the transition length property if the light supports transitions.
|
|
||||||
LightCall &set_transition_length_if_supported(uint32_t transition_length);
|
|
||||||
/// Start and set the flash length of this call in milliseconds.
|
|
||||||
LightCall &set_flash_length(optional<uint32_t> flash_length);
|
|
||||||
/// Start and set the flash length of this call in milliseconds.
|
|
||||||
LightCall &set_flash_length(uint32_t flash_length);
|
|
||||||
/// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on)
|
|
||||||
LightCall &set_brightness(optional<float> brightness);
|
|
||||||
/// Set the target brightness of the light from 0.0 (fully off) to 1.0 (fully on)
|
|
||||||
LightCall &set_brightness(float brightness);
|
|
||||||
/// Set the brightness property if the light supports brightness.
|
|
||||||
LightCall &set_brightness_if_supported(float brightness);
|
|
||||||
/** Set the red RGB value of the light from 0.0 to 1.0.
|
|
||||||
*
|
|
||||||
* Note that this only controls the color of the light, not its brightness.
|
|
||||||
*/
|
|
||||||
LightCall &set_red(optional<float> red);
|
|
||||||
/** Set the red RGB value of the light from 0.0 to 1.0.
|
|
||||||
*
|
|
||||||
* Note that this only controls the color of the light, not its brightness.
|
|
||||||
*/
|
|
||||||
LightCall &set_red(float red);
|
|
||||||
/// Set the red property if the light supports RGB.
|
|
||||||
LightCall &set_red_if_supported(float red);
|
|
||||||
/** Set the green RGB value of the light from 0.0 to 1.0.
|
|
||||||
*
|
|
||||||
* Note that this only controls the color of the light, not its brightness.
|
|
||||||
*/
|
|
||||||
LightCall &set_green(optional<float> green);
|
|
||||||
/** Set the green RGB value of the light from 0.0 to 1.0.
|
|
||||||
*
|
|
||||||
* Note that this only controls the color of the light, not its brightness.
|
|
||||||
*/
|
|
||||||
LightCall &set_green(float green);
|
|
||||||
/// Set the green property if the light supports RGB.
|
|
||||||
LightCall &set_green_if_supported(float green);
|
|
||||||
/** Set the blue RGB value of the light from 0.0 to 1.0.
|
|
||||||
*
|
|
||||||
* Note that this only controls the color of the light, not its brightness.
|
|
||||||
*/
|
|
||||||
LightCall &set_blue(optional<float> blue);
|
|
||||||
/** Set the blue RGB value of the light from 0.0 to 1.0.
|
|
||||||
*
|
|
||||||
* Note that this only controls the color of the light, not its brightness.
|
|
||||||
*/
|
|
||||||
LightCall &set_blue(float blue);
|
|
||||||
/// Set the blue property if the light supports RGB.
|
|
||||||
LightCall &set_blue_if_supported(float blue);
|
|
||||||
/// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights.
|
|
||||||
LightCall &set_white(optional<float> white);
|
|
||||||
/// Set the white value value of the light from 0.0 to 1.0 for RGBW[W] lights.
|
|
||||||
LightCall &set_white(float white);
|
|
||||||
/// Set the white property if the light supports RGB.
|
|
||||||
LightCall &set_white_if_supported(float white);
|
|
||||||
/// Set the color temperature of the light in mireds for CWWW or RGBWW lights.
|
|
||||||
LightCall &set_color_temperature(optional<float> color_temperature);
|
|
||||||
/// Set the color temperature of the light in mireds for CWWW or RGBWW lights.
|
|
||||||
LightCall &set_color_temperature(float color_temperature);
|
|
||||||
/// Set the color_temperature property if the light supports color temperature.
|
|
||||||
LightCall &set_color_temperature_if_supported(float color_temperature);
|
|
||||||
/// Set the effect of the light by its name.
|
|
||||||
LightCall &set_effect(optional<std::string> effect);
|
|
||||||
/// Set the effect of the light by its name.
|
|
||||||
LightCall &set_effect(const std::string &effect);
|
|
||||||
/// Set the effect of the light by its internal index number (only for internal use).
|
|
||||||
LightCall &set_effect(uint32_t effect_number);
|
|
||||||
LightCall &set_effect(optional<uint32_t> effect_number);
|
|
||||||
/// Set whether this light call should trigger a publish state.
|
|
||||||
LightCall &set_publish(bool publish);
|
|
||||||
/// Set whether this light call should trigger a save state to recover them at startup..
|
|
||||||
LightCall &set_save(bool save);
|
|
||||||
|
|
||||||
/** Set the RGB color of the light by RGB values.
|
|
||||||
*
|
|
||||||
* Please note that this only changes the color of the light, not the brightness.
|
|
||||||
*
|
|
||||||
* @param red The red color value from 0.0 to 1.0.
|
|
||||||
* @param green The green color value from 0.0 to 1.0.
|
|
||||||
* @param blue The blue color value from 0.0 to 1.0.
|
|
||||||
* @return The light call for chaining setters.
|
|
||||||
*/
|
|
||||||
LightCall &set_rgb(float red, float green, float blue);
|
|
||||||
/** Set the RGBW color of the light by RGB values.
|
|
||||||
*
|
|
||||||
* Please note that this only changes the color of the light, not the brightness.
|
|
||||||
*
|
|
||||||
* @param red The red color value from 0.0 to 1.0.
|
|
||||||
* @param green The green color value from 0.0 to 1.0.
|
|
||||||
* @param blue The blue color value from 0.0 to 1.0.
|
|
||||||
* @param white The white color value from 0.0 to 1.0.
|
|
||||||
* @return The light call for chaining setters.
|
|
||||||
*/
|
|
||||||
LightCall &set_rgbw(float red, float green, float blue, float white);
|
|
||||||
#ifdef USE_JSON
|
|
||||||
LightCall &parse_color_json(JsonObject &root);
|
|
||||||
LightCall &parse_json(JsonObject &root);
|
|
||||||
#endif
|
|
||||||
LightCall &from_light_color_values(const LightColorValues &values);
|
|
||||||
|
|
||||||
void perform();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/// Validate all properties and return the target light color values.
|
|
||||||
LightColorValues validate_();
|
|
||||||
|
|
||||||
bool has_transition_() { return this->transition_length_.has_value(); }
|
|
||||||
bool has_flash_() { return this->flash_length_.has_value(); }
|
|
||||||
bool has_effect_() { return this->effect_.has_value(); }
|
|
||||||
|
|
||||||
LightState *parent_;
|
|
||||||
optional<bool> state_;
|
|
||||||
optional<uint32_t> transition_length_;
|
|
||||||
optional<uint32_t> flash_length_;
|
|
||||||
optional<float> brightness_;
|
|
||||||
optional<float> red_;
|
|
||||||
optional<float> green_;
|
|
||||||
optional<float> blue_;
|
|
||||||
optional<float> white_;
|
|
||||||
optional<float> color_temperature_;
|
|
||||||
optional<uint32_t> effect_;
|
|
||||||
bool publish_{true};
|
|
||||||
bool save_{true};
|
|
||||||
};
|
|
||||||
|
|
||||||
enum LightRestoreMode {
|
enum LightRestoreMode {
|
||||||
LIGHT_RESTORE_DEFAULT_OFF,
|
LIGHT_RESTORE_DEFAULT_OFF,
|
||||||
LIGHT_RESTORE_DEFAULT_ON,
|
LIGHT_RESTORE_DEFAULT_ON,
|
||||||
|
@ -204,14 +58,6 @@ class LightState : public Nameable, public Component {
|
||||||
*/
|
*/
|
||||||
LightColorValues current_values;
|
LightColorValues current_values;
|
||||||
|
|
||||||
/// Deprecated method to access current_values.
|
|
||||||
ESPDEPRECATED("get_current_values() is deprecated, please use .current_values instead.")
|
|
||||||
LightColorValues get_current_values();
|
|
||||||
|
|
||||||
/// Deprecated method to access remote_values.
|
|
||||||
ESPDEPRECATED("get_remote_values() is deprecated, please use .remote_values instead.")
|
|
||||||
LightColorValues get_remote_values();
|
|
||||||
|
|
||||||
/** The remote color values reported to the frontend.
|
/** The remote color values reported to the frontend.
|
||||||
*
|
*
|
||||||
* These are different from the "current" values: For example transitions will
|
* These are different from the "current" values: For example transitions will
|
||||||
|
@ -222,6 +68,14 @@ class LightState : public Nameable, public Component {
|
||||||
*/
|
*/
|
||||||
LightColorValues remote_values;
|
LightColorValues remote_values;
|
||||||
|
|
||||||
|
/// Deprecated method to access current_values.
|
||||||
|
ESPDEPRECATED("get_current_values() is deprecated, please use .current_values instead.")
|
||||||
|
LightColorValues get_current_values();
|
||||||
|
|
||||||
|
/// Deprecated method to access remote_values.
|
||||||
|
ESPDEPRECATED("get_remote_values() is deprecated, please use .remote_values instead.")
|
||||||
|
LightColorValues get_remote_values();
|
||||||
|
|
||||||
/// Publish the currently active state to the frontend.
|
/// Publish the currently active state to the frontend.
|
||||||
void publish_state();
|
void publish_state();
|
||||||
|
|
||||||
|
@ -231,29 +85,22 @@ class LightState : public Nameable, public Component {
|
||||||
/// Return the name of the current effect, or if no effect is active "None".
|
/// Return the name of the current effect, or if no effect is active "None".
|
||||||
std::string get_effect_name();
|
std::string get_effect_name();
|
||||||
|
|
||||||
/** This lets front-end components subscribe to light change events.
|
/**
|
||||||
*
|
* This lets front-end components subscribe to light change events. This callback is called once
|
||||||
* This is different from add_new_current_values_callback in that it only sends events for start
|
* when the remote color values are changed.
|
||||||
* and end values. For example, with transitions it will only send a single callback whereas
|
|
||||||
* the callback passed in add_new_current_values_callback will be called every loop() cycle when
|
|
||||||
* a transition is active
|
|
||||||
*
|
|
||||||
* Note the callback should get the output values through get_remote_values().
|
|
||||||
*
|
*
|
||||||
* @param send_callback The callback.
|
* @param send_callback The callback.
|
||||||
*/
|
*/
|
||||||
void add_new_remote_values_callback(std::function<void()> &&send_callback);
|
void add_new_remote_values_callback(std::function<void()> &&send_callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The callback is called once the state of current_values and remote_values are equal
|
* The callback is called once the state of current_values and remote_values are equal (when the
|
||||||
|
* transition is finished).
|
||||||
*
|
*
|
||||||
* @param send_callback
|
* @param send_callback
|
||||||
*/
|
*/
|
||||||
void add_new_target_state_reached_callback(std::function<void()> &&send_callback);
|
void add_new_target_state_reached_callback(std::function<void()> &&send_callback);
|
||||||
|
|
||||||
/// Return whether the light has any effects that meet the trait requirements.
|
|
||||||
bool supports_effects();
|
|
||||||
|
|
||||||
#ifdef USE_JSON
|
#ifdef USE_JSON
|
||||||
/// Dump the state of this light as JSON.
|
/// Dump the state of this light as JSON.
|
||||||
void dump_json(JsonObject &root);
|
void dump_json(JsonObject &root);
|
||||||
|
@ -265,10 +112,17 @@ class LightState : public Nameable, public Component {
|
||||||
/// Set the gamma correction factor
|
/// Set the gamma correction factor
|
||||||
void set_gamma_correct(float gamma_correct);
|
void set_gamma_correct(float gamma_correct);
|
||||||
float get_gamma_correct() const { return this->gamma_correct_; }
|
float get_gamma_correct() const { return this->gamma_correct_; }
|
||||||
void set_restore_mode(LightRestoreMode restore_mode) { restore_mode_ = restore_mode; }
|
|
||||||
|
|
||||||
|
/// Set the restore mode of this light
|
||||||
|
void set_restore_mode(LightRestoreMode restore_mode);
|
||||||
|
|
||||||
|
/// Return whether the light has any effects that meet the trait requirements.
|
||||||
|
bool supports_effects();
|
||||||
|
|
||||||
|
/// Get all effects for this light state.
|
||||||
const std::vector<LightEffect *> &get_effects() const;
|
const std::vector<LightEffect *> &get_effects() const;
|
||||||
|
|
||||||
|
/// Add effects for this light state.
|
||||||
void add_effects(const std::vector<LightEffect *> &effects);
|
void add_effects(const std::vector<LightEffect *> &effects);
|
||||||
|
|
||||||
void current_values_as_binary(bool *binary);
|
void current_values_as_binary(bool *binary);
|
||||||
|
@ -293,6 +147,8 @@ class LightState : public Nameable, public Component {
|
||||||
|
|
||||||
/// Internal method to start an effect with the given index
|
/// Internal method to start an effect with the given index
|
||||||
void start_effect_(uint32_t effect_index);
|
void start_effect_(uint32_t effect_index);
|
||||||
|
/// Internal method to get the currently active effect
|
||||||
|
LightEffect *get_active_effect_();
|
||||||
/// Internal method to stop the current effect (if one is active).
|
/// Internal method to stop the current effect (if one is active).
|
||||||
void stop_effect_();
|
void stop_effect_();
|
||||||
/// Internal method to start a transition to the target color with the given length.
|
/// Internal method to start a transition to the target color with the given length.
|
||||||
|
@ -307,18 +163,21 @@ class LightState : public Nameable, public Component {
|
||||||
/// Internal method to start a transformer.
|
/// Internal method to start a transformer.
|
||||||
void set_transformer_(std::unique_ptr<LightTransformer> transformer);
|
void set_transformer_(std::unique_ptr<LightTransformer> transformer);
|
||||||
|
|
||||||
LightEffect *get_active_effect_();
|
/// Internal method to save the current remote_values to the preferences
|
||||||
|
void save_remote_values_();
|
||||||
|
|
||||||
/// Object used to store the persisted values of the light.
|
/// Store the output to allow effects to have more access.
|
||||||
ESPPreferenceObject rtc_;
|
LightOutput *output_;
|
||||||
/// Restore mode of the light.
|
|
||||||
LightRestoreMode restore_mode_;
|
|
||||||
/// Default transition length for all transitions in ms.
|
|
||||||
uint32_t default_transition_length_{};
|
|
||||||
/// Value for storing the index of the currently active effect. 0 if no effect is active
|
/// Value for storing the index of the currently active effect. 0 if no effect is active
|
||||||
uint32_t active_effect_index_{};
|
uint32_t active_effect_index_{};
|
||||||
/// The currently active transformer for this light (transition/flash).
|
/// The currently active transformer for this light (transition/flash).
|
||||||
std::unique_ptr<LightTransformer> transformer_{nullptr};
|
std::unique_ptr<LightTransformer> transformer_{nullptr};
|
||||||
|
/// Whether the light value should be written in the next cycle.
|
||||||
|
bool next_write_{true};
|
||||||
|
|
||||||
|
/// Object used to store the persisted values of the light.
|
||||||
|
ESPPreferenceObject rtc_;
|
||||||
|
|
||||||
/** Callback to call when new values for the frontend are available.
|
/** Callback to call when new values for the frontend are available.
|
||||||
*
|
*
|
||||||
* "Remote values" are light color values that are reported to the frontend and have a lower
|
* "Remote values" are light color values that are reported to the frontend and have a lower
|
||||||
|
@ -333,11 +192,12 @@ class LightState : public Nameable, public Component {
|
||||||
*/
|
*/
|
||||||
CallbackManager<void()> target_state_reached_callback_{};
|
CallbackManager<void()> target_state_reached_callback_{};
|
||||||
|
|
||||||
LightOutput *output_; ///< Store the output to allow effects to have more access.
|
/// Default transition length for all transitions in ms.
|
||||||
/// Whether the light value should be written in the next cycle.
|
uint32_t default_transition_length_{};
|
||||||
bool next_write_{true};
|
|
||||||
/// Gamma correction factor for the light.
|
/// Gamma correction factor for the light.
|
||||||
float gamma_correct_{};
|
float gamma_correct_{};
|
||||||
|
/// Restore mode of the light.
|
||||||
|
LightRestoreMode restore_mode_;
|
||||||
/// List of effects for this light.
|
/// List of effects for this light.
|
||||||
std::vector<LightEffect *> effects_;
|
std::vector<LightEffect *> effects_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
//
|
//
|
||||||
// Modified by Otto Winter on 18.05.18
|
// Modified by Otto Winter on 18.05.18
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
// type for nullopt
|
// type for nullopt
|
||||||
|
|
Loading…
Reference in a new issue