diff --git a/esphome/components/fastled_base/fastled_light.h b/esphome/components/fastled_base/fastled_light.h index 24a5918506..55a48fcb3e 100644 --- a/esphome/components/fastled_base/fastled_light.h +++ b/esphome/components/fastled_base/fastled_light.h @@ -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}; diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index f794b4b17f..640d8112b1 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -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 diff --git a/esphome/components/light/addressable_light.h b/esphome/components/light/addressable_light.h index 904f507e1d..993b430d3c 100644 --- a/esphome/components/light/addressable_light.h +++ b/esphome/components/light/addressable_light.h @@ -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}; diff --git a/esphome/components/light/addressable_light_effect.h b/esphome/components/light/addressable_light_effect.h index ec95e714e1..545af6a0f2 100644 --- a/esphome/components/light/addressable_light_effect.h +++ b/esphome/components/light/addressable_light_effect.h @@ -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 ¤t_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 ¤t_color) override { const uint32_t now = millis(); diff --git a/esphome/components/light/automation.h b/esphome/components/light/automation.h index 4d6e1c094a..f907c70963 100644 --- a/esphome/components/light/automation.h +++ b/esphome/components/light/automation.h @@ -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 class DimRelativeAction : public Action { template class LightIsOnCondition : public Condition { 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 class LightIsOnCondition : public Condition { template class LightIsOffCondition : public Condition { 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 class AddressableSet : public Action { + 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 diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index bcbb6d129a..4b33b77073 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -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; diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index b77a355c5d..93dea2628d 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -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, diff --git a/esphome/components/light/types.py b/esphome/components/light/types.py index 90ff877f26..c9f638a4a4 100644 --- a/esphome/components/light/types.py +++ b/esphome/components/light/types.py @@ -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) diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 689ed9e71b..86ae21ddd0 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -144,29 +144,24 @@ class NeoPixelBusLightOutputBase : public Component, public light::AddressableLi template class NeoPixelRGBLightOutput : public NeoPixelBusLightOutputBase { 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 class NeoPixelRGBWLightOutput : public NeoPixelBusLightOutputBase { 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 NeoPixelBusLightOutputBasecontroller_->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 diff --git a/esphome/components/script/__init__.py b/esphome/components/script/__init__.py index e453009308..962e2d56ca 100644 --- a/esphome/components/script/__init__.py +++ b/esphome/components/script/__init__.py @@ -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) diff --git a/esphome/components/script/script.h b/esphome/components/script/script.h index 9452aa0d2d..f937b9d637 100644 --- a/esphome/components/script/script.h +++ b/esphome/components/script/script.h @@ -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 class ScriptExecuteAction : public Action { @@ -30,5 +39,15 @@ template class ScriptStopAction : public Action { Script *script_; }; +template class IsRunningCondition : public Condition { + 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 diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 8f8e86a806..7f79ab3ccd 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -52,6 +52,11 @@ template 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 *automation_parent_{nullptr}; @@ -81,6 +86,12 @@ template 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 &tuple) { this->play_next_tuple_(tuple, typename gens::type()); @@ -121,6 +132,11 @@ template 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 void play_tuple_(const std::tuple &tuple, seq) { this->play(std::get(tuple)...); } @@ -140,6 +156,8 @@ template class Automation { void trigger(Ts... x) { this->actions_.play(x...); } + bool is_running() { return this->actions_.is_running(); } + protected: Trigger *trigger_; ActionList actions_; diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 67834430c8..ad50a3921f 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -108,16 +108,29 @@ template class DelayAction : public Action, 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::play_next, this, x...); + auto f = std::bind(&DelayAction::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 class LambdaAction : public Action { @@ -168,6 +181,8 @@ template class IfAction : public Action { this->else_.stop(); } + bool is_running() override { return this->then_.is_running() || this->else_.is_running() || this->is_running_next(); } + protected: Condition *condition_; ActionList then_; @@ -210,6 +225,8 @@ template class WhileAction : public Action { void stop() override { this->then_.stop(); } + bool is_running() override { return this->then_.is_running() || this->is_running_next(); } + protected: Condition *condition_; ActionList then_; @@ -251,6 +268,8 @@ template class WaitUntilAction : public Action, 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 *condition_; bool triggered_{false};