Addressable updates

This commit is contained in:
Otto Winter 2019-04-25 10:36:55 +02:00
parent 766f6c045d
commit 595dfe7e24
No known key found for this signature in database
GPG key ID: DB66C0BE6013F97E
13 changed files with 193 additions and 41 deletions

View file

@ -27,11 +27,6 @@ class FastLEDLightOutput : public Component, public light::AddressableLight {
inline int32_t size() const override { return this->num_leds_; }
inline light::ESPColorView operator[](int32_t index) const override {
return light::ESPColorView(&this->leds_[index].r, &this->leds_[index].g, &this->leds_[index].b, nullptr,
&this->effect_data_[index], &this->correction_);
}
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
@ -236,6 +231,11 @@ class FastLEDLightOutput : public Component, public light::AddressableLight {
}
protected:
light::ESPColorView get_view_internal(int32_t index) const override {
return {&this->leds_[index].r, &this->leds_[index].g, &this->leds_[index].b, nullptr,
&this->effect_data_[index], &this->correction_};
}
CLEDController *controller_{nullptr};
CRGB *leds_{nullptr};
uint8_t *effect_data_{nullptr};

View file

@ -4,6 +4,9 @@
namespace esphome {
namespace light {
const ESPColor ESPColor::BLACK = ESPColor(0, 0, 0, 0);
const ESPColor ESPColor::WHITE = ESPColor(255, 255, 255, 255);
ESPColor ESPHSVColor::to_rgb() const {
// based on FastLED's hsv rainbow to rgb
const uint8_t hue = this->hue;
@ -76,9 +79,18 @@ void ESPRangeView::set(const ESPColor &color) {
(*this->parent_)[i] = color;
}
}
ESPColorView ESPRangeView::operator[](int32_t index) const { return (*this->parent_)[index]; }
ESPColorView ESPRangeView::operator[](int32_t index) const {
index = interpret_index(index, this->size());
return (*this->parent_)[index];
}
ESPColorView ESPRangeView::Iterator::operator*() const { return (*this->range_->parent_)[this->i_]; }
int32_t HOT interpret_index(int32_t index, int32_t size) {
if (index < 0)
return size + index;
return index;
}
} // namespace light
} // namespace esphome

View file

@ -147,6 +147,9 @@ struct ESPColor {
ESPColor fade_to_black(uint8_t amnt) { return *this * amnt; }
ESPColor lighten(uint8_t delta) { return *this + delta; }
ESPColor darken(uint8_t delta) { return *this - delta; }
static const ESPColor BLACK;
static const ESPColor WHITE;
};
struct ESPHSVColor {
@ -353,6 +356,8 @@ class ESPColorView : public ESPColorSettable {
class AddressableLight;
int32_t interpret_index(int32_t index, int32_t size);
class ESPRangeView : public ESPColorSettable {
public:
class Iterator {
@ -385,6 +390,10 @@ class ESPRangeView : public ESPColorSettable {
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;
@ -463,9 +472,14 @@ class ESPRangeView : public ESPColorSettable {
class AddressableLight : public LightOutput {
public:
virtual int32_t size() const = 0;
virtual ESPColorView operator[](int32_t index) const = 0;
ESPColorView operator[](int32_t index) const { return this->get_view_internal(interpret_index(index, this->size())); }
ESPColorView get(int32_t index) { return this->get_view_internal(interpret_index(index, this->size())); }
virtual void clear_effect_data() = 0;
ESPRangeView range(int32_t from, int32_t to) { return ESPRangeView(this, from, to); }
ESPRangeView range(int32_t from, int32_t to) {
from = interpret_index(from, this->size());
to = interpret_index(to, this->size());
return ESPRangeView(this, from, to);
}
ESPRangeView all() { return ESPRangeView(this, 0, this->size()); }
ESPRangeView::Iterator begin() { return this->all().begin(); }
ESPRangeView::Iterator end() { return this->all().end(); }
@ -476,7 +490,7 @@ class AddressableLight : public LightOutput {
}
if (amnt > this->size())
amnt = this->size();
this->range(0, this->size() - amnt) = this->range(amnt, this->size());
this->range(0, -amnt) = this->range(amnt, this->size());
}
void shift_right(int32_t amnt) {
if (amnt < 0) {
@ -485,7 +499,7 @@ class AddressableLight : public LightOutput {
}
if (amnt > this->size())
amnt = this->size();
this->range(amnt, this->size()) = this->range(0, this->size() - amnt);
this->range(amnt, this->size()) = this->range(0, -amnt);
}
bool is_effect_active() const { return this->effect_active_; }
void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; }
@ -516,6 +530,7 @@ class AddressableLight : public LightOutput {
protected:
bool should_show_() const { return this->effect_active_ || this->next_show_; }
void mark_shown_() { this->next_show_ = false; }
virtual ESPColorView get_view_internal(int32_t index) const = 0;
bool effect_active_{false};
bool next_show_{true};

View file

@ -113,11 +113,10 @@ class AddressableColorWipeEffect : public AddressableLightEffect {
it.shift_right(1);
const AddressableColorWipeEffectColor color = this->colors_[this->at_color_];
const ESPColor esp_color = ESPColor(color.r, color.g, color.b, color.w);
if (!this->reverse_) {
it[it.size() - 1] = esp_color;
} else {
if (!this->reverse_)
it[-1] = esp_color;
else
it[0] = esp_color;
}
if (++this->leds_added_ >= color.num_leds) {
this->leds_added_ = 0;
this->at_color_ = (this->at_color_ + 1) % this->colors_.size();
@ -145,7 +144,7 @@ class AddressableScanEffect : public AddressableLightEffect {
explicit AddressableScanEffect(const std::string &name) : AddressableLightEffect(name) {}
void set_move_interval(uint32_t move_interval) { this->move_interval_ = move_interval; }
void apply(AddressableLight &it, const ESPColor &current_color) override {
it.all() = ESPColor(0, 0, 0, 0);
it.all() = ESPColor::BLACK;
it[this->at_led_] = current_color;
const uint32_t now = millis();
if (now - this->last_move_ > this->move_interval_) {
@ -190,7 +189,7 @@ class AddressableTwinkleEffect : public AddressableLightEffect {
else
view.set_effect_data(new_pos);
} else {
view = ESPColor(0, 0, 0, 0);
view = ESPColor::BLACK;
}
}
while (random_float() < this->twinkle_probability_) {
@ -220,8 +219,7 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect {
this->last_progress_ = now;
}
uint8_t subsine = ((8 * (now - this->last_progress_)) / this->progress_interval_) & 0b111;
for (auto &&i : it) {
ESPColorView view = i;
for (auto view : it) {
if (view.get_effect_data() != 0) {
const uint8_t x = (view.get_effect_data() >> 3) & 0b11111;
const uint8_t color = view.get_effect_data() & 0b111;
@ -261,9 +259,8 @@ class AddressableFireworksEffect : public AddressableLightEffect {
public:
explicit AddressableFireworksEffect(const std::string &name) : AddressableLightEffect(name) {}
void start() override {
const auto &it = *this->get_addressable_();
for (int i = 0; i < it.size(); i++)
it[i] = ESPColor(0, 0, 0, 0);
auto &it = *this->get_addressable_();
it.all() = ESPColor::BLACK;
}
void apply(AddressableLight &it, const ESPColor &current_color) override {
const uint32_t now = millis();

View file

@ -2,6 +2,7 @@
#include "esphome/core/automation.h"
#include "light_state.h"
#include "addressable_light.h"
namespace esphome {
namespace light {
@ -83,7 +84,7 @@ template<typename... Ts> class DimRelativeAction : public Action<Ts...> {
template<typename... Ts> class LightIsOnCondition : public Condition<Ts...> {
public:
explicit LightIsOnCondition(LightState *state) : state_(state) {}
bool check(Ts... x) override { return this->state_->get_current_values().is_on(); }
bool check(Ts... x) override { return this->state_->current_values.is_on(); }
protected:
LightState *state_;
@ -91,11 +92,41 @@ template<typename... Ts> class LightIsOnCondition : public Condition<Ts...> {
template<typename... Ts> class LightIsOffCondition : public Condition<LightState, Ts...> {
public:
explicit LightIsOffCondition(LightState *state) : state_(state) {}
bool check(Ts... x) override { return !this->state_->get_current_values().is_on(); }
bool check(Ts... x) override { return !this->state_->current_values.is_on(); }
protected:
LightState *state_;
};
template<typename... Ts> class AddressableSet : public Action<Ts...> {
public:
explicit AddressableSet(LightState *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(int32_t, range_from)
TEMPLATABLE_VALUE(int32_t, range_to)
TEMPLATABLE_VALUE(uint8_t, red)
TEMPLATABLE_VALUE(uint8_t, green)
TEMPLATABLE_VALUE(uint8_t, blue)
TEMPLATABLE_VALUE(uint8_t, white)
void play(Ts... x) override {
auto *out = (AddressableLight *) this->parent_->get_output();
int32_t range_from = this->range_from_.value_or(x..., 0);
int32_t range_to = this->range_to_.value_or(x..., out->size());
auto range = out->range(range_from, range_to);
if (this->red_.has_value())
range.set_red(this->red_.value(x...));
if (this->green_.has_value())
range.set_green(this->green_.value(x...));
if (this->blue_.has_value())
range.set_blue(this->blue_.value(x...));
if (this->white_.has_value())
range.set_white(this->white_.value(x...));
}
protected:
LightState *parent_;
};
} // namespace light
} // namespace esphome

View file

@ -1,6 +1,7 @@
#pragma once
#include "light_effect.h"
#include "esphome/core/automation.h"
namespace esphome {
namespace light {
@ -63,6 +64,21 @@ class LambdaLightEffect : public LightEffect {
uint32_t last_run_{0};
};
class AutomationLightEffect : public LightEffect {
public:
AutomationLightEffect(const std::string &name) : LightEffect(name) {}
void stop() override { this->trig_->stop(); }
void apply() override {
if (!this->trig_->is_running()) {
this->trig_->trigger();
}
}
Trigger<> *get_trig() const { return trig_; }
protected:
Trigger<> *trig_{new Trigger<>};
};
struct StrobeLightEffectColor {
LightColorValues color;
uint32_t duration;

View file

@ -1,14 +1,17 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.const import CONF_NAME, CONF_LAMBDA, CONF_UPDATE_INTERVAL, CONF_TRANSITION_LENGTH, \
CONF_COLORS, CONF_STATE, CONF_DURATION, CONF_BRIGHTNESS, CONF_RED, CONF_GREEN, CONF_BLUE, \
CONF_WHITE, CONF_ALPHA, CONF_INTENSITY, CONF_SPEED, CONF_WIDTH, CONF_NUM_LEDS, CONF_RANDOM
CONF_WHITE, CONF_ALPHA, CONF_INTENSITY, CONF_SPEED, CONF_WIDTH, CONF_NUM_LEDS, CONF_RANDOM, \
CONF_THEN
from esphome.util import Registry
from .types import LambdaLightEffect, RandomLightEffect, StrobeLightEffect, \
StrobeLightEffectColor, LightColorValues, AddressableLightRef, AddressableLambdaLightEffect, \
FlickerLightEffect, AddressableRainbowLightEffect, AddressableColorWipeEffect, \
AddressableColorWipeEffectColor, AddressableScanEffect, AddressableTwinkleEffect, \
AddressableRandomTwinkleEffect, AddressableFireworksEffect, AddressableFlickerEffect
AddressableRandomTwinkleEffect, AddressableFireworksEffect, AddressableFlickerEffect, \
AutomationLightEffect
CONF_ADD_LED_INTERVAL = 'add_led_interval'
CONF_REVERSE = 'reverse'
@ -28,8 +31,9 @@ CONF_ADDRESSABLE_TWINKLE = 'addressable_twinkle'
CONF_ADDRESSABLE_RANDOM_TWINKLE = 'addressable_random_twinkle'
CONF_ADDRESSABLE_FIREWORKS = 'addressable_fireworks'
CONF_ADDRESSABLE_FLICKER = 'addressable_flicker'
CONF_AUTOMATION = 'automation'
BINARY_EFFECTS = ['lambda', 'strobe']
BINARY_EFFECTS = ['lambda', 'automation', 'strobe']
MONOCHROMATIC_EFFECTS = BINARY_EFFECTS + ['flicker']
RGB_EFFECTS = MONOCHROMATIC_EFFECTS + ['random']
ADDRESSABLE_EFFECTS = RGB_EFFECTS + [CONF_ADDRESSABLE_LAMBDA, CONF_ADDRESSABLE_RAINBOW,
@ -58,6 +62,15 @@ def lambda_effect_to_code(config, effect_id):
config[CONF_UPDATE_INTERVAL])
@register_effect('automation', AutomationLightEffect, "Automation", {
cv.Required(CONF_THEN): automation.validate_automation(single=True),
})
def automation_effect_to_code(config, effect_id):
var = yield cg.new_Pvariable(effect_id, config[CONF_NAME])
yield automation.build_automation(var.get_trig(), [], config[CONF_THEN])
yield var
@register_effect('random', RandomLightEffect, "Random", {
cv.Optional(CONF_TRANSITION_LENGTH, default='7.5s'): cv.positive_time_period_milliseconds,
cv.Optional(CONF_UPDATE_INTERVAL, default='10s'): cv.positive_time_period_milliseconds,

View file

@ -20,6 +20,7 @@ DimRelativeAction = light_ns.class_('DimRelativeAction', automation.Action)
LightEffect = light_ns.class_('LightEffect')
RandomLightEffect = light_ns.class_('RandomLightEffect', LightEffect)
LambdaLightEffect = light_ns.class_('LambdaLightEffect', LightEffect)
AutomationLightEffect = light_ns.class_('AutomationLightEffect', LightEffect)
StrobeLightEffect = light_ns.class_('StrobeLightEffect', LightEffect)
StrobeLightEffectColor = light_ns.class_('StrobeLightEffectColor', LightEffect)
FlickerLightEffect = light_ns.class_('FlickerLightEffect', LightEffect)

View file

@ -144,29 +144,24 @@ class NeoPixelBusLightOutputBase : public Component, public light::AddressableLi
template<typename T_METHOD, typename T_COLOR_FEATURE = NeoRgbFeature>
class NeoPixelRGBLightOutput : public NeoPixelBusLightOutputBase<T_METHOD, T_COLOR_FEATURE> {
public:
inline light::ESPColorView operator[](int32_t index) const override {
uint8_t *base = this->controller_->Pixels() + 3ULL * index;
return light::ESPColorView(base + this->rgb_offsets_[0], base + this->rgb_offsets_[1], base + this->rgb_offsets_[2],
nullptr, this->effect_data_ + index, &this->correction_);
}
light::LightTraits get_traits() override {
auto traits = light::LightTraits();
traits.set_supports_brightness(true);
traits.set_supports_rgb(true);
return traits;
}
protected:
light::ESPColorView get_view_internal(int32_t index) const override {
uint8_t *base = this->controller_->Pixels() + 3ULL * index;
return light::ESPColorView(base + this->rgb_offsets_[0], base + this->rgb_offsets_[1], base + this->rgb_offsets_[2],
nullptr, this->effect_data_ + index, &this->correction_);
}
};
template<typename T_METHOD, typename T_COLOR_FEATURE = NeoRgbwFeature>
class NeoPixelRGBWLightOutput : public NeoPixelBusLightOutputBase<T_METHOD, T_COLOR_FEATURE> {
public:
inline light::ESPColorView operator[](int32_t index) const override {
uint8_t *base = this->controller_->Pixels() + 4ULL * index;
return light::ESPColorView(base + this->rgb_offsets_[0], base + this->rgb_offsets_[1], base + this->rgb_offsets_[2],
base + this->rgb_offsets_[3], this->effect_data_ + index, &this->correction_);
}
light::LightTraits get_traits() override {
auto traits = light::LightTraits();
traits.set_supports_brightness(true);
@ -174,6 +169,13 @@ class NeoPixelRGBWLightOutput : public NeoPixelBusLightOutputBase<T_METHOD, T_CO
traits.set_supports_rgb_white_value(true);
return traits;
}
protected:
light::ESPColorView get_view_internal(int32_t index) const override {
uint8_t *base = this->controller_->Pixels() + 4ULL * index;
return light::ESPColorView(base + this->rgb_offsets_[0], base + this->rgb_offsets_[1], base + this->rgb_offsets_[2],
base + this->rgb_offsets_[3], this->effect_data_ + index, &this->correction_);
}
};
} // namespace neopixelbus

View file

@ -8,6 +8,7 @@ script_ns = cg.esphome_ns.namespace('script')
Script = script_ns.class_('Script', automation.Trigger.template())
ScriptExecuteAction = script_ns.class_('ScriptExecuteAction', automation.Action)
ScriptStopAction = script_ns.class_('ScriptStopAction', automation.Action)
IsRunningCondition = script_ns.class_('IsRunningCondition', automation.Condition)
CONFIG_SCHEMA = automation.validate_automation({
cv.Required(CONF_ID): cv.declare_id(Script),
@ -34,3 +35,11 @@ def script_execute_action_to_code(config, action_id, template_arg, args):
def script_stop_action_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_condition('script.is_running', IsRunningCondition, automation.maybe_simple_id({
cv.Required(CONF_ID): cv.use_id(Script)
}))
def script_is_running_to_code(config, condition_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(condition_id, template_arg, paren)

View file

@ -7,7 +7,16 @@ namespace script {
class Script : public Trigger<> {
public:
void execute() { this->trigger(); }
void execute() {
bool prev = this->in_stack_;
this->in_stack_ = true;
this->trigger();
this->in_stack_ = prev;
}
bool script_is_running() { return this->in_stack_ || this->is_running(); }
protected:
bool in_stack_{false};
};
template<typename... Ts> class ScriptExecuteAction : public Action<Ts...> {
@ -30,5 +39,15 @@ template<typename... Ts> class ScriptStopAction : public Action<Ts...> {
Script *script_;
};
template<typename... Ts> class IsRunningCondition : public Condition<Ts...> {
public:
explicit IsRunningCondition(Script *parent) : parent_(parent) {}
bool check(Ts... x) override { return this->parent_->script_is_running(); }
protected:
Script *parent_;
};
} // namespace script
} // namespace esphome

View file

@ -52,6 +52,11 @@ template<typename... Ts> class Trigger {
return;
this->automation_parent_->stop();
}
bool is_running() {
if (this->automation_parent_ == nullptr)
return false;
return this->automation_parent_.is_running();
}
protected:
Automation<Ts...> *automation_parent_{nullptr};
@ -81,6 +86,12 @@ template<typename... Ts> class Action {
this->next_->stop_complex();
}
}
virtual bool is_running() { return this->is_running_next(); }
bool is_running_next() {
if (this->next_ == nullptr)
return false;
return this->next_->is_running();
}
void play_next_tuple(const std::tuple<Ts...> &tuple) {
this->play_next_tuple_(tuple, typename gens<sizeof...(Ts)>::type());
@ -121,6 +132,11 @@ template<typename... Ts> class ActionList {
this->actions_begin_->stop_complex();
}
bool empty() const { return this->actions_begin_ == nullptr; }
bool is_running() {
if (this->actions_begin_ == nullptr)
return false;
return this->actions_begin_->is_running();
}
protected:
template<int... S> void play_tuple_(const std::tuple<Ts...> &tuple, seq<S...>) { this->play(std::get<S>(tuple)...); }
@ -140,6 +156,8 @@ template<typename... Ts> class Automation {
void trigger(Ts... x) { this->actions_.play(x...); }
bool is_running() { return this->actions_.is_running(); }
protected:
Trigger<Ts...> *trigger_;
ActionList<Ts...> actions_;

View file

@ -108,16 +108,29 @@ template<typename... Ts> class DelayAction : public Action<Ts...>, public Compon
TEMPLATABLE_VALUE(uint32_t, delay)
void stop() override { this->cancel_timeout(""); }
void stop() override {
this->cancel_timeout("");
this->num_running_ = 0;
}
void play(Ts... x) override { /* ignore - see play_complex */
}
void play_complex(Ts... x) override {
auto f = std::bind(&DelayAction<Ts...>::play_next, this, x...);
auto f = std::bind(&DelayAction<Ts...>::delay_end_, this, x...);
this->num_running_++;
this->set_timeout(this->delay_.value(x...), f);
}
float get_setup_priority() const override { return setup_priority::HARDWARE; }
bool is_running() override { return this->num_running_ > 0 || this->is_running_next(); }
protected:
void delay_end_(Ts... x) {
this->num_running_--;
this->play_next(x...);
}
int num_running_{0};
};
template<typename... Ts> class LambdaAction : public Action<Ts...> {
@ -168,6 +181,8 @@ template<typename... Ts> class IfAction : public Action<Ts...> {
this->else_.stop();
}
bool is_running() override { return this->then_.is_running() || this->else_.is_running() || this->is_running_next(); }
protected:
Condition<Ts...> *condition_;
ActionList<Ts...> then_;
@ -210,6 +225,8 @@ template<typename... Ts> class WhileAction : public Action<Ts...> {
void stop() override { this->then_.stop(); }
bool is_running() override { return this->then_.is_running() || this->is_running_next(); }
protected:
Condition<Ts...> *condition_;
ActionList<Ts...> then_;
@ -251,6 +268,8 @@ template<typename... Ts> class WaitUntilAction : public Action<Ts...>, public Co
float get_setup_priority() const override { return setup_priority::DATA; }
bool is_running() override { return this->triggered_ || this->is_running_next(); }
protected:
Condition<Ts...> *condition_;
bool triggered_{false};