mirror of
https://github.com/Threnklyn/esphome-dev.git
synced 2026-05-24 06:38:29 +02:00
6682c43dfa
## Description: Move esphome-core codebase into esphome (and a bunch of other refactors). See https://github.com/esphome/feature-requests/issues/97 Yes this is a shit ton of work and no there's no way to automate it :( But it will be worth it 👍 Progress: - Core support (file copy etc): 80% - Base Abstractions (light, switch): ~50% - Integrations: ~10% - Working? Yes, (but only with ported components). Other refactors: - Moves all codegen related stuff into a single class: `esphome.codegen` (imported as `cg`) - Rework coroutine syntax - Move from `component/platform.py` to `domain/component.py` structure as with HA - Move all defaults out of C++ and into config validation. - Remove `make_...` helpers from Application class. Reason: Merge conflicts with every single new integration. - Pointer Variables are stored globally instead of locally in setup(). Reason: stack size limit. Future work: - Rework const.py - Move all `CONF_...` into a conf class (usage `conf.UPDATE_INTERVAL` vs `CONF_UPDATE_INTERVAL`). Reason: Less convoluted import block - Enable loading from `custom_components` folder. **Related issue (if applicable):** https://github.com/esphome/feature-requests/issues/97 **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here> ## Checklist: - [ ] The code change is tested and works locally. - [ ] Tests have been added to verify that the new code works (under `tests/` folder). If user exposed functionality or configuration variables are added/changed: - [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs).
450 lines
17 KiB
C++
450 lines
17 KiB
C++
#pragma once
|
|
|
|
#include "esphome/core/component.h"
|
|
#include "light_output.h"
|
|
#include "light_state.h"
|
|
|
|
namespace esphome {
|
|
namespace light {
|
|
|
|
inline static uint8_t esp_scale8(uint8_t i, uint8_t scale) { return (uint16_t(i) * (1 + uint16_t(scale))) / 256; }
|
|
|
|
struct ESPColor {
|
|
union {
|
|
struct {
|
|
union {
|
|
uint8_t r;
|
|
uint8_t red;
|
|
};
|
|
union {
|
|
uint8_t g;
|
|
uint8_t green;
|
|
};
|
|
union {
|
|
uint8_t b;
|
|
uint8_t blue;
|
|
};
|
|
union {
|
|
uint8_t w;
|
|
uint8_t white;
|
|
};
|
|
};
|
|
uint8_t raw[4];
|
|
};
|
|
inline ESPColor() ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT
|
|
inline ESPColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) ALWAYS_INLINE : r(red),
|
|
g(green),
|
|
b(blue),
|
|
w(white) {}
|
|
inline ESPColor(uint8_t red, uint8_t green, uint8_t blue) ALWAYS_INLINE : r(red), g(green), b(blue), w(0) {}
|
|
inline ESPColor(uint32_t colorcode) ALWAYS_INLINE : r((colorcode >> 16) & 0xFF),
|
|
g((colorcode >> 8) & 0xFF),
|
|
b((colorcode >> 0) & 0xFF),
|
|
w((colorcode >> 24) & 0xFF) {}
|
|
inline ESPColor(const ESPColor &rhs) ALWAYS_INLINE {
|
|
this->r = rhs.r;
|
|
this->g = rhs.g;
|
|
this->b = rhs.b;
|
|
this->w = rhs.w;
|
|
}
|
|
inline bool is_on() ALWAYS_INLINE { return this->r != 0 || this->g != 0 || this->b != 0 || this->w != 0; }
|
|
inline ESPColor &operator=(const ESPColor &rhs) ALWAYS_INLINE {
|
|
this->r = rhs.r;
|
|
this->g = rhs.g;
|
|
this->b = rhs.b;
|
|
this->w = rhs.w;
|
|
return *this;
|
|
}
|
|
inline ESPColor &operator=(uint32_t colorcode) ALWAYS_INLINE {
|
|
this->w = (colorcode >> 24) & 0xFF;
|
|
this->r = (colorcode >> 16) & 0xFF;
|
|
this->g = (colorcode >> 8) & 0xFF;
|
|
this->b = (colorcode >> 0) & 0xFF;
|
|
return *this;
|
|
}
|
|
inline uint8_t &operator[](uint8_t x) ALWAYS_INLINE { return this->raw[x]; }
|
|
inline ESPColor operator*(uint8_t scale) const ALWAYS_INLINE {
|
|
return ESPColor(esp_scale8(this->red, scale), esp_scale8(this->green, scale), esp_scale8(this->blue, scale),
|
|
esp_scale8(this->white, scale));
|
|
}
|
|
inline ESPColor &operator*=(uint8_t scale) ALWAYS_INLINE {
|
|
this->red = esp_scale8(this->red, scale);
|
|
this->green = esp_scale8(this->green, scale);
|
|
this->blue = esp_scale8(this->blue, scale);
|
|
this->white = esp_scale8(this->white, scale);
|
|
return *this;
|
|
}
|
|
inline ESPColor operator*(const ESPColor &scale) const ALWAYS_INLINE {
|
|
return ESPColor(esp_scale8(this->red, scale.red), esp_scale8(this->green, scale.green),
|
|
esp_scale8(this->blue, scale.blue), esp_scale8(this->white, scale.white));
|
|
}
|
|
inline ESPColor &operator*=(const ESPColor &scale) ALWAYS_INLINE {
|
|
this->red = esp_scale8(this->red, scale.red);
|
|
this->green = esp_scale8(this->green, scale.green);
|
|
this->blue = esp_scale8(this->blue, scale.blue);
|
|
this->white = esp_scale8(this->white, scale.white);
|
|
return *this;
|
|
}
|
|
inline ESPColor operator+(const ESPColor &add) const ALWAYS_INLINE {
|
|
ESPColor ret;
|
|
if (uint8_t(add.r + this->r) < this->r)
|
|
ret.r = 255;
|
|
else
|
|
ret.r = this->r + add.r;
|
|
if (uint8_t(add.g + this->g) < this->g)
|
|
ret.g = 255;
|
|
else
|
|
ret.g = this->g + add.g;
|
|
if (uint8_t(add.b + this->b) < this->b)
|
|
ret.b = 255;
|
|
else
|
|
ret.b = this->b + add.b;
|
|
if (uint8_t(add.w + this->w) < this->w)
|
|
ret.w = 255;
|
|
else
|
|
ret.w = this->w + add.w;
|
|
return ret;
|
|
}
|
|
inline ESPColor &operator+=(const ESPColor &add) ALWAYS_INLINE { return *this = (*this) + add; }
|
|
inline ESPColor operator+(uint8_t add) const ALWAYS_INLINE { return (*this) + ESPColor(add, add, add, add); }
|
|
inline ESPColor &operator+=(uint8_t add) ALWAYS_INLINE { return *this = (*this) + add; }
|
|
inline ESPColor operator-(const ESPColor &subtract) const ALWAYS_INLINE {
|
|
ESPColor ret;
|
|
if (subtract.r > this->r)
|
|
ret.r = 0;
|
|
else
|
|
ret.r = this->r - subtract.r;
|
|
if (subtract.g > this->g)
|
|
ret.g = 0;
|
|
else
|
|
ret.g = this->g - subtract.g;
|
|
if (subtract.b > this->b)
|
|
ret.b = 0;
|
|
else
|
|
ret.b = this->b - subtract.b;
|
|
if (subtract.w > this->w)
|
|
ret.w = 0;
|
|
else
|
|
ret.w = this->w - subtract.w;
|
|
return ret;
|
|
}
|
|
inline ESPColor &operator-=(const ESPColor &subtract) ALWAYS_INLINE { return *this = (*this) - subtract; }
|
|
inline ESPColor operator-(uint8_t subtract) const ALWAYS_INLINE {
|
|
return (*this) - ESPColor(subtract, subtract, subtract, subtract);
|
|
}
|
|
inline ESPColor &operator-=(uint8_t subtract) ALWAYS_INLINE { return *this = (*this) - subtract; }
|
|
static ESPColor random_color() {
|
|
uint32_t rand = random_uint32();
|
|
uint8_t w = rand >> 24;
|
|
uint8_t r = rand >> 16;
|
|
uint8_t g = rand >> 8;
|
|
uint8_t b = rand >> 0;
|
|
const uint16_t max_rgb = std::max(r, std::max(g, b));
|
|
return ESPColor(uint8_t((uint16_t(r) * 255U / max_rgb)), uint8_t((uint16_t(g) * 255U / max_rgb)),
|
|
uint8_t((uint16_t(b) * 255U / max_rgb)), w);
|
|
}
|
|
};
|
|
|
|
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) {}
|
|
ESPColor 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);
|
|
ESPColor 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;
|
|
}
|
|
};
|
|
|
|
class ESPColorCorrection {
|
|
public:
|
|
ESPColorCorrection() : max_brightness_(255, 255, 255, 255) {}
|
|
void set_max_brightness(const ESPColor &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) {
|
|
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;
|
|
}
|
|
}
|
|
inline ESPColor color_correct(ESPColor color) const ALWAYS_INLINE {
|
|
// corrected = (uncorrected * max_brightness * local_brightness) ^ gamma
|
|
return ESPColor(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 ESPColor color_uncorrect(ESPColor color) const ALWAYS_INLINE {
|
|
// uncorrected = corrected^(1/gamma) / (max_brightness * local_brightness)
|
|
return ESPColor(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];
|
|
ESPColor max_brightness_;
|
|
uint8_t local_brightness_{255};
|
|
};
|
|
|
|
class ESPColorView {
|
|
public:
|
|
inline ESPColorView(uint8_t *red, uint8_t *green, uint8_t *blue, uint8_t *white, uint8_t *effect_data,
|
|
const ESPColorCorrection *color_correction) ALWAYS_INLINE : red_(red),
|
|
green_(green),
|
|
blue_(blue),
|
|
white_(white),
|
|
effect_data_(effect_data),
|
|
color_correction_(color_correction) {}
|
|
inline const ESPColorView &operator=(const ESPColor &rhs) const ALWAYS_INLINE {
|
|
this->set(rhs);
|
|
return *this;
|
|
}
|
|
inline const ESPColorView &operator=(const ESPHSVColor &rhs) const ALWAYS_INLINE {
|
|
this->set(rhs);
|
|
return *this;
|
|
}
|
|
inline void set(const ESPColor &color) const ALWAYS_INLINE { this->set_rgbw(color.r, color.g, color.b, color.w); }
|
|
inline void set(const ESPHSVColor &color) const ALWAYS_INLINE {
|
|
ESPColor rgb = color.to_rgb();
|
|
this->set_rgb(rgb.r, rgb.g, rgb.b);
|
|
}
|
|
inline void set_red(uint8_t red) const ALWAYS_INLINE {
|
|
*this->red_ = this->color_correction_->color_correct_red(red);
|
|
}
|
|
inline void set_green(uint8_t green) const ALWAYS_INLINE {
|
|
*this->green_ = this->color_correction_->color_correct_green(green);
|
|
}
|
|
inline void set_blue(uint8_t blue) const ALWAYS_INLINE {
|
|
*this->blue_ = this->color_correction_->color_correct_blue(blue);
|
|
}
|
|
inline void set_white(uint8_t white) const ALWAYS_INLINE {
|
|
if (this->white_ == nullptr)
|
|
return;
|
|
*this->white_ = this->color_correction_->color_correct_white(white);
|
|
}
|
|
inline void set_rgb(uint8_t red, uint8_t green, uint8_t blue) const ALWAYS_INLINE {
|
|
this->set_red(red);
|
|
this->set_green(green);
|
|
this->set_blue(blue);
|
|
}
|
|
inline void set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) const ALWAYS_INLINE {
|
|
this->set_rgb(red, green, blue);
|
|
this->set_white(white);
|
|
}
|
|
inline void set_effect_data(uint8_t effect_data) const ALWAYS_INLINE {
|
|
if (this->effect_data_ == nullptr)
|
|
return;
|
|
*this->effect_data_ = effect_data;
|
|
}
|
|
inline ESPColor get() const ALWAYS_INLINE {
|
|
return ESPColor(this->get_red(), this->get_green(), this->get_blue(), this->get_white());
|
|
}
|
|
inline uint8_t get_red() const ALWAYS_INLINE { return this->color_correction_->color_uncorrect_red(*this->red_); }
|
|
inline uint8_t get_green() const ALWAYS_INLINE {
|
|
return this->color_correction_->color_uncorrect_green(*this->green_);
|
|
}
|
|
inline uint8_t get_blue() const ALWAYS_INLINE { return this->color_correction_->color_uncorrect_blue(*this->blue_); }
|
|
inline uint8_t get_white() const ALWAYS_INLINE {
|
|
if (this->white_ == nullptr)
|
|
return 0;
|
|
return this->color_correction_->color_uncorrect_white(*this->white_);
|
|
}
|
|
inline uint8_t get_effect_data() const ALWAYS_INLINE {
|
|
if (this->effect_data_ == nullptr)
|
|
return 0;
|
|
return *this->effect_data_;
|
|
}
|
|
inline void raw_set_color_correction(const ESPColorCorrection *color_correction) ALWAYS_INLINE {
|
|
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 : public LightOutput {
|
|
public:
|
|
virtual int32_t size() const = 0;
|
|
virtual ESPColorView operator[](int32_t index) const = 0;
|
|
virtual void clear_effect_data() = 0;
|
|
bool is_effect_active() const { return this->effect_active_; }
|
|
void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; }
|
|
void write_state(LightState *state) override {
|
|
auto val = state->current_values;
|
|
auto max_brightness = static_cast<uint8_t>(roundf(val.get_brightness() * val.get_state() * 255.0f));
|
|
this->correction_.set_local_brightness(max_brightness);
|
|
|
|
if (this->is_effect_active())
|
|
return;
|
|
|
|
// don't use LightState helper, gamma correction+brightness is handled by ESPColorView
|
|
ESPColor color = ESPColor(uint8_t(roundf(val.get_red() * 255.0f)), uint8_t(roundf(val.get_green() * 255.0f)),
|
|
uint8_t(roundf(val.get_blue() * 255.0f)),
|
|
// white is not affected by brightness; so manually scale by state
|
|
uint8_t(roundf(val.get_white() * val.get_state() * 255.0f)));
|
|
|
|
for (int i = 0; i < this->size(); i++) {
|
|
(*this)[i] = color;
|
|
}
|
|
|
|
this->schedule_show();
|
|
}
|
|
void set_correction(float red, float green, float blue, float white = 1.0f) {
|
|
this->correction_.set_max_brightness(ESPColor(uint8_t(roundf(red * 255.0f)), uint8_t(roundf(green * 255.0f)),
|
|
uint8_t(roundf(blue * 255.0f)), uint8_t(roundf(white * 255.0f))));
|
|
}
|
|
void setup_state(LightState *state) override { this->correction_.calculate_gamma_table(state->get_gamma_correct()); }
|
|
void schedule_show() { this->next_show_ = true; }
|
|
|
|
protected:
|
|
bool should_show_() const { return this->effect_active_ || this->next_show_; }
|
|
void mark_shown_() { this->next_show_ = false; }
|
|
|
|
bool effect_active_{false};
|
|
bool next_show_{true};
|
|
ESPColorCorrection correction_{};
|
|
};
|
|
|
|
} // namespace light
|
|
} // namespace esphome
|